Styling QML without manually marking each property to be styled - css

I know that QML does not support CSS styling like widgets do, and I have read up on alternative approaches to styling/theming:
https://qt-project.org/wiki/QmlStyling
http://www.slideshare.net/BurkhardStubert/practical-qml-key-navigation/34
Common for these approaches is that they require the developer to specify the parts of the QML that can be styled, either by binding to a property in a “styling QML file/singleton”, or by using a Loader to load a different QML component based on style name. What I would like is something that works like the "id" selector in CSS instead of the "class" selector, so that the individual QML files do not have to know whether they will be styled later on or not.
My current approach make all the QML files look similar to this (using approach in link 2):
Main.qml
Rectangle {
Id: background
color: g_theme.background.color
//g_theme is defined in root context and loaded dynamically
}
What I would like to do is:
Main.qml
Rectangle {
Id: background
color: “green” // default color
}
And then have a styling file that defines (or similar)
Main.qml #background.color: red
Is this possible at the moment, or something that is in the pipeline for a future Qt version, or will the preferred way of styling continue to be something similar to the approach described in the links above?

The preferred way isn't applying a style on default components, but deriving from these components to create pre-styled custom components.
What I do for my projects :
First, I create one centralized 'theme' file, as a JavaScript shared module :
// MyTheme.js
.pragma library;
var bgColor = "steelblue";
var fgColor = "darkred";
var lineSize = 2;
var roundness = 6;
Next, I create custom components that rely on it :
// MyRoundedRect.qml
import QtQuick 2.0;
import "MyTheme.js" as Theme;
Rectangle {
color: Theme.bgColor;
border {
width: Theme.lineSize;
color: Theme.fgColor;
}
radius: Theme.roundness;
}
Then, I can use my pre-styled component everywhere with a single line of code :
MyRoundedRect { }
And this method has a huge advantage : it's really object-oriented, not simple skinning.
If you want you can even add nested objects in your custom component, like text, image, shadow, etc... or even some UI logic, like color-change on mouse hover.
PS : yeah one can use QML singleton instead of JS module, but it requires extra qmldir file and is supported only from Qt 5.2, which can be limiting. And obviously, a C++ QObject inside a context property would also work (e.g. if you want to load skin properties from a file on the disk...).

It could also be helpful to look at Qt Quick Controls Styles
When using Controls Styles it is not necessary to explicitly assign each property in the target control. All properties can be defined in a separate [ControlName]Style component (e.g. ButtonStyle).
Then in target component (e.g. Button) you can just reference to style component in one line of code.
The only one downside here is that Style components are available for Qt Quick Controls only. Not for any Qt Component.

Related

PyQt styling with QSS & #pyqtProperty

As a simple example, suppose I have a QPushButton that has some basic styling applied to it using button.setStyleSheet(). Additionally, I want some parts of the style (background color) to change in response to events triggered by the user.
I could simply make calls to button.setStyleSheet() throughout my code, but these will erase pre-existing style attributes. So I would not be able to edit just one styling attribute in response to some signal, rather I would have to specify all style attributes every time the style changes (even if only one or a few style attributes are actually being modified).
I understand this can be done more neatly with the QSS property selector which can apparently be used with "any Qt property that supports QVariant::toString()". This is also described for C++ here: dynamic properties & stylesheets.
So suppose I have a custom widget class, and I want some of its styling to depend (responsively) on a property of my custom class, e.g. a state property. It sounds like I should be able to do something like
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtCore import pyqtProperty
class StatefulWidget(QPushButton):
""" QPushButton colored to reflect its state. """
def __init__(self, state=0):
super().__init__()
self.state = state
self.setStyleSheet('''
StatefulWidget[state=0] {
background-color: red;
}
StatefulWidget[state=1] {
background-color: green;
}
''')
#pyqtProperty(int)
def state(self):
return self._state
#state.setter
def state(self, state):
# Register change of state
self._state = state
# Update displayed style
self.style().polish(self)
but this does not work for me.
Can anyone suggest the proper way to make this work? The intended behavior is that the style should be updated whenever the property changes, but the underlying CSS/QSS should be fixed.
Related:
Update stylesheet without losing original style
The documentation about the QSS property selector says:
You may use this selector to test for any Qt property that supports QVariant::toString()
The QSS parser code is quite complex and somehow cryptic, and I've never been able to completely follow its logic, but I believe that it always tries to convert string values to numbers whenever they contain digits, so the 0 value for the QSS property selector will not match the "0" resulting from toString().
As a general rule, always use quotes around values:
self.setStyleSheet('''
StatefulWidget[state="0"] {
background-color: red;
}
StatefulWidget[state="1"] {
background-color: green;
}
''')

