[GH-ISSUE #55] Support for the various model classes in Qt. #25

Open
opened 2026-05-05 10:59:50 -06:00 by gitea-mirror · 7 comments
Owner

Originally created by @pauldotknopf on GitHub (Aug 10, 2018).
Original GitHub issue: https://github.com/qmlnet/qmlnet/issues/55

  • QAbstractItemModel
  • QQmlListProperty
  • etc...
Originally created by @pauldotknopf on GitHub (Aug 10, 2018). Original GitHub issue: https://github.com/qmlnet/qmlnet/issues/55 * ```QAbstractItemModel``` * ```QQmlListProperty``` * etc...
Author
Owner

@sowelipililimute commented on GitHub (Jul 30, 2020):

I'd be interested in helping tackle this issue; though I'm not too familiar with how the bindings are structured.

What would I need to be poking?

<!-- gh-comment-id:666054271 --> @sowelipililimute commented on GitHub (Jul 30, 2020): I'd be interested in helping tackle this issue; though I'm not too familiar with how the bindings are structured. What would I need to be poking?
Author
Owner

@pauldotknopf commented on GitHub (Jul 30, 2020):

I created this issue without understanding the use-case really. I think that should be the first step. Say, what are we trying to accomplish by bringing in the QAbtractItemModel, etc.

<!-- gh-comment-id:666344089 --> @pauldotknopf commented on GitHub (Jul 30, 2020): I created this issue without understanding the use-case really. I think that should be the first step. Say, what are we trying to accomplish by bringing in the ```QAbtractItemModel```, etc.
Author
Owner

@sowelipililimute commented on GitHub (Jul 30, 2020):

Models are Qt's bread and butter for data; what you can do without them is severely handicapped. Models provide a ton of stuff that you'd have to implement yourself otherwise, e.g. tabular data, tree data, the integration with components, the ability to lazy load data, the canFetchMore/fetchMore combination, etc.

<!-- gh-comment-id:666467304 --> @sowelipililimute commented on GitHub (Jul 30, 2020): Models are Qt's bread and butter for data; what you can do without them is severely handicapped. Models provide a ton of stuff that you'd have to implement yourself otherwise, e.g. tabular data, tree data, the integration with components, the ability to lazy load data, the canFetchMore/fetchMore combination, etc.
Author
Owner

@sowelipililimute commented on GitHub (Aug 1, 2020):

From what I can tell, you'd need to make a C API for the QModelIndex, QMimeData, QAbstractItemModel, QAbstractProxyModel, and the QAbstractListModel/QAbstractTreeModel classes, then you'd need to make C# classes that wrap around these C APIs

That part should be fairly straightforward.

What I'm not sure on is how to have the C# object expose its wrapped C++ object that invokes the C# implementations when passing from C# -> C++/QML.

<!-- gh-comment-id:667585226 --> @sowelipililimute commented on GitHub (Aug 1, 2020): From what I can tell, you'd need to make a C API for the QModelIndex, QMimeData, QAbstractItemModel, QAbstractProxyModel, and the QAbstractListModel/QAbstractTreeModel classes, then you'd need to make C# classes that wrap around these C APIs That part should be fairly straightforward. What I'm not sure on is how to have the C# object expose its wrapped C++ object that invokes the C# implementations when passing from C# -> C++/QML.
Author
Owner

@pauldotknopf commented on GitHub (Aug 2, 2020):

From what I can tell, you'd need to make a C API for the QModelIndex, QMimeData, QAbstractItemModel, QAbstractProxyModel, and the QAbstractListModel/QAbstractTreeModel classes, then you'd need to make C# classes that wrap around these C APIs

Yup, sounds about right.

What I'm not sure on is how to have the C# object expose its wrapped C++ object that invokes the C# implementations when passing from C# -> C++/QML.

Yes, things get really squirrelly when you want to use C# inheritance/overrides with C++ inheritance/overrides. However, it is possible. I explored this when I was looking to create a QQuickPaintedItem type in C#.

1st, there is a question of ownership. When you expose the C++ type to QML, it's the .NET reference that would hold ownership. So, if you expose the type in QML, and then lose the reference to the .NET object, the .NET GC will eventually destroy your C++ reference that's currently being used in QML, which is bad. What would be the approach here? If someone does expose a model to QML, they'd just have to know to keep the .NET instance around? I don't like that idea, because I hate segfaults, but I'm not seeing a better solution.

Then, there is a question of overrides/inheritance.

Before I begin, side note, I've wrapped a LOT of C++ types in C#, and most of it was boilerplate stuff, stuff that could have been easily auto-generated with some kind of DSL, like Swig. I thought about creating another C# project with a DSL that would allow you to "model" a native type (or C++ object), and it would then auto-generate the required C-code to interop with it. I should have focused my efforts doing this, instead of creating create/destroy/invoke methods for every C++ type. This approach would have made it easier to allow the overrides.

Back to the question of overrides/inheritance.

MonoCppSharp had a good solution here.

In C++, create an implementation of QModelIndex that overrides everything, and instead invokes a bunch of delegates that are stored as private members. These delegates would be pointers to instance-level methods of a C# object. The C# object will also have default implementations that callback to the base method of the C++ type.

Here is the sequence.

  1. Create C# wrapper.
public class QModelIndex
{
}
  1. In C# ctor, create a native C++ object, passing pointer to every virtual method that can be overridden.
QModelIndex::QModelIndex()
{
   IntPtr cPointerVirtualMethod = WrapInstanceMethod(nameof(VirtualMethod)); // pseudo
   IntPtr nativePointer = PInvoke.create_q_model_index(cPointerVirtualMethod);
}
  1. In C++, the type would override every virtual method, calling the cPointerVirtualMethod passed in from create_q_model_index.
  2. A default implementation in C# would call native code's default base implementation.
public virtual void VirtualMethod()
{
    PInvoke.q_model_index_call_base_VirtualMethod();
}
  1. Then, when people create derivatives of QModelIndex in C#, they can override VirtualMethod and have the base.VirtualMethod idiomatic.
public class MyModelIndex : QModelIndex
{
    public virtual override VirtualMethod()
    {
        // Do my stuff...
        ....
        // Call the base C++ implementation
        base.VirtualMethod();
    }
}

I think you can begin to see how involved this would be for a single type, let alone 10-or-so types. And, if we need to change how it works, we'd have to massage these 10-or-so implementations independently. I'd rather have some kind of generator, but I'd accept either approach (hand-crafted vs auto-generated from DSL).

<!-- gh-comment-id:667613005 --> @pauldotknopf commented on GitHub (Aug 2, 2020): > From what I can tell, you'd need to make a C API for the QModelIndex, QMimeData, QAbstractItemModel, QAbstractProxyModel, and the QAbstractListModel/QAbstractTreeModel classes, then you'd need to make C# classes that wrap around these C APIs Yup, sounds about right. > What I'm not sure on is how to have the C# object expose its wrapped C++ object that invokes the C# implementations when passing from C# -> C++/QML. Yes, things get really squirrelly when you want to use C# inheritance/overrides with C++ inheritance/overrides. However, it is possible. I explored this when I was looking to create a ```QQuickPaintedItem``` type in C#. 1st, there is a question of ownership. When you expose the C++ type to QML, it's the .NET reference that would hold ownership. So, if you expose the type in QML, and then lose the reference to the .NET object, the .NET GC will eventually destroy your C++ reference that's currently being used in QML, which is bad. What would be the approach here? If someone does expose a model to QML, they'd just have to *know* to keep the .NET instance around? I don't like that idea, because I hate segfaults, but I'm not seeing a better solution. Then, there is a question of overrides/inheritance. *Before I begin, side note, I've wrapped a LOT of C++ types in C#, and most of it was boilerplate stuff, stuff that could have been easily auto-generated with some kind of DSL, like Swig. I thought about creating another C# project with a DSL that would allow you to "model" a native type (or C++ object), and it would then auto-generate the required C-code to interop with it. I should have focused my efforts doing this, instead of creating create/destroy/invoke methods for every C++ type. This approach would have made it easier to allow the overrides.* Back to the question of overrides/inheritance. MonoCppSharp had a good solution here. In C++, create an implementation of ```QModelIndex``` that overrides *everything*, and instead invokes a bunch of delegates that are stored as private members. These delegates would be pointers to instance-level methods of a C# object. The C# object will also have default implementations that callback to the base method of the C++ type. Here is the sequence. 1. Create C# wrapper. ```csharp public class QModelIndex { } ``` 2. In C# ctor, create a native C++ object, passing pointer to every virtual method that can be overridden. ```csharp QModelIndex::QModelIndex() { IntPtr cPointerVirtualMethod = WrapInstanceMethod(nameof(VirtualMethod)); // pseudo IntPtr nativePointer = PInvoke.create_q_model_index(cPointerVirtualMethod); } ``` 3. In C++, the type would override every virtual method, calling the ```cPointerVirtualMethod``` passed in from ```create_q_model_index```. 4. A default implementation in C# would call native code's default base implementation. ```csharp public virtual void VirtualMethod() { PInvoke.q_model_index_call_base_VirtualMethod(); } ``` 5. Then, when people create derivatives of ```QModelIndex``` in C#, they can override ```VirtualMethod``` and have the ```base.VirtualMethod``` idiomatic. ```csharp public class MyModelIndex : QModelIndex { public virtual override VirtualMethod() { // Do my stuff... .... // Call the base C++ implementation base.VirtualMethod(); } } ``` I think you can begin to see how involved this would be for a single type, let alone 10-or-so types. And, if we need to change how it works, we'd have to massage these 10-or-so implementations independently. I'd rather have some kind of generator, but I'd accept either approach (hand-crafted vs auto-generated from DSL).
Author
Owner

@sowelipililimute commented on GitHub (Aug 4, 2020):

Is the BaseDisposable type responsible for just memory management or does it handle passing the wrapped C++ type to the QML engine as well? Haven't really found what causes C# types to expose their C++ types to the QML engine when you pass them in methods.

<!-- gh-comment-id:668339038 --> @sowelipililimute commented on GitHub (Aug 4, 2020): Is the `BaseDisposable` type responsible for just memory management or does it handle passing the wrapped C++ type to the QML engine as well? Haven't really found what causes C# types to expose their C++ types to the QML engine when you pass them in methods.
Author
Owner

@pauldotknopf commented on GitHub (Aug 4, 2020):

It can handle passing the C++ type back to QML, but be careful with ownership. It works best with inner shared_ptr types, so that if C++ takes an instance, when .NET frees it's reference, it doesn't effect any instances that may exist in C++.

<!-- gh-comment-id:668342671 --> @pauldotknopf commented on GitHub (Aug 4, 2020): It can handle passing the C++ type back to QML, but be careful with ownership. It works best with inner ```shared_ptr``` types, so that if C++ takes an instance, when .NET frees it's reference, it doesn't effect any instances that may exist in C++.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/qmlnet#25
No description provided.