Proper way to set a child item property in QML - qt

Everyone knows about how to set a background for Buttons, Popups etc via the background property of these elements. But I am wondering, how can I create such a property myself for my own custom elements? I found a way but it looks pretty ugly and I can't seem to find the qml code for Button, Popup etc where said property is defined. So i played a bit and came up with the idea of using Bindings like this:
Item {
id: root
property Item background: Rectangle {
color: "red"
}
Binding {
target: root.background
property: "parent"
value: root
}
Binding {
target: root.background
property: "anchors.fill"
value: root
when: root.background.parent == root
delayed: true
}
}
As mentioned that looks pretty tiresome if you need to declare a lot of properties of the child. So, how does Qt do it or what is the proper way of doing it?

// ItemWithBackground.qml
Item {
property alias background: backgroundLoader.sourceComponent
Loader {
id: backgroundLoader
anchors { fill: parent }
sourceComponent: Rectangle { color: 'red' } // default background implementation
}
}
// Usage example:
ItemWithBackground {
background: Rectangle {
color: 'green'
}
}

If you’re on a recent Qt version, have a look at using inline components. They allow you to create API’s like this easily.

Related

Resize the cursor

I'm building a drawing application (Like paint or Sketchpad) and I need to resize my cursor depending on of the line width of the pencil. The problem is, apparently, you can't resize your cursor. The solution that I found is to use a custom cursor (the normal cursor is changed for an image) and to resize the image. The thing is, I don't know if I need a function to do that or I can directly change the size of the image via SCSS (CSS).
Here's what I've done so far:
private setCursor(cursorType: DrawingCursor): void {
this.Canvas.setAttribute("style", "cursor:url(" + cursorType + "), auto;");}
The cursorType is the url of the custom cursor.
I'm doing this from an angular 8 project (in Typescript).
Thank you !
You can use NgClass binding to implement it. This way the Angular binding takes care of applying the CSS classes so you don't need to set styles manually.
To implement this solution first define the CSS classes for the canvas and the different cursor sizes. For example:
.myCanvas {
width: 400px;
height: 400px;
background-color: green;
}
.brush18px {
cursor: url('brush18px.png'), auto;
}
.brush24px {
cursor: url('brush24px.png'), auto;
}
.brush36px {
cursor: url('brush36px.png'), auto;
}
Then in the component you define a property that will provide the classes for the canvas and a property for the size of the brush. For example:
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
#Input() cursorSize = '18';
get canvasClasses() {
return {
myCanvas: true,
brush18px: this.cursorSize === '18',
brush24px: this.cursorSize === '24',
brush36px: this.cursorSize === '36',
};
};
}
The canvasClasses property must return an object that specifies for each CSS class name if it is applied or not by setting the value to true or false. I use a getter property here so the values update automatically when the size changes.
Now you can bind this in the template and the cursor will automatically update based on the cursorSize value.
Here is how the template looks like:
<canvas [ngClass]="canvasClasses"></canvas>
<br/>
<select [(ngModel)]="cursorSize">
<option>18</option>
<option>24</option>
<option>36</option>
</select>
I have created also a working StackBlitz sample so you can see it in action.
Another possible solution based on the comment on the first answer. This is more of a workaround solution where you hide the actual cursor and move around an image instead so it looks like it is the cursor. This was inspired by the second answer on this post about resizing a cursor image.
I implemented this solution in Angular using a directive that you add to the canvas element in the template. The directive takes as the main parameter the image to show as the cursor and has an additional parameter for the size for the image. I recommend an SVG image that resizes well. Still it is possible to use also a standard image.
Here is the implementation of the Directive for an SVG image:
#Directive({
selector: '[svgCursor]'
})
export class SvgCursorDirective {
private cursorSizeValue: number = 16;
#Input('svgCursor') public svgImage: SVGSVGElement;
#Input() public set cursorSize(cursorSize: number) {
this.cursorSizeValue = cursorSize;
this.updateCursorSize();
}
constructor(el: ElementRef) {
el.nativeElement.style.cursor = 'none'; // hides the browser cursor
}
#HostListener('mouseenter') onMouseEnter() {
// makes image visible only when mous enters the element
this.svgImage.style.visibility = 'visible';
}
#HostListener('mousemove', ['$event']) onMouseMove(e: MouseEvent) {
// draws the image at the mouse position
this.svgImage.style.left = e.clientX.toString();
this.svgImage.style.top = (e.clientY - this.cursorSizeValue).toString();
}
#HostListener('mouseleave') onMouseLeave() {
// hides image when the mouse leaves the element
this.svgImage.style.visibility = 'hidden';
}
private updateCursorSize() {
if (this.svgImage != null) {
this.svgImage.style.width = this.cursorSizeValue.toString();
this.svgImage.style.height = this.cursorSizeValue.toString();
}
}
}
Once you have the directive you can use it in the following way inside a component template:
<svg #cursorImage class="cursor" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M7 14c-1.66 0-3 1.34-3 3 0 1.31-1.16 2-2 2 .92 1.22 2.49 2 4 2 2.21 0 4-1.79 4-4 0-1.66-1.34-3-3-3zm13.71-9.37l-1.34-1.34c-.39-.39-1.02-.39-1.41 0L9 12.25 11.75 15l8.96-8.96c.39-.39.39-1.02 0-1.41z"/></svg>
<canvas class="myCanvas" [svgCursor]="cursorImage" [cursorSize]="cursorSize"></canvas>
As you can see you need to add a template reference variable to the image so you can pass it as a parameter to the svgCursor directive.
Also important for this to work you need to set the correct CSS styles to the image to disable things that are not needed. It is also set to invisble so it becomes visible only when the mouse enters te canvas.
This are the styles I used:
.myCanvas {
width: 400px;
height: 400px;
background-color: lightgreen;
}
.cursor {
position: absolute;
cursor: none;
pointer-events: none;
visibility: hidden;
}
I have created also a working StackBlitz sample so you can see how this works.

In QML, how can I create a parent Component that takes child Component instances and puts them in the parent Components hierarchy?

Qt's QML has a number of widgets where you instantiate the widget and then give it an instance of a Component for it to display and use.
For example, the Popup widget can be used like:
Popup { // <-- Parent Component
Rectangle { // <-- Child Component
width: 100;
height: 100;
color: 'red';
}
}
How can I do this myself? How can I write a custom QML Component that will take a child Component instance and put it beneath my tree?
If I have a silly Component like:
// File Parent.qml
Rectangle {
default property var child_inst;
width: child_inst.width + 20;
width: child_inst.width + 20;
color:'red';
child_inst; // <-- how is this done?
}
and used like:
Parent {
Text { text: 'hello world!' }
}
What is the syntax or mechanism by which I can instantiate or move the child_inst to be a child of my Parent?
Wow, that's hella confusing way to put it, but if I understand correctly, you want to have children actually redirected to some other object automatically. This is possible, for example:
// Obj.qml
Rectangle {
width: 50
height: 50
default property alias content: row.children
Row {
id: row
}
}
and then:
Obj {
Rectangle {
width: 10
height: 10
color: "red"
}
Rectangle {
width: 10
height: 10
color: "green"
}
Rectangle {
width: 10
height: 10
color: "blue"
}
}
The result is that the rgb rectangles are displayed in a row, because even though they are nested in Obj, they are internally delegated to the row.
Using a default property means that you don't have to specify it manually. Of course, if you don't want that, you can even use multiple different placeholders, for example, you can have:
property alias content: contentItem.children // this is a column of contents
property alias control: buttons.children // this is a row of buttons
Keep in mind that if you don't use the default property, you will have to specify the objects as a comma separated list in the case they are multiple:
Obj {
content : [
Rectangle {
width: 10
height: 10
color: "red"
},
Rectangle {
width: 10
height: 10
color: "green"
},
Rectangle {
width: 10
height: 10
color: "blue"
}
]
}
This is not necessary if it is a single object, it can be created like this:
Obj {
content : Rectangle {
width: 10
height: 10
color: "green"
}
}
But you can also use a single target and have your row nest the objects externally, which can save you having to bother with the array notation and gives you more flexibility:
Obj {
content : Row {
// bunch of stuff here
}
}
In this case, the content can be a simple positioned Item, that will serve as a placeholder, as it is the case for the popup component.
And finally, you can also use a Loader if you want to specify an inline component, that is one that is not defined in a dedicated QML file but using the Component element.

