Thursday, April 7, 2011

WPF Window Drag/Move Boundary

Hi everyone, just curious if you know of any way to setup a drag boundary for a window?

It would be nice to have these properties:

Me.MinLeft = 10
Me.MinTop = 10
Me.MaxLeft = 150
Me.MaxTop = 150

Those are made up properties, btw, which would be nice to have.

I know I could probably setup a timer to fire ever 10th of a second and check the left and top and then move it back if it's over. But it would be more elegant to have the window act like it hit a wall and can't go any farther, like moving to the edge of the screen or something similar.

Edit: There seems to be some confusion somewhere, the point I'm trying to make is in the paragraph above, dragging, not re-sizing.

From stackoverflow
  • There are dependency properties for WPF's Window for this purpose.

    Here they are:

    • Window.MaxWidth
    • Window.MaxHeight

    These properties will constrain the size of the Window, just like the WinForm's Form.

    ScottN : I'm speaking of dragging a window around the screen, not re-sizing it. Think of setting a region on the screen the window is only allowed to move round in, even though your screen is bigger. Like a drag container is best I can think of.
    ScottN : I'm sitting here reading this again and wondering if I asked the question wrong and how you could think I was speaking of window size and not dragging, I don't even mention the word "size" or "re-size" in my question...
    eriawan : Drag boundary? I think it's by default is draggable. Do you want drag handle like the ones in the Windows Forms?
    Drew Noakes : He wants to constraint the area within which a window can be dragged.
  • Maybe you could handle PreviewMouseMove (either the event or override the corresponding protected method) and set e.Handled = true whenever the mouse movement would cause the window to move outside the region you want to constrain it to.

    This seems like the most logical, WPF-like way of doing this.

  • Here is teh "magic" you need to create this functionality, all you have to do is set the Window_SourceInitialized method to the window's SourceInitialized event and insert you logic where the big comment is.

    I combined this code from several sources, so there could be some syntax errors in it.

    internal enum WM
    {
       WINDOWPOSCHANGING = 0x0046,
    }
    
    [StructLayout(LayoutKind.Sequential)]
    internal struct WINDOWPOS
    {
       public IntPtr hwnd;
       public IntPtr hwndInsertAfter;
       public int x;
       public int y;
       public int cx;
       public int cy;
       public int flags;
    }
    
    private void Window_SourceInitialized(object sender, EventArgs ea)
    {
       HwndSource hwndSource = (HwndSource)HwndSource.FromVisual((Window)sender);
       hwndSource.AddHook(DragHook);
    }
    
    private static IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
    {
       switch ((WM)msg)
       {
          case WM.WINDOWPOSCHANGING:
          {
              WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
              if ((pos.flags & (int)SWP.NOMOVE) != 0)
              {
                  return IntPtr.Zero;
              }
    
              Window wnd = (Window)HwndSource.FromHwnd(hwnd).RootVisual;
              if (wnd == null)
              {
                 return IntPtr.Zero;
              }
    
              bool changedPos = false;
    
              // ***********************
              // Here you check the values inside the pos structure
              // if you want to override tehm just change the pos
              // structure and set changedPos to true
              // ***********************
    
              if (!changedPos)
              {
                 return IntPtr.Zero;
              }
    
              Marshal.StructureToPtr(pos, lParam, true);
              handeled = true;
           }
           break;
       }
    
       return IntPtr.Zero;
    }
    
  • As I have no doubt that Nir's answer will work spending a little time implementing it, I was able to do what I wanted a little bit more elegant with this code:

    Private Sub myWindow_LocationChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.LocationChanged
    
        Dim primaryBounds As System.Drawing.Rectangle = Windows.Forms.Screen.PrimaryScreen.Bounds
        Dim windowBounds As System.Drawing.Rectangle = New System.Drawing.Rectangle(CInt(Me.Left), CInt(Me.Top), CInt(Me.Width), CInt(Me.Height))
    
        If (windowBounds.Left < 0) Then
            windowBounds = New System.Drawing.Rectangle(0, windowBounds.Top, windowBounds.Width, windowBounds.Height)
        ElseIf (windowBounds.Right > primaryBounds.Right) Then
            windowBounds = New System.Drawing.Rectangle(primaryBounds.Right - windowBounds.Width, windowBounds.Top, windowBounds.Width, windowBounds.Height)
        End If
    
        If (windowBounds.Top < 0) Then
            windowBounds = New System.Drawing.Rectangle(windowBounds.Left, 0, windowBounds.Width, windowBounds.Height)
        ElseIf (windowBounds.Bottom > primaryBounds.Bottom) Then
            windowBounds = New System.Drawing.Rectangle(windowBounds.Left, primaryBounds.Bottom - windowBounds.Height, windowBounds.Width, windowBounds.Height)
        End If
    
        Me.Left = windowBounds.Left
        Me.Top = windowBounds.Top
    
    End Sub
    

    This made the window being dragged stay within the primary screen (whole window), but you could easily change the bounds to whatever values you needed.

0 comments:

Post a Comment

Note: Only a member of this blog may post a comment.