[GH-ISSUE #198] Qmlnet should support QQuickPaintedItem #126

Closed
opened 2026-05-05 11:06:01 -06:00 by gitea-mirror · 22 comments
Owner

Originally created by @nguyenvuduc on GitHub (May 9, 2020).
Original GitHub issue: https://github.com/qmlnet/qmlnet/issues/198

I think qmlnet is great compare to Xamarin Forms. However, lacking of ability for creating a custom visualization control is a big limitation for qmlnet. I have been working on many desktop application projects. In my opinions, an ui framework must provide a mechanism for developers to develop new fully custom draw/custom interaction components. Otherwise, it is difficult to scale.

Currently, the only way to implement a custom draw component with qmlnet is using Canvas and then pass the Context2D back to csharp code behind. This is really inconvenient and difficult to manage for a big project.

Do you have any plan to make qmlnet supports QQuickPaintedItem? It will be a big add on for qmlnet.

Originally created by @nguyenvuduc on GitHub (May 9, 2020). Original GitHub issue: https://github.com/qmlnet/qmlnet/issues/198 I think qmlnet is great compare to Xamarin Forms. However, lacking of ability for creating a custom visualization control is a big limitation for qmlnet. I have been working on many desktop application projects. In my opinions, an ui framework must provide a mechanism for developers to develop new fully custom draw/custom interaction components. Otherwise, it is difficult to scale. Currently, the only way to implement a custom draw component with qmlnet is using Canvas and then pass the Context2D back to csharp code behind. This is really inconvenient and difficult to manage for a big project. Do you have any plan to make qmlnet supports QQuickPaintedItem? It will be a big add on for qmlnet.
Author
Owner

@pauldotknopf commented on GitHub (May 10, 2020):

I would like to support QQuickPaintedItem item.

First, I'd like to understand the goals.

Currently, you can create custom QML components in C++/QML that can be consumed in QmlNet (or any other Qt application).

I imagine you'd like to be able to build the presentation with QPainter and C#, correct? Avoiding C++?

<!-- gh-comment-id:626318256 --> @pauldotknopf commented on GitHub (May 10, 2020): I would like to support ```QQuickPaintedItem``` item. First, I'd like to understand the goals. Currently, you can create custom QML components in C++/QML that can be consumed in QmlNet (or any other Qt application). I imagine you'd like to be able to build the presentation with ```QPainter``` and C#, correct? Avoiding C++?
Author
Owner

@nguyenvuduc commented on GitHub (May 10, 2020):

Yes, I would like to be able to build the presentation with QPainter and C#, avoiding C++

I don't know how to develop custom QML components in C++/QML and then register the type in QmlNet as you said. I assume it might be a lot of work because I will have to compile it into dll, .so and whatsoever so that it can be used in other platforms.

I tried to use Canvas and context2d yesterday. Somehow invoking methods of dynamic javascript context2d object in C# doesn't work at all. I have to workaround by doing the drawing in C# and then pass the image's byte array to javascript for rendering. The drawing performance is terrible :(.

Regards.

<!-- gh-comment-id:626373916 --> @nguyenvuduc commented on GitHub (May 10, 2020): Yes, I would like to be able to build the presentation with QPainter and C#, avoiding C++ I don't know how to develop custom QML components in C++/QML and then register the type in QmlNet as you said. I assume it might be a lot of work because I will have to compile it into dll, .so and whatsoever so that it can be used in other platforms. I tried to use Canvas and context2d yesterday. Somehow invoking methods of dynamic javascript context2d object in C# doesn't work at all. I have to workaround by doing the drawing in C# and then pass the image's byte array to javascript for rendering. The drawing performance is terrible :(. Regards.
Author
Owner

@devmil commented on GitHub (Aug 18, 2020):

I had a look into that because I think I also need that for my current hobby project but I can't see how this would work easily.
The paint(QPainter*) method gets called from the Qt RenderThread and it would be totally overkill to start P/Invoking at that moment. But this is the intended behavior (that the content gets drawn in that exact moment).

The only thing that I can imagine is to define a subset of what QPainter can do and set the actions from the .Net side so that the QPaintedQuickItem can use those actions to do the actual drawing when the RenderThread wants it to.

This would have some downsides:

  • No immediate reaction to size changes
  • Only a Qml.Net defined subset would be available
  • A delay between "I want to draw something" to "It gets drawn"

For my usecase this would be totally sufficient. Not sure about @nguyenvuduc 's usecase?

What do you think @pauldotknopf ?

<!-- gh-comment-id:675407869 --> @devmil commented on GitHub (Aug 18, 2020): I had a look into that because I think I also need that for my current hobby project but I can't see how this would work easily. The paint(QPainter*) method gets called from the Qt RenderThread and it would be totally overkill to start P/Invoking at that moment. But this is the intended behavior (that the content gets drawn in that exact moment). The only thing that I can imagine is to define a subset of what QPainter can do and set the actions from the .Net side so that the QPaintedQuickItem can use those actions to do the actual drawing when the RenderThread wants it to. This would have some downsides: - No immediate reaction to size changes - Only a Qml.Net defined subset would be available - A delay between "I want to draw something" to "It gets drawn" For my usecase this would be totally sufficient. Not sure about @nguyenvuduc 's usecase? What do you think @pauldotknopf ?
Author
Owner

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

The paint(QPainter*) method gets called from the Qt RenderThread and it would be totally overkill to start P/Invoking at that moment.

When use say "RenderThread", do you mean the main loop, or the background Qt composition thread that users typically don't interact with?

<!-- gh-comment-id:678111367 --> @pauldotknopf commented on GitHub (Aug 21, 2020): > The paint(QPainter*) method gets called from the Qt RenderThread and it would be totally overkill to start P/Invoking at that moment. When use say "RenderThread", do you mean the main loop, or the background Qt composition thread that users typically don't interact with?
Author
Owner

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

From the docs.

Note: The QML Scene Graph uses two separate threads, the main thread does things such as processing events or updating animations while a second thread does the actual OpenGL rendering. As a consequence, paint() is not called from the main GUI thread but from the GL enabled renderer thread. At the moment paint() is called, the GUI thread is blocked and this is therefore thread-safe.

Oof.

I get that dealing with sync items (mutex, timers, etc), that method needs special consideration, but would PInvoke really add any impact that PInvoking from the regular main loop wouldn't add either? My experience is that if the main loop pauses, you still see a delay in rendering, because the render thread eventually runs out of things to paint from it's queue.

<!-- gh-comment-id:678113637 --> @pauldotknopf commented on GitHub (Aug 21, 2020): From [the docs](https://doc.qt.io/qt-5/qquickpainteditem.html#paint). > Note: The QML Scene Graph uses two separate threads, the main thread does things such as processing events or updating animations while a second thread does the actual OpenGL rendering. As a consequence, paint() is not called from the main GUI thread but from the GL enabled renderer thread. At the moment paint() is called, the GUI thread is blocked and this is therefore thread-safe. Oof. I get that dealing with sync items (mutex, timers, etc), that method needs special consideration, but would PInvoke really add any impact that PInvoking from the regular main loop wouldn't add either? My experience is that if the main loop pauses, you still see a delay in rendering, because the render thread eventually runs out of things to paint from it's queue.
Author
Owner

@devmil commented on GitHub (Aug 21, 2020):

From the docs.

Note: The QML Scene Graph uses two separate threads, the main thread does things such as processing events or updating animations while a second thread does the actual OpenGL rendering. As a consequence, paint() is not called from the main GUI thread but from the GL enabled renderer thread. At the moment paint() is called, the GUI thread is blocked and this is therefore thread-safe.

Oof.

I get that dealing with sync items (mutex, timers, etc), that method needs special consideration, but would PInvoke really add any impact that PInvoking from the regular main loop wouldn't add either? My experience is that if the main loop pauses, you still see a delay in rendering, because the render thread eventually runs out of things to paint from it's queue.

The problem is that the update(QPainter*) method is called very frequently. And the nature of painting to a canvas is most likely that you have a bunch of setup and draw calls (more in the 100 than in the 10) so P/Invoke overhead adds up quickly.
This is why I think that it is much better to have the expensive part (P/Invoke) separated from the actual RenderThread.

Of course this is a different approach. The trigger is no longer the update(QPainter*) call but you have to define your own trigger to do a new recording session.

I think that this would be the better approach for most use cases (at least for mine :D)

<!-- gh-comment-id:678127651 --> @devmil commented on GitHub (Aug 21, 2020): > From [the docs](https://doc.qt.io/qt-5/qquickpainteditem.html#paint). > > > Note: The QML Scene Graph uses two separate threads, the main thread does things such as processing events or updating animations while a second thread does the actual OpenGL rendering. As a consequence, paint() is not called from the main GUI thread but from the GL enabled renderer thread. At the moment paint() is called, the GUI thread is blocked and this is therefore thread-safe. > > Oof. > > I get that dealing with sync items (mutex, timers, etc), that method needs special consideration, but would PInvoke really add any impact that PInvoking from the regular main loop wouldn't add either? My experience is that if the main loop pauses, you still see a delay in rendering, because the render thread eventually runs out of things to paint from it's queue. The problem is that the update(QPainter*) method is called very frequently. And the nature of painting to a canvas is most likely that you have a bunch of setup and draw calls (more in the 100 than in the 10) so P/Invoke overhead adds up quickly. This is why I think that it is much better to have the expensive part (P/Invoke) separated from the actual RenderThread. Of course this is a different approach. The trigger is no longer the update(QPainter*) call but you have to define your own trigger to do a new recording session. I think that this would be the better approach for most use cases (at least for mine :D)
Author
Owner

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

The trigger is no longer the update(QPainter*) call but you have to define your own trigger to do a new recording session.

What exactly does this look like? If you are drawing an animated scene, does that you mean you have to do something like:

Timer {
    interval: 16 /* roughly 60fps */
    onTriggered: {
        myViewModel.updateScene();
    }
}
<!-- gh-comment-id:678140373 --> @pauldotknopf commented on GitHub (Aug 21, 2020): > The trigger is no longer the update(QPainter*) call but you have to define your own trigger to do a new recording session. What exactly does this look like? If you are drawing an animated scene, does that you mean you have to do something like: ```javascript Timer { interval: 16 /* roughly 60fps */ onTriggered: { myViewModel.updateScene(); } } ```
Author
Owner

@devmil commented on GitHub (Aug 21, 2020):

Yes, or some Event from .Net, depends on what you are drawing.
But I don't think that QQuickPaintedItem stimulated from .NET can manage 60 FPS (if you don't have a very simple element)

<!-- gh-comment-id:678142009 --> @devmil commented on GitHub (Aug 21, 2020): Yes, or some Event from .Net, depends on what you are drawing. But I don't think that QQuickPaintedItem stimulated from .NET can manage 60 FPS (if you don't have a very simple element)
Author
Owner

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

Ok. Not ideal, but it's simple, and get's the job done.

<!-- gh-comment-id:678144661 --> @pauldotknopf commented on GitHub (Aug 21, 2020): Ok. Not ideal, but it's simple, and get's the job done.
Author
Owner

@Blquinn commented on GitHub (Sep 3, 2020):

Do you think it would be worth trying to do the full QQuickPaintedItem implementation and see if it really slows things down? I understand that you don't want to make the interop too chatty, but it's possible that you can still get 60fps out of it with pinvokes. It would probably be worth actually measuring the overhead before making that conclusion. Plus you'd be "opting in" to the painted item's chattiness, so the user could decide what "too slow" is.

I recently wrote a very basic FFI benchmark using dotnet core 3.1 and it's pinvoke is extremely fast, assuming I actually wrote it correctly. https://github.com/dyu/ffi-overhead/pull/17 It calls a c add function 5 million times in 1300ms (26 microseconds per call). Presumably there would be significantly more overhead involved in the case of the painter. However, in this instance the pinvoke was added only a 9% overhead over the native c call. It's a naive benchmark of course, but dotnet core's FFI seems to be really fast (twice as fast as mono's in the bench), so it might be worth looking into.

I think this feature would require calling into dotnet's runtime from native code, which may be a whole different beast, I'm not sure. I might write a benchmark for that too...

<!-- gh-comment-id:686762931 --> @Blquinn commented on GitHub (Sep 3, 2020): Do you think it would be worth trying to do the full QQuickPaintedItem implementation and see if it really slows things down? I understand that you don't want to make the interop too chatty, but it's possible that you can still get 60fps out of it with pinvokes. It would probably be worth actually measuring the overhead before making that conclusion. Plus you'd be "opting in" to the painted item's chattiness, so the user could decide what "too slow" is. I recently wrote a very basic FFI benchmark using dotnet core 3.1 and it's pinvoke is extremely fast, assuming I actually wrote it correctly. https://github.com/dyu/ffi-overhead/pull/17 It calls a c add function 5 million times in 1300ms (26 microseconds per call). Presumably there would be significantly more overhead involved in the case of the painter. However, in this instance the pinvoke was added only a 9% overhead over the native c call. It's a naive benchmark of course, but dotnet core's FFI seems to be really fast (twice as fast as mono's in the bench), so it might be worth looking into. I think this feature would require calling into dotnet's runtime from native code, which may be a whole different beast, I'm not sure. I might write a benchmark for that too...
Author
Owner

@devmil commented on GitHub (Sep 4, 2020):

