C++ Builder derived TComboBox to have Items by default - c++

A VCL component newbie here, so pardon me if this is a stupid question...
I'm trying to make a TComboBox component with default items in it upon dropped onto the form, i.e. a TMonthComboBox that will have the list of months in its Items list, when dropped on the form.
I've found that trying to access the Items property during Construction will result in a "Control '' has no parent window" error if I try to drop such combobox on the form.
here is (part of) the constructor:
__fastcall TMonthCombo::TMonthCombo(TComponent *Owner):TComboBox(Owner)
{
this->Style = csDropDownList; // this is okay
this->Items->Add("January"); // This is causing problem
}
I figured that the problem is stemming from the fact that the Items property is not available yet at this point of the construction.
Is there anyway to make sure the component is ready to accept values into its Items property, within the component source code itself (i.e. not adding the list items in the Properties Editor at design time)?
Before anyone tells me to "Just add the items in your Application code at run time", I have to explain that this ComboBox will be used quite frequently in many places, and Month selection is just a simple example I used to explain the problem, the actual values I want to put in the ComboBox is much more varied and most of the time, dynamic in nature. It also have to response to the user selection in varied ways.
I have tried the run-time way, but it's getting very tedious. That's why I'm making it into a component, so that it will handle itself without me having to repeatedly input multiple versions of codes just to populate the ComboBoxes.
Thanks for any help.
Edit: After tried manlio's solution, the ComboBox has an odd look in run-time:
The ComboBox has double image at run time. What have I done wrong?
__fastcall TYearCombo::TYearCombo(TComponent* Owner) : TComboBox(Owner), init_items(true)
{
}
//---------------------------------------------------------------------------
void __fastcall TYearCombo::CreateWnd()
{
unsigned short yr, mn, dy;
this->Width = 90;
this->Style = csDropDownList;
this->DropDownCount = 11;
TDate d = Today();
d.DecodeDate(&yr, &mn, &dy);
year = yr;
if (init_items)
{
init_items = false;
TComboBox::CreateWnd();
Items->BeginUpdate();
for(int i=year-5; i<=year+5; i++)
{
Items->Add(IntToStr(i));
}
this->ItemIndex = 5;
Items->EndUpdate();
}
}
//---------------------------------------------------------------------------

You can try this:
Override the CreateWnd virtual method and add a init_items private data member:
class TMonthCombo : public TComboBox
{
// ...
protected:
virtual void __fastcall CreateWnd();
private:
bool init_items;
// ...
};
Set the init_items flag:
TMonthCombo::TMonthCombo(TComponent *Owner) : TComboBox(Owner),
init_items(true)
{
// ...
}
Inside CreateWnd you can add new items:
void __fastcall TMonthCombo::CreateWnd()
{
TComboBox::CreateWnd();
if (init_items)
{
init_items = false;
Items->BeginUpdate();
Items->Add("January");
// ...
Items->EndUpdate();
}
}
Further notes:
"Control has no parent" in Create ComboBox (TComboBox requires an allocated HWND in order to store strings in its Items property).
simply cast the constructor's Owner parameter to TWinControl and assign the result to the component's Parent property isn't a solution:
TMonthCombo::TMonthCombo(TComponent *Owner) : TComBoBox(Owner)
{
Parent = static_cast<TWinControl *>(Owner);
// ...
}
the assignment solves the "Control has no parent window error" but creates another problem: the form is always the component's parent (you cannot add the form to a different container).

Related

Most efficient way to check for messages in array

