Silverlight for Windows Embedded tutorial (step 6)
- by Valter Minute
In this tutorial step we will develop a very simple clock application that may be used as a screensaver on our devices and will allow us to discover a new feature of Silverlight for Windows Embedded (transforms) and how to use an “old” feature of Windows CE (timers) inside a Silverlight for Windows Embedded application.
Let’s start with some XAML, as usual:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="640" Height="480" FontSize="18"
x:Name="Clock">
<Canvas x:Name="LayoutRoot" Background="#FF000000">
<Grid Height="24" Width="150" Canvas.Left="320" Canvas.Top="234" x:Name="SecondsHand" Background="#FFFF0000">
<TextBlock Text="Seconds" TextWrapping="Wrap" Width="50" HorizontalAlignment="Right" VerticalAlignment="Center" x:Name="SecondsText" Foreground="#FFFFFFFF" TextAlignment="Right" Margin="2,2,2,2"/>
</Grid>
<Grid Height="24" x:Name="MinutesHand" Width="100" Background="#FF00FF00" Canvas.Left="320" Canvas.Top="234">
<TextBlock HorizontalAlignment="Right" x:Name="MinutesText" VerticalAlignment="Center" Width="50" Text="Minutes" TextWrapping="Wrap" Foreground="#FFFFFFFF" TextAlignment="Right" Margin="2,2,2,2"/>
</Grid>
<Grid Height="24" x:Name="HoursHand" Width="50" Background="#FF0000FF" Canvas.Left="320" Canvas.Top="234">
<TextBlock HorizontalAlignment="Right" x:Name="HoursText" VerticalAlignment="Center" Width="50" Text="Hours" TextWrapping="Wrap" Foreground="#FFFFFFFF" TextAlignment="Right" Margin="2,2,2,2"/>
</Grid>
</Canvas>
</UserControl>
This XAML file defines three grid panels, one for each hand of our clock (we are implementing an analog clock using one of the most advanced technologies of the digital world… how cool is that?). Inside each hand we put a TextBlock that will be used to display the current hour, minute, second inside the dial (you can’t do that on plain old analog clocks, but it looks nice).
As usual we use XAML2CPP to generate the boring part of our code.
We declare a class named “Clock” and derives from the TClock template that XAML2CPP has declared for us.
class Clock : public TClock<Clock>
{
...
};
Our WinMain function is more or less the same we used in all the previous samples. It initializes the XAML runtime, create an instance of our class, initialize it and shows it as a dialog:
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
if (!XamlRuntimeInitialize())
return -1;
HRESULT retcode;
IXRApplicationPtr app;
if (FAILED(retcode=GetXRApplicationInstance(&app)))
return -1;
Clock clock;
if (FAILED(clock.Init(hInstance,app)))
return -1;
UINT exitcode;
if (FAILED(clock.GetVisualHost()->StartDialog(&exitcode)))
return -1;
return exitcode;
}
Silverlight for Windows Embedded provides a lot of features to implement our UI, but it does not provide timers. How we can update our clock if we don’t have a timer feature? We just use plain old Windows timers, as we do in “regular” Windows CE applications!
To use a timer in WinCE we should declare an id for it:
#define IDT_CLOCKUPDATE 0x12341234
We also need an HWND that will be used to receive WM_TIMER messages. Our Silverlight for Windows Embedded page is “hosted” inside a GWES Window and we can retrieve its handle using the GetContainerHWND function of our VisualHost object.
Let’s see how this is implemented inside our Clock class’ Init method:
HRESULT Init(HINSTANCE hInstance,IXRApplication* app)
{
HRESULT retcode;
if (FAILED(retcode=TClock<Clock>::Init(hInstance,app)))
return retcode;
// create the timer user to update the clock
HWND clockhwnd;
if (FAILED(GetVisualHost()->GetContainerHWND(&clockhwnd)))
return -1;
timer=SetTimer(clockhwnd,IDT_CLOCKUPDATE,1000,NULL);
return 0;
}
We use SetTimer to create a new timer and GWES will send a WM_TIMER to our window every second, giving us a chance to update our clock. That sounds great… but how could we handle the WM_TIMER message if we didn’t implement a window procedure for our window?
We have to move a step back and look how a visual host is created. This code is generated by XAML2CPP and is inside xaml2cppbase.h:
virtual HRESULT CreateHost(HINSTANCE hInstance,IXRApplication* app)
{
HRESULT retcode;
XRWindowCreateParams wp;
ZeroMemory(&wp, sizeof(XRWindowCreateParams));
InitWindowParms(&wp);
XRXamlSource xamlsrc;
SetXAMLSource(hInstance,&xamlsrc);
if (FAILED(retcode=app->CreateHostFromXaml(&xamlsrc, &wp, &vhost)))
return retcode;
if (FAILED(retcode=vhost->GetRootElement(&root)))
return retcode;
return S_OK;
}
As you can see the CreateHostFromXaml function of IXRApplication accepts a structure named XRWindowCreateParams that control how the “plain old” GWES Window is created by the runtime.
This structure is initialized inside the InitWindowParm method:
// Initializes Windows parameters, can be overridden in the user class to change its appearance
virtual void InitWindowParms(XRWindowCreateParams* wp)
{
wp->Style = WS_OVERLAPPED;
wp->pTitle = windowtitle;
wp->Left = 0;
wp->Top = 0;
}
This method set up the window style, title and position. But the XRWindowCreateParams contains also other fields and, since the function is declared as virtual, we could initialize them inside our version of InitWindowParms:
// add hook procedure to the standard windows creation parms
virtual void InitWindowParms(XRWindowCreateParams* wp)
{
TClock<Clock>::InitWindowParms(wp);
wp->pHookProc=StaticHostHookProc;
wp->pvUserParam=this;
}
This method calls the base class implementation (useful to not having to re-write some code, did I told you that I’m quite lazy?) and then initializes the pHookProc and pvUserParam members of the XRWindowsCreateParams structure. Those members will allow us to install a “hook” procedure that will be called each time the GWES window “hosting” our Silverlight for Windows Embedded UI receives a message.
We can declare a hook procedure inside our Clock class:
// static hook procedure
static BOOL CALLBACK StaticHostHookProc(VOID* pv,HWND hwnd,UINT Msg,WPARAM wParam,LPARAM lParam,LRESULT* pRetVal)
{
...
}
You should notice two things here. First that the function is declared as static. This is required because a non-static function has a “hidden” parameters, that is the “this” pointer of our object. Having an extra parameter is not allowed for the type defined for the pHookProc member of the XRWindowsCreateParams struct and so we should implement our hook procedure as static. But in a static procedure we will not have a this pointer. How could we access the data member of our class? Here’s the second thing to notice. We initialized also the pvUserParam of the XRWindowsCreateParams struct. We set it to our this pointer. This value will be passed as the first parameter of the hook procedure. In this way we can retrieve our this pointer and use it to call a non-static version of our hook procedure:
// static hook procedure
static BOOL CALLBACK StaticHostHookProc(VOID* pv,HWND hwnd,UINT Msg,WPARAM wParam,LPARAM lParam,LRESULT* pRetVal)
{
return ((Clock*)pv)->HostHookProc(hwnd,Msg,wParam,lParam,pRetVal);
}
Inside our non-static hook procedure we will have access to our this pointer and we will be able to update our clock:
// hook procedure (handles timers)
BOOL HostHookProc(HWND hwnd,UINT Msg,WPARAM wParam,LPARAM lParam,LRESULT* pRetVal)
{
switch (Msg)
{
case WM_TIMER:
if (wParam==IDT_CLOCKUPDATE)
UpdateClock();
*pRetVal=0;
return TRUE;
}
return FALSE;
}
The UpdateClock member function will update the text inside our TextBlocks and rotate the hands to reflect current time:
// udates Hands positions and labels
HRESULT UpdateClock()
{
SYSTEMTIME time;
HRESULT retcode;
GetLocalTime(&time);
//updates the text fields
TCHAR timebuffer[32];
_itow(time.wSecond,timebuffer,10);
SecondsText->SetText(timebuffer);
_itow(time.wMinute,timebuffer,10);
MinutesText->SetText(timebuffer);
_itow(time.wHour,timebuffer,10);
HoursText->SetText(timebuffer);
if (FAILED(retcode=RotateHand(((float)time.wSecond)*6-90,SecondsHand)))
return retcode;
if (FAILED(retcode=RotateHand(((float)time.wMinute)*6-90,MinutesHand)))
return retcode;
if (FAILED(retcode=RotateHand(((float)(time.wHour%12))*30-90,HoursHand)))
return retcode;
return S_OK;
}
The function retrieves current time, convert hours, minutes and seconds to strings and display those strings inside the three TextBlocks that we put inside our clock hands.
Then it rotates the hands to position them at the right angle (angles are in degrees and we have to subtract 90 degrees because 0 degrees means horizontal on Silverlight for Windows Embedded and usually a clock 0 is in the top position of the dial.
The code of the RotateHand function uses transforms to rotate our clock hands on the screen:
// rotates a Hand
HRESULT RotateHand(float angle,IXRFrameworkElement* Hand)
{
HRESULT retcode;
IXRRotateTransformPtr rotatetransform;
IXRApplicationPtr app;
if (FAILED(retcode=GetXRApplicationInstance(&app)))
return retcode;
if (FAILED(retcode=app->CreateObject(IID_IXRRotateTransform,&rotatetransform)))
return retcode;
if (FAILED(retcode=rotatetransform->SetAngle(angle)))
return retcode;
if (FAILED(retcode=rotatetransform->SetCenterX(0.0)))
return retcode;
float height;
if (FAILED(retcode==Hand->GetActualHeight(&height)))
return retcode;
if (FAILED(retcode=rotatetransform->SetCenterY(height/2)))
return retcode;
if (FAILED(retcode=Hand->SetRenderTransform(rotatetransform)))
return retcode;
return S_OK;
}
It creates a IXRotateTransform object, set its rotation angle and origin (the default origin is at the top-left corner of our Grid panel, we move it in the vertical center to keep the hand rotating around a single point in a more “clock like” way.
Then we can apply the transform to our UI object using SetRenderTransform.
Every UI element (derived from IXRFrameworkElement) can be rotated! And using different subclasses of IXRTransform also moved, scaled, skewed and distorted in many ways. You can also concatenate multiple transforms and apply them at once suing a IXRTransformGroup object.
The XAML engine uses vector graphics and object will not look “pixelated” when they are rotated or scaled.
As usual you can download the code here: http://cid-9b7b0aefe3514dc5.skydrive.live.com/self.aspx/.Public/Clock.zip
If you read up to (down to?) this point you seem to be interested in Silverlight for Windows Embedded. If you want me to discuss some specific topic, please feel free to point it out in the comments!
Technorati Tags: Silverlight for Windows Embedded,Windows CE