Статьи

Кроссплатформенное поведение для создания скриншотов в Windows (телефон) 8.1

В настоящее время я занимаюсь разработкой приложения, которое должно иметь возможность делиться или сохранять все, что находится на экране. Я наткнулся на эту статью по Люком ван ден Ouweland о RenderTargetBitmap и подумал, что я мог) сделать это в более общем плане (повторно) использовать и б) сделать это играть хорошо с MVVM.

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

Основной код функциональности — который все еще очень похож на оригинальный образец Лука — находится в самом поведении. Чтобы вызвать его из модели представления, я вызываю помощь мессенджера MVVMLight . Итак, я начну с сообщения, которое viewmodel и поведение используют для связи:

using GalaSoft.MvvmLight.Messaging;

namespace WpWinNl.Behaviors
{
  public class ScreenshotMessage : MessageBase
  {
    public ScreenshotMessage(object sender = null, object target = null, 
      ScreenshotCallback callback = null) : base(sender, target)
    {
      Callback = callback;
    }

    public ScreenshotCallback Callback { get; set; }
  }

  public delegate void ScreenshotCallback(string fileName);
}

Это стандартное сообщение MVVMLight с дополнительным дополнением, которое может нести дополнительную полезную нагрузку обратного вызова. Созданное мной поведение сохраняет файл в KnownFolder, но оно может отправить имя файла, который был создан, обратно вызывающему объекту, вызвав указанный обратный вызов.

Основы поведения — реализованные снова как SafeBehavior — на самом деле довольно просты:

using System;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media.Imaging;
using GalaSoft.MvvmLight.Messaging;

namespace WpWinNl.Behaviors
{
  public class ScreenshotBehavior : SafeBehavior<FrameworkElement>
  {
    protected override void OnSetup()
    {
      Messenger.Default.Register<ScreenshotMessage>(this, ProcessMessage );
      base.OnSetup();
    }

    protected override void OnCleanup()
    {
      Messenger.Default.Unregister(this);
      base.OnCleanup();
    }

    private async void ProcessMessage(ScreenshotMessage m)
    {
      if (m.Target != null && Target != null)
      {
        if (m.Target.Equals(Target))
        {
          await DoRender(m.Callback);
        }
      }
      else
      {
        await DoRender(m.Callback);       
      }
    }
  }
}

Поэтому он слушает сообщение ScreenshotMessage. Когда Target (свойство зависимости в этом поведении) равно цели, указанной в сообщении, рендеринг выполняется. Думайте о Target как об общем ключе — это позволяет модели представления запускать определенное поведение, используя — обычно — строку. Это я показываю в примере решения . Если оба нацелены на модели поведения в и цель данного сообщения являются недействительными, это пожары , а также. Вы можете использовать это, если у вас есть только одно поведение и один вызов. Однако если такое поведение используется в нескольких местах, я настоятельно рекомендую указать цель, иначе вы можете получить очень интересные условия гонки.

Фактический метод рендеринга — это почти весь код Лука с небольшими изменениями:

private async Task DoRender(ScreenshotCallback callback)
{
  var renderTargetBitmap = new RenderTargetBitmap();
  await renderTargetBitmap.RenderAsync(AssociatedObject);
  var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();

  var storageFile = 
    await KnownFolders.SavedPictures.CreateFileAsync(
      string.Concat(Prefix, ".png"),
      CreationCollisionOption.GenerateUniqueName);
  using (var stream = await storageFile.OpenAsync(FileAccessMode.ReadWrite))
  {
    var encoder = 
       await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
    encoder.SetPixelData(
      BitmapPixelFormat.Bgra8,
      BitmapAlphaMode.Ignore,
      (uint) renderTargetBitmap.PixelWidth,
      (uint) renderTargetBitmap.PixelHeight, 96d, 96d,
      pixelBuffer.ToArray());

    await encoder.FlushAsync();
    if (callback != null)
    {
      callback(storageFile.Name);
    }
  }
}

Он создает уникальный файл на основе свойства зависимостей префикса в известной папке «SavedPictures», отображает экран в виде файла png, сохраняет его и при желании вызывает обратный вызов, передающий имя обратно этому обратному вызову — скорее всего, метод модель представления, которая затем может воздействовать на нее.

Остальная часть поведения — это два свойства зависимостей, которые я опущу для краткости. Вам не нужно их вообще использовать — если вы не установите Prefix, он будет использовать «Скриншот»

Чтобы использовать его: как было сказано ранее, перетащите его поверх элемента пользовательского интерфейса, из которого вы хотите сделать снимки экрана, а затем добавьте, например, следующий код в вашу модель представления:

public ICommand ScreenshotCommand
{
  get
  {
    return new RelayCommand(() =>
    {
      var m = new ScreenshotMessage(this, 
        "screenshot", MyCallback);
      Messenger.Default.Send((m));
    });
  }
}

private void MyCallback(string name)
{
  Debug.WriteLine(name);
}

В сообщении я установил цель как «снимок экрана», поэтому в XAML теперь должно быть написано

<Behaviors:ScreenshotBehavior Target="screenshot" Prefix="MyScreenshot"/>

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

Самое интересное, конечно, что в этом удивительном мире, в котором мы живем сегодня, поведение на самом деле может быть кросс-платформенным и определяться в PCL и работать как на Windows Phone 8.1, так и на «большой Windows» 8.1 :).

MyScreenshotМой скриншот (2)

В демонстрационном решении , которое содержит приложение для Windows Phone, приложение для Windows и общий проект, вы увидите, что я снова перетащил файл Main.Xaml в общий проект просто для удовольствия. Не забудьте установить доступ к библиотеке изображений в обоих манифестах приложения. Я всегда об этом забываю.

MyScreenshotВ Windows изображения попадают в мою папку «USERPROFILE% \ Picures», в Windows Phone они попадают в «Изображения \ Сохраненные изображения».

Интересная деталь — я установил серый фон для корневой сетки приложения. Если я не установлю цвет, фон изображения в Windows будет не черным, а обрезанным до 1297×1080 вместо 1920×1080, как и в моем родном разрешении. Я еще не смог определить, почему это так.

Я построил это поведение поверх моего пакета WpWinNl 2.0.3, но вы можете легко адаптировать его, чтобы заставить его работать как обычное поведение, просто используя процедуру, описанную здесь .

Ох, а картина? Это просто старая фотография грузовика Mercedes моей жены в 2004 году, когда она и ее коллега вошли в Книгу рекордов Гиннеса, пытаясь создать самый длинный грузовой конвой из когда-либо существовавших . Это короткая остановка на обочине дороги Дайк до того, как фактическая колонна длиной 9,5 км была собрана. Интересная деталь: все 416 водителей были женщинами . Конвой без проблем проехал 22 км, и, как метко сказала моя жена, она впервые попала в пробку.