Qt.createComponent url of the library components - qt

Below is a function from TimelinePresenter.qml which is a custom component I created.
function createMenu() {
var menuComp = Qt.createComponent("Menu.qml");
if( menuComp.status != Component.Ready )
{
if( menuComp.status == Component.Error )
console.debug("Error: " + menuComp.errorString());
return;
}
}
It gives the error:
Error: qrc:/qml/timeline/Menu.qml:-1 No such file or directory
TimelinePresenter.qml is a resource file specified in the .qrc file and its path is qml/timeline as shown in error message so qml engine is trying to find the Menu.qml there expectedly. How can I specify the path to create qt's Menu component?
Edit:
my resources.qrc file
<RCC>
<qresource prefix="/">
<file>qml/main_window.qml</file>
<file>qml/timeline/TimelineViewItem.qml</file>
<file>qml/timeline/HorizontalLine.qml</file>
<file>qml/timeline/TimelineView.qml</file>
<file>qml/timeline/VerticalLine.qml</file>
<file>qml/timeline/timeline-item/timeline_item.h</file>
<file>qml/timeline/TimelinePresenter.qml</file>
<file>qml/timeline/timeline-item/analog_timeline_item.h</file>
<file>qml/timeline/timeline-item/digital_timeline_item.h</file>
<file>qml/timeline/timeline_presenter_backend.h</file>
<file>qml/ControllableListPresenter.qml</file>
<file>qml/controllable_list_backend.h</file>
<file>qml/controllable-popup/AddControlUnitPopup.qml</file>
<file>qml/styled/CenteredPopup.qml</file>
<file>qml/styled/StyledTextField.qml</file>
</qresource>
</RCC>

You are confusing the creation of a component with the creation of an object that belongs to a component.
The Menu component already exists and is provided by Qt, what you must do is create the object using the Qt.createQmlObject() method.
Example:
var menuObj = Qt.createQmlObject('import QtQuick.Controls 2.0 ; Menu {
MenuItem { text: "Cut" }
MenuItem { text: "Copy" }
MenuItem { text: "Paste" } }', parentItem, "dynamicSnippet1");
Complete Example:
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
id: parentItem
Component.onCompleted: {
var menu = Qt.createQmlObject('import QtQuick.Controls 2.0 ; Menu {
MenuItem { text: "Cut" }
MenuItem { text: "Copy" }
MenuItem { text: "Paste" }
}', parentItem,"dynamicSnippet1");
// test: open menu
menu.open()
}
}

