CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Peter's Gekko

public Blog MyNotepad : Imho { }

June 2005 - Posts

  • Tablet PC input: recognizing gestures and creating strokes from code

    In my previous post on Tablet PC pen input I explored handwriting recognition. I explored how the language specific recognizers of the tablet API translated your scribbling into text, listed the possible alternatives and the confidence it had in the results. The Tablet API has another recognizer built in, this one recognizes gestures. In this post I will explore that a little further. My demo code will try to recognize some specific gestures and draw it's idealized form from strokes created in code.

    Again I have a simple Winform with a panel on it with a private InkCollector object. The collector is created in the form's constructor and disposed in the form's Dispose method. For the details on that check my previous post. By default an inkcollector object collects strokes. By setting it's CollectionMode property you change this. Here the collector is told to only check for gestures. The user can draw on the panel, but after a short lapse of time the strokes will disappear having been processed by the gesture recognizer. A gesture is a certain movement of the pen. For instance tapping the digitizer is a gesture. But also drawing a line, circle or even an arrow. These gestures fall into two categories. First come the system-gestures, which include tap, double tap and drag, a full list is is the SystemGesture enumeration. The second group are application gestures, a full list of of these is in the ApplicationGesture enumeration.

    In this story I will dive deeper into the latter. A system gesture is always recognized, but if you want your application to recognize an application gesture you must subscribe to it using the inkcollector's SetGestureStatus method. You can be a lazy coder by passing in ApplicationGesture.AllGestures but that is a bad habit. Recognizing gestures is a costly matter so please be economic when subscribing. When a gesture is recognized the InkCollector's Gesture event will fire. Add an event handler to inspect the gestures and do something with them.

    using System;
    using System.Drawing;
    using System.Collections;
    using System.ComponentModel;
    using System.Windows.Forms;
    using System.Data;
    using Microsoft.Ink;


    namespace GestureSketcher
    {
        public class Form1 : System.Windows.Forms.Form
        {
            private System.ComponentModel.Container components = null;
            private System.Windows.Forms.Panel panel1;

            private InkCollector ic;

            public Form1()
            {
                InitializeComponent();
                ic = new InkCollector(panel1);
                ic.CollectionMode = CollectionMode.GestureOnly;
                ic.SetGestureStatus(ApplicationGesture.Scratchout, true);
                ic.SetGestureStatus(ApplicationGesture.Up, true);
                ic.SetGestureStatus(ApplicationGesture.Down, true);
                ic.SetGestureStatus(ApplicationGesture.Left, true);
                ic.SetGestureStatus(ApplicationGesture.Right, true);
                ic.SetGestureStatus(ApplicationGesture.Square, true);
                ic.SetGestureStatus(ApplicationGesture.Circle, true);
                ic.SetGestureStatus(ApplicationGesture.SemiCircleRight, true);
                ic.SetGestureStatus(ApplicationGesture.ChevronUp, true);
                ic.SetGestureStatus(ApplicationGesture.ArrowUp, true);           
                ic.Gesture +=new InkCollectorGestureEventHandler(ic_Gesture);
                ic.Enabled = true;
            }

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            protected override void Dispose( bool disposing )
            {
                if( disposing )
                {
                    if (components != null)
                    {
                        components.Dispose();
                        ic.Dispose();
                    }
                }
                base.Dispose( disposing );
            }

            #region Windows Form Designer generated code
            private void InitializeComponent()
            {
                // just panel1

            }
            #endregion

            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            [STAThread]
            static void Main()
            {
                Application.Run(new Form1());
            }



            private void ic_Gesture(object sender, InkCollectorGestureEventArgs e)
            {
                // Get bounding box of strokes drawn
                Rectangle rcBounds = e.Strokes.GetBoundingBox();

                // Calculate bounding points
                Point rcBoundsTopLeft = new Point(rcBounds.Left, rcBounds.Top);
                Point rcBoundsTopMid = new Point((rcBounds.Left + rcBounds.Right) /2, rcBounds.Top);
                Point rcBoundsTopRight = new Point(rcBounds.Right, rcBounds.Top);   

                Point rcBoundsMidLeft = new Point(rcBounds.Left, (rcBounds.Top + rcBounds.Bottom) /2);
                Point rcBoundsMidRight = new Point(rcBounds.Right, (rcBounds.Top + rcBounds.Bottom)/2);

                Point rcBoundsBottomLeft = new Point(rcBounds.Left, rcBounds.Bottom);
                Point rcBoundsBottomMid = new Point((rcBounds.Left + rcBounds.Right) /2, rcBounds.Bottom);
                Point rcBoundsBottomRight = new Point(rcBounds.Right, rcBounds.Bottom);

                int gesturesRecognized = 0;

                for (int i=0; i< e.Gestures.Length;i++)
                {
                    bool curved = false;
                    Point[] pts = new Point[0];
                    Gesture gest = e.Gestures[i];

                    switch(gest.Id)
                    {                       
                        case ApplicationGesture.Down :
                            pts = new Point[2];
                            pts[0] = e.Strokes[0].GetPoint(0);
                            pts[1] = new Point(pts[0].X, rcBounds.Bottom);
                            break;
                        case ApplicationGesture.Up :
                            pts = new Point[2];
                            pts[0] = e.Strokes[0].GetPoint(0);
                            pts[1] = new Point(pts[0].X, rcBounds.Top);
                            break;
                        case ApplicationGesture.Left :
                            pts = new Point[2];
                            pts[0] = e.Strokes[0].GetPoint(0);
                            pts[1] = new Point(rcBounds.Left, pts[0].Y);
                            break;
                        case ApplicationGesture.Right :
                            pts = new Point[2];
                            pts[0] = e.Strokes[0].GetPoint(0);
                            pts[1] = new Point(rcBounds.Right, pts[0].Y);
                            break;
                        case ApplicationGesture.Square :
                            pts = new Point[5];
                            pts[0] = rcBoundsTopLeft;
                            pts[1] = rcBoundsTopRight;
                            pts[2] = rcBoundsBottomRight;
                            pts[3] = rcBoundsBottomLeft;
                            pts[4] = rcBoundsTopLeft;
                            break;
                        case ApplicationGesture.ArrowUp :
                            pts = new Point[5];
                            pts[0] = rcBoundsMidLeft;
                            pts[1] = rcBoundsTopMid;
                            pts[2] = rcBoundsBottomMid;
                            pts[3] = rcBoundsTopMid;
                            pts[4] = rcBoundsMidRight;
                            break;
                        case ApplicationGesture.ChevronUp:
                            pts = new Point[3];
                            pts[0] = rcBoundsBottomLeft;
                            pts[1] = rcBoundsTopMid;
                            pts[2] = rcBoundsBottomRight;
                            break;
                        case ApplicationGesture.SemiCircleRight :
                            pts = new Point[3];
                            pts[0] = rcBoundsBottomLeft;
                            pts[1] = rcBoundsTopMid;
                            pts[2] = rcBoundsBottomRight;
                            curved = true;
                            break;
                        case ApplicationGesture.Scratchout :
                            // erase last stroke drawn
                            ic.Ink.DeleteStroke(ic.Ink.Strokes[ic.Ink.Strokes.Count - e.Strokes.Count- 1]);
                            panel1.Invalidate();
                            break;

                    }

                    if (pts.Length > 0)
                    {
                        gesturesRecognized++;
                        // Create a stroke to draw form recognized
                        Stroke str = ic.Ink.CreateStroke(pts);
                        str.DrawingAttributes.FitToCurve = curved;

                        switch(gest.Confidence)
                        {
                            case RecognitionConfidence.Strong :
                                str.DrawingAttributes.Color = Color.Green;
                                break;
                            case RecognitionConfidence.Intermediate :
                                str.DrawingAttributes.Color = Color.Orange;
                                break;
                            case RecognitionConfidence.Poor :
                                str.DrawingAttributes.Color = Color.Red;
                                break;
                        }

                        if (gesturesRecognized == 1)
                        {
                            // Emphasize first recognizer result
                            str.DrawingAttributes.Width = str.DrawingAttributes.Width * 2;
                            str.DrawingAttributes.Height = str.DrawingAttributes.Height * 2;
                        }
                        // Draw stroke on panel
                        ic.Ink.Strokes.Add(str);
                    }
                }

            }
        }
    }
     

    All the fun stuff happens in the ic_Gesture method. In the parameters it receives an InkCollectorGestureEventArgs object, full of interesting information such as the strokes drawn and the gestures recognized. The strokes drawn have a bounding box. This is the rectangle which encloses them. Out of the 4 corner points I calculate the points half way the rectangle's sides. The events arguments also contain a list of gestures. This list is comparable with the list of words when recognizing handwriting, this is a list of the calculated guesses of the gesture. My code loops through these and checks for the id of the gesture.

    When the strokes have been analyzed by the recognizer they are discarded. The code will create a new stroke to draw a shape which corresponds to the gesture. You create a stroke out of an array of points. You get the coordinates of a stroke by using the Stroke.GetPoint method. The actual value of these coordinates is not in pixels but in HiMeteric format. The accuracy of a tablet digitizer is far more precise than the pixels of the screen, by using the HiMetric format this information is preserved.

    The simplest gestures are a simple line up, down, left or right. Drawing a line requires two points. The starting point is copied from the first point of the stroke drawn. As end point the stroke's bounding box provides the coordinates. The result is a stroke which will draw a perfectly straight horizontal or vertical line. Create a stroke from the array of (these two) points using the Ink.CreateStroke method. The stroke will be drawn by adding it to the inkcollector's Strokes collection. Before doing that I'll do some decoration by setting properties of the stroke's DrawingAttributes property. Each gesture has a recognition Confidence, I use this to set the color. The first gesture recognized will be drawn with a thicker line by setting Height and Width.

    A somewhat more complex gesture is a square. There are several ways to draw this gesture: in one stroke making a lot of turns or in two strokes, each with just one turn. There is a small lag between the last stroke drawn and the gesture recognizer firing. This lag makes it possible to have a gesture which consists of multiple strokes. To build a square from one stroke you need 5 points. The top left corner will be the first and the last point to get a (visual) closed structure. Actually it is one open stroke whose begin- and end-point overlap.

    When it comes to gestures pointing up the gesture recognizer is going to have a hard time. In the demo I have subscribed to the arrow-up, the chevron-up (this looks like the letter v upside down) and the semicircle-left gesture. Despite its name the latter is best recognized by drawing the upper half of a circle. When these gestures are recognized the strokes are constructed from the points calculated. A semicircle is a rounded shape. When you set the FitToCurve property of the stroke's DrawingAttributes a Bezier curve fitting the strokes point's will be drawn. Drawing these kinds of gestures you will see that the gesture recognizer will propose several alternatives, leading to several shapes been drawn. The color of the shapes tell the confidence the recognizer has. Even if it's pretty sure it will return alternatives. Just like text recognition.

    There are loads of things you can do with gestures. As a small example I have subscribed to the ScratchOut gesture. It will erase the last stroke drawn by our code. Strokes entered by the user are discarded when the recognizer has finished. Before that they are still in the Strokes collection of the InkOverlay. To find the last stroke drawn I have to count the user's strokes to get the right index in the strokes collection.

    Now I have a very simple sketcher. To expand it's possibilities just subscribe to the other gestures. If you want to subscribe to SystemGestures you have to add an eventhandler to the InkCollector's SystemGesture event. Another method, same idea. A special note deserves the Circle gesture. When it comes to drawing a circle things are going to get hard as there is no simple way to draw a circle using Bezier curves. This is something to address with a custom renderer. Something for a new post.

    Scribble away !

  • Windows XP (europea) N edition

    The European Microsoft anti-trust case stirred quite a pen back in march 2004. The case had two parts. One of them was that Microsoft should split Media Player from Windows. My rant on the subject was a little oil canister (a honey pot attracts hackers, an oil canister attracts flames). As a result of the settling Windows XP N(o Media player) recently hit the market. And to set up another canister, I believe it just shows I was right:

    • Nobody is interested in XP N. Dell and Sony don't even offer it on their systems.
    • It's not a penny cheaper then XP.
    • You can install WinAmp, Real or whatever just as easy on the "normal" XP.

    In the recent vote for the European constitution the Netherlands voted against Europe. To my regret. But these kind of actions don't help any consumer a bit and do make people very skeptical about European institutions. This has been such an enormous amount of energy and money wasted.

    <spark>Or is Microsoft marketing the only one who won</spark>

  • Why go to a conference (the PDC in particular) and blog about it

    It’s conference and meeting season. Teched US is just behind, next week is TechEd Europe and as a side show Dutch usergroup dotned meets the 4th of july for a presentation by Juval Löwy. In september the summit will be the PDC.

    Why should you go to conferences and meetings ? There are lots and lots of reasons. Let me name some of them based on my past experiences.

    • Learn new things. This is allways named first, but actually it’s not quite true. There is no such thing as “become an Indigo expert in a keynote” and almost everything can be found on the internet. Recently a discussion here on codebetter questioned if it was worthwhile to go at all. What I always find interesting is how your fellow visitors respond to the presentations. You are attending a signpost talk, the audience is a good indication whether it is pointing in the right direction.
    • Learn how others solved things. Very good are the birds of a feather and chalk and talk sessions. Here you will not see the usual rockstars but other people who are struggling with the same things as you and want to talk with (and not just to) you about that.
    • Meet the experts. On the PDC this is even officialy organized. Imagine asking Anders Hejlsberg in person why he never implemented that particualr Delphi feature in C#. Imagine seeing Charles Petzold dance around with a tablet PC.

    • Give feedback. Last PDC the WinFx file system team had huge sheets (the picture above) with all the proposed classes and asked everybody to annotate them. Perhaps this feedback was to much which became the real reason why the new file system is not going to be part of Longhorn
    • Faces to the names. Some bloggers you read every day have a picture of themselves on their blogs. Most don’t. Now you can see what they are like in real life. Personally this is the best part of any meeting; it brings all that abstract talk and code back to something with a human appearance.
    • Books. Despite the internet a good book will teach you best. Of most books there is a possibility to read a sample chapter on-line. Just one. And it’s hard to compare books. Most publishers have a big store giving you the opportunity to reallly browse and compare and they’ll give you a discount as well.
    • Goodies. A good conference has at least one book in the conference bag. Often it’s a book you wouldn’t buy yourself despite the fact that you actually should. Like a book on security. Last PDC “writing secure code” was in the conference bag, on VSlive it was “Implementing security for applications”. But there are far more goodies. I have my channel9 guy sitting on the top of my PC. When your are short on USB memory pens or stress balls : this is the chance to get some. The bigger the conference, the better the goodies. Be selective or bring a big empty bag.

    Why blog ?

    My incomplete list shows that you will drown in information. As a start to get that organized you better blog it. If you can’t write something down to explain it to somebody, you don’t understand it yourself. As an extra bonus this results in an on-line diary of your visit. And there is more:

    Who’se going to pay that ?

    The meetings of an usergroup like dotned are free, but visiting a conference can cost quite a penny. Who’s going to pay that ? When you work for a company  it will be  your boss. His return on investiment will be that your productivity will rise after all inspiration gained. When you work for yourself you could argue : “Hey I cannot bill those hours”. In that case you are allready making to many hours. Get a break, go and get some new inspiratrion to keep the fire burning. A burnout is probably nearby. Or your bookkeeper (like mine) might argue : “Hey, you don’t have the financial resources to do that”. In that case try this:

    blogging my way to pdc

    one more reason to blog.

  • Stop the trackback myth

    The heading of a blog home page usually contains some statistics like the number of posts in the blog and the number of comments. Almost always it will mention the number of trackbacks. The codebetter blog homepage has a list of bloggers (reverse) ordered by number of posts, with our trackbacks. The way the latter changes over time made me suspicious. There are liars, big liars and (blog) statistics so I would like to know what I am actually reading.

    A trackback is a word everybody is using, but almost nobody can give a good definition what it really is. I went to wikipedia :

    From Wikipedia, the free encyclopedia.

    TrackBack is a system implemented by Movable Type and later adopted by many blogging tools, including TypePad, WordPress, and Nucleus, that allows a blogger to see who has seen the original post and has written another entry concerning it. The system works by sending a 'ping' between the blogs, and therefore providing the alert.

    TrackBack typically appears below a blog entry and shows a summary of what has been written on the target blog, together with a URL and the name of the blog.

    The specification for TrackBack and its implementation were developed by Six Apart, creators of the MovableType weblog and content management system.

    Aha, so it is invented by a specific blogging tool. And not the most popular one amongst developers. What I know about the way .text and cs implement trackbacks when posting is that they scan a post for links and will send a trackback to all links found. And it's up to the receiver what to do with them. What I have noticed about the way .text and cs deal with incoming trackbacks is that it's next to random. The most extreme examples I've seen is referring to some of my own posts and seeing my count rise by six. And there are lots of links to my post which didn't influence my count at all. So the tools used to post and to refer determine if a trackback is seen in the statistics.

    What's of real interest are referrals. In the statitstics of your blog it's a list of links which have been followed to find the post. Including links from google where you can see what search phrases have been used to find your story. Referral is also in wikipedia but no-one has taken the effort yet to enter it as an internet term.

    To sum it up:

    • Trackbacks are no open standard
    • Trackbacks depend on the blogging tools used
    • Referrals tell the real story

    Should CodeBetter switch from displaying trackback count to referral count ? Or to page hits ? A side problem would be we'de have to swich to scientific notation :) Imho we should display nothing, it's the content itself which counts.

     

  • Recognizing handwriting on a Tablet PC: beyond Strokes.ToString()

    Having spent 3 solid days on doing a crash-course VS 2005 training it's time for a little diversion. Finally I made the time for another post on programming the Tablet PC.

    The tablet PC is very good in recognizing handwriting, it will even make sense out of the worst scribblings you can imagine, for instance my own handwriting.

    The image shows the Tablet Input Panel (TIP) which is part of the tablet OS. It shows the textual representation of the ink, clicking a word shows a list of alternative results from the recognizer. In this post I will demonstrate how to do that in your own code.

    I have a Winform with on it: a panel to scrible, a button to clear the panel, a button to recognize and a listbox to display the results of the recognition. Here's the code :

    using System;
    using System.Drawing;
    using System.Collections;
    using System.ComponentModel;
    using System.Windows.Forms;
    using Microsoft.Ink;

    namespace TabletPCdemo
    {
       public class InkPad : System.Windows.Forms.Form
       {
          private System.Windows.Forms.Panel panel1;
          private System.Windows.Forms.ListBox listBox1;

          /// Required designer variable.

          private System.ComponentModel.Container components = null;
          private System.Windows.Forms.Button buttonRecognize;
          private System.Windows.Forms.Button buttonClear;

          [STAThread]
          static void Main()
          {
             Application.Run(new InkPad());
          }

          private InkOverlay ic;

          public InkPad()
          {
             InitializeComponent();
             ic = new InkOverlay(panel1);
             ic.Enabled = true;
          }

          protected override void Dispose( bool disposing )
          {
             if( disposing )
             {
                if(components != null)
                {
                   components.Dispose();
                   if (ic != null)
                      ic.Dispose();
                }
             }
          base.Dispose( disposing );
          }

          private void buttonClear_Click(object sender, System.EventArgs e)
          {
             // Wait for pen input to complete
             while (ic.CollectingInk);
             ic.Ink.DeleteStrokes();
             panel1.Invalidate();
          }

          private void buttonRecognize_Click(object sender, System.EventArgs e)
          {
             // Wait for pen input to complete
             while (ic.CollectingInk);
             // Any strokes drawn ?
             if (ic.Ink.Strokes.Count == 0)
             {
                MessageBox.Show("No ink written");
                return;
             }

             // Is this a tablet PC ?
             Recognizers myLanguages = new Recognizers();
             if (myLanguages.Count == 0)
            {
               MessageBox.Show("No recognizers isntalled !");
               return;
             }

             // Get the recognizer for en-us
             Recognizer myLanguage = myLanguages.GetDefaultRecognizer(0x0409);
             RecognizerContext myContext = myLanguage.CreateRecognizerContext();

             // Prefered results
             WordList myWords = new WordList();
             myWords.Add("Gekko");
             myContext.WordList = myWords;

             myContext.Strokes = ic.Ink.Strokes;

             RecognitionStatus myStatus;

             RecognitionResult myResult = myContext.Recognize(out myStatus);

             if (myStatus == RecognitionStatus.NoError)
             {
                RecognitionAlternates myAlternates = myResult.GetAlternatesFromSelection();
                listBox1.Items.Clear();
                foreach (RecognitionAlternate ra in myAlternates)
                   listBox1.Items.Add(string.Format("Result : {0}, Confidence: {1}", ra.ToString(), ra.Confidence.ToString()));
             }

             myContext.Dispose();

       }

    }

    It’s using the Microsoft.Ink namespace which is in the  Microsoft.Ink.dll

    In the constructor of the form a new InkOverlay object is created. This class has a large list of overloaded constructors. One of them takes a windows handle and another one accepts a windows control. It is important which constructor you are going to use as that will influence the level of code access security required to run the code. When you want to scrible on an ActiveX control there is only a windows handle available. When you want to scrible on a panel you should pass the control itself to the constructor. Your code will work just as well when you pass the panel’s handle but the rise in the cas will be enough to prevent your tablet code running in (the default security settings of) IE.

    The InkOverlay object is not a managed object so you have to clean up when finished with it. The Dispose method of the form invokes the Dispose  method of the inkoverlay. Now the inkoverlay is ready for use, having set it’s Enabled property you can scribble on the panel.

    The InkOverlay object has an Ink property which holds the strokes drawn by the pen. Clearing the panel is a matter of deleting the strokes, calling DeleteStrokes will do just that. Checking the CollectingInk property in a loop the code first waits untill all ink input is processed. Usually this is instantanious but sometimes you will see that there is a lag between the movement of your pen and the ink actually drawn. The loop will make sure the ink is ready. Having deleted the strokes you have to invalidate the panel so it will be redrawn. As an empty panel.

    When it comes to recognizing handwriting most tablet PC code examples show the oneliner:

    ic.Ink.Strokes.ToString();

    The Strokes class has overloaded the ToString method to return the most probable recognition result. The code given here will return all results, like the TIP.

    Before recognizing it performs two checks, to prevent exceptions. First of all there have to be some strokes to recognize. Makes sense, but you’ll get an exception trying to recognize an empty strokes collection. Next the code checks if there are any recognizers installed. You can install the ink assemblies on a non Tablet PC. Your app will work fine, using the mouse you can even scribble. But the recognizers are only installed on a real tablet. There are several recognizers available, for English, German, French, Chinese, a couple more and a couple more announced. The code has to pick the recognizer for a specific language using the GetDefaultRecognizer method. The parameterless overload checks for the language of the tablet the app is running on. But it does this in a (imho) buggy manner. Experimenting shows it checks for the regional settings and not the language settings. I’m running the English version of the Windows Tablet PC with my regional settings set to Dutch. The result is that GetDefaultRecognizer starts looking for a Dutch recognizer. Which is, alas, not (yet) available. To select the recognizer to use I pass in an LCID (locale ID) constant. This is a point where the COM base of the tablet API really shines through.

    To use the recognizer you need its context, which is created by the CreateRecognizerContext method. I want to steer the recognizer’s result and do so by adding a list of favored words to the context. Create a new WordList object and add your favorites; for the sake of demo I’ve only added “gekko” (the Dutch spelling of gecko) to mine. The strokes to be recognized are added to the context and a RecognitionResult object is created. Now all is set up and the Recognize method will perform the actual recognition, returning the result in the recognition object.

    Now I can read all recognition results, in order of probability, from the result using the GetAlternatesFromSelection method. Besides the text recognized also the Confidence the recognizer has can be read from the RecognitionAlternates. Don’t forget to dispose the context, it’s also an unmanaged resource.

    You can see that even if the recognizer got my bad writing right it does not have that much confidence. But my Gekko is favoured.

    Far better when I really do my best.

  • Blog'n my way to the PDC

    Channel9 has started a contest where you can win a ticket to the PDC, including an airline ticket and hotel. You take part by blogging. Including a post why you should win the prize. Do take part ! And here's my contribution :

    blogging my way to pdc

    Why do I want to go to the PDC ?

    Why do I want to get my ticket this way ?

    • I spent this year's conference budget on Windows Anywhere / VS live. Which was not too bad but by far not as vibrant as the PDC. I'm a one person shop so I have to pay all by myself and I really have no budget left.
    • I like to blog.

    As a teaser what you can expect from me here's a picture of the PDC 2003 which sums up why it was such a great event:

    Yes, it's a tablet PC !

  • Idispose the Finalizer

    This posts is not about managing .net objects !

    Darrell and John spread the word on the Finalizer, a robot running on .NET. It's a battlebot backup up by its own website. Great. But it is by long not something new. Over here in (er gaat niets boven) Groningen we have the RoboChallenge where robots compete. Not by smashing each other up but by catching colored balls. In an intelligent way like "get 5 green ones first and the black and yellow ones after that". The machines are runnning on a variety of software, from Linux to Java and .NET. Take this .net beauty, spotted on the first edition:

    This year was the second edition. My favorite ran on Java, the other big framework. In a presentation (54 MB rar download :< ! in Dutch..)  its creator demonstrated how he had created an enormous class library where the objects communicated using remote method invocation. No kidding. The strong point in using the framework approach that it was quite easy to finetune the systems parameters with little dialogs all over the place without losing the overview. It was a pitty the machine locked up in the first minute of the final round, else it would have been a clear winner. The actual winner was quite good as well, but they did not have such a fascinating software model.

    Next years edition will be open to machines from all over the world. I feel like joining based on the Finalizer software. I don't think my kids Lego will do for the mechanical part of the story :) But I do know a name for the project : IDispose. Anybody want to join ?

  • Turn a .NET winforms application into a (COM) automation server

    As a start to the next 2 years of blogging I want to look back on an "ancient" technology : COM, which is still the glue between software parts created with a great diversity of tools. COM brought me to .NET, exploring the world of COM in Delphi I discovered the .NET 1.0 betas. Never to leave again.

    COM support in .NET is good. You can make your .NET code accessible to any COM client (for instance VBscript) by setting the "Register for COM Interop" property in a project.

    Having done that all the public classes in your assembly are available as COM server classes. Public methods and properties are exposed as COM callable methods and props. You can finetune this by setting the ComVisible attribute on a class or method

    using System;
    using System.Runtime.InteropServices;

    namespace MyAutomationLibrary
    {

       [ClassInterface(ClassInterfaceType.AutoDual)]
       [ProgId("MyComClass.Test")]
       public class MyComClass
       {

          [ComVisible(false)]
          public void MyNonComMethod()
          {

    This makes building COM servers with .NET a snap.

    But there is one huge drawback: you can register only a dll for COM interop. And not an exe like a WinForms application. Making a Winforms application (COM) automatable requires a little work on the architecture of your app.

    • Creat a windows forms application
    • Add a class library to the solution
    • Add System.Windows.Forms to the references of the lib
    • Add a form to the class library
    • Add the classlibrary to the references of the winforms app
    • Delete the main form of the windows app
    • Add a new class to the winform application

    Your solution should now look like this

    By deleting the mainform from the windows app we have also removed the entry point of the application. By default on startup a .net executable searches for a static main method. VS generates this method for you in the mainform. The new entry point will now be in the MainClass.

    using System;
    using System.Windows.Forms;

    namespace MyAutomatableWinformApp
    {

       public class MainClass
       {

           [STAThread]
           static void Main()
          {
             Application.Run(new MyWinFormClassLibrary.MainForm());
          }

       }
    }

    It does exactly the same as the original main method, but it creates a form which is housed in the class library instead of the exe itself. Now you still have your winform app and have moved all “real” code into a class library.

    The next step is to make the application automatable. Add a new class libary to the solution and register it for COM interop. The library references the windows form class library and System.Windows.Forms. The Show method of the COM class will fire up the mainform found in the MyWinFormClassLibrary. Just creating the form and running it, like the Main method did, has one problem: the method will not return to the calling COM client untill the application has stopped running the mainform. That is when the winform app has terminated. To circumvent this the form has to be started in a separate thread. Given the great threading support in .NET this no big deal either.

    using System;
    using System.Runtime.InteropServices;
    using System.Threading;
    using System.Windows.Forms;

    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ProgId("MyWinComServer.Test")]
    public class MyComClass
    {

       private void formThread()
       {
          Application.Run(new MyWinFormClassLibrary.MainForm());
       }

       public void Show()
       {
          Thread t = new Thread(new ThreadStart(formThread));
          t.ApartmentState = ApartmentState.STA;
          t.Start();
       }

    }

    The actual code to run the mainform is in the private formThread method. Which is wrapped up in a ThreadStart delegate. A more detailed article on threading in winforms is here.

    This snippet of VBscript will test your code

    set o = CreateObject("MyWinComServer.Test")
    o.Show()
    msgBox("Click to stop")

    It creates the mainform. You can toy around with that until you click the script-messagebox. Which will terminate your main app.

    As the thread in the server interacts with the UI you have to set the thread’s appartmentstate to STA. Another thing to watch out for is when you want to access the form from other methods in your COM class. You will cross a thread boundary and have to Invoke methods and properties on the form. More details are in this article.

    That should do the trick. Of a very great value was this blogpost by Rick Strahl on automating winforms in general. This result is just a couple of lines code but .NET is doing some very powerfull things here. More than enough to get you into trouble, so feel free to chime in.

     

  • Grown up in public: 2 years of blogging

     Today is a celebration. Exactly two years ago my first blog-post showed up on the dotnetjunkies. At the time blogging was quite new to me, being an author for the junkies I was invited to start a blog. And the remainder is history, blogging has taken the place of writing articles. To some it even replaces visiting conferences. To me the best part of blogging is that it's highly interactive. It's not just a flow of information from writer to reader, the many valuable comments teach me a lot. The other very good thing is that the size of information chunks can be so much smaller. A small tidbit is enough for a post. Later on you can assemble several post, and their comments, into an article.
    So blogging feels like Growing up in public. Which was the titel of my former dnj blog. I had borrowed this name from a Dutch theater group, recently found out it's also the title of a Lou Reed song (watch out for the popup). Now I have grown up (somewhat) and very happy to be here on CodeBetter.

    Some statistics:

    Off to the next 2 years.

More Posts