I'm currently working on a chess-like game in c++ and was wondering if there were any pattern or good programming ways to check for messages between classes.
In my problem I have two classes, a boardSquare- and a GameBoard-class (BoardSquareManager). The boardsquare has functionality that when you click a square a boolean value will be set to true meaning the boardSquare has been clicked. Currently in the GameBoard-class I have an array of BoardSquares and in order to check if one boardsquare has been clicked I go through every BoardSquare each tick and see if anyone has been clicked, it looks like this.
void AGameBoard::CheckMessages()
{
for (int i = 0; i < 64; i++)
{
if (BoardSquareArray[i]->GetWasClicked())
{
iClickedTile = BoardSquareArray[i]->GetSquareNum();
BoardSquareArray[i]->DeactivateClick();
bTileWasClicked = true;
}
}
}
In my opinion it feels unnecessary to have to look through the entire array all the time just so I can get information if a tile has been clicked or not, do you know any better way of doing this on?
I appreciate all answers!
Have considered the Observer pattern? The classic Design Patterns book from E. Gamma contains a lot patterns which come from implementing GUI applications. Other patterns which are probably interesting are Command (do want to undo moves?), Decorator, Chain of Command, Composite, Strategy, Template Method. I would definitely recommend to read the book.
For now I have gone with making a static class which has a queue. I use this class to send messages from the BoardSquare to the Gameboard. (Making a static class might not be the "best" solution in long term goals but since this is a prototype I was figuring it might work out for now)
Code for Static MessageClass:
/*
* Public variables
*/
static void SendMsgToBoard(int& tile) { TileNumbers.push_back(tile); }
static int& RecieveMsgFromSquare() { return TileNumbers.front(); }
static void SquareMsgRecieved() { TileNumbers.pop_front(); }
static bool IsEmpty() { return TileNumbers.empty(); }
private:
/*
* Private Variables
*/
static std::deque<int> TileNumbers;
New Code for Boardsquare:
messagePasser::SendMsgToBoard(iSquareNum);
New Code for Gameboard:
if (!MessagePasser::IsEmpty())
{
if (IsTileOccupied)
{
//Do stuff...
}
else
//Do Other Stuff...
MessagePasser::SquareMsgRecieved();
}
Make some std::vector or std::stack and push there click events with coordinates of tile which was clicked. You can also make some onClick method which takes pointer do AGameBoard and sent to it message about clicked tile.
Search something on the internet about events handling.

Dynamic StringGrid C++ Builder

I created a program in C++Builder 6 and I have a problem now.
I have 6 files: Unit1.cpp, Unit1.h, Unit2.cpp, Unit2.h, Unit3.cpp, Unit3.h.
Unit1.cpp is file for main form.
Problem : I want to create in Function void __fastcall TForm3::Button1Click(TObject *Sender) a TStringGrid which will be visible in Unit1.cpp and Unit2.cpp. Next click should create new TStringGrid with new name(previous exist)
I tried to fix my problem, I wrote some code, but it is not enough for me.
In Unit1.h I added:
void __fastcall MyFunction(TStringGrid *Grid1);
In Unit1.cpp I added:
void __fastcall TForm1::MyFunction(TStringGrid *Grid1)
{
Grid1 = new TStringGrid(Form2);
Grid1->Parent = Form2;
}
In Unit3.cpp I added:
#include "Unit1.h"
and the Button click function is:
void __fastcall TForm3::Button1Click(TObject *Sender)
{
Form1->MyFunction(Form1->Grid); //Grid was declarated previous in Unit1.h
}
Now when I used this method I dynamically create a TStringGrid, but only one. How do I create as many TStringGrids (with unique names) as the number of times the button is pressed? (Now i must declarate TStringGrid in Unit1.h).
First, note that your code is creating multiple TStringGrids. It is just creating them all with the same dimensions in the same place on the form, so you only see the one on top.
--
What you want to be able to do (Form1->dynamically_created_TStringGrid) is not possible, but there are a couple of methods available to you to get similar behavior.
The std::vector method
Unit1.h
#ifndef Unit1H
#define Unit1H
#include <vector>
//your other includes here
class PACKAGE Form1 : public TForm
{
__published: //IDE-managed Components
//your components here
private:
/.../
std::vector<TStringGrid *> SGridVec;
public:
/.../
AnsiString AddStringGrid();
TStringGrid * GetStringGridByName(const AnsiString &Name);
TStringGrid * GetStringGridByIndex(const unsigned int Index);
}
Unit1.cpp
AnsiString TForm1::AddStringGrid()
{
SGridVec.push_back(new TStringGrid(Form2)); //Form2 is owner and handles memory management
if (SGridVec.back())
{
SGridVec.back()->Parent = Form2;
SGridVec.back()->Name = "some uniquely generated name";
return SGridVec.back()->Name;
}
return ""; //add was unsuccessful
}
TStringGrid * TForm1::GetStringGridByName(const AnsiString &Name)
{
for(std::vector<TStringGrid *>::iterator sgItr = SGridVec.begin();
sgItr != SGridVec.end();
++sgItr)
{
if (*sgItr && (*sgItr)->Name == Name)
{
return *sgItr;
}
}
return NULL; //StringGrid with Name was not found
}
TStringGrid * TForm1::GetStringGridByIndex(const unsigned int Index)
{
if (Index < SGridVec.size() && SGridVec.at(Index) != NULL)
return SGridVec.at(Index);
return NULL; //StringGrid at Index was not found
}
Using this method you could call AddStringGrid() and store the return value. Then when you wanted to manipulate a given TStringGrid on Form1 you would call GetStringGridByName and pass in the name of the TStringGrid you want to manipulate. You could also implement something very similar with a std::map, even as a public member.
The FindChildControl method
Unit1.h
#ifndef Unit1H
#define Unit1H
#include <map>
//your other includes here
class PACKAGE Form1 : public TForm
{
__published: //IDE-managed Components
//your components here
public:
/.../
AnsiString AddStringGrid();
}
Unit1.cpp
AnsiString TForm1::AddStringGrid()
{
TStringGrid *temp_ptr = new TStringGrid(Form2); //Form2 is owner and handles memory management
if (temp_ptr)
{
temp_ptr->Parent = Form2;
temp_ptr->Name = "some uniquely generated name";
return temp_ptr->Name;
}
return ""; //add was unsuccessful
}
void SomeClass::SomeFunctionThatUsesForm2sTStrinGrids(/.../)
{
/.../ //some code
TStrinGrid *temp_ptr = static_cast<TStringGrid *>(Form2->FindChildControl("name of control"));
if (temp_ptr)
{
//do stuff to the string grid
}
/.../ //some other code
}
This version basically uses the Parent -> Children relationship to find the dynamically created TStringGrid instead of storing it in a std::vector. My gut says that it is slower and less safe than the std::vector method, but I don't have any proof. It also doesn't offer any simple, reliable way to get at the StringGrids if you "forget" the unique names you gave them, while the std::vector lets you access them by index, or via an iterator if you make one available. There is GetChildren, but it does not look very intuitive to use.
--
At first I thought you would get a memory leak every time you call TForm1::MyFunction, but if I'm understanding the Builder 6 documentation correctly, that is not the case:
The TComponent class also introduces the concept of ownership that is
propagated throughout the VCL and CLX. Two properties support
ownership: Owner and Components. Every component has an Owner property
that references another component as its owner. A component may own
other components. In this case, all owned components are referenced in
the component’s Array property.
A component's constructor takes a single parameter that is used to
specify the new component's owner. If the passed-in owner exists, the
new component is added to the owner's Components list. Aside from
using the Components list to reference owned components, this property
also provides for the automatic destruction of owned components. As
long as the component has an owner, it will be destroyed when the
owner is destroyed. For example, since TForm is a descendant of
TComponent, all components owned by the form are destroyed and their
memory freed when the form is destroyed. This assumes that all of the
components on the form clean themselves up properly when their
destructors are called.
[.pdf page 53]
So even though you are assigning a newed object to Grid1 every time you pass it in to that function, those objects are still owned by Form2 and will be cleaned up when Form2 is destructed.
All that said, you should be aware that, if you stick with the implementation you have posted in the OP, you won't be able to manipulate any of your string grids except for the last one unless you access them from Form2->Array.

