简体   繁体   English

如何在保留其大小的同时自动将WPF窗口捕捉到屏幕边缘?

[英]How to automatically snap a WPF window to an edge of the screen while retaining its size?

As the application starts, I'd like my WPF window to automatically snap to the right edge of the screen. 当应用程序启动时,我希望我的WPF窗口自动捕捉到屏幕的右边缘。 Is there a way to do that? 有没有办法做到这一点? I also want to be able to retain its dimensions. 我也想保留它的尺寸。 So, unlike the snapping behavior that happens when you drag a window to the edge of the screen, causing the window to resize to either a portion of the screen or full screen, I want my window to simply snap to the edge at a certain location by default or if dragged by the user to a specific location afterwards, without resizing. 因此,与将窗口拖动到屏幕边缘时发生的捕捉行为不同,导致窗口调整大小到屏幕的一部分或全屏,我希望我的窗口只是捕捉到某个位置的边缘默认情况下或之后由用户拖动到特定位置,而不调整大小。 I still want to retain the ability of the user to drag the window away from the edge. 我仍然希望保留用户将窗口拖离边缘的能力。

Is there anything like that already implemented or would I have to create my own behavior schema? 是否有类似的东西已经实现或者我必须创建自己的行为模式? I tried numerous search keyword combinations, but couldn't find anything similar to what I'm doing. 我尝试了很多搜索关键字组合,但找不到类似于我正在做的事情。 Some of the searches included disabling snapping behavior or providing snapping behavior, but nothing in the way I described above. 一些搜索包括禁用捕捉行为或提供捕捉行为,但没有像我上面描述的那样。

EDIT: 编辑:

I haven't been able to find a ready solution, so I wrote my own. 我找不到一个现成的解决方案,所以我写了自己的解决方案。 This solution is based on BenVlodgi's suggestions, so I thank him for helping me out. 这个解决方案基于BenVlodgi的建议,所以我感谢他帮助我。 This is a very rough implementation and still requires a lot of polishing and better code techniques, but it works and it's a good base for anyone wanting to try this. 这是一个非常粗略的实现,仍然需要大量的抛光和更好的代码技术,但它的工作原理,它是任何想要尝试这一点的人的良好基础。 It's incredibly simple and works very well with WPF. 它非常简单,与WPF配合得非常好。 The only limitation of this implementation is that I haven't tried getting it to work with two screens yet, but it's incredibly simple (I'm just not going to have time for it and I don't need that functionality at this point). 这个实现的唯一限制是我还没有试过让它与两个屏幕一起工作,但它非常简单(我只是没有时间去做它,我现在不需要那个功能) 。 So, here's the code and I hope that it helps someone out there: 所以,这是代码,我希望它可以帮助那里的人:

public partial class MainWindow : Window
{
    // Get the working area of the screen.  It excludes any dockings or toolbars, which
    // is exactly what we want.
    private System.Drawing.Rectangle screen = 
                                 System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;

    // This will be the flag for the automatic positioning.
    private bool dragging = false;

    //  The usual initialization routine
    public MainWindow()
    {
        InitializeComponent();
    }

    // Wait until window is lodaded, but prior to being rendered to set position.  This 
    // is done because prior to being loaded you'll get NaN for this.Height and 0 for
    // this.ActualHeight.
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        // Sets the initial position.
        SetInitialWindowPosition();
        // Sets the monitoring timer loop.
        InitializeWindowPositionMonitoring();
    }

    // Allows the window to be dragged where the are no other controls present.
    // PreviewMouseButton could be used, but then you have to do more work to ensure that if
    // you're pressing down on a button, it executes its routine if dragging was not performed.
    private void Window_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        // Set the dragging flag to true, so that position would not be reset automatically.
        if (e.ChangedButton == System.Windows.Input.MouseButton.Left)
        {
            dragging = true;
            this.DragMove();
        }
    }

    // Similar to MouseDown.  We're setting dragging flag to false to allow automatic 
    // positioning.
    private void Window_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        if (e.ChangedButton == System.Windows.Input.MouseButton.Left)
        {
            dragging = false;
        }
    }

    // Sets the initial position of the window.  I made mine static for now, but later it can
    // be modified to be whatever the user chooses in the settings.
    private void SetInitialWindowPosition()
    {
        this.Left = screen.Width - this.Width;
        this.Top = screen.Height / 2 - this.Height / 2;
    }

    // Setup the monitoring routine that automatically positions the window based on its location
    // relative to the working area.
    private void InitializeWindowPositionMonitoring()
    {
        var timer = new System.Windows.Threading.DispatcherTimer();
        timer.Tick += delegate
        {
            // Check if window is being dragged (assuming that mouse down on any portion of the
            // window is connected to dragging).  This is a fairly safe assumption and held
            // true thus far.  Even if you're not performing any dragging, then the position
            // doesn't change and nothing gets reset.  You can add an extra check to see if
            // position has changed, but there is no significant performance gain.
            // Correct me if I'm wrong, but this is just O(n) execution, where n is the number of
            // ticks the mouse has been held down on that window.
            if (!dragging)
            {
                // Checking the left side of the window.
                if (this.Left > screen.Width - this.Width)
                {
                    this.Left = screen.Width - this.Width;
                }
                else if (this.Left < 0)
                {
                    this.Left = 0;
                }

                // Checking the top of the window.
                if (this.Top > screen.Height - this.Height)
                {
                    this.Top = screen.Height - this.Height;
                }
                else if (this.Top < 0)
                {
                    this.Top = 0;
                }
            }
        };

        // Adjust this based on performance and preference.  I set mine to 10 milliseconds.
        timer.Interval = new TimeSpan(0, 0, 0, 0, 10);
        timer.Start();
    }
}

