QML: Make property readonly in derived class - qt

I'm pretty sure the answer is no, but I thought I should ask anyway. Can an inherited property be made readonly/constant in a pure QML derived class?
// Base.qml
Item {
property int foo: 42
}
// Derived.qml
Base {
foo: 99 // Make constant somehow!
}
I'm currently tackling this by detecting a change in foo and printing an error to the console, but that's hardly a mark of good API...
I have a menu item class which can represent menu entries, submenus, separators, etc. I need to specialise it's behaviour, but only for when it is in submenu mode. I can change the inheritance tree so I have a subclass for each menu type, but this seems silly as each subclass will only contain a single readonly property. However if there isn't another way, I will have to.

You can also transform foo as a readonly alias to an internal property _foo that you will use only for assignment in the derived class:
// Base.qml
Item {
id: base
readonly property alias foo: base._foo
property int _foo: 42 // only used for assignment in subclasses
}
To be used in another Component:
Item {
Base {
id: child0
}
Base {
id: child1
_foo: 99
}
Base {
id: child2
_foo: child1.foo
}
Component.onCompleted: {
console.log("child0:", child0.foo,"child1:", child1.foo, "child2:", child2.foo)
child1.foo = 5 // not allowed!
console.log("child0:", child0.foo,"child1:", child1.foo, "child2:", child2.foo)
}
}
Output:
child0: 42 child1: 99 child2: 99
TypeError: Cannot assign to read-only property "foo"
Note: of course it is still possible to modify _foo later. But if you remember to use it only for assignment, then you have the desired behavior with a readonly foo in the derived class.

