Code Available on GitHub

Part 7 Specific Code on GitHub

If you search Google for Dynamics NAV item pictures you will find that there are a few examples out there for easily associating a picture with an item. However, there is not much on displaying multiple images for an item. This is a challenge I had to solve for my employer. Many items had anywhere from a single image to 10+ images.

I decided to come up with a .Net add-in control that was able to display multiple images for any given item and on any page (e.g. the item list, item card and beyond). I have decided to create a tutorial to show you how to create such a control. This will be quite lengthy, so I will split it up into smaller parts for you to digest more easily. The tutorial will be split into the following 7 part series:

  1. Part 1 – Introduction
  2. Part 2 – Creating the .Net image control model
  3. Part 3 – Creating the .Net image control viewmodel
  4. Part 4 – Creating the .Net image control view
  5. Part 5 – Creating the .Net add-in control wrapper class
  6. Part 6 – Creating the Dynamics NAV Item Images factbox page
  7. Part 7 – Hooking up the item Images factbox page (this post)

Part 7 – Hooking up the Item Images Factbox Page

Initial Preparation Notes

We have finally arrived at the final part of the tutorial! This time we will be making a few minor adjustments from the previous parts (a couple things didn’t quite work out exactly how I wanted, so I had to make some modifications) and then hooking the Factbox page up to the Item Card. If you haven’t already, I suggest you grab the source code from GitHub, so you can follow along. This will also allow me to skip going through the folder structure and project setup and focus on the code itself. Any other preparation steps and notes can be found in Part 2 of the tutorial.

Minor Modifications to Add-In Control

Making Repairs
Michal Jarmoluk
Before we can hook up the Factbox page housing the MultiImageAddinControl, we need to make a few adjustments.

ImageRequestEventArgs

The first change required is to the ImageRequestEventArgs class. We need to make sure this class is marked as Serializable because it is passed from .Net to NAV. The easiest way to do this is to add the Serializable attribute to the class itself:

/// <summary>
/// This class represents details for requested item images.
/// </summary>
[Serializable]
public class ImageRequestEventArgs : EventArgs
{
// remaining code elided
}
PageableImageControlViewModel

I discovered a bug when the add-in control would initialize the pageable images (called when we first load up an item and its images), but the control did not have focus. It would not properly update the previous and next buttons until you put focus on the add-in control. I found an explanation on MSDN about the CommandManager InvalidateRequerySuggested method:

The CommandManager only pays attention to certain conditions in determining when the command target has changed, such as change in keyboard focus. In situations where the CommandManager does not sufficiently determine a change in conditions that cause a command to not be able to execute, InvalidateRequerySuggested can be called to force the CommandManager to raise the RequerySuggested event.

So I updated the InitPageableImages method found in the PageableImageControlViewModel to call CommandManager.InvalidateRequerySuggested and the problem was corrected:

public void InitPageableImages()
{
CanPageNext = _images.Count > PageSize;
CanPagePrevious = false;
CurrentPage = 0;
PageableImages.Reset(GetImages());
OnPropertyChanged("PageableImages");
CommandManager.InvalidateRequerySuggested(); // ADDED THIS LINE
}
MultiImageAddinHostControl

This is where the majority of changes were made. The first change was a cosmetic change. I didn’t like the way the ElementHost looked (a WinForms control that hosts the WPF control), so I wrapped it in a Panel and gave it a border. It’s still not as nice as I’d like, but for now it will do:

