Статьи

Making of — Первая версия слоя презентации для Windows Phone 7

В Интернете возникает множество вопросов о том, как сделать снимки экрана из приложения Windows Phone 7 или как сделать снимок экрана системы. Печальная (или, скажем, не очень хорошая) новость заключается в том, что сейчас нет такой функциональности, доступной для обычных разработчиков, и фактического инструмента, который вы, вероятно, видели на PDC (или любой другой презентации официального представителя Microsoft по этой причине). с устройством WP7, подключенным к компьютеру, и экран, отображаемый на настольном компьютере, является внутренним приложением, которое в данный момент не распространяется.

Хотя невозможно отследить экран системы, это можно сделать в отдельном приложении Windows Phone 7, предполагая, что вы являетесь действительным разработчиком этого приложения. Я кратко упомянул эту идею в своем блоге с видео, которое показывает фактическую работу прототипа. В этой статье я на самом деле описываю часть кода.

Базовая архитектура была для меня довольно очевидна — мне нужно было приложение для Windows Phone 7, предпочтительно на базе Silverlight, веб-служба (используется WCF) и клиентское настольное приложение, которое фактически получит графический контент. Однако был интересный момент — как мне организовать постоянную петлю, чтобы приложение не зависало на телефоне? Мои первоначальные мысли были связаны с некоторыми событиями, которые запускаются при запуске приложения, однако нет события, которое запускается при первом запуске приложения, так что это было вычеркнуто.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Подход, который я использовал ниже, является первым проектом и не полностью оптимизирован.

Я всегда мог использовать конструктор приложения для этой задачи, но мне нужно было бы как-то инициировать процесс отправки. Мгновенное начало действия в конструкторе не является хорошей идеей, потому что оно будет зависать от пользовательского интерфейса. В этом случае я начну действие в обработчике события Page_Loaded для главной страницы.

Фактический метод, который запустит процесс отправки, выглядит следующим образом:

public static void StartSending()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerAsync();
}

Очевидно, что я не могу сохранить процесс отправки в главном потоке, потому что произойдет падение производительности — пользовательский интерфейс перестанет отвечать на запросы и будет отправлено одно постоянное изображение.

Вот что я сделал для DoWork:

static void worker_DoWork(object sender, DoWorkEventArgs e)
{
IScheduler sched = Scheduler.ThreadPool;

while (true)
{
sched.Schedule(new Action(() =>
{
MemoryStream s = new MemoryStream();
d.BeginInvoke(new Action(() =>
{
WriteableBitmap b = new WriteableBitmap(RootFrame, null);

b.SaveJpeg(s, 480, 800, 0, 20);

MainServiceRef.Service1Client c = new MainServiceRef.Service1Client();
c.SetDataAsync(s.GetBuffer());
}));
}));

Thread.Sleep(1000);
}

}

Даже внутри фонового потока я использую планировщик для помещения новых потоков в пул потоков. Это уменьшит влияние на активность приложения — изначально у меня была раздражающая задержка ответа, но я, кажется, исправил ее (или, по крайней мере, уменьшил ее до уровня, когда пользователь не сможет ее заметить).

Чтобы сделать цикл постоянным, есть while (true) . Для каждой отдельной итерации я планирую новое действие, которое вызывается локальным диспетчером. Кстати, диспетчер инициализируется при загрузке главной страницы. В классе приложения у меня есть это:

public static Dispatcher d;

Теперь вот что у меня есть в MainPage_Loaded:

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
App.d = Dispatcher;
App.StartSending();
}

Таким образом, я могу вызывать действия, связанные с пользовательским интерфейсом, в потоке без пользовательского интерфейса. 

Так что я получаю экземпляр WriteableBitmap, основанный на текущем кадре (представленном как RootFrame). Вы можете спросить — почему бы не работать с отдельными страницами? Сам фрейм является элементом UIE, который содержит страницы; поэтому имеет смысл просто захватить изображение фрейма с той страницей, на которой он находится — таким образом, вам не нужно проверять текущую страницу и так далее.

Я думал о передаче WriteableBitmap непосредственно в службу или даже в экземпляр PhoneApplicationFrame, но оба эти типа не сериализуемы, поэтому было бы излишним пытаться выдвигать их напрямую. Вместо этого я получаю представление в кодировке JPEG в приложении, а затем отправляю двоичный массив в службу. Обратите внимание, что я снижаю качество кодирования до 20% в методе SaveJpeg, чтобы избежать отправки ненужных изображений HQ. Конечно, вы можете сделать это, но это работает намного быстрее с более низким качеством изображения, и, как вы, вероятно, заметили в видео, которое я показал, вы не увидите большой разницы.

Кроме того, существует спящий поток в одну секунду, чтобы избежать полного замораживания потока. Таким образом, существует резерв времени, который помогает возобновить процесс ввода, чтобы пользователь мог что-то сделать в приложении.

Сам сервис настолько прост, насколько может быть:

public class Service1 : IService1
{
public static byte[] b { get; set; }

public void SetData(byte[] stream)
{
b = stream;
}

public byte[] GetData()
{
return b;
}

}

Все это относится к одному интерфейсу:

[ServiceContract]
public interface IService1
{
[OperationContract]
void SetData(byte[] element);

[OperationContract]
byte[] GetData();
}

Клиентское приложение представляет собой пример проекта WinForms с компонентом Timer, для которого установлен обработчик события Tick:

private void timer1_Tick(object sender, EventArgs e)
{
byte[] set = (new ServiceReference1.Service1Client()).GetData();
MemoryStream stream = new MemoryStream(set);

Image mainImage = Image.FromStream(stream);

if (mainImage != null)
{
pictureBox1.Image = mainImage;
}
}

Двоичные данные преобразованы в экземпляр Image. Просто как тот. Через тот же сервис. Поздравляем! Теперь вы знаете, как выглядит моя первая реализация.