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の取得に失敗したりするかもしれないので、エラー処理は忘れずに。