public class MultiImageAddinHostControl : WinFormsControlAddInBase
{
private Panel _panel; // ADDED PANEL
private ElementHost _host;
private MultiImageView _view;
private PageableImageControlViewModel _vm;
private IImageRepository _imageRepository;
/// <summary>
/// Creates the Windows Forms control for the control add-in.
/// </summary>
/// <returns>Returns the Windows Forms control.</returns>
protected override Control CreateControl()
{
_panel = new Panel
{
// ADDED BORDER
BorderStyle = BorderStyle.FixedSingle
};
_host = new ElementHost
{
Dock = DockStyle.Fill
};
// PANEL HOSTS THE ELEMENTHOST
_panel.Controls.Add(_host);
_vm = new PageableImageControlViewModel
{
PageSize = 3
};
_view = new MultiImageView
{
DataContext = _vm
};
_view.InitializeComponent();
_host.Child = _view;
// SET THE ELEMENTHOST SIZE TO MINWIDTH/MINHEIGHT
_host.Size = new Size((int) _view.MinWidth, (int) _view.MinHeight);
// SET THE PANEL SIZE TO SLIGHTLY LARGER TO GIVE A BIT OF PADDING
_panel.Size = new Size((int) _view.MinWidth + 5, (int) _view.MinHeight + 5);
return _panel;
}

Continuing with some cosmetic changes, I also had to override the AllowCaptionControl property of the WinFormsControlAddInBase class to ensure we didn’t show a caption. This prevents an ugly caption from appearing on the top left of the MultiImageAddinControl (which also pushes it to the right… just trust me when I say it is ugly):

/// <summary>
/// Gets a value indicating whether to allow caption for the control.
/// </summary>
/// <value>
/// <c>true</c> if caption for control is allowed; otherwise, <c>false</c>.
/// </value>
public override bool AllowCaptionControl
{
get { return false; }
}

The final change in this class was not cosmetic and I was disappointed that this had to be done. My original plan was to allow NAV to build our list of images and directly assign them to the image repository (which is created in NAV so it has complete control over which implementation of the IImageRepository interface is being used). Unfortunately, taking something like a file path or URL and converting it to the source of a System.Windows.Control.Image requires the use of a dependency object, which I was not able to get working directly in NAV. I’m sure there is a way, but for now the following approach was good enough:

/// <summary>
/// Sets the image paths.
/// </summary>
/// <param name="imagePaths">The image paths.</param>
[ApplicationVisible]
public void SetImagePaths(IEnumerable<string> imagePaths)
{
var images = new List<System.Windows.Controls.Image>();
var converter = new ImageSourceConverter();
foreach (var path in imagePaths)
{
var img = new System.Windows.Controls.Image();
img.SetValue(System.Windows.Controls.Image.SourceProperty, converter.ConvertFromString(path));
images.Add(img);
}
_imageRepository.SetImages(images);
}

Basically, I have created a method that takes a list strings (any type of path to an image; a file path, url etc.) and converts the strings to the source property for a corresponding list of images. This list is then passed to the repository that was created by NAV and passed in earlier via the SetImageRepository method.

One quick note. I fully qualified the name System.Windows.Controls.Image, because there was ambiguity in this class between that Image class and the System.Drawing.Image (needed for the Size property for the ElementHost and Panel classes in the CreateControl method).

Creating Supporting Image Tables

Support Tables
Gerd Altmann
Now that we have updated our .Net control, we can move back to NAV (if you are following along manually, you’ll now want to rebuild the add-in control and move it to the necessary folders; this can be done the same way as covered in Part 6).

We’ll start by creating two tables. This is not necessarily needed, as it depends on how you want to store your images. I have decided to go with an Image table and Item Image table. The Image table is designed to hold a unique id number and location (could be a url or file path etc., I will be using file names). The Item Image table is to support a many-to-many relationship between the Item table and the Image table. Each item can have multiple images and each image may be used by more than one item.

Image Table

This is the definition of the Image table:

I have added the following data to the Image table (I will be using 8 sample images for the demo):

Item Image Table

This is the definition of the Item Image table:

The primary key on the Item Image table is all three fields. The first two are required for uniqueness, but I included the Sequence to ensure the sorting was what I expect. Here is the sample data I am using for this demo:

I am using the first four items in the Cronus database (1000, 1001, 1100 and 1100) and I have set a variable number of images for each item. Also notice that I am using image 004 for multiple items (hence the many-to-many relationship and the need for this table).

Creating a Query Object

Back in Part 2, you may have noticed that when we created the IImageRepository interface, we creating an implementation called the NavQueryObjectImageRepository. This was a bit of a foreshadowing that I planned to create a NAV query object that would be responsible for finding the images for a given item and sending them to the add-in control.

Item Image Query

Item Image Query

The query is straight forward. You have the Item Image table with a join to the Image table via Image.”No.” = “Item Image”.”No.”.

Updating the MultiImage Factbox

Fact BoxWe are finally ready to update the factbox page we created in Part 6. If you recreated the .Net add-in control, you must remove it from the page and re-add it in order to pick up the changes that were made. This rebinding is required to refresh the page’s event handlers and any new [ApplicationVisible] properties or methods. If you grabbed the source code from GitHub, Page 50000 is good to go as-is.

Global Variables

The following global variables will be needed:

The reasoning for these will be apparent when you see the code below.

Setting The Image Repository

In the OnAfterGetRecord function, we are going to call SetImageRepository:

OnAfterGetRecord

SetImageRepository is responsible for creating any IImageRepository implementation (in our case it will be a NavQueryObjectImageRepository) and passing it to the add-in control:

SetImageRepository

This function checks to ensure we have actually changed our item number to save unnecessary reloading if OnAfterGetRecord is called more than once. If it has changed and the number is not blank, we create a new NavQueryObjectImageRepository and pass in the item number. Then we call the SetImageRepository method found in our add-in control (the host control) and pass in the repository.

What this does on the .Net side is actually triggers a call from the PageableImageControlViewModel.InitPageableImages method, which fires off a request for images. This is picked up from NAV.

Here is the OnRequestImages function’s local variables:

And the code for the function (note that it takes our ImageRequestEventArgs as a parameter):

OnRequestImages
Click to enlarge

This function first creates a list of strings (as for the GenericFactory, I came up with this idea a while ago and then found a better implementation that Vjeko did based off of this post… although it may have been one of his NAV Tech Days demos where he posted the implementation that I’m using for the most part… his explanation sums it up well)). It then takes our Item Image Query object, sets a filter on the item number passed in from the ImageRequestEventArgs and loops through the results, adding the image locations to the list. The query is then closed and the image paths are sent to the add-in control.

