You are not logged in.

Dear visitor, welcome to QtForum.org. If this is your first visit here, please read the Help. It explains in detail how this page works. To use all features of this page, you should consider registering. Please use the registration form, to register here or read more information about the registration process. If you are already registered, please login here.

1

Saturday, May 19th 2012, 4:51am

How to get to QDeclarativeView from QDeclarativeItem (or another way to get a pointer to one of my C++ objects)

Hello, I'm very new to QT and working with a sample QTQuick project a coworker set me up with. It basically is a single CustomCanvasClass that does some custom rendering. The main() code is below, first section. The custom painting is in the second section.

So I think at runtime the "viewer" instantiates the "item" (CustomCanvasClass derived from QTDeclarativeItem), and the QTDeclarativeItem overrides the paint method to do some slick rendering.

What I'd like to do is have that rendering be data driven -- so in other words in the paint event the customcanvasclass can access a pointer back to some central data object, and does its rendering accordingly. The problem is I don't have control over the instantiation of the CustomCanvasClass :QTDeclarativeItem object , so can't figure out a way to connect it to anything central.

So my question is, does a QTDeclarativeItem have a pointer back to the QTDeclarativeView (the "viewer" object)? If it did, then I could tuck some data in the viewer object (by deriving my own class from QTDeclarativeView), and then on the CustomCanvasClass paint, it could access the viewer, see what the custom values are, and paint itself accordingly.

So in summary:

Can I get to the QDeclarativeView (viewer object) from a QDeclarativeItem (my CustomCanvasClass object)?

Hopefully that makes some sense. Thanks VERY much for any help.

Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(int argc, char *argv[])
{

	QApplication app(argc, argv);

	qmlRegisterType<CustomCanvasClass>("CustomCanvasClass", 1, 0, "CustomCanvasClass");

	QDeclarativeView *viewer = new QDeclarativeView;

	viewer->setSource(QUrl::fromLocalFile("qml/sample/main.qml"));

	viewer->setFixedSize(480,272);
	viewer->show();

	return app.exec();
}


Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void CustomCanvasClass::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
	int lineThickness = (m_outerDia - m_innerDia)/2;
	QPen pen(m_color, lineThickness);
	pen.setStyle(Qt::SolidLine);
	QBrush brush(Qt::transparent);
	painter->setPen(pen);
	painter->setBrush(brush);
	painter->setRenderHints(QPainter::Antialiasing, true);

	QRectF rect(3, 3, m_outerDia, m_outerDia);

	int startAngle = DEGREE_MULTIPLIER * TWELVE_O_CLOCK_DEGREE;
	int spanAngle = DEGREE_MULTIPLIER * (m_tenth_percent * 360 / 1000);
	painter->drawArc(rect, startAngle, spanAngle);
}

2

Saturday, May 19th 2012, 10:19am

I'm not sure, but it sounds like that is a bad way to do it. The canvas shoudln't have to know anything about the view.

You could maybe pass some settings *from* the viewer *to* the canvas, but the canvas should not be pulling the setting from the viewer.

Can you change the way the canvas is instantiated by modifying the qml?
If you have a problem, CUT and PASTE your code. Do not retype or simplify it. Give a COMPLETE and COMPILABLE example of your problem. Otherwise we are all guessing the problem from a fabrication where relevant details are often missing.

3

Saturday, May 19th 2012, 10:25am

just seen this

Source code

1
2
3
4
QDeclarativeView view;
view.setSource(QUrl::fromLocalFile("MyItem.qml"));
view.show();
QObject *object = view.rootObject();

http://apidocs.meego.com/1.2/qt4/qtbinding.html

so from the same url, above, you could then do some more set up like either of these,

Source code

1
2
object->setProperty("width", 500);
QDeclarativeProperty(object, "width").write(500);

to customise your canvas painting.
If you have a problem, CUT and PASTE your code. Do not retype or simplify it. Give a COMPLETE and COMPILABLE example of your problem. Otherwise we are all guessing the problem from a fabrication where relevant details are often missing.

4

Saturday, May 19th 2012, 5:36pm

Thank you amleto.

I've been going through that qtbinding link you sent, it is extremely educational, thank you.

However, it's not *exactly* what I need... I don't need QML and C++ to talk (ie, I don't need to reference my objects inside the qml file) -- I need C++ and C++ to talk, but QML is in between.

All I need is to be able to retrieve a pointer to one of my own central objects from inside the

void CustomCanvasClass::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)

function. This should be possible, it is an object tree... I'm reading up on the QDeclarativeContext class, which seems like it *might* be for this purpose

I see that the QDeclarativeView has a pointer to the QDeclarativeEngine, and the QDeclarativeEngine contains a QDeclarativeContext. The QDeclarativeContext might be overridable and I can add my own members (?).

