PDA

View Full Version : How can I configure a color in a wizard?



apatwork
7th November 2017, 12:59
Hi there,

I'm building a GUI in C++ on Linux using Qt 5.9. I want to implement a functionality for choosing a color as part of a wizard. What I have in mind is a solution somewhat similar to what I know from the color selection for syntactic items in C/C++ code in Eclipse CDT's preferences which looks like this:

12657

I created a subclass of QWizardPage which contains (among other widgets) a QLabel which contains the text "Color:" followed by a QPushButton:

12658

With this code placed in the wizard page's ctor. I implement the color
chooser functionality:


VisualPropertiesPage::VisualPropertiesPage(QWidget *parent) :
QWizardPage(parent),
ui(new Ui::VisualPropertiesPage) {

ui->setupUi(this);


// Initialize the color chooser.
QPalette pal;
pal.setColor(QPalette::Button, Qt::black);
pal.setColor(QPalette::ButtonText, Qt::white);
ui->lineColorBtn->setPalette(pal);

[...]

To execute the QColorDialog I implement a slot method as follows:



void VisualPropertiesPage::on_lineColorBtn_clicked() {
QColor color = QColorDialog::getColor(Qt::black, this, "Pick a color",
QColorDialog::DontUseNativeDialog);
QPalette pal;
pal.setColor(QPalette::Button, color);
ui->lineColorBtn->setPalette(pal);
}


This way I can easily select a color. My problem now is:

How can I access the choosen color in the wizard's accept() method?

I know that there is the method

registerField(cons QString &name, QWidget *widget, const char *property, const char *changedSignal)

but couldn't figure out yet, how to call it to gain access to the
button's color. I don't know how to specify the third and fourth parameter.

Or is there possibly a completely different (and easier) way to
configure a color value in a wizard?

Any hint is appreciated!

d_stranz
8th November 2017, 03:18
By far the easiest method is to simply declare a QColor member variable in your wizard page class and set it to the user's choice instead of retrieving the color into a temporary variable in the slot. You can then implement a "getter" method to retrieve the color in the accept handler.

Or, to be really in the Qt mindset, your VisualPropertiesPage should emit a signal ("lineColorChanged( const QColor & )" say) with the new value of the color. The wizard implements a slot to handle that signal and store it away in the user preferences somewhere. This decouples the wizard from the wizard pages; all the wizard needs to know is that the page will emit a signal when the color changes. It doesn't have to know anything else about the page.

apatwork
9th November 2017, 11:28
Hi,

thanks a lot for your helpful instructions! I first tried out the Qt-ish solution based on signals and slots as follows:

In the wizard page class I added a connect(...) statement:


VisualPropertiesPage::VisualPropertiesPage(QWidget *parent) :
QWizardPage(parent),
ui(new Ui::VisualPropertiesPage) {

ui->setupUi(this);

[...]
GraphWizard *wiz = static_cast<GraphWizard*>(wizard());
connect(this, SIGNAL(lineColorSet(const QColor&)), wiz, SLOT(setLineColor(const QColor&)));
[...]


I defined a signal lineColorSet(QColor &color):



class VisualPropertiesPage : public QWizardPage {
Q_OBJECT

[...]
signals:
void lineColorSet(const QColor &color);
[...]


Finally in the slot method on_lineColorBtn_clicked() I added an emit staetement:



void VisualPropertiesPage::on_lineColorBtn_clicked() {
QColor color = QColorDialog::getColor(Qt::black, this, "Pick a color",
QColorDialog::DontUseNativeDialog);
QPalette pal;
pal.setColor(QPalette::Button, color);
ui->lineColorBtn->setPalette(pal);
QWizard *aWizard = wizard();

emit lineColorSet(color);
}


In the QWizard subclass GraphWizard I defined a member



QColor mLineColor;


and a slot to set that member:



public slots:
void setLineColor(const QColor &color);


Now the signal slot mechanism should work. Much to my surprise this didn't work. The signal is emitted but the setLineColor(...) method in GraphWizard does not get called. I have no idea why. So I implemented the needed functionality in a somewhat similar way to your first proposal:

In the slot method on_lineColorBtn_clicked() instead of the emit statement I added this line:



QWizard *aWizard = wizard();
static_cast<GraphWizard*>(aWizard)->setLineColor(color);


This way the color is passed to the wizard and can be accessed there in the accept() method.

While this now in principle works fine I'm still curious if there would be a possibility to implement this functionality somehow using the registerField(...)/field(...) mechanism. As already mentioned one normally would place a registerField(...) method in the ctor. of the wizard page. The method's signature looks as follows:



void registerField(const QString &name, QWidget *widget, const char *property = Q_NULLPTR,
const char *changedSignal = Q_NULLPTR)



So my idea now is to pass the palette object through the field. So my call to registerField should look somewhat similar to this:



registerField("lineColorPalette", ui->lineColorBtn, ui->lineColorBtn->palette(),
ui->lineColorBtn-><SomeSignal>);



Any ideas?

d_stranz
9th November 2017, 16:23
GraphWizard *wiz = static_cast<GraphWizard*>(wizard());
connect(this, SIGNAL(lineColorSet(const QColor&)), wiz, SLOT(setLineColor(const QColor&)));

I would do this the other way around: make the connection in the wizard where you create the property page, not in the property page constructor.

Personally, I think the registerField() method is not a very good idea. It tightly couples a change that the user is making to some property value to the detailed UI implementation of the widgets used to make the change. Suppose you discover some fabulous "ColorChooserButton" widget that works perfectly instead of what you are using now. In addition to replacing the button in your UI, you'll also probably have to change the registerField() method since this new button probably won't have the same properties as a standard pushbutton, and your wizard code will have to change too.

With the signal / slot approach, it doesn't matter if you use a pushbutton, a fancy ColorChooserButton, or your mother to implement setting the property. All the wizard needs to know is that however the color gets changed, it will get a signal when it does.

I don't know why your slot doesn't get called. You'd have to show more than small pieces of code so we can follow the logic and see how and where you are making the connection. If the connect() call doesn't work, Qt should be issuing a warning on the debug output that says so.

apatwork
10th November 2017, 10:34
Hi,

thanks a lot for your valuable hint! Think, I now understand your design that decouples the way the color is determined on the QWizardPage subclass VisualPropertiesPage from the use of the value in the QWizard subclass GraphWizard's accept() method. So I added this simple connect statement in the GraphWizard's constructor (must admit that in between I renamed lineColor to penColor:-):


connect(ui->visualPropertiesPage, SIGNAL(penColorSet(QColor)), this, SLOT(setPenColor(QColor)));

and in the VisualProperties class's slot method on_penColorBtn_clicked I added an emit statement that emits the


penColorSet(QColor &color)

signal. All this works now like a charm and I gained some better understanding of good design with Qt!