Qt QColorDialog - Escape Key 不是 "eaten"(即不从事件队列中删除)

Qt QColorDialog - Escape Key not "eaten" (i.e. not remove from the event queue)

在我的应用程序中,我有一个带有一列的 QTableWidget,用户可以在其中单击以 select 一种颜色。它在以下限制下正常工作:当用户键入 Escape 键时,对话框将按预期取消并关闭,但按键事件仍保留在队列中。这导致此键事件的第二个效果,当对话框关闭时,就好像用户按下了两次键。

完整代码如下:

void CChildrenConfigScreen::actionCellClicked(int row, int col)
{
   //   ui->config_children_table->selectRow(row);
   switch(col) {
      case eColChildColor: {
            CBiStateButton* cbox = reinterpret_cast<CBiStateButton*>(ui->config_children_table->cellWidget(row, 0));
            assert( cbox != nullptr );

            if( cbox->state() != Qt::Unchecked ) {
               QTableWidgetItem* cell = ui->config_children_table->item(row, col);
               cell->setSelected(false);
               // =======
               QColorDialog dialog(this);
               dialog.setCurrentColor(cell->background().color());
               dialog.exec();
               if( dialog.result() == QDialog::Accepted ) {
                  cell->setBackground(QBrush(dialog.currentColor()));
               } else {
                  // drop escape key event ???
               }
               // =======
            }
         }
         break;
   }
}

所以,我的问题:

  1. 这是正常行为吗?还是 Qt 中的错误?
  2. 我是不是做错了什么?

这是我实施的解决方案。不是很漂亮,但是到目前为止我能做到的最好,并且可以按要求工作。定义有点冗长,但使用起来确实方便。

首先,一个基于堆栈的对象(Android 和 MacOS 尚未实现,仅 Windows):

// ==============================
// Used to disable the Escape key

class CKeyEventFilter {
   public:
      typedef char KeyType;
      typedef  std::function<void()> Functor;

   private:
      class CFilter: public QAbstractNativeEventFilter
      {
         public:

         private:
            KeyType mKey;
            Functor mAction;

         public:
            explicit CFilter(KeyType key, Functor action)
               : mKey(key)
               , mAction(action)
            {
            }

            bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override
            {
               bool stop_it = false;

#if defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)
               if (eventType == "xcb_generic_event_t") {
                   xcb_generic_event_t* event = reinterpret_cast<xcb_generic_event_t *>(message);
                   assert(event != nullptr);
                   // ...
               }
#elif defined(Q_OS_MACOS)
               if (eventType == "mac_generic_NSEvent" {
                   MSG  * event = reinterpret_cast<NSEvent*>(message);
                   assert(event != nullptr);
                  // ...
               }
#elif defined(Q_OS_WINDOWS)
               if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG") {
                   MSG* event = reinterpret_cast<MSG*>(message);
                   assert(event != nullptr);

                   if(event->message == WM_KEYDOWN || event->message == WM_KEYUP ) {
                      if( event->wParam == mKey ) {
                         // keep stop_it to false for the key being processed
                         // but tells the 'next client' that he doesn't need
                         // to process it a second time.
                         mAction();
                      }
                   }
               }
#else
#error "Yet unsupported OS"
#endif

               return stop_it;
            }
      };

      CFilter mFilter;

   public:
      explicit CKeyEventFilter(KeyType key, Functor action)
         : mFilter(key, action)
      {
         qApp->installNativeEventFilter(&mFilter);
      }

      virtual ~CKeyEventFilter()
      {
         qApp->removeNativeEventFilter(&mFilter);
      }
};

用法:

{
   // Workaround with this stack based object (0x1B = 27 = ascii code for Esc char)
   CKeyEventFilter filter(0x1B, []() { CMainWindow::win()->setSkipEscape(); } );

   // Example of purpose: if dialog is left with the Escape Key,
   // CMainWindow::setSkipEscape() in the lambda above will be called
   QTableWidgetItem* cell = ui->config_children_table->item(row, col);
   cell->setSelected(false);
   QColorDialog dialog(this);
   dialog.setCurrentColor(cell->background().color());
   dialog.exec();
   if( dialog.result() == QDialog::Accepted ) {
      cell->setBackground(QBrush(dialog.currentColor()));
   }
}

跳过实际上已经处理过的 Esc 键(在此示例中,在上面的 QColorDialog 中,但可能是 QMessageBox 或任何其他对话框):

void CMainWindow::popScreen()
{
   // mSkipEscape is initialized to false in CMainWindow's constructor
   // and set by CMainWindow::setSkipEscape() in the lambda above.
   if( mSkipEscape ) {
      mSkipEscape = false;
   } else {
      if( ! mScreenStack.empty()) {
         EScreens screen = mScreenStack.top();
         mScreenStack.pop();
         this->goScreen(screen);
      }
   }
}

我希望这对某些人有用。