Make sure that your window has the following: 确保您的窗口具有以下内容:

MouseDown="Window_MouseDown"
MouseUp="Window_MouseUp"
WindowStartupLocation="Manual" 
Loaded="Window_Loaded"

Also, this doesn't work well with Windows native components of the window, such as the top bar, so I disable the style and create my own (which is actually good for me, since I don't want the windows style for this): 此外,这不适用于窗口的Windows本机组件,如顶栏,所以我禁用该样式并创建我自己的(这对我来说实际上很好,因为我不希望这样的Windows样式):

WindowStyle="None"

There is no API calls you can make (as far as I've seen) to use the Windows snapping features, however you could just get the System.Windows.Forms.Screen.PrimaryScreen.WorkingArea of the screen and set your Top , Left , Height and Width Properties of your Window accordingly. 您可以使用Windows捕捉功能进行API调用(据我所见),但是您可以获取屏幕的System.Windows.Forms.Screen.PrimaryScreen.WorkingArea并设置您的TopLeftWindow HeightWidth属性相应。

Edit : The above suggestion does require Forms, which you probably don't want. 编辑 :上述建议确实需要表格,您可能不需要。 I believe the WPF equivalent is System.Windows.SystemParameters.WorkArea 我相信WPF等价物是System.Windows.SystemParameters.WorkArea

I don't like the polling approach, but it's been excessively difficult to find a better solution that is still simple in WPF, so I'm gonna post my own. 我不喜欢轮询方法,但是找到一个在WPF中仍然很简单的更好的解决方案是非常困难的,所以我要发布自己的。

The solution that I found is actually quite simple, in that it reimplements the behaviour of the DragMove() method of the window, which gives you the option to change the window position while it's being dragged. 我发现的解决方案实际上非常简单,因为它重新实现了窗口的DragMove()方法的行为,这使您可以选择在拖动时更改窗口位置。 The following code reimplements DragMove() by storing the distance between the top left corner of the window and the mouse cursor. 下面的代码通过存储窗口左上角和鼠标光标之间的距离来重新实现DragMove()

public partial class MainWindow : Window
{
    // this is the offset of the mouse cursor from the top left corner of the window
    private Point offset = new Point();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        Point cursorPos = PointToScreen(Mouse.GetPosition(this));
        Point windowPos = new Point(this.Left, this.Top);
        offset = (Point)(cursorPos - windowPos);

        // capturing the mouse here will redirect all events to this window, even if
        // the mouse cursor should leave the window area
        Mouse.Capture(this, CaptureMode.Element);
    }

    private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        Mouse.Capture(null);
    }

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        if (Mouse.Captured == this && Mouse.LeftButton == MouseButtonState.Pressed)
        {
            Point cursorPos = PointToScreen(Mouse.GetPosition(this));
            double newLeft = cursorPos.X - offset.X;
            double newTop = cursorPos.Y - offset.Y;

            // here you can change the window position and implement
            // the snapping behaviour that you need

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

Now you could implement the snapping / sticky window behaviour like this: The window will stick to the edge of the screen if it's within a range of 25 pixels (plus or minus). 现在你可以像这样实现捕捉/粘滞窗口行为:如果窗口在25像素(正负)范围内,窗口将粘在屏幕边缘。

int snappingMargin = 25;

if (Math.Abs(SystemParameters.WorkArea.Left - newLeft) < snappingMargin)
    newLeft = SystemParameters.WorkArea.Left;
else if (Math.Abs(newLeft + this.ActualWidth - SystemParameters.WorkArea.Left - SystemParameters.WorkArea.Width) < snappingMargin)
    newLeft = SystemParameters.WorkArea.Left + SystemParameters.WorkArea.Width - this.ActualWidth;

if (Math.Abs(SystemParameters.WorkArea.Top - newTop) < snappingMargin)
    newTop = SystemParameters.WorkArea.Top;
else if (Math.Abs(newTop + this.ActualHeight - SystemParameters.WorkArea.Top - SystemParameters.WorkArea.Height) < snappingMargin)
    newTop = SystemParameters.WorkArea.Top + SystemParameters.WorkArea.Height - this.ActualHeight;

The downside of this approach is that the snapping will not work, if the window is being dragged on the title bar, because that doesn't fire the OnMouseLeftButtonDown event (which I don't need, because my window is borderless). 这种方法的缺点是,如果在标题栏上拖动窗口,则捕捉将不起作用,因为这不会触发OnMouseLeftButtonDown事件(我不需要,因为我的窗口是无边界的)。 Maybe it will still help someone. 也许它仍然可以帮助某人。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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