Как отличить "Window close button click (X)" vs. window.Close() в закрывающем обработчике

Существует ли разумный способ определить, было ли закрыто окно с помощью

  • Пользователь, нажимая кнопку (X) в правом верхнем углу окна или
  • window.Close() вызывается программно.

Я хотел бы обнаружить это в окне. Обработчик закрытия. Я могу установить флаг, когда я вызываю window.Close(), но это не очень красивое решение.

Ответ 1

Я не уверен, что мне это нравится вообще, но это вопрос, который у вас, очевидно, есть причина для запроса. если вы должны были взять трассировку стека в событии OnClosing, вы можете найти событие Window.Close.

protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
   bool wasCodeClosed = new StackTrace().GetFrames().FirstOrDefault(x => x.GetMethod() == typeof(Window).GetMethod("Close")) != null;
   if (wasCodeClosed)
   {
       // Closed with this.Close()
   }
   else
   {
       // Closed some other way.
   }

   base.OnClosing(e);
}

Ответ 2

Разница заключается в следующем:

Window.Close() вызывает отправку WM_CLOSE в окно.

Кнопка Alt + F4 и X вызывает сообщение WM_SYSCOMMAND с типом SC_CLOSE. Вы можете решить, хотите ли вы продолжить маршрутизацию этого сообщения (и в конечном итоге вызывать WM_CLOSE).

Вот фрагмент кода, чтобы поймать это сообщение. Верните "True" из делегата, если вы хотите отменить поведение по умолчанию:

class SystemMenu : IDisposable
{
    const int WM_SYSCOMMAND = 0x0112;
    const int SC_CLOSE = 0xF060;

    public delegate bool HandleSystemCommand();
    HwndSource _source;
    HandleSystemCommand _handler;

    public SystemMenu(Window window, HandleSystemCommand handler )
    {
        _handler = handler;
        _source  = HwndSource.FromHwnd(new WindowInteropHelper( window ).Handle);
        _source.AddHook(WndProc);
    }

    public void Dispose() {
        _source.RemoveHook(WndProc);
    }

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        switch (msg)
        {
            case WM_SYSCOMMAND:
                int command = wParam.ToInt32() & 0xfff0;
                if (command == SC_CLOSE)
                    handled = _handler();
                break;
            default:
                break;
        }
        return IntPtr.Zero;
    }
}