如何使一个WPF窗口移动拖动扩展窗框? [英] How do I make a WPF window movable by dragging the extended window frame?

查看:281
本文介绍了如何使一个WPF窗口移动拖动扩展窗框?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在像Windows资源管理器和Internet Explorer的应用程序,你可以抓住扩展帧区域的标题栏并拖动窗口周围的下方。

In applications like Windows Explorer and Internet Explorer, one can grab the extended frame areas beneath the title bar and drag windows around.

有关的WinForms应用程序,窗体和控件都尽可能接近原生的Win32 API,因为他们可以得到;人会简单地覆盖其形式的WndProc()处理程序,过程中的<一个href="http://msdn.microsoft.com/en-us/library/ms645618%28VS.85%29.aspx"><$c$c>WM_NCHITTEST窗口消息和欺骗系统,以为一个点击框区域是真正的标题栏点击通过返回 HTCAPTION 。我已经做了我自己的WinForms应用服务,以愉快的效果。

For WinForms applications, forms and controls are as close to native Win32 APIs as they can get; one would simply override the WndProc() handler in their form, process the WM_NCHITTEST window message and trick the system into thinking a click on the frame area was really a click on the title bar by returning HTCAPTION. I've done that in my own WinForms apps to delightful effect.

在WPF中,我还可以实现类似的的WndProc()方法和它连接到我的WPF窗口的句柄同时延长了窗框进入客户区,像这样:

In WPF, I can also implement a similar WndProc() method and hook it to my WPF window's handle while extending the window frame into the client area, like this:

// In MainWindow
// For use with window frame extensions
private IntPtr hwnd;
private HwndSource hsource;

private void Window_SourceInitialized(object sender, EventArgs e)
{
    try
    {
        if ((hwnd = new WindowInteropHelper(this).Handle) == IntPtr.Zero)
        {
            throw new InvalidOperationException("Could not get window handle for the main window.");
        }

        hsource = HwndSource.FromHwnd(hwnd);
        hsource.AddHook(WndProc);

        AdjustWindowFrame();
    }
    catch (InvalidOperationException)
    {
        FallbackPaint();
    }
}

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    switch (msg)
    {
        case DwmApiInterop.WM_NCHITTEST:
            handled = true;
            return new IntPtr(DwmApiInterop.HTCAPTION);

        default:
            return IntPtr.Zero;
    }
}

的问题是,因为我盲目地设置办理=真并返回 HTCAPTION ,点击随时随地的但是窗口图标或控制按钮导致要拖动窗口。也就是说,一切都在下面的红色导致拖动突出。这甚至包括调整大小处理窗口(非客户区)的两侧。我的WPF控件,即文本框和标签控制,也停止接收点击的结果:

The problem is that, since I'm blindly setting handled = true and returning HTCAPTION, clicking anywhere but the window icon or the control buttons causes the window to be dragged. That is, everything highlighted in red below causes dragging. This even includes the resize handles at the sides of the window (the non-client area). My WPF controls, namely the text boxes and the tab control, also stop receiving clicks as a result:

我要的是只

  1. 的标题栏,
  2. 客户区的区...
  3. ...这不是由我控制占用

为可拖动。也就是说,我只希望这些红色区域为可拖动(客户区+标题栏):

to be draggable. That is, I only want these red regions to be draggable (client area + title bar):

如何修改我的的WndProc()方法和我的窗口的XAML / code-身后,以确定哪些领域应该返回<$ C $的其余部分C> HTCAPTION ,哪些不应该?我想的东西沿的使用S来检查对我的控制位置的点击位置的线条,但我不知道如何去了解它在WPF的土地。

How do I modify my WndProc() method and the rest of my window's XAML/code-behind, to determine which areas should return HTCAPTION and which shouldn't? I'm thinking something along the lines of using Points to check the location of the click against the locations of my controls, but I'm not sure how to go about it in WPF land.

