Is it possible to disable the automatic window-docking feature of Windows 7 in a WPF application?
Here's my solution. Windows will not snap if their ResizeMode is set to ResizeMode.NoResize, therefore, the trick is to determine reliably when a drag/move begins and ends.
EDIT: alexandrud correctly pointed out that this will only work for "borderless" windows (WindowStyle = None in WPF terms).
Many Bothans died to bring us this information.
class NoSnapWindow : System.Windows.Window
{
public NoSnapWindow()
{
SourceInitialized += delegate {
var source = HwndSource.FromVisual(this) as HwndSource;
source.AddHook(SourceHook);
};
}
private IntPtr SourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case 0x112: // WM_SYSCOMMAND
switch (wParam.ToIn32() & ~0x0F)
{
case 0xF010: // SC_MOVE
ResizeMode = ResizeMode.NoResize;
break;
}
break;
case 0x2A2: // WM_MOUSELEAVE
ResizeMode = ResizeMode.CanResize;
break;
}
return IntPtr.Zero;
}
}
If you are giving example of "Sticky Notes" of Win7, you may have noticed that it does NOT have standard window border. On that as a base, I can only tell you that there's no direct way of doing this except you set ResizeMode="NoResize"
and handling the resize behavior manually. Following is a very basic, non-professional solution that i've quickly created to get you started, but you can append more functions if you like :)
<Window
x:Class="WpfApplication1.Window1"
x:Name="window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Width="300"
Height="300"
ResizeMode="NoResize"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
WindowState="Maximized">
<Window.Resources>
<x:Array
x:Key="TextBlockList"
Type="{x:Type TextBlock}">
<TextBlock
Text="○ Resize Horizontally by dragging right grip" />
<TextBlock
Text="○ Resize Vertically by dragging bottom grip" />
<TextBlock
Text="○ Move Horizontally by dragging left grip" />
<TextBlock
Text="○ Move Verticallyby dragging top grip" />
</x:Array>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition
Height="Auto" />
<RowDefinition
Height="{Binding Height, Mode=OneWay, ElementName=window}" />
<RowDefinition
Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="Auto" />
<ColumnDefinition
Width="{Binding Width, Mode=OneWay, ElementName=window}" />
<ColumnDefinition
Width="Auto" />
</Grid.ColumnDefinitions>
<GridSplitter
Grid.Column="1"
Grid.Row="1"
HorizontalAlignment="Left"
MinWidth="5" />
<GridSplitter
Grid.Column="1"
Grid.Row="1"
HorizontalAlignment="Right"
MinWidth="5" />
<GridSplitter
Grid.Column="1"
Grid.Row="1"
VerticalAlignment="Top"
MinHeight="5"
ResizeDirection="Rows"
HorizontalAlignment="Stretch" />
<GridSplitter
Grid.Column="1"
Grid.Row="1"
VerticalAlignment="Bottom"
MinHeight="5"
ResizeDirection="Rows"
HorizontalAlignment="Stretch" />
<Border
Grid.Column="1"
Grid.Row="1"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Margin="5">
<Grid x:Name="root">
<ItemsControl
ItemsSource="{StaticResource TextBlockList}" />
</Grid>
</Border>
</Grid>
</Window>
You can even make a control (basically a panel) that can be resized and moved within its parent canvas. Now this control can be filled into a transparent maximized window. This'll give you an illusion of your control being a window that doesn't respond to 'Window Snap' and will not dock!
Hope this helps.
Regards,
Mihir Gokani
A rather simple solution I found that works with borderless windows as well: Just hide the maximize button (event if it's already not displayed due to the lack of caption bar):
[DllImport("user32.dll")]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
private const int GWL_STYLE = -16;
private const int WS_MAXIMIZEBOX = 0x10000;
private void Window_OnSourceInitialized(object sender, EventArgs e)
{
var hwnd = new WindowInteropHelper((Window)sender).Handle;
var value = GetWindowLong(hwnd, GWL_STYLE);
SetWindowLong(hwnd, GWL_STYLE, (int)(value & ~WS_MAXIMIZEBOX));
}
I recently needed to do this to a custom, resizable ResizeMode = CanResizeWithGrip
WPF window with no window decorations (no title bar and buttons). I used DragMove()
to move the window, and when It is maximized by AeroSnap, the window becomes unmovable and hence locked in place.
I tried Barn Monkey's solution, which partially worked, but it would still show the AeroSnap graphic and resize the app to fullscreen size. I modified it below and now it works as expect: still resizable, but no AeroSnap at all.
void Window1_MouseDown(object sender, MouseButtonEventArgs e)
{
if( e.LeftButton == MouseButtonState.Pressed )
{
// this prevents win7 aerosnap
if( this.ResizeMode != System.Windows.ResizeMode.NoResize )
{
this.ResizeMode = System.Windows.ResizeMode.NoResize;
this.UpdateLayout();
}
DragMove();
}
}
void Window1_MouseUp( object sender, MouseButtonEventArgs e )
{
if( this.ResizeMode == System.Windows.ResizeMode.NoResize )
{
// restore resize grips
this.ResizeMode = System.Windows.ResizeMode.CanResizeWithGrip;
this.UpdateLayout();
}
}
EDIT:
It's been a while since I wrote this, but since people still look at this I'll update it with what I use now. I still use basically the same method for preventing edge snapping and moving my windows, but I now have them packed into custom Behavior<>
classes that I can attach to a Window
or UserControl
. This makes them very easy to use with MVVM (I use Caliburn Micro).
The behavior classes are:
/// <summary>
/// behavior that makes a window/dialog draggable by clicking anywhere
/// on it that is not a control (ie, button)
/// </summary>
public class DragMoveBehavior<T> : Behavior<T> where T : FrameworkElement
{
protected override void OnAttached()
{
AssociatedObject.MouseLeftButtonDown += MouseDown;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.MouseLeftButtonDown -= MouseDown;
base.OnDetaching();
}
void MouseDown( object sender, EventArgs ea ) => Window.GetWindow( sender as T )?.DragMove();
}
public class WinDragMoveBehavior : DragMoveBehavior<Window> { }
public class UCDragMoveBehavior : DragMoveBehavior<UserControl> { }
/// <summary>
/// behavior that makes a window/dialog not resizable while clicked. this prevents
/// the window from being snapped to the edge of the screen (AeroSnap). if DragMoveBehavior
/// is also used, this must be attached first.
/// </summary>
/// <typeparam name="T"></typeparam>
public class NoSnapBehavior<T> : Behavior<T> where T : FrameworkElement
{
ResizeMode lastMode = ResizeMode.NoResize;
protected override void OnAttached()
{
AssociatedObject.MouseLeftButtonDown += MouseDown;
AssociatedObject.MouseLeftButtonUp += MouseUp;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.MouseLeftButtonDown -= MouseDown;
AssociatedObject.MouseLeftButtonUp -= MouseUp;
base.OnDetaching();
}
/// <summary>
/// make it so the window can be moved by dragging
/// </summary>
void MouseDown( object sender, EventArgs ea )
{
var win = Window.GetWindow( sender as T );
if( win != null && win.ResizeMode != ResizeMode.NoResize )
{
lastMode = win.ResizeMode;
win.ResizeMode = ResizeMode.NoResize;
win.UpdateLayout();
}
}
void MouseUp( object sender, EventArgs ea )
{
var win = Window.GetWindow( sender as T );
if( win != null && win.ResizeMode != lastMode )
{
win.ResizeMode = lastMode;
win.UpdateLayout();
}
}
}
public class WinNoSnapBehavior : NoSnapBehavior<Window> { }
public class UCNoSnapBehavior : NoSnapBehavior<UserControl> { }
I then attach them to my dialog box Views with:
<UserControl ...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:util:="...">
<i:Interaction.Behaviors>
<util:UCNoSnapBehavior/>
<util:UCDragMoveBehavior/>
</i:Interaction.Behaviors>
...
</UserControl>
And it just works!
It might not be the perfect solution for you, but for me setting the form as non-resizable did the trick.
I don't have a Windows 7 box here, so I can't test this, but here's what I would try:
1- Create a test form and override the WndProc
2- Test and log specific messages pertaining to Size, Position and WindowState changing.
3- Determine if the messages sent to the window when docked are a combination of Size/Position/WindowState or if there is another, new Windows 7 message (5 minutes of searching didn't reveal anything to me.)
4- Once you have the messages, check to see if there is a "unique" case that is occurring.
5- Modify your code to accommodate that unique case.
If no one else comes up with anything, I might give this a whirl at home this weekend.