PDA

View Full Version : Still need help: Best way to have application "skins"?



codeslicer
22nd February 2008, 16:42
What's the best way to have different "skins" for an application each with different effects and slightly different buttons. Is reimplementing the main class a solution? Or Can the QUILoader be used? (I'm afraid it might be a bit slow, and might not support multiple-inheritance)

Anyways, if anyone could help out, that would be great :rolleyes:

Thanks in advance ~codeslicer :p

jpn
22nd February 2008, 16:56
See Style Sheet Example (http://doc.trolltech.com/4.3/widgets-stylesheet.html).

codeslicer
22nd February 2008, 17:14
In one of my "skins", which is currently just a dialog, I use setMask and in the end have an irregulary shaped window. I have different event handlers which draw buttons which fade in and out when the mouse hovers over them. Then I want a plain skin which uses the system's window manager to do all the work. Does the QStyle class support this situation? Thanks so far though :cool:

THRESHE
22nd February 2008, 17:46
I have different event handlers which draw buttons which fade in and out when the mouse hovers over them. Then I want a plain skin which uses the system's window manager to do all the work. Does the QStyle class support this situation? Thanks so far though :cool:
That's possible for hovering you should use styles like this

QPushButton
{
background-image: url(:/images/Buttons_Styles/b_butt_free.png);
border: none;
}
QPushButton:hover
{
background-image: url(:/images/Buttons_Styles/b_butt_over.png);
border: none;
}
QPushButton:pressed
{
background-image: url(:/images/Buttons_Styles/b_butt_down.png);
border: none;
}
QPushButton:disabled
{
background-image: url(:/images/Buttons_Styles/b_butt_disabled.png);
border: none;
}
And then use QWidget::setStyleSheet

codeslicer
22nd February 2008, 19:27
Ok, how would I designate certain parts of the qlabel to react to hover? For example, if I have this image:

====
- [] x
====

And it's one big QLabel, how would I set different images depending on where the button was using Style Sheets? I want to do it like the HTML image maps.
Thanks in advance for any help :)

No help yet?

codeslicer
22nd February 2008, 22:24
*bump*
Anyone?

wysota
23rd February 2008, 01:08
What exactly is the effect you want to achieve?

codeslicer
23rd February 2008, 01:22
Having different "skins" and forms in general. This is the situation:

I have an app which has a custom titlebar, buttons, and even a custom shape using QWidget::setMask(), but for platforms on which this wouldn't really fit in (IE Mac), I want the user to be able to "turn off" the skin and load a form which doesn't use any effects/custom titlebars and uses the default window decorations. Should I create two seperate QWidget classes, ie myApp() and myAppWithCoolSkin(), and check what option the user picked by checking QSettings before the widget is even initialized? That looks like it would involve copy and paste of the same code.

Any help will be appreciated. Thanks in advance, again :D

wysota
23rd February 2008, 01:49
As for everything but custom window shapes you can use a custom QStyle subclass and simply replace one style with another upon demand. As for custom shapes that you want to obtain through setMask() unfortunately you need to do the masking manually, thus you need to come up with a system of reading shapes from some configuration files and applying them to your widgets.

codeslicer
23rd February 2008, 02:20
Well, I don't really want a whole skin manager, just let the user pick between the stylish one and the plain one. I don't need to have different skin files and such, just let the user chose between the one with setMask() and buttons with effects, etc, and one which will try to fit as best as possible with the native platform.

Can I use different classes then? With the same code?

wysota
23rd February 2008, 08:51
You can use the same classes. Just have a component which will be able to style other components.

codeslicer
23rd February 2008, 13:18
Ok, but if I have 2 seperate .ui files, how would I declare them in the same class? All of my buttons, boxes, etc will still have the same object names, so I guess I don't really need to change the "core" of my program. Maybe, in the constructor, I should have an if() tag which should check QSettings if the app should load the new/old skin, and somehow load a different ui file based on that?

But I have a question. If I use the QUILoader class, is it slow? And does it support multiple inheritance? Thanks :)

