다음을 통해 공유


Silverlight for Windows Embedded CE 6.0: First sample codes (stress and multiple instances of a control in separate DLL)

  • Intro about SWE (Silverlight for Windows Embedded)
  • Sample code for simple starting stress-test
  • Sample code showing multiple instances of a control, implemented as separate DLL

sweUIs 

Part of my job as Support Engineer is to handle requests coming from ISVs developing applications for Windows CE: this is not always an easy task because contrarily to Windows Mobile, for which OEMs must stick to some specifications (otherwise their OS would fail the “Windows Mobile Logo Test Kit”), when it comes to Windows CE (or “Windows Embedded CE”, you know what I mean…) the OEMs have the complete freedom of developing whatever module or driver to be included in their OS, so in some cases the only option to understand a problem’s cause or to reach a specific goal programmatically, is to engage the OEM. I surely have already talked about this in the past… Happy

So, requests from ISVs targeting Windows CE are rare compared to the ones for Windows Mobile, but after the release of Windows Embedded CE 6.0 R3 (“Cashmere” ) Microsoft did on late September 2009, I’ve started handling some very interesting cases about Silverlight for Windows Embedded (“SWE” from now on). During past months many have discussed about this topic and how “particular” it seems to be programming for Silverlight in C++ instead that in .NET… so I’d like to share some info and sample code that I’ve personally been using so far.

Before doing that, even just because this is the very first post about SWE I’m writing, let me digress a bit so that we can all be on the same page. Above all, if you have no idea what SWE is at all about, then I think that nothing else than watching it in action would help on describing it, and apart from some other videos from latest conferences we may benefit of a 15' minutes-long demo recorded by Mike Hall back in June 2009 together with Jeff MacDuff and Todd Segal, available thru his post Creating compelling user interfaces for embedded devices(even if Jeff and Todd paid attention not to disclose the “name” of the underlying technology, we now know it’s SWE! Hot).

So, the idea is very simple: just divide the job done by designers and by developers and let everyone do what is good at. Designers have their tools to develop the UI (for example, Expression Blend) and developers have theirs to develop the Business Logic (for example, Visual Studio). Developers won’t even need to know how the UI looks like in fact (my sample code below are an example for that). More about this idea is available through the document Introduction to Application Development with Silverlight for Windows Embedded.

The important thing is that those out-of-browser applications are possible through a powerful engine, i.e. the XAML Runtime, that takes care of glueing the UI to the code-behind by supporting a subset of Silverlight 2 XAML. The object model to programmaticaly interact with such runtime is native.

Other introductive videos, as reported by Olivier Bloch in his post:

Now: imagine you’re a Windows Mobile developer or in any case not a CE6.0 OEM – this means you don’t have the tools to even start investigating how programming for SWE looks like. If that’s the case, remember that precisely as it was for its predecessors, also Windows Embedded CE 6.0 R3 has its own evaluation edition that you can freely download and that is fully functional for 6 months. Note that this is no longer a separate IDE as it was for Windows CE 5.0: it’s an addin that integrates with VS2005 SP1 – follow all the steps here. The evaluation copy includes the software tool for interaction design, Expression Blend 2.0 and gives you the ability to create a so-called “Private SDK” related to the XAML Runtime-powered CE image (the required component is SYSGEN_XAML_RUNTIME) and containing a Device Emulator image targetable from VS2008 directly.

Still about tools, Olivier Bloch explained here why there’s a lack of really tied integration between the UI and the code-behind: “[…] Well here is the answer: there has been some decisions made to make this technology available as is because our customers were in urgent need of this technology. ”. I’ve not yet personally tested it, but this missing functionality seems to be provided by the (Italian! Winking) Microsoft MVP Walter Minute through his XAML2CPP tool.

So, after this quick intro, what I’d like to share here is:

  1. A starting sample code useful for testing purposes
  2. A sample showing how to use multiple instances of a SWE Custom User Control library (DLL separate from the SWE Application)