Why not use inherited getter/setters for a private foo?
something like :
//Base.qml
Item
{
QtObject
{
id : privateFoo
property int foo : 42
}
function getFoo() {return privateFoo.foo;}
function setFoo(newFoo) { privateFoo.foo = newFoo; }
}
//derived.qml
Base
{
function getFoo() {return 99;}
function setFoo(newFoo) { /*do nothing */ }
}
(I haven't tested this code)

Related

Is there a way to type an object parsed from json in qml

The json document is like this. I want to define a class for it in qml, just like the interface keyword in typescript does.
{
"ScannerID": "ID",
"Status": 1,
"SuccessRate": 0.999,
"Result": [{
"Codes": "result_11111",
"Positions": {
"CenterX": 10.0,
"CenterY": 10.0,
"Width": 100.0,
"Height": 100.0,
"Angle": 50
},
"Types": "QrCode"
},
//more items
]
}
I tried to define a qml class in file ScannerResult.qml
import QtQuick 2.4
QtObject {
property string pScannerID
property int pStatus
property real pSuccessRate
// how to define Result with strong type?
property ? pResult
function load(obj) {
pScannerID = obj.ScannerID
//......
}
}
then use it
...
ScannerResult {
id: scannerResult
}
function log(jsonstr) {
let obj = JSON.parse(jsonstr);
scannerResult.load(obj)
console.log(scannerResult.pScannerID) // it works
}
...
But it's hard to handle the vector of objects under "Result" key. Because qml doesn't allow to define class B under class A.
So does anyone have a good idea about how to define a strong-typed class to hold the object parsed from json in qml? Then I can use that class with auto-completion in qtcreator.
You can create a property var and assign it as a list/array/vector to hold pResult. To populate this list, you can create a "class B" Component, and dynamically create instances of this class them when parsing your JSON object.
Using your code structure, here is an updated version of ScannerResult.qml:
import QtQuick 2.4
QtObject {
property string pScannerID
property int pStatus
property real pSuccessRate
property var pResult: []
function load(obj) {
pScannerID = obj.ScannerID
//... etc
for (var element of obj.Result) {
let resultQtObject = resultComponent.createObject(this, {})
resultQtObject.pCodes = element.Codes
resultQtObject.pTypes = element.Types
resultQtObject.pPositions.pCenterX = element.Positions.CenterX
//... etc
pResult.push(resultQtObject)
}
}
property Component resultComponent: Component {
QtObject {
property string pCodes
property string pTypes
property QtObject pPositions: QtObject {
property real pCenterX
property real pCenterY
property real pWidth
property real pHeight
property int pAngle
}
}
}
}
Your updated log function works as follows:
function log(jsonstr) {
let obj = JSON.parse(jsonstr);
scannerResult.load(obj)
console.log(scannerResult.pScannerID) // it works
console.log(scannerResult.pResult[0].pPositions.pCenterX) // it works
}
Because you are dynamically creating instances of this Component at runtime, QtCreator cannot make auto-completion suggestions for it. As you mentioned your vector is unknown length, I believe that runtime instantiation is your only option, and thus there is no solution that can accommodate auto-completion.
One other thing to note is that if you are re-running log()/load() many times you will need to clean the pResult list and destroy() the dynamically created objects.

Async Loading of a TreeView

Hey I am very new to tornadofx struggeling with async loading of data for the treeview. I am loading categories from a rest endpoint, which I want to show in there.
It seems like there's no direct data binding to the children.
when using 'bindChildren' I can provide the observable list, but I have to convert them into Node's. which then would make the populate block kind of obsolete.
What's the recommended way of doing this? I cannot find anything about this.
// Category
interface Category<T : Category<T>> {
val id: String
val name: String
val subcategories: List<T>?
}
//default category:
class DefaultCategory(override val name: String) : Category<DefaultCategory> {
override val id: String = "default"
override val subcategories: List<DefaultCategory>? = null
}
//ViewModel
class CategoryViewModel : ViewModel() {
val sourceProperty = SimpleListProperty<Category<*>>()
fun loadData() {
// load items for treeview into 'newItems'
sourceProperty.value = newItems
}
}
// TreeViewFactoryMethod
private fun createTreeView(
listProperty: SimpleListProperty<Category<*>>
): TreeView<Category<*>> {
return treeview {
root = TreeItem(DefaultCategory("Categories"))
isShowRoot = false
root.isExpanded = true
root.children.forEach { it.isExpanded = true }
cellFormat { text = it.name }
populate { parent ->
when (parent) {
root -> listProperty.value
else -> parent.value.subcategories
}
}
}
}
Assuming that on a button click I call viewmodel.loadData(), I would expect the TreeView to update as soon as there's some new data. (If I would've found a way to bind)
I've never had to use bindChildren for TornadoFX before and your use of async isn't very relevant to what I think is your primary problem. So, admittedly, this question kind of confused me at first but I'm guessing you're just wondering why the list isn't appearing in your TreeView? I've made a test example with changes to make it work.
// Category
interface Category<T : Category<T>> {
val id: String
val name: String
val subcategories: List<T>?
}
//default category:
class DefaultCategory(override val name: String) : Category<DefaultCategory> {
override val id: String = "default"
override val subcategories: List<DefaultCategory>? = null
}
//Just a dummy category
class ChildCategory(override val name: String) : Category<ChildCategory> {
override val id = name
override val subcategories: List<ChildCategory>? = null
}
//ViewModel
class CategoryViewModel : ViewModel() {
//filled with dummy data
val sourceProperty = SimpleListProperty<Category<*>>(listOf(
ChildCategory("Categorya"),
ChildCategory("Categoryb"),
ChildCategory("Categoryc"),
ChildCategory("Categoryd")
).asObservable())
fun loadData() {
sourceProperty.asyncItems {
//items grabbed somehow
listOf(
ChildCategory("Category1"),
ChildCategory("Category2"),
ChildCategory("Category3"),
ChildCategory("Category4")
).asObservable()
}
}
}
class TestView : View() {
val model: CategoryViewModel by inject()
override val root = vbox(10) {
button("Refresh Items").action {
model.loadData()
}
add(createTreeView(model.sourceProperty))
}
// TreeViewFactoryMethod
private fun createTreeView(
listProperty: SimpleListProperty<Category<*>>
): TreeView<Category<*>> {
return treeview {
root = TreeItem(DefaultCategory("Categories"))
isShowRoot = false
root.isExpanded = true
root.children.forEach { it.isExpanded = true }
cellFormat { text = it.name }
populate { parent ->
when (parent) {
root -> listProperty
else -> parent.value.subcategories
}
}
}
}
}
There are 2 important distinctions that are important.
1. The more relevant distinction is that inside the populate block, root -> listProperty is used instead of root.listProperty.value. This will make your list appear. The reason is that a SimpleListProperty is not a list, it holds a list. So, yes, passing in a plain list is perfectly valid (like how you passed in the value of the list property). But now that means the tree view isn't listening to your property, just the list you passed in. With that in mind, I would be considerate over the categories' subcategory lists are implemented as well.
2. Secondly, notice the use of asyncItems in the ViewModel. This will perform whatever task asynchronously, then set the items to list on success. You can even add fail or cancel blocks to it. I'd recommend using this, as long/intensive operations aren't supposed to be performed on the UI thread.

Abstract function qml