Oh, and when are style sheets going to be supported on Qt/Mac?

THRESHE
23rd February 2008, 14:44
Oh, and when are style sheets going to be supported on Qt/Mac?
They are already supported :)

codeslicer
23rd February 2008, 15:07
Ok, that's good!

But how do I use 2 seperate ui files in the same class? QUILoader? Sounds slow and looks like it doesn't support multiple inheritance...

THRESHE
23rd February 2008, 15:17
Ok, that's good!

But how do I use 2 seperate ui files in the same class? QUILoader? Sounds slow and looks like it doesn't support multiple inheritance...
I don't really understand what do you want to do. If you want to set style sheet to a widget in ui form just press right mouse button on it and select change style sheet and change it :) like on the screenshot

jpn
23rd February 2008, 15:22
But how do I use 2 seperate ui files in the same class?
What do you intend to do? It makes no sense to load two different .ui's on a single widget, does it? You can use direct approach (http://doc.trolltech.com/4.3/designer-using-a-component.html#the-direct-approach) if you have two different widgets for which you want to load different forms. But by using direct approach you also lose a lot of flexibility. Then why not just wrap those two widgets as separate classes which both use single or multiple inheritance approach...

codeslicer
23rd February 2008, 15:47
Thanks for trying to help me. A lot. Maybe I explained myself wrong.

I have this form with style sheets and custom buttons, setMask() enabled on some parts, etc. Then, I have a second form which doesn't use any of that, and uses the Operating System's Style. Now, at first, the "fancy" form is used for the program, but what if the user doesn't like it? Or what if somehow it doesn't work on his/her system? I want the user to be able to chose between using the "fancy" form and the "plain" one.

If I want to have two different forms, with the "fancy" one containing different functions which would be used for setting effects, and the core functions, while the "plain" one will just have the core functions(functions which perform what the program is going to do, ie download and read a file), how would I approach that. Can I have two seperate classes, one which would use the form with the effects, then a seperate one which wouldn't use any effects at all?

Is this possible? :confused:

jpn
23rd February 2008, 15:59
Wouldn't it be only a matter of removing style sheets, custom styles and/or masks (which ever you end up using)?

codeslicer
23rd February 2008, 16:10
Well, it's not as simple as myApp->removeStyle() or something like that.

The "fancy" design is completely different, the only things that are the same are the object names...

jpn
23rd February 2008, 16:18
The "fancy" design is completely different, the only things that are the same are the object names...
You mean like the form layout is completely different? I suppose you could use single inheritance approach and have two different ui members:


#include "ui_plainwidget.h"
#include "ui_fancywidget.h"

class MyFancyWidget : public QWidget
{
...
private:
Ui::FancyWidget fancyUi;
Ui::PlainWidget plainUi;
};

void MyFancyWidget::setFancyMode()
{
saveFormState(); // you might want to store contents if you let user to change mode on the fly
qDeleteAll(children()); // or something like this to clear everything, it is important to delete layout()
fancyUi.setupUi(this);
restoreFormState(); // restore contents
}

void MyFancyWidget::setPlainMode()
{
...
plainUi.setupUi(this);
...
}

codeslicer
23rd February 2008, 17:05
Thanks! Only one question. Later in my code, the "core" of it, how would I refer to the objects on my forms, if they have the same names? Do I have to use plainUi.myTextBoxObject, or can I just ise myTextObject like in multi-inheritance? Thanks again! :D

jpn
23rd February 2008, 17:22
Thanks! Only one question. Later in my code, the "core" of it, how would I refer to the objects on my forms, if they have the same names? Do I have to use plainUi.myTextBoxObject, or can I just ise myTextObject like in multi-inheritance? Thanks again! :D