How to define gradient color globally?

I have Gradient in my QML as shown below example. Is it possible to define color "#1AD6FD" or "#1D62F0" globally in QML?
gradient: Gradient {
GradientStop { position: 0.00; color: "#1AD6FD" }
GradientStop { position: 1.00; color: "#1D62F0" }
}
You can define custom properties on any QML object like this.
Rectangle {
id: root
// you can define your on custom properties like this
property color gradientColor1: "#1AD6FD"
property color gradientColor2: "#1D62F0"
}
And then use it in any other item that has access to the object where it is defined via the id.
Item {
gradient: Gradient {
GradientStop { position: 0.00; color: root.gradientColor1 }
GradientStop { position: 1.00; color: root.gradientColor1 }
}
}
You could also define the whole gradient globally if you like to reuse that.
If you want access to the properties from other QML files there are multiple ways to do that. If you have access to the QML root object you can also access its properties from other files or you can define a global JavaScript library with some constants for color values, but not whole QML objects in there I think (only pure JS).
Another simple way is to define an independent QML object with just some global properties for your project and include that in your other QML files with just an id to reference it, that is maybe the easiest solution.
Just define an empty QML-Item with some properties like the Rectangle example above and include it in your other QML files like this
Rectangle {
id: root
// include your custom QML global item (GlobalProperties.qml)
GlobalProperties {
id: globalProperties
}
// then you can use it like before, i.e.
color: globalProperties.gradientColor1 // color prop from your GlobalProperties.qml
}

Qt Quick Controls Styles ReferenceError

I want to isolate the style of my components in a file like "ComponentStyle.qml" (Qt5)
ComponentSyle.qml
Item {
id:root
Component {
id: touchStyle
ButtonStyle {
....
}
}
Component {
id: switchStyle
SwitchStyle {
....
}
}
}
So, in my main.qml, I want to use the style like :
main.qml
...
Button {
style: touchStyle
text: "Press me"
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
}
But, it wont work ! I have some errors :
file:///C:/Qt/5.2.0/msvc2012/qml/QtQuick/Controls/Switch.qml:133: TypeError: Type error
file:///C:/Qt/5.2.0/msvc2012/qml/QtQuick/Controls/Button.qml:92: TypeError: Cannot read property 'width' of null
main.qml:60: ReferenceError: touchStyle is not defined*
Is someone can help me ? Thanks a lot !
Unfortunately I think I would have to see more of your ComponentStyle.qml but I think you're missing properties.
You should use these two links to get an overall idea of how to format you code for themes and styles.
Styling - Has great examples on styling text and buttons very applicable to your case.
QmlStyling - Lists different approaches and techniques for implementing styling.
I hope that helps.

QML (Cascades) - insert container at position

Cascades, 10.2 SDK
MyCustomContainer.qml:
import bb.cascades 1.2
Container {
id: rootContainer
Container{
id: childContainer
// some content
}
}
MyPage.qml:
import bb.cascades 1.2
Page {
MyCustomContainer{
Label{
id: label
}
}
}
Label will be added on top of all content inside the rootContainer. I'd like to insert the label(or any other content) below all the content in MyCustomContainer. I've tried creating a property inside MyCustomContainer:
property Container backgroundContent
and add it as first child inside the MyCustomContainer as:
id: rootContainer
backgroundContent{
id: backgroundContent
}
and just set it in MyPage as:
MyCustomContainer{
backgroundContent: Container{
}
but I get
Cannot assign to non-existent property "id"
since the property value has not been set.
Can I somehow insert the content from MyPage.qml at the root of the MyCustomContainer?
It sounds like you want to use a default property to assign any children of the MyCustomContainer to be placed into the childContainer.
import bb.cascades 1.2
Container {
id: rootContainer
default property content: childContainer.children
Container{
id: childContainer
// All children when this control is used will be placed inside this component.
}
}
Then it can be naturally used as:
MyCustomContainer {
Container {
}
This will render the Container within the childContainer of the MyCustomContainer component.
Try using backgroundContent.add()
The solution is rather simple. Predict at which place can components be injected in the root container and place another container there.
Container {
id: rootContainer
Container{
id: injectContainer
}
Container{
id: childContainer
// some content
}
}
injectContainer.add(label)

Resources