But the missing link is getting from the QDeclarativeItem registered via qmlRegisterType, back to the viewer (or context, or engine... any path will do).

5

Saturday, May 19th 2012, 7:11pm

as I say, that is not a good design, or good goal to try and do.

if the data is dynamic, then you should set up some signals/slot connections on the canvas to respond to data changes. The canvas should save whatever subset of this data (that it seems not to own) it needs for customised painting.
If you have a problem, CUT and PASTE your code. Do not retype or simplify it. Give a COMPLETE and COMPILABLE example of your problem. Otherwise we are all guessing the problem from a fabrication where relevant details are often missing.

6

Saturday, May 19th 2012, 7:48pm

"as I say, that is not a good design"

Why? The way I look at it, it is perfectly acceptable for your [presentation] (i.e., the viewer, widget, whatever) to have full knowledge of your [database], but not the other way around.

I prefer not to "push" the data from my database to the screen. This would be needless copying (which ends up being the big performance hit in data-driven, "snappy" applications). I want the renderer to inspect the values of the database and render accordingly. In pseudocode, as follows:

class cDatabase
{
cItem * ptr_FirstItem; // singly linked list of complex objects
cItem * ptr_LastItem;
int m_Count;
}

void MyCustomCanvas::Paint(painter * p)
{
[retrieves pointer to specific central Database object, preferably as member variable -- usually I like to send in a reference in the constructor, but doesnt seem possible here

for each cItem in ptr_Database list
painter.DrawLine to next coordinate of approipriate color
next

}

I don't want the canvas to save the subset of data internally -- this is extra work to notify and sync up the data (proportional to the size, which in my case is large), less maintainable (becuase you need to also modify sync mechanism if you change the database). The canvas' pixels are the only "saved" data the canvas will "own." Staging it some local arrays prior to issuing the rendering commands is an unnecessary (and time consuming and memory hungry) middle step. This makes the rendering non-generic (i.e., it will only work for my "database") -- but that is just fine, it's no good for anything else anyway.

So, all I really want is to pass a pointer down to the QDeclarativeItem. So far I'm able to subclass the QDeclarativeView and give it a pointer to my database, but cant get from the View to the Item. (I've tried searching the "children" of the View using FindChild -- as suggested by your very helpful link -- but no luck yet.

This post has been edited 2 times, last edit by "RonMexico" (May 19th 2012, 8:04pm) with the following reason: (fixed typos, and for clarity)


7

Saturday, May 19th 2012, 8:01pm

By the way this is an *especially* useful approach if you have multiple elements on the screen that all need to be synced up with some central dataset. If each scans the dataset and renders itself accodingly, then all you have to do is update the data and trigger a refresh. Otherwise you are looking at lots of notifications, and lots of redundant copying, formatting, and resyncing to internal data stores inside each element. I've tried these approaches side by side and the way I'm trying to do it here is *way* better (for this particular type of application).

This post has been edited 1 times, last edit by "RonMexico" (May 19th 2012, 8:10pm)


8

Saturday, May 19th 2012, 11:11pm

Why? The way I look at it, it is perfectly acceptable for your [presentation] (i.e., the viewer, widget, whatever) to have full knowledge of your [database], but not the other way around.
It's bad practice (for either to know about the other). The design is tightly coupled and rigid (inflexible). If you want to change your db implementation... oops, you need to change your gui as well. You need some dependency inversion/"programming to interfaces" to decouple the system.

if you want multiple clients to share one data set then look at implicit sharing (QSharedData and QSharedDataPointer http://doc.qt.nokia.com/4.7-snapshot/qsh…ta.html#details)

You can avoid redundant copying as an implementation detail, but you should not avoid decoupling the system.
If you have a problem, CUT and PASTE your code. Do not retype or simplify it. Give a COMPLETE and COMPILABLE example of your problem. Otherwise we are all guessing the problem from a fabrication where relevant details are often missing.

9

Sunday, May 20th 2012, 12:40am

It's not "bad practice," it's a deliberate tradeoff, for memory and speed -- and I'm actually not really sure there's anything sacrificed. I have a large (i.e., it will use up a large fraction of RAM, imagine 100,000 points to plot) amount of data and it would be a signficant performance and memory hit to reformat and copy the data to intermediate memory staging area, and then render from that staging area to the screen. Do you see how that is an intermediate step with tangible costs that is not actually necessary? Rather than (1) read from external->translate->write to local storage, (2) read from local storage->translate to rendering commands which write to pixels, I'm just doing (1) read from external->translate to rendering commands to write to pixels in one step.

Think of it this way, rather than the canvas "storing" data in an intermediate struct array, it's "storing" data on the pixels with rendering commands. What's the difference? If you change your central data store in both approaches you're going to have to change your step (1) to read the new format-- whether it is part of the visual widget itself, or some intermediate code (which counts), they are "coupled" in either case because one has to understand the other, or some intermediate code has to understand both -- whether you are editing the database, widget, or the adapter code (if any), it's roughly the same amount of work. I really don't even see the disadvantage, I'm just getting rid of the middle man. And frankly this insight has been a supercharger for my UI design -- with large amounts of data, all that copying ends up taking all your time.

Believe me, I understand separation of concerns, and love OO design, but I think if you're displaying data then your data and widget are coupled, either directly or via some adapter code.

10

Sunday, May 20th 2012, 2:06am

there's nothing that says you have to copy the data instead of passing weak references. In this case there is minimal overhead. Coupling your GUI to your data source *is* bad practice and you shouldn't pretend otherwise just because it's 'optimised'. It's your project - if you want to hack it then feel free :)
If you have a problem, CUT and PASTE your code. Do not retype or simplify it. Give a COMPLETE and COMPILABLE example of your problem. Otherwise we are all guessing the problem from a fabrication where relevant details are often missing.

11

Sunday, May 20th 2012, 3:48am

It's not bad design. Likewise feel free to write slow, bloated code for the sake of a principle that I have just explained does not apply.

12

Sunday, May 20th 2012, 10:24am

likewise, I have stated it will not be slow or bloated by using weak references. Your explanation as to why applying 'good practices and principles' to your code doesn't apply is not sound.

You show lack of understanding by stating that dependency inversion doesn't decouple a system.
You mistakenly state that an intermediate class has to understand both data source and gui (it only needs to know two interfaces).
You mistakenly state the changing data source code will lead to the same amount of coding required in a coupled system as in a decoupled one.
If you have a problem, CUT and PASTE your code. Do not retype or simplify it. Give a COMPLETE and COMPILABLE example of your problem. Otherwise we are all guessing the problem from a fabrication where relevant details are often missing.

13

Sunday, May 20th 2012, 12:48pm

Don't understand weak reference, it wikipedias as having to do with garbage collection. If you provide an short example then maybe we wont just end up agreeing to disagree.

(treating your last three statements as a,b,c)

I understand (a) and (b), but it is not poor design it is a tradeoff. I don't care if my "view" only lives to support this specific database structure, and the advantages are tremendous (pending understanding of other technique such as weak reference). Even if there isn't unnecessary copying (which I don't see yet), then some intermediate interface is just more stuff that has to be modified for changes, and only allows D (database) and V (view) to be separated more easily, specifically the V to be used with other Ds, which I will never need (these Vs are very specific and will never, ever live except to serve D).

Completely disagree with (c), because it is generally the same amount of work modifying whatever intermediate class, and the interfaces, as it is modifying my view to work with the directly "coupled" database. And if the "decoupled" approach requires you to copy/store data, I can almost guarantee you it will be more work.

And the nice part about all my V knowing the design of D is that if I add things (which is the direction these things usually change) to D, it becomes magically available to V (e.g., can immediately start using it in the paint function, it's just a new member of those linked objects to be used if desired -- or ignored with *no change*), without having to edit any intermediate code, or (shudder) to define a place to transfer it to and figure out when to do the transfer.

This post has been edited 2 times, last edit by "RonMexico" (May 20th 2012, 1:15pm) with the following reason: typos


14

Sunday, May 20th 2012, 3:33pm

code bomb...

Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#include <vector>
#include <algorithm>
#include <string>
#include <iostream>

#pragma region interfaces

template<class T>
class IVectorProvider
{
public:
virtual std::vector<T> const * getVector() const = 0;
};


template<class T>
class IGuiDataProvider
{
public:
  typedef std::vector<T> const * TDataContainer;
  virtual TDataContainer getGuiData() const = 0;
};

class IDrawable
{
public:
  virtual void draw() = 0;
};

#pragma endregion

template <class T>
class Data : public IVectorProvider<T>
{
public:

  void addDataElement(T const & elem)
  {
    m_data.push_back(elem);
  }

  std::vector<T> const * getVector() const
  {
    return &m_data;
  }

private:
  std::vector<T> m_data;
};


class Adapter : public IGuiDataProvider<int>
{
public:
  Adapter(IVectorProvider<int> * const provider)
    : m_dataSource(provider)
  {}
  
  virtual std::vector<int> const * getGuiData() const
  {
    return m_dataSource->getVector();
  }

private:
  IVectorProvider<int> const * m_dataSource;
};


class GuiThing : public IDrawable
{
public:
  GuiThing(std::string const & name, IGuiDataProvider<int> const * dataProvider)
    : m_name(name), m_guiDataHandle(dataProvider)
  {}

  void draw()
  {
    useData();
  }

private:
  void useData()
  {
    IGuiDataProvider<int>::TDataContainer container;
    container = m_guiDataHandle->getGuiData();
    
    std::cout << m_name << '\n';
    for(int i = 0; i < container->size(); ++i)
    {
      std::cout << container->at(i) << '\n'; // use the data
    }

  }

private:
  IGuiDataProvider<int> const * m_guiDataHandle;
  std::string m_name;
};


int main()
{
  Data<int> data;

  data.addDataElement(1);
  data.addDataElement(2);
  data.addDataElement(3);

  Adapter adapter(&data);

  GuiThing gt1("one", &adapter);
  GuiThing gt2("two", &adapter);
  GuiThing gt3("three", &adapter);
  GuiThing gt4("four", &adapter);


  std::vector<IDrawable*> drawables;

  drawables.push_back(&gt1);
  drawables.push_back(&gt2);
  drawables.push_back(&gt3);
  drawables.push_back(&gt4);

  std::for_each(drawables.begin(), drawables.end(), std::mem_fun(&IDrawable::draw));

  std::cin.get();
}


Now, if you only want to modify the format of data on gui side or data side then you are free to do so in a localised place. Of course this will likely introduce a copy.

As things stand, the system is uncoupled and either end can be modified freely. There is no data copying.
If you have a problem, CUT and PASTE your code. Do not retype or simplify it. Give a COMPLETE and COMPILABLE example of your problem. Otherwise we are all guessing the problem from a fabrication where relevant details are often missing.

15

Sunday, May 20th 2012, 4:25pm

We're back to agreeing to disagree. You're passing pointers to a collection just like I'm doing. The content of the items must be agreed on between viewer and database just like mine. The adapter doesn't do much for you except allow you to write other data
sources for the same view -- if you will never need to do that (I
won't) it's needless complexity.

If you change the type of the items in the collection, you have to change what you do with it (i.e., within your unspecified "use the data"). That's my point -- you can add abstractions to separate it physically all you want, but the database and the data viewer are coupled practically, in the real world. If you change your database in a way thats important to that view -- its the same amount of work inside "use the data"

Also I note that you changed your position a little. You said a few posts ago:

"The canvas should save whatever subset of this data (that it seems not to own) it needs for customised painting."

That is what I most sharply disagree with. What you propose below is fine, but not automatically "better practice" than mine. It's a good habit, and I agree things should be made reusable where it helps. But my data view (and lots of views in my experience) will never need the ineroperability provided by that adapter, because it is permanently coupled with this specific database just by the specific nature of what its showing. Anything trying to make my database/view (and lots of database/views) "uncoupled" is window dressing.

This post has been edited 2 times, last edit by "RonMexico" (May 20th 2012, 4:32pm) with the following reason: typo


16

Sunday, May 20th 2012, 4:31pm

Oh, and to connect it back to original question: how the heck would I connect
m_guiDataHandle

to a data source within the GuiThing constructor

in QT? If you tell me that I promise will create an adapter. :)

17

Sunday, May 20th 2012, 4:52pm

We're back to agreeing to disagree. You're passing pointers to a collection just like I'm doing. The content of the items must be agreed on between viewer and database just like mine. The adapter doesn't do much for you except allow you to write other data
sources for the same view -- if you will never need to do that (I
won't) it's needless complexity.

If you change the type of the items in the collection, you have to change what you do with it (i.e., within your unspecified "use the data"). That's my point -- you can add abstractions to separate it physically all you want, but the database and the data viewer are coupled practically, in the real world. If you change your database in a way thats important to that view -- its the same amount of work inside "use the data"

No, if the type of items in the Data's collection changes then you adjust/make a new adapter, and the gui code will be unchanged for *all* (gui) classes that need the data source. If there are sibling/specialised GuiThings then there is more maintenance work when the data format is modified.

Quoted


Also I note that you changed your position a little. You said a few posts ago:

"The canvas should save whatever subset of this data (that it seems not to own) it needs for customised painting."

Yes, I did that as soon as I started talking about weak references.

Quoted


That is what I most sharply disagree with. What you propose below is fine, but not automatically "better practice" than mine.

If better practice is defined as employing a higher number of generally accepted good programming practices, then it IS automatically better practice by virtue of the fact that it implements more good practices than your suggestion.


As for connecting m_guiDataHandle, I dont see how it could be connected in the ctor, but using info from previous links in this thread, it should be able to be setup via a public setter after recovering the the full GuiThing type after some casting.


Source code

1
2
3
4
5
6
7
8
9
QDeclarativeView view;
view.setSource(QUrl::fromLocalFile("MyItem.qml"));
view.show();
QObject *object = view.rootObject();

// *** todo ***
// cast the object to its 'real' type
// make the data (and any adapter)
// call some setup method on object passing the appropriate args.
If you have a problem, CUT and PASTE your code. Do not retype or simplify it. Give a COMPLETE and COMPILABLE example of your problem. Otherwise we are all guessing the problem from a fabrication where relevant details are often missing.