Images on a page or in a grid
This article describes the steps for displaying images on a page or in a grid. The article also provides background about some of the ways that images can be used, and the APIs that are used.
Note
For accessibility, when you use an image to indicate status or show data, the image must be accompanied by a tooltip, enhanced preview, label, or other textual representation that describes the value or status that the image represents.
Finance and Operation apps do not use embedded resources for images. Instead, it uses lightweight symbols. The coding pattern has changed slightly to support the new image control.
For ImageList uses, the runtime accepts the old ImageID value and maps it to a symbol, so that existing code continues to work.
Note
In some cases, there is no image even after runtime mapping, and this behavior is intentional.
AX 2012 displays images in a grid column to indicate status. These images were sometimes retrieved from embedded resources that are no longer available.
AX 2012 offers the following storage options for images:
- An embedded resource where images are offered as part of the kernel itself
- An Application Object Server (AOS) resource where developers or independent software vendors (ISVs) can add their own image resources
- A file location where developers or ISVs can load images at run time
- A database field that is stored as a bitmap
The following storage options are available for images:
- An AOS resource where developers or ISVs can add their own image resources
- A URL location where developers or ISVs can load images at run time
- A database field that is stored as a container.
- A symbol font, where images are rendered by name from the font
Images that are stored as AOS resources allow for the use of an image that isn't categorized as user data, and can be used with your application.
Note
If there are legacy embedded resource images that UX has approved for use, those embedded images can be manually transferred to an AOS resource and used.
A typical web application maintains a collection of images on an Internet Information Services (IIS) server and just provides a URL to the image. Although this approach is supported, we don't expect that it will be used very much. Instead, we expect that the symbol font will be used as an image source.
Of course, application logic will store an image in a database to allow for strong employee photos, product images, and so on, and this approach is a first-class experience.
A symbol font is the most performant and scalable image format. We expect that characters from the symbol font will be used for most application use cases (grid row by row status, button images, and so on).
For the list of symbols that are available in the symbol font, see Symbol font.
Image type: Symbol
Pros | Cons |
---|---|
|
A limited number of framework-defined symbols is available. |
Design time
Image location: Symbol Typical image: "Person"
Run time
Sometimes, you don't have an image for a particular record in a grid, but you don't want an empty space where the image should be. The following example shows how you can use a display method to check for an image value, and then substitute a placeholder image instead.
public display container customerImage()
{
container imageContainer = this.Image;
if (imageContainer == connull())
{
// there is no image… the container is null
// show a generic person outline image
ImageReference imageReference =
ImageReference::constructForSymbol(ImageReferenceSymbol::Person);
imageContainer = imageReference.pack();
}
return imageContainer;
}
Image type: AOT Resource
Pros | Cons |
---|---|
In addition to the pros of using URL images (see the next section), AOT resources are modeled and managed by the development tools. | A limited number of framework-defined images is available. |
Design time
| You just create a new resource and then save the image into the Application Object Tree (AOT) resource. When you model your image control on a page, you specify the resource name, not the image name. This approach is typically used for legacy images (icons) that don’t have equivalents in the symbol font. Image location: AOTResource Typical image: "ResourceMicrosoft Dynamics AX" (a .jpg is added to resources) |
Run time
public display container imageDataMethod()
{
ImageReference imageReference =
ImageReference::constructForAotResource(
'ResourceMicrosoft Dynamics AX');
return imageReference.pack();
}
Image type: URL Image
Pros | Cons |
---|---|
|
|
Run time
The following example shows an image that uses a URL that is contained in a string.
public display container imageDataMethod()
{
ImageReference imageReference =
ImageReference::constructForUrl(this.ImageURL);
return imageReference.pack();
}
This code sends a small JavaScript Object Notation (JSON) message to the control on the client. This message instructs the control to treat the image as a URL and let the browser do the work of downloading the image. No download occurs on the server. Storing an image URL in a database table You can also have a container field for the image column on your table. You can then use code that resembles the following example to store the ImageReference pack.
CLIControls_ImageTable imageTable;
ttsbegin;
ImageReference imageReference =
ImageReference::constructForUrl(
'http://dynamics/PublishingImages/ERPLogos/DynamicsLogo.jpg');
imageTable.ImageField = imageReference.pack();
imageTable.insert();
ttscommit;
This code causes the user’s browser to download the image from the specified URL. The use of ImageReference involves some overhead, but this approach lets you use a single application programming interface (API) to handle images that are created from binary data, URLs, AOT resources, or symbols. You can even mix and match image types between rows of data.
Image type: Binary Image
Pros | Cons |
---|---|
Usually, this approach offers the easiest migration if the Image class in X++ was already used, or if binary images were previously stored in the database. |
|
Design time | Run time |
---|---|
Using a database field This approach is typically used to display data, such as employee pictures and product images. You can bind directly to a field, or you can use a display method. Data Source Data Field Data Method | Typically, the images are loaded from database, and no additional code is required. For cases where the image is managed in a data method, see the data method examples. |
Display methods and images (three return types)
When you use a display method for an image type to show an image in a grid, three return types are understood by the image control that works with the framework. All three return types can be used to display an image.
- Int (imagelist array index)
- Container (image instance)
- ResID (which is mapped to a symbol)
Note
ResID and Int are the same return types. If the imageList property of the image control instance has been assigned an instance value, the display method return value is considered an array index into the imagelist. If the imageList property is null, the return value is used to map a legacy ResID to a symbol.
Images in a grid and the legacy ImageList collection
In AX 2012 and earlier versions, a common use pattern for displaying images is to store an image as a resource or use a kernel-supplied image resource, and then at run time, extract that image and place it in a reusable collection that is known as an ImageList. The guidance is to use lighter-weight symbol images. You should rewrite all legacy code so that it uses symbols directly. You should also replace all code that uses the ImageList collection. If you don't make these changes, the legacy ImageList collection won't display images, because use of this collection relies on embedded (kernel) resources that no longer exist. Therefore, to support legacy code until it can be updated, the ImageList collection maps the ResID for an embedded resource to a new font-based symbol to help guarantee that any code that uses the ImageList collection will continue to run and provide an image.
Using the imageList property for backward compatibility
An image control has a property that is named imageList. You pass in an instance of the ImageList collection to this property. In this way, the image is an array of images that you select via the array number.
public void init()
{
int imgCnt;
// create an imagelist instance
Imagelist imageList = new ImageList(
ImageList::smallIconWidth(),
Imagelist::smallIconHeight());
super();
// add images to the instance (return value is not needed)
// Note that a legacy ResID is used in the new Image contstructor.
// This is a compatibility mapping of resource to symbol.
imgCnt = imagelist.add(new Image(#ImageInfo));
imgCnt = imagelist.add(new Image(#ImageWarning));
imgCnt = imagelist.add(new Image(#ImageError));
// pass the image list instance to the control
ImageListDM.imageList(imageList);
}
// at runtime, select the image you want to show: when the control has an imagelist instance,
// this int value is used to index into that array
public display int imageListDataMethod()
{
int imgCnt = imageCnt mod 3;
imageCnt++;
return imgCnt;
}
/*
Note: The legacy image resource ID's #ImageInfo, #ImageWarning, #ImageError are
mapped from the legacy resource id to a symbol name in the X++
class ImageLoader
*/
Display method that returns an ImageRes (legacy image resource ID)
// this is an example of backward compatibility the use of ImageRes will become obsolete
display ImageRes checkIfError(HRMCompEventEmpl _hrmCompEventEmpl)
{
if (!_hrmCompEventEmpl.RecId)
{
return 0;
}
if (_hrmCompEventEmpl.Status == HRMCompEventEmplStatus::Ignore ||
_hrmCompEventEmpl.Status == HRMCompEventEmplStatus::Approved ||
_hrmCompEventEmpl.Status == HRMCompEventEmplStatus::Loaded)
{
return 0;
}
else
{
if (_hrmCompEventEmpl.ErrorStatus == HRMCompEventErrorStatus::Error)
{
return #ImageError;
}
if (_hrmCompEventEmpl.ErrorStatus == HRMCompEventErrorStatus::Warning)
{
return #ImageWarning;
}
if (_hrmCompEventEmpl.ErrorStatus == HRMCompEventErrorStatus::Info)
{
return #ImageInfo;
}
}
return 0;
}
Display method that returns a container
public display container checkIfError(HRMCompEventEmpl _hrmCompEventEmpl)
{
ImageReference imageReference;
container imageContainer;
if (_hrmCompEventEmpl.RecId && _hrmCompEventEmpl.Status == HRMCompEventEmplStatus::Created)
{
switch (_hrmCompEventEmpl.ErrorStatus)
{
case HRMCompEventErrorStatus::Error:
imageReference = ImageReference::constructForSymbol('Error');
break;
case HRMCompEventErrorStatus::Warning:
imageReference = ImageReference::constructForSymbol('Warning');
break;
case HRMCompEventErrorStatus::Info:
imageReference = ImageReference::constructForSymbol('Info');
break;
}
}
if (imageReference)
{
imageContainer = imageReference.pack();
}
return imageContainer;
}
Obtaining and displaying an image from the user by using file upload
Model a page that has an image control and a FileUpload button.
// model a new FileUpload control (style=minimal)
// class declaration
FileUpload uploadControl;
// form init() create a callback event handler to be notified when upload is complete
public void init()
{
//when uploading an image, this method is called upon completion.
uploadControl = FileUpload1;
uploadControl.notifyUploadCompleted += eventhandler(this.UploadCompleted);
}
// form close() release the callback event handler
public void close()
{
// when the form closes, release the eventhandler for file upload callback
// FileUpload uploadControl;
super();
// uploadControl = FileUpload1;
uploadControl.notifyUploadCompleted -= eventhandler(this.UploadCompleted);
}
// when the upload completes, grab the image and store it in the database
/// <summary>
/// This method is called by the file upload mechanism, when the upload completes
/// </summary>
public void UploadCompleted()
{
Binary binaryImage;
System.Net.WebClient webClient;
System.IO.MemoryStream stream;
String255 myUrl;
if(uploadControl.uploadSuccess())
{
InteropPermission perm = new InteropPermission(InteropKind::ClrInterop);
perm.assert();
// BP Deviation Documented
webClient = new System.Net.WebClient();
// BP Deviation Documented
// if success, downloadURL contains the path to the Azure blob location for the file
stream = new System.IO.MemoryStream(webClient.DownloadData(uploadControl.downloadUrl()));
// grab the data and assign to the image field
binaryImage = Binary::constructFromMemoryStream(stream);
// assign to the database field (type=container)
FMVehicleModel.Image = binaryImage.getContainer();
CodeAccessPermission::revertAssert();
}
}
Example of in-memory bitmap manipulation
In this example, an image is created from scratch. However, developers can also load a bitmap from an alternative source and then manipulate the image as desired (for example, by cropping, stretching, or resizing, or by changing the opacity). After any manipulation is completed, the developers can display the image by using the image control, or they can assign it to a data source field.
public void clicked()
{
Binary binaryImage;
Image image;
int x,y;
super();
InteropPermission perm = new InteropPermission(InteropKind::ClrInterop);
perm.assert();
/*
In this example, we’ll create a bitmap programmatically, we’ll use a memory
Stream o’bytes to then convert to the container format the image control expects.
*/
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(100,100);
System.IO.MemoryStream myStream = new System.IO.MemoryStream();
// draw some stuff (or load a bitmap from an alternative source)
for( x=0; x < bitmap.Height; ++x)
{
for( y=0; y< bitmap.Width; ++y)
{
bitmap.SetPixel(x,y,System.Drawing.Color::White);
}
}
for(x=0; x < bitmap.Height; ++x)
{
bitmap.SetPixel(x,x, System.Drawing.Color::Red);
}
// move our bitmap to an in memory stream
bitmap.Save(myStream, System.Drawing.Imaging.ImageFormat::Bmp);
// stream goes to raw binary
binaryImage = Binary::constructFromMemoryStream(myStream);
// create a blank image and copy our binary data to the image format
image = new Image();
image.setData(binaryImage.getContainer());
// copy the image data to the image control
MyImage.image(image);
// alternatively, skip the image conversion step and assign directly to the data field
binaryImage = Binary::constructFromMemoryStream(myStream);
// assign to the database field (type=container)
datafield.Image = binaryImage.getContainer();
CodeAccessPermission::revertAssert();
}
Additional examples (URL, binary, and symbol)
The following table explains two concepts: Image Class and FormImageControl.
Image Class | This class is a run-time representation of an image. | Four constructors:
|
FormImageControl | This control is used to add an image at run time. | .image(new image()); |
Using a display method to show an image from a URL string
In this example, a display method is used to translate a string that contains a URL to the format that the image control expects.
public display container imageDataMethod()
{
ImageReference imageReference =
ImageReference::constructForUrl(this.ImageURL);
return imageReference.pack();
}
This code sends a small JSON message to the control on the client. This message instructs the control to treat the image as a URL and let the browser do the work of downloading the image. No download occurs on the server.
Using a display method to show a blank image
There might be times when you have no image for a particular record in a grid, but you don't want an empty space where the image should be. This example shows how you can use a display method to check for an image value and then substitute a placeholder image instead.
public display container customerImage()
{
container imageContainer = this.Image;
if (imageContainer == connull()) // there is no image… the container is null
{
ImageReference imageReference =
ImageReference::constructForSymbol(ImageReferenceSymbol::Person); // show a generic person outline image
imageContainer = imageReference.pack();
}
return imageContainer;
}
public display container statusImageDataMethod()
{
ImageReference statusImage;
if (this.Status == NoYes::Yes)
{
statusImage =
ImageReference::constructForSymbol(ImageReferenceSymbol::Accept);
}
else
{
statusImage =
ImageReference::constructForSymbol(ImageReferenceSymbol::Cancel);
}
return statusImage.pack();
}
Taking an image URL and storing the image in table
You can have a container field for the image column on your table. You can then use code that resembles the following example to store the ImageReference pack.
CLIControls_ImageTable imageTable;
ttsbegin;
ImageReference imageReference =
ImageReference::constructForUrl(
'http://dynamics/PublishingImages/ERPLogos/DynamicsLogo.jpg');
imageTable.ImageField = imageReference.pack();
imageTable.insert();
ttscommit;
Like the display method that is described in the "Using a display method to show an image from a URL string" section, this code causes the user's browser to download the image from the specified URL. Although this approach involves some overhead, you can use a single API to handle images that are created from binary data, URLs, AOT resources, or symbols. You can even mix and match image types between rows of data.
Feedback
https://aka.ms/ContentUserFeedback.
Coming soon: Throughout 2024 we will be phasing out GitHub Issues as the feedback mechanism for content and replacing it with a new feedback system. For more information see:Submit and view feedback for