遅い→起動時

http://d.hatena.ne.jp/pmint/

WPFでWindowsXPでも使える影付きウィンドウを作ってみた (1)


d:id:pmint:20130801:p1の続き。


前回のはウィンドウとしては不完全で、画面いっぱいに広げたとしても画面端が空いているように見えてしまう。ウィンドウに見えるところが実はウィンドウ領域内(クライアント領域)なので。これをどうにかするため画面端に近づくほど影部分を細くするようにしてみた。


影部分はウィンドウの内枠(Border)。Window.BorderThicknessで広さが決まる。この値を変えてもウィンドウサイズは変わらないので、ウィンドウ本体(クライアント領域)が拡大縮小してしまう。その結果、画面端に向かうほどウィンドウが広がっていく気持ちの悪い仕様になった。


こうじゃなく画面端からはみ出した分だけ影を細くするのが正解かな。影部分だけでなくウィンドウサイズも調整しないと。


プロジェクトファイル(VS2010)

DropShadow4.zip

ClickOnceで実行してみる(InternetExplorer向け)

実装

ウィンドウ定義(XAML)
<Window x:Class="DropShadow4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" BorderThickness="30" ResizeMode="CanResizeWithGrip" WindowStartupLocation="CenterScreen"
        AllowsTransparency="True" WindowStyle="None"
        MouseDown="Window_MouseDown" MouseDoubleClick="Window_MouseDoubleClick" Activated="Window_Activated" Deactivated="Window_Deactivated"
        SizeChanged="Window_SizeChanged" LocationChanged="Window_LocationChanged" StateChanged="Window_StateChanged"
        >
    <Window.Background>
        <ImageBrush ImageSource="pack://application:,,,/Resources/cream_pixels.png" Stretch="None" TileMode="Tile" Viewport="0,0,160,160" ViewportUnits="Absolute" />
    </Window.Background>
</Window>
イベントその他(C#)
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Effects;
using P = System.Windows.SystemParameters;

namespace DropShadow4
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private DropShadowEffect _effect = new DropShadowEffect();
        Thickness _initialBorderThickness;

        public MainWindow()
        {
            InitializeComponent();
            this._initialBorderThickness = this.BorderThickness;
            this.MaxWidth = P.MaximumWindowTrackWidth;
            this.MaxHeight = P.MaximumWindowTrackHeight;
            {
                _effect.Color = Colors.Black;
                _effect.BlurRadius = 20;
                _effect.ShadowDepth = 0;
                _effect.Opacity = 0.25;
                _effect.RenderingBias = RenderingBias.Performance;
            }
        }

        private void Window_Activated(object sender, EventArgs e)
        {
            this.Effect = _effect;
        }

        private void Window_Deactivated(object sender, EventArgs e)
        {
            this.Effect = null;
        }

        private void Window_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
                this.DragMove();
        }

        private void Window_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Left)
                this.WindowState = (this.WindowState != WindowState.Maximized) ? WindowState.Maximized : WindowState.Normal;
        }

        private void LayoutWindow()
        {
            var thickness = new Thickness(this._initialBorderThickness.Left, this._initialBorderThickness.Top, this._initialBorderThickness.Right, this._initialBorderThickness.Bottom);

            Rect bounds;
            switch (this.WindowState)
            {
                case WindowState.Maximized:
                    bounds = new Rect(new Point(0, 0), new Size(P.MaximumWindowTrackWidth, P.MaximumWindowTrackHeight));
                    break;
                default:
                    bounds = this.RestoreBounds;
                    break;
            }

            {
                var over = new Thickness(0);
                {
                    over.Left = Math.Min(0, bounds.Left - (P.WorkArea.Left + this._initialBorderThickness.Left));
                    if (over.Left < 0)
                        thickness.Left = Math.Max(0, this._initialBorderThickness.Left - Math.Abs(over.Left));
                }
                {
                    over.Top = Math.Min(0, bounds.Top - (P.WorkArea.Top + this._initialBorderThickness.Top));  //  + P.CaptionHeight
                    if (over.Top < 0)
                        thickness.Top = Math.Max(0, this._initialBorderThickness.Top - Math.Abs(over.Top));
                }
                {
                    over.Right = Math.Max(0, bounds.Right - (P.WorkArea.Right - this._initialBorderThickness.Right));
                    if (over.Right > 0)
                        thickness.Right = Math.Max(0, this._initialBorderThickness.Right - over.Right);
                }
                {
                    over.Bottom = Math.Max(0, bounds.Bottom - (P.WorkArea.Bottom - this._initialBorderThickness.Bottom));
                    if (over.Bottom > 0)
                        thickness.Bottom = Math.Max(0, this._initialBorderThickness.Bottom - over.Bottom);
                }
                Debug.WriteLine(new { over, thickness, bounds }, "LayoutWindow");
            }

            this.BorderThickness = thickness;
        }

        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Debug.WriteLine(new { e.PreviousSize, e.NewSize }, "Window_SizeChanged");
            LayoutWindow();
        }

        private void Window_LocationChanged(object sender, EventArgs e)
        {
            Debug.WriteLine(new { this.Left, this.Top }, "Window_LocationChanged");
            LayoutWindow();
        }

        private void Window_StateChanged(object sender, EventArgs e)
        {
            Debug.WriteLine(new { this.WindowState, this.RestoreBounds }, "Window_LocationChanged");
            LayoutWindow();
        }

    }
}

