2010年05月04日

最大化ボタン最小化ボタン

WPF の Window クラスでは、WinForm における MaximizeBox/MinimizeBox プロパティは廃止され、代わりにResizeMode プロパティで賄われるようになっています。しかし ResizeMode 列挙体には CanMinimize はあっても CanMaximize はないため、最小化だけ禁止というのは実現できません。またウィンドウ境界線のドラッグによるリサイズ動作も兼ねるため、最大化ボタンは使えないけど自由なリサイズは許可したい、といった要求には応えられません。ResizeMode によるリサイズ機能関連のプロパティ一元化は方向として良いと思うんですが、柔軟性が犠牲になっているのは残念な物です。

リサイズは、最大化、最小化、ドラッグによるリサイズ、という三つの独立した機能が考えられます。独立しているんですから、それぞれ独立したプロパティにしてしまいましょう。依存関係プロパティにすればバインディングもできて便利です。

これらのリサイズ機能は Windows の機能に依存しています。具体的に言うとウィンドウスタイルの、WS_MAXIMIZEBOX、WS_MINIMIZEBOX、WS_SIZEBOX です。これらの設定は WinForm なら CreateParams をオーバーライドするだけで簡単に実装できたんですが、その辺をできるだけ隠蔽している WPF では面倒です。ウィンドウハンドルを取得して、Win32API の SetWindowLong 関数で後からスタイルを設定する必要があります。

public class CustomWindow : Window {
  static CustomWindow() {
    Window.ResizeModeProperty.OverrideMetadata(
      typeof(CustomWindow),
      new FrameworkPropertyMetadata(ResizeMode.CanResize, OnResizeModeChanged));
  }
  public static readonly DependencyProperty IsShowMinimizeBoxProperty
    = DependencyProperty.Register(
      "IsShowMinimizeBox", typeof(bool), typeof(CustomWindow),
      new UIPropertyMetadata(true, OnIsShowMinimizeBoxChanged));
  public bool IsShowMinimizeBox {
    get { return (bool)this.GetValue(IsShowMinimizeBoxProperty); }
    set { this.SetValue(IsShowMinimizeBoxProperty, value); }
  }
  private static void OnIsShowMinimizeBoxChanged(
      DependencyObject d, DependencyPropertyChangedEventArgs e) {
    var window = d as CustomWindow;
    if (window == null) return;
    window.SetResizeStyle();
  }
  public static readonly DependencyProperty IsShowMaximizeBoxProperty
    = DependencyProperty.Register(
      "IsShowMaximizeBox", typeof(bool), typeof(CustomWindow),
      new UIPropertyMetadata(true, OnIsShowMaximizeBoxChanged));
  public bool IsShowMaximizeBox {
    get { return (bool)this.GetValue(IsShowMaximizeBoxProperty); }
    set { this.SetValue(IsShowMaximizeBoxProperty, value); }
  }
  private static void OnIsShowMaximizeBoxChanged(
      DependencyObject d, DependencyPropertyChangedEventArgs e) {
    var window = d as CustomWindow;
    if (window == null) return;
    window.SetResizeStyle();
  }
  public static readonly DependencyProperty IsResizableProperty
    = DependencyProperty.Register(
      "IsResizable", typeof(bool), typeof(CustomWindow),
      new UIPropertyMetadata(true, OnIsResizableChanged));
  public bool IsResizable {
    get { return (bool)this.GetValue(IsResizableProperty); }
    set { this.SetValue(IsResizableProperty, value); }
  }
  private static void OnIsResizableChanged(
      DependencyObject d, DependencyPropertyChangedEventArgs e) {
    var window = d as CustomWindow;
    if (window == null) return;
    window.SetResizeStyle();
  }
  private static void OnResizeModeChanged(
      DependencyObject d, DependencyPropertyChangedEventArgs e) {
    // ResizeModeが変更された場合、ResizeModeに従って各プロパティを調整
    // 逆方向の調整(三つともfalseに設定されたらResizeModeをNoResizeにするとか)
    //  は取り敢えずやらない
    var window = d as CustomWindow;
    if (window == null) return;
    switch ((ResizeMode)e.NewValue) {
    case ResizeMode.NoResize :
      window.IsShowMaximizeBox = false;
      window.IsShowMinimizeBox = false;
      window.IsResizable = false;
      break;
    case ResizeMode.CanMinimize :
      window.IsShowMaximizeBox = false;
      window.IsShowMinimizeBox = true;
      window.IsResizable = false;
      break;
    case ResizeMode.CanResize :
    case ResizeMode.CanResizeWithGrip :
      window.IsShowMaximizeBox = true;
      window.IsShowMinimizeBox = true;
      window.IsResizable = true;
      break;
    }
  }
  protected override void OnSourceInitialized(EventArgs e) {
    base.OnSourceInitialized(e);
    // WindowInteropHelperを作成できる最速のタイミング
    this.helper = new WindowInteropHelper(this);
    if (!this.IsShowMinimizeBox || !this.IsShowMaximizeBox || !this.IsResizable) {
      this.SetResizeStyle();
    }
  }
  private WindowInteropHelper helper;
  private const int GWL_STYLE = -16;
  private const int WS_MAXIMIZEBOX = 0x10000;
  private const int WS_MINIMIZEBOX = 0x20000;
  private const int WS_SIZEBOX = 0x40000;
  private void SetResizeStyle() {
    if (this.helper != null) {
      var handle = this.helper.Handle;
      var style = this.GetWindowStyle();
      if (this.IsResizable)
        style |= WS_SIZEBOX;
      else
        style &= ~WS_SIZEBOX;
      if (this.IsShowMinimizeBox)
        style |= WS_MINIMIZEBOX;
      else
        style &= ~WS_MINIMIZEBOX;
      if (this.IsShowMaximizeBox)
        style |= WS_MAXIMIZEBOX;
      else
        style &= ~WS_MAXIMIZEBOX;
      this.SetWindowStyle(style);
    }
  }
  private int GetWindowStyle() {
    var handle = new HandleRef(this, this.helper.Handle);
    // 32bitプロセスか64bitプロセスかで呼び出すAPIが変わる
    if (IntPtr.Size == 4)
      return NativeMethods.GetWindowLong(handle, GWL_STYLE);
    else
      return NativeMethods.GetWindowLongPtr(handle, GWL_STYLE).ToInt32();
  }
  private void SetWindowStyle(int style) {
    var handle = new HandleRef(this, this.helper.Handle);
    // 32bitプロセスか64bitプロセスかで呼び出すAPIが変わる
    if (IntPtr.Size == 4)
      NativeMethods.SetWindowLong(handle, GWL_STYLE, style);
    else
      NativeMethods.SetWindowLongPtr(handle, GWL_STYLE, new IntPtr(style));
  }
  private static class NativeMethods {
    [DllImport("user32", CharSet=CharSet.Auto)]
    public static extern int GetWindowLong(HandleRef window, int index);
    [DllImport("user32", CharSet=CharSet.Auto)]
    public static extern int SetWindowLong(HandleRef window, int index, int value);
    [DllImport("user32", CharSet=CharSet.Auto)]
    public static extern IntPtr GetWindowLongPtr(HandleRef window, int index);
    [DllImport("user32", CharSet=CharSet.Auto)]
    public static extern IntPtr SetWindowLongPtr(HandleRef window, int index,
                                                 IntPtr value);
  }
}
posted by Hongliang at 04:22| Comment(0) | TrackBack(0) | WPF | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.seesaa.jp/tb/148698461

この記事へのトラックバック

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。

×

この広告は1年以上新しい記事の投稿がないブログに表示されております。