In the case you have described in your comments, I would suggest to only create one Menu and only popup() it at the place where you have clicked, setting it in a specific context.
I prepared a small example to illustrate how the Menu could be used:
import QtQuick 2.7
import QtQuick.Window 2.0
import QtQuick.Controls 2.3 // Necessary for the "Action" I used. Create the Menu otherwise if you are bound to older versions.
import QtQml 2.0
ApplicationWindow {
id: window
visible: true
width: 600
height: 600
Repeater {
model: ListModel {
ListElement { color: 'black'; x: 400; y: 50 }
ListElement { color: 'black'; x: 100; y: 190 }
ListElement { color: 'black'; x: 70; y: 80 }
ListElement { color: 'black'; x: 30; y: 0 }
ListElement { color: 'black'; x: 340; y: 500 }
ListElement { color: 'black'; x: 210; y: 10 }
}
delegate: MouseArea {
x: model.x
y: model.y
width: 50
height: 50
property QtObject modelItem: model
onClicked: menu.openMenu(x + mouse.x, y + mouse.y, modelItem)
Rectangle {
color: model.color
anchors.fill: parent
}
}
}
Menu {
id: menu
Action { text: "green" ; onTriggered: { menu.currentContext.color = text } }
Action { text: "blue" ; onTriggered: { menu.currentContext.color = text } }
Action { text: "pink" ; onTriggered: { menu.currentContext.color = text } }
Action { text: "yellow" ; onTriggered: { menu.currentContext.color = text } }
Action { text: "orchid" ; onTriggered: { menu.currentContext.color = text } }
Action { text: "orange" ; onTriggered: { menu.currentContext.color = text } }
Action { text: "teal" ; onTriggered: { menu.currentContext.color = text } }
Action { text: "steelblue"; onTriggered: { menu.currentContext.color = text } }
property QtObject currentContext
function openMenu(x, y, context) {
currentContext = context
popup(x, y)
}
}
}
Though I think this answer might solve your problem, I know that it is not really the answer to the question you stated initially.
For the Component-part: I think you misunderstood what a Component is - it is not an Item. It is a prestage in the creation of QtObjects and more something like a prototype or configured factory.
So your function - if it would work - would end at the creation of a invisible thing, from which you could create objects, by calling createObject().
Creating Components is the right thing to do, if you want to create an object at a later time and you might want to create similar objects multiple times, either by JavaScript or by other QML-types that expect Components as some input (e.g. delegates).
To create Components you have multiple possibilities, e.g.:
Qt.createComponent(url)
Component { SomeItem {} }
The first expects you to know the url, which in your case, you do not. To circumvent that, the easiest solution is, to create a new File, like MyMenu.qml
that only contains the Menu {} - then you can create a Component from this.
The second does not expects you to know the url, but it is not dynamically created.
Component {
id: myCmp
Menu {
}
}
onSomeSignal: myCmp.createObject({ prop1: val1 }, this)
Here the Component is automatically created when the object in the file is instantiated. This makes that (one time) initially a bit slower, since more code has to be processed, but you don't have to do it later.
Creating objects like eyllanesc shows with Qt.createQmlObject("Write a new QML-File here") might be also used to create a Component if the top-level element is a Component. If you don't have a Component as top-level, it will also first create a component that is once used to create a QtObject and then is discarded. It is the slowest but most flexible way to dynamically create objects.

Related

How to style MenuBarItem with mnemonics strings in QML

