If I have a WebEngineView in a ListView delegate, how can I call loadHtml when the delegate is loaded? For example:
ListView
{
model: myModel
delegate: Component
{
Item:
{
WebEngineView
{
id: myWebView
text: myWebView.loadHtml(model.modelData.htmlText)
}
}
}
}
The above shows the idea of what I would like to do. Is there a signal I could hook into for each delegate Item where I could call myWebView.loadHtml()?
I do not know about a text-property of the WebEngineView but I have never used it sofar.
I think, what you want is the Component.onCompleted-handler like this:
ListView {
model: myModel
delegate: Component {
Item {
WebEngineView {
id: myWebView
Component.onCompleted: loadHtml(model.modelData.htmlText, baseURL)
}
}
}
}
I don't know about your usecase, but the loadHtml-method has a second argument for the baseURL to look for ressources such as CSS or Images, which might be needed by you.
Related
Background
I have a tree-like QStandardItemModel, whose items I would like to access from QML.
Here is how the model is defined on the C++ side:
backend.h
class Backend : public QObject
{
Q_OBJECT
Q_PROPERTY(QStandardItemModel *model READ model CONSTANT)
public:
explicit Backend(QObject *parent = nullptr);
QStandardItemModel *model() const;
private:
QStandardItemModel *m_model;
};
backend.cpp
Backend::Backend(QObject *parent) :
QObject(parent),
m_model(new QStandardItemModel(this))
{
auto *itemFirst = new QStandardItem(tr("First"));
auto *itemSecond = new QStandardItem(tr("Second"));
auto *subItem = new QStandardItem(tr("First_02"));
subItem->appendRow(new QStandardItem("First_02_01"));
itemFirst->appendRow(new QStandardItem(tr("First_01")));
itemFirst->appendRow(subItem);
itemFirst->appendRow(new QStandardItem(tr("First_03")));
itemSecond->appendRow(new QStandardItem(tr("Second_00")));
itemSecond->appendRow(new QStandardItem(tr("Second_01")));
m_model->appendRow(itemFirst);
m_model->appendRow(itemSecond);
}
QStandardItemModel *Backend::model() const
{
return m_model;
}
The model is exported to QML in main.cpp like this:
Backend backend;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("backend", &backend);
qmlRegisterUncreatableType<QStandardItemModel>("QStandardItemModel", 1, 0, "QStandardItemModel", "The model should be created in C++");
Using TreeView from QtQuick.Controls 1.4 in main.qml like this:
TreeView {
anchors.fill: parent
model: backend.model
TableViewColumn {
title: "Name"
role: "display"
}
}
I get the desired results, i.e. all items nested correctly:
Problem
When I try to manually iterate over the nested items using Repeater and DelegateModel like this:
ColumnLayout {
anchors.fill: parent
Repeater {
model: backend.model
Item {
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors.fill: parent
Text {
color: "blue"
text: model.display
}
Repeater {
model: DelegateModel {
model: backend.model
rootIndex: modelIndex(index)
Item {
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors.fill: parent
Text {
color: "green"
text: model.display
}
Repeater {
model: DelegateModel {
model: backend.model
rootIndex: modelIndex(index)
Text {
color: "red"
text: model.display
}
}
}
}
}
}
}
}
}
}
}
the main branches (marked with blue) and the items on the first nesting level (marked with green) are the right ones, but I get the wrong items on the second nesting level (marked with red):
How to fix the code to correctly iterate over the QStandardItemModel's items on each nesting level?
The problem is in these two lines: rootIndex: modelIndex(index).
index is the index of the 'parent' model, but modelIndex(...) is the method of the current model.
I've tried it with this (slightly modified) piece of code and it worked:
Repeater {
model: DelegateModel {
id: model1
model: backend.model
delegate: ColumnLayout{
Text {
text: "Data: " + display
}
Repeater {
model: DelegateModel {
id: model2
model: backend.model
// 'index' comes from 'model1', so use the 'modelIndex' method from 'model1'
rootIndex: model1.modelIndex(index)
delegate: ColumnLayout{
Text {
text: "- Data: " + display
}
Repeater {
model: DelegateModel {
id: model3
model: backend.model
// 'index' comes from 'model2', so use the 'modelIndex' method from 'model2'
rootIndex: model2.modelIndex(index)
delegate: Text {
text: "-- Data: " + display
}
}
}
}
}
}
}
}
}
Recently, I've been interested about how to create a simple general model with submodels and pass its data to .qml page. I got help and did it. But now I've got another problem. Now I can use roles, but I can't use signals or methods.
Here how my code looked like before
basemodel.h
class BaseModel : public QObject
{
Q_OBJECT
Q_PROPERTY(ExtraModel* extra READ extraModel CONSTANT)
public:
explicit BaseModel(QObject *parent = nullptr);
ExtraModel* extraModel() const { return extraModel_; }
private:
ExtraModel* extraModel_ = nullptr;
};
basemodel.cpp
BaseModel::BaseModel(QObject *parent)
: QObject(parent),
extraModel_(new ExtraModel(this))
{
}
And here how my .qml page was before I changed it to BaseModel
Rectangle {
signal selectionChanged(int value, string pageTitle, string itemName)
SilicaListView {
id: list
anchors.fill: parent
model: ExtraModel {
id: _extraModel
onSelectedChanged: {
selectionChanged(selected, name, itemName)
}
}
delegate: Rectangle {
MouseArea {
anchors.fill: parent
onClicked: _extraModel.activate(index)
}
}
}
}
And thats how I want it to be (or something like that)
Rectangle {
signal selectionChanged(int value, string pageTitle, string itemName)
BaseModel {
id: _baseModel
}
SilicaListView {
id: list
anchors.fill: parent
model: _baseModel.extra {
id: _extraModel
onSelectedChanged: {
selectionChanged(selected, name, itemName)
}
}
delegate: Rectangle {
MouseArea {
anchors.fill: parent
onClicked: _extraModel.activate(index)
}
}
}
}
But _baseModel.extra doesn't work as component so I asked how to use signals from _baseModel.extra and got the answer: Connections object. So, I searched and found what connections object is. So, I've tried to use it, but I only figured out I can't access my signal from ExtraModel or probably doing something wrong.
That's how I tried to use connection object
SilicaListView {
id: _list
anchors.fill: parent
model: _baseModel.extra
Connections {
id: _extraModel
target: _baseModel.extra
onSelectedChanged: {
selectionChanged(selected, name, itemName)
}
}
...
}
So, the question is how to access my signals and methods from ExtraModel using BaseModel?
To connect to signals using a Connections object, your code should look like this:
model: _baseModel.extra
Connections {
target: _baseModel.extra
onSelectedChanged: {
selectionChanged(selected, name, itemName)
}
}
EDIT:
To call invokable functions, you should just be able to do this:
MouseArea {
anchors.fill: parent
onClicked: _baseModel.extra.activate(index)
}
I have several QML files, main one is the one that opens the ApplicationWindow when ever I try to get back from child QML to main, new window is opened again!
how can I prevent of doing this?
I thought that enabling a flag in child QML, but there may be other ways!
I tried stackview in QML
Is there any way to prevent from opening new page when I get back to main QML?
create a loader in main window and call every page into that loader when ever you need to change page just change loader's source
Window {
Loader{
id:myLoader
anchors.fill: parent
source: "LoginPage.qml"
}
Connections{
target: myLoader.item
onBack_clicked:{
loginid = ""
myLoader.source = "LoginPage.qml"
}
onSetting_clicked:{
myLoader.source = "Setting.qml"
}
}
}
and for child qml files : (for me Setting.qml)
Item {
signal back_clicked()
Button {
id: button1
anchors.right: parent.right
anchors.rightMargin: 15
onClicked: {
back_clicked()
}
}
}
but if you want to not destroy old page use SwipeView Or StackView:
SwipeView {
id: swipeView
clip: true
currentIndex: 0
Item{
id:firstPage
clip:true
//your page
}
Item{
id:secondPage
clip:true
//your page
}
}
and to change pages just change currentIndex
swipeView.currentIndex = 1
UPDATE:
StackView {
id: stackView
initialItem: one
}
Component {
id: one
Item{//your first page }
}
Component {
id: two
Item{//your second page }
}
and to push your pages :
stackView.push({ item: two})
//or
stackView.push("MySecondView.qml")
to get back to old or main page just pop it :
stackView.pop()
I have a tree model derived from a QAbstractItemModel. And I can display the data in a tree like way.
What I want is to display teh data by the layers. To display only one level of a layer at a time AND put each layer on a stack and navigate backwards by poping the layer from the stack.
I guess I have to implement a custom delegate? Any advice would be highly appreciated. Thank you.
I recently implemented something similar, based on a QFileSystemModel, set as a qml contextProperty, named treeModel in the example below.
The idea was to keep track of the current QModelIndex, and to use the data() & rowCount() functions of the QAbstractItemModel to get the actual model data, and to use a recursive stack view for the navigation
General layout
ApplicationWindow {
id: main
visible: true
width: 640
height: 480
ColumnLayout
{
anchors.fill: parent
// Breadcrumb
SEE BELOW
// View
StackView
{
id: stackView
Layout.fillHeight: true
Layout.fillWidth: true
initialItem: TreeSlide {}
}
}
}
TreeSlide
The view itself is pretty simple. I didn't used anything fancy here, and it displays only one role, but you could extend it without trouble. Note that the view's model is NOT your treeModel, but instead just the rowCount for the rootIndex.
ListView
{
Layout.fillHeight: true
Layout.fillWidth: true
model: treeModel.rowCount(rootIndex)
clip: true
snapMode: ListView.SnapToItem
property var rootIndex
// I used a QFileSytemModel in my example, so I had to manually
// fetch data when the rootIndex changed. You may not need this though.
onRootIndexChanged: {
if(treeModel.canFetchMore(rootIndex))
treeModel.fetchMore(rootIndex)
}
Connections {
target: treeModel
onRowsInserted: {
rootIndexChanged()
}
}
delegate: ItemDelegate {
property var modelIndex: treeModel.index(index,0, rootIndex)
property bool hasChildren: treeModel.hasChildren(modelIndex)
width: parent.width
text: treeModel.data(modelIndex)
onClicked: {
if(hasChildren)
{
// Recursively add another TreeSlide, with a new rootIndex
stackView.push("TreeSlide.qml", {rootIndex: modelIndex})
}
}
}
}
Breadcrumb
To navigate the model, instead of a simple back button, I used a kind of dynamic breadcrumb
// Breadcrumb
RowLayout
{
Repeater
{
id: repeat
model: {
var res = []
var temp = stackView.currentItem.rootIndex
while(treeModel.data(temp) != undefined)
{
res.unshift(treeModel.data(temp))
temp = temp.parent
}
res.unshift('.')
return res
}
ItemDelegate
{
text : modelData
onClicked: {
goUp(repeat.count - index-1)
}
}
}
}
the goUp function simply goes up the stack by poping items
function goUp(n)
{
for(var i=0; i<n; i++)
stackView.pop()
}
To to do it completely by guides we should use DelegateModel and DelegateModel.rootIndex
DelegateModel {
id: delegateSupportPropConfigModel
model: supportModel
delegate: SupportPropConfigListItem {
id: currentItem
width: scrollRect2.width - 60
fieldName: model.fieldName
fieldValue: model.value
onClick:{
delegateSupportPropConfigModel.rootIndex = supportPropConfigModel.index(0, 0, supportPropConfigModel)
}
}
}
Column {
id: columnSettings
spacing: 2
Repeater {
model: delegateSupportPropConfigModel
}
}
How can I assign different models to a ListView, which is inside a Repeater?
I did a sketch (my actual project is much larger):
Column {
Repeater {
model: ["Line1","Line2","Line3","Line4"]
Rectangle {
ListView {
model: ???
delegate: Rectangle {
Text {
text: somemodelproperty
}
}
}
}
}
}
Currently, I am solving the idea by copy-pasting 10 Rectangles, each containing a ListView. In C++, I have implemented 10 QList<QObject*>, and each list is "bounded" to a ListView by doing
QQmlContext * example = engine.rootContext();
example->setContextProperty(modelname,QVariant::fromValue(listobject));
I am pretty sure there is a more intelligent way to do this, but I have just started working with QML a few days ago, and can't figure out a solution.
There is the catch, you can't use an id as a value for a list element, nor can you nest a list model inside a list element, at least not directly.
You can however populate it like this:
Item {
id: models
ListModel {/*...*/}
ListModel {/*...*/}
//...
}
ListModel {
id: outerModel
}
function set() {
for (var i = 0; i < models.data.length; ++i) {
outerModel.append({"model": models.data[i] /* and other stuff */})
}
}
Or if you prefer using your C++ data, you can add a QObject* "model" property to each of the elements in the list and set it with a function, either as in the example above or with the ids for the inner models you specify.
On a second thought, you might want to use another name instead of "model" because I can't imagine QML will be happy about something like model: model
Update: You could try doing something like this (assuming your 10 models are exposed as m1 - m10 in QML)
property var subModels: [m1, m2, ... m10]
Then for the ListView inside the repeater delegate you can:
ListView {
model: subModels[index]
// ...
}
Then assuming you have 10 elements in the repeater, the model for each list view will be selected from the array with the appropriate element index.
Declare model for repeater instead of using array of strings. You can use ListModel for that purposes. You can also add to ListModels element, ListElement, any properties you want. Declare something like following:
ListModel {
id: repeaterModel
ListElement { title: "Line1"; model: modelForFirstElement; }
ListElement { title: "Line2"; model: modelForSecondElement; }
// ...
}
Then assign it to your Repeater's model property
Repeater {
id: repeater
model: repeaterModel
// ...
Then you will be able to access to model for your ListView by just calling model's model property, like this (assuming that you have assigned id "repeater" for your Repeater element):
ListView {
model: repeater.model.model
// ...
For the model, consider an array that has a sub-array nested within. To differentiate since both the outer Repeater and the inner ListView have a different instance of modelData consider copying the outer modelData to a property named outerModelData.
In the following example, I refactor out Column-Repeater-ListView and replaced it with ListView-ListView. Both patterns achieve the same thing but the latter does it shorter.
import QtQuick
import QtQuick.Controls
Page {
ListView {
anchors.fill: parent
model: [
{la:"English",v:["one","two","three"]},
{la:"Mandarin",v:["yi","er","san"]},
{la:"French",v:["un","duex","trois"]},
{la:"German",v:["eins","zwei","drei"]},
{la:"Indonesian",v:["satu","dua","tiga"]},
]
delegate: ListView {
property var outerModelData: modelData
width: 120
height: childrenRect.height
header: Text { text: outerModelData.la }
model: outerModelData.v
delegate: Frame {
width: 100
Text { text: modelData }
}
}
}
}
You can Try it Online!