F#でWPF --- メッセージボックス --- System.Windows.MessageBox不使用
下記記事において、MVVMにおけるメッセージボックス表示の実装を行いました。
F#でWPF --- ファイルダイアログCommand - 何でもプログラミング
上記記事での実装は、stringの値にバインドしているというよりは、stringの変化のトリガーに引っかけて動作しているものでした。
今回は変数変更のトリガーではなく、変数の状態に基づいてメッセージボックスを表示するよう実装してみます。
作成するアプリケーション
メッセージボタンを押すとメッセージ領域が表示されるアプリケーションを作成します。
アプリケーションコード
前回と比較して、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は後ほど説明します。
<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 - 何でもプログラミング