ウィンドウを画面端にスナップさせるビヘイビア

| | WPF, プログラミング | ,

WPFアプリケーションを作ってて「ウィンドウがモニタの端にスナップ(吸い付くように自動で移動)してほしい!」っていうあなた!
そんな簡単に時に使えるビヘイビアです。

スナップ距離やスナップ有効・無効の設定のバインディングもできます。
あとモニタの表示倍率が100%でないときでも動作します(たぶん)。

WPFなのにSystem.Windows.Forms.dllとSystem.Drawing.dllを参照する必要がありなんだか負けた気がしますが、気にしないでいきましょう。

class WindowSnapBehavior : Behavior<Window>
{
	#region EnableSnap 依存関係プロパティ
	public double SnapDistance
	{
		get { return (double)GetValue(SnapDistanceProperty); }
		set { SetValue(SnapDistanceProperty, value); }
	}
	public static readonly DependencyProperty SnapDistanceProperty =
		DependencyProperty.Register("SnapDistance", typeof(double), typeof(WindowSnapBehavior), new PropertyMetadata(7.0));
	#endregion

	#region EnableSnap 依存関係プロパティ
	public bool EnableSnap
	{
		get { return (bool)GetValue(EnableSnapProperty); }
		set { SetValue(EnableSnapProperty, value); }
	}
	public static readonly DependencyProperty EnableSnapProperty =
		DependencyProperty.Register("EnableSnap", typeof(bool), typeof(WindowSnapBehavior), new PropertyMetadata(true));
	#endregion


	protected override void OnAttached()
	{
		AssociatedObject.LocationChanged += AssociatedObject_LocationChanged;
	}

	protected override void OnDetaching()
	{
		AssociatedObject.LocationChanged -= AssociatedObject_LocationChanged;
	}

	void AssociatedObject_LocationChanged(object sender, EventArgs e)
	{
		if (!EnableSnap) { return; }

		var window = AssociatedObject;
		if (window.WindowState != WindowState.Normal) { return; }

		// DPIから物理ピクセルへ変換する行列を取得してそれぞれの長さを変換
		var mat = PresentationSource.FromVisual(window).CompositionTarget.TransformToDevice;
		var scaledTopLeft = mat.Transform(new Point(window.Left, window.Top));
		var scaledBottomRight = mat.Transform(new Point(window.Left + window.ActualWidth, window.Top + window.ActualHeight));
		var scaledSnapDisatance = mat.Transform(new Point(SnapDistance, SnapDistance));
		var scaledSize = scaledBottomRight - scaledTopLeft;

		var screen = System.Windows.Forms.Screen.FromPoint(new System.Drawing.Point((int)scaledTopLeft.X, (int)scaledTopLeft.Y));
		var newTop = scaledTopLeft.Y;
		var newLeft = scaledTopLeft.X;

		// 横方向の調整
		if (Math.Abs(screen.Bounds.Left - scaledTopLeft.X) <= scaledSnapDisatance.X) {
			newLeft = screen.Bounds.Left;
		}
		else if (Math.Abs(screen.Bounds.Right - scaledBottomRight.X) <= scaledSnapDisatance.X) {
			newLeft = screen.Bounds.Right - scaledSize.X;
		}

		// 縦方向の調整
		if (Math.Abs(screen.Bounds.Top - scaledTopLeft.Y) <= scaledSnapDisatance.Y) {
			newTop = screen.Bounds.Top;
		}
		else if (Math.Abs(screen.Bounds.Bottom - scaledBottomRight.Y) <= scaledSnapDisatance.Y) {
			newTop = screen.Bounds.Bottom - scaledSize.Y;
		}

		window.Left = newLeft;
		window.Top = newTop;
	}
}

使う時はこんな感じです。

<Window x:Class="Studiotaiha.SnapTest.Views"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
		xmlns:behaviors="clr-namespace:Studiotaiha.SnapTest.Views.Behaviors"
        Title="SnapTest">
	<i:Interaction.Behaviors>
		<behaviors:WindowSnapBehavior />
	</i:Interaction.Behaviors>
	
	<Grid>
		<TextBlock>コンテンツ</TextBLock>
	</Grid>
</Window>

実際に使う時はPresentationSourceの取得に失敗したりするかもしれないので、エラー処理は忘れずに。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です