[WPF] ウィンドウのリサイズが終了するまで、コントロールのリサイズを行わない方法 その2

Pocket

間が開いてしまったが、ウィンドウのリサイズが終了するまで、コントロールのリサイズを行わない方法 その1 の続き。

このままだと、描画されていない部分が黒く残ってしまい気持ちが悪いので、レイアウトを行わない場合でも、ViewBox を使ってスケーリングして表示してみる。

方法

「その1」の時と同様、WM_ENTERSIZEMOVE と、WM_EXITSIZEMOVE のメッセージを拾って、サイズ変更中の状態を取得する。
WM_EXITSIZEMOVE がきたら、ViewBox の子要素を、ViewBox の ActualSize にあわせて、レイアウトを変更させる。

Window から ViewBox へ、サイズ変更中の連絡を行う方法はいろいろあるが、ViewBox を複数億場合などを考慮して、IsSizing 添付プロパティを使うことにする。

コード

中にラップ機能付き TextBlock を持たせたウィンドウ。

<Window x:Class="LayoutAfterResize02.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:LayoutAfterResize02"
        Title="LAR02" Loaded="Window_Loaded" Width="300" Height="300">
    <local:ResizeLagBox>
        <!-- 直下に置くと、Grid に Margin 設定した場合など、無視されてしまうので、Decorator でくくる。 -->
        <Decorator>
            <Grid>
                <TextBlock TextWrapping="WrapWithOverflow">
                    色はにほへど 散りぬるを 我が世たれぞ…
                    寿限無、寿限無 五劫の擦り切れ 海砂利…
                    The quick brown fox jumps over the lazy dog
                </TextBlock>
            </Grid>
        </Decorator>
    </local:ResizeLagBox>
</Window>

Window メッセージの処理と、添付プロパティが変更された場合のレイアウト変更も書いておく必要がある。

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Threading;

namespace LayoutAfterResize02 {
    /// <summary>メインウィンドウクラス</summary>
    public partial class MainWindow : Window {
        // ウィンドウメッセージ
        const int WM_SIZE = 0x0005;
        const int WM_ENTERSIZEMOVE = 0x0231;
        const int WM_EXITSIZEMOVE = 0x0232;

        // Win32API の PostMessage 関数のインポート
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        static extern bool PostMessage(IntPtr hWnd, Int32 Msg, IntPtr wParam, IntPtr lParam);

        /// <summary>ウィンドウコンストラクタ</summary>
        public MainWindow() {
            InitializeComponent();
        }

        /// <summary>ウィンドウロードイベントハンドラ</summary>
        private void Window_Loaded(object sender, RoutedEventArgs e) {
            var hsrc = HwndSource.FromVisual(this) as HwndSource;
            hsrc.AddHook(WndProc);
        }

        /// <summary>ウィンドウプロシージャ</summary>
        IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
            switch (msg) {
                case WM_ENTERSIZEMOVE:
                    LagResizeBox.SetIsSizing(this, true);
                    break;
                case WM_EXITSIZEMOVE:
                    LagResizeBox.SetIsSizing(this, false);
                    break;
            }
            return IntPtr.Zero;
        }
    }

    /// <summary>リサイズ中のみ、子要素のレイアウトを変更せず、スケーリングを行うデコレータ</summary>
    class LagResizeBox : Viewbox {
        /// <summary>指定したオブジェクトから、IsSizing 添付プロパティの値を取得</summary>
        public static bool GetIsSizing(DependencyObject obj) {
            return (bool)obj.GetValue(IsSizingProperty);
        }

        /// <summary>指定したオブジェクトに、IsSizing 添付プロパティの値を設定</summary>
        public static void SetIsSizing(DependencyObject obj, bool value) {
            obj.SetValue(IsSizingProperty, value);
        }

        /// <summary>IsSizing 依存関係プロパティ</summary>
        public bool IsSizing {
            get { return (bool)GetValue(IsSizingProperty); }
            set { SetValue(IsSizingProperty, value); }
        }

        /// <summary>IsSizing 添付プロパティ (子要素への継承あり)</summary>
        public static readonly DependencyProperty IsSizingProperty =
            DependencyProperty.RegisterAttached("IsSizing", typeof(bool), typeof(FrameworkElement)
                , new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits, IsSizingPropertyChanged));

        /// <summary><see cref="IsSizingProperty"/> 添付プロパティ変更イベントハンドラ</summary>
        static void IsSizingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){
            var box = d as LagResizeBox;
            if (box != null && (bool)e.OldValue == true && (bool)e.NewValue == false) {
                box.Fit();
            }
        }

        /// <summary>オブジェクトの初期化</summary>
        public LagResizeBox() {
            this.Stretch = System.Windows.Media.Stretch.Fill;
        }

        protected override void OnInitialized(EventArgs e) {
            base.OnInitialized(e);

            // 最初の描画が終了後、Fit を呼ぶ
            Dispatcher.BeginInvoke(new Action(() => {
                this.Fit();
            }), DispatcherPriority.Loaded);
        }

        void Fit() {
            // 子要素のレイアウトを、ViewBox の描画サイズに合わせる
            var child = this.Child as FrameworkElement;
            if (child != null) {
                child.Width = this.ActualWidth;
                child.Height = this.ActualHeight;
            }
        }
    }
}

結果

サイズ変更中は、要素が引き延ばされて表示され、サイズが確定後、あらためてレイアウト調整が行われた形になる。

↓サイズ変更中はスケーリングされ…、

↓マウスを離すとこうなる。

[WPF] ウィンドウのリサイズが終了するまで、コントロールのリサイズを行わない方法 その2」への2件のフィードバック

  1. ■1は、スパムとして処理されてしまったようなので、文章として書くと、xamlの中の、localでResizeLagBoxとなっていますが、LagResizeBoxとクラス名に変更するということです。

  2. WPFの処理速度アップのために、参考になるコードがありがとうございます。

    Windows 10 Proの環境で、以下、2カ所変更することでエラー無く機能するようになりました。

    ■1
     → 

    ■2
    DependencyProperty.RegisterAttached(“IsSizing”, typeof(bool), typeof(FrameworkElement)
    → 
    DependencyProperty.RegisterAttached(“IsSizing”, typeof(bool), typeof(LagResizeBox)

    参照:
    https://codeday.me/jp/qa/20190213/239406.html

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください