Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

I can see my home screen with Windows Phone 7

DZone's Guide to

I can see my home screen with Windows Phone 7

· Mobile Zone
Free Resource

Discover how to focus on operators for Reactive Programming and how they are essential to react to data in your application.  Brought to you in partnership with Wakanda

Yesterday I already talked about accessing your PC webcam from a Windows Phone 7 device. I figured that the same principle (well, the core engine) can be applied to an application that will be able to share the local screen (applied to Windows systems).

There are the same three basic components - the desktop application that will capture the current state of the user's screen. I must mention that this can be re-organized as a standalone Windows service, but for testing purposes a simple console application will do. Then there is the intermediary link - the WCF service that will get (and store) the binary data and at the same time will be serving it to the requesting application.

NOTE: You can download the source code for the service here.

And there is the mobile application that will actually display the data.

Starting with the Windows client, I decided to simply go with a console application so that it will be easier to focus on the functionality and not the UI. There is an important point you should consider here. The first idea that comes to mind would be using default classes provided by .NET Framework to take a screenshot. However, there are significant limitations if you decide to go that way - the main one being the fact that there is no way to take a screenshot while the application is minimized, for example. Of course there are workarounds for this, but what I am going to do is I am going to use WinAPI directly.

As I said, I already have a console application so all I have to do is implement custom methods. First of all, what I need to do is implement the screen capturing mechanism. If you, for example, would follow this example , you should have two custom classes in your project - USER32API and GDI32API (naming may vary).

USER32API is responsible for accessing methods from user32.dll - it is the library that manipulates the user-accessed elements of the UI (e.g. windows, dialogs, desktop), On the other hand, GDI32API is going to facilitate access to the methods exposed by gdi32.dll - the library responsible for the Graphics Device Interface, that allows us to perform specific operations on image data.

That being said, here is what I have added to the app:

static class GDI32API
{
[DllImport("gdi32.dll")]
public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest,
int nWidth, int nHeight, IntPtr hObjectSource,
int nXSrc, int nYSrc, int dwRop);

[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC,
int nWidth, int nHeight);

[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleDC(IntPtr hDc);

[DllImport("gdi32.dll")]
public static extern bool DeleteDC(IntPtr hDC);

[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
}

static class USER32API
{
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}

[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();

[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);

[DllImport("user32.dll")]
public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);

[DllImport("user32.dll")]
public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect);
}

In the main program class I added the Capture method that will return the current screen state:

static Image Capture(IntPtr handle)
{
IntPtr hSource = USER32API.GetWindowDC(handle);
USER32API.RECT wRect = new USER32API.RECT();
USER32API.GetWindowRect(handle, ref wRect);
int width = wRect.Right - wRect.Left;
int height = wRect.Bottom - wRect.Top;
IntPtr hDest = GDI32API.CreateCompatibleDC(hSource);
IntPtr hBitmap = GDI32API.CreateCompatibleBitmap(hSource, width, height);
IntPtr hOld = GDI32API.SelectObject(hDest, hBitmap);
GDI32API.BitBlt(hDest, 0, 0, width, height, hSource, 0, 0,
0x00CC0020);
GDI32API.SelectObject(hDest, hOld);
GDI32API.DeleteDC(hDest);
USER32API.ReleaseDC(handle, hSource);

Image image = Image.FromHbitmap(hBitmap);
GDI32API.DeleteObject(hBitmap);
return image;
}

Unlike the webcam method, here I am not using the clipboard to store and pass the image across calls in my application.

Now that the basic screenshot taking mechanism is ready, it's time to add a service reference to BitmapPasser:

Once added, there is the SendScreenshot method:

static void SendScreenshot()
{
Image image = Capture(USER32API.GetDesktopWindow());
using (MemoryStream stream = new MemoryStream())
{
Image derived = image.GetThumbnailImage(image.Width / 4, image.Height / 4, null, IntPtr.Zero);
derived.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);

BitmapPasser.Service1Client client = new BitmapPasser.Service1Client();
client.SetData(stream.ToArray());
}
Console.WriteLine("Screen data sent. Timestamp: " + DateTime.Now.ToString());
}

What this method does is it retrieves the screenshot, resizes the source image by dividing the height and width by 4 (remember that the image can be transmitted via a cellular data network), puts the image in a stream and then is calling SetData from the BitmapPasser service that will set the static byte array to the binary representation of the image. That's all it does.

But we're not there yet - you need to make this process continuous. So in the Main method I am instantiating a BackgroundWorker that will perform the sending task:

BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync();
Console.Read();

When it's working, all it does is it calls SendScreenshot:

static void worker_DoWork(object sender, DoWorkEventArgs e)
{
SendScreenshot();
}

But when it is done working, it's starting all over:

static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
((BackgroundWorker)sender).RunWorkerAsync();
}

That way when I run it, it continuously sends screenshots to the service:

The mobile app is composed of one page that has the XAML structured like this:

<phone:PhoneApplicationPage 
x:Class="DesktopViewerMobile.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="728" d:DesignHeight="480"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Landscape" Orientation="Landscape"
shell:SystemTray.IsVisible="True" Loaded="PhoneApplicationPage_Loaded" >

<Grid x:Name="LayoutRoot" Background="Transparent">
<Image Name="DesktopImage"></Image>
</Grid>
</phone:PhoneApplicationPage>

I explicitly set the layout to Landscape so that it is easier to see what is going on.

The code-behind is a bit more extensive, but it follows a similar pattern that was applied to the desktop application. When the page is loaded, I am instantiating a BackgroundWorker:

private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync();
}

When it executes its work, here is what it does:

void worker_DoWork(object sender, DoWorkEventArgs e)
{
state=false;
BitmapPasser.Service1Client client = new BitmapPasser.Service1Client();
client.GetDataAsync();
client.GetDataCompleted += new EventHandler<BitmapPasser.GetDataCompletedEventArgs>(client_GetDataCompleted);

while (!state)
{
Thread.Sleep(0);
}
}

Here, state shows whether the request was completed or not and it is a simple boolean variable that is declared in the class header. When the request is initiated, it is set to false and is only set to true once the image was retrieved and set to the Image control. As you can see - there is a while loop. It won't let the program get out of the thread and trigger the completion event unless it is really completed. Otherwise, the RunWorkerCompleted event handler will be triggered the moment the last line is reached (since there is another async operation that is not tied to the current background thread).

I must mention that you should add a reference to the same BitmapPasser service in the Windows Phone 7 application as well. 

When the data is received (in GetDataCompleted), I am using this:

void client_GetDataCompleted(object sender, BitmapPasser.GetDataCompletedEventArgs e)
{
using (MemoryStream stream = new MemoryStream(e.Result))
{
BitmapImage image = new BitmapImage();
image.SetSource(stream);
DesktopImage.Source = image;
}
state = true;
}

I am getting the image from its binary representation (notice that I am putting it into a stream) and after that I am simply setting the source for the Image control. Only then I am setting the state to true so that the current BackgroundWorker completes its operation and starts over.

Once you're done, make sure that the service is running, the client application is sending data and the mobile application is running as well. You should get a final result similar to this:

You can download the source code below:

Learn how divergent branches can appear in your repository and how to better understand why they are called “branches".  Brought to you in partnership with Wakanda

Topics:

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}