読者です 読者をやめる 読者になる 読者になる

F#でWPF --- メッセージボックス --- System.Windows.MessageBox不使用

F# MVVM WPF

下記記事において、MVVMにおけるメッセージボックス表示の実装を行いました。
F#でWPF --- ファイルダイアログCommand - 何でもプログラミング

上記記事での実装は、stringの値にバインドしているというよりは、stringの変化のトリガーに引っかけて動作しているものでした。

今回は変数変更のトリガーではなく、変数の状態に基づいてメッセージボックスを表示するよう実装してみます。

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

メッセージボタンを押すとメッセージ領域が表示されるアプリケーションを作成します。

f:id:any-programming:20170213215253p:plain

構成

メインとなるコントロールの外側に新たにコントロールを用意し、メッセージ表示有効の時にメインを無効化し、メッセージボックスを表示します。

f:id:any-programming:20170213221001p:plain

アプリケーションコード

前回と比較して、IsMessageVisibleが追加されました。

type Model = 
    { Message          : string
      IsMessageVisible : bool }

let initialModel =
    { Message = ""
      IsMessageVisible = false }

type Msg = 
    | ShowMessage
    | CloseMessage

let updateModel model msg =
    match msg with
    | ShowMessage -> 
        { model with Message = "Hello"
                     IsMessageVisible = true }

    | CloseMessage ->
        { model with Message = ""
                     IsMessageVisible = false }

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


MainWindow.xaml

一番外側をMessageBoxPresenterで囲っています。

MessageBoxPresenterは後程説明します。

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:local="clr-namespace:MessageBox;assembly=MessageBox"
        Title="MainWindow" Height="200" Width="200">
    <local:MessageBoxPresenter ClosedCommand="{Binding CloseMessage}" IsOpen="{Binding IsMessageVisible}" Text="{Binding Message}">
        <Grid>
            <Button Content="メッセージボックス" Command="{Binding ShowMessage}" HorizontalAlignment="Right" Margin="0,0,10,10" VerticalAlignment="Bottom" Width="100" />
        </Grid>
    </local:MessageBoxPresenter>
</Window>


MessageBoxPresenter

ContentProperty属性を付けることにより、子要素(MainWindow.xamlのGrid)が自動的にMainContentにセットされます。

FocusVisualStyleにnullを設定することにより、フォーカス時に点線で囲われてしまうのを防ぎます。

boolToVisibility、invertBool、executeCommandは後ほど説明します。

Xaml

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      mc:Ignorable="d" 
      d:DesignWidth="300" d:DesignHeight="300">
    <ContentControl x:Name="contentControl" FocusVisualStyle="{x:Null}"/>
    <Grid x:Name="gridMessageBox" Margin="30" Background="DodgerBlue">
        <TextBlock x:Name="textBlock" Text="TextBlock" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center" TextWrapping="Wrap" />
        <Button x:Name="buttonClose" Content="閉じる" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0 0 10 10" Width="75" Height="23"/>
    </Grid>
</Grid>

F#

[<ContentProperty("MainContent")>]
type MessageBoxPresenter() as this =
    inherit ContentControl(FocusVisualStyle = null)
    let grid = Application.LoadComponent(Uri("MessageBox.xaml", UriKind.Relative)) :?> Grid
    let contentControl = grid.FindName("contentControl") :?> ContentControl
    let gridMessageBox = grid.FindName("gridMessageBox") :?> Grid
    let textBlock      = grid.FindName("textBlock")      :?> TextBlock
    let buttonClose    = grid.FindName("buttonClose")    :?> Button    
    do  this.Content <- grid
        textBlock.SetBinding     (TextBlock.TextProperty,           Binding("Text",        Source = this))                               |> ignore
        gridMessageBox.SetBinding(Grid.VisibilityProperty,          Binding("IsOpen",      Source = this, Converter = boolToVisibility)) |> ignore
        contentControl.SetBinding(ContentControl.ContentProperty,   Binding("MainContent", Source = this))                               |> ignore
        contentControl.SetBinding(ContentControl.IsEnabledProperty, Binding("IsOpen",      Source = this, Converter = invertBool))       |> ignore

        buttonClose.Click |> Event.add (fun _ -> executeCommand this.ClosedCommand ())

    static member val MainContentProperty = DependencyProperty.Register("MainContent", typeof<obj>, typeof<MessageBoxPresenter>)
    member this.MainContent with get()      = this.GetValue(MessageBoxPresenter.MainContentProperty)
                            and  set(x:obj) = this.SetValue(MessageBoxPresenter.MainContentProperty, x)

    static member val TextProperty = DependencyProperty.Register("Text", typeof<string>, typeof<MessageBoxPresenter>)
    member this.Text with get()         = this.GetValue(MessageBoxPresenter.TextProperty) :?> string
                     and  set(x:string) = this.SetValue(MessageBoxPresenter.TextProperty, x)

    static member val IsOpenProperty = DependencyProperty.Register("IsOpen", typeof<bool>, typeof<MessageBoxPresenter>)
    member this.IsOpen with get()       = this.GetValue(MessageBoxPresenter.IsOpenProperty) :?> bool
                       and  set(x:bool) = this.SetValue(MessageBoxPresenter.IsOpenProperty, x)

    static member val ClosedCommandProperty = DependencyProperty.Register("ClosedCommand", typeof<ICommand>, typeof<MessageBoxPresenter>)
    member this.ClosedCommand with get()           = this.GetValue(MessageBoxPresenter.ClosedCommandProperty) :?> ICommand
                              and  set(x:ICommand) = this.SetValue(MessageBoxPresenter.ClosedCommandProperty, x)


boolToVisibility、invertBool、executeCommand

Binding時に値を変換するにはIValueConverterを利用します。

TwoWayで利用することはないので、Convertのみ実装すれば問題ありません。

let createValueConverter (f : 'a -> 'b) = 
    { new IValueConverter with
        member x.Convert(value, _, _, _) =
            value :?> 'a |> f :> obj
        member x.ConvertBack(_, _, _, _) = 
            failwith "Not available" }

let boolToVisibility = createValueConverter (fun (x : bool) ->
    if x then Visibility.Visible
         else Visibility.Collapsed)

let invertBool = createValueConverter not        
    
let executeCommand (x : ICommand) parameter = 
    if x <> null then 
        x.Execute(parameter)


XAMLデザイナが動作しない

MainWindow.xamlでMessageBoxPresenterを貼り付けた際、現状デザイナが動作しなくなります。

MessageBoxPresenterでのLoadComponentでリソース位置が解決できていないようです。

解決策がわかりましたら修正いたします。

EmbeddedResourceなら動作しました。
F#でWPF --- Resource、Content、EmbeddedResource - 何でもプログラミング