That's a good point I didn't think of. :) Unfortunately those two ui members have nothing in common... How about introducing additional pointers to required objects (hopefully you don't have tons of them) so that you don't have to continuously check the mode and clutter your code with conditional blocks?


class MyFancyWidget
{
...
private:
Ui::FancyWidget fancyUi;
Ui::PlainWidget plainUi;

QTextEdit* myTextObject;
...
};

void MyFancyWidget::setFancyMode()
{
...
myTextObject = fancyUi.myTextObject;
}

void MyFancyWidget::setPlainMode()
{
...
myTextObject = plainUi.myTextObject;
}

codeslicer
23rd February 2008, 17:49
Great!! Thanks! It would take me several years to think of that one! :eek:

Any chance I could create some for(int i=0; i<childWidgets().length(); i++) loop? Note that childWidgets() may not be an actual property, but for the sake of this question...

So could I somehow apend that name? If it's not possible, it's ok.

I don't need to do much, just a couple of buttons and a text box.

Also, where would I put the connect()'s? In each of the plainUi and fancyUi functions, or in the constructor?

Thanks a lot! :p

jpn
23rd February 2008, 18:05
Any chance I could create some for(int i=0; i<childWidgets().length(); i++) loop? Note that childWidgets() may not be an actual property, but for the sake of this question...
Do you mean something like:


QPushButton* button = findChild<QPushButton*>("myButtonWithCertainObjectName");
if (button) // just to assure it was found
button->doSomething();

// or

QList<QPushButton*> allButtons = findChildren<QPushButton*>();
foreach (QPushButton* button, allButtons)
button->doSomething();



Also, where would I put the connect()'s? In each of the plainUi and fancyUi functions, or in the constructor?
The whole GUI, including all widgets, gets re-created when you call respective setupUi(). So you must re-establish all signal-slot connections every time the "mode" is changed.

wysota
23rd February 2008, 18:19
If I want to have two different forms, with the "fancy" one containing different functions which would be used for setting effects, and the core functions, while the "plain" one will just have the core functions(functions which perform what the program is going to do, ie download and read a file), how would I approach that. Can I have two seperate classes, one which would use the form with the effects, then a seperate one which wouldn't use any effects at all?

You can also have a common base class with objects common to the two "modes" and two subclasses - one "plain" and one "fancy".

codeslicer
23rd February 2008, 19:31
@wysota - maybe I don't get it, but the buttons, textboxes, labels, etc are children of each of the respective forms.

@jpn - by loop I meant instead of having to use:


void MyFancyWidget::setFancyMode()
{
myTextObject = fancyUi.myTextObject;
myButtonObjectt = fancyUi.myButtonObject;
}

void MyFancyWidget::setPlainMode()
{
...
myTextObject = plainUi.myTextObject;
myButtonObject = plainUi.myButtonObject;
}


I could use:


for(int i=0; i<fancyUi.children().size(); i++) {
childrenList[i]
}

I don't really know how to make this, but what I'm trying to do is to dynamically convert all of fancyUi's children into names which wouldn't require prefixing fancyUi.

Or, could I use this?

using namespace fancyUi

But this has to be globally declared right? I can't put it in each of the mode-changing functions can I? What I'm trying to do is somehow "fake" the multi-inheritance approach from single inheritance to make life easier. I can make pointers to each single object, but as my program grows this looks like it would involve lots of unneeded code.

Thanks!

wysota
23rd February 2008, 20:37
@wysota - maybe I don't get it, but the buttons, textboxes, labels, etc are children of each of the respective forms.

So where is a problem with that?

codeslicer
23rd February 2008, 20:47
Those widgets(buttons, text boxes) are contained in different layouts which have skins, therefore I can't remove those layouts without disrupting the children.

Please read my post above. ^

wysota
23rd February 2008, 21:52
I don't really see the problem...

Correct me if I'm wrong but essentially you have two versions of the same widget - one with reduced and the other with extra functionality (regardless of how the functionality is presented). So you can have a class that holds the common part of the two versions and two subclasses extending it with functionality unique to each representation. Then it's just a matter of instantiating objects of a proper (depending on the "mode") and cloning the state of the common part from one instance to the other.


class Base : public QWidget {
//...
};

class Plain : public Base, private Ui::BaseForm {
//...
};

class Fancy : public Base, private Ui::FancyForm {
//...
};

//...

Base *window = new Fancy(...);
// or
Base *window = new Plain(...);

BTW. What you are doing is not "skinning" - you have different functionality (at least I assume that based on what you have written in this thread) thus it is something more than just changing the way content is presented.

codeslicer
23rd February 2008, 23:06
Ok, I'm sorry but I don't understand what you mean by having a base and 2 subclasses...

I have 2 seperate ui files with different styles. The "plain" one contains just the essentials, nothing more than a simple form with no style whats so ever(Other than the style Qt gives to it at run time, based on the OS). Then, I have a "fancy" one which includes the core functions and buttons, etc, but also contains some different actions in the constructor(like setMask()). It also has a different style, like different images, integrated style sheets in Qt Designer, and more.

In my program I want to let the user chose which style he wants. After he chooses, the program restarts, and reads from QSettings what he wants. If it's the fancy style, the program should read the "fancy" ui file and perform extra operations on the main window, in addition to the core commands.
However, if it's plain, then the program should read the "plain" ui file, and only do the core commands.

I received some suggestions from jpn, having setFancyMode() and setPlainMode(), but my question is, how can I transform the application from single-inheritance to multi-inheritance. Ie, I don't want to have to call plainUi.myTextBox, instead calling just myTextBox.

If I create pointers to each object during the setPlainStyle() or setFancyStyle() functions, do I have to somehow globally declare them in the header file, or will they be globally defined already for use in other functions of the same class?

Thanks a lot so far, I think I'm not getting it because I'm kinda new and need more explanation than a normal coder would :o So again, thanks for all this help, this forum is the best I've been on :D

codeslicer
24th February 2008, 00:16
I don't think I need to dynamically redefine all of the objects in the forum, only the essential buttons. One more thing, should I use pointers, ie:



QLineEdit myLineEdit = *plainUi.myLineEdit;


or direct declarations:



myLineEdit = plainUi.myLineEdit;


Also, do I need to declare these in the header file?

Thanks :)