A dependency loop

I've designed an object inherits from CDialog (called NBDialog, and some derived objects of controls, such as CEdit, CDateTimeCtrl, CComboBox etc.
The NBDialog is one project, and the controls are in other projects.
Naturally, All of the controls are put on the dialog and use dialog's methods, so I have to
#include NBDialog.h, and to add its .lib file for the linker.
I also want to handle all those controls from the dialog, so I wrote in NBDialog.h the following lines:
class NBCommonEditBox;
class NBDateTimeCtrl;
class NBCommonComboBox;
CMapWordToOb* NBGetEditBoxMap();
NBCommonEditBox* NBGetEditBoxById(unsigned long ID);
CMapWordToOb* NBGetDateTimeMap();
NBDateTimeCtrl* NBGetDateTimeById(unsigned long ID);
CMapWordToOb* NBGetComboBoxMap();
NBCommonComboBox* NBGetComboBoxById(unsigned long ID);
This way NBDialog.h doesn't know the context of the object, but it knows they are exist and stores them in the maps.
Now I want to extend the NBDialog project and add a method which will get the print information of all controls, so all objects which inhertied from NBDialog will be able to use this method. The print information is defined in the controls implementation.
EDIT: If I write this method in NBDialog.cpp, I can't compile it, because NBDialog doesn't know the context of the controls' classes:
CStringList* NBDialog::NBGetMainTexts()
{
CStringList* mainTexts = new CStringList();
POSITION pos;
WORD key;
NBCommonEditBox* currEdit = NULL;
for (pos = this->NBGetEditBoxMap()->GetStartPosition(); pos != NULL;)
{
this->NBGetEditBoxMap()->GetNextAssoc(pos, key, (CObject*&)currEdit);
currEdit->NBStringsToPrint(mainTexts);
}
return mainTexts;
}
Is there a way to write the desired method?
Easiest way is to define an interface for this and add that interface instead of the CObject. The interface can offer a method to get hold of the control itself. Don;t be afraid of multiple inheritance - yes it can have a slight performance penalty but it is not going to be an issue for you. In this case it will be similar to interface inheritance in Java since you would use a pure interface.
You could also implement this in a similar way that avoids multiple inheritance but it adds more complexity that you don't need.
// Interface goes in the NBDialog project
class INBControl {
public:
virtual ~INBControl() = 0;
virtual CWnd* getWnd() = 0;
virtual void getStringsToPrint(CStringList& strings) = 0;
};
inline INBControl::~INBControl() {}
class NBCommonComboBox : public CComboBox, public INBControl
{
public:
// ... stuff ...
virtual CWnd* getWnd() {
return this;
}
virtual void getStringsToPrint(CStringList& strings) {
strings.AddTail("foo"); // for example
}
};
// NBDialog
#include <map>
class NBDialog : public CDialog
{
public:
// .. stuff ..
private:
typedef std::map<int, INBControl*> ControlMap;
ControlMap control_map_;
};
void NBDialog::addNBControl(INBControl* control, int id)
{
CWnd* wnd = control->getWnd();
// Do stuff with the control such as add it
control_map_[id] = control;
}
// let the caller be responsible for [de]allocation of the string list
void NBDialog::NBGetMainTexts(CStringList& texts)
{
ControlMap::iterator i = control_map_.begin();
ControlMap::iterator e = control_map_.end();
for(; i != e; ++i) {
i->second->getStringsToPrint(texts);
}
}
Alternatively use a custom windows message and iterate all the controls, down-casting to CWnd and using SendMessage on its HWND. Each control will need to handle your custom windoes mesaage. You could pass a pointer to the string list in the LPARAM of the message. This apprach is flexible but somewhat brittle/unsafe and could crash if you end up using the same message ID for something else by accident.
Your implementation file (NBDialog.cpp) is free to #include the necessary headers to make this work (presumably things like NBCommonComboBox.h, etc.) Because the .cpp file isn't #include'd by anything you won't cause any circular include problems.

