2009-11-11 12 views
9

Tengo una aplicación WPF en la que la decoración de la ventana principal es personalizada, a través de WindowStyle = "None". Dibujo mi propia barra de título y los botones min/max/close. Desafortunadamente, Windows no aplica mis propiedades MinWidth y MinHeight cuando la ventana cambia de tamaño, lo que permite cambiar el tamaño de la ventana hasta 3x3 (appx, lo suficiente para mostrar los identificadores para hacer crecer la ventana).Cómo aplicar MinWidth y MinHeight en una ventana de WPF donde WindowStyle = "None"?

Ya estoy teniendo que interceptar eventos de ventana (sp 0x0024) para corregir el error de maximización (donde se maximizará sobre la barra de tareas de Windows) causado por WindowStyle = none. No tengo miedo de interceptar más eventos para lograr lo que necesito.

¿Alguien sabe cómo hacer que mi ventana no cambie de tamaño debajo de mis propiedades MinWidth y MinHeight, si es posible? ¡¡Gracias!!

Respuesta

11

Necesita manejar un mensaje de Windows para hacerlo, pero no es complicado.

Tienes que manejar el mensaje WM_WINDOWPOSCHANGING, hacerlo en WPF requiere un poco de código repetitivo, puedes ver a continuación la lógica real es solo dos líneas de código.

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

¡Gracias! Esto es exactamente lo que necesitaba. Ya tenía la fuente conectada, porque estaba monitoreando 0x0024 (maximizar el evento), así que solo tuve que agregar un caso a mi interruptor. ¡Gracias de nuevo! –

+0

Gracias. Eso lo solucionó bien y correctamente. – Dennis

+0

¿Hay alguna forma de detener el parpadeo de la pantalla "Negro" cuando se intenta exceder MinHeight o MinWidth? El parpadeo es malo. –

0

No puedo verificar esto en este momento porque estoy en una computadora portátil Mac, pero creo que he hecho esto antes al manejar el evento SizeChanged y luego detectar si se está violando la MinWidth/Height y, si por lo tanto, simplemente establezca la propiedad Ancho/Altura nuevamente en el valor mínimo.

+0

Gracias por la sugerencia. De hecho, funciona, pero proporciona una experiencia extraña: todavía pueden arrastrar todo el camino hacia abajo, pero la ventana los "combate". La otra respuesta, a continuación, produce el UX adecuado. –

+0

De acuerdo, la solución anterior es mucho más agradable siempre y cuando no tenga miedo de ensuciarse las manos con mensajes de Windows de bajo nivel. :) –

12

que era capaz de resolver este problema estableciendo handled (el último parámetro para WindowProc()) a false en el caso de 0x0024 (que el PO mencionó que ya estaba enganchando para fijar la maximización) y después configurar MinHeight y MinWidth en su ventana XAML. Esto permite que el manejo de este mensaje de ventana caiga en los mecanismos de WPF predeterminados.

De esta manera, los atributos Min * en su Window administran el tamaño mínimo y el código GetMinMaxInfo personalizado administra el tamaño máximo.

+2

Lo voté porque funciona también en Windows 8 64 bits, mientras que la solución anterior crea un error COM al intentar cambiar el tamaño por debajo de las dimensiones mínimas. – Geoffrey

2

El siguiente código funcionará para cualquier configuración de 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

La solución funciona, pero tiene un error. La ventana se mueve cuando intento cambiar el tamaño de la ventana arrastrando el borde superior o izquierdo. Sucedió cuando changePos es verdadero. Aquí está el código sin este error:

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; 
} 
Cuestiones relacionadas