Как применить MinWidth и MinHeight в окне WPF, где WindowStyle = None?

У меня есть приложение WPF, в котором оформление главного окна является пользовательским через WindowStyle="None". Я рисую свой собственный заголовок и кнопки min/max/close. К сожалению, Windows не применяет мои свойства MinWidth и MinHeight при изменении размера окна, что позволяет изменять размер окна до 3x3 (примерно - достаточно, чтобы показать дескрипторы для увеличения окна).

Мне уже приходится перехватывать события окна (sp. 0x0024), чтобы исправить ошибку максимизации (когда она будет максимизироваться над панелью задач Windows), вызванную WindowStyle=none. Я не боюсь перехватывать больше событий, чтобы добиться того, что мне нужно.

Кто-нибудь знает, как заставить мое окно не изменять размер ниже моих свойств MinWidth и MinHeight, если это вообще возможно? Спасибо!!


person Ben McMillan    schedule 11.11.2009    source источник


Ответы (5)


Вам нужно обработать сообщение 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;
}
person Nir    schedule 12.11.2009
comment
Спасибо! Это именно то, что мне нужно. У меня уже был подключен источник, потому что я отслеживал 0x0024 (событие максимизации), поэтому мне просто нужно было добавить случай к моему коммутатору. Еще раз спасибо! - person Ben McMillan; 12.11.2009
comment
Есть ли способ остановить мигание черного экрана при попытке превышения MinHeight или MinWidth? Мерцание плохое. - person Jason Stevenson; 27.02.2013
comment
handeled = true здесь вызывал сбой в некоторых системах (UCEERR_RENDERTHREADFAILURE). Комментирование этой строки избавляет от сбоя, а остальные по-прежнему работают нормально. - person Simoyd; 19.10.2017

Я смог решить эту проблему, установив handled (последний параметр для WindowProc()) на false в случае 0x0024 (который, как упомянул ОП, он уже подключал для исправления максимизации), а затем установив MinHeight и MinWidth в вашем Window XAML. Это позволяет обрабатывать это оконное сообщение через механизмы WPF по умолчанию.

Таким образом, атрибуты Min* на вашем Window управляют минимальным размером, а пользовательский код GetMinMaxInfo управляет максимальным размером.

person Karlin Fox    schedule 12.06.2012
comment
Я проголосовал за это, потому что оно работает также в 64-разрядной версии Windows 8, в то время как приведенное выше решение создает ошибку COM при попытке изменить размер ниже минимальных размеров. - person Geoffrey; 09.11.2012

Код ниже будет работать для любых настроек 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;
person Ayyappan Subramanian    schedule 14.04.2016

Я не могу проверить это в данный момент, потому что я на ноутбуке Mac, но я полагаю, что делал это раньше, обрабатывая событие SizeChanged, а затем определяя, нарушается ли MinWidth/Height, и, если да, просто устанавливая свойство Width/Height обратно к мин.

person Drew Marsh    schedule 12.11.2009
comment
Спасибо за предложение. Это действительно работает, но дает странный опыт — они все еще могут тянуться вниз, но окно борется с ними. Другой ответ ниже дает правильный UX. - person Ben McMillan; 12.11.2009
comment
Согласитесь, приведенное выше решение намного приятнее, если вы не боитесь испачкать руки низкоуровневыми сообщениями Windows. :) - person Drew Marsh; 12.11.2009

Решение работает, но есть ошибка. Окно движется, когда я пытаюсь изменить размер окна, перетаскивая верхнюю или левую границу. Это произошло, когда changePos истинно. Вот код без этой ошибки:

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;
}
person Andrey A .Eroshenko    schedule 21.09.2016