2010-01-12 15 views
9

Estoy usando un QMenu como menú contextual. Este menú está lleno de QActions. Una de estas QActions es seleccionable y me gustaría poder marcarla/desmarcarla sin cerrar el menú contextual (y tener que volver a abrirla para elegir la opción que desee).Evitar que un QMenu se cierre cuando se activa una de sus QAction

He intentado desconectar las señales emitidas por la QAction seleccionable sin suerte.

¿Alguna idea? Gracias.

Respuesta

13

Utilice QWidgetAction y QCheckBox para una "acción comprobable" que no haga que el menú se cierre.

QCheckBox *checkBox = new QCheckBox(menu); 
QWidgetAction *checkableAction = new QWidgetAction(menu); 
checkableAction->setDefaultWidget(checkBox); 
menu->addAction(checkableAction); 

En algunos estilos, esto no aparecerá exactamente igual a una acción comprobable. Por ejemplo, para el estilo Plastique, la casilla de verificación debe sangrarse un poco.

+0

Muchas gracias. Con el estilo plastique hay un margen para agregar. Así que puse la casilla de verificación en un widget con un diseño, y lo establecí en los márgenes (tal vez hay una manera más simple ...) Una última cosa: la casilla de verificación no se expande a todo el ancho del menú, por lo que si ocurre después del final de la etiqueta de la caja, el menú está cerrado y el cuadro no está marcado. Establecer la política de tamaño no tiene ningún efecto. – gregseth

+0

Esto no funciona con 'QsystemTrayIcon.contextMenu()' en Ubuntu Unity ya que Unity no muestra Widgets desde adentro 'QWidgetAction' – Germar

+0

@gregseth ¿Hay alguna manera de expandir la casilla de verificación al ancho completo del menú? – bob

1

Estas son las ideas par que he tenido ... no estoy seguro del todo que van a trabajar aunque;)

1) Tratar de capturar el evento usando el método de la QMenu aboutToHide(); Tal vez puedas "Cancelar" el proceso de ocultar?

2) ¿Quizás podría considerar usar un EventFilter?

tratar de tener un vistazo a: http://qt.nokia.com/doc/4.6/qobject.html#installEventFilter

3) De lo contrario podría reimplementar QMenu añadir su propio comportamiento, pero parece mucho trabajo para mí ...

Espero que esto ayude un poco !

0

(I comenzó con la respuesta de Andy, así que gracias a Andy!)

1) aboutToHide() funciona, volviendo a hacer estallar el menú en una posición en caché, pero también puede entrar en un bucle infinito. Probar si hacer clic en el mouse fuera del menú para ignorar la reapertura debería ser el truco.

2) Probé un filtro de eventos pero bloquea el clic real al elemento del menú.

3) Use ambos.

Aquí hay un patrón sucio para demostrar que funciona. Esto mantiene a la intemperie menú cuando el usuario mantiene pulsada la tecla CTRL al hacer clic en:

# in __init__ ... 
    self.options_button.installEventFilter(self) 
    self.options_menu.installEventFilter(self) 
    self.options_menu.aboutToHide.connect(self.onAboutToHideOptionsMenu) 

    self.__options_menu_pos_cache = None 
    self.__options_menu_open = False 

def onAboutToHideOptionsMenu(self): 
    if self.__options_menu_open:   # Option + avoid an infinite loop 
     self.__options_menu_open = False # Turn it off to "reset" 
     self.options_menu.popup(self.__options_menu_pos_cache) 

def eventFilter(self, obj, event): 
    if event.type() == QtCore.QEvent.MouseButtonRelease: 
     if obj is self.options_menu: 
      if event.modifiers() == QtCore.Qt.ControlModifier: 
       self.__options_menu_open = True 

      return False 

     self.__options_menu_pos_cache = event.globalPos() 
     self.options_menu.popup(event.globalPos()) 
     return True 

    return False 

Yo digo que es sucia porque el widget aquí está actuando como un filtro de eventos, tanto para el botón que abre el menú, así como el propio menú . Usar clases de filtros de eventos explícitos sería lo suficientemente fácil de agregar y facilitaría un poco las cosas.

