简体   繁体   中英

How do you disable Aero Snap in an application?

是否可以在WPF应用程序中禁用Windows 7的自动窗口停靠功能?

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!

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

I was using anthony's solution for awhile, but if you switch ResizeMode the window would temporary remove the sizing border, which is a little anoying. Here's another solution. By setting the WS_OVERLAPPEDWINDOW flag and removing the WS_THICKFRAME flag will disable Aero Snap feature for the window while not temporary removing the sizing border. You can play around with the styles to get the exact style you need, but key is removing the WS_THICKFRAME flag.

        public enum WindowStyles: int
{
    WS_BORDER = 0x00800000,
    WS_CAPTION = 0x00C00000,
    WS_CHILD = 0x40000000,
    WS_CHILDWINDOW = 0x40000000,
    WS_CLIPCHILDREN = 0x02000000,
    WS_CLIPSIBLINGS = 0x04000000,
    WS_DISABLED = 0x08000000,
    WS_DLGFRAME = 0x00400000,
    WS_GROUP = 0x00020000,
    WS_HSCROLL = 0x00100000,
    WS_ICONIC = 0x20000000,
    WS_MAXIMIZE = 0x01000000,
    WS_MAXIMIZEBOX = 0x00010000,
    WS_MINIMIZE = 0x20000000,
    WS_MINIMIZEBOX = 0x00020000,
    WS_OVERLAPPED = 0x00000000,
    WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
    WS_POPUP = unchecked((int)0x80000000),
    WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU,
    WS_SIZEBOX = 0x00040000,
    WS_SYSMENU = 0x00080000,
    WS_TABSTOP = 0x00010000,
    WS_THICKFRAME = 0x00040000,
    WS_TILED = 0x00000000,
    WS_TILEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
    WS_VISIBLE = 0x10000000,
    WS_VSCROLL = 0x00200000,
}

int newWinLongStyle = 0;
newWinLongStyle |= (int)WindowStyles.WS_OVERLAPPEDWINDOW;
newWinLongStyle ^= (int)WindowStyles.WS_THICKFRAME; 
WindowInteropHelper helper = new WindowInteropHelper(this);
NativeMethods.SetWindowLong(helper.Handle, (int)WindowStyles.GWL_STYLE, newWinLongStyle);

I needed to detect Windows 7 Aero snaps/docks to prevent window-size changes on a WPF application. During my search, I stumbled upon this post and found the answer given by anthony very helpful.

Following is what worked for me.

private void DisplayWindow_MouseMove(object sender, MouseEventArgs e) 
{
    if (e.LeftButton == MouseButtonState.Released) 
    {
        this.ResizeMode = System.Windows.ResizeMode.CanResizeWithGrip;
    }
}

private void DisplayWindow_LocationChanged(object sender, EventArgs e) 
{
    this.ResizeMode = System.Windows.ResizeMode.NoResize;
}

The window's XAML had the ResizeMode="CanResizeWithGrip" setting.

Edit :
My response was not handle the Windows 7 Aero snap properly. bjo's response elegantly solved the problem for me.

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;
    }
}

DragMove suspends the UI Thread. That code also works.

void Window1_MouseDown(object sender, MouseButtonEventArgs e)
{
    if( e.LeftButton == MouseButtonState.Pressed )
    {
        // this prevents win7 aerosnap
        this.ResizeMode = System.Windows.ResizeMode.NoResize;
        this.UpdateLayout();

        DragMove();

        // restore resize grips
        this.ResizeMode = System.Windows.ResizeMode.CanResizeWithGrip;
        this.UpdateLayout();
    }
}

它可能不是你的完美解决方案,但对我来说,将表单设置为不可调整大小的方法就可以了。

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.

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));
    }

设置“this.MaximizeBox = false”实际上在Windows 10上为我打破了Snap。也许这对你有用吗?

In Ease of Access in control panel, choose

Make it easier to focus on tasks

and tick

Prevent windows from being automatically arranged when moved to the edge of the screen

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM