2009-11-11 121 views
9

我有一个WPF应用程序,其中主窗口的装饰是通过WindowStyle =“None”自定义的。我画我自己的标题栏和最小/最大/关闭按钮。不幸的是,当窗口大小调整时,Windows并没有执行我的MinWidth和MinHeight属性,因此允许将窗口调整到3x3(appx--足以显示窗口增长的句柄)。如何在WindowStyle =“None”的WPF窗口中强制执行MinWidth和MinHeight?

我已经不得不拦截窗口事件(sp。0x0024)来修复由WindowStyle = none引起的最大化错误(它将在Windows任务栏上最大化)。我不害怕拦截更多的事件来实现我所需要的。

有谁知道如何让我的窗口不能在我的MinWidth和MinHeight属性下面调整大小,如果它甚至可能的话?谢谢!!

回答

11

你确实需要处理一个Windows消息来完成它,但它并不复杂。

您必须处理WM_WINDOWPOSCHANGING消息,在WPF中这样做需要一些样板代码,您可以在下面看到实际逻辑仅仅是两行代码。

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 them just change the pos 
      // structure and set changedPos to true 
      // *********************** 

      // this is a simplified version that doesn't work in high-dpi settings 
      // pos.cx and pos.cy are in "device pixels" and MinWidth and MinHeight 
      // are in "WPF pixels" (WPF pixels are always 1/96 of an inch - if your 
      // system is configured correctly). 
      if(pos.cx < MinWidth) { pos.cx = MinWidth; changedPos = true; } 
      if(pos.cy < MinHeight) { pos.cy = MinHeight; changedPos = true; } 


      // *********************** 
      // end of "logic" 
      // *********************** 

      if (!changedPos) 
      { 
      return IntPtr.Zero; 
      } 

      Marshal.StructureToPtr(pos, lParam, true); 
      handeled = true; 
     } 
     break; 
    } 

    return IntPtr.Zero; 
} 
+0

谢谢!这正是我需要的。我已经连接了源代码,因为我正在监视0x0024(最大化事件),所以我不得不向我的交换机添加一个案例。再次感谢! – 2009-11-12 19:24:03

+0

谢谢你。这是很好和适当的排序。 – Dennis 2011-06-10 17:49:26

+0

当MinHeight或MinWidth尝试超过时,有什么办法可以阻止“黑色”屏幕闪烁?闪烁是不好的。 – 2013-02-26 21:17:37

0

我目前无法验证此问题,因为我在Mac笔记本电脑上,但我相信我之前通过处理SizeChanged事件,然后检测MinWidth/Height是否被违反,并且如果所以,只需将宽度/高度属性设置回最小值。

+0

谢谢你的建议。它确实有效,但提供了一个奇怪的体验 - 他们仍然可以一路拖下去,但窗口“打架”他们。下面的其他答案会产生正确的用户体验。 – 2009-11-12 19:23:01

+0

同意,只要您不害怕让低级别的Windows消息“脏”,上述解决方案就会更好。 :) – 2009-11-12 20:45:35

12

我能够通过的情况下为0x0024(对于WindowProc()最后一个参数)设置handledfalse(其中OP提到他已经钩住修复最大化),然后设置MinHeight来解决这个问题, MinWidth在您的Window XAML中。这让这个窗口消息的处理可以通过默认的WPF机制。

这样,Window上的Min *属性管理最小大小,而自定义GetMinMaxInfo代码管理最大大小。

+2

我投票赞成,因为它也可以在Windows 8 64位上运行,而上面的解决方案在尝试调整到最小尺寸以下时会产生COM错误。 – Geoffrey 2012-11-08 21:12:45

2

下面的代码适用于任何DPI设置。

case 0x0046: //Window position message to be handled to restrict the min and max height of the window on 120% screen 
        { 
         WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS)); 
         if ((pos.flags & (int)SWP.NOMOVE) != 0) 
         { 
          return IntPtr.Zero; 
         } 

         System.Windows.Window wnd = (System.Windows.Window)HwndSource.FromHwnd(hwnd).RootVisual; 
         if (wnd == null) 
         { 
          return IntPtr.Zero; 
         } 

         bool changedPos = false; 

         //Convert the original to original size based on DPI setting. Need for 120% screen 
         PresentationSource MainWindowPresentationSource = PresentationSource.FromVisual(wnd); 
         Matrix m = MainWindowPresentationSource.CompositionTarget.TransformToDevice; 
         if (pos.cx < (wnd.MinWidth * m.M11)) { pos.cx = (int)(wnd.MinWidth * m.M11); changedPos = true; } 
         if (pos.cy < (wnd.MinHeight * m.M22)) { pos.cy = (int)(wnd.MinHeight * m.M22); changedPos = true; } 

         if (!changedPos) 
         { 
          return IntPtr.Zero; 
         } 

         Marshal.StructureToPtr(pos, lParam, true); 
         handled = true; 
        } 
        break; 
0

解决方案正在工作,但有一个错误。当我尝试通过拖动顶部或左侧边框来调整窗口大小时,窗口正在移动。它发生在changePos为真时。 这里是没有这个bug的代码:

private static WindowPos _prevPos = new WindowPos(); 
/// <summary> 
/// You do need to handle a windows message to do it, but it's not complicated. 
/// You have to handle the WM_WINDOWPOSCHANGING message, doing that in WPF requires 
/// a bit of boilerplate code, you can see below the actual logic is just two lines of code. 
/// </summary> 
/// <param name="hwnd"></param> 
/// <param name="lParam"></param> 
private static bool OnWmWindowPosChanging(IntPtr hwnd, IntPtr lParam) 
{ 
    // ReSharper disable once InconsistentNaming 
    const int SwpNoMove = 0x0002; 
    WindowPos pos = (WindowPos) Marshal.PtrToStructure(lParam, typeof (WindowPos)); 
    if ((pos.flags & SwpNoMove) != 0) return false; 

    Window wnd = (Window) HwndSource.FromHwnd(hwnd)?.RootVisual; 
    if (wnd == null) return false; 

    bool changePos = false; 

    if (pos.cx < wnd.MinWidth) 
    { 
     pos.cx = (int)wnd.MinWidth; 
     // here is we keep pos x 
     if (_prevPos.hwnd != IntPtr.Zero) 
      pos.x = _prevPos.x; 
     changePos = true; 
    } 

    if (pos.cy < wnd.MinHeight) 
    { 
     pos.cy = (int)wnd.MinHeight; 
     // here is we keep pos y 
     if (_prevPos.hwnd != IntPtr.Zero) 
      pos.y = _prevPos.y; 
     changePos = true; 
    } 

    // Debug.WriteLine($"x = {pos.x}, y = {pos.y}; w = {pos.cx}, h = {pos.cy}"); 

    if (!changePos) return false; 
    // Keep prev pos for bounded window 
    _prevPos = pos; 
    Marshal.StructureToPtr(pos, lParam, true); 

    return true; 
}