Freigeben über


Building a WPF Emulator Part V

In our last episode the super hero(ok it was just me) was creating a device emulator for a KIOSK system based on the .NET Micro Framework. So far we’ve seen the application code from the custom emulator side of things but haven’t looked at any of the XAML yet. So what is this XAML stuff anyway? XAML is an XML language (Xml Application Markup Language) for describing hierarchies of components. In WPF those components are .NET classes. Instead of writing a lot of code to initialize and set up the UI for an application you can declare it in XAML. XAML was designed alongside of the Windows Presentation Framework (WPF) but the two are technically orthogonal. (This was a big surprise to me when I first started digging into WPF, the appendix on XAML in Chris Sells and Ian Griffith’s excellent book Programming WPFwas a real eye opener on that.)

The application XAML file is one of the first you see for the KIOSK emulator it looks like this:

 <Application
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  
     xmlns:app="clr-namespace:KioskEmulator"
     x :Class="KioskEmulator.App" 
     StartupUri ="UI\MainWindow.xaml" 
 >
     <Application.Resources>
     </Application.Resources>
 </Application>

The most important items of note for the application XAML in this sample are the StartupUri attribute that specifies the starting XAML window and the x:Class attribute which specifies the name of a class to generate for this application. Ultimately this XAML description generates a C# class that contains the basic application init. The generated code for that looks like this:

     public partial class App : System.Windows.Application {
  
         /// <summary>
         /// InitializeComponent
         /// </summary>
         [System.Diagnostics.DebuggerNonUserCodeAttribute()]
         public void InitializeComponent() {
  
             #line 5 "..\..\App.xaml"
             this.StartupUri = new System.Uri("UI\\EngMainWindow.xaml"
                                             , System.UriKind.Relative);
  
             #line default
             #line hidden
         }
  
         /// <summary>
         /// Application Entry Point.
         /// </summary>
         [System.STAThreadAttribute()]
         [System.Diagnostics.DebuggerNonUserCodeAttribute()]
         public static void Main() {
             KioskEmulator.App app = new KioskEmulator.App();
             app.InitializeComponent();
             app.Run();
         }
     }

Ahhhh – there’s Main()! Note that the class is declared as partial so that’s how it relates to the App code I showed in Part II of this series.

The App class from Part II contained a bit of application specific startup logic that is necessary for the application to function. Based on how things are often done with Windows Forms (and our sample emulators based on it) you’d expect that the code for the main window class of the emulator would have a lot of application specific stuff in it. But, as you probably guessed by how I point that out – you’d be wrong! Here’s what the main window class code looks like:

 using System.Windows;
  
 namespace KioskEmulator
 {
     /// <summary>
     /// Interaction logic for EngMainWindow.xaml
     /// </summary>
     public partial class EngMainWindow : Window
     {
         public EngMainWindow( )
         {
             InitializeComponent( );
         }
     }
 }

Honestly, that’s it - nothing more! This is one of those blow your mind / pull the rug out from under you kind of things when you first encounter it. (at least for me it was – what, wait no Onclick handlers, no Onbutton pressed handlers - what’s going on?) This is the true beauty of XAML and WPF and the Model View + View Model (MV+VM) design pattern.  There is no business logic in the UI – not a thing. Any code that is needed in the window class is entirely related to the UI. (We’ll see in a later post that the nicer looking designer version needs a little bit of code.) Once you get your head around this idea you’ll really start to love the WPF.

Of course the natural question from this new model is: If there’s no business logic in the window code how does the application work? Well that’s the job of data binding and the ViewModel as part of the MV+VM pattern. To help get a handle on that here’s the XAML for the main window (the engineers ugly version) [ NOTE: to keep things simple and focused I’ve removed the elements and attributes that are not relevant to understanding the connection to the ViewModel ] :

 <Window x:Class="KioskEmulator.EngMainWindow"
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:em="https://microsoft.com/MicroFramework/Emulator" 
     xmlns:app="clr-namespace:KioskEmulator" 
     Title="Kiosk Emulator"
   >
   <Canvas Height="367" Width="420">
     <Image em :TouchBehavior.TouchPort="{Binding TouchPort}" 
            Source ="{Binding LcdImage}" 
     />
     <Button em:ButtonPressBehavior.ChangedCommand="{Binding SetPinStateCommand}" 
             em:ButtonPressBehavior.PinName="Select" 
             em:ButtonPressBehavior.PropertyName="IsPressed" 
     >Select</Button>
     <Label app :DropCardBehavior.DropCardCommand="{Binding DropNfcCard}" 
            app:DropCardBehavior.ReaderType="NfcCard" 
   >Nfc Reader</Label>
     <Label app :DropCardBehavior.DropCardCommand="{Binding DropMagCard}" 
            app:DropCardBehavior.ReaderType="MagCard" 
     >MagStrip</Label>
     <ToggleButton IsChecked=" { Binding LED1,Mode =OneWay} ">LED1</ToggleButton>
     <ToggleButton IsChecked=" { Binding LED2,Mode =OneWay} ">LED2</ToggleButton>
     <ToggleButton IsChecked=" { Binding LED3,Mode =OneWay} ">LED3</ToggleButton> 
     <ToggleButton IsChecked=" { Binding RFIDLED,Mode =OneWay} ">RFIDLED</ToggleButton>
   </Canvas>
 </Window>