PS: I can't change the topic title. Can a mod please change it to SOLVED? That would be great, thanks :p

wysota
24th February 2008, 00:28
Ok, I'm sorry but I don't understand what you mean by having a base and 2 subclasses...
Hmm... you know what a "class" in object oriented language is, right?


I have 2 seperate ui files with different styles. The "plain" one contains just the essentials, nothing more than a simple form with no style whats so ever(Other than the style Qt gives to it at run time, based on the OS). Then, I have a "fancy" one which includes the core functions and buttons, etc, but also contains some different actions in the constructor(like setMask()). It also has a different style, like different images, integrated style sheets in Qt Designer, and more.
Yes, I have read what you had written :)


In my program I want to let the user chose which style he wants. After he chooses, the program restarts, and reads from QSettings what he wants. If it's the fancy style, the program should read the "fancy" ui file and perform extra operations on the main window, in addition to the core commands.
However, if it's plain, then the program should read the "plain" ui file, and only do the core commands.
Do both these UI contain the same widgets (just positioned or styled differently)? Could you maybe attach them to your post so that we may have a look at them? If you only wish to change the look of your widget, you don't need two separate UI files - you can apply different stylesheets on the same set of widgets or set a different style on the widget.

codeslicer
24th February 2008, 01:45
I kinda want to keep it a secret for now... :p

But my two ui files are different, yet same. Here's what I mean.

In the plain one, all I have is a tabwidget and several group boxes w/different buttons and such.

Now in the fancy one, I still have that same tab widget, But it is surrounded by a layout of QLabels, which, combined with setMask(), form the dialog's unique display. So in other words, it still contains the same objects, but more, with more styles and images.

wysota
24th February 2008, 02:19
I kinda want to keep it a secret for now... :p

I'm going to keep the solution a secret as well then. Most probably (with about 99.9% probability) you don't need two UI files, one is enough to obtain both modes.

