F#でWPF --- UI画像キャプチャ --- キャプチャ時VM変更

下記記事にてUI画像をキャプチャする方法を記述しました。
F#でWPF --- UI画像キャプチャ - 何でもプログラミング

今回は、キャプチャ時のみに表示を変更したい場合に対応します。

実現方法

キャプチャ実行時にViewModelが公開しているキャプチャ用のViewModelをDataContextに設定します。(保存後は元のDataContextに戻します。)

今回は関数にして、生成を遅延させています。(Viewのパラメータも渡せるので、関数の形が良いと思います。)
f:id:any-programming:20170215001118p:plain

作成するアプリケーション

キャプチャボタンを押すとGridをPNGで保存するアプリケーションです。

保存されたPNGのテキストは"Hello"から"World"に変わっています。
f:id:any-programming:20170215000318p:plain

SaveImageAction

前回と比べ、VMを生成する関数のプロパティを追加し、キャプチャ時にDataContextを入れ替えています。

open System.IO
open System.Windows
open System.Windows.Media
open System.Windows.Media.Imaging
open System.Windows.Interactivity
open Microsoft.Win32
open Microsoft.FSharp.Linq.NullableOperators

type SaveImageAction() =
    inherit TriggerAction<FrameworkElement>()

    static member val TargetProperty = DependencyProperty.Register("Target", typeof<FrameworkElement>, typeof<SaveImageAction>)
    member this.Target with get()                   = this.GetValue(SaveImageAction.TargetProperty) :?> FrameworkElement
                       and  set(x:FrameworkElement) = this.SetValue(SaveImageAction.TargetProperty, x)

    static member val VMForCapturingProperty = DependencyProperty.Register("VMForCapturing", typeof<unit -> obj>, typeof<SaveImageAction>)
    member this.VMForCapturing with get()              = this.GetValue(SaveImageAction.VMForCapturingProperty) :?> unit -> obj
                               and  set(x:unit -> obj) = this.SetValue(SaveImageAction.VMForCapturingProperty, x)

    override this.Invoke _ = 
        let dialog = SaveFileDialog(Filter = "PNGファイル|*.png")
        if this.Target <> null && dialog.ShowDialog() ?= true then            
            let oldVM = this.Target.DataContext
            this.Target.DataContext <- this.VMForCapturing()

            let width  = this.Target.ActualWidth
            let height = this.Target.ActualHeight
            let bmp = RenderTargetBitmap(int width, int height, 96.0, 96.0, PixelFormats.Pbgra32)
            this.Target.Measure(Size(width, height))
            this.Target.Arrange(Rect(0.0, 0.0, width, height))
            this.Target.UpdateLayout()
            bmp.Render(this.Target)

            this.Target.DataContext <- oldVM

            let pngEncoder = PngBitmapEncoder()
            pngEncoder.Frames.Add(BitmapFrame.Create(bmp))
            use writer = new StreamWriter(dialog.FileName)
            pngEncoder.Save(writer.BaseStream)


アプリケーションコード

アプリケーション名はSaveWpfImage.exeで、SaveImageActionはnamespace Actionsに定義されています。

Xaml

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
        xmlns:local="clr-namespace:Actions;assembly=SaveWpfImage"
        Title="MainWindow" Height="100" Width="200">
    <Grid x:Name="grid" Background="CornflowerBlue">
        <Button Content="キャプチャ" HorizontalAlignment="Left" Margin="100,35,0,0" VerticalAlignment="Top" Width="75">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <local:SaveImageAction Target="{Binding ElementName=grid}" VMForCapturing="{Binding VMForCapturing}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
        <TextBlock Text="{Binding Text}" Foreground="White" HorizontalAlignment="Left" Margin="35,15,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/>
    </Grid>
</Window>

F#

open System
open System.Windows

type ViewModel =
    { Text : string }
    member this.VMForCapturing = fun () -> { Text = "World" } :> obj

[<STAThread>]
[<EntryPoint>]
let main argv = 
    let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window
    window.DataContext <- { Text = "Hello" }
    Application().Run(window) |> ignore    
    0