Do you think it would be worth trying to do the full QQuickPaintedItem implementation and see if it really slows things down?

In general - yes. I think it would be worth trying this out or even just provide both options so that the user can use what fits best.

I'm still struggling a bit with how to provide the interface to the .Net user.

I understand that idea to have a QuickPaintedItem class in .Net that basically behaves like the C++ counterpart does.

But in my head it did not yet click. I'm struggling with imagine how to solve that best.

For the user to use a custom type in QML we basically have to register a .Net type for that. But this .Net type is made known in the QML world using the QObject meta system. It is not a "real" Quickitem.

So let's imagine we want to have a MyPaintedItem. The user creates a MyPaintedItem in .Net that derives from PaintedItem in .Net. In oder to use MyPaintedItem {} in QML we have to register a QObject (because that is all what we can do) named MyPaintedItem in QML.
This QObject has to behave like a QuickItem because it is part of the Scene Graph. All the things like layouting, focus management, input handling all the UI events that exist have to be bridged.
And I can't imagine that this simply works... Not to forget the sheer amount of API that has to be bridged.
https://github.com/qt/qtdeclarative/blob/dev/src/quick/items/qquickitem.h
https://github.com/qt/qtdeclarative/blob/dev/src/quick/items/qquickitem.cpp

Maybe I'm wrong but I'm pretty sure that a QuickItem can't simply be replaced by an arbitrary QObject by just implementing its API.

