Continuing from my previous blog post [http://community.dynamics.com/product/ax/axtechnical/b/dynamicsax_wpfandnetinnovations/archive/2012/12/08/write-once-use-everywhere-paradigms.aspx], it’s probably a good idea now to illustrate the principle of WOUE with a demo. I’m going to show how we could re-use a component that we (originally) developed for the Dynamics-windows-rich-client in another application. In this case I’ll be using MS Excel, but pretty much any application that can host .Net UserControls will work.
· For this demo you will need MS Office with VSTO (Visual Studio Tools for Office) installed. The standard Contosso VM comes with this pre-configured.
We will be re-using the “note-taking” UserControl that we have been developing thus far. The screenshot below shows how the managed host control looks within a form.
Image may be NSFW.
Clik here to view.
In order to transfer this UserControl to another application (and for it work) we need the target application to be able to supply it with context. In this case, we’ve configured the UserControl to accept 2 key properties:
· ContextTableId: the internal “bigint” identifier that Dynamics maintains for AOT tables.
· ContextRecId: the internal “bigint” identifier that Dynamics uses to uniquely identify records.
This is all the information the UserControl needs to retrieve the “notes” stored against that record. This means that the Excel application needs to be able to supply the above 2 items and this can easily be done by downloading data directly into Excel using the Dynamics data connector add-in’s. We want to be able to create something that looks like this:
Image may be NSFW.
Clik here to view.
The user will initially download a data-dump from the table and then as the user clicks into the records (using the mouse or cursor keys), the UserControl will update/refresh itself automatically. This in effect, replicates the same behaviour when it runs within the Dynamics-windows-rich-client. The only difference there was that the UserControl updated/refreshed when navigating to a new record.
Firstly, we need to create a new “Excel 2010 Workbook” project (this can be separate or integrated into the original UserControl project).
Image may be NSFW.
Clik here to view.
We then need to add a reference to the UserControl DLL.
· If you created your new project separate to the UserControl then you will need to use the “Browse” tab to find the assembly, otherwise, you simply need to use the “Projects” tab.
The UserControl should have all the connectivity to the AOS encapsulated within it so you don’t need to include any further Dynamics interop assemblies.
Right-click the Excel project and add a new UserControl:
Image may be NSFW.
Clik here to view.
Drag and drop a standard windows-forms-panel from the designer toolbox and resize this panel to make sure that there is adequate space to accommodate your UserControl.
· Good practice for WPF UserControls is to set all dimensions to “Auto”, which means they will make a “best-effort” fit into the available space on the target device.
In the code-behind for the UserControl, add the relevant code to instantiate and initialise the UserControl. The code below will vary based on the name of the referenced UserControl assembly and what you’ve decided to call it. In this case my referenced assembly and UserControl name is: XEInterop.Startup
UserControl1.cs
|
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Windows; using System.Windows.Forms; using System.Windows.Forms.Integration; using XEInterop;
namespace XEWpfExcelApplication { publicpartialclassUserControl1 : UserControl { privateElementHost ctrlHost; private XEInterop.Startup XEUserControl;
public UserControl1() { InitializeComponent(); }
privatevoid UserControl1_Load(object sender, EventArgs e) { // instantiate UserControl and add it to the panel ctrlHost = newElementHost(); ctrlHost.Dock = DockStyle.Fill; ctrlHost.Name = "XEUserControl"; panel1.Controls.Add(ctrlHost); XEUserControl = new XEInterop.Startup(); XEUserControl.InitializeComponent(); ctrlHost.Child = XEUserControl;
// initialise context for UserControl and attempt AOS connect XEUserControl.ContextEnvironment = "WpfExcelApplication"; XEUserControl.ContextTable = "XETable"; XEUserControl.ContextMode = "Read"; XEUserControl.Logon(); }
public XEInterop.Startup getXEUserControl() { return XEUserControl; } } }
|
Once this is done, then we need to turn our attention to the worksheet. In VSTO you need to manually wire the events. We need handlers for all of the following events:
· Startup: will allocate a “block-of-cells” (region) to which the named UserControl will be added.
· Shutdown: will de-allocate resources used by the UserControl (the AOS communication channel needs to be closed down).
· SelectionChange: will update the context for the UserControl when the user clicks on a new cell (refresh/updates it).
The complete listing for this functionality is given below:
Sheet1.cs
|
using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; using System.Windows.Forms; using System.Xml.Linq; using Microsoft.Office.Tools.Excel; using Microsoft.VisualStudio.Tools.Applications.Runtime; using Excel = Microsoft.Office.Interop.Excel; using Office = Microsoft.Office.Core; using System.Windows.Forms.Integration;
namespace XEWpfExcelApplication { publicpartialclassSheet1 { UserControl1 customControl;
privatevoid Sheet1_Startup(object sender, System.EventArgs e) { // get 1st worksheet and allocate a "block" range of cells for the user control Excel.Worksheet ws = (Excel.Worksheet)Globals.ThisWorkbook.Worksheets[1]; Excel.Range r1 = (Excel.Range)ws.Range["E3:T13"];
// create a new instance of the user control and add it to the controls collection customControl = newUserControl1(); Microsoft.Office.Tools.Excel.ControlSite dynamicControl = this.Controls.AddControl(customControl, r1, "dynamic"); }
privatevoid Sheet1_Shutdown(object sender, System.EventArgs e) { customControl.Dispose(); }
#region VSTO Designer generated code
///<summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. ///</summary> privatevoid InternalStartup() { // standard startup/shutdown event handlers this.Startup += new System.EventHandler(this.Sheet1_Startup); this.Shutdown += new System.EventHandler(this.Sheet1_Shutdown);
// functionality specific event handlers this.SelectionChange += new Microsoft.Office.Interop.Excel.DocEvents_SelectionChangeEventHandler(this.Sheet1_SelectionChange); }
#endregion
privatevoid Sheet1_SelectionChange(Microsoft.Office.Interop.Excel.Range Target) { if (Target.Column == 1) { // get a handle on the user control and set its context XEInterop.Startup XEUserControl = customControl.getXEUserControl(); XEUserControl.ContextRecId = 0; try { XEUserControl.ContextRecId = (long)Target.Value; } catch (Exception ex) {}
// update the user control using the background dispatcher (you dont want Excel functionality to suffer) XEUserControl.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (System.Action)(() => { XEUserControl.Process(); })); } else { return; } } } }
|
Now, when you run the spreadsheet, you should see the UserControl change whenever you click on a new record (you will need to download some data first using the Dynamics Connector Addin’s). If your UserControl does more than just display data (i.e. post data as well) then that functionality will work just the same as if you were in the native Dynamics environment (security context of the logged-in windows-user is used).
This is a huge leap forward when it comes to component re-use. The following YouTube video illustrates the principles being discussed above: [http://youtu.be/rFy2w-gdPFA?hd=1]
REGARDS
Image may be NSFW.
Clik here to view.