What is the best way to handle theme changes in QML?
I noticed some controls like Switch And ApplicationWindow do this automatically, but others like Text and Rectangle just don't!
Is it at all possible to avoid having to check which theme is currently set and then each time set the color accordingly? (color: theme.position < 1 ? "black" : "white")
main.qml
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Controls.Material 2.13
//ok: the background color changes automatically when the theme changes
ApplicationWindow {
id: root
visible: true
width: 1366
height: 768
title: qsTr("Theme")
Material.theme: theme.position < 1 ? Material.Light : Material.Dark
//ok: the text color changes automatically when the theme changes
Switch {
id: theme
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 10
text: "Dark theme"
checked: false
}
//not ok: the background is always white
Rectangle {
anchors.centerIn: parent
width: 200
height: width
//not ok: the color is always black
Text {
anchors.centerIn: parent
text: "some text"
font.pixelSize: 40
}
}
}
qtquickcontrols2.conf
[Controls]
Style=Material
[Material]
Theme=Dark
Accent=Orange
Primary=BlueGrey
Text and Rectangle are primitives from Qt Quick, which means that they don't understand Qt Quick Controls' Material style colour propagation. You can use Label and Frame instead:
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Controls.Material 2.13
ApplicationWindow {
id: root
visible: true
width: 1366
height: 768
title: qsTr("Theme")
Material.theme: theme.position < 1 ? Material.Light : Material.Dark
Switch {
id: theme
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 10
text: "Dark theme"
checked: false
}
Frame {
anchors.centerIn: parent
width: 200
height: width
Label {
anchors.centerIn: parent
text: "some text"
font.pixelSize: 40
}
}
}
Note that Frame will consume mouse events, so if you don't want that, you'll need to use e.g. Control, and handle the colours yourself using the Material style's attached properties:
Control {
anchors.centerIn: parent
width: 200
height: width
background: Rectangle {
color: parent.Material.background
border.color: parent.Material.foreground
}
Label {
anchors.centerIn: parent
text: "some text"
font.pixelSize: 40
}
}
Related
I'm being tasked with creating a customized title bar for our application. It needs to have rounded corners and a settings button, amongst other things. It will run exclusively on windows.
Our application uses Qt and QML for the front end.
So the only way I could find how to do this is by making the application window frameless and creating the title bar from scratch.
This is my test code:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.0
ApplicationWindow {
id: mainWindow
visible: true
visibility: Window.Maximized
title: qsTr("Hello World")
flags: Qt.FramelessWindowHint | Qt.Window | Qt.WA_TranslucentBackground
//flags: Qt.Window | Qt.WA_TranslucentBackground
color: "#00000000"
TitleBar {
id: mainTitleBar
width: mainWindow.width;
height: mainWindow.height*0.018
color: "#aaaaaa"
onCloseApplication: {
Qt.quit();
}
onMinimizeApplication: {
mainWindow.visibility = Window.Minimized
}
}
Component.onCompleted: {
console.log("Size: " + mainWindow.width + "x" + mainWindow.height)
mainTitleBar.width = mainWindow.width
mainTitleBar.height = mainWindow.height*0.023;
}
Rectangle {
id: content
width: mainWindow.width
height: mainWindow.height - mainTitleBar.height
anchors.top: mainTitleBar.bottom
anchors.left: mainTitleBar.left
color: "#00ff00"
}
}
And
Here is the title bar code (TitleBar.js file):
import QtQuick 2.0
import QtQuick.Controls 2.0
Rectangle {
/*
* Requires setting up of
* -> width
* -> height
* -> title text
* -> icon path.
* -> Background color.
*/
id: vmWindowTitleBar
border.width: 0
x: 0
y: 0
radius: 20
signal closeApplication();
signal minimizeApplication();
// The purpose of this rectangle is to erase the bottom rounded corners
Rectangle {
width: parent.width
height: parent.height/2;
anchors.bottom: parent.bottom
anchors.left: parent.left
border.width: 0
color: parent.color
}
Text {
id: titleBarText
text: "This is The Title Bar"
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: parent.width*0.018
}
Button {
id: minimizeButton
width: height
height: vmWindowTitleBar.height*0.8
anchors.right: closeButton.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: parent.width*0.018
background: Rectangle {
id: btnMinimizeRect
color: vmWindowTitleBar.color
anchors.fill: parent
}
onPressed:{
minimizeApplication()
}
scale: pressed? 0.8:1;
contentItem: Canvas {
id: btnMinimizeCanvas
contextType: "2d"
anchors.fill: parent
onPaint: {
var ctx = btnMinimizeCanvas.getContext("2d");
var h = minimizeButton.height;
var w = minimizeButton.width;
ctx.reset();
ctx.strokeStyle = minimizeButton.pressed? "#58595b": "#757575";
ctx.lineWidth = 6;
ctx.lineCap = "round"
ctx.moveTo(0,h);
ctx.lineTo(w,h);
ctx.closePath();
ctx.stroke();
}
}
}
Button {
id: closeButton
//hoverEnabled: false
width: height
height: vmWindowTitleBar.height*0.8
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: parent.width*0.018
background: Rectangle {
id: btnCloseRect
color: vmWindowTitleBar.color
anchors.fill: parent
}
onPressed:{
closeApplication()
}
scale: pressed? 0.8:1;
Behavior on scale{
NumberAnimation {
duration: 10
}
}
contentItem: Canvas {
id: btnCloseCanvas
contextType: "2d"
anchors.fill: parent
onPaint: {
var ctx = btnCloseCanvas.getContext("2d");
var h = closeButton.height;
var w = closeButton.width;
ctx.reset();
ctx.strokeStyle = closeButton.pressed? "#58595b": "#757575";
ctx.lineWidth = 2;
ctx.lineCap = "round"
ctx.moveTo(0,0);
ctx.lineTo(w,h);
ctx.moveTo(w,0);
ctx.lineTo(0,h);
ctx.closePath();
ctx.stroke();
}
}
}
}
Now the problem comes with minimizing the application. The first thing I realize is that when using the Qt.FramelessWindowHint flag, the icon does not appear in the Windows Taskbar. Furthermore if I minimize it this happens:
And If I click on it, it doesn't restore.
So my question is, is there a way to reproduce regular minimize behavior when pressing the minimize button?
Or alternatively, is there a way I can completely customize the title bar of the application so that I can achieve the look and feel set by our UI designer?
NOTE: The current look is just a quick test. I have not set the gradient, font, or the aforementioned settings button.
As for me, playing with frameless windows and transparent background is kind of workaround. As I know, the only way to apply a custom shape to the window is QWindow::setMask. Sinse Window is derived from QWindow you can do that in this way.
For example, in the main.cpp:
QWindow *wnd = qobject_cast<QWindow *>(engine.rootObjects().at(0));
auto f = [wnd]() {
QPainterPath path;
path.addRoundedRect(QRectF(0, 0, wnd->geometry().width(), wnd->geometry().height()), 30, 30);
QRegion region(path.toFillPolygon().toPolygon());
wnd->setMask(region);
};
QObject::connect(wnd, &QWindow::widthChanged, f);
QObject::connect(wnd, &QWindow::heightChanged, f);
f();
Since you 'cut' the shape from the window itself, excluding title bar and frames you can leave the window flags as is.
Look at this way, I try to create something that you do but change completely your code.
the problem that makes change in your window size after you minimize the window is that you didn't set the initial width and height for the window so when you minimize the app it shows in the minimum width and height.
so you need to add just this in main.qml and set the initial width and height to the maximum.
width: maximumWidth
height:maximumHeight
but In the code below I change something else too.
For example, you didn't need to emit signals and then catch them in main.qml
you have access to mainWindow in TitleBar.qml.
in TitleBar.qml :
import QtQuick 2.0
import QtQuick.Controls 2.0
Rectangle {
anchors.fill: parent
height: 30
Row {
id: row
anchors.fill: parent
Label {
id: label
text: qsTr("Title ")
}
Button {
id: button
x: parent.width -80
text: qsTr("close")
onClicked:
{
mainWindow.close()
}
}
Button {
id: button1
x: parent.width -160
width: 90
text: qsTr("Minimized")
onClicked:
{
mainWindow.showMinimized()
}
}
}
}
and in main.qml :
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.0
import "."
Window {
id: mainWindow
visible: true
visibility: Window.FullScreen
title: qsTr("Hello World")
flags: Qt.FramelessWindowHint | Qt.Window | Qt.WA_TranslucentBackground
width: maximumWidth
height:maximumHeight
Rectangle {
id: content
anchors.fill: parent
x: 0
y: 20
width: mainWindow.width
height: mainWindow.height - mainTitleBar.height
anchors.top: mainTitleBar.bottom
anchors.left: mainTitleBar.left
color: "#00ff00"
}
TitleBar {
id: mainTitleBar
color: "#aaaaaa"
anchors.bottomMargin: parent.height -40
anchors.fill: parent
}
}
here is what i did i have a text area inside a scrollview and a button at the bottom.
but when i try to overflow the text area by typing words so it will exceed the rectangle, the scrollbar does not appear, also when typing and pressing enter until the bottom of the Rectangle is reached, the vertical scrollbar does not appear as well.
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
Window {
id: mainWindow
width: 500
height: 460
visible: true
title: qsTr("Edit Markdown source")
flags: Qt.WindowCloseButtonHint | Qt.CustomizeWindowHint | Qt.Dialog | Qt.WindowTitleHint
color: "red"
Column{
id: cols
anchors.fill: parent
anchors.margins: 5
spacing: 3
Rectangle {
id: frame2
width: parent.width
height: 400
border.color: 'gray'
border.width: 1
clip: true
color: "blue"
ScrollView {
id: view
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AsNeeded
TextArea {
text: ""
color: "white"
font.family: "Helvetica Neue"
font.pixelSize: 15
width: 10
height: 10
}
}
}
Rectangle{
id:saveRec
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 20
width: 80
height: 40
color: Qt.rgba(62/255,138/255,204/255,1)
radius: 4
Text{
anchors.centerIn: parent
text:"Save"
color:"white"
font.family: fontName
font.pixelSize: 15
}
MouseArea{
id:saveMouse
hoverEnabled:true
anchors.fill: parent
onEntered: {
saveRec.opacity = 0.5
}
onExited: {
saveRec.opacity = 1
}
onClicked:{
//...
}
}
}
}
}
All you're missing is that your ScrollView has no defined size. If you tell it how big it is, the scrollbars will get drawn. I'm not sure why you're setting the TextArea's height/width to be 10, but in my test it worked with or without those lines.
ScrollView {
id: view
anchors.fill: parent // Define the ScrollView's size
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AsNeeded
TextArea {
text: ""
color: "white"
font.family: "Helvetica Neue"
font.pixelSize: 15
// width: 10 // Not needed
// height: 10 // Not needed
}
}
Trying different code combinations and partially solving my problem I came across a behavior that I can not quite explain. So to the point, When I create a simple TextArea without Scrollview it looks like this:
RowLayout {
id: rowLayout
Rectangle{
height: 50
width: 295
TextArea {
id: textArea
text: (" message...")
wrapMode: Text.WrapAnywhere
anchors.fill: parent
}
}
Text area creates a default background. And now I want to do TextArea with ScrollView ALSO with the default TextArea background but it comes out something like that :
RowLayout {
id: rowLayout
Rectangle{
height: 50
width: 295
ScrollView {
id: scrollView1
anchors.fill: parent
TextArea {
id: textArea
text: (" message...")
wrapMode: Text.WrapAnywhere
}
}
}
The only chance to set the default TextArea background is set implicitHeight,implicitWidth but then after entering the text into a TextArea until the scrollbar appears, the background extends over the entire length by going behind the other components like this :
RowLayout {
id: rowLayout
Rectangle{
//color: "#00000000"
height: 50
width: 295
ScrollView {
id: scrollView1
anchors.fill: parent
TextArea {
id: textArea
text: (" message...")
wrapMode: Text.WrapAnywhere
implicitHeight: 50
implicitWidth: 295
}
}
}
So the only thing I want is a scrollable textarea but with this black default background and NOT my background which I can do with rectangle.
Can anyone take a look?
Thank you :)
I tried do my best. Check the example below, hope it will help =)
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 400
height: 400
RowLayout {
width: 295
height: 50
anchors.centerIn: parent
ScrollView {
Layout.fillHeight: true
Layout.fillWidth: true
background: Rectangle { color: "black" }
TextArea {
id: messageField
placeholderText: qsTr("message...")
color: "white"
wrapMode: TextArea.WrapAnywhere
}
}
}
}
Result:
In the example below the Button component doesn't work because Drawer dragMargin overlap it.
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
id: window
visible: true
width: 640
height: 480
title: qsTr("Drawer example")
Drawer {
id: menu
dragMargin: 60
width: window.width * 0.85
height: window.height
background: Rectangle {
color: "blue"
}
}
Button {
id: log
text: "Click me!"
anchors.top: parent.top
anchors.left: parent.left
onClicked: {
console.log("Clicked!");
}
}
}
Is there a way to fix this issue? I tried to change z properties but it doesn't work.
I found this link on the Qt forum. It seems that the problem described is an open issue to be resolved by Qt.
Is possible to create a custom style file in QT and apply it for all QML files (like CSS does applies to HTML )?
Is there any "class" declaration for QML ?
If you want to declare a "class" in QML you have to create new QML file. Its name must begin with a capital letter. You can also create custom objects using C++ but probably that is not what you are looking for.
Let's say you want to create custom Text element so the text is always centered and fits the given dimensions. So you create a file named CustomText.qml and write:
/* CustomText.qml file */
import QtQuick 2.0
Text {
id: customText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
clip: true
fontSizeMode: Text.Fit
font.pixelSize: height
wrapMode: Text.WordWrap
minimumPixelSize: 3
color: "black"
/* using these lines you can set custom font loaded from a file */
// font.family: customFont.name
// FontLoader {
// id: customFont
// source: "qrc:/myCustomFont.ttf"
// }
}
Now you can use it like this:
/* main.qml file */
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
width: 300
height: 300
Rectangle {
id: rectangle1
color: "lightgrey"
x: 5
y: 5
width: 200
height: 50
CustomText {
anchors.fill: parent
text: "testing custom text object"
}
}
Rectangle {
id: rectangle2
color: "lightgrey"
anchors.left: rectangle1.left
anchors.top: rectangle1.bottom
anchors.topMargin: 5
width: 50
height: 50
CustomText {
anchors.fill: parent
text: "testing custom text object"
}
}
Rectangle {
id: rectangle3
color: "lightgrey"
anchors.left: rectangle2.left
anchors.top: rectangle2.bottom
anchors.topMargin: 5
width: 100
height: 100
CustomText {
anchors.fill: parent
text: "testing custom text object"
}
}
}
That is how it would look like: