SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

VMからウィンドウを閉じる添付ビヘイビア

MVVMな設計でアプリを作るときの、よくある悩みとして、
VMから画面を閉じる処理をどうやればいいんだ??
というのがあります。

これって、添付ビヘイビアを使えばスッキリ書けるのでは、、、と思い、
ウィンドウを閉じる動作を付加する添付ビヘイビアを作ってみました。

仕組み

f:id:minami_SC:20140405223315p:plain
仕組みは上の図のような感じ。

  1. bool型のCloseプロパティを持った添付ビヘイビアを作成
    1. このプロパティがtrueになると、ウィンドウを閉じる
  2. VMにウィンドウの表示状態を示すCloseWindowプロパティを作成
    1. このプロパティと、添付ビヘイビア側で作成したCloseプロパティをバインド

VMからウィンドウを閉じたりしたい
⇒「ウィンドウを開いているか/閉じているか」というビューの状態をVMに持たせる。
というのはMVVMの設計として自然な流れかなぁ、と思います。

コード

この添付ビヘイビア自身と、このビヘイビアを使用するためのコードは以下の通り。

CloseWindowAttachedBehavior.cs

Closeという添付プロパティを作っています。
ウィンドウや各種コントロールに対して、この添付プロパティをClose=Trueという風にセットすると、ウィンドウが閉じられます。

OnCloseChangedでは、ビヘイビアがセットされた対象がwindow型ではなかった場合、
ビヘイビアがセットされた要素の属するウィンドウを閉じるようにしてます。

    public class CloseWindowAttachedBehavior
    {
        public static bool GetClose(DependencyObject obj)
        {
            return (bool)obj.GetValue(CloseProperty);
        }
        public static void SetClose(DependencyObject obj, bool value)
        {
            obj.SetValue(CloseProperty, value);
        }
        // Using a DependencyProperty as the backing store for Close.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CloseProperty =
            DependencyProperty.RegisterAttached("Close", typeof(bool), typeof(CloseWindowAttachedBehavior), new PropertyMetadata(false, OnCloseChanged));

        private static void OnCloseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var win = d as Window;
            if (win == null)
            {
                // Window以外のコントロールにこの添付ビヘイビアが付けられていた場合は、
                // コントロールの属しているWindowを取得
                win = Window.GetWindow(d);
            }

            if (GetClose(d))
                win.Close();
        }
    }
MainWindow.xaml
<Window x:Class="AttachedBehaviorTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:AttachedBehaviorTest"
        Title="MainWindow"
        Width="300"
        Height="200"
        local:CloseWindowAttachedBehavior.Close="{Binding CloseWindow}">
    <Grid>
        <Button Width="75"
                Margin="10"
                HorizontalAlignment="Right"
                VerticalAlignment="Bottom"
                Command="{Binding CloseCommand}"
                Content="閉じる" />
    </Grid>
</Window>
MainWindow.xaml.cs
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            this.DataContext = new MainWindowViewModel();
        }
    }
MainWindowViewModel.cs
    class MainWindowViewModel : BindableBase
    {
        private bool closeWindow;
        /// <summary>
        /// 画面の表示状態を示すプロパティ。Trueにすると画面を閉じる
        /// </summary>
        public bool CloseWindow
        {
            get { return closeWindow; }
            set { this.SetProperty(ref this.closeWindow, value); }
        }


        private RelayCommand closeCommand;
        public RelayCommand CloseCommand
        {
            get { return closeCommand = closeCommand ?? new RelayCommand(Close); }
        }

        private void Close()
        {
            this.CloseWindow = true;
        }
    }