I want to customize MenuBar (from QtQuick.Controls 2.4) in my Qt application, so I followed the example from Qt website (https://doc.qt.io/qt-5/qtquickcontrols2-customize.html#customizing-menubar).
The example does not contain mnemonics, however. Here is my code for the MenuBar which has mnemonics:
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
import "../singletons"
MenuBar {
Menu {
title: qsTr("&File")
Action { text: qsTr("&Open...") }
Action { text: qsTr("&Save") }
Action { text: qsTr("Save &As...") }
MenuSeparator { }
Action { text: qsTr("&Quit") }
}
Menu {
title: qsTr("&Edit")
Action { text: qsTr("Cu&t") }
Action { text: qsTr("&Copy") }
Action { text: qsTr("&Paste") }
}
Menu {
title: qsTr("&Help")
Action { text: qsTr("&About") }
}
background: Rectangle {
color: Style._ColorPrimaryDark
}
delegate: MenuBarItem {
id: menuBarItem
contentItem: Text {
text: menuBarItem.text
opacity: enabled ? 1.0 : 0.3
color: "white"
verticalAlignment: Text.AlignVCenter
}
}
}
When I run the code, the MenuBar items look like this (the mnemonic shortcuts still work though):
Without the style, the MenuBar items have the mnemonic character underlined as expected:
I couldn't find anything about this problem. Is there any way or workaround so I could keep the mnemonics and customize the looks?
Looks like a bug. The native element uses some private control IconLabel which isn't accessible ( see it here). Using Label also doesn't solve the issue. So the solution is avoiding item customization, or to use some stupid workaround like this:
delegate: MenuBarItem {
id: menuBarItem
function replaceText(txt)
{
var index = txt.indexOf("&");
if(index >= 0)
txt = txt.replace(txt.substr(index, 2), ("<u>" + txt.substr(index + 1, 1) +"</u>"));
return txt;
}
contentItem: Label {
text: replaceText(menuBarItem.text)
color: "white"
verticalAlignment: Text.AlignVCenter
textFormat: Text.RichText
}
}

Binding specific item of a ListModel to a Component

I am building an application with HsQML. This is my first encounter with QML, my second ever work in Qt, and first larger project with Haskell, so forgive my ignorance.
In the UI, I have a TabView. The first Tab contains a ListView which is bound to a model and displays a list of items. Double-clicking an item in the ListView opens a new tab with a component which correctly shows that item's details (my guess is by virtue of the new tab inheriting its context from the list item that was clicked).
Now, my objective is to open a tab in which to create a new item for that model. The idea is to create a blank data item (optionally adding it to the model), and "load" this into the same component type used for editing existing items. I scoured QML's documentation and could not find anything even remotely related, which makes me think the approach is completely flawed.
TabView {
id : rootTabs
Tab {
ListView {
model : AutoListModel {
source : workflowModel // this is sort of HsQML specific, data comes as a list from Haskell
}
delegate : Rectangle {
Text {
text : modelData.name
}
MouseArea {
anchors.fill : parent
// this part works because the new component inherits its modelData from the current context
// so the new tab has correct data
onDoubleClicked : {
rootTabs.addTab(modelData.name, Qt.createComponent("WorkflowView.qml"))
rootTabs.currentIndex = rootTabsCount - 1
}
}
}
}
Button {
text : "Create workflow"
// this is the part in question - how do I assign the newly appended data to comp?
onClicked : {
wModel.appendBlank()
comp = Qt.createComponent("WorkflowView.qml")
var tab = rootTabs.addTab("New workflow", comp)
comp.statusChanged.connect(tabLoaded)
}
}
}
}
WorkflowEdit.qml:
Rectangle {
TextField {
id : nameInput
text : modelData.name
Binding {
target : modelData
property : "name"
value : nameInput.text
}
}
}
I think I have what you're looking for. It was a little tricky because Tab are essentially loaders. It was a matter of creating an extra property for the Tab QML type as a place to store a model index. And since tabs are simply children of a TabView, new tabs can be parented to the TabView instead of using the addTab() method. Note that for my model I used a ListModel.
main.qml
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
TabView {
id : rootTabs
anchors.fill: parent
ListModel {
id: listModel
ListElement { car: "Toyota" }
ListElement { car: "Chevrolet" }
ListElement { car: "Honda" }
ListElement { car: "Daihatsu" }
ListElement { car: "Ford" }
ListElement { car: "Nissan" }
ListElement { car: "Hyundai" }
ListElement { car: "Acura" }
}
MyTab {
title: "Default"
Item {
ListView {
id: listView
anchors { fill: parent; bottomMargin: 240 }
model : listModel
delegate : Rectangle {
width: parent.width
height: 40
Text {
text : car
color: "black"
font.pointSize: 20
}
MouseArea {
anchors.fill : parent
onDoubleClicked : {
var myTab = Qt.createComponent("MyTab.qml")
var workflow = Qt.createComponent("Workflow.qml")
myTab.createObject(rootTabs, { "title": car, "modelIndex": index, "sourceComponent": workflow });
rootTabs.currentIndex = rootTabs.count - 1
}
}
}
}
Button {
anchors {fill: parent; topMargin: 240 }
text : "Create workflow"
onClicked : {
listModel.append( { "car" : "New car" } )
var myTab = Qt.createComponent("MyTab.qml")
var workflow = Qt.createComponent("Workflow.qml")
myTab.createObject(rootTabs, { "title": "New Workflow", "modelIndex": listModel.count - 1 , "sourceComponent": workflow });
}
}
}
}
}
}
MyTab.qml
import QtQuick 2.0
import QtQuick.Controls 1.4
Tab {
property int modelIndex
}
Workflow.qml
import QtQuick 2.0
import QtQuick.Controls 1.4
Rectangle {
TextField {
id : nameInput
text : listModel.get(modelIndex).car
onTextChanged: {
// Update model using modelIndex. Observe updates in listview
listModel.set(modelIndex, { "car" : text })
}
}
}
TabView::addTab returns a Tab object, which is basically a Loader object. Loader::item is the current loaded object. So, the solution is to add an new empty model data to the tab as follows (in Button::onClicked):
var tab = ...
tab.loaded.connect(function () {tab.item.data = newModelData;}); // newModelData = wModel.appendBlank() ???
And you should add the property modelData explicitly to WorkflowEdit.qml:
Rectangle {
property var data: modelData // create property data and assign the context variable modelData to it by default
TextField {
id : nameInput
text : data === undefined ? "" : data.name
Binding {
target : data
property : "name"
value : nameInput.text
}
}
}

