Поверхности кнопки закрытия системы в пользовательском окне Chrome, когда ResizeMode установлен в NoResize - PullRequest
1 голос
/ 22 марта 2012

Я реализовал пользовательский хром окна, используя библиотеку интеграции оболочки WPF (http://archive.msdn.microsoft.com/WPFShell).

Chrome работает, пока я не установлю ReSizeMode на NoResize. Затем, при наведении курсора на кнопку закрытия, я замечаю, что основная кнопка закрытия системы отображается под пользовательской кнопкой закрытия chrome. Custom Chrome Button partially hides System Close Button

Ожидаемое поведение заключается в том, что основная кнопка закрытия никогда не должна отображаться. Если я переместлю окно или выберу другое окно на своем рабочем столе и вернусь к этому, я заметил, что кнопка закрытия системы снова скрыта. Так что, вероятно, это, по сути, когда (1) приложение запускается впервые и (2) когда ResizeMode = NoResize.

Мое первое подозрение в том, как мы обрабатываем WM.NCHITTEST в Custom Chrome. Если я изменю эту функцию, чтобы она возвращала HTCLient, тогда эта проблема решена. Однако я теряю способность перетаскивать, а также щелкать правой кнопкой мыши на панели заголовков.

Вот код обработчика сообщения WM.NCHITTEST из класса WindowsChromeWorker.

    private IntPtr _HandleNCHitTest( WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled ) {
        IntPtr lRet = IntPtr.Zero;
        handled = false;

        // Give DWM a chance at this first.
        if ( Utility.IsOSVistaOrNewer && _chromeInfo.GlassFrameThickness != default( Thickness ) && _isGlassEnabled ) {
            // If we're on Vista, give the DWM a chance to handle the message first.
            handled = NativeMethods.DwmDefWindowProc( _hwnd, uMsg, wParam, lParam, out lRet );
        }


        // Handle letting the system know if we consider the mouse to be in our effective non-client area.
        // If DWM already handled this by way of DwmDefWindowProc, then respect their call.
        if ( IntPtr.Zero == lRet ) {
            var mousePosScreen = new Point( Utility.GET_X_LPARAM( lParam ), Utility.GET_Y_LPARAM( lParam ) );
            Rect windowPosition = _GetWindowRect();

            HT ht = _HitTestNca(
                DpiHelper.DeviceRectToLogical( windowPosition ),
                DpiHelper.DevicePixelsToLogical( mousePosScreen ) );

            // Don't blindly respect HTCAPTION.
            // We want UIElements in the caption area to be actionable so run through a hittest first.
            if ( ht != HT.CLIENT) {
                Point mousePosWindow = mousePosScreen;
                mousePosWindow.Offset( -windowPosition.X, -windowPosition.Y );
                mousePosWindow = DpiHelper.DevicePixelsToLogical( mousePosWindow );
                IInputElement inputElement = _window.InputHitTest( mousePosWindow );
                if ( inputElement != null && WindowChrome.GetIsHitTestVisibleInChrome( inputElement ) ) {
                    ht = HT.CLIENT;
                }
            }
            //handled = false;
            handled = true;
            lRet = new IntPtr((int)ht);
        }
        return lRet;
    }

    private static readonly HT[,] _HitTestBorders = new[,]
    {
        { HT.TOPLEFT,    HT.TOP,     HT.TOPRIGHT    },
        { HT.LEFT,       HT.CLIENT,  HT.RIGHT       },
        { HT.BOTTOMLEFT, HT.BOTTOM,  HT.BOTTOMRIGHT },
    };

    private HT _HitTestNca( Rect windowPosition, Point mousePosition ) {
        // Determine if hit test is for resizing, default middle (1,1).
        int uRow = 1;
        int uCol = 1;
        bool onResizeBorder = false;

        //if (_window.ResizeMode == ResizeMode.NoResize)
          //  _chromeInfo.ResizeBorderThickness = new Thickness(0);

        // Determine if the point is at the top or bottom of the window.
        if ( mousePosition.Y >= windowPosition.Top && mousePosition.Y < windowPosition.Top + _chromeInfo.ResizeBorderThickness.Top + _chromeInfo.CaptionHeight ) {
            onResizeBorder = ( mousePosition.Y < ( windowPosition.Top + _chromeInfo.ResizeBorderThickness.Top ) );
            uRow = 0; // top (caption or resize border)
        } else if ( mousePosition.Y < windowPosition.Bottom && mousePosition.Y >= windowPosition.Bottom - (int)_chromeInfo.ResizeBorderThickness.Bottom ) {
            uRow = 2; // bottom
        }

        // Determine if the point is at the left or right of the window.
        if ( mousePosition.X >= windowPosition.Left && mousePosition.X < windowPosition.Left + (int)_chromeInfo.ResizeBorderThickness.Left ) {
            uCol = 0; // left side
        } else if ( mousePosition.X < windowPosition.Right && mousePosition.X >= windowPosition.Right - _chromeInfo.ResizeBorderThickness.Right ) {
            uCol = 2; // right side
        }

        // If the cursor is in one of the top edges by the caption bar, but below the top resize border,
        // then resize left-right rather than diagonally.
        if ( uRow == 0 && uCol != 1 && !onResizeBorder ) {
            uRow = 1;
        }

        HT ht = _HitTestBorders[uRow, uCol];

        if ( ht == HT.TOP && !onResizeBorder) {
            ht = HT.CAPTION;
        }

        return ht;
    }

Есть идеи как это исправить?

Большое спасибо, Арджун

1 Ответ

3 голосов
/ 25 марта 2012

Весь обряд .. Я понял эту проблему.

Прежде всего мои первоначальные подозрения были неверны. Обработка сообщения WM.NCHITTEST не была неверной. Это был действительно вопрос стилей окон.

Лучшим решением было скрыть кнопку закрытия системы и позволить кнопке Chrome Close работать. Но решение, которое вы найдете в сети, т. Е. Переключить битовый флаг SYSMENU в стилях окна http://winsharp93.wordpress.com/2009/07/21/wpf-hide-the-window-buttons-minimize-restore-and-close-and-the-icon-of-a-window/, в моем случае не сработало.

Кнопка закрытия скрыта, но, несмотря на то, что для ResizeMode установлено значение NoResize, я заметил, что включены и курсор изменения размера, и меню изменения размера {Max \ Min \ Restore}.

После нескольких часов проб и ошибок я придумал этот фрагмент кода:

    //This property descriptor is used to hook-onto the resizemode change notification

      private void Window_Loaded( object sender, System.Windows.RoutedEventArgs e)
      {
      // When the default handling of ResizeMode = NoResize causes problems - this is why custom handling is required.
    System.ComponentModel.DependencyPropertyDescriptor _resizeModePropertyDescriptor;

     _resizeModePropertyDescriptor = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(Window.ResizeModeProperty,
            typeof(Window));
        _resizeModePropertyDescriptor.AddValueChanged(this._window, new EventHandler(this._Window_ResizeModePropertyChanged));
      }

    /// <summary>
    /// This property change handler only reacts when the ReSizeMode is set to NoResize.
    /// In the default window style when Resize = NoResize, causes the underlying system close button to show up under the Chrome Close Button.
    /// This is a fix to handle that problem. [ Refer to defect #5134 for further details.
    /// </summary>
    /// <param name="sender">ChromeWorker object</param>
    /// <param name="e">Event Args - Not really used</param>
    private void _Window_ResizeModePropertyChanged(object sender, EventArgs e)
    {
        if (_window.ResizeMode == ResizeMode.NoResize)
        {
            //Got these styles merely by trial and error.
            _ModifyStyle(
                WS.SYSMENU | WS.DLGFRAME | WS.BORDER | WS.CLIPCHILDREN | WS.CLIPSIBLINGS,  //Styles to Remove
                WS.POPUPWINDOW);                                                            //Style to Add
        }
    }

    /// <summary>Add and remove a native WindowStyle from the HWND.</summary>
    /// <param name="removeStyle">The styles to be removed.  These can be bitwise combined.</param>
    /// <param name="addStyle">The styles to be added.  These can be bitwise combined.</param>
    /// <returns>Whether the styles of the HWND were modified as a result of this call.</returns>
    private bool _ModifyStyle( WS removeStyle, WS addStyle ) {
        Assert.IsNotDefault( _hwnd );
        var dwStyle = (WS)NativeMethods.GetWindowLongPtr( _hwnd, GWL.STYLE ).ToInt32();
        var dwNewStyle = ( dwStyle & ~removeStyle ) | addStyle;
        if ( dwStyle == dwNewStyle ) {
            return false;
        }

        NativeMethods.SetWindowLongPtr( _hwnd, GWL.STYLE, new IntPtr( (int)dwNewStyle ) );
        return true;
    }

Вы также можете использовать событие SourceInitialized для соединения, но я действительно не проверял это. Это оно! Это захватывает интенсивную неделю исследования Window Chromes!

Я должен признать, что я немного заинтригован - работа над WPF так долго не заставляла меня осознавать, что существует целый мир win32, который намного мощнее (и разочаровывает!) *

...