Los bools probablemente podrían reemplazarse con una comprobación para ver si el mouse está sobre el menú, y si no, no lo abra. Sin embargo, la clave CTRL todavía debe tenerse en cuenta para mi caso de uso, por lo que probablemente no esté lejos de ser una buena solución.

Cuando el usuario mantiene presionada la tecla CTRL y hace clic en el menú, activa un interruptor para que el menú se abra nuevamente cuando intenta cerrarse. La posición se almacena en caché para que se abra en la misma posición. Hay un parpadeo rápido, pero se siente bien ya que el usuario sabe que está presionando una tecla para que esto funcione.

Al final del día (literalmente) ya tenía todo el menú haciendo lo correcto. Solo quería agregar esta funcionalidad y definitivamente no quería cambiar a usar un widget solo para esto. Por esta razón, estoy manteniendo incluso este sucio parche por ahora.

1

Esta es mi solución:

// this menu don't hide, if action in actions_with_showed_menu is chosen. 
    class showed_menu : public QMenu 
    { 
     Q_OBJECT 
    public: 
     showed_menu (QWidget *parent = 0) : QMenu (parent) { is_ignore_hide = false; } 
     showed_menu (const QString &title, QWidget *parent = 0) : QMenu (title, parent) { is_ignore_hide = false; } 
     void add_action_with_showed_menu (const QAction *action) { actions_with_showed_menu.insert (action); } 

     virtual void setVisible (bool visible) 
     { 
     if (is_ignore_hide) 
      { 
      is_ignore_hide = false; 
      return; 
      } 
     QMenu::setVisible (visible); 
     } 

     virtual void mouseReleaseEvent (QMouseEvent *e) 
     { 
     const QAction *action = actionAt (e->pos()); 
     if (action) 
      if (actions_with_showed_menu.contains (action)) 
      is_ignore_hide = true; 
     QMenu::mouseReleaseEvent (e); 
     } 
    private: 
     // clicking on this actions don't close menu 
     QSet <const QAction *> actions_with_showed_menu; 
     bool is_ignore_hide; 
    }; 

    showed_menu *menu = new showed_menu(); 
    QAction *action = showed_menu->addAction (new QAction (menu)); 
    menu->add_action_with_showed_menu (action); 
7

No parece haber ninguna manera elegante de evitar que el menú se cierre. Sin embargo, el menú solo se cerrará si la acción realmente se puede activar, es decir, está habilitada. Entonces, la solución más elegante que encontré es engañar al menú deshabilitando la acción en el momento en que se activaría.

  1. Subclase QMenu
  2. reimplementar controladores de eventos relevantes (como mouseReleaseEvent()) aplicación
  3. En el controlador de eventos, desactivar la acción, a continuación, llamar a la clase base, a continuación, permiten la acción de nuevo, y accionan manualmente

Este es un ejemplo de mouseReleaseEvent reimplementado():

void mouseReleaseEvent(QMouseEvent *e) 
{ 
    QAction *action = activeAction(); 
    if (action && action->isEnabled()) { 
     action->setEnabled(false); 
     QMenu::mouseReleaseEvent(e); 
     action->setEnabled(true); 
     action->trigger(); 
    } 
    else 
     QMenu::mouseReleaseEvent(e); 
} 

Para que la solución sea perfecta, se debe hacer lo mismo en todos los controladores de eventos que puedan desencadenar la acción, como keyPressEvent(), etc.

El problema es que no siempre es fácil saber si su reimplementación debería realmente disparar la acción, o incluso qué acción debería activarse. Probablemente, lo más difícil sea la acción desencadenada por mnemónicos: deberá volver a implementar el algoritmo complejo en QMenu :: keyPressEvent() usted mismo.

+0

Esta es exactamente la misma solución que acabo de encontrar, y funciona bien por lo que yo sé. Supongo que debería haber leído todas las respuestas aquí antes de experimentar por mi cuenta. – mooware

+2

En lugar de deshabilitar y habilitar la acción, tampoco puede llamar a 'QMenu :: mouseReleaseEvent'. En cuyo caso funciona como un encanto. Ignorar 'keyPressEvent' y agregar comportamiento para el botón de espacio funcionó bien también. – bcmpinc

Cuestiones relacionadas