TutorialCode 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

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

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:

EnabledField No.Field NameData TypeLengthDescription
True1No.Code20Unique ID number
True5LocationText250File path, URL, etc.

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

No.Location
001001.jpg
002002.jpg
003003.jpg
004004.jpg
005005.jpg
006006.jpg
007007.jpg
008008.jpg
Item Image Table

This is the definition of the Item Image table:

EnabledField No.Field NameData TypeLengthDescription
True1Item No.Code20
True5Image No.Code20
True10SequenceIntegerDetermines order images are displayed

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:

Item No.Image No.Sequence
10000011
10000022
10000033
10000044
10010041
11000051
11000062
11100071
11000082

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:

NameDataTypeSubtypeLength
CurrentItemNoCode20
ImageRepositoryDotNetJason.Down.Blog.MutliImageAddinDemo.Model.NavQueryObjectImageRepository.'Jason.Down.Blog.MutliImageAddinDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cfeb9f2c0b3be8fb'
BasePathText

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:

NameDataTypeSubtypeLength
StringTypeDotNetSystem.String.'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
ImagePathListDotNetSystem.Collections.Generic.List`1.'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
GenericFactoryCodeunitDot Net Generic Factory
ItemImageQueryQueryItem Image Query

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.