GUI Tree View Client
This section explains how to implement the Example Entry/Exit Recipes in a GUI tree view client.
The following screenshot shows a tree view that displays two views of the recipe data.
At the top, for each tag that currently matches some recipe, we show the recipes it matches and the order in which we got the entry events. So for the tag 0011CE003200000558, 1/0 -> 0/0 means that the tag is in 1/0 and also 0/0, but it entered 1/0 first and the 0/0 afterwards. This shows how you can detect 'direction of motion' by putting together multiple recipes. The sequence in time in which recipes are activated or deactivated allows the application programmer to work out the direction the tag must have been moving.
At the bottom, each recipe and the tags that it contains, are shown.
The code for this example (in TagEntryExitExample.cs) is a bit more extensive and needs a bit of Windows Forms knowledge. Therefore, only the parts that relate to the abstract state maintenance are shown. The most important point to notice is that the events are executed in a separate thread; you might therefore have to use some mutual exclusion to protect the state.

#region Recipe / Tag containment state and protocol event handlers /// <summary> /// A flag to tell us whether we have yet been contacted by a sensor. /// </summary> bool AwaitingInitialConnection = true; // All the recipes mentioned in this session and the tags in them. // If we were expecting huge numbers of tags we would use some kind // of set structure to represent the tags matching each recipe, but // in this case we just stick with the list where find is O(N). Dictionary<uint, LinkedList<ulong>> RecipeTags = new Dictionary<uint, LinkedList<ulong>>(); // All the tags mentioned in this session and a list of the recipes // each one is in, in chronological order of entry. This demonstrates // how we can use recipe tag/entry events to determine 'direction of // travel'. Dictionary<ulong, LinkedList<uint>> TagRecipes = new Dictionary<ulong, LinkedList<uint>>(); /// <summary> /// Generate a 32 bit key out of two 16 bit ids /// </summary> /// <param name="id1">The recipe Id 1</param> /// <param name="id2">The recipe Id 2</param> /// <returns>The 32 bit recipe key generated from (id1,id2)</returns> uint Key(ushort id1, ushort id2) { uint result = id1; result <<= 16; result |= id2; return result; } /// <summary> /// Extract recipe ids from a 32 bit key /// </summary> /// <param name="key">The 32 bit recipe key</param> /// <param name="id1">The recipe Id 1</param> /// <param name="id2">The recipe Id 2</param> void ExtractIdsFromKey(uint key, out ushort id1, out ushort id2) { id1 = (ushort)(key >> 16); id2 = (ushort)(key & 0xFFFF); } /// <summary> /// Process tag entry or exit commands. /// </summary> /// <param name="insert">The mode -- insert if true, remove if false</param> /// <param name="key">A key made from the two tag ids</param> /// <param name="tags">The list of tags to insert/remove</param> void InsertOrRemove(bool insert, uint key, List<ulong> tags) { // Note that we generate a set of tags for the recipe contents // even if we have an exit event -- this is because the protocol // is able to assert the existence of a recipe (Id1,Id2) pair // by sending an exit event, and if a recipe has no matching tags // at start up then this is what it will do. The fact that there // is a recipe, but it is empty, is still potentially useful at // the application level. if (!RecipeTags.ContainsKey(key)) RecipeTags[key] = new LinkedList<ulong>(); // Insert or remove tags to/from the recipe contents as appropriate. LinkedList<ulong> contents = RecipeTags[key]; foreach (var tag in tags) if (insert) { if (!contents.Contains(tag)) contents.AddLast(tag); } else { LinkedListNode<ulong> node = contents.Find(tag); if (node != null) contents.Remove(node); } // Insert or remove to/from the trail of tag recipes. foreach (var tag in tags) if (insert) { if (!TagRecipes.ContainsKey(tag)) TagRecipes[tag] = new LinkedList<uint>(); TagRecipes[tag].AddLast(key); } else { if (TagRecipes.ContainsKey(tag)) { LinkedList<uint> recipes = TagRecipes[tag]; LinkedListNode<uint> node = recipes.Find(key); if (node != null) recipes.Remove(node); } } } /// <summary> /// Session start event handler. This is executed in the server thread /// so any processing must be reasonably short and, for WinForms programs, /// any GUI operations must be queued for evaluation on the main thread. /// The state will be accessed by this thread and the GUI thread, so we need /// to use some mutual exclusion to protect it. /// </summary> void Protocol_OnSessionStart() { lock (RecipeTags) { AwaitingInitialConnection = false; RecipeTags.Clear(); TagRecipes.Clear(); } InvokeDisplayUpdate(); } /// <summary> /// Tag/recipe exit event handler. This is executed in the server thread /// so any processing must be reasonably short and, for WinForms programs, /// any GUI operations must be queued for evaluation on the main thread. /// The state will be accessed by this thread and the GUI thread, so we need /// to use some mutual exclusion to protect it. /// </summary> void Protocol_OnExit(ushort id1, ushort id2, List<ulong> tags) { lock (RecipeTags) { InsertOrRemove(false, Key(id1, id2), tags); } InvokeDisplayUpdate(); } /// <summary> /// Tag/recipe entry event handler. This is executed in the server thread /// so any processing must be reasonably short and, for WinForms programs, /// any GUI operations must be queued for evaluation on the main thread. /// The state will be accessed by this thread and the GUI thread, so we need /// to use some mutual exclusion to protect it. /// </summary> void Protocol_OnEntry(ushort id1, ushort id2, List<ulong> tags) { lock (RecipeTags) { InsertOrRemove(true, Key(id1, id2), tags); } InvokeDisplayUpdate(); } #endregion
|