The sample is very easy to read and I used it as starting poing to run specific tests, for example about automating resources-stressing. The code is based on the documentation Create a Silverlight for Windows Embedded Application:

 //global vars
 IXRStackPanelPtr m_pStackPanel; 
 IXRApplicationPtr m_pApp;
  
  
 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
 {
     int exitCode                    = 0;
     HRESULT hr                      = S_FALSE;
     IXRVisualHostPtr vhost          = NULL;
     IXRFrameworkElementPtr root     = NULL;
  
     //Initialize Xaml Runtime
     if (!XamlRuntimeInitialize())
         return -1;
  
     //Get XRAppInstance
     hr = GetXRApplicationInstance(&m_pApp);
     CHR(hr);
  
     XRWindowCreateParams wp;
     ZeroMemory(&wp, sizeof(XRWindowCreateParams));
     wp.Style       = WS_OVERLAPPED | WS_SYSMENU;
     wp.pTitle      = L"SWE Testing";
     wp.Left        = 0;
     wp.Top         = 0;
  
     //Retrieve resource associated to the PAGE.XAML file
     XRXamlSource xamlsrc;
     xamlsrc.SetResource(hInstance,TEXT("XAML"), MAKEINTRESOURCE(IDR_XAML_PAGE));
  
     //Create host
     hr = m_pApp->CreateHostFromXaml(&xamlsrc, &wp, &vhost);
     CHR(hr);
  
     //Get Root Element of the XAML
     hr = vhost->GetRootElement(&root);
     CHR(hr);
     
     //Get the Stack Panel
     hr = root->FindName(TEXT("LayoutRoot"), &m_pStackPanel); //Grid = StackPanel
     CHR(hr);
  
     //TODO: Add test code here
     //For automation purposes, create a Timer
     //Automation testing code in TimerProc function
     Timer = SetTimer(0, 0, 250, TimerProc);
  
     UINT exitcode;
     hr = vhost->StartDialog(&exitcode);
     CHR(hr);
  
 Exit:
     XamlRuntimeUninitialize();
  
     return (int)hr;
 }
  
  
 void CALLBACK TimerProc(HWND hwnd, UINT, UINT, DWORD dwTime)
 {
     //TODO: Automation code here
     //... 
  
     //Just to see the UI refreshed
     InvalidateRect(GetActiveWindow(), NULL, TRUE);
 }

In another case the ISV was interested on maintaining main application (or main page) separate from the control DLLs, so that he could re-use the control separately by other applications. Also, he was interested on having multiple instances of the same control within the same application.

What I could learn through this Service Request is that the XAML Runtime takes care of (quite) everything for you! Love Struck I left many comments in the sample code (for me at least!) but probably the most important bit here is that a control class is derived from XRCustomUserControlImpl<Class [,Interface]> , therefore it’s an abstract class hence can’t be instantiated and needs to have static functions only. This means that you can’t really talk about “visual controls hosted in an application” – what happens instead is that the XAML Runtime executes and renders different bits of XAML code. So, look at “controls” as “chunks of XAML” (this is not really object-oriented…). This also means that before using the control, you need to initialize it becasue before calling IXRApplication::CreateHostFromXaml from the application EXE code (note closely where CreateHostFromXaml is called in WinMain), the XAML Runtime must know all the associations between Classes and XAML Namespaces.

