Decorator pattern and other common code for my windows



  • Hi,

    I want my Qwidgets, QDockWidgets, QMainWindows to be FramelessWindowHint. Because of this, I also need to implement some mouse events in order to move the window in the screen and so on. I know how to achieve this for a single window, but now my goal is to generalize this system to my whole application without (I hope) any code repetition.
    How this could be done? What if I need some windows to have this feature but not another (for example a window that doesnt implement one of the mentioned events. Could be decorator pattern used here? I've tried but is not an easy task, I prefer some experience voice to guide me before I continue doing things wrong...

    Thanks in advance!


  • Moderators

    create a plain QWidget/QFrame (with FramelessWindowHint) and implement the mouse event handling for reposition and resizing there.
    Then just add your top-level widgets as child widgets to this widget. Probably the easiest would be to add the child widget to a layout to ensure they are resized when your window is resized.



  • Thanks, it works fine for my QMainWindow, but ie. I have a QMenuBar (a class derived from QMenuBar) that I want its mouseEvents to be overriden. With your system, I will do a menubar->setParent(yourQWidget) and menubar->layour()->addWidget(yourQWidget).
    The thing is this settings are overriden in the moment that "MainWindow->setMenuBar(menubar)" instruction is called later. This modifies the layout and also the parent, so I cant add this behaviour to my QMenuBar (or other QWidgets that somebody needs to be its parent or have it in its layout)


  • Moderators

    why do you need it for QMenuBar?!
    A menu bar shouldn't be resizeable...at least it doesn't make sense.



  • Because I want to use the empty zone of the QMenuBar to move the whole window, so I need to reimplement mouseMoveEvent and mousePressEvent on it.


  • Moderators

    you should only wrap the most top-level widget with the "decorator-widget". You then can move the window, when you install the decorator-widget as an event-filter on the QMenuBar. Check if the mouse press event is in the empty zone by checking QMenuBar::actionAt(pos) == NULL



  • This works fine. So now I have a wrapper for my QMainWindow and this QMainWindow has installed a eventFilter that is the wrapper class. What could I do if in the future I need to reimplement QMainWindows eventFilter?

    Thanks


  • Moderators

    what do you mean? Change it :)

    Also i meant that you implement the event filter in the decoration widget. And install this decoration widget as the event filter on your QMenuBar.

    You can keep the code very general to make it work in many cases. Show the code of your eventFilter() please.



  • @
    TitlelessWindow::TitlelessWindow(QWidget* widget){

    //...Removing title code and some decorating stuff... also:
    widget->setParent(this);
    this->layout()->addWidget(widget);

    }

    void TitlelessWindow::mouseMoveEvent(QMouseEvent *event){

    QPoint newPos=event->globalPos();
    move(newPos - clickPos);
    }

    void TitlelessWindow::mousePressEvent( QMouseEvent * e){

    clickPos=e->pos();
    QWidget::mousePressEvent(e);
    }

    bool TitlelessWindow::eventFilter(QObject *obj, QEvent *event)
    {
    if (event->type() == QEvent::MouseMove) {

    QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
    mouseMoveEvent(mouseEvent);
    return true;

     }if (event->type() == QEvent::MouseButtonPress) {
    

    QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
    mousePressEvent(mouseEvent);
    return true;

     } else {
         // standard event processing
         return QObject::eventFilter(obj, event);
     }
    

    }@

    This is the code of my wrapper class you told me. Then, I decorate QMainWindow with it, and I install its eventFilter for my QMenuBar. This is currently working, but I dont know if its what you meant.


  • Moderators

    ok looks good.
    Some tips:

    • In TitlelessWindow::eventFilter() you can check if "obj" is of type QMenuBar and if it is cast it. Then you need to use actionAt() otherwise your menu items won't be clickable.

    • Don't route the events from the event filter through your class' event handler. This works in your case, but since they are propagated they may have side effects if you use top-level windows with a parent widget.
      @
      bool TitlelessWindow::eventFilter(QObject *obj, QEvent *event)
      {
      ...
      switch( event->type() )
      {
      case QEvent::MouseButtonPress:
      {
      QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);

           QMenuBar* menuBar = qobject_cast<QMenuBar*>(obj);
           if( menuBar )
           {
                   if( menuBar->actionAt(mouseEvent->pos()) == 0 )
                         //accept movement of window
           }
      

      }
      break;
      case QEvent::MouseMove:
      ...
      break;
      }

    return QWidget::eventFilter(obj,event);
    };
    @

    Or you could also check in the constrcutor if the widget is a QMainWindow type and install the eventFilter on the menuBar() there.
    Then the whole implementation is in 1 class and all you have to do is wrap your widget with this decoration widget.



  • Thanks for your help,

    Regarding in your second tip, I originally put this code inside the eventFilter instead calling the mouseMoveEvent and mousePressEvent, but then, when I clicked, holded and moved the mouse 1px, it was jumping up like 10px for some reason at the start of the movement. Any idea why this behaviour?


  • Moderators

    most probably a mapping issue.
    Easiest would be to always use the event's globalPos() and map it to your decoration widget. Then you should be safe.



  • Hi,

    I had to consider the geometry differences bewteen contained and container to call the QWidget::move function.

    Only one question more: How would you add more event hadling for the QMenuBar? I have a wrapper class for the QMenuBar (MyQMenuBar), but I've just made a installEventFilter pointing to the TitlelessWindow class.


  • Moderators

    [quote author="aidaqt" date="1383750043"]
    I had to consider the geometry differences bewteen contained and container to call the QWidget::move function.
    [/quote]
    Thats why i said it's easier to use the event's QMouseEvent::globalPos() and map it to your top-level widget using QWidget::mapFromGlobal(). ;)

    [quote author="aidaqt" date="1383750043"]
    Only one question more: How would you add more event hadling for the QMenuBar? I have a wrapper class for the QMenuBar (MyQMenuBar), but I've just made a installEventFilter pointing to the TitlelessWindow class.[/quote]
    sorry but i don't understand the question.



  • If I would need to implement an eventFilter in my QMainWindow or MyQMenuBar, how could I achieve it?
    Think for example if I wanted to implement more logic in MouseMoveEvent but only for my QMainWindow.

    And how could I combine my TitlelessWindow with another similar one "DecoratedWindow"?


  • Moderators

    [quote author="aidaqt" date="1383751265"]If I would need to implement an eventFilter in my QMainWindow or MyQMenuBar, how could I achieve it?
    Think for example if I wanted to implement more logic in MouseMoveEvent but only for my QMainWindow.
    [/quote]

    If it's for all QMainWindow's then do it like i did in my code for the QMenuBar (using qobject_cast).
    If it's jsut for a special window you could subclass from this class and also reimplement eventFilter() and implement the special case and for the rect call the base class implementation...

    [quote author="aidaqt" date="1383751265"]
    And how could I combine my TitlelessWindow with another similar one "DecoratedWindow"?[/quote]
    This is just a matter of Object-orientation. Meaning you could subclass TitlelessWindow... or generate a base-class which only implements the parts which are common for both, etc.



  • Hi,

    More problems, I hope your patience hasnt ran out yet.. :)

    -If I apply this TitlelessWindow to a QMainWindow everything is OK, widgets inside are in a centralWidget containing them.

    -If a window has not centralWidget, decoration is applied to the concrete widgets inside ( QGroupBox for example) but the other parts of the window are transparent. Why do I need a centralWidget (or one containing the QGroupBoxes)?

    -For a reason, QGroupBoxes doesnt inherits the eventFilter, but QTabWidgets does, so I needed to made MyQGroupBox class...

    Thanks again


  • Moderators

    you only need to add the decoration-widget to widgets you know they are top-level widgets if i understood you correct what you want to achieve.
    And also your decoration window doesn't necessarily need to inherit QMainWindow. It's enough if it looks like this:
    @
    TitlelessWindow::TitlelessWindow(QWidget* content, QWidget* parent)
    : QFrame(parent)
    {
    //...Removing title code and some decorating stuff... also:

     QVBoxLayout* layout = new QVBoxLayout;
         layout->addWidget(content);
     this->setLayout(layout);
    

    }
    @



  • Hi,

    My TitlelessWindow inherits from QWidget. This is my Titleless constructor code:

    @TitlelessWindow::TitlelessWindow(QWidget* widget) {

                 //...Decorating stuff...
    

    widget->setParent(this); //Do I really need this?

    this->setGeometry(widget->geometry()); //Pick the size. Not working also

                this->setLayout(new QGridLayout ()); 
    

    this->layout()->addWidget(widget); //Add the QMainWindow or other window to the layout of TitlelessWindow. //Do I really need this?

    //Decorate the wrapper class
    this->setWindowFlags(Qt::FramelessWindowHint);
    this->setAttribute(Qt::WA_TranslucentBackground); //This is being propagated to the child widgets if not centralWidget is present

    @


  • Moderators

    you can leave out the setParent since this is done when you add the widget to the layout anyway.

    I would recommend to do the layout like i did, as long as you do not need the QGridLayout. Beside QGridLayout::addWidget(QWidget*) is not like it is intended to be used (QGridLayout expects row and col parameters).

    Do you really need the widget attribute "TranslucentBackground"? You could also use QWidget::setMask() for instance.
    If you need it, you can try to unset it on the child widget right after you have set it on the frame widget. Or set the attributes at the very first in the constructor.
    Or also try QWidget::setAutoFillbackground(true) on the child widget.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.