codeslicer
24th February 2008, 02:39
Well, either I'm wrong, or I have a 0.01 chance of getting this right. Attached, you will find screen shots of my program(Yes it might look a bit "weird" but here it is...)

wysota
24th February 2008, 10:45
Yes, you can do that using a single UI. Attached you'll find a UI with two different stylesheets set. Combined with setMask you'll achieve the effect you want.

codeslicer
24th February 2008, 13:07
Well, those "borders" are images. Also, they need to dynamically expand, so there are severl of them, all different. Using stylesheets, would I just set the QLabel image to nothing?

wysota
24th February 2008, 13:36
Well, those "borders" are images. Also, they need to dynamically expand, so there are severl of them, all different.

It's all achievable using style sheets.

codeslicer
24th February 2008, 14:10
Thanks, I'll try that.

But what should I do, create "placeholders" for those images?

wysota
24th February 2008, 14:19
No. Just style the form or put a frame in the form and style it like I did in the images I posted "border" and "border-image" statements are your friends.

codeslicer
24th February 2008, 14:45
How do I have multiple border images, ie some images have to be fixed size while the others have to stretch.

My form has to be dynamic, different fonts are used on different OS's, so I can't create one big image...

wysota
24th February 2008, 15:21
How do I have multiple border images, ie some images have to be fixed size while the others have to stretch.

See the docs for border-image.

codeslicer
24th February 2008, 16:40
Wow, I guess that's why you're a guru :cool:

Thanks a lot =]

I guess I'll still use the functions setPlainMode() and setFancyMode() and based on that set and remove masks, etc.

How would I set masks on the border image though? And would the "stand" (See picture in previous post) be put in the border image, or should it be seperate? Thanks again :)

wysota
24th February 2008, 16:44
How would I set masks on the border image though?
You mask widgets, not images.


And would the "stand" (See picture in previous post) be put in the border image, or should it be seperate?
Yes, it can be part of the border image. You can obtain the shape you want using setMask.

codeslicer
24th February 2008, 16:57
Ok, then how would I mask the border-image widget? :p

wysota
24th February 2008, 17:03
Mask the top level widget (window). Of course you need to calculate the mask first, but it shouldn't be hard.

codeslicer
25th February 2008, 01:56
I don't think this will work, because, the buttons at the bottom of the monitor will stretch. Take a look at that screenshot on page 2. But that's a nice idea, if I didn't have to worry about scaling, then that would have been the ideal solution.

I'm thinking of using QWidget->hide() on each of those image labels, what do you think, will that work?

Thanks for helping me out, my lack of explanation skills keeps requiring more and more solutions :p

mchara
25th February 2008, 07:03
You may use QStyle and make usage of polish() / unpolish() methods to set/unset masks etc.
It would be more flexible when you'll be adding more styles in future (u have all style stuff in single class & you don't have to remember about changes in lots of places in entire application code).

I'm using single style for skins effect that gets images path in constructor and styleplugin searches skins directory and creates an instance of the same style for different images set.
It causes that adding new skins doesn't require coding, just some painting job.

wysota
25th February 2008, 08:02
I don't think this will work, because, the buttons at the bottom of the monitor will stretch.
Not necessarily. If you get the cuts right and combine it with setting the border size on each edge it might just work.

codeslicer
25th February 2008, 11:42
Not necessarily. If you get the cuts right and combine it with setting the border size on each edge it might just work.


The form resizes based on its contents, so I can't have fixed size.

wysota
25th February 2008, 12:03
Read again how border-image works. Pay special attention to the parameters. Read about border-width as well.

codeslicer
25th February 2008, 22:37
Yes, I read it. It's just that the buttons and stand will stretch to the right and left, horizontally. Oh well, the stretch probably won't be more than a few pixels.

To understand better, attached is the ui form + resources...

Please do not copy/use those images in your applications (Although I doubt you will need to)

codeslicer
26th February 2008, 11:11
*Bump*

Border image doesn't have a middle image that doesn't stretch right?

wysota
26th February 2008, 11:30
I think it does. But if it doesn't work, use background-image.