In code I am dynamically adding controls (e.g. TextBox, ComboBox, Label, etc) that I would like to now loop through and get the values from each applicable (e.g. not Labels) control that a user inputted data for.
foreach (Control control in EditForm.Children)
{
values = new List<string>();
fieldName = control.Name;
if (control is ComboBox)
{
ComboBox cmb = control as ComboBox;
string value = cmb.SelectedValue.ToString();
}
}
The problem is that I get an error during runtime of
Unable to cast object of type 'System.Windows.Controls.TextBlock' to
type 'System.Windows.Controls.Control'.
Is there a more generic class I should be using instead of 'Control'? How can I loop through each control and have access to the needed values (includes the control's Name)
"UIElement" is most likely a safe choice.
If you're only interested elements of a specific type, you can also consider using Linq to filter out elements by class type (don't forget to include a "using System.Linq" directive):
foreach (ComboBox combo in EditForm.Children.OfType<ComboBox>())
{
//...
}
Related
I am currently building a game engine.
UML-Diagram:
[MainWindow]
[CNode]
[CNode2D]
[CSprite2D]
Each node has it's own properties (say "name", "width", "height"), where each node-type has different properties.
Now, I want to show the properties of my nodes like this (bottom right):
To reach this, I have a struct which represents every property like
struct propertyTemplate {
propertyTemplate (QString namei, QString valuei, QString expectedi, bool changedi = false) {
name = namei;
value = valuei;
expected = expectedi;
changed = changedi;
}
QString name;
QString value;
QString expected; //What the programm expects;
bool changed = false; //if these are the default values
};
CNode has a vector of propertyTemplate which gets filled in the constructor of CSprite2D
properties.push_back(propertyTemplate("Name", getName(), "string"));
properties.push_back(propertyTemplate("Sprite-link", "", "link"));
properties.push_back(propertyTemplate("Height", "100", "float"));
properties.push_back(propertyTemplate("Width", "100", "float"));
properties.push_back(propertyTemplate("Position X", "0", "float"));
properties.push_back(propertyTemplate("Position Y", "0", "float"));
properties.push_back(propertyTemplate("Rotation", "0", "string"));
When the properties have to be shown, I run this function:
void MainWindow::listProperties () {
if (ui->treeWidget_2->currentItem()->text(ui->treeWidget_2->columnAt(100)) == "Sprite2D") {
CSprite2D *sprite = static_cast< CSprite2D* >(getNodeByName(ui->treeWidget_2->currentItem()->text(ui->treeWidget_2->columnAt(0)))); //gets a pointer to the string judging by nam
if (sprite == NULL) { //if the return value is NULL
} else {
int counter = 0; //counter for the while-loop
//for ( auto momItem : propertyItems) delete momItem; //item
ui->treeWidget_3->clear();
propertyItems.clear();
while (counter < sprite->properties.size()) {
propertyTemplate prop = sprite->properties[counter]; //gets the current item in the property-array
propertyItems.push_back(new QTreeWidgetItem); //gets filled by the while-loop
propertyItems[propertyItems.size()-1]->setText(0, prop.name); //sets the text of the item
propertyItems[propertyItems.size()-1]->setText(1, prop.value);//set the text of the other item
propertyItems[propertyItems.size()-1]->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);;
ui->treeWidget_3->insertTopLevelItem(ui->treeWidget_3->topLevelItemCount(), propertyItems[propertyItems.size()-1]); //appends the items
counter ++;
}
}
}
}
The problem: When the user wants to change the property, the program really doesn't know what setter to call, because the real properties are stored in variables... The array of propertyTemplate is really much just for showing the properties inside of the QTreeWidget.
My toughts are that you could add a function-pointer when initialising the array, but I wasn't able to because the pointers are inside of a function... I also tried a lambda, but
properties.push_back(propertyTemplate("Rotation", "0", "string",[&this] () {setName()};));
gave me a compiler-error (I updated the constructor of propertyTemplate therefore, so don't blame the constructor ;))
I already talked to my rubber-duck and friends, and they both told me to use another approach, but I can't think of one better.
ALSO: I'm new to C++/Qt so I might just be the victim of trivial errors. In this case sorry.
I think you are going about this the wrong way. You should really use the Qt property system for a bunch of reasons, but mainly those two:
First and foremost, Qt has the MOC - the meta object compiler, which generates meta information for every object. That information contains the count of properties, their names and so on. You can iterate the properties and access detailed information about them, you can use that to populate your property editor via a model. This way it will work with arbitrary properties for any object, rather than having some static configuration you will have to go back and do for every new type or type change.
Second - for Qt properties you have QObject::property() and QObject::setProperty() which work in tandem with QVariant that will in most cases be able to handle type conversions. So all you need is to have the property name and a QVariant holding a compatible value, you don't need to know what setters to call and so on. This makes it significantly easier to get and set property values regardless of their type.
The classes you need to look into are QMetaObject and QMetaProperty, possibly also QVariant and QAbstractListModel.
Also, I see you are using GUI elements for data storage, which is a big NO-NO. Don't put data in the GUI, you are inviting future trouble. Keep the data in a data layer, and only access that from the GUI. Put the nodes in a vector, then implement a model adapter for that vector, then use model-view-delegate in the GUI.
So the structure should be something like this:
node
|
nodes vector
|
nodes model <- GUI nodes view <- node delegate
|
selected node
|
QMetaObject/QMetaProperty query
|
property model <- GUI property view <- property delegate
|
set/get
I've implemented a wxListCtrl and would like to sort the list alphabetically. Although this sounds similar as the question here, it is different with one major difference: I'm using SetItemData() and GetItemData() to store the index of a vector.
The code supplied here works nicely, but requires GetItemData(). Even though the comparison function does not refer to any of the item data, if I omit it, the sorting is not complete.
static int wxCALLBACK MyCompareFunction(long item1, long item2, long sortData) {
wxSortedListCtrl *ctrl = (wxSortedListCtrl*) sortData;
wxString a, b;
a = ctrl->GetItemText(item1, ctrl->GetSortedColumn());
b = ctrl->GetItemText(item2, ctrl->GetSortedColumn());
if( ctrl->IsSortedAscending() )
return a.CmpNoCase(b);
return b.CmpNoCase(a);
}
bool wxSortedListCtrl::SortItems(void) {
long item = -1;
for ( ;; ) {
item = GetNextItem(item);
if ( item == -1 )
break;
SetItemData(item, item);
//this is needed even though MyCompareFunction doesn't use it AT ALL.
//however it overwrites the data that I use myself...
}
return wxListCtrl::SortItems(MyCompareFunction, (long)this);
}
How would I use this sort function while maintaining the item data I've set myself with SetItemData()?
If you want to rely on the control to do the sorting (as opposed to resorting the items internally and just reinserting them into it in the correct order), you must be able to access the sort key (i.e. text in your case) via the item data. So, instead of just associating your own data with each item, associate some struct containing both the item text and your custom data with it.
Alternatively, use wxLC_VIRTUAL and just override OnGetItemXXX() to return the items in the correct order. This may sometimes be simpler than dealing with sorting the items manually and is definitely much more efficient for any non-trivial number of items.
I have some problem with speed to access to QList<qreal> property.
I have declared:
Q_PROPERTY(QList<qreal> circlePointsX READ circlePointsX NOTIFY circlePointsXChanged);
QList<qreal> circlePointsX(void) const
{
return mCirclePointsX;
}
and in QML file, I made
pArea.circlesPointsX = paintAreaHelper.circlePointsX;
and after that some code is reading point by point:
var cPointsX = circlesPointsX;
var cPointsY = circlesPointsY;
var noOfPoints = circlesPointsX.length - 4;
for (var i = 0; i <= noOfPoints; i+=4)
{
ctx.moveTo(cPointsX[i], cPointsY[i]);
ctx.lineTo(cPointsX[i+1], cPointsY[i+1]);
ctx.lineTo(cPointsX[i+2], cPointsY[i+2]);
ctx.lineTo(cPointsX[i+3], cPointsY[i+3]);
ctx.lineTo(cPointsX[i], cPointsY[i]);
}
of course the type of property is var
property var circlesPointsX;#
and assignment:
var cPointsX = circlesPointsX;
does not speed up anything, because it's just copying the reference.
I debuged it, and for every single loop access, the c++ method is called.
I would like to copy the data from c++ once and access it from "local qml copy" instead of calling c++ getter every time.
The documentation sheds some light on it:
If the sequence is exposed as a Q_PROPERTY, accessing any value in the sequence by index will cause the sequence data to be read from the QObject's property, then a read to occur. Similarly, modifying any value in the sequence will cause the sequence data to be read, and then the modification will be performed and the modified sequence will be written back to the QObject's property.
If the sequence is returned from a Q_INVOKABLE function, access and mutation is much cheaper, as no QObject property read or write occurs; instead, the C++ sequence data is accessed and modified directly.
So, your solution is to declare circlePointsX as:
Q_INVOKABLE QList<qreal> circlePointsX() const { return mCirclePointsX; }
You should drop the circlePoints property, or rename it to something else.
Nitpick: Putting void in the parameter list is a C-ism that has no place in C++. The reason for it in C was that void foo() is equivalent to void foo(...). This is no longer the case in C++.
I need to get the number after the button to increment in a for loop. For example, button1 becomes button2, etc. I have tried appending a variable which increments but C++ Builder gives an error saying "Button is not a member of TMain." Is there any way to achieve the end goal or get around this?
You can't construct new identifiers from others at run time. The compiler is correct that Button really isn't a member of your TMain class.
Instead, build the string name of the component you want, and then call your form's FindComponent method to get the component with that name.
for (int i = 1; i <= 2; ++i) {
std::string name = "Button" + IntToStr(i);
TButton* button = dynamic_cast<TButton*>(this->FindComponent(name));
}
That requires that the buttons' Name properties be set accordingly.
Another solution is to forego the component names and put your objects in a proper container, like a vector. For example, you can override the Loaded method (which is where you can be sure all your form's components have been created) and fill a vector there:
void TMain::Loaded() {
TForm::Loaded();
this->m_buttons.push_back(Button1);
this->m_buttons.push_back(Button2);
}
Now when you want to iterate over your buttons, you just iterate over the vector instead:
for (std::vector<TButton*>::const_iterator it = m_buttons.begin();
it != m_buttons.end();
++it) {
// ...
}
I'm practicing a bit with Gtk+. I've been abel to create a window, with a working menu.
I can create test objects(basically, a square, asking the user to input the side length) and store them in a vector, but I can't list them.
What I want is to show a scrolled window listing all of the stored objects, something like:
_Square 1-side:7_
_Square 2-side:25_
Until now, I could show the scrolled window with a text label, but none of the info about the stored objects.
Here's the code that I have tried:
Gtk::Dialog dialog("Listing Squares",false,true);
dialog.set_default_size(500,30);
Gtk::Button close("Close");
close.signal_clicked().connect( sigc::mem_fun(*this,&Window::onFileListButtonClose) );
Gtk::VBox* vbox = dialog.get_vbox();
Gtk::ScrolledWindow sw;
sw.set_policy(Gtk::POLICY_AUTOMATIC,Gtk::POLICY_AUTOMATIC);
/** FETCH FROM ARRAY*/
for(unsigned int i(0); i<vc.size();++i){
Gtk::Label label( "Square number " + i );
sw.add( label );
}
sw.show_all_children();
vbox->pack_start( sw );
vbox = 0;
dialog.add_action_widget(close,1);
dialog.show_all_children();
dialog.run();
[EDIT:]
1) vc is a std::vector. It is a class attribute.
2) The piece of code for asking the user to input the length of the square and storing it in vc.
void Window::onMenuFileNew(void) {
Gtk::Dialog dialog("New Square",true,true);
dialog.set_default_size(70,20);
dialog.set_has_separator(true);
Gtk::Button close("Close");
entry.set_max_length(2);
entry.set_text("");
close.signal_clicked().connect( sigc::mem_fun(*this,&Window::onFileNewButtonClose) );
Gtk::Label lab("Square side length:");
Gtk::VBox* vbox = dialog.get_vbox();
vbox->pack_start( lab );
vbox->pack_start( entry );
vbox = 0;
dialog.add_action_widget(close,1);
dialog.show_all_children();
dialog.run();
}
void Window::onFileNewButtonClose(void) {
int side = atoi( (entry.get_text()).c_str() );
vc.push_back(Cuadrado( side ));
}
Any help would be appreciated. :)
PS: Before trying to list the squares, I created some of them!
According to the documentation, the add member function accepts widgets by reference. This means that the objects you pass here must exist throughout the lifetime of the container referencing them. In the for loop, they cease to exist as soon as the loop makes one iteration, if you create the labels before the loop, they cease to exist at the end of the function. You run into the equivalent of this: Returning a reference to a local or temporary variable.
Now, this is a little bit shooting in the dark because I don't really know Gtk and I don't know if the widget is copied somewhere else, so that the original may be destructed, but it looks the way I described it above from a purely C++ point of view.
Just to make sure this is the culprit, define all your labels globally to your application and see if they appear. If they do, you'll know that you need to declare the labels in a way that they are alive (e.g. on heap) after that function/loop are over (and still can be destroyed appropriately).