i want to create an abstract class with abstract functions in QML in theory some thing like this:
//abstract.qml
QtObject{
abstract function implementLater(var input);
}
Abstract{
//and here i have to implement it
function implementLater(var input){
console.log(input)
}
}
how can i do this in qml or maybe in c++ and register it to qml?
update :
here is what i'm trying to do, in my uvaluemask object i should have a function named mask which will be abstract and i call this for every child which will be different, you can call childs method directly i'm doing this right now and its working, but my component "UValueMask" is missing difinition for mask method,i mean i need to force users of UValueMask to define a mask method .
//UValueMask.qml
QtObject {
property string name:""
}
//singleton Object
UListObject{
property UValueMask timeMask :UValueMask{
id:timemask
name: "time"
function mask(input,splitter){
return innerObj.convertIntToTime(input,splitter)
}
}
property UValueMask dateMask: UValueMask{
id:datemask
name:"date"
function mask(input,splitter){
return innerObj.convertIntToDate(input,splitter)
}
}
}

How to assign a dictionary to a QML property?

I have a custom QML object written in C++ where one property is of the type QVariantMap, so it should be compatible with Javascript objects.
However, I have some troubles actually assigning a JS object:
Uploader {
sample: selectSampleButton.fileUrl
parameters: {
x: '3'
y: "String"
}
}
Is this even possible like this? One possiblity I found that works is to assign it in Javascript:
uploader.parameters = {x: "Test"};
You need to wrap it in parentheses, otherwise it is parsed as a binding expression :
Uploader {
sample: selectSampleButton.fileUrl
parameters: ({
x: '3'
y: "String"
})
}
If you have a fixed set of keys you could create a "grouped property", e.g. like font or anchors
The type of a grouped property is just a QObject derived class with respective Q_PROPERTY declaration and an instance of that is used by the main class, in your case the class behin Uploader as a Q_PROPERTY of a pointer to the new type.
Roughly like this
class UploaderParameters : public QObject
{
Q_OBJECT
Q_PROPERTY(int x MEMBER m_x NOTIFY xChanged)
};
class Uploader : public QObject
{
Q_PROPERTY(UploaderParameters* parameters MEMBER m_parameters CONSTANT)
};
in QML
Uploader {
parameters.x: 3
}
or
Uploader {
parameters {
x: 3
}
}

Get all descendants types of base class

I have a base class called BaseEvent and several descendants classes:
public class BaseEvent {
// the some properties
// ...
}
[MapInheritance(MapInheritanceType.ParentTable)]
public class Film : BaseEvent {
// the some properties
// ...
}
[MapInheritance(MapInheritanceType.ParentTable)]
public class Concert : BaseEvent {
// the some properties
// ...
}
I have a code which create the BaseEvent instance at runtime:
BaseEvent event = new BaseEvent();
// assign values for a properties
// ...
baseEvent.XPObjectType = Database.XPObjectTypes.SingleOrDefault(
t => t.TypeName == "MyApp.Module.BO.Events.BaseEvent");
Now, this event will be shows in BaseEvent list view.
I want to do the following: when a user click Edit button then show in list view lookup field with all descendants types. And when user saves record change ObjectType to selected value.
How can I do this?
Thanks.
PS. this is asp.net app.
I'm not sure that your approach is correct for what you are trying to achieve. First, I'll answer the question you have asked, and afterwards I'll try to explain how the XAF already provides the functionality you are trying to achieve, namely how to choose which subclass of record to create from the user interface.
In order to create a property which allows the user to choose a Type within the application, you can declare a TypeConverter:
public class EventClassInfoTypeConverter : LocalizedClassInfoTypeConverter
{
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
List<Type> values = new List<Type>();
foreach (ITypeInfo info in XafTypesInfo.Instance.PersistentTypes)
{
if ((info.IsVisible && info.IsPersistent) && (info.Type != null))
{
// select BaseEvent subclasses
if (info.Type.IsSubclassOf(typeof(BaseEvent)))
values.Add(info.Type);
}
}
values.Sort(this);
values.Insert(0, null);
return new TypeConverter.StandardValuesCollection(values);
}
}
And then your base event class would look like:
public class BaseEvent: XPObject
{
public BaseEvent(Session session)
: base(session)
{ }
private Type _EventType;
[TypeConverter(typeof(EventClassInfoTypeConverter))]
public Type EventType
{
get
{
return _EventType;
}
set
{
SetPropertyValue("EventType", ref _EventType, value);
}
}
}
However, I suspect this is not the functionality you require. Modifying the value of the property will NOT change the base type of the record. That is, you will end up with a record of type BaseEvent which has a property Type equal to 'Concert' or 'Film'.
XAF already provides a mechanism for selecting the type of record to create. In your scenario, you will find that the New button is a dropdown with your different subclasses as options:
Therefore you do not need to create a 'type' property within your object. If you need a column to show the type of event in the list view, you can declare a property as follows
[PersistentAlias("XPObjectType.Name")]
public string EventType
{
get
{
return base.ClassInfo.ClassType.Name;
}
}

Resources