2010-01-12 13 views
9

Sto usando un QMenu come menu di scelta rapida. Questo menu è pieno di QActions. Uno di questi QActions è controllabile e vorrei poterlo selezionare/deselezionare senza chiudere il menu di scelta rapida (e doverlo riaprire nuovamente per scegliere l'opzione che desidero).Previene un QMenu dalla chiusura quando viene attivato uno dei suoi QAction

Ho provato a disconnettere i segnali emessi dalla QAction controllabile senza fortuna.

Qualche idea? Grazie.

risposta

13

Utilizzare QWidgetAction e QCheckBox per una "azione controllabile" che non provoca la chiusura del menu.

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

In alcuni stili, questo non apparirà esattamente come un'azione controllabile. Ad esempio, per lo stile Plastique, la casella di controllo deve essere rientrata un po '.

+0

Grazie mille. Con lo stile plastico c'è davvero un margine da aggiungere. Così ho inserito la casella di controllo in un widget con un layout e impostato i margini (forse c'è un modo più semplice ...) Un'ultima cosa: la casella di controllo non si espande per tutta la larghezza del menu, quindi se il clic si verifica dopo la fine dell'etichetta della scatola, il menu è chiuso e la casella non è selezionata. L'impostazione della politica delle dimensioni non ha alcun effetto. – gregseth

+0

Questo non funziona con 'QsystemTrayIcon.contextMenu()' su Ubuntu Unity come Unity non mostra Widget dall'interno di 'QWidgetAction' – Germar

+0

@gregseth c'è un modo per espandere la casella di controllo alla larghezza completa del menu? – bob

1

Ecco idee paio che ho avuto ... Non sono sicuro a tutti lavoreranno tho;)

1) cercare di catturare l'evento utilizzando il metodo del QMenu aboutToHide(); Forse puoi "annullare" il processo di nascondere?

2) Forse potresti prendere in considerazione l'utilizzo di EventFilter?

cercare di avere uno sguardo a: http://qt.nokia.com/doc/4.6/qobject.html#installEventFilter

3) In caso contrario si potrebbe reimplementare QMenu aggiungere la tua comportamento, ma sembra un sacco di lavoro per me ...

Spero che questo aiuti un po ' !

0

(ho iniziato con la risposta di Andy, quindi grazie Andy!)

1) aboutToHide() funziona, da ri-popping menu in una posizione memorizzata nella cache, ma può anche entrare in un ciclo infinito. Testare se il mouse viene cliccato fuori dal menu per ignorare la riapertura dovrebbe fare il trucco.

2) Ho provato un filtro eventi ma blocca il clic effettivo sulla voce di menu.

3) Utilizzare entrambi.

Ecco uno schema sporco per dimostrare che funziona. Ciò mantiene l'aperta menu quando l'utente tiene premuto il tasto CTRL quando si fa clic:

# 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 

dico che è sporca perché il widget qui agisce come un filtro eventi sia per il pulsante che apre il menu così come il menu stesso . Usare classi di filtro eventi esplicite sarebbe abbastanza semplice da aggiungere e renderebbe le cose un po 'più facili da seguire.

È probabile che i bool vengano sostituiti con un segno di spunta per verificare se il mouse si trova sopra il menu e, in caso contrario, non aprirlo. Tuttavia, il tasto CTRL deve ancora essere preso in considerazione per il mio caso d'uso, quindi probabilmente non è lontano da una soluzione così semplice.

Quando l'utente tiene premuto CTRL e fa clic sul menu, capovolge un interruttore in modo che il menu si apra di nuovo quando tenta di chiudere. La posizione viene memorizzata nella cache in modo che si apra nella stessa posizione. C'è uno sfarfallio veloce, ma ci si sente bene poiché l'utente sa che stanno tenendo premuto un tasto per farlo funzionare.

Alla fine della giornata (letteralmente) ho già avuto l'intero menu facendo la cosa giusta. Volevo solo aggiungere questa funzionalità e sicuramente non volevo passare all'utilizzo di un widget solo per questo. Per questo motivo, sto mantenendo anche questa patch sporca per ora.

1

Questa è la mia soluzione:

// 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

Non sembra essere un modo elegante per evitare che il menu si chiuda. Tuttavia, il menu si chiude solo se l'azione può effettivamente attivarsi, cioè è abilitata. Quindi, la soluzione più elegante che ho trovato è ingannare il menu disabilitando brevemente l'azione nel momento in cui sarebbe stata attivata.

  1. sottoclasse QMenu
  2. Reimplementare gestori di eventi rilevanti (come mouseReleaseEvent()) attuazione
  3. Nel gestore eventi, disabilitare l'azione, quindi chiamare classe di base, quindi attivare nuovamente l'azione, e attivano manualmente

Questo è un esempio di reimplementato mouseReleaseEvent():

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); 
} 

Per rendere la soluzione perfetta, simile dovrebbe essere fatto in tutti i gestori di eventi che possono attivare l'azione, come keyPressEvent(), ecc ...

Il problema è che non è sempre facile sapere se la reimplementazione dovrebbe effettivamente innescare l'azione, o anche quale azione dovrebbe essere attivata. Il più difficile è probabilmente il trigger di azione da parte di mnemonics: sarà necessario reimplementare il complesso algoritmo in QMenu :: keyPressEvent().

+0

Questa è esattamente la stessa soluzione che mi è venuta in mente e funziona bene per quanto posso dire. Credo che avrei dovuto leggere tutte le risposte qui prima di sperimentare da solo. – mooware

+2

Invece di disabilitare e abilitare l'azione, non puoi nemmeno chiamare 'QMenu :: mouseReleaseEvent'. In tal caso funziona come un fascino. Anche l'override di 'keyPressEvent' e l'aggiunta del comportamento per il pulsante dello spazio hanno funzionato bene. – bcmpinc

Problemi correlati