In other words, the “initialization” of a control should be done in2 steps:

  1. Set up the global var for the application instance so that Application’s EXE and Control’s DLL share the same and secondly invoke .Register on the control so that the XAML Runtime knows how to associate XAML Namespace and control class [in the sample, this is MyControl::MyControlPreInitialize]
  2. Initialization of the control, for example in terms of delegates associated to its events [in the sample, this is MyControl::MyControlInitialize]
 //CONTROL CLASS DECLARATION 
 class __declspec(uuid("{1F8F37C1-CA69-4894-A6AA-8667F9387E2E}")) //Change GUID!
 MyControl : public XRCustomUserControlImpl<MyControl>
 {
     public:
         static HRESULT GetXamlSource(XRXamlSource* pXamlSource);
         static HRESULT Register(HINSTANCE hInstance);
  
         //Added these functions so that they can be exported through a .LIB (created by adding a .DEF to the project)
         //Any exe application using this control will need to link the .LIB
         static HRESULT MyControlInitialize(IXRFrameworkElementPtr root, LPCTSTR pszControlClassName);
         static HRESULT MyControlPreInitialize(HINSTANCE hInstance);
 };
  
  
 //*************************************************************************************
  
 //Standard implementation of XRCustomUserControlImpl<>.GetXamlSource
 HRESULT MyControl::GetXamlSource(XRXamlSource* pXamlSource)
 {
     //Retrieve the resource (the XAML file) from the DLL Module
     pXamlSource->SetResource((HINSTANCE)GetModuleHandle(lpThisModule), TEXT("XAML"), MAKEINTRESOURCE(IDR_XAML_MYCTRL));
     return S_OK;
 }
  
 //Standard implementation of XRCustomUserControlImpl<>.Register
 HRESULT MyControl::Register(HINSTANCE hInstance)
 {
     HRESULT hr = S_FALSE;
     hr = XRCustomUserControlImpl::Register(__uuidof(MyControl), L"MyControl", L"clr-namespace:SLForEmbedded");
     CHR(hr);
  
 Exit:
     return hr;
 }
  
  
 // Setup the global var for the application instance and invoke .Register on the control.
 HRESULT MyControl::MyControlPreInitialize(HINSTANCE hInstance)
 {
     s_appInstance = hInstance;
  
     HRESULT hr = S_FALSE;
  
     /*LEFT FOR COMMENTING PURPOSES, IN CASE THE .XAML WILL CONTAIN IMAGE or FONT <Application.Resources>
     ////Initialize Xaml Runtime --> If needed, then add MyControl::UnInitialize
     //if (!XamlRuntimeInitialize())
     //    return -1;
 
     ////1. Get XRAppInstance 
     //IXRApplicationPtr app;
     //hr = GetXRApplicationInstance(&app);
     //CHR(hr);
 
     //2. Add control resource 
     /*This is needed when there are resources within the XAML of type XAML_RESOURCE
     //hr = app->AddResourceModule(hInstance);
     //CHR(hr);
 
     
     // ONLY IF <Application.Resources> IN THE APPLICATION's XAML:
     //The following code shows an example of XAML markup that must be parsed 
     //into a resource dictionary:
     //<Application.Resources>
     //    <SolidColorBrush x:Key="Text_DefaultColor" Color="#FF292F33"/>
     //</Application.Resources>
     //
     //IXRResourceDictionary* pResourceDictionary;
     //XRXamlSource Source;
     //Source.SetFile(MAKEINTRESOURCE(IDR_XAML_MYCONTROL));
     //hr = app->LoadResourceDictionary(&Source, &pResourceDictionary);
     //CHR(hr);
     //hr = app->GetResourceDictionary(&pResourceDictionary);
     //CHR(hr);
     */
  
  
     //3. Register control's Namespace
     hr = MyControl::Register(hInstance);
     CHR(hr);
  
 Exit:
     return hr;
 }
  
  
 //Initialize the control and event handlers
 HRESULT MyControl::MyControlInitialize(IXRFrameworkElementPtr root, LPCTSTR pszControlClassName)
 {
     HRESULT hr = S_FALSE;
  
     IXRControlPtr m_myUserControl;
     BtnEventHandler m_btnUserControlHandler;
     IXRDelegate<XRMouseButtonEventArgs>* m_btnUserControlDelegate;
     IXRButtonBasePtr m_btnUserControl;
  
     //hr = root->FindName(TEXT("MyUserControl"), &m_myUserControl);
     hr = root->FindName(pszControlClassName, &m_myUserControl);
     CHR(hr);
  
     //m_btnUserControlHandler.m_StoryboardPaused = FALSE;
     //BtnEventHandler::m_StoryboardPaused = FALSE;
     m_StoryboardPaused = FALSE;
  
     hr = m_myUserControl->FindName(TEXT("btnUserControl"), &m_btnUserControl);
     CHR(hr);
  
     hr = CreateDelegate(&m_btnUserControlHandler, &BtnEventHandler::btnUserControlOnClick,&m_btnUserControlDelegate);
     CHR(hr);
  
     hr = m_btnUserControl->AddClickEventHandler(m_btnUserControlDelegate);
     CHR(hr);
  
 Exit:
     return hr;
 }
  
  
 //TODO: Add event handler code
 class BtnEventHandler
 {
 public:
     HRESULT btnUserControlOnClick(IXRDependencyObject* pSender, XRMouseButtonEventArgs* pArgs)
     {
         MessageBox(GetActiveWindow(), L"User Control button clicked.", L"Event Handler", MB_OK | MB_ICONINFORMATION);
         return S_OK;
     }
 };

