How to change QWizard default buttons in each QWizardPage



  • Hello again!! I'm making a program with Qt 4.8.5 on a Fedora system (unix). It's a QWizard structure with its QWizardPages.

    I needed (in past, before my boss told me to make some changes) to change the default QWizard buttons (back, next, finish...) and customize it. I found I could do it putting the next lines on the constructor of my QWizard class called BaseWizard (class BaseWizard : public QWizard)

    QList<QWizard::WizardButton> button_layout;
    button_layout <<QWizard::Stretch << QWizard::CustomButton1 << QWizard::BackButton << QWizard::NextButton << QWizard::FinishButton;
    this->setOptions(QWizard::NoDefaultButton);
    

    With that what I have from left to right is one custom buttons, the back button, the next button and the finish button, and when I want I can show or hide the custom buttons with SetVisible() / SetDisabled() / setEnabled() functions.

    That worked perfect for what I wanted until now... I have to make some changes to the program so I need to change those buttons depending on the page the user is. As I said before, I know I can change the visibility of CustomButton 1 for example BUT I can't do the same with back button so... my quesiton is: How can I decide which buttons I show in every QWizardPage (and their text) and which is the best way to do it?

    I've tried creating a function on my BaseWizard

    // Function to have only 2 custom buttons
    void BaseWizard::ChangeButtons()
    {
        QList<QWizard::WizardButton> button_layout;
        button_layout <<QWizard::Stretch << QWizard::CustomButton1 << QWizard::CustomButton2;
     }
    

    And then in the QWizardPage (lets call it WP) using it like:

    BaseWizard *bz;
    bz->ChangeButtons();
    

    But when I do that nothing changes.I can still see the NextButon for example. I have tried also using first a button_layout.clear(); to see if clenaing it before adding the buttons works, but not.

    I have also tried changing the text of CustomButton1. If I do it in the WP after calling ChangeButtons with wizard()->button(QWizard::CustomButton1)->setText("TEXT CHANGED"); Then the text changes but if I put it in the ChangeButton() function with this->button(QWizard::CustomButton1)->setText("BBBBB"); it does nothing (But its entering into the funcion). Ofc if I try to change the text of CustomButton2 in WP nothing happens because I can't still see that button... so any idea of what I am doing wrong or how could I get what I try will be very apreciated,

    Thank you so much.



  • No one? :(( I'm still stuck there :(


  • Moderators

    What do you want to achieve with this code:

    void BaseWizard::ChangeButtons()
    {
        QList<QWizard::WizardButton> button_layout;
        button_layout <<QWizard::Stretch << QWizard::CustomButton1 << QWizard::CustomButton2;
     }
    

    You create a local variable button_layout which is destroyed when ChangeButtons() finishes!
    Shouldn't you call setButtonLayout(button_layout):

    void BaseWizard::ChangeButtons()
    {
        QList<QWizard::WizardButton> button_layout;
        button_layout <<QWizard::Stretch << QWizard::CustomButton1 << QWizard::CustomButton2;
        setButtonLayout(button_layout)
     }
    


  • @jsulm O.o ermmmm... wow! yes, I used that function on the constructor the first time I create the buttons_layout but forgot it there in the function .... what a Stupid mistake... BUT now, putting it like you said it makes my program crashes.

        QList<QWizard::WizardButton> button_layout;
        button_layout.clear();
        qDebug() << " 1";
        button_layout <<QWizard::Stretch << QWizard::CustomButton1 << QWizard::CustomButton2 << QWizard::NextButton << QWizard::FinishButton;
        qDebug() << " 2 ";
        setButtonLayout(button_layout);
        qDebug() << " 3";
    

    If I run it with that code it prints 1 2 and then crashes in the setButtonLayout() function with a nice The program has unexpectedly finished.


  • Moderators

    Can you post the stack trace?


  • Qt Champions 2016

    @roseicollis

    BaseWizard *bz;
    bz->ChangeButtons();
    

    Is this exactly how you use your wizard? If that's the case bz is just a wild pointer and doesn't actually reference any object.



  • @jsulm said:

    stack trace?

    How can I get it?

    @kshegunov Well I put the funcion on my BaseWizard and was trying to call it from a QWizardPage like that thinking it was correct... how should I do it instead?


  • Moderators

    You should get it if you execute your app from QtCreator and it crashes.

    You did not initialize the pointer, so it is showing to nirvana:

    BaseWizard *bz;
    bz->ChangeButtons();
    

  • Qt Champions 2016

    @roseicollis
    Well, you have to operate on an object. If the way shown is the exact excerpt of the code you'll always get a segmentation fault, because in ChangeButtons() you dereference this by calling setButtonLayout(). You can use instead:

    BaseWizard * bz = dynamic_cast<BaseWizard *>(wizard());
    

    or any other way that actually provides you with the valid wizard object.

    Kind regards.



  • @jsulm Well in the Compile output it says that exit normally (if that is what you mean, if not, can you point me where can I find or see the stack trace?

    @kshegunov said:

    BaseWizard * bz = dynamic_cast<BaseWizard *>(wizard());

    Annd.... DING DING DING! We have a winner here! Yes, this absollutely solves my problem. Thank you so much! I love a little with all that about casts... why can't you just do something like BaseWizard *bz = new BaseWizard() ? why do you have to use a cast and moreover, dynamic_cast ?


  • Qt Champions 2016

    @roseicollis
    Firstly you'll need the wizard your page belongs to, this is the one you're trying to modify, that's why I suggested using the wizard() method. But that method (naturally) gives you a pointer to the base class (QWizard). Because you're trying to typecast a pointer down in the hierarchy and not up (which is always safe) your best bet is to do that with dynamic_cast. This ensures that if the object you receive is not from that hierarchy branch, or you pass a NULL pointer as argument, or your object can't be cast downwards (meaning that it actually is a base class object), the cast will return NULL. This provides you with a safe way to cast object pointers. The most clean approach is to use qobject_cast which works pretty much the same way, but for that you need your class to be declared as a metatype to Qt, which I was not sure you had done.

    Kind regards.



  • @kshegunov

    but for that you need your class to be declared as a metatype to Qt, which I was not sure you had done.

    Neither do I :PWhat do you mean with metatype and how do you declare it? I just use to code making it works and if it does not, then I search on the internet and try to learn what it does but all that you mentioned its far away from my knowlegde...but I want to learn it all so that's why I ask it :) Thank you so much!

    One more thing: I have my BaseWizard class like that:

      #include "WPn"// include of all the wizardpages I have   (in the .cpp)
      class BaseWizard : public QWizard
    

    And the BasePAge like this:

       #include "basewizard.h" //(in the .cpp)
       class BasePage : public QWizardPage
    

    And every QWizardPage like this:

      #include "basewizard.h" // in .cpp
      #include "basepage.h"   // in .cpp
      class WP1 : public BasePage
    

    So now I'm trying to call in basewizard's constructor a function from basepage, but still have the same problem and don't know how to do it really, I've tried with something like:

          BasePage *bp = dynamic_cast<BasePage *>( new QWizardPage()); // really not sure how to writte it sorry)
          bp->SetSubTitleStyle();
    

    I know that as basewizard is not including basepage it is not possible (and I can't include it because its BaseWizard the one included on BasePage. So...How could I do that?

    I need that because when you use setStyleSheet() on QT, it keeps the last one, it doesn't merge them or something like that so I have a general css for labels set in BaseWizard and I want one label (common in all wp) to have another style. I've also tried putting the Wizard style on basepage instead of basewizard but then the program crashes.. I do it putting that in the constructor of basepage:

    BaseWizard * bz = dynamic_cast<BaseWizard *>(wizard());
    bz->setStyleSheet("QWizard{background-color: black);}"
                      "QComboBox:focus, QLineEdit:focus{ background-color: #dddddd; }"
                      "QPushButton{ font-weight: bold; }"
                      "QPushButton:focus{background-color: #8888a8); color:orange;}"
                      "QLabel{font-weight: bold;color:orange}");
    
        SetSubTitleStyle(); //function that chagnes the common label css
    

  • Qt Champions 2016

    @roseicollis said:

    What do you mean with metatype and how do you declare it?

    A meta-type is a class/type that is known to Qt's meta-object system. You're required to declare your class as a meta-type when you intent to use Qt's template functions that depend on it (these include QVariant's conversions and qobject_cast). You also need to register your class at runtime as a meta-type if it will be used as an argument for queued signal-slot connections, which is relevant to multithreaded applications.

    On your other question:

        BasePage *bp = dynamic_cast<BasePage *>( new QWizardPage()); // really not sure how to writte it sorry)
        bp->SetSubTitleStyle();
    

    This is a bad idea, because of two things:

    1. Your wizard is managing your pages, but should not be required to know anything about how the pages are built or styled. This is a responsibility of your page, not your wizard.
    2. The code is simply wrong, from C++ point of view. You create an object of type QWizardPage, then you try to cast the pointer returned to a derived class pointer, which will always cause dynamic_cast to return NULL (simply because QWizardPage is not a BasePage). So at the end of the day, when you call your function, you'll just get a segmentation fault because bp is NULL.

    Suppose you have the following hierarchy:

    class A { };
    class B : public A  {};
    class C : public A  {};
    class D : public B  {};
    

    This means B is A, C is A, D is B (but because B is A, D is A as well). What you're trying to tell the compiler with your code is: "I have an object of type A and want to treat it (the dynamic_cast) as an object of type B", but you see, this is not correct, because while B is A, A is not B, so the dynamic_cast will always return NULL (meaning it can't provide a meaningful conversion). Now suppose you have objects created like this:

    A * a = new A;   // This is okay, our pointer references object of the same type
    A * b = new B;   // This is okay as well, our pointer references an object of type B (but threats it as an object of type A). It's possible because B extends A.
    B * bb = new A; // Error! The object is of type A, and cannot be meaningfully accessed like it was of type B. B does not extend A, it's the other way around!
    

    So...How could I do that?

    You could set your style for the page in the page's constructor (not in the wizard's constructor).

    I hope this helps.
    Kind regards.



  • @kshegunov Ohhh I see it now, I understand it so much better. Thank you for the explanation and the examples, they helped me a lot as they are easy to understand for me :). I know I can do it in every WP constructor but I wanted to avoid it as it mean I will have this line in every of my 50 wizardpages so that's why I was trying to set it in another way but seeing it is totally 'ilegal' then I'll do it this way.

    Really greatfull for your reply! :D


  • Qt Champions 2016

    @roseicollis
    You're welcome.

    I know I can do it in every WP constructor but I wanted to avoid it as it mean I will have this line in every of my 50 wizardpages so that's why I was trying to set it in another way but seeing it is totally 'ilegal' then I'll do it this way.

    Well this is exactly why you have a BasePage class, isn't it? You put all the common functionality inside your base class (this would be your BasePage constructor) and when a wizard page is supposed to do something different you derive from your BasePage a new type that will provide you with the new behavior. My point is that your Wizard should not know about how the page is built or styled, but all the pages are of type BasePage, and BasePage does and should handle all the common things related to the wizard pages, while the wizard itself just collects and displays the pages.

    Kind regards.



  • @kshegunov Yes yes I understand you and that was what I was trying to achieve but dunno why it didn't work fine. finally it works correctly without having to call it every time in every constructor of every wizardpage. The label that was not ok with the stylesheet had a QPalette with the colour and then some css with setStyleSheet() so seems that both are incompatibles and doing that the setStyleSheet() function was ignored. I've removed the QPalette and now it works perfect.

    Thanks for everything!



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