<!-- gh-comment-id:687373854 --> @devmil commented on GitHub (Sep 4, 2020): > Do you think it would be worth trying to do the full QQuickPaintedItem implementation and see if it really slows things down? In general - yes. I think it would be worth trying this out or even just provide both options so that the user can use what fits best. I'm still struggling a bit with how to provide the interface to the .Net user. I understand that idea to have a QuickPaintedItem class in .Net that basically behaves like the C++ counterpart does. But in my head it did not yet click. I'm struggling with imagine how to solve that best. For the user to use a custom type in QML we basically have to register a .Net type for that. But this .Net type is made known in the QML world using the QObject meta system. It is not a "real" Quickitem. So let's imagine we want to have a MyPaintedItem. The user creates a MyPaintedItem in .Net that derives from PaintedItem in .Net. In oder to use `MyPaintedItem {}` in QML we have to register a QObject (because that is all what we can do) named MyPaintedItem in QML. This QObject has to behave like a QuickItem because it is part of the Scene Graph. All the things like layouting, focus management, input handling all the UI events that exist have to be bridged. And I can't imagine that this simply works... Not to forget the sheer amount of API that has to be bridged. https://github.com/qt/qtdeclarative/blob/dev/src/quick/items/qquickitem.h https://github.com/qt/qtdeclarative/blob/dev/src/quick/items/qquickitem.cpp Maybe I'm wrong but I'm pretty sure that a QuickItem can't simply be replaced by an arbitrary QObject by just implementing its API.
Author
Owner

@Blquinn commented on GitHub (Sep 4, 2020):

@devmil I'm a total amateur at this native interop stuff to be clear :), but I'll try my hand at giving an answer.

How I imagine it would work is basically we just call a dotnet class method every time qt calls paint. Let's try to avoid implementing QObject, or QuickItem in dotnet, that obviously sounds like a nightmare. The paint() method and QPainter are what we really want access to.

I think there would be a native c++ class which implements QQuickPaintedItem to bridge to our dotnet code. Then there would be the dotnet class which the user will implement (or subclass) which has a paint() method that the c++ class can call. We would also have to provide a dotnet binding to QPainter, but that should be fairly straightforward I think. So every time the c++ bridge class' paint method is called, it would convert the painter to a dotnet painter, and call the dotnet paint method with that dotnet painter.

Then you'd register the dotnet class. Perhaps some magic would need to be done in the register class to wire up the bridge.

Well that's how I imagined it would work anyway, but maybe someone else has a better idea, or maybe that's impossible.