You may have noticed the BasePath being combined with the image location. It is a global variable we had created earlier. I didn’t show this, but it is set in the OnInit trigger of the page to a hardcoded value where I am storing an Image folder that has all of the images. This will vary with where you decide to put your images. The Images folder for this demo is in the same folder I put the add-in assembly (the .dll etc.) for the RTC.

OnInit_BasePath
Click to enlarge

One final note about this page is that it needs it’s SourceTable property set to Item.

Putting the MultiImage Factbox on the Item Card

The final step (in NAV at least) is to hook the MultiImage Factbox page into another page. I have chosen the Item Card, but you can easily hook this into the Item List or anywhere else that references the Item table. To hook up the MultiImage Factbox, you only need to do two things:

  1. Add the page as a Page Part.
  2. Link the two pages together via the Item.”No.” field.
Item Card
Click to enlarge

Put the Images In Place

The very last step is to put the images wherever they need to be. In my case, they are in the same location as the BasePath that I mentioned above. I have included the sample images in the GitHub download.

See It In Action

Here are a few screenshots to see it in action on the item card. You can just use Ctrl+Pgdn or Ctrl+Pgup to cycle through the items while on the Item Card, rather than having to close and reopen for each item. The images will load as you cycle through.

Item Card In Action 1
Click to enlarge
Item Card In Action 2
Click to enlarge
Item Card In Action 3
Click to enlarge

And that’s it! We have finally concluded this 7 part tutorial. Thanks for following along. I hope you have enjoyed it and perhaps it will inspire you to come up with some great add-in ideas.

17 thought on “Dynamics NAV .Net Multi-Image Add-In Control Part 7 – Hooking Up the Item Images Factbox Page”
  1. Hello Jason,
    There nice and there cool tutorial. I’m working right now on the drag and drop files/images feature in Dynamics Nav.
    It is very cool that you had time to post this tutorial.
    Thanks!!!

  2. Thank youvery much for this Guide. It works great on NAV2013. I am trying to get it to work on NAV2015 and NAV2016 but I am unable to make it work.
    I googled if anything changed but no success so far. The Page compiles except for the “CurrPage” parts (CurrPage.MultiImageControl.SetImageRepository(ImageRepository);)
    The error states that he couldn’t load the type..

    Any insights from you would be greatly appreciated.
    I report back when I found the solution 🙂

    1. Hi Bart,

      We are actually in the middle of upgrading from NAV 2013 R2 to 2016 (just finished merging all of the pages yesterday actually, but haven’t gotten to compiling yet). One of the issues we have to address is our .Net add-in controls. Eventually we’ll be replacing them with JavaScript versions so they are compatible with the web client, but we may need to keep the .Net versions around for the RTC in the meantime. I’ll be looking into getting everything to compile this week or next, so I’ll have to address the issue you’re running into. I’ll let you know when I figure it out.

      1. Hey Jason,

        Thank you for your fast reply! I managed to find the solution in the mean time.
        When you upgrade an add-in make sure you rebuild your project with the Microsoft.Dynamics.Framework.UI.Extensibility.dll of the target Navision.
        You probably knew this but it might help another consultant 🙂

        Thanks again!

        1. Ah yes, that’s a definite gotcha you need to watch out for. I’m glad that’s all it was. I was worried I might have to add a couple of extra days onto our upgrade process this time around to figure this one out.

    1. Hi July,

      We have the control working as-is for the 2016 RTC (you may have to replace the Microsoft.Dynamics.Framework.UI.Extensibility.dll in the lib folder with the one found in C:\Program Files (x86)\Microsoft Dynamics NAV\90\RoleTailored Client\ (or wherever you have your installation… the important part is using the one in the 90 subfolder, since version 9.0 is NAV 2016). With that being said, we are currently in the process of converting our .Net controls over to JavaScript (using TypeScript) so that they are compatible with the NAV Web Client. Once we get that finished, I’ll likely do a new version of this tutorial.

    1. You could develop the .Net side without an issue, but I think you would need a developer license for the NAV side. I’m not completely sure to be honest, since I’ve always had access to a develop license. I do know that we can compile objects from the development environment at our customer locations if we ever need to do a quick fix program that can’t wait for an official hotfix/patch. This is possible with the customer license in the database… so maybe it’s possible without a developer license. I’m not sure though, so it’s probably best to confirm with someone who knows the licensing rules etc.

  3. Never mind , got it figured out.
    Just had to convert the base64 string i was passing to the control to ImageSource in the HostControler and set the MainImageSource to BitmapImage from Image.

    Thank you for sharing the control!

  4. Hi Jason,

    This is exactly what I am looking for to display within the web client, did you get any further with your Javascript version?

    Thanks in advance

    1. Hi Tim,

      Unfortunately, I haven’t worked with Dynamics NAV since I moved to another company in early 2017.

Leave a Reply to Jason Down Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.