F#でWPF --- UI画像キャプチャ

今回は、表示されているWPFコントロールを画像として保存する方法を書いていきます。

F#でWPFを利用する方法は下記記事を参照してください。
F#でWPF --- ウィンドウ表示 - 何でもプログラミング

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

キャプチャボタンを押すと、Gridの内部がpngで保存されるアプリケーションを作成します。
f:id:any-programming:20170213233701p:plain

SaveImageAction

今回はTriggerActionを利用して実装していきます。

TargetにバインドされたFrameworkElementをPNG画像として出力します。

FrameworkElementからBitmapにするにはRenderTargetBitmapを利用します。

Measure、Arrange、UpdateLayoutを呼んでコントロールを希望のサイズに更新します。場合によって全部呼ぶ必要がないケースもありますが、セットにして記述しておく方が楽です。

BitmapをPNGで保存するにはPngBitmapEncoder利用します。

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)

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

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


アプリケーションコード

BindingでPathを設定しないと、コントロールそのものを渡すことができます。

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="55,20,0,0" VerticalAlignment="Top" Width="75">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <local:SaveImageAction Target="{Binding ElementName=grid}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
    </Grid>
</Window>

F#

open System
open System.Windows

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