<!-- gh-comment-id:687390463 --> @Blquinn commented on GitHub (Sep 4, 2020): @devmil I'm a total amateur at this native interop stuff to be clear :), but I'll try my hand at giving an answer. How I imagine it would work is basically we just call a dotnet class method every time qt calls paint. Let's try to avoid implementing QObject, or QuickItem in dotnet, that obviously sounds like a nightmare. The paint() method and QPainter are what we really want access to. I think there would be a native c++ class which implements QQuickPaintedItem to bridge to our dotnet code. Then there would be the dotnet class which the user will implement (or subclass) which has a paint() method that the c++ class can call. We would also have to provide a dotnet binding to QPainter, but that should be fairly straightforward I think. So every time the c++ bridge class' paint method is called, it would convert the painter to a dotnet painter, and call the dotnet paint method with that dotnet painter. Then you'd register the dotnet class. Perhaps some magic would need to be done in the register class to wire up the bridge. Well that's how I imagined it would work anyway, but maybe someone else has a better idea, or maybe that's impossible.
Author
Owner

@devmil commented on GitHub (Sep 4, 2020):

Well that's how I imagined it would work anyway, but maybe someone else has a better idea, or maybe that's impossible.

Yes, that part is also clear to me. The critical point is the "magic" :)
You have to instantiate a QuickItem derived class (but always the same class) and connect it to a .Net class and you want to do that by writing the .Net classname in QML.

<!-- gh-comment-id:687400269 --> @devmil commented on GitHub (Sep 4, 2020): > Well that's how I imagined it would work anyway, but maybe someone else has a better idea, or maybe that's impossible. Yes, that part is also clear to me. The critical point is the "magic" :) You have to instantiate a QuickItem derived class (but always the same class) and connect it to a .Net class and you want to do that by writing the .Net classname in QML.
Author
Owner

@Blquinn commented on GitHub (Sep 4, 2020):

You can register dotnet objects in qml with this package already. What is different about this situation that would make it so difficult? I feel like this type of interop must already be happening in other places in this package because there's already two way communication taking place.

<!-- gh-comment-id:687412836 --> @Blquinn commented on GitHub (Sep 4, 2020): You can register dotnet objects in qml with this package already. What is different about this situation that would make it so difficult? I feel like this type of interop must already be happening in other places in this package because there's already two way communication taking place.
Author
Owner

@devmil commented on GitHub (Sep 4, 2020):

Yes, wiring up the interop is not the problem. As you said, this is already solved.
The problem lies in the type registration.

QML allowes to register C++ types for usage in QML.
Qml.Net can't register a C++ type for each .Net PaintedItem class, because the number of classes has to be known at compile time already.
This means Qml.Net can register the same C++ type using different QML names (I think so, did not test that) but every registration has to use another .Net class in the end.

So assuming we have a NetQuickItem C++ class that derives from QQuickPaintedItem.
When a user wants to have a custom PaintedItem (MyPaintedItem)
Qml.Net could register NetQuickItem using "MyPaintedItem" so that it can be used in QML like MyPaintedItem {}.
What QML then does is to instantiate the C++ class (NetQuickItem) whenever it sees MyPaintedItem instantiation.
We now have to link that special situation to the MyPaintedItem .Net class. This is the tricky part.

While thinking about this:

qmlRegisterType returns a type id (https://doc.qt.io/qt-5/qqmlengine.html#qmlRegisterType). Maybe this could be used to store a mapping between type id and .net class. This would work when NetPaintedItem can access this type id at runtime.

<!-- gh-comment-id:687419733 --> @devmil commented on GitHub (Sep 4, 2020): Yes, wiring up the interop is not the problem. As you said, this is already solved. The problem lies in the type registration. QML allowes to register C++ types for usage in QML. Qml.Net can't register a C++ type for each .Net PaintedItem class, because the number of classes has to be known at compile time already. This means Qml.Net can register the same C++ type using different QML names (I think so, did not test that) but every registration has to use another .Net class in the end. So assuming we have a NetQuickItem C++ class that derives from QQuickPaintedItem. When a user wants to have a custom PaintedItem (MyPaintedItem) Qml.Net could register NetQuickItem using "MyPaintedItem" so that it can be used in QML like `MyPaintedItem {}`. What QML then does is to instantiate the C++ class (NetQuickItem) whenever it sees MyPaintedItem instantiation. We now have to link that special situation to the MyPaintedItem .Net class. This is the tricky part. While thinking about this: qmlRegisterType returns a type id (https://doc.qt.io/qt-5/qqmlengine.html#qmlRegisterType). Maybe this could be used to store a mapping between type id and .net class. This would work when NetPaintedItem can access this type id at runtime.
Author
Owner

@devmil commented on GitHub (Sep 5, 2020):

I searched the last 3 hours and tried using different private headers to get any QmlType information from C++... didn't work out.
Looking at the Qt sources it seems that the QmlType (MyPaintedItem) gets thrown away after the instance (NetQuickItem) is created so there is no way for the NetQuickitem to know which .Net class it belongs to.

The only thing I can think of, but that's really hacky, is to create n slots for QuickItem types and use them. This way we could store the associated .Net class using the slot number.
The slot number could be a template argument to NetQuickItem.
Basically like this:
b4cf9f8dac/QmlNet/qml/QQmlApplicationEngine.cpp (L79)

<!-- gh-comment-id:687632899 --> @devmil commented on GitHub (Sep 5, 2020): I searched the last 3 hours and tried using different private headers to get any QmlType information from C++... didn't work out. Looking at the Qt sources it seems that the QmlType (MyPaintedItem) gets thrown away after the instance (NetQuickItem) is created so there is no way for the NetQuickitem to know which .Net class it belongs to. The only thing I can think of, but that's really hacky, is to create n slots for QuickItem types and use them. This way we could store the associated .Net class using the slot number. The slot number could be a template argument to NetQuickItem. Basically like this: https://github.com/qmlnet/qmlnet-native/blob/b4cf9f8dacad14e74a52e26cad1915f34da792f1/QmlNet/qml/QQmlApplicationEngine.cpp#L79
Author
Owner

@dvnguyen-uofa commented on GitHub (Sep 20, 2020):

Just to let you all know. I have solved this issue by myself by implementing a custom canvas component that support a Paint signal. Developers can use this signal and draw on the SKBitmap that has been prepared by the component. Then the component will just draw that bitmap on its surface. By this way, I can have all the drawing functions that are supported by SkiaSharp (which is quite powerful) to draw my custom control.

The painful downside with this solution is I have to maintain that custom canvas by myself. I have to build and compile it for every platform that I want my software supports.

<!-- gh-comment-id:695853201 --> @dvnguyen-uofa commented on GitHub (Sep 20, 2020): Just to let you all know. I have solved this issue by myself by implementing a custom canvas component that support a Paint signal. Developers can use this signal and draw on the SKBitmap that has been prepared by the component. Then the component will just draw that bitmap on its surface. By this way, I can have all the drawing functions that are supported by SkiaSharp (which is quite powerful) to draw my custom control. The painful downside with this solution is I have to maintain that custom canvas by myself. I have to build and compile it for every platform that I want my software supports.
Author
Owner

@nguyenvuduc commented on GitHub (Sep 21, 2020):

Here is the code. The way to use it is:

  • In DotNet is you create a qml component that has this DotNetCanvas in it.
  • The vm property of the DotNetCanvas should be your .net object. The .net object must have _OnPaint method which will be called when the canvas need to be redraw.
  • In your _OnPaint method you can draw to the canvas by invoking paint_drawImage method:
           unsafe
            {
                fixed(byte* pBuffer = buffer)
                {
                    var canvas = this.Canvas as INetQObject;
                    canvas.InvokeMethod("paint_drawImage", new object[] { (long)pBuffer });
                }
            }

class DotNetCanvas : public QQuickPaintedItem
{
    Q_OBJECT
    Q_DISABLE_COPY(DotNetCanvas)

    QPainter* _painter;
    QObject* _vm;

    Q_PROPERTY(QObject* vm MEMBER _vm);
public:
    explicit DotNetCanvas(QQuickItem *parent = nullptr);
    void paint(QPainter *painter) override;
    ~DotNetCanvas() override;

    Q_INVOKABLE void paint_drawImage(long long data_);
};

DotNetCanvas::DotNetCanvas(QQuickItem *parent)
    : QQuickPaintedItem(parent)
{
    this->_vm = 0;
}

void DotNetCanvas::paint(QPainter *painter)
{
    this->_painter = painter;
    QMetaObject::invokeMethod(this->_vm, "_OnPaint", Qt::ConnectionType::DirectConnection);
}

void DotNetCanvas::paint_drawImage(long long data_)
{
    int* pInts = (int*)data_;
    int x = *(pInts++);
    int y = *(pInts++);
    int width = *(pInts++);
    int height = *(pInts++);

    QImage img((uchar*)pInts, width, height, QImage::Format_RGBA8888);
    this->_painter->drawImage(x, y, img);
}
<!-- gh-comment-id:695855141 --> @nguyenvuduc commented on GitHub (Sep 21, 2020): Here is the code. The way to use it is: - In DotNet is you create a qml component that has this DotNetCanvas in it. - The vm property of the DotNetCanvas should be your .net object. The .net object must have _OnPaint method which will be called when the canvas need to be redraw. - In your _OnPaint method you can draw to the canvas by invoking paint_drawImage method: ``` unsafe { fixed(byte* pBuffer = buffer) { var canvas = this.Canvas as INetQObject; canvas.InvokeMethod("paint_drawImage", new object[] { (long)pBuffer }); } } ``` ---------------------------------------------------------------- ``` class DotNetCanvas : public QQuickPaintedItem { Q_OBJECT Q_DISABLE_COPY(DotNetCanvas) QPainter* _painter; QObject* _vm; Q_PROPERTY(QObject* vm MEMBER _vm); public: explicit DotNetCanvas(QQuickItem *parent = nullptr); void paint(QPainter *painter) override; ~DotNetCanvas() override; Q_INVOKABLE void paint_drawImage(long long data_); }; DotNetCanvas::DotNetCanvas(QQuickItem *parent) : QQuickPaintedItem(parent) { this->_vm = 0; } void DotNetCanvas::paint(QPainter *painter) { this->_painter = painter; QMetaObject::invokeMethod(this->_vm, "_OnPaint", Qt::ConnectionType::DirectConnection); } void DotNetCanvas::paint_drawImage(long long data_) { int* pInts = (int*)data_; int x = *(pInts++); int y = *(pInts++); int width = *(pInts++); int height = *(pInts++); QImage img((uchar*)pInts, width, height, QImage::Format_RGBA8888); this->_painter->drawImage(x, y, img); } ```
Author
Owner

@Blquinn commented on GitHub (Sep 21, 2020):

@nguyenvuduc That is an interesting solution. Any what kind of performance you would get from a solution like that?

I don't like the idea of using another drawing library however, I think we should definitely try use QPainter for that...

@devmil Sounds like you're basically stuck for now without a way of getting the qml type inside of the c++ class. However, for a proof of concept, could you just manually write the classpath of the net object in a qml field? So like

MyPaintedItem {
  id: foo
  classPath: "com.github.qmlnet.MyPaintedItem"
}

Or something like that?

Obviously that's not a good long term solution, but just to get some forward momentum, it could be worth while.

<!-- gh-comment-id:696122803 --> @Blquinn commented on GitHub (Sep 21, 2020): @nguyenvuduc That is an interesting solution. Any what kind of performance you would get from a solution like that? I don't like the idea of using another drawing library however, I think we should definitely try use QPainter for that... @devmil Sounds like you're basically stuck for now without a way of getting the qml type inside of the c++ class. However, for a proof of concept, could you just manually write the classpath of the net object in a qml field? So like ``` MyPaintedItem { id: foo classPath: "com.github.qmlnet.MyPaintedItem" } ``` Or something like that? Obviously that's not a good long term solution, but just to get some forward momentum, it could be worth while.
Author
Owner

@devmil commented on GitHub (Sep 21, 2020):

Sorry I didn't mention it here, only in the PR. I finally found a way to implement this and the PR already contains a working solution.
Needs more polish and then it is ready for prime time.

https://github.com/qmlnet/qmlnet/pull/219

<!-- gh-comment-id:696273467 --> @devmil commented on GitHub (Sep 21, 2020): Sorry I didn't mention it here, only in the PR. I finally found a way to implement this and the PR already contains a working solution. Needs more polish and then it is ready for prime time. https://github.com/qmlnet/qmlnet/pull/219
Author
Owner

@devmil commented on GitHub (Dec 6, 2020):

Should be fixed now @pauldotknopf ?

<!-- gh-comment-id:739501198 --> @devmil commented on GitHub (Dec 6, 2020): Should be fixed now @pauldotknopf ?
Author
Owner

@pauldotknopf commented on GitHub (Dec 7, 2020):

Yup.

<!-- gh-comment-id:740003506 --> @pauldotknopf commented on GitHub (Dec 7, 2020): Yup.
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#126
No description provided.