For the application:

 //Application's MAIN Entry Pointint WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
 {
     int exitCode                    = 0;
     HRESULT hr                      = S_FALSE;
     IXRApplicationPtr app           = NULL;
  
     //(let's keep it simple here...)This is not needed when the XAML has not <Application.Resources>
     //IXRResourceManager* pResourceMgr = NULL;
     IXRVisualHostPtr vhost          = NULL;
     IXRFrameworkElementPtr root     = NULL;
     IXRButtonBasePtr btn            = NULL;
  
     //showing a page with its own button control
     //<Button Margin="200,200,200,200" Content="Button" x:Name="MyButton"/>
     IXRDelegate<XRMouseButtonEventArgs>* clickdelegate = NULL;
     PageBtnEventHandler btnHandler;
  
  
     //Initialize Xaml Runtime
     if (!XamlRuntimeInitialize())
         return -1;
  
     //Get XRAppInstance
     hr = GetXRApplicationInstance(&app);
     CHR(hr);
  
     ////An application should register an IXRResourceManager object for Silverlight
     ////before the application executes any functionality related to loading resources.
     ////<Image Source=foo.jpg" />
     ////<TextBlock x:Name="GridTextBlock10" Grid.Row="1" Grid.Column="0" FontSize="14" FontFamily="castelar.ttf#Castellar" FontStyle='Italic'  Text='Hello'/>
     // -->
     ////IXRResourceManager* pResourceMgr;
     //hr = app->RegisterResourceManager(pResourceMgr);
     //CHR(hr);
  
     //This is needed when there are resources within the XAML of type XAML_RESOURCE
     //hr = app->AddResourceModule(hInstance);
     //CHR(hr);
  
     XRWindowCreateParams wp;
     ZeroMemory(&wp, sizeof(XRWindowCreateParams));
     wp.Style       = WS_OVERLAPPED | WS_SYSMENU;
     wp.pTitle      = L"SWE Testing";
     wp.Left        = 0;
     wp.Top         = 0;
  
     //Retrieve resource associated to the PAGE.XAML file
     XRXamlSource xamlsrc;
     xamlsrc.SetResource(hInstance,TEXT("XAML"), MAKEINTRESOURCE(IDR_XAML_PAGE));
  
     //Register control with application instance
     //This method added so that the control code can do init and call Register on the control
     //so that the control's class is tied to the XAML Runtime's namespace
     //and this is done in control's DLL code
     hr = MyControl::MyControlPreInitialize(hInstance);
     CHR(hr);
  
     //Create host
     hr = app->CreateHostFromXaml(&xamlsrc, &wp, &vhost);
     CHR(hr);
  
     //Get Root Element of the XAML
     hr = vhost->GetRootElement(&root);
     CHR(hr);
  
     //MyButton is a control directly defined and used by the application
     //defined in PAGE.XAML, look at x.Name of the <Button> element
     //left here just to show a control example inside the same application
     hr = root->FindName(TEXT("MyButton"), &btn);
     CHR(hr);
     hr = CreateDelegate(&btnHandler,&PageBtnEventHandler::OnClick,&clickdelegate);
     CHR(hr);
     hr = btn->AddClickEventHandler(clickdelegate);
     CHR(hr);
  
  
     //MyControl is the control exported by SilverlightControl DLL project
     //deriving from XRCustomUserControlImpl<MyControl>, it's an abstract class
     //i.e. can't be instantiated and needs to have STATIC functions only
     hr = MyControl::MyControlInitialize(root, TEXT("MyUserControl"));
     CHR(hr);
  
     hr = MyControl::MyControlInitialize(root, TEXT("MyUserControl2"));
     CHR(hr);
  
  
     UINT exitcode;
     hr = vhost->StartDialog(&exitcode);
     CHR(hr);
  
 Exit:
     RELEASE_OBJ(clickdelegate);
     XamlRuntimeUninitialize();
  
     return (int)hr;
 }
  
  
 //Handler of a button included directly in the EXE
 class PageBtnEventHandler
 {
     public:
         HRESULT OnClick(IXRDependencyObject* source,XRMouseButtonEventArgs* args)
         {
             MessageBox(NULL,TEXT("Click!"),TEXT("Button"),MB_OK);
             return S_OK;
         }
 };

