Share via


Pen based form demo

This demo was written for Convergence 2007 to showcase how Dynamics Ax can leverage managed code. In this example it uses a managed API to allow form navigation using tablet gestures, that is small figures drawn with the pen on the tablet screen.

 

The technology shown here will work in 4.0 as well as in 5.0 when that version hits the streets.

 

The theory of operation of this is quite simple, because of the beautiful API that is provided for capturing gestures. This application is merely scratching the surface of what this API can do. The only thing needed for this to work is to set up an ink analyser to do the heavy lifting for you. For this example we choose to handle the following gestures:

 

Stroke Action
Left Previous record
Right Next record
< First record
> Next record
Check Close form
Scratch out Delete this record
Circle  Print this record

The complete list of available gestures can be found at https://msdn2.microsoft.com/en-us/library/ms827547.aspx

The trick for this is how to communicate the event that happens in the managed world to the unsuspecting X++ code. There is no direct way of subscribing to an event in the managed world in X++. The way I have done this may be useful in other scenarios as well, so I'll describe it in some detail:

The control on which the gestures are drawn is a Window control, which has no other semantics than publishing its window handle. As anyone who has done any windows programming back in the old unmanaged days will know, the windows handle is an identifier that is used to identify the window for all operations (drawing etc) that takes place in the window. The trick here is to pass this windows handle to the constructor of the class that recognizes the gestures. When a gesture is recognized, the proper event handler is called, and this event handler will simply post a custom message to the windows handle. When Ax is done with its own messages, this message will be handled. There is no default handler for this message (it is a custom message after all, not a predefined one), but because Uffe had a remarkable foresight 10 years ago, he added a way of subscribing to these custom events.This is done by calling the installMessageProc method on the form with the name of a method to call as a result of the given message being posted to the given handle. The init method on the form then does the following:

void init()
{
int h;
// ...
super();

h = PenArea.hWnd(); // Get the windows handle from control.

// Pass it on the the stroke handler so it can react by
// posting messages to this windows handle:
strokeHandler = new Villadsen.StrokeHandler(h);

// Make sure the method called CallbackMethod is called
// when the custom message with the id 0x7ffe is posted
// to the handle
this.installMessageProc(0x7ffe, h, "Callbackmethod");

// ...
}

The CallbackMethod then simply branches on the gesture that was recognized and does the appropriate thing. For legibility I have created a method for each action.

void CallbackMethod(int hwnd, int message, int wParam, int lParam, int px, int py)
{
#InkGestures

// print "Called back! Value is ", wparam, ",", lparam;

switch (lparam)
{
case #Left:
Element.DoPrevious();
break;
case #Right:
Element.DoNext();
break;
case #ChevronLeft:
Element.DoFirst();
break;
case #ChevronRight:
Element.DoLast();
break;
case #Check:
Element.close();
break;
case #ScratchOut:
Element.DoDelete();
break;
case #Circle:
// Circle has to be drawn pretty fast.
Element.printPreview();
break;
}
}

The managed code (i.e the StrokeHandler) is shown below. Note that the managed libraries do not have an implementation of PostMessage, so one is created by using the DllImport facility.

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using Microsoft.Ink;
using System.Runtime.InteropServices;

namespace Villadsen
{
public class StrokeHandler
{
private Microsoft.Ink.InkOverlay inkOverlay;
private Microsoft.Ink.InkAnalyzer inkAnalyzer;
private int handle;

[DllImport("user32", EntryPoint = "PostMessage")]
public static extern int PostMessageA(int hwnd, int wMsg, int wParam, int lParam);

public StrokeHandler(int windowsHandle)
{
this.handle = windowsHandle;

this.inkOverlay = new InkOverlay((IntPtr)this.handle);
this.inkAnalyzer = new InkAnalyzer(inkOverlay.Ink, null);

// Only collect gestures
inkOverlay.CollectionMode = CollectionMode.GestureOnly;
// But do collect all of them.
inkOverlay.SetGestureStatus(ApplicationGesture.AllGestures, true);
// Call this event handler to handle an incoming gesture
inkOverlay.Gesture += new InkCollectorGestureEventHandler(inkOverlay_Gesture);
// The drawing surface should be in front of the control.
inkOverlay.AttachMode = InkOverlayAttachMode.InFront;
// Draw in red
inkOverlay.DefaultDrawingAttributes.Color = Color.Red;

inkOverlay.Enabled = true;
}

// Event handler for the incoming events. This will post the incoming gensture
// back to Ax through the windows handle of the form that created the StrokeHandler.
void inkOverlay_Gesture(object sender, InkCollectorGestureEventArgs e)
{
// System.Console.WriteLine("Got a gesture {0}", e.Gestures[0].Id);
PostMessageA(this.handle, 0x7ffe, 1, (int)e.Gestures[0].Id);
e.Strokes.Clear();
}
}
}

That's it! That's really all there is to it.You can find the source code on ftp://ftp.villadsen.dk/GestureDemo.zip

 

 

PenForm.gif

Comments

  • Anonymous
    October 31, 2007
    Thank you Peter for the inspiration. One comment regarding the handle(hwnd); it's not possibly to change that in C# directly with on a UserControl, but using the SetParent static method from user32.dll, it's working just as well as you example!

  • Anonymous
    June 08, 2012
    Thanks you Peter I am unable to download the source code on ftp.villadsen.dk/GestureDemo.zip