how to send text field values from one qml to another qml page in cascades blackberry

I am new to development on BB cascades. I've created two QML Pages. I want to pass data from one QML Page to another.
I want to pass the values phonenumber(id:phonenumber) and amount ( id:amount) from mobile.qml to payment.qml.
Please anyone help me out. Thank you in advance.
Mobile.qml:
import bb.cascades 1.4
import bb.data 1.0
Page {
onCreationCompleted: {
getData()
getCircle()
}
Container {
background: backgroundPaint.imagePaint
attachedObjects: [
ImagePaintDefinition {
id: backgroundPaint
imageSource: "asset:///images/background.png"
}
]
TextField {
id:phonenumber
hintText: "Enter Phone Number"
horizontalAlignment: HorizontalAlignment.Center
verticalAlignment: VerticalAlignment.Center
topMargin: ui.du(3)
// On text change the label text is updated.
input
{
keyLayout: KeyLayout.Text
}
}
TextField {
id:amount
hintText: "Enter Amount"
horizontalAlignment: HorizontalAlignment.Center
verticalAlignment: VerticalAlignment.Center
topMargin: ui.du(3)
// On text change the label text is updated.
input
{
keyLayout: KeyLayout.Text
}
}
Button {
id: newButton
horizontalAlignment: HorizontalAlignment.Center
verticalAlignment: VerticalAlignment.Center
topMargin: ui.du(3)
text: "Recharge"
appearance: ControlAppearance.Primary
color: Color.create("#F93249")
onClicked: {
var blogpage = goToWebView.createObject();
navigationPane.push(blogpage);
}
attachedObjects: ComponentDefinition {
id: goToWebView
source: "payment.qml"
}
}
}
attachedObjects: [
ComponentDefinition {
id: newOptionDef
Option {}
}
]
}
payment.qml:
import bb.cascades 1.4
Page {
Container {
background: backgroundPaint.imagePaint
attachedObjects: [
ImagePaintDefinition {
id: backgroundPaint
imageSource: "asset:///images/background.png"
}
]
}
}
Next time please post only code related to problem. As for your problem you can use parent as a proxy to access one item from another one.
For example, assume we have a component:
Page.qml
import QtQuick 2.4
import QtQuick.Controls 1.2
Item {
id:page
width: 200
height: 200
property int callee
function func() {
txt.text = txt.text + " (changed)"
}
Text {
id: txt
anchors.centerIn: parent
text: "click me"
MouseArea {
anchors.fill: parent
onClicked: {
page.parent.proxyFunction(page.callee);
}
}
}
}
and so Item contains several Pages:
import QtQuick 2.4
import QtQuick.Window 2.0
Window {
width: 400
height: 200
Row {
anchors.fill: parent
function proxyFunction(page) {
children[page].func();
}
Page {callee: 1}
Page {callee: 0}
}
}
So here as you can see clicking Text in one of Pages triggers changing Text in another Page.
In your Page add these lines:
import bb.cascades 1.4
import bb.data 1.0
Page {
id: mobilePage // ADD THIS
property string m_amount // ADD THIS
property string m_phoneNumber // ADD THIS
// the rest of the code
onClicked: { // Buttons onClick
mobilePage.m_amount = amount.text // ADD THIS
mobilePage.m_phoneNumber = phonenumber.text // ADD THIS
var blogpage = goToWebView.createObject()
navigationPane.push(blogpage)
}
}
Now in payment.qml you can use this:
console.log(mobilePage.m_amount)
console.log(mobilePage.m_phoneNumber)
You have to create a property in payment.qml at page level.
Page{
Id: payment
Properly string phonenumber;
Properly string amount;
Container{
Label{
Id: lblphonenumber
text: phonenumber
}
Label{
Id: lblamout
text: amount
}
}
in your main.qml you have to do this:
onClicked: {
var blogpage = goToWebView.createObject();
blogpage.phonenumber = yourvalue;
blogpage.amount = yourvalue;
navigationPane.push(blogpage);
}
This is it :)