At the top I’ve added a couple extra namespace entries to reference the WPF emulator standard behaviors and a custom application one to access the drag and drop support for the card readers. The engineers design uses a canvas as the primary UI element container. This is usually not the best idea for production UI but is a simple way for a dev to get something going for basic testing. Inside the canvas is an image control. The first thing to notice about the image control is that it has the em:TouchBehavior.TouchPort="{Binding TouchPort}" attribute. This uses an attached property to create an attached behavior for touch support and binds it to a property on the ViewModel called TouchPort. ok, I can hear some readers thinking – “ok, right – huh?”. (Bet you didn’t know I could read minds through this here interweb thingy didya?) What that means is that we can define behavior for a UI Element independent of the actual element itself and then re-use it wherever we need to. Thus we can attach the TouchBehavior.TouchPort property to any UIElement and bind that property to a TouchPort in the Viewmodel and things get automatically hooked up. If the view model is derived from the EmulatorViewModel (part of my custom WPF emulator assembly) then all the work is done. The emulator View model will receive commands from the attached touch behavior and send it on to the emulator engine! So with the new WPF emulator library adding touch is as simple as one attribute on any UIElement in XAML – That’s what I call cool!. [Uh-oh, nerd alert!]

For the KIOSK the touch support is attached to an Image for the LCD, getting the LCD updates is also just a matter of setting up the data binding for the Image’s Source property to the ViewModel’s LcdImage property.

While the Kiosk hardware doesn’t have any buttons, it only has the LCD and touch panel, it was useful to run basic sample touch applications on the emulator as I was building up the WPF library to verify things worked. Thus the engineers view has the Select button in there. The button is using the ButtonPressBehavior, which has three attached properties. The first is a data bound command SetPinStateCommand, which comes from the general EmulatorViewModel, to change the state of a GPIO pin in the emulator based on a change in state of a boolean property. The PinName is the name used to register the Pin in the emulator class. Using a name helps keep things cleaner and allows for pin remapping in hardware revisions to impact only one place in the code. The third property is the Property name of the control that will trigger a change in the state. In this case we want to change the state of the pin with the state of the IsPressed property of the button class. Unfortunately the IsPressed property is not one that the Button class implementation will allow you to data bind directly to. Which is what drove the need for the ButtonPressBehavior.

The MagStrip and NFC Readers required drag and drop capabilities to be able to drag a data file onto the emulator to simulate swiping/tapping a card. Since a card could have both NFC and magstrip data I created a single application specific behavior for that and attached it to the label controls and data bind it to a command provided by the ViewModel. The ReaderType Property determines which type of card the reader supports so that the drag and drop behavior will show a proper icon as a file is dragged over the NFC or magstrip areas. (e.g. if you drag a card file with magstrip only data over the NFC area it should indicate you can’t drop that file on the NFC reader.)

The KIOSK Device has 3 LEDs that I emulated using a standard ToggleButton control (like a radio button without the “select only one of many” behavior). I use data binding to bind the IsChecked property of the button to the led property of the ViewModel. However, since the LED is an output it is read only on the ViewModel and therefore must be specified as OneWay binding. Otherwise, WPF will try to connect up to the property set method so that whenever the IsChecked property changes (say from clicking on the button) then the ViewModel is updated. That will generate an exception since the setter method doesn’t exist.

The really amazing thing about all this data binding and attached properties for behaviors is that it is completely re-usable without altering the ViewModel itself. The UI can be literally anything a designer can dream up and represent in XAML and the rest of the code doesn’t need to care one teensy little bit. Even better is that there doesn’t even need to be any UI!? Yep, you read that correctly - you can make unit testing or automated tests of all your core logic by exercising the ViewModel directly in the same way that the XAML windows would do. This is a big benefit to automated testing as it is more easily automated then GUI UI testing.

OK, that’s a lot to absorb, especially for us “old dogs”. I’ll leave things for the moment so there aren’t any heads exploding. Next post I’ll dig into the whole view model and data binding in more details along with the View model support classes.