[WPF]枠なしでリサイズ&ドラッグ移動可能なウィンドウを作る

実は知らなかったんすけど、.NET 4.5から標準搭載された
WindowChromeクラスってのを使うと
VisualStudio2012,2013みたいなボーダーなしのウィンドウアプリとか
全然楽ちんにできるのね。

というわけでいきなり答えをいってしまったけれど、
WindowChromeを使って↓こんなようなウィンドウが作れる。
枠なしウィンドウ

使い方は、↓こちらさまのページが完璧にわかりやすく書かれている。
WPF で Zune のようなウィンドウを作る

ごていねいに右上の最大化・最小化・閉じるボタンを
Marlettフォントで換装するStyleまで用意されているので
そのままパクらせていただくなどした。
 

あとは細かい補足をいくつか。
 

■標準のキャプションボタンを無効化する

もとからある標準の最大/最小化・閉じるボタンは
決して無くなってしまうわけではなくて、
拡張されたクライアント領域が上から覆いかぶさって
見えなくなっているだけなので、上からそのへんをクリックすると
普通にクリックイベントを拾ってクローズされたりしてしまう。

これを無効にしたい場合はWindowChromeタグの中で以下のプロパティを設定。
UseAeroCaptionButtons=”False”
 

■最大化されてないときは元に戻すボタンを表示しない

最大化ボタン/元に戻すボタンが常に両方表示されてしまうのは
ちょっとかっこ悪いので、最大化してるときは最大化ボタンを、
最大化していないときは元に戻すボタンを表示しないようにしたい。

頑張ってXAML+コンバータだけでやるならこんな感じ。

    <StackPanel Orientation="Horizontal" Margin="5"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Top">
        <StackPanel.Resources>
            <my:EqualsToVisibleConveter x:Key="visiConverter" />
        </StackPanel.Resources>
        <Button Content="0" Style="{DynamicResource CaptionButtonStyleKey}" />
        <Button Content="1" Style="{DynamicResource CaptionButtonStyleKey}">
            <Button.Visibility>
                <Binding RelativeSource="{RelativeSource AncestorType=Window}"
                         Path="WindowState"
                         Mode="OneWay"
                         Converter="{StaticResource visiConverter}">
                    <Binding.ConverterParameter>
                        <WindowState>Normal</WindowState>
                    </Binding.ConverterParameter>
                </Binding>
            </Button.Visibility>
        </Button>
        <Button Content="2" Style="{DynamicResource CaptionButtonStyleKey}">
            <Button.Visibility>
                <Binding RelativeSource="{RelativeSource AncestorType=Window}"
                         Path="WindowState"
                         Mode="OneWay"
                         Converter="{StaticResource visiConverter}">
                    <Binding.ConverterParameter>
                        <WindowState>Maximized</WindowState>
                    </Binding.ConverterParameter>
                </Binding>
            </Button.Visibility>
        </Button>
        <Button Content="r" Style="{DynamicResource CaptionButtonStyleKey}" />
    </StackPanel>
    //値の一致を表示状態に変換するコンバータ
    [ValueConversion(typeof(object), typeof(Visibility))]
    public class EqualsToVisibleConveter : IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value == null)
            {
                return parameter == null ? Visibility.Visible : Visibility.Collapsed;
            }
            return value.Equals(parameter) ? Visibility.Visible : Visibility.Collapsed;
        }

        //使わないよ
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

xamlのほうは、書いてない部分はリンク先のコードそのままです。
コマンド実装してないのでもちろんこのままクリックしても何も起きないよ。
 

■DragMoveを呼ぶ前の状態チェック

ウィンドウのMouseLeftButtonDownイベントでDragMoveメソッドを呼べば
ウィンドウ全体でドラッグ移動ができるようになるよという話。

細かい話だけど、このDragMoveというメソッドは
マウスのボタンをプレスしてない状態で呼び出すと例外を投げるという
めんどうくさいやつなのだ。

ふつうはMouseLeftButtonDownから呼び出せば常にマウス押下状態になるけど、
たとえば、別のハンドラも同じイベントに登録してて
そっちが先に呼ばれちゃったりすると
このDragMoveをやるハンドラに来たときには
すでにマウスボタンを離した後だったりするケースがありえる。

なので習慣としてチェックを入れておくほうが安全でしょう。

private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    //マウスボタン押下状態でなければ何もしない
    if (e.ButtonState != MouseButtonState.Pressed) return;

    this.DragMove();
}

以上、そんなわけでWindowChromeクラスは便利だねという話なんだけど、
せっかくなのでウィンドウ全部透過とかしてみたいなと思ったら、
標準のキャプションボタンが透けてくれねぇことに気づいた。

なので今度はWindowChromeを使わない場合の実装についてもふれておこう。
そのうち。