Styling Qml Without Manually Marking Each Property to Be Styled

Styling QML without manually marking each property to be styled

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...).

Trying to style Qt QML items by inserting child styling item

Your second method does not work because your Component sets properties to an item that does not necessarily exist at the time of creating the Component, instead it sets the property when the styleTarget changes. On the other hand, it is not necessary for MyStyle to be an Item since it is not shown, instead use QtObject, and finally to generalize your method change property Item styleTarget toproperty Rectangle styleTarget:

QtObject {
property Item styleTarget
onStyleTargetChanged: {
styleTarget.color = "lightblue"
styleTarget.radius = 5
styleTarget.border.width = 3
styleTarget.border.color= "black"
}
}

QML - required custom property

no, you can't. The most robust way is simply to give a valid default value to the property.

a workaround could be to give an invalid value (e.g -1) and check value in the Component.onCompleted slot of your item and show a console.log if property wasn't valid...

but prefer the first way, a component should always be usable with default values, for reusability goals!

Styling GroupBox in QML 2

you could do something like this:

GroupBox {
id: control
title: qsTr("GroupBox")
anchors.centerIn: parent
width: 300
height: 150

background: Rectangle {
y: control.topPadding - control.padding
width: parent.width
height: parent.height - control.topPadding + control.padding
color: "transparent"
border.color: "#21be2b"
radius: 2
}

label: Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.top
anchors.bottomMargin: -height/2
color: "white" //set this to the background color
width: parent.width * 0.7
height: title.font.pixelSize
Text {
id: title
text: qsTr("My Tilte")
anchors.centerIn: parent
font.pixelSize: 32
}
}
}

The more complicated solution if you want your label to be transparent is to use a Canvas to draw the background Rectangle:

CustomBox.qml

import QtQuick 2.7

Item {
id: box
property string borderColor: "black"
property int borderWidth: 1

onWidthChanged: canvas.requestPaint()
onHeightChanged: canvas.requestPaint()

Canvas {
id: canvas
anchors.fill: parent
antialiasing: true

onPaint: {
var ctx = canvas.getContext('2d')

ctx.strokeStyle = box.borderColor
ctx.lineWidth = box.borderWidth
ctx.beginPath()
ctx.moveTo(width*0.15, 0)
ctx.lineTo(0, 0)
ctx.lineTo(0, height)
ctx.lineTo(width, height)
ctx.lineTo(width, 0)
ctx.lineTo(width - width * 0.15, 0)
ctx.stroke()
}
}
}

You can then use it like this:

GroupBox {
id: control
title: qsTr("GroupBox")
anchors.centerIn: parent
width: 300
height: 150

background: CustomBox {
y: control.topPadding - control.padding
width: parent.width
height: parent.height - control.topPadding + control.padding
borderColor: "black"
borderWidth: 1
}

label: Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.top
anchors.bottomMargin: -height/2
color: "transparent"
width: parent.width * 0.7
height: title.font.pixelSize
Text {
id: title
text: qsTr("Settings")
anchors.centerIn: parent
font.pixelSize: 32
}
}
}

How to bind a property to a singleton object property from QML

You could try to assign the binding like this:

Component.onCompleted: Singleton.name = Qt.binding(function() { return qmlName })

It works for normal QML-Objects, not sure it works with a singleton class, though. Anyhow, you can read more about this approach in the section "Creating Property Bindings from JavaScript".

Dynamically change QML theme at runtime

I was able to make it work, thanks to other threads.

First off, create your themes like this user does, which inherit from AbstractStyle, allowing much more flexibility.

https://stackoverflow.com/a/25866188/2425044

Our property will then be defined by the value returned by a JS function:

import "qrc:/graphics/componentCreation.js" as Theme

Item
{
id: homeViewItem
anchors.centerIn: parent

// Load default theme at startup
property AbstractTheme currentTheme: Theme.createThemeObject(homeViewItem, "qrc:/redTheme/redTheme.qml");

Rectangle
{
color: currentTheme.textColorStandard;
}
}

componentCreation.js

// Create themes components and load them in the apps' QML files

var component;
var sprite;

function createThemeObject(item, themePath)
{
component = Qt.createComponent(themePath);
sprite = component.createObject(item);

if (sprite === null)
console.log("componentCreation.js: error creating " + themePath + " object");
else
return sprite;
}

Let's say you want to change theme when the user clicks on a Button:

Button
{
id: themeButton
text: "Change to blue theme"
onClicked:
{
// Remove content of redTheme.rcc, register blueTheme.rcc
cpp_class.changeTheme("redTheme", "blueTheme")
// blueTheme's content is now available, let's fill its content into a QML object
currentTheme = Theme.createThemeObject(homeViewItem, "qrc:/blueTheme/blueTheme.qml")
}
}

Remember, redTheme.qml and blueTheme.qml are contained in qrc files which are themselves compiled into rcc files.

Here's the definition of changeTheme(const QString&, const QString&), which unregisters the old theme and registers the new one:

void cpp_class::changeTheme(const QString &oldTheme, const QString &newTheme)
{
bool un = QResource::unregisterResource(QCoreApplication::applicationDirPath() + "/data/themes/" + app + "/" + oldTheme + ".rcc");
if (!un)
std::cerr << oldTheme.toStdString() << "could not be unregistered" << std::endl;
bool in = QResource::registerResource(QCoreApplication::applicationDirPath() + "/data/themes/" + app + "/" + newTheme + ".rcc");
if (!in)
std::cerr << newTheme.toStdString() << "could not be registered as an external binary resource" << std::endl;
}

Other threads that have helped me:

https://wiki.qt.io/Qml_Styling

http://www.slideshare.net/BurkhardStubert/practical-qml-key-navigation (begins at slide 34)

Is there a QML type allowing to execute certain actions once some condition became true?

There is a third party library that does that : benlau's QuickPromise

You could use it that way :

import QuickPromise 1.0
//...
Promise {
resolveWhen: someExpression
onFulfilled: arbitraryAction()
}

DateTimeEdit in QML like in Qtwidgets

There is no equivalent in QML. But, you can easily create your own widget with a TextField and a custom validator:

In QML:

TextField {
text : "01/01/1970 00:00:00"
inputMask: "99/99/9999 99:99:99"
validator: DateTimeValidator {}
}

In C++:

// datetimevalidator.h
#include <QValidator>
#include <QDateTime>

class DateTimeValidator: public QValidator
{
public:
DateTimeValidator();

State validate(QString& input, int& pos) const;
};
// datetimevalidator.cpp
#include <datetimevalidator.h>
DateTimeValidator::DateTimeValidator(): QValidator()
{}

QValidator::State DateTimeValidator::validate(QString& input, int& pos) const
{
QDateTime dt = QDateTime::fromString(input, "MM/dd/yyyy HH:mm:ss");
if (dt.isNull()) // If null, the input cannot be parsed
{
return QValidator::Invalid;
}
return QValidator::Acceptable;
}

In the main(), register your validator to be able to use it in QML:

#include "datetimevalidator.h"

int main(int argc, char** argv)
{
QApplication app(argc, argv);
qmlRegisterType<DateTimeValidator>("my.components", 1, 0, "DateTimeValidator");
...
}


Related Topics



Leave a reply



Submit