問題点


マウスドラッグによるウィンドウ移動 + そのイベントハンドラー内でウィンドウ位置を変更すると…

  1. マウスでウィンドウを移動させる
  2. PreviewMouseMove内でウィンドウ位置調整
  3. ウィンドウは移動したものの、マウスポインター位置がそのまま
  4. ウィンドウ:「ポインターが瞬間移動した!?」
  5. PreviewMouseMoveが発生するので、また調整しないと
  6. 2に戻る


→ (たぶんこんな理由で)ウィンドウがガクブル震え出す。



ドラッグ中にマウスとウィンドウ位置のずれを解消しないといけない?


サンプル(C#)
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;

namespace WindowMoving1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.PreviewMouseMove += new MouseEventHandler(Window_PreviewMouseMove);
        }

        private Point _pos;
        private void Window_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (_pos == default(Point))
            {
                _pos = e.MouseDevice.GetPosition(this);
                return;
            }

            Debug.WriteLine(new { _pos }, "Window_PreviewMouseMove1");

            if (e.LeftButton == MouseButtonState.Pressed)
            {
                var delta = new Vector();

                var pos = e.MouseDevice.GetPosition(this);
                delta.X = pos.X - _pos.X;
                delta.Y = pos.Y - _pos.Y;
                Debug.WriteLine(new { delta }, "Window_PreviewMouseMove2");
                //Thread.Sleep(200);

                {
                    _pos = pos;                     // ×ウィンドウがブルブル
                    //_pos = default(Point);      // ○スムーズに動く
                }

                this.Left += delta.X;
                this.Top += delta.Y;
            }
            else
            {
                _pos = default(Point);
            }
        }
    }
}

解決策


座標更新をイベント1回ごとの加算式ではなく、何度更新しても同じ結果になるようにするだけ。

using System.Diagnostics;
using System.Windows;
using System.Windows.Input;

namespace WindowMoving2
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.WindowStyle = WindowStyle.None;

            this.MouseDown += new MouseButtonEventHandler(Window_MouseDown);
            this.MouseUp += new MouseButtonEventHandler(Window_MouseUp);
            this.MouseMove += new MouseEventHandler(Window_MouseMove);
        }

        private Point _dragStart;
        void Window_MouseDown(object sender, MouseButtonEventArgs e)
        {
            _dragStart = e.GetPosition(this);
        }

        void Window_MouseUp(object sender, MouseButtonEventArgs e)
        {
            _dragStart = default(Point);
        }

        private void Window_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                Debug.Assert(_dragStart != default(Point));

                var delta = new Vector();
                {
                    var pos = e.GetPosition(this);
                    delta.X = pos.X - _dragStart.X;
                    delta.Y = pos.Y - _dragStart.Y;
                }

                this.Left += delta.X;
                this.Top += delta.Y;
            }
        }
    }
}

でもやっぱりドラッグ中にウィンドウ位置を調整するとウィンドウがブルブル震え出すんですよね…。