泥庭

2014年5月31日

アスペクト比を保ったままWindowの拡大縮小

Filed under: .NET, WPF — タグ: , — yone64 @ 2:29 PM

ひとつ前の記事、「Client Sizeを指定してWindowを作成する」の続きです。

要件

  • Viewboxが表示されているWindowをリサイズする際に、縦横比を保ったままにする。

Viewboxにコントロールを入れ込んだということは、つまりWindowに合わせてコントロールの拡大縮小がやりたいわけです。しかしWindowのアスペクト比が自由に変更できてしまうと、上下または左右に不恰好な隙間ができてしまいます。これは格好悪いのでWindowのアスペクト比を固定したいです。

実装

XAML側

<Window x:Class="WpfApplication2.ViewboxedWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ViewboxedWindow" SizeToContent="WidthAndHeight"
        Loaded="Window_Loaded">
    <Viewbox x:Name="viewbox"/>
</Window>

C#側

/// <summary>
/// ViewboxedWindow.xaml の相互作用ロジック
/// </summary>
public partial class ViewboxedWindow : Window
{
    private double _fixRate;
    private double _horizontalMargin;
    private double _verticalMargin;

    public ViewboxedWindow()
    {
        InitializeComponent();
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        var hwndSource = (HwndSource)HwndSource.FromVisual(this);
        hwndSource.AddHook(WndHookProc);
    }

    public void SetContent(FrameworkElement content)
    {
        viewbox.Child = content;
        viewbox.Width = content.Width;
        viewbox.Height = content.Height;
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        this.SizeToContent = SizeToContent.Manual;

        this._horizontalMargin = this.ActualWidth - viewbox.Width;
        this._verticalMargin = this.ActualHeight - viewbox.Height;
        this._fixRate = viewbox.Width / viewbox.Height;

        viewbox.Width = double.NaN;
        viewbox.Height = double.NaN;
    }

    private const int WM_SIZING = 0x214;
    private const int WMSZ_LEFT = 1;
    private const int WMSZ_RIGHT = 2;
    private const int WMSZ_TOP = 3;
    private const int WMSZ_TOPLEFT = 4;
    private const int WMSZ_TOPRIGHT = 5;
    private const int WMSZ_BOTTOM = 6;
    private const int WMSZ_BOTTOMLEFT = 7;
    private const int WMSZ_BOTTOMRIGHT = 8;

    private IntPtr WndHookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == WM_SIZING)
        {
            var rect = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT));

            var w = rect.right - rect.left - this._horizontalMargin;
            var h = rect.bottom - rect.top - this._verticalMargin;

            switch (wParam.ToInt32())
            {
                case WMSZ_LEFT:
                case WMSZ_RIGHT:
                    rect.bottom = (int)(rect.top + w / this._fixRate + this._verticalMargin);
                    break;
                case WMSZ_TOP:
                case WMSZ_BOTTOM:
                    rect.right = (int)(rect.left + h * this._fixRate + this._horizontalMargin);
                    break;
                case WMSZ_TOPLEFT:
                    if (w / h > this._fixRate)
                    {
                        rect.top = (int)(rect.bottom - w / this._fixRate - this._verticalMargin);
                    }
                    else
                    {
                        rect.left = (int)(rect.right - h * this._fixRate - this._horizontalMargin);
                    }
                    break;
                case WMSZ_TOPRIGHT:
                    if (w / h > this._fixRate)
                    {
                        rect.top = (int)(rect.bottom - w / this._fixRate - this._verticalMargin);
                    }
                    else
                    {
                        rect.right = (int)(rect.left + h * this._fixRate + this._horizontalMargin);
                    }
                    break;
                case WMSZ_BOTTOMLEFT:
                    if (w / h > this._fixRate)
                    {
                        rect.bottom = (int)(rect.top + w / this._fixRate + this._verticalMargin);
                    }
                    else
                    {
                        rect.left = (int)(rect.right - h * this._fixRate - this._horizontalMargin);
                    }
                    break;
                case WMSZ_BOTTOMRIGHT:
                    if (w / h > this._fixRate)
                    {
                        rect.bottom = (int)(rect.top + w / this._fixRate + this._verticalMargin);
                    }
                    else
                    {
                        rect.right = (int)(rect.left + h * this._fixRate + this._horizontalMargin);
                    }
                    break;
                default:
                    break;
            }
            Marshal.StructureToPtr(rect, lParam, true);
        }
        return IntPtr.Zero;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }
}

ポイント

一気にソースコード用が増えましたね。それはさておき…、XAML側はViewboxを配置しているだけなので特に問題ないですよね。WindowのリサイズはWPFだけでは拾えない(※)ので、Win32APIの出番です。OnSourceInitializedメソッドあたりがその処理を受け持っています。あとは、WndHookProcメソッドでアスペクト比が一致するように調整し続けるだけですね。

(※)WPFでサイズ変更を拾う場合はは、SizeChangedイベントになるのですが、イベントが発生するタイミングですでにサイズ変更描画済みなので(Changedだし仕方ないよね)、ここでサイズ変更かけると画面が激しくちらつきますので、お勧めできません。

広告

1件のコメント »

  1. まさにこれをやりたかったので、大変参考になりました。ただ、Window_Loadedでマージンの処理はViewBoxもActualWidth、ActualHeightで取らないと駄目なようです。(サイズが指定されていないのでNaNになるので)

    コメント by yuizi — 2015年10月7日 @ 12:53 PM


RSS feed for comments on this post. TrackBack URI

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

WordPress.com で無料サイトやブログを作成.

%d人のブロガーが「いいね」をつけました。