Refactoring advice: How to avoid type checking in this OO design

I'm looking for advice on refactoring to improve my class design and avoid type checking.
I am using the Command design pattern to construct a menu tree. An item in the menu could be of various types (e.g., an immediate action [like "Save"], a toggle on/off property which displays with check/icon depending on its state [like "italics"], etc.). Crucially, there are also submenus, which replace (rather than displaying off to the side of) the current menu on the screen. These submenus of course contain their own list of menu items, which could have more nested submenus.
The code is something like (all public for simplicity in presentation):
// Abstract base class
struct MenuItem
{
virtual ~MenuItem() {}
virtual void Execute() = 0;
virtual bool IsMenu() const = 0;
};
// Concrete classes
struct Action : MenuItem
{
void Execute() { /*...*/ }
bool IsMenu() const { return false; }
// ...
};
// ... other menu items
struct Menu : MenuItem
{
void Execute() { /* Display menu */ }
bool IsMenu() const { return true; }
// ...
std::vector<MenuItem*> m_items;
typedef std::vector<MenuItem*>::iterator ItemIter;
};
The main menu is just an instance of Menu, and a separate class keeps track of the menu position, including how to go into and out of submenus:
struct Position
{
Position( Menu* menu )
: m_menu( menu )
{
// Save initial position
m_pos.push_back( MenuPlusIter( m_menu, m_menu->m_items.begin() ) );
}
// Ignore error conditions for simplicity
void OnUpPressed() { m_pos.back().iter--; }
void OnDownPressed() { m_pos.back().iter++; }
void OnBackPressed() { m_pos.pop_back(); }
void OnEnterPressed()
{
MenuItem* item = *m_pos.back().iter;
// Need to behave differently here if the currently
// selected item is a submenu
if( item->IsMenu() )
{
// dynamic_cast not needed since we know the type
Menu* submenu = static_cast<Menu*>( item );
// Push new menu and position onto the stack
m_pos.push_back( MenuPlusIter( submenu, submenu->m_items.begin() ) );
// Redraw
submenu->Execute();
}
else
{
item->Execute();
}
}
private:
struct MenuPlusIter
{
Menu* menu;
Menu::ItemIter iter;
MenuPlusIter( Menu* menu_, Menu::ItemIter iter_ )
: menu( menu_ )
, iter( iter_ )
{}
};
Menu* m_menu;
std::vector<MenuPlusIter> m_pos;
};
The key function is Position::OnEnterPressed(), where you see an explicit type check in the call to MenuItem::IsMenu() and then a cast to the derived type. What are some options to refactor this to avoid the type check and cast?
IMO, the refactoring starting point would be these statements:
1. m_pos.push_back( MenuPlusIter( m_menu, m_menu->m_items.begin() ) );
2. m_pos.push_back( MenuPlusIter( submenu, submenu->m_items.begin() ) );
The fact that the same kind of statement repeat itself is, IMO, the sign for the need to refactor that.
If you could factor (1) in a method of your base class, and then override it in the derived class to take into account the specific behavior (2), then you could just put this in Execute.
Correct me if I am wrong: the idea is the a menu has items, and each item has got an action associated to it that is triggered when some event is detected.
Now, when the item you select is a submenu, the the Execute action has the meaning: activate the submenu (I am using activate in a generic sense). When the item is not a submenu, then Execute is a different beast.
I don't have a full comprehension of your menu system, but it seems to me you have a sort of hierarchy menu/submenu (the positions), and some actions that are triggered depending of the kind of node.
What I envision is that the menu/submenu relationship is a hierarchy that allows you to define leaf-nodes (when you don't have a submenu), and non-leaf-nodes (the submenu). A leaf node invoke an action, a non-leaf-node invoke a different kind of action which deals with activating a submenu (this action goes back to the menu system, so you do not encapsulate knowledge about the menu system in it, you simply relay the action to menu system).
Don't know if this makes sense to you.
An alternative would be to expose a method in Position which enables a Menu to be pushed onto the stack, and call that method at the start of Menu:Execute. Then the body of OnEnterPressed just becomes
(*m_pos.back().iter)->Execute();
This probably isn't the response you were looking for, but in my opinion your solution is by far superior to any solution that doesn't involve type checking.
Most C++ programmers are offended by the idea that you need to check an object's type in order to decide what to do with it. Yet in other languages like Objective-C and most weakly typed script languages this is actually highly encouraged.
In your case I think using the type check is well chosen since you need the type information for the functionality of Position. Moving this functionality into one of the MenuItem subclasses would IMHO violate competence separation. Position is concerned with the viewing and controller part of your menu. I don't see why the model classes Menu or MenuItem should be concerned with that. Moving to a no-typecheck solution would decrease code quality in terms of object orientation.
What you need is the ability to express "either an action or a menu", which is very cumbersome to write using polymorphism if actions and menus have very different interfaces.
Instead of trying to force them into a common interface (Execute is a poor name for the submenu method), I'd go further than you and use dynamic_cast.
Also, dynamic_cast is always superior to a flag and static_cast. Actions do not neet to tell the world that they aren't submenus.
Rewritten in the most idiomatic C++, this gives the following code. I use std::list because of its convenience methods splice, insert and remove which don't invalidate iterators (one of the few good reasons for using linked lists). I also use std::stack for keeping track of the open menus.
struct menu_item
{
virtual ~menu_item() {}
virtual std::string label() = 0;
...
};
struct action : menu_item
{
virtual void execute() = 0;
};
struct submenu : menu_item
{
// You should go virtual here, and get rid of the items member.
// This enables dynamically generated menus, and nothing prevents
// you from having a single static_submenu class which holds a
// vector or a list of menu_items.
virtual std::list<menu_item*> unfold() = 0;
};
struct menu
{
void on_up() { if (current_item != items.begin()) current_item--; }
void on_down() { if (++current_item == items.end()) current_item--; }
void on_enter()
{
if (submenu* m = dynamic_cast<submenu*>(current_item))
{
std::list<menu_item*> new_menu = m->unfold();
submenu_stack.push(submenu_info(*current_item, new_menu));
items.splice(current_item, new_menu);
items.erase(current_item);
current_item = submenu_stack.top().begin;
redraw(current_item, items.end());
}
else if (action* a = dynamic_cast<action*>(current_item))
a->execute();
else throw std::logic_error("Unknown menu entry type");
// If we were to add more else if (dynamic_cast) clauses, this
// would mean we don't have the right design. Here we are pretty
// sure this won't happen. This is what you say to coding standard
// nazis who loathe RTTI.
}
void on_back()
{
if (!submenu_stack.empty())
{
const submenu_info& s = submenu_stack.top();
current_item = items.insert(items.erase(s.begin, s.end), s.root);
submenu_stack.pop();
redraw(current_item, items.end());
}
}
void redraw(std::list<menu_item*>::iterator begin,
std::list<menu_item*>::iterator end)
{
...
}
private:
std::list<menu_item*> items;
std::list<menu_item*>::iterator current_item;
struct submenu_info
{
submenu* root;
std::list<menu_item*>::iterator begin, end;
submenu_info(submenu* root, std::list<menu_item*>& m)
: root(root), begin(m.begin()), end(m.end())
{}
};
std::stack<submenu_info> submenu_stack;
};
I tried to keep the code straightforward. Feel free to ask if something is unclear.
[About iterator invalidation when doing splice, see this question (tl;dr: it is OK to splice and keep the old iterators provided you don't use a too old compiler).]
The language already provides this mechanism- it's dynamic_cast. However, in a more general sense, the inherent flaw in your design is this:
m_pos.push_back( MenuPlusIter( submenu, submenu->m_items.begin() ) );
It should go in the Execute() function, and refactor as necessary to make that happen.

Properties editor design pattern?

Warning: This is super in-depth. I understand if you don't even want to read this, this is mostly for me to sort out my thought process.
Okay, so here's what I'm trying to do. I've got these objects:
When you click on one (or select several) it should display their properties on the right (as shown). When you edit said properties it should update the internal variables immediately.
I'm trying to decide on the best way to do this. I figure the selected objects should be stored as a list of pointers. It's either that, or have an isSelected bool on each object, and then iterate over all of them, ignoring the non-selected ones, which is just inefficient. So we click on one, or select several, and then the selectedObjects list is populated. We then need to display the properties. To keep things simple for the time being, we'll assume that all objects are of the same type (share the same set of properties). Since there aren't any instance-specific properties, I figure we should probably store these properties as static variables inside the Object class. Properties basically just have a name (like "Allow Sleep"). There is one PropertyManager for each type of property (int,bool,double). PropertyManagers store all the values for properties of their respective type (this is all from the Qt API). Unfortunately, because PropertyManagers are required to create Properties I can't really decouple the two. I suppose this means that I have to place the PropertyManagers with the Properties (as static variables). This means we have one set of properties, and one set of property managers to manage all the variables in all the objects. Each property manager can only have one callback. That means this callback has to update all the properties of its respective type, for all objects (a nested loop). This yields something like this (in pseudo-code):
function valueChanged(property, value) {
if(property == xPosProp) {
foreach(selectedObj as obj) {
obj->setXPos(value);
}
} else if(property == ...
Which already bothers me a little bit, because we're using if statements where we shouldn't need them. The way around this would be to create a different property manager for every single property, so that we can have unique callbacks. This also means we need two objects for each property, but it might be a price worth paying for cleaner code (I really don't know what the performance costs are right now, but as I know you'll also say -- optimize when it becomes a problem). So then we end up with a ton of callbacks:
function xPosChanged(property, value) {
foreach(selectedObj as obj) {
obj->setXPos(value);
}
}
Which eliminates the entire if/else garbage but adds a dozen more event-listeners. Let's assume I go with this method. So now we had a wad of static Properties, along with their corresponding static PropertyManagers. Presumably I'd store the list of selected objects as Object::selectedObjects too since they're used in all the event callbacks, which logically belong in the object class. So then we have a wad of static event callbacks too. That's all fine and dandy.
So now when you edit a property, we can update the interal variables for all the selected objects via the event callback. But what happens when the internal variable is updated via some other means, how do we update the property? This happens to be a physics simulator, so all the objects will have many of their variables continuously updated. I can't add callbacks for these because the physics is handled by another 3rd party library. I guess this means I just have to assume all the variables have been changed after each time step. So after each time step, I have to update all the properties for all the selected objects. Fine, I can do that.
Last issue (I hope), is what values should we display when multiple objects are selected an there is an inconsistency? I guess my options are to leave it blank/0 or display a random object's properties. I don't think one option is much better than the other, but hopefully Qt provides a method to highlight such properties so that I can at least notify the user. So how do I figure out which properties to "highlight"? I guess I iterate over all the selected objects, and all their properties, compare them, and as soon as there is a mismatch I can highlight it. So to clarify, upon selected some objects:
add all objects to a selectedObjects list
populate the properties editor
find which properties have identical values and update the editor appropriately
I think I should store the properties in a list too so that I can just push the whole list onto the properties editor rather than adding each property individually. Should allow for more flexibility down the road I think.
I think that about covers it... I'm still not certain how I feel about having so many static variables, and a semi-singleton class (the static variables would be initialized once when the first object is created I guess). But I don't see a better solution.
Please post your thoughts if you actually read this. I guess that's not really a question, so let me rephrase for the haters, What adjustments can I make to my suggested design-pattern to yield cleaner, more understandable, or more efficient code? (or something along those lines).
Looks like I need to clarify. By "property" I mean like "Allow Sleeping", or "Velocity" -- all objects have these properties -- their VALUES however, are unique to each instance. Properties hold the string that needs to be displayed, the valid range for the values, and all the widget info. PropertyManagers are the objects that actually hold the value. They control the callbacks, and the value that's displayed. There is also another copy of the value, that's actually used "internally" by the other 3rd party physics library.
Trying to actually implement this madness now. I have an EditorView (the black area drawing area in the image) which catches the mouseClick event. The mouseClick events then tells the physics simulator to query all the bodies at the cursor. Each physics body stores a reference (a void pointer!) back to my object class. The pointers get casted back to objects get pushed onto a list of selected objects. The EditorView then sends out a signal. The EditorWindow then catches this signal and passes it over to the PropertiesWindow along with the selected objects. Now the PropertiesWindow needs to query the objects for a list of properties to display... and that's as far as I've gotten so far. Mind boggling!
The Solution
/*
* File: PropertyBrowser.cpp
* Author: mark
*
* Created on August 23, 2009, 10:29 PM
*/
#include <QtCore/QMetaProperty>
#include "PropertyBrowser.h"
PropertyBrowser::PropertyBrowser(QWidget* parent)
: QtTreePropertyBrowser(parent), m_variantManager(new QtVariantPropertyManager(this)) {
setHeaderVisible(false);
setPropertiesWithoutValueMarked(true);
setIndentation(10);
setResizeMode(ResizeToContents);
setFactoryForManager(m_variantManager, new QtVariantEditorFactory);
setAlternatingRowColors(false);
}
void PropertyBrowser::valueChanged(QtProperty *property, const QVariant &value) {
if(m_propertyMap.find(property) != m_propertyMap.end()) {
foreach(QObject *obj, m_selectedObjects) {
obj->setProperty(m_propertyMap[property], value);
}
}
}
QString PropertyBrowser::humanize(QString str) const {
return str.at(0).toUpper() + str.mid(1).replace(QRegExp("([a-z])([A-Z])"), "\\1 \\2");
}
void PropertyBrowser::setSelectedObjects(QList<QObject*> objs) {
foreach(QObject *obj, m_selectedObjects) {
obj->disconnect(this);
}
clear();
m_variantManager->clear();
m_selectedObjects = objs;
m_propertyMap.clear();
if(objs.isEmpty()) {
return;
}
for(int i = 0; i < objs.first()->metaObject()->propertyCount(); ++i) {
QMetaProperty metaProperty(objs.first()->metaObject()->property(i));
QtProperty * const property
= m_variantManager->addProperty(metaProperty.type(), humanize(metaProperty.name()));
property->setEnabled(metaProperty.isWritable());
m_propertyMap[property] = metaProperty.name();
addProperty(property);
}
foreach(QObject *obj, m_selectedObjects) {
connect(obj, SIGNAL(propertyChanged()), SLOT(objectUpdated()));
}
objectUpdated();
}
void PropertyBrowser::objectUpdated() {
if(m_selectedObjects.isEmpty()) {
return;
}
disconnect(m_variantManager, SIGNAL(valueChanged(QtProperty*, QVariant)),
this, SLOT(valueChanged(QtProperty*, QVariant)));
QMapIterator<QtProperty*, QByteArray> i(m_propertyMap);
bool diff;
while(i.hasNext()) {
i.next();
diff = false;
for(int j = 1; j < m_selectedObjects.size(); ++j) {
if(m_selectedObjects.at(j)->property(i.value()) != m_selectedObjects.at(j - 1)->property(i.value())) {
diff = true;
break;
}
}
if(diff) setBackgroundColor(topLevelItem(i.key()), QColor(0xFF,0xFE,0xA9));
else setBackgroundColor(topLevelItem(i.key()), Qt::white);
m_variantManager->setValue(i.key(), m_selectedObjects.first()->property(i.value()));
}
connect(m_variantManager, SIGNAL(valueChanged(QtProperty*, QVariant)),
this, SLOT(valueChanged(QtProperty*, QVariant)));
}
With a big thanks to TimW
Did you have a look at Qt's (dynamic) property system?
bool QObject::setProperty ( const char * name, const QVariant & value );
QVariant QObject::property ( const char * name ) const
QList<QByteArray> QObject::dynamicPropertyNames () const;
//Changing the value of a dynamic property causes a
//QDynamicPropertyChangeEvent to be sent to the object.
function valueChanged(property, value) {
foreach(selectedObj as obj) {
obj->setProperty(property, value);
}
}
Example
This is an incomplete example to give you my idea about the property system.
I guess SelectableItem * selectedItem must be replaced with a list of items in your case.
class SelectableItem : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName );
Q_PROPERTY(int velocity READ velocity WRITE setVelocity);
public:
QString name() const { return m_name; }
int velocity() const {return m_velocity; }
public slots:
void setName(const QString& name)
{
if(name!=m_name)
{
m_name = name;
emit update();
}
}
void setVelocity(int value)
{
if(value!=m_velocity)
{
m_velocity = value;
emit update();
}
}
signals:
void update();
private:
QString m_name;
int m_velocity;
};
class MyPropertyWatcher : public QObject
{
Q_OBJECT
public:
MyPropertyWatcher(QObject *parent)
: QObject(parent),
m_variantManager(new QtVariantPropertyManager(this)),
m_propertyMap(),
m_selectedItem(),
!m_updatingValues(false)
{
connect(m_variantManager, SIGNAL(valueChanged(QtProperty*, QVariant)), SLOT(valueChanged(QtProperty*,QVariant)));
m_propertyMap[m_variantManager->addProperty(QVariant::String, tr("Name"))] = "name";
m_propertyMap[m_variantManager->addProperty(QVariant::Int, tr("Velocity"))] = "velocity";
// Add mim, max ... to the property
// you could also add all the existing properties of a SelectableItem
// SelectableItem item;
// for(int i=0 ; i!=item.metaObject()->propertyCount(); ++i)
// {
// QMetaProperty metaProperty(item.metaObject()->property(i));
// QtProperty *const property
// = m_variantManager->addProperty(metaProperty.type(), metaProperty.name());
// m_propertyMap[property] = metaProperty.name()
// }
}
void setSelectedItem(SelectableItem * selectedItem)
{
if(m_selectedItem)
{
m_selectedItem->disconnect( this );
}
if(selectedItem)
{
connect(selectedItem, SIGNAL(update()), SLOT(itemUpdated()));
itemUpdated();
}
m_selectedItem = selectedItem;
}
private slots:
void valueChanged(QtProperty *property, const QVariant &value)
{
if(m_updatingValues)
{
return;
}
if(m_selectedItem && m_map)
{
QMap<QtProperty*, QByteArray>::const_iterator i = m_propertyMap.find(property);
if(i!=m_propertyMap.end())
m_selectedItem->setProperty(m_propertyMap[property], value);
}
}
void itemUpdated()
{
m_updatingValues = true;
QMapIterator<QtProperty*, QByteArray> i(m_propertyMap);
while(i.hasNext())
{
m_variantManager->next();
m_variantManager->setValue(
i.key(),
m_selectedItem->property(i.value()));
}
m_updatingValues = false;
}
private:
QtVariantPropertyManager *const m_variantManager;
QMap<QtProperty*, QByteArray> m_propertyMap;
QPointer<SelectableItem> m_selectedItem;
bool m_updatingValues;
};
Calm down, your code has not O(n^2) complextity. You have a nested loop, but only one counts to N (the number of objects), the other counts to a fixed number of properties, which is not related to N. So you have O(N).
For the static variables, you write "there aren't any instance-specific properties", later you write about updates of the individual properties of your objects, which are exactly instance-specific properties. Maybe you are confusing the "class Properties" (which is of course shared among all properties) with the individual properties? So I think you don't need static members at all.
Do you want to display changes to the objects only if they appear, or do you want a continuos display? If your hardware is able to handle the latter, I would recommend going that way. In that case, you have to iterate over all objects anyway and update them along the way.
Edit: The difference is that in the former (update on change) the drawing is initiated by the operation of changing the values, for example a object movement. For the latter, a continuos display, you would add a QTimer, which fires say 60 times a second and calls a SLOT(render()) which does the actual rendering of all objects. Depending on the rate of changes this may actually be faster. And it is probably easier to implement.
Another possibilty is let Qt handle the whole drawing, using a Graphics View, which handles the objects-to-draw internally in a very efficient tree structure. Take a look at
http://doc.trolltech.com/4.5/graphicsview.html
If you want to display only the changes, you could use individual callbacks for each properties value. Each time the value of a property is changed (in this case making the properties vlaues private and using setSomeThing(value)), you call the update function with an emit(update()). If you are absolutly concernd about emit being slow, you could use "real" callbacks via function pointers, but I don't recommend that, Qt's connect/signal/slot is so much easier to use. And the overhead is in most cases really neglible.