How to prevent user from entering invalid values in wxGrid cell? - c++

I'd like to create wxGrid where user can edit some cells, but prohibit entering incorrect valued. For example, only strings of length 4 could be entered there. So, if user enter string of another length, I'd like to show a error message and return to cell editor. How to do it?
If I handle a cell change event EVT_GRID_CELL_CHANGE, for example
void Frame::OnGridCellChange(wxGridEvent& event)
{
int r = event.GetRow(); // get changed cell
int c = event.GetCol(); // get changed cell
if (Grid->GetCellValue(r, c).length() != 4)
{Error E (this);
/* Create the Error message */
E.ShowModal();
// The error message shown, uses clicks OK
// So, what to do here?
}
Grid->ShowCellEditControl(); is not a solution because cell change won't be generated if user edit nothing, but just click another cell - incorrect value would appear in Grid.
Handling EVT_GRID_EDITOR_HIDDEN seems not suitable since it appears before new value actually saved to cell.

You need to use your own specialization of the cell editor.
http://docs.wxwidgets.org/trunk/classwx_grid_cell_editor.html

Perhaps using an event of type wxEVT_GRID_CELL_CHANGING would work for you? If the string that is returned by calling GetString() on the event is not four characters long then you can veto the event, something like:
void Frame::OnGridCellChanging(wxGridEvent& event)
{
if (event.GetString().length() != 4)
{
//Veto the event so the change is not stored
event.Veto();
Error E (this);
E.ShowModal();
}
This does seem to require a wxWidgets 2.9.x release however.

Related

Make ShowHint work on custom control with several different Rects, each with their own Hint

Using C++ Builder 2009
I have a custom control that inherits from TCustomControl, on which I paint several squares (TRect) with content etc.
I now wanted to show Hint as I hover over every square, but I'm not sure how to implement this best.
I attempted to simply use TCustomControl's ShowHint, and change Hint as I hover over the squares, but the problem is that the control doesn't show hint anymore after it first disappears, unless I leave the control and come back to it.
I hoped I could simply 'reset' it's state while hovering from one square to another but it doesn't work.
Assuming my approach is wrong to start with, kindly let me know what I should do to get the desired effect ?
if (State == rsHover && Item->FState != rsHover) // Not in the rsHover state yet, but going to
{
if (Item->Hint.Length())
{
if (ShowHint)
{
// Attempt to reset Hint's internal working, to no avail
Hint = L"" ;
ShowHint = false ;
}
Hint = Item->Hint ;
ShowHint = true ;
}
else
{
ShowHint = false ;
}
}
else if (State != rsHover)
{
ShowHint = false ;
}
The correct way to implement this feature is to make your component handle the CM_HINTSHOW message. The message's LParam value will be a pointer to a THintInfo record, whose fields you can freely modify as needed (in particular, HintStr and CursorRect).
To access the record, you can either
type-cast the LParam directly to THintInfo*.
type-cast the entire TMessage to TCMHintShow, and then access its HintInfo field.
By defining your own CursorRect rectangles, you can "[divide your control] into several hint regions", each with a different HintStr value. The CursorPos field indicates the mouse's current position within the control. When the mouse moves outside of the current CursorRect, the control will receive a new CM_HINTSHOW message, and you can update the CursorRect and HintStr fields as needed.

Accessing a Combobox inside a dataGridView Column?

I'm working on a scheduling program, and inside the dataGridView, we have a few ComboBox Columns that are populated by 3 entries upon creation, but I wanted to be able to add more as the user creates them, but I have no idea how you would access the combobox data. Any help is appreciated!
// this is initialized in a separate part.
/* System::Windows::Forms::DataGridView^ dataGridView;*/
System::Windows::Forms::DataGridViewComboBoxColumn^ newCol =
(gcnew System::Windows::Forms::DataGridViewComboBoxColumn());
dataGridView->Columns->AddRange(gcnew cli::array< System::Windows::Forms::DataGridViewComboBoxColumn^ >(1) {newCol});
// add the choices to the boxes.
newCol->Items->AddRange("User inputted stuff", "More stuff", "Add New...");
Solution
If you have access to the data from the user entry and you know the column index for the DataGridViewComboBoxColumn, you should be able to just do the following wherever needed:
DataGridViewComboBoxColumn^ comboboxColumn = dataGridView->Columns[the_combobox_column_index];
if (comboboxColumn != nullptr)
{
comboboxColumn->Items->Add("the new user entry");
}
Comments Response
how could you change the selected index of that combobox (the one that
the edit was triggered on)? [...] we want it so that when the new item
is added the selected index is set to that new item).
Couple of ways come to mind.
Add a single line within the if-statement of the above code. This will set the default displayed value for each DataGridViewComboBoxCell in the DataGridViewComboBoxColumn.
if (comboboxColumn != nullptr)
{
comboboxColumn->Items->Add("the new user entry");
comboboxColumn->DefaultCellStyle->NullValue = "the new user entry";
}
Pros: Clean, efficient. Previous user-selected values are left intact. The cell's FormattedValue will display the new user value by default if no other selection has been made.
Cons: Doesn't actually set a cell's selected value, so Value will return null on cells not explicitly user-selected.
Actually set the value of certain cells (based on your criteria) to the user-added value.
if (comboboxColumn != nullptr)
{
comboboxColumn->Items->Add("the new user entry");
for (int i = 0; i < dataGridView->Rows->Count; i++)
{
DataGridViewComboBoxCell^ cell = dataGridView->Rows[i]->Cells[the_combobox_column_index];
if ( cell != nullptr /* and your conditions are met */ )
{
cell->Value = "the new user entry";
}
}
}
Pros: The Value of targeted cells is actually set to the new user value.
Cons: Logic deciding which cells should be affected is more complicated.

How can I dynamically re-create a wxMenu (sub menu) with a variable number of items?

I want to create a list of COM ports in a sub menu that is updated every time the sub menu is viewed.
My plan:
Create a list of objects with data about each detected port, up to 32 object pointers. Example: comDetected *COMsFound[MAX_COM_DETECT]; (working)
Delete() old menu entries (working)
Create a new menu upon EVT_MENU_OPEN() with AppendRadioItem() (working)
Use EVT_MENU() to run the same function for each COM port selection
How do I determine in the event handling function (from wxCommandEvent?) which menu option caused the event? Without this information, I will need 32 separate functions.
Is there a more dynamic way to create the objects and events to avoid the arbitrary limit of 32 I have created?
Edit - This is what I have now for menu re-creation, which seems to be working:
Re-edit - not so good, as explained by bogdan
void FiltgenFrame::OnMenuOpen(wxMenuEvent& event)
{
//fill in COM port menu when opened
if(event.GetMenu() == COMSubMenu)
{
int i;
wxString comhelp;
//re-scan ports
comport->getPorts();
if(comport->COMdetectChanged == 1)
{
comport->currentCOMselection = 0; //when menu is regenerated, selection returns to 0
//get rid of old menu entries
for(i = 0; i < comport->oldnumCOMsFound; i++)
{
COMSubMenu->Delete(FILTGEN_COM1 + i);
COMSubMenu->Unbind(wxEVT_MENU, [i](wxCommandEvent&)
{
logMsg(DBG_MENUS, ACT_NORMAL, "menu COM select index: %d\n", i);
}, FILTGEN_COM1 + i);
}
//add new menu entries
for(i = 0; i < comport->numCOMsFound; i++)
{
comhelp.Printf("Use %s", comport->COMsFound[i]->name);
COMSubMenu->AppendRadioItem(FILTGEN_COM1 + i, comport->COMsFound[i]->name, comhelp);
COMSubMenu->Bind(wxEVT_MENU, [i](wxCommandEvent&)
{
comport->currentCOMselection = i;
logMsg(DBG_MENUS, ACT_NORMAL, "menu COM select index: %d\n", i);
}, FILTGEN_COM1 + i);
}
}
}
}
Edit - re-worked code 1-29-15. Broke apart OnMenuOpen and recreateCOMmenu due to factors unrelated to this question. Added COMselectionHandler because of advice.
void FiltgenFrame::COMselectionHandler(wxCommandEvent& event)
{
comport->currentCOMselection = event.GetId() - FILTGEN_COM1;
logMsg(DBG_MENUS, ACT_NORMAL, "COM menu select index: %d\n", comport->currentCOMselection);
}
void FiltgenFrame::recreateCOMmenu()
{
logMsg(DBG_MENUS, ACT_NORMAL, "FiltgenFrame::recreateCOMmenu():\n");
int i;
wxString comhelp;
//re-scan ports
comport->getPorts();
if(comport->COMdetectChanged == 1)
{
comport->currentCOMselection = 0; //when menu is regenerated, selection returns to 0
//get rid of old menu entries
for(i = 0; i < comport->oldnumCOMsFound; i++)
{
COMSubMenu->Delete(FILTGEN_COM1 + i);
COMSubMenu->Unbind(wxEVT_MENU, &FiltgenFrame::COMselectionHandler, this, FILTGEN_COM1 + i);
}
//add new menu entries
for(i = 0; i < comport->numCOMsFound; i++)
{
comhelp.Printf("Use %s", comport->COMsFound[i]->name);
COMSubMenu->AppendRadioItem(FILTGEN_COM1 + i, comport->COMsFound[i]->name, comhelp);
COMSubMenu->Bind(wxEVT_MENU, &FiltgenFrame::COMselectionHandler, this, FILTGEN_COM1 + i);
}
}
}
void FiltgenFrame::OnMenuOpen(wxMenuEvent& event)
{
//fill in COM port menu when opened
if(event.GetMenu() == COMSubMenu)
{
recreateCOMmenu();
}
}
Since dynamic seems to be the key word here, I would go for dynamic event handling (actually, I always go for dynamic event handling using Bind, it's so much nicer than the alternatives):
auto pm = new wxMenu(); //I suppose you're adding this to an existing menu.
std::wstring port_str = L"COM";
int id_base = 77; //However you want to set up the IDs of the menu entries.
for(int port_num = 1; port_num <= 32; ++port_num)
{
int id = id_base + port_num;
pm->AppendRadioItem(id, port_str + std::to_wstring(port_num));
pm->Bind(wxEVT_MENU, [port_num](wxCommandEvent&)
{
//Do something with the current port_num; for example:
wxMessageBox(std::to_wstring(port_num));
//You can also capture id if you prefer, of course.
}, id);
}
In the lambda expression, we capture the port number by value, so, for each iteration, the current port_num will be captured. This achieves exactly what you asked for: the same function (the operator() of the lambda's closure type) associated with each menu entry. The function knows the entry for which it was called because it has access to the captured port_num value, stored in the lambda's closure object - a small object, most likely the size of one int in this case.
To avoid a fixed limit on the number of objects, you can simply store them in a std::vector. If you want the vector to own the objects (have them destroyed automatically when the vector is destroyed), then you can store them directly in a std::vector<comDetected>. If something else owns the objects and takes care of destroying them separately, you could use std::vector<comDetected*>.
UPDATE: When writing my first solution, I didn't realize you'll want to Unbind and re-bind those event handlers; pretty obvious in hindsight, really, but... anyway, my mistake, sorry.
Here's the problem: as far as I can tell, there's no straightforward way to Unbind a lambda function object that was passed directly to Bind as I did in my example. Simply calling Unbind as you're doing in your updated code isn't going to work, because that Unbind will try to find an event handler that was installed by a corresponding call to Bind with the exact same arguments. That won't happen for the reasons explained in the next section (there's also an explanation for why it seems to work), but you might be more interested in solutions, so I'll start with those.
Solution 1 (the best one in your case): Forgo using lambdas; just use either a free function or a member function pointer. In this case, you'll need to get the menu entry ID from evt.GetId() and get the port index from it; something like this:
void handler_func(wxCommandEvent& evt)
{
int i = evt.GetId() - FILTGEN_COM1;
comport->currentCOMselection = i;
logMsg(DBG_MENUS, ACT_NORMAL, "menu COM select index: %d\n", i);
}
Then, your code would look like this:
void FiltgenFrame::OnMenuOpen(wxMenuEvent& event)
{
/* ... */
COMSubMenu->Unbind(wxEVT_MENU, handler_func, FILTGEN_COM1 + i);
/* ... */
COMSubMenu->Bind(wxEVT_MENU, handler_func, FILTGEN_COM1 + i);
/* ... */
}
The example above is using a free function. You can also use a member function - more info here.
Solution 2: In case you can rebuild that menu at some other time than EVT_MENU_OPEN(), you could go for destroying the whole wxMenu and rebuilding and inserting it into its parent menu in the right place. Destroying the old menu object will take care of all the dynamic event handlers bound to it, so you don't need to Unbind them. However, destroying the menu just before it's displayed doesn't sound like a good idea - I haven't tried it, but as far as I can tell it won't work, or behave in highly platform-dependent ways.
Here are the reasons for which Unbind won't work directly with lambdas:
The object generated by a lambda expression has a unique type. Even if you copy-paste the exact same lambda expression to another place in your code, that second lambda will generate a closure object of a type different from the one generated by the original lambda. Since Unbind checks the type of the functor argument against the types of the installed handlers, it will never find a match.
Even if we got around the problem above, there's another one: the function object passed to Unbind also needs to have the same address as the one passed to the corresponding Bind. The object generated when the lambda expression is passed directly to Bind is a temporary (it will typically be allocated on the stack), so making any assumptions about its address across function calls is just incorrect.
We can get around the two problems above (store the closure objects separately somewhere and so on), but I think any such solution is far too cumbersome to be worth considering - it will negate all the advantages of the lambda-based solution.
Here's why it seems to work in your code:
If Unbind doesn't find an event handler to remove, it just returns false; all existing handlers remain in there. Later on, Bind adds a new handler for the same event type and same entry ID at the front of the event handler list, so newer handlers are called first. Unless a handler calls evt.Skip() before returning, the event is considered handled after the handler returns and no other handlers are called.
Even though it sort of works as you expect, letting all those old unused handlers accumulate in the list every time the menu is rebuilt isn't a good idea, obviously.

Comparing Substrings to JList Strings

In advance, please forgive me if I do not give adequate background information for my question. Long time reader, first time asker.
I am making a program where one has a database of cars accessed through a tab delimited .txt file (we did something like this recently in my programming class, so I wanted to expand upon it).
Instead of using the terminal window, my format is displaying the Car objects (containing make, model, year, price, etc.) in ArrayList. I'm using JFrame, a JList, and a ListModel since I'm using an array of Car objects.
In my program, I wanted to create a delete method where the user could delete items from the database. Initially they would select the item from the JList and then would click on the delete button. This invokes the delete() method, which is the tab shown below...
void delete()
{
int i = list.getSelectedIndex();
String string = (String)listModel.getElementAt(i);
for(Car c : cars)
{
String year = String.valueOf(c.getYear());
String conditionReport = String.valueOf(c.getConditionReport());
String price = String.valueOf(c.getPrice());
if(c.getMake().indexOf(string) != -1 && c.getModel().indexOf(string) != -1 && year.indexOf(string) != -1 && conditionReport.indexOf(string) != -1 && price.indexOf(string) != -1 && c.getWarranty().indexOf(string) != -1 && c.getComments().indexOf(string) != -1)
{
int choice = JOptionPane.showConfirmDialog(null, "Are you sure you would like to remove the " + cars.get(i).getYear() + " " + cars.get(i).getMake() + " " + cars.get(i).getModel() + "?", "Choose One", JOptionPane.YES_NO_OPTION);
if(choice == JOptionPane.NO_OPTION || choice == JOptionPane.CLOSED_OPTION)
{
return;
} else
{
cars.remove(c);
listModel.removeElementAt(i);
}
}
}
writeFile();
}
I have pinpointed my issue to be inside the if statement. (I printed things before and after to try to find where the program is lying. 'list' is my JList and 'listmodel' is my default list model. Car is an object I created that contains the elements (as seen by the get methods). The elements shown in the listModel are merely Strings that show getMake(), getModel(), and so forth... (Each 'get' item is separated by about 10 spaces.)
What am I doing wrong in the if statement? I figured that the getMake() and getModel() (and so forth) would be substrings of the index selected.
Thank you so much for your assistance! Any input regarding ways I could make further questions more specific and clear would be greatly appreciated!
It seems like you are doing this to find the selected Car in some kind of data structure. You would be better off doing something like programming a custom list model that had access to cars itself. Then you could retrieve the selection more immediately. If cars is an ArrayList that list merely parallels I also don't see why you can't do something to the effect of cars.remove(list.getSelectedIndex());. Or since JList can display any object, override Car's toString to display what the list currently displays and have the list display Cars. Then you can cars.remove((Car)list.getSelectedValue());.
But aside from that, based on your description it sounds like you mean to do the evaluation the other way. It's the list item that should contain all of the Car attributes, rather than all of the Car attributes containing the list item. So something like
if( string.contains(c.getMake()) && string.contains(year) // and so on
(Or with indexOf but since contains merely returns indexOf > -1, using contains makes your code somewhat shorter.)

ClientDataSet CalcFields' odd trouble

I've got an odd and persistent problem... (Also my right shift key just stopped working, so please bear with my possibly odd caps & symbols; trying to get my fingers trained to use the left shift key is a pain.)
I'm running a CalcFields event when a ClientDataSet opens & goes through records (ie, AutoCalcFields is true). It takes every record and does a little modification to it, as in a field comes in with "88", it changes it to "$88.00" and puts it in a new field, and so on for formatting purposes. This works great, the only problem is that CalcFields seems to completely ignore the first record in the set. It shows the records in a DBGrid and the first record is there, but none of this formatting has been done to it. So I step through the code and CalcField never touches the first record. Well, actually, it DOES touch the first record, (I'm assuming). When I step through the code, CalcField fires off twice with two completely blank sets of information. I know CalcFields is executed when the dataset is open, and stepping through, that's where the first blank input comes from, which is fine. The second blank input comes in first when it's going through each record. Again, the first record shows up perfectly intact in the DBGrid, but completely blank on Calcfield.
Does anybody have any idea on why this is happening? This is driving me crazy and I've tracked it up and down and can't figure out what the heck is going on.
Here's the CalcField code, for what it's worth:
void __fastcall TDataModule1::sdsSEARCHCalcFields(TDataSet *DataSet)
{
// to view for debugging....
DataSet->FieldByName("MISBN")->AsString;
AnsiString formattedField;
String field;
double dInputPower;
dInputPower = DataSet->FieldByName("MBILL$")->AsFloat;
formattedField = FormatFloat("#,##0.00", dInputPower);
DataSet->FieldByName("BILL")->Text = formattedField;
dInputPower = DataSet->FieldByName("MTGUID")->AsFloat;
formattedField = FormatFloat("#,##0.00", dInputPower);
DataSet->FieldByName("GUID")->Text = formattedField;
field = DataSet->FieldByName("MISBN")->AsString;
int lght = field.Length();
String str = field.SubString(14, 1);
if (field.Length() > 13 && field.SubString(14, 1) == ".")
{
DataSet->FieldByName("ISBN")->Text = field.SetLength(13);
}
}
//---------------------------------------------------------------------------
and here's the code that executes it. note that it's a ctQuery...
DataModule1->sdsSEARCH->Active = false;
DataModule1->cdsSEARCH->Active = false;
DataModule1->dsSEARCH->Enabled = false;
DataModule1->sdsSEARCH->CommandType = ctQuery;
DataModule1->sdsSEARCH->CommandText = queryStr;
DataModule1->sdsSEARCH->Active = true;
DataModule1->cdsSEARCH->Active = true;
DataModule1->dsSEARCH->Enabled = true;
why not use the DisplayFormat property? for example, in in a TClientDataSet.AfterOpen event do something like this:
dynamic_cast<TNumericField*>(DataSet->FieldByName("quantity_to_date_adjustment"))->DisplayFormat = "#,##0.00;(-#,##0.00)";