Qt/QML: How to refer to a component/object from a GridView's model's ListElement

I have a GridView with a delegate that is supposed to use a Loader to load and display components which are defined in the same QML file.
Let's say I have a GridView like this:
GridView {
delegate: Rectangle {
Loader { sourceComponent: model.pageContents }
}
model: ListModel {
ListElement { /* how do I reference any of the components defined below from here, so the Loader can actually load and display it... ? */ }
}
}
Component {
id: page_01
Rectangle {
color: "red"
// Page contents for page 1 go here.
}
}
Component {
id: page_02
Rectangle {
color: "red"
// Page contents for page 2 go here.
}
}
I know I can create QML objects and components from Strings, external files and URLs. But I'd like to ideally do something like this:
ListModel {
ListElement { pageContents: page_01 }
ListElement { pageContents: page_02 }
}
I'd prefer to keep everything in a single QML file, so I can easily transfer and store it on the device without having to worry about resolving external dependencies, etc.
How do I refer to components in the same QML file from within ListElements?
Due to ListElement values limitation you cannot just put item id here. But you easily can use some external storage, for example property to store pointers to your pages. In example below I use array with pages ids and an index to wanted page as ListElement data:
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
width: 600
height: 600
GridView {
id: grid
anchors.fill: parent
property var pages: [page_01, page_02]
model: ListModel {
ListElement { pageIndex: 0 }
ListElement { pageIndex: 1 }
}
delegate: Loader { sourceComponent: grid.pages[pageIndex] }
}
Component {
id: page_01
Rectangle {
color: "red"
width: 100
height: 100
Component.onCompleted: console.log("page_01 was created")
}
}
Component {
id: page_02
Rectangle {
color: "blue"
width: 100
height: 100
Component.onCompleted: console.log("page_02 was created")
}
}
}

Dynamically create QML ListElement and content

So I am trying to dynamically create ListElements in a ListModel. This works fine until I try writing some content in the ListElements to be loaded dynamically.
I tried making an own file with the ListElement within and the hour as a property, but the model then I got an error saying that ListElements can not be nested.
The error for running the code below is:
Cannot assign to non-existent property "hour"
How can I solve this?
Code:
import QtQuick 2.0
ListModel
{
id: listModel
Component.onCompleted:
{
for (var i = 0; i < 24; i++)
{
var object = createListElement(listModel)
}
}
function createListElement(parent)
{
var object = Qt.createQmlObject('import QtQuick 2.0; ListElement { hour: "01" }', parent);
return object;
}
}
EDIT:
Change the code line in the function to:
var object = Qt.createQmlObject('import QtQuick 2.0; ListElement { property string hour: "23" }', parent);
Now I get no errors, but the elements are still not showing in the list.
I'm not sure why that doesn't work, but using plain old JavaScript objects does the job:
import QtQuick 2.4
import QtQuick.Window 2.0
Window {
width: 400
height: 400
ListView {
id: listView
anchors.fill: parent
model: listModel
delegate: Rectangle {
width: listView.width
height: listView.height / 4
Text {
text: hour
anchors.centerIn: parent
}
}
}
ListModel {
id: listModel
Component.onCompleted: {
for (var i = 0; i < 24; i++) {
append(createListElement());
}
}
function createListElement() {
return {
hour: "01"
};
}
}
}

Resources