How to view QML component with variable settings in Qt designer?

Well I've applied some singleton style in my QML components and with those variable settings I cannot see the proper QML designer view. It's clear that Qt Designer is not able to read those variable values from my style file and render properly.
Is there any way to view the component in designer mode when using style variables? In below snippet if I pass actual values designer shows the component as expected.
What is best practice in QML to apply common component coloring and sizing factors?
WaveComponent.qml
Rectangle {
id: waveRect
width: Style.waveBlockWidth
height: Style.waveBlockHeight
color: Style.blockColor
radius: Style.blockRadius
}

How to create a custom grouping of properties and apply them to different controls?

I have a window with Buttons, TextFields, Labels etc. These all share common properties like font family, color, size etc. What I would like to be able to do is to define a grouping of these properties (called, say, textStyles.MainTitle or textStyles.DescriptiveText etc.) that would include a font family, size and weight, height and color. And then in the QML file I would write something like:
myCustomProperty: textStyles.MainTitle
and this would apply those values to the control. How can I do this?
QML controls are styled by implementing their respective styles, for example for Button you have to implement a ButtonStyle.
As for the actual "grouping" you can just use a QtObject
property QtObject textStyles : QtObject {
property FontLoader mainTitle : FontLoader {...}
....
}
You can also extend styled components as dedicated types:
// StyledText.qml
Text {
font.family: someFont
font.pixelSize: fontSize
color: someColor
font.italic: true
font.letterSpacing: -1
...
}
And then use that by just StyledText {} instead of repeatedly styling regular Text elements.
Where / in what file do I place the QtObject snippet? I don't understand what // StyledText.qml is, or what a FontLoader is.
If you want it to be available in your entire application, you can put it as a property of your root object in main.qml, thanks to dynamic scoping textStyles will resolve from every other file in your project. You can put entire component styles in it as well and share in the project.
StyledText.qml is just an extra qml file you add to your project, you can being with just implementing its body in an existing qml file, then rightclick on Text and select refactoring -> move component into separate file.
A FontLoader is just a regular QML component, you can use it to load specific fonts and use as a font source for text. You don't have to use that, you can use font families from your system fonts as well. The font loader is useful for fonts you bundle with your application.

Adding new stylesheet parameters for custom Qt widgets

I would like to add stylesheet options for a custom widget I have developed. We have extended the QPushButton to be a different colour and to flash when it is depressed. This has been done by adding a new property, background color down. And we set this in code. But I would like to set this instead using a Qt stylesheet entry, something like
QFlashingButton
{
background-color-down: yellow;
flashing-interval: 5;
}
I can see one way to do this, read out the stylesheet info using the stylesheet() method, then parse it for parameters relevant to my widget and set them. But I am wondering if there is some way to access the code Qt have themselves for processing stylesheets. At first sight of their code this seems perhaps not to be publically available.
As long as the parameter you want to control in the stylesheet is a QProperty, you can set it in the stylesheet using the syntax: qproperty-<PROPERTY_NAME>: <PROPERTY_VALUE>
I don't think property names can actually have dashes in them, so assuming your QProperties on your custom widget are actually backgroundColorDown and flashingInterval, then your stylesheet would look like:
QFlashingButton
{
qproperty-backgroundColorDown: yellow;
qproperty-flashingInterval: 5;
}

Flex: How to access properties of component in dynamic creation?

I have a component which is created dynamically. I want to access the properties on it.
for example i create a vbox and i want to access the text font or gap of the component
var MyVBox: VBox = new VBox;
MyPanel.addChild(MyVBox);
How should it be done?
All properties and methods are accessed with "." (dot) notation.
Example:
myVBox.width = 400;
Styles are set using the setStyle() method. In your case that would be
myVBox.setStyle("fontFamily", "arial");
myVBox.setStyle("verticalGap", 20);
Check the docs at http://livedocs.adobe.com/flex/3/langref/ for the available properties and styles of each component.
The thing to remember when using ActionScript instead of MXML is that the style properties are not accessed as properties on the object but through the getStyle("propertyName") method. Font is a style for example.

Resources