修改[4/24]:关于它的一个简单方法是有一个无形的控制,甚至是窗口本身,以的MouseLeftButtonDown 通过调用 DragMove()的窗口(见<一href="http://stackoverflow.com/questions/5493149/how-do-i-make-a-wpf-window-movable-by-dragging-the-extended-window-frame/5773515#5773515">Ross's回答)。问题是,由于某种原因, DragMove()并不重要,如果窗口最大化工作,所以也没有发挥好与Windows 7的Aero对齐。由于我打算为Windows 7集成,这不是我的情况下,可接受的解决方案。

EDIT [4/24]: one simple way about it is to have an invisible control, or even the window itself, respond to MouseLeftButtonDown by invoking DragMove() on the window (see Ross's answer). The problem is that for some reason DragMove() doesn't work if the window is maximized, so it doesn't play nice with Windows 7 Aero Snap. Since I'm going for Windows 7 integration, it's not an acceptable solution in my case.

推荐答案

由于今天上午我收到一封电子邮件,我被提示进行工作示例应用程序,展示了这个非常功能。我已经做了,现在, //wpfdraggableframe.$c$cplex.com :你可以在 HTTP找到它。刚刚从来源$ C ​​$ C 标签下载最新的版本,在Visual Studio中打开它,并构建并运行它。

Sample code

Thanks to an email I got this morning, I was prompted to make a working sample app demonstrating this very functionality. I've done that now; you can find it at http://wpfdraggableframe.codeplex.com. Just download the latest revision from the Source Code tab, open it in Visual Studio, and build and run it.

在全部完整的应用程序是MIT许可,但你很可能会把它拆开,并把它的code位在你自己的,而不是使用应用程序,code全 - 不说,许可证阻止你要么做。此外,虽然我知道应用程序的主窗口的设计是不是接近类似于上面的线框图的任何地方,这个想法是一样的提出的问题。

The complete application in its entirety is MIT-licensed, but you'll probably be taking it apart and putting bits of its code around your own rather than using the app code in full — not that the license stops you from doing that either. Also, while I know the design of the application's main window isn't anywhere near similar to the wireframes above, the idea is the same as posed in the question.

希望这可以帮助别人!

我终于解决了这个问题。感谢<一href="http://stackoverflow.com/questions/5493149/how-do-i-make-a-wpf-window-movable-by-dragging-the-extended-window-frame/5493446#5493446">Jeffrey大号Whitledge 的指着我在正确的方向! 他的回答被接受,因为如果没有它,我也不会设法制定出一个解决方案 修改[9/8]:这个答案现在已经接受,因为它更完成;我给杰弗里一个漂亮的大赏金,而不是对他的帮助。

I finally solved it. Thanks to Jeffrey L Whitledge for pointing me in the right direction! His answer was accepted because if not for it I wouldn't have managed to work out a solution. EDIT [9/8]: this answer is now accepted as it's more complete; I'm giving Jeffrey a nice big bounty instead for his help.

有关子孙后代的缘故,这里是我做到了(报价杰弗里的答案在相关的,因为我去):

For posterity's sake, here's how I did it (quoting Jeffrey's answer where relevant as I go):

获取的鼠标点击的位置(距离的wParam,lParam的可能?),并用它来创建一个(可能与某种坐标变换的?)

Get the location of the mouse click (from the wParam, lParam maybe?), and use it to create a Point (possibly with some kind of coordinate transformation?).

这个信息可以从的lParam WM_NCHITTEST 信息的获得。的x坐标的光标的是其低位字和y坐标的光标的是它的高位字,作为 MSDN描述

This information can be obtained from the lParam of the WM_NCHITTEST message. The x-coordinate of the cursor is its low-order word and the y-coordinate of the cursor is its high-order word, as MSDN describes.

由于坐标是相对于整个屏幕,我需要调用 Visual.PointFromScreen()在我的窗口转换的坐标是相对于窗口的空间。

Since the coordinates are relative to the entire screen, I need to call Visual.PointFromScreen() on my window to convert the coordinates to be relative to the window space.

然后调用静态方法 VisualTreeHelper.HitTest(视觉,点)将它传递您刚刚进行。返回值将指示最高的Z顺序控制。

Then call the static method VisualTreeHelper.HitTest(Visual,Point) passing it this and the Point that you just made. The return value will indicate the control with the highest Z-Order.

我必须通过在顶级电网的控制,而不是作为视觉测试反对点。同样,我不得不检查结果是否为空,而不是检查,如果它是窗口。如果是空,光标没有击中任何网格的子控件的 - 换句话说,它击中了无人居住的窗框区域。无论如何,关键是使用 VisualTreeHelper.HitTest()方法。

I had to pass in the top-level Grid control instead of this as the visual to test against the point. Likewise I had to check whether the result was null instead of checking if it was the window. If it's null, the cursor didn't hit any of the grid's child controls — in other words, it hit the unoccupied window frame region. Anyway, the key was to use the VisualTreeHelper.HitTest() method.

现在,话虽如此,有两个警告可能适用于你,如果你按照我的步骤:

Now, having said that, there are two caveats which may apply to you if you're following my steps:

  1. 如果你没有覆盖整个窗口,而只是部分延伸的窗框,你必须把过,这不是由窗框填充为客户区域填充矩形控制。

  1. If you don't cover the entire window, and instead only partially extend the window frame, you have to place a control over the rectangle that's not filled by window frame as a client area filler.

在我的情况,我的标签控件的内容区域适合该矩形区域就好了,如图所示的图。在您的应用程序,你可能需要将矩形形状或面板控制和写生,适当的颜色。这样的控制将受到打击。

In my case, the content area of my tab control fits that rectangular area just fine, as shown in the diagrams. In your application, you may need to place a Rectangle shape or a Panel control and paint it the appropriate color. This way the control will be hit.

此问题有关客户区填料引出了下一个:

This issue about client area fillers leads to the next:

如果您的网格或其他顶级控制有一个背景纹理或渐变在扩展窗框的整个网格区域的将响应命中,即使在任何完全透明区域背景(见选中测试,在Visual层)。在这种情况下,你要忽略对电网本身命中,只注重控制之内了。

If your grid or other top-level control has a background texture or gradient over the extended window frame, the entire grid area will respond to the hit, even on any fully transparent regions of the background (see Hit Testing in the Visual Layer). In that case, you'll want to ignore hits against the grid itself, and only pay attention to the controls within it.

因此​​:

// In MainWindow
private bool IsOnExtendedFrame(int lParam)
{
    int x = lParam << 16 >> 16, y = lParam >> 16;
    var point = PointFromScreen(new Point(x, y));

    // In XAML: <Grid x:Name="windowGrid">...</Grid>
    var result = VisualTreeHelper.HitTest(windowGrid, point);

    if (result != null)
    {
        // A control was hit - it may be the grid if it has a background
        // texture or gradient over the extended window frame
        return result.VisualHit == windowGrid;
    }

    // Nothing was hit - assume that this area is covered by frame extensions anyway
    return true;
}

该窗口现在移动通过单击并拖动窗口只有未使用空间。

The window is now movable by clicking and dragging only the unoccupied areas of the window.

不过,这还不是全部。回想一下,在第一幅图的非客户区包括窗口边框也受 HTCAPTION 使窗口不再是可调整大小。

But that's not all. Recall in the first illustration that the non-client area comprising the borders of the window was also affected by HTCAPTION so the window was no longer resizable.

要解决这个问题,我不得不检查光标是否被击中的客户区或非客户区。为了检验这一点,我需要使用<一个href="http://msdn.microsoft.com/en-us/library/ms633572%28VS.85%29.aspx"><$c$c>DefWindowProc()功能,看看它是否返回 HTCLIENT

To fix this I had to check whether the cursor was hitting the client area or the non-client area. In order to check this I needed to use the DefWindowProc() function and see if it returned HTCLIENT:

// In my managed DWM API wrapper class, DwmApiInterop
public static bool IsOnClientArea(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam)
{
    if (uMsg == WM_NCHITTEST)
    {
        if (DefWindowProc(hWnd, uMsg, wParam, lParam).ToInt32() == HTCLIENT)
        {
            return true;
        }
    }

    return false;
}

// In NativeMethods
[DllImport("user32.dll")]
private static extern IntPtr DefWindowProc(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam);

最后,这是我的最后一个窗口过程的方法:

Finally, here's my final window procedure method:

// In MainWindow
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    switch (msg)
    {
        case DwmApiInterop.WM_NCHITTEST:
            if (DwmApiInterop.IsOnClientArea(hwnd, msg, wParam, lParam)
                && IsOnExtendedFrame(lParam.ToInt32()))
            {
                handled = true;
                return new IntPtr(DwmApiInterop.HTCAPTION);
            }

            return IntPtr.Zero;

        default:
            return IntPtr.Zero;
    }
}

这篇关于如何使一个WPF窗口移动拖动扩展窗框?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