В настоящее время я занимаюсь разработкой приложения, которое должно иметь возможность делиться или сохранять все, что находится на экране. Я наткнулся на эту статью по Люком ван ден 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 :).
В демонстрационном решении , которое содержит приложение для Windows Phone, приложение для Windows и общий проект, вы увидите, что я снова перетащил файл Main.Xaml в общий проект просто для удовольствия. Не забудьте установить доступ к библиотеке изображений в обоих манифестах приложения. Я всегда об этом забываю.
В Windows изображения попадают в мою папку «USERPROFILE% \ Picures», в Windows Phone они попадают в «Изображения \ Сохраненные изображения».
Интересная деталь — я установил серый фон для корневой сетки приложения. Если я не установлю цвет, фон изображения в Windows будет не черным, а обрезанным до 1297×1080 вместо 1920×1080, как и в моем родном разрешении. Я еще не смог определить, почему это так.
Я построил это поведение поверх моего пакета WpWinNl 2.0.3, но вы можете легко адаптировать его, чтобы заставить его работать как обычное поведение, просто используя процедуру, описанную здесь .
Ох, а картина? Это просто старая фотография грузовика Mercedes моей жены в 2004 году, когда она и ее коллега вошли в Книгу рекордов Гиннеса, пытаясь создать самый длинный грузовой конвой из когда-либо существовавших . Это короткая остановка на обочине дороги Дайк до того, как фактическая колонна длиной 9,5 км была собрана. Интересная деталь: все 416 водителей были женщинами . Конвой без проблем проехал 22 км, и, как метко сказала моя жена, она впервые попала в пробку.