Вчера я уже говорил о доступе к веб-камере вашего ПК с устройства Windows Phone 7. Я полагал, что тот же принцип (ну, ядро ядра) может быть применен к приложению, которое сможет использовать общий экран (применительно к системам Windows).
Есть те же три основных компонента — настольное приложение, которое будет фиксировать текущее состояние экрана пользователя. Я должен упомянуть, что это может быть реорганизовано как отдельная служба Windows, но для целей тестирования подойдет простое консольное приложение. Затем существует промежуточная ссылка — служба WCF, которая будет получать (и хранить) двоичные данные и в то же время будет передавать их запрашивающему приложению.
ПРИМЕЧАНИЕ. Вы можете скачать исходный код службы здесь .
И есть мобильное приложение, которое будет отображать данные.
Начиная с клиента Windows, я решил просто пойти с консольным приложением, чтобы было легче сосредоточиться на функциональности, а не на пользовательском интерфейсе. Здесь есть важный момент, который вы должны рассмотреть. Первая идея, которая приходит на ум, — использовать классы по умолчанию, предоставляемые .NET Framework, чтобы сделать снимок экрана. Однако, если вы решите пойти по этому пути, существуют существенные ограничения, главным из которых является то, что, например, нет возможности сделать снимок экрана, пока приложение свернуто. Конечно, для этого есть обходные пути, но я собираюсь использовать WinAPI напрямую.
Как я уже сказал, у меня уже есть консольное приложение, поэтому все, что мне нужно сделать, это реализовать пользовательские методы. Прежде всего, мне нужно реализовать механизм захвата экрана. Если вы, например, последуете этому примеру , в вашем проекте должно быть два пользовательских класса — USER32API и GDI32API (наименование может отличаться).
USER32API отвечает за доступ к методам из user32.dll — это библиотека, которая управляет элементами пользовательского интерфейса, к которым обращаются пользователи (например, окна, диалоги, рабочий стол). С другой стороны, GDI32API собирается облегчить доступ к методам, предоставляемым gdi32.dll — библиотека, отвечающая за интерфейс графического устройства, которая позволяет нам выполнять определенные операции с данными изображения.
При этом, вот что я добавил в приложение:
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);
}
В основной класс программы я добавил метод Capture, который будет возвращать текущее состояние экрана:
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;
}
В отличие от метода веб-камеры, здесь я не использую буфер обмена для хранения и передачи изображения через вызовы в моем приложении.
Теперь, когда основной механизм создания снимков экрана готов, пришло время добавить ссылку на службу в BitmapPasser:
После добавления есть метод SendScreenshot:
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());
}
Этот метод извлекает снимок экрана, изменяет размеры исходного изображения путем деления высоты и ширины на 4 (помните, что изображение может передаваться через сотовую сеть передачи данных), помещает изображение в поток и затем вызывает SetData из Служба BitmapPasser, которая устанавливает статический байтовый массив в двоичное представление изображения. Это все, что он делает.
Но мы еще не там — вам нужно сделать этот процесс непрерывным. Итак, в методе Main я создаю экземпляр BackgroundWorker, который будет выполнять задачу отправки:
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync();
Console.Read();
Когда он работает, он просто вызывает SendScreenshot:
static void worker_DoWork(object sender, DoWorkEventArgs e)
{
SendScreenshot();
}
Но когда все работает, все начинается снова:
static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
((BackgroundWorker)sender).RunWorkerAsync();
}
Таким образом, когда я запускаю его, он постоянно отправляет скриншоты в сервис:
Мобильное приложение состоит из одной страницы, структура которой выглядит следующим образом:
<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>
Я явно установил макет на Пейзаж, чтобы было легче увидеть, что происходит.
Код позади немного более обширный, но он следует аналогичному шаблону, который был применен к настольному приложению. Когда страница загружена, я создаю экземпляр 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();
}
Когда он выполняет свою работу, вот что он делает:
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);
}
}
Здесь состояние показывает, был ли запрос завершен или нет, и это простая логическая переменная, которая объявлена в заголовке класса. Когда запрос инициируется, для него устанавливается значение false и устанавливается значение true только после извлечения изображения и установки элемента управления изображением. Как видите — есть цикл while. Это не позволит программе выйти из потока и вызвать событие завершения, если оно действительно не завершено. В противном случае обработчик события RunWorkerCompleted будет запущен в тот момент, когда будет достигнута последняя строка (поскольку существует другая асинхронная операция, которая не привязана к текущему фоновому потоку).
Я должен упомянуть, что вы должны добавить ссылку на тот же сервис BitmapPasser в приложении Windows Phone 7.
Когда данные получены (в GetDataCompleted), я использую это:
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;
}
Я получаю изображение из его двоичного представления (обратите внимание, что я помещаю его в поток), и после этого я просто устанавливаю источник для элемента управления Image. Только тогда я устанавливаю состояние true, чтобы текущий BackgroundWorker завершил свою работу и начал заново.
Когда вы закончите, убедитесь, что служба запущена, клиентское приложение отправляет данные и мобильное приложение также работает. Вы должны получить конечный результат, подобный этому:
Вы можете скачать исходный код ниже: