Change fusion style colors in QML dynamically - qt

I'm developing QML application using Fusion style. I'm using Qt 5.15 lts.
I want to make a function which switch color themes(light mode, dark mode, etc.) in runtime.
The documentation says that changing the color system of fusion style can be done by modifying palette object in QML.
I believe it will be easily done in ApplicationWindow type, but I have to use my custom Window object, rather than ApplicationWindow.
So I tried to it in C++, like below.
// #theme_dark_ and #theme_light_ are pre-defined palette object.
Q_INVOKABLE void Backend::SetDarkMode(const bool flag) {
qGuiApp->setPalette(flag ? theme_dark_ : theme_light_);
}
I checked setPalette method works before starting QML application(QGuiApplication::exec), but it didn't when QML app is running.
Is there any way to modify palette in Window type in QML, or calling QCoreApplication::setPalette in QML app runtime?
Thanks.

In the following example, I have a Page component where I can set the palette which will be inherited by all its children:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
palette: darkMode.checked ? darkTheme : lightTheme
Flickable {
id: flickable
anchors.fill: parent
contentWidth: columnLayout.width
contentHeight: columnLayout.height
clip: true
ScrollBar.vertical: ScrollBar {
width: 20
policy: ScrollBar.AlwaysOn
}
ColumnLayout {
id: columnLayout
width: flickable.width - 40
CheckBox {
id: darkMode
text: checked ? qsTr("Dark Mode") : qsTr("Light Mode")
checked: false
}
Label {
text: qsTr("Sample Label")
}
CheckBox {
text: qsTr("Sample CheckBox")
}
RadioButton {
text: qsTr("Sample Radio Button")
}
Button {
text: qsTr("Sample Button")
}
ComboBox {
Layout.preferredWidth: 200
model: [ "ComboValue 1", "ComboValue 2", "ComboValue 3" ]
}
Slider {
from: 0
to: 100
}
ListView {
model: 20
Layout.preferredHeight: contentHeight
delegate: ItemDelegate {
text: "ListItem " + (index + 1)
onClicked: ListView.view.currentIndex = index
}
}
}
}
Palette {
id: darkTheme
alternateBase: "#000"
base: "#000"
button: "#111"
buttonText: "#fff"
dark: "#666"
highlight: "#d73"
highlightedText: "#000"
light: "#000"
mid: "#444"
midlight: "#333"
placeholderText: "#80000000"
shadow: "#888"
text: "#fff"
window: "#222"
windowText: "#fff"
}
Palette {
id: lightTheme
alternateBase: "#fff"
base: "#fff"
button: "#eee"
buttonText: "#000"
dark: "#999"
highlight: "#38c"
highlightedText: "#fff"
light: "#fff"
mid: "#bbb"
midlight: "#ccc"
placeholderText: "#80000000"
shadow: "#777"
text: "#000"
window: "#eee"
windowText: "#000"
}
}
You can Try it Online!

Related

Qt, QML, ColorImage is not a type

I made a QML button component and I used a component named ColorImage for the icon. After searching for a way to change image color. I found out that Qt no longer support ColorOverlay
Hover, I just typed in 'color' in Qt Design Studio and ColorImage popped up. I tried to find documentation online but couldn't find anything. However, when I decided to try it, it just works as I expected:
This is the relevant code from my button:
contentItem: ColorImage {
id: buttonIcon
source: imageSource
fillMode: Image.PreserveAspectFit
height: parent.height
color: iconColor
anchors.fill: actionBarButton
anchors.margins: 4
}
When the hovered state from the button becomes true it enables the following state:
State {
when: (hovered && !checked)
name: "hoveredNotChecked"
PropertyChanges {
target: buttonIcon
color: "white"
}
PropertyChanges {
target: buttonBackground
color: iconColor
}
},
which swaps the icon and the background color on the button.
This works in the preview of the Qt Designer. However, when I try to run it from Pyside, it tells me: ColorImage is not a type and simply fails to load the button.
I tried to find documentation about ColorImage to figure out maybe there's an import missing. However, I could not turn up anything. Qt Designer's internal help did not turn up anything as well. It is as if this component doesn't exist. But it does, and it works in Design Studio.
Here is the full code for my button:
Button {
id: actionBarButton
property color iconColor: "red"
property color backgroundColor: "blue"
property string toolTipText: "Play video!"
property string imageSource: "images/round_play_arrow_white_36dp.png"
property string imageSourceChecked: "images/round_play_arrow_white_36dp.png"
states: [
State {
when: (hovered && !checked)
name: "hoveredNotChecked"
PropertyChanges {
target: buttonIcon
color: "white"
}
PropertyChanges {
target: buttonBackground
color: iconColor
}
},
State {
when: (hovered && checked)
name: "hoveredChecked"
PropertyChanges {
target: buttonIcon
source: imageSourceChecked
color: "white"
}
PropertyChanges {
target: buttonBackground
color: iconColor
}
},
State {
when: checked
name: "checked"
PropertyChanges {
target: buttonIcon
source: imageSourceChecked
}
}
]
transitions: Transition {
ColorAnimation {
duration: 300
}
}
contentItem: ColorImage {
id: buttonIcon
source: imageSource
fillMode: Image.PreserveAspectFit
height: parent.height
color: iconColor
anchors.fill: actionBarButton
anchors.margins: 4
}
onHoveredChanged: {
}
background: Rectangle {
id: buttonBackground
color: backgroundColor
anchors.fill: actionBarButton
}
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: actionBarButton.toolTipText
}
This is how it looks in the designer:
Can someone help me figure out why it complains about ColorImagenot being a type when I try to launch?
Edit:
The imports in the file above:
import QtQuick 2.15
import QtQuick.Controls 2.15
ColorImage is a Qt internal private component:
https://github.com/qt/qtdeclarative/blob/dev/src/quickcontrols2impl/qquickcolorimage.cpp
It doesn't appear to be supported for non-internal use.
If you really want to use it, try import QtQuick.Controls.impl 2.15
Note that ColorOverlay is available again in Qt 6.2 in Qt5Compat:
https://doc.qt.io/qt-6/qml-qt5compat-graphicaleffects-coloroverlay.html
It will eventually be replaced by Qt Quick MultiEffect:
https://marketplace.qt.io/products/qt-quick-multieffect

Material Style In Qml Does Not Load Colors

i used methods here to use material design in my QtQuick project and used Accent and Themes from here controls loading in material style correctly in normal qml files , but in qml files loaded by loader result is like this:
this loader is in main.qml :
Loader{
id:myLoader
anchors.fill: parent
source: "LoginPage.qml"
}
and here is my dynamic qml file
import QtQuick 2.8
import QtQuick.Controls 2.1
import QtQuick.Controls.Material 2.3
import QtGraphicalEffects 1.0
Rectangle{
Material.theme: Material.Dark
Material.accent: Material.Teal
property string error_msg: ""
id: loginPage
Button {
id: button
width: 80
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 0
font.family: "B Nazanin"
enabled: webView.loadProgress == 100 ? true:false
KeyNavigation.tab: button1
Material.accent: Material.Orange
onClicked: {
login()
}
}
}
The dark there is presumed to have a dark background. It will not set your window background for you, it only affects GUI controls. And the button in particular doesn't use use the accent color, just the foreground color, unless toggle is enabled, in which case it will use the accent color to signify that.
Material.theme: Material.Dark
Material.accent: Material.Teal
Rectangle {
anchors.fill: r2
color: "#262626"
}
Row {
id: r1
Button {
text: "test"
checkable: true
Material.accent: Material.Orange
}
Button {
text: "test"
checkable: true
}
}
Rectangle {
anchors.fill: r2
}
Row {
id: r2
anchors.top: r1.bottom
Button {
text: "test"
checkable: true
Material.accent: Material.Orange
}
Button {
text: "test"
checkable: true
}
}
As you see, on a white background, the dark theme button looks blurry and washed out. If you want to set the button color, that would be the Material.background whereas the text would be Material.foreground.
I had a similar problem.
Just use ApplicationWindow as your root element and it will work.
And additionally you have to load Material before instantiating QML.
QGuiApplication application(argc, argv);
QQmlApplicationEngine engine;
QQuickStyle::setStyle("Material");

Dynamic validator for TextField / Change validator at runtime

I have small problem/requirement about validators, which i'm currently not able to implement.
I have a simple TextField (QtQuick.Control 2) which is decorated with different colors depending on a state. It should also use a different RegExpValidator (with another regular expression) depending on the current state.
Does anyone have an idea how i can switch/change/modify a RegExpValidator at runtime? (e.g. when a PushButton is pressed or onEditingFinished-Event is triggered)
My current qml code is:
import QtQuick 2.3
import QtQuick.Controls 2.2 as Quick
import QtQuick.Layouts 1.3
RowLayout {
id: layout
property color modeColor: "whitesmoke"
property color modeTextColor: "gray"
spacing: 0
Rectangle {
id: rect
Layout.fillWidth: true
Layout.minimumWidth: 100
Layout.preferredWidth: 100
Layout.maximumWidth: 100
Layout.preferredHeight: layout.implicitHeight
color: modeColor
border.width: 1
border.color: modeColor
Text {
id: recttext
anchors.centerIn: parent
text: "Enter key"
color: modeTextColor
}
}
Quick.TextField {
id: input
Layout.fillWidth: true
placeholderText: "Text"
background: Rectangle {
color: "whitesmoke"
border.width: 1
border.color: modeColor
}
validator: RegExpValidator { regExp: /.*:$/ }
onEditingFinished: {
recttext.text = input.text
if (layout.state == "keyinput") {
layout.state = "valinput"
// should change to another regExp validator
} else {
layout.state = "keyinput"
// should change to another regExp validator
}
input.clear()
}
Keys.onPressed: {
if (event.key == Qt.Key_Escape) {
layout.state = "keyinput"
recttext.text = "Enter key"
input.clear()
}
}
}
states: [
State {
name: "keyinput"
PropertyChanges { target: layout; modeColor: "whitesmoke"; modeTextColor: "gray" }
},
State {
name: "valinput"
PropertyChanges { target: layout; modeColor: "red"; modeTextColor: "white" }
}
]
state: "keyinput"
}
Best Regards, Chris
edit:
The endgoal is an inputline for entering key-value pairs (where i could use a specific RegExpValidator for a specific key.)
Here it should use RegExpValidator for entering a key e.g. "author:" + Pressing Enter (keyinput-mode)
#
After switching into the valueinput-mode, it should use another RegExpValidator:
btw. It's a desktop application :)
I hope this can help you!
property var valid1 : IntValidator { bottom:0; top: 2000}
property var valid2 : IntValidator { bottom:2000; top: 4000}
...
validator: if(condition) { valid1 }
else { valid2 }

Menu / sidebar with interactive buttons inside item

A picture is worth thousand words. This is menu / sidebar which I want to get:
Note that it is fake. This is just static QTreeWidget preparation with 3 columns, colspan and header hidden.
Nedded features:
Grayed upper case items should not be possible to select (partly could be solved by QTreeWidgetItem::setFlags(ItemIsSelectable))
Icons on the right side (open, eject) should be interactive. I mean click signal and cursor should change to "hand" when mouse is over these icons
When resizing menu / sidebar using QSplitter then icons on the right side should anchor to the right edge
I'm beginner in Qt Framework but not such lame. I studied examples to find interesting solutions but I'm not sure which will be the easiest:
Play with QGraphicsView
Create QToolButton descendant and try to add child buttons. Then put everything into QWidget, add labels, layout and prepare stylesheet to imitate QTreeWidget
Create QTreeWidget descendant and play with painter, mouse move event etc.
Any suggestions or other solutions? Never tried QML / QtQuick, had no time to learn it but maybe I could use QDeclarativeView or QQuickWidget
Use Drawers and Listview in conjunction to get your results. QML Drawers are the slidable widgets which you can program to open() on a click or a press event.
Here is the official write up on QML Drawer
https://doc-snapshots.qt.io/qt5-5.7/qml-qtquick-controls2-drawer.html
The following example from Qt runs exactly what is mentioned in the question.
http://doc.qt.io/qt-5/qtquickcontrols2-gallery-example.html
Taken straight from the code just to give you an Idea :
import QtQuick 2.6
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.0
import QtQuick.Controls.Material 2.0
import QtQuick.Controls.Universal 2.0
import Qt.labs.settings 1.0
ApplicationWindow {
id: window
width: 360
height: 520
visible: true
title: "Qt Quick Controls 2"
Settings {
id: settings
property string style: "Default"
}
header: ToolBar {
Material.foreground: "white"
RowLayout {
spacing: 20
anchors.fill: parent
ToolButton {
contentItem: Image {
fillMode: Image.Pad
horizontalAlignment: Image.AlignHCenter
verticalAlignment: Image.AlignVCenter
source: "qrc:/images/drawer.png"
}
onClicked: drawer.open()
}
Label {
id: titleLabel
text: "Gallery"
font.pixelSize: 20
elide: Label.ElideRight
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
Layout.fillWidth: true
}
ToolButton {
contentItem: Image {
fillMode: Image.Pad
horizontalAlignment: Image.AlignHCenter
verticalAlignment: Image.AlignVCenter
source: "qrc:/images/menu.png"
}
onClicked: optionsMenu.open()
Menu {
id: optionsMenu
x: parent.width - width
transformOrigin: Menu.TopRight
MenuItem {
text: "Settings"
onTriggered: settingsPopup.open()
}
MenuItem {
text: "About"
onTriggered: aboutDialog.open()
}
}
}
}
}
Drawer {
id: drawer
width: Math.min(window.width, window.height) / 3 * 2
height: window.height
ListView {
id: listView
currentIndex: -1
anchors.fill: parent
delegate: ItemDelegate {
width: parent.width
text: model.title
highlighted: ListView.isCurrentItem
onClicked: {
if (listView.currentIndex != index) {
listView.currentIndex = index
titleLabel.text = model.title
stackView.replace(model.source)
}
drawer.close()
}
}
model: ListModel {
ListElement { title: "BusyIndicator"; source: "qrc:/pages/BusyIndicatorPage.qml" }
ListElement { title: "Button"; source: "qrc:/pages/ButtonPage.qml" }
ListElement { title: "CheckBox"; source: "qrc:/pages/CheckBoxPage.qml" }
ListElement { title: "ComboBox"; source: "qrc:/pages/ComboBoxPage.qml" }
ListElement { title: "Dial"; source: "qrc:/pages/DialPage.qml" }
ListElement { title: "Delegates"; source: "qrc:/pages/DelegatePage.qml" }
ListElement { title: "Drawer"; source: "qrc:/pages/DrawerPage.qml" }
ListElement { title: "Frame"; source: "qrc:/pages/FramePage.qml" }
ListElement { title: "GroupBox"; source: "qrc:/pages/GroupBoxPage.qml" }
ListElement { title: "Menu"; source: "qrc:/pages/MenuPage.qml" }
ListElement { title: "PageIndicator"; source: "qrc:/pages/PageIndicatorPage.qml" }
ListElement { title: "Popup"; source: "qrc:/pages/PopupPage.qml" }
ListElement { title: "ProgressBar"; source: "qrc:/pages/ProgressBarPage.qml" }
ListElement { title: "RadioButton"; source: "qrc:/pages/RadioButtonPage.qml" }
ListElement { title: "RangeSlider"; source: "qrc:/pages/RangeSliderPage.qml" }
ListElement { title: "ScrollBar"; source: "qrc:/pages/ScrollBarPage.qml" }
ListElement { title: "ScrollIndicator"; source: "qrc:/pages/ScrollIndicatorPage.qml" }
ListElement { title: "Slider"; source: "qrc:/pages/SliderPage.qml" }
ListElement { title: "SpinBox"; source: "qrc:/pages/SpinBoxPage.qml" }
ListElement { title: "StackView"; source: "qrc:/pages/StackViewPage.qml" }
ListElement { title: "SwipeView"; source: "qrc:/pages/SwipeViewPage.qml" }
ListElement { title: "Switch"; source: "qrc:/pages/SwitchPage.qml" }
ListElement { title: "TabBar"; source: "qrc:/pages/TabBarPage.qml" }
ListElement { title: "TextArea"; source: "qrc:/pages/TextAreaPage.qml" }
ListElement { title: "TextField"; source: "qrc:/pages/TextFieldPage.qml" }
ListElement { title: "ToolTip"; source: "qrc:/pages/ToolTipPage.qml" }
ListElement { title: "Tumbler"; source: "qrc:/pages/TumblerPage.qml" }
}
ScrollIndicator.vertical: ScrollIndicator { }
}
}
ToolButton with drawer.png as Image, is programmed to open and close the sidebar.
I hope this helps.
if you decide to go QML (which I would recommend, unless you have specific reasons not to) here's what you could do.
Step one: Use a ListView with section headers: http://qmlbook.github.io/en/ch06/index.html#lists-with-sections
Step two: Create an item delegate with multiple actions / subitems.
Just take a look at the Qml docs and examples or the QML Book linked above to understand the basics of the model, ListView and delegate concepts:
http://qmlbook.github.io/en/ch06/index.html#
As to anchoring, you can do that pretty easily too by using the anchors property and the many layout options QML gives you:
http://qmlbook.github.io/en/ch04/index.html#positioning-elements
Hope this helps.

How to ensure Button has focus when QML tab is activated, rather than TextField?

In QML, I have a Tab containing a TextField and a Button. How do I ensure the Button has focus when the tab is selected, instead of the TextField? Setting "focus:" to true and false, respectively, does not do it. In the code below, the goal is for btnRefresh to have focus when a tab is selected, instead of txtName.
main.qml:
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.2 // For TabViewStyle
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
TabView {
id: tabView
anchors.fill: parent
anchors.margins: 20
tabPosition: Qt.BottomEdge
Tab {title: "Tab 1"; source: "mytab.qml"}
Tab {title: "Tab 2"; source: "mytab.qml"}
style: TabViewStyle {
frameOverlap: 1
tab: Rectangle {
color: styleData.selected ? "steelblue" :"lightsteelblue"
border.color: "steelblue"
implicitWidth: Math.max(text.width + 4, 80)
implicitHeight: 20
radius: 2
Text {
id: text
anchors.centerIn: parent
text: styleData.title
color: styleData.selected ? "white" : "black"
}
}
frame: Rectangle { color: "steelblue" }
}
}
}
mytab.qml:
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
Rectangle {
anchors.fill: parent
GridLayout {
columns: 2
anchors.fill: parent
rowSpacing: 10
RowLayout {
Layout.columnSpan: 2
Label {
id: lblName
text: "Name:"
}
TextField {
id: txtName;
text: "a name"
Layout.preferredWidth: lblName.implicitWidth * 1.5;
focus: false
}
}
TextArea {
id: textSetup
text: "Text Area"
Layout.columnSpan: 2
Layout.fillWidth: true
Layout.fillHeight: true
}
Button {
id: btnRefresh
Layout.columnSpan: 2
Layout.alignment: Qt.AlignHCenter
text: qsTr("Refresh")
focus: true
}
}
}
Whenever you switch tabs in a TabView, a signal handler onVisibleChanged is called on the two tabs (one that disappeared and the one that appeared) since the visibility of these tabs has changed. You can try adding following code to your Tabs:
Tab {
id: tab1
title: "Tab 1"; source: "mytab.qml"
onVisibleChanged: {
if (visible) tab1.item.funcSetFocusOnButton();
}
}
Please note the way a function is called on a tab using item.
Now in "mytab.qml", you create a javascript function funcSetFocusOnButton which sets focus on your button. So your mytab.qml will have this additional code:
Rectangle {
//Your GridLayout{}
funcSetFocusOnButton() {
btnRefresh.forceActiveFocus();
}
}
Note here that the function funcSetFocusOnButton should be a direct child of your base item (rectangle here). Hope this helps!

Resources