Note how straightforward the XAML for the page (main application) is – quite intuitively, it just contains one line for each control:

 <UserControl
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
     x:Class="SLForEmbedded.Page"
     xmlns:SLForEmbedded="clr-namespace:SLForEmbedded"
     Width="640" Height="480">
  
   <Grid x:Name="LayoutRoot" Background="White">
     <Button Margin="200,200,200,200" Content="Button" x:Name="MyButton"/>
     <SLForEmbedded:MyControl HorizontalAlignment="Left" VerticalAlignment="Top" x:Name="MyUserControl"/>
     <SLForEmbedded:MyControl HorizontalAlignment="Right" VerticalAlignment="Top" x:Name="MyUserControl2"/>
   </Grid>
 </UserControl>

The XAML for the control may be useless for this post, however for the sake of completeness:

 <UserControl
     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
     xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
     mc:Ignorable="d"
     x:Class="SLForEmbedded.MyControl">
     <Grid x:Name="LayoutRoot">
         <Button Height="25" Width="125" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,10,0,0" Content="Click User Control" x:Name="btnUserControl"/>
     </Grid>
 </UserControl>

As usual, macros were:

 #pragma once
  
 #ifndef MACROS
 #define MACROS
  
 #define _ExitLabel Exit
 #define _ExitCode exitCode
  
 #define CHR(hResult) \
     if(S_OK != hResult) { hr = (hResult); goto _ExitLabel;} 
  
 #define RELEASE_OBJ(s)  \
     if (NULL != s)      \
     {                   \
         s->Release();   \
         s = NULL;       \
     }
 #endif

 

 

 

 

Hope this may help out developers learning SWE (like myself… Winking)

Cheers,
~raffaele

Comments

  • Anonymous
    October 26, 2010
    Thanks a lot and ask a question: environment: silverlight for WinCE 6.0 R3 question: Click the button in the main window then how to show the child window?

  • Anonymous
    October 27, 2010
    I didn't able to find stress test code from your sample.

  • Anonymous
    October 27, 2010
    Dear ! thanks for your post, please notify the "Sample code for Stress-test" from your code. I didn't able to find stress test code from your sample. Thanks.

  • Anonymous
    October 28, 2010
    Hi Sabarinathan, the stressing code can be included in the TimerProc function, as I mentioned:  //TODO: Add test code here      //For automation purposes, create a Timer      //Automation testing code in TimerProc function      Timer = SetTimer(0, 0, 250, TimerProc); void CALLBACK TimerProc(HWND hwnd, UINT, UINT, DWORD dwTime) {    //TODO: Automation code here    //...    //Just to see the UI refreshed    InvalidateRect(GetActiveWindow(), NULL, TRUE); } For example, in my specific cases I wanted to add and remove "many" buttons at runtime. So the "automation-code" was something like:    for (int i=1; i<1000; i++)    {        CreateButton(i); // check HRESULT    } where: // Create a button and add this into the stack panel HRESULT CreateButton(int number) {            IXRUIElementCollectionPtr children = NULL;            IXRButtonPtr button = NULL;            HRESULT hr = S_OK;            if (!FAILED(m_pStackPanel->GetChildren(&children))) // get the IXRUIElementCollection            {                        int index = 0;                        ULONG refCount = 0;                        if (!FAILED(m_pApp->CreateObject(&button))) // Create the Button                        {                                    button->SetHeight(20);                                    hr = children->Add(button, &index); // add this into the collection                                    if (FAILED(hr))                                    {                                                return hr;                                    }                        }                      }            else                        return S_FALSE;            return S_OK; } As you can imagine, the "stressing" depends on your scenario. HTH!

  • Anonymous
    October 28, 2010
    > question: Click the button in the main window then how to show the child window? Hi Alex, I'm going re-using the same words that the Italian MVP Walter Minute used to answer a query very similar to yours. If you don't know Walter's blog yet, pls set it in your bookmarks for Silverlight for Windows Embedded! :-) the blog is geekswithblogs.net/WindowsEmbeddedCookbook. "You can create a new page following the same operation sequence used in the WinMain to create the main window. Using CreateHostFromXaml you can load a new XAML object and create a Window that could be activated using ShowDialog" (geekswithblogs.net/.../silverlight-for-embedded-tutorial.aspx) Remember also that windows can have styles and extended styles, as documented at msdn.microsoft.com/.../ee503310.aspx. HTH!

  • Anonymous
    October 28, 2010
    Dear ,Thank u very much for support, really i got some idea using your sample, will get back once done automate stress test. cheers, sabari

  • Anonymous
    November 09, 2010
    HI Raff, We done the automated testing tool using ur sample code, thanks for ur gr8 support. Regards, saba