miercuri, 14 septembrie 2011

Silverlgith for Embedded #2 - Loading Dynamically UserControls

Yesterday I wanted to write a post about a classic but, fancy "Hello World!" using Silverlight for Embedded UI framework running on a WinCE 7 machine (or virtual machine). Anyway this kind of posts are already on the Internet, so after a few days of headaches I come with something else, maybe not so original, but good enough to help other people.

The idea is that I want to take the advantages of a WinCE 7 machine - low power, low form factor and build a fanless PC that can run in an industrial harsh environment with lots of dust. Moreover I want to give a resistive touchscreen (for workers' gloves) and a cool simple UI that can be used by anyone without an initial training. For me Silverlight for Embedded sounded like a valuable framework and I decided to use it, but like always the things are not as they seems to be. Yeah, that's a powerful framework built on top of OpenGL and DirectX, but not so friendly at least at first sight.

My goal was to build a full screen UI with several screens (similar to webpages) that you can navigate back and forth and insert some data in these forms to be saved in a database somewhere remote on a server. 

The first problem is how you can abstract this notion of screen and how can you make a minimal navigator? I want always to remain in the same application because I need to share some information between screens similar with the Session of a web-application.

The Session object could be a DataPool implemented with a minimal Singleton. But the core problem remains the screens and the navigation mechanism between them.

After reading and reading and walking through the samples and the generated code during project conversion from Blend 3 to Visual Studio with WEST I figured out a solution. I noticed that in fact the MainPage is a UserControl that is loaded during initialization phase of the Windows application (the WinMain stuff), so my idea was to somehow dynamically load user controls. 


My first attempt was to remove the first control created during initialization process and replaced with a new UserControl, but this approach even if it was a good idea as a principle was a failure. Probably it's still technically possible but is too complicated and unsafe, so I realized that is a dead end.


The approach was to keep the skeleton of the application as it was generated by Microsoft guys and find something else. So I added two more controls in the Blend 3 project, as it can be seen in the following image:


So, the App.xaml belongs to Microsoft and we don't have to touch it, the MainPage.xaml will act as a main container where all the other UserControls are going to be loaded. For the sake of this demonstration I created two UserControls: UserControl1.xaml and UserControl2.xaml, they will be the children of MainPage.xaml.


Next step is to go in MainPage.xaml and design it in a manner that can load these two controls like in the next picture:




As you can I split the LayoutRoot which is a grid in two rows and in the upper row I placed a navigation bar with two simple buttons: "Load Control 1" and "Load Control 2". The lower row is going to be the place holder of the dynamic loaded UserContols, for this purpose I added a new Grid with one row and one cell named "HostGrid".


For readability purposes UserControl1 is a big red square, while UserControl2 is blue.
Next step is to go in Visual Studio, create a new Silverlight for Embedded project and import the Blend 3 project. We open MainPage.xaml and from Windows Embedded Events window we will select btnCtrl1 and attach a handler to the Click event, the same will be done to btnCtrl2. We will write the following code-snippet:


// ============================================================================
//  btnCtrl1_Click
// 
//  Description: Event handler implementation
//
//  Parameters:  pSender - The dependency object that raised the click event.
//               pArgs - Event specific arguments.
// ============================================================================
HRESULT MainPage::btnCtrl1_Click (IXRDependencyObject* pSender, XRMouseButtonEventArgs* pArgs)
{
    HRESULT hr = E_NOTIMPL;

    if ((NULL == pSender) || (NULL == pArgs))
    {
        hr = E_INVALIDARG;
    }

    IXRFrameworkElementPtr pRoot;
    IXRVisualHostPtr pVisualHost;
    App::GetVisualHost(&pVisualHost);

    pVisualHost->GetRootElement(&pRoot);

    XRPtr pControlTemp;
    pRoot->FindName(L"UserControl1", &pControlTemp);

    XRPtr pControlTemp2;
    pRoot->FindName(L"UserControl2", &pControlTemp2);

    if (pControlTemp2)
    {
        pControlTemp2->SetVisibility(XRVisibility_Collapsed);
    }

    if (0 == pControlTemp)
    {
        IXRGridPtr pGrid;
        pRoot->FindName(L"HostGrid", &pGrid);

        IXRUIElementCollectionPtr pCollection;
        pGrid->GetChildren(&pCollection);

        IXRApplicationPtr pApplication;
        App::GetApplication(&pApplication);

        XRPtr pControl;
        hr = pApplication->CreateObject(__uuidof(UserControl1),&pControl);
        pControl->SetName((TEXT("UserControl1")));

        pCollection->Add(pControl, NULL);
    }
    else
    {
        pControlTemp->SetVisibility(XRVisibility_Visible);
    }

    return hr;
}


As you can see first I am getting the root element, and we look for UserControl1 and UserControl2. If UserControl2 was found we set the visibility collapsed, since we want to display UserControl1. 
The next step is to check if UserControl1 was created and if yes set it on Visible, otherwise create it and display it. I think the code is self describing. In MainPage.cpp I included UserControl1.h and UserControl2.h

First Control loaded



Second Control loaded
Above are two print-screens from a WinCE 7 running inside a Virtual PC machine, when we press first button it is loaded UserControl1, when is pressed the second button is loaded UserControl2.


Conclusions


The solution is simple, but it can be developed. For example a nice thing to have would be a Screen Manager like a screen factory and based on some IDs to give us the instance we need. The code can be refactored the creation of the screen and the check that if already exists or not is the same for any screen so we could have it in a helper class.


Another possible improvement would be to move the navigation structure in an XML file, and validate somehow the transitions from screen to screen. Having this for complex HMIs we could think for using a finite-state machine like IBM Rational Rapsody OXF framework.


The developer should evaluate if it worth to keep all the screens in memory or not. It depends on the memory of the device, the number of screens and the size in memory of each screen. Personally if I would have a light HMI I would keep all the screens in memory for a fast user-experience.


Notes:


The code above is just an example concept and needs to be polished before ever be used in production.
The solution is based on a Microsoft WinCE 7 white-paper called: Silverlight for Windows Embedded Developer's Guide the second tutorial "Create a Custom User Control in Silverlight for Windows Embedded"
To setup a Virtual PC with WinCE 7 on it you can follow other two whitepapers:
Getting Started with Virtual CEPC
Advanced Virtual CEPC 


0 comentarii:

Despre mine

Fotografia mea
Just another guy from the old city of Iaşi