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

时间:2014-03-03 21:21:08

标签: c# .net wpf

当应用程序启动时,我希望我的WPF窗口自动捕捉到屏幕的右边缘。有没有办法做到这一点?我也想保留它的尺寸。因此,与将窗口拖动到屏幕边缘时发生的捕捉行为不同,导致窗口调整大小到屏幕的一部分或全屏,我希望我的窗口只是捕捉到某个位置的边缘默认情况下或之后由用户拖动到特定位置,而不调整大小。我仍然希望保留用户将窗口拖离边缘的能力。

是否有类似的内容已经实现,或者我是否必须创建自己的行为架构?我尝试了很多搜索关键字组合,但找不到类似于我正在做的事情。一些搜索包括禁用捕捉行为或提供捕捉行为,但没有像我上面描述的那样。

修改

我找不到一个现成的解决方案,所以我写了自己的解决方案。这个解决方案基于BenVlodgi的建议,所以我感谢他帮助我。这是一个非常粗略的实现,仍然需要大量的抛光和更好的代码技术,但它的工作原理,它是任何想要尝试这一点的人的良好基础。它非常简单,与WPF配合得非常好。这个实现的唯一限制是我还没有试过让它与两个屏幕一起工作,但它非常简单(我只是没有时间去做它,我现在不需要那个功能) 。所以,这是代码,我希望它可以帮助那些人:

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

确保您的窗口具有以下内容:

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

此外,这不适用于窗口的Windows本机组件,如顶栏,所以我禁用样式并创建自己的(这对我来说实际上很好,因为我不想要窗口这种风格):

WindowStyle="None"

2 个答案:

答案 0 :(得分:5)

您可以使用Windows捕捉功能进行API调用(据我所见),但是您可以获取屏幕的System.Windows.Forms.Screen.PrimaryScreen.WorkingArea并设置TopLeftHeightWidth Window相应的属性。

修改:上述建议确实需要您可能不需要的表单。我相信WPF等价物是System.Windows.SystemParameters.WorkArea

答案 1 :(得分:4)

我不喜欢轮询方法,但是找到一个在WPF中仍然很简单的更好的解决方案是非常困难的,所以我要发布自己的。

我找到的解决方案实际上非常简单,因为它重新实现了窗口DragMove()方法的行为,这使您可以选择在拖动时更改窗口位置。以下代码通过存储窗口左上角和鼠标光标之间的距离来重新实现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;
        }
    }
}

现在你可以实现像这样的捕捉/粘滞窗口行为:如果窗口在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;

这种方法的缺点是,如果在标题栏上拖动窗口,则捕捉将不起作用,因为这不会触发OnMouseLeftButtonDown事件(我不需要,因为我的窗口是无边框的)。也许它仍然可以帮助别人。