Custom events with/from wxComboBox and wxGrid Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
SalmonsSteve
Earned a small fee
Earned a small fee
Posts: 18
Joined: Sun Jun 24, 2012 6:00 pm

Custom events with/from wxComboBox and wxGrid

Post by SalmonsSteve » Sun Feb 10, 2019 3:18 pm

The problem I have recently run into is that when using a wxGridCellChoiceEditor I seem to be able to only catch 2 events, the cell changing event and the cell changed event. The cell changed event isn't emitted until the cell loses focus to another cell. I want to validate and store data when the combobox closes but I need the grid's ID, Row, and Column. If the combobox closeup event is caught the ID is negative and not assigned until the grid cell is activated. So here's what I want to do, catch the combobox event in the grid class and get the index. If I can only get the selection then I can iterate through the array string used to load the cell choice editor to get this. Then I want to emit a custom event from the grid that will carry with it the grid's id, the index from the selection, the current row and current column in the grid. From there I will catch the event in the parent frame and based on the id and other values pulled from the event I will be able to store the data in the appropriate location of the appropriate array.

I've dealt with custom event handlers written inside control classes before, and I've logged the closeup events when they are emitted so I don't think that catching the combobox inside the grid class will be an issue.

I have no experience with writing my own events. I am just starting to look into this and have yet to begin writing the simple grid class that I will use to test all of this. So, does one of the samples do anything similar to this or does anybody know of a good reference I should look while I sift through search results and various class documentation? Has anybody already done something like this for cell choice editors?

User avatar
doublemax
Moderator
Moderator
Posts: 12995
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Custom events with/from wxComboBox and wxGrid

Post by doublemax » Sun Feb 10, 2019 3:41 pm

Why is wxEVT_GRID_CELL_CHANGING not sufficient for your purpose?
Use the source, Luke!

SalmonsSteve
Earned a small fee
Earned a small fee
Posts: 18
Joined: Sun Jun 24, 2012 6:00 pm

Re: Custom events with/from wxComboBox and wxGrid

Post by SalmonsSteve » Sun Feb 10, 2019 4:05 pm

using the event handler macro EVT_GRID_CMD_CELL_CHANGING(GRID_ID, myFrame::ConcGridCellChanging) the function ConcGridCellChanging does not get hit until you click off of the GridCellChoiceEditor. I want to have the data stored without the cell having to lose focus. I would have thought the changing event would have fired without losing focus but stepping through the debugger tells me that it doesn't.

Manolo
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 653
Joined: Mon Apr 30, 2012 11:07 pm

Re: Custom events with/from wxComboBox and wxGrid

Post by Manolo » Sun Feb 10, 2019 5:32 pm

You can create your own cell editor, deriving from a wx's one. You must override some virtual methods:

Code: Select all

class myGridCellChoiceEditor : public wxGridCellChoiceEditor
{
public:
    ......

    virtual void Create(wxWindow* parent, wxWindowID id, wxEvtHandler* evtHandler);

    virtual wxGridCellEditor *Clone() const;

    virtual void BeginEdit(int row, int col, wxGrid* grid);

    virtual bool EndEdit(int row, int col, const wxGrid* grid,  const wxString& oldval, wxString *newval);

    virtual void ApplyEdit(int row, int col, wxGrid* grid);
.....
private:
    wxDECLARE_NO_COPY_CLASS(myGridCellChoiceEditor );
};
You can learn more in the wx docs. And in the source code for wxGridCellEditor.

ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 2928
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: Custom events with/from wxComboBox and wxGrid

Post by ONEEYEMAN » Sun Feb 10, 2019 11:31 pm

Hi,
Please consider using cell changing event for verification.

It is bad practice to do the verification with every single key press and user usually will be confused when you display errors on every key press.

Thank you.

SalmonsSteve
Earned a small fee
Earned a small fee
Posts: 18
Joined: Sun Jun 24, 2012 6:00 pm

Re: Custom events with/from wxComboBox and wxGrid

Post by SalmonsSteve » Sun Feb 10, 2019 11:39 pm

I accomplished it with the following:

in the .h

Code: Select all

#ifndef CUSTOM_GRID_H
#define CUSTOM_GRID_H
#include <vector>

#include "wx/grid.h"
wxDECLARE_EVENT(EVT_GRID_CHOICEEDITOR_CLOSED, wxGridEvent);

class Custom_Grid : public wxGrid
{
    public:
        Custom_Grid(wxWindow *parent, wxWindowID id, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize,
                 long style=wxWANTS_CHARS, wxString name=wxGridNameStr);

		virtual ~Custom_Grid();
		//function to store information required to find combobox index and call SetCellChoiceEditor
        void CreateCellChoiceEditor(int row, int col, size_t count, const wxString choices[]);
		
    protected:

    private:
        wxWindowID m_id;

        void OnComboBox(wxCommandEvent& evt);
	void OnChoiceEditorClosed(wxGridEvent& evt);

	std::vector<std::vector <wxString>> m_choices;  //choices in the GridCellChoiceEditor loaded into vector
	std::vector<std::vector <int>> m_choiceEditor;  //row, column, index to keep track of the index
										//for location in GridCellChoiceEditor string vector
										//each GridCellChoiceEditor location has it's own array of strings

        DECLARE_EVENT_TABLE()
};

#endif // CUSTOM_GRID_H

in the .cpp

Code: Select all

#include "custom_grid.h"
wxDEFINE_EVENT(EVT_GRID_CHOICEEDITOR_CLOSED, wxGridEvent);

Custom_Grid::Custom_Grid(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, wxString name):
                    wxGrid(parent, id, pos, size, style, name)
{
    //ctor
    m_id = id;	//store the grid's id <-- I didn't look closely to see if this was already done in base class
	// Bind the new event
    Bind(EVT_GRID_CHOICEEDITOR_CLOSED, &Custom_Grid::OnChoiceEditorClosed, this);
}

Custom_Grid::~Custom_Grid()
{
    //dtor
}
void Custom_Grid::OnComboBox(wxCommandEvent& evt)
{
    int row = m_currentCellCoords.GetRow();	//get the current row
    int col = m_currentCellCoords.GetCol();		//get the current column
    int index;							// to store GridCellChoiceEditor index
    bool found = false;					
    wxString tempstring;
    
 //Save the current control's value otherwise the string returned by GetCellValue(row,col)
 //will be the value before changing the choice
    SaveEditControlValue();
	
    wxString val = GetCellValue(row,col);		//get the string value in the cell
    wxGridEvent event(m_id, EVT_GRID_CHOICEEDITOR_CLOSED, this, row,col);  //create a custom wxGridEvent
    event.SetString(val);					//set the string value in the event
    while(!found)
    {	
        //I'm sure this part can be immproved for performance
	//The arrays I am dealing with are small and I do not
	//have multiple columns with ChoiceEditors in them
	//so the vectors aren't getting very large
        for(size_t i=0; i<m_choiceEditor.size(); ++i)
        {
			if( !found )
			{	//I'm sure this part can be immproved for performance
				//The arrays I am dealing with are small and I do not
				//have multiple columns with ChoiceEditors in them
				//so the vectors aren't getting very large
				if(m_choiceEditor.at(i).at(0) == row && m_choiceEditor.at(i).at(1)== col)
				{
					for(size_t j=0; j<m_choices.at(i).size(); ++j)
					{
						if(val.IsSameAs(m_choices.at(i).at(j)))
						{
							found = true;
							if(j==0 || j==1)
							{
								index = 0;
								//first the first two items are an empty string and
								//a string to clear the row
							}
							else
							{
								index = (int)j-1;
								//for consistency with legacy application the value
								//I need is actually index-1.  Change this for the
								//value that you need relative to the actual index
							}
						}
                    }
                }
            }
        }
    }

	event.SetInt(index);	//set the Int value in the event
	wxPostEvent(this, event); //EVT_GRID_CHOICEEDITOR_CLOSED emitted with string value, index, row, and column
						//to be used upstream

}

void Custom_Grid::OnChoiceEditorClosed(wxGridEvent& evt)
{
	// event can be caught here
	// call Skip() to catch event elsewhere
    evt.Skip();

}
void Custom_Grid::CreateCellChoiceEditor(int row, int col, size_t count, const wxString choices[])
{
    int newEditor = m_choices.size()+1; //create a new index for m_choices vector
    std::vector<int> temp;				
    temp.push_back(row);				
    temp.push_back(col);
    temp.push_back(newEditor);
    m_choiceEditor.push_back(temp);		// store {row, col, index} in choiceEditor  <-- that is probably a bad name
    wxString tempstring;
    std::vector<wxString> tempChoices;
    for(size_t i = 0; i<count; ++i)
    {
        tempstring.Clear();	
        tempstring.Append(choices[i]);
        tempChoices.push_back(tempstring);
    }
    m_choices.push_back(tempChoices);	// store a vector of strings for this ChoiceEditor in a vector
    SetCellEditor(row, col, new wxGridCellChoiceEditor(count,choices));	//use wxGrid::SetCellEditor to actually create

}
BEGIN_EVENT_TABLE(Custom_Grid,wxGrid)
    EVT_COMBOBOX(wxID_ANY, Custom_Grid::OnComboBox)
END_EVENT_TABLE()

in myFrame.cpp

Code: Select all

#include "custom_grid.h"
...
//(insert lines below into constructor)
// Bind Custom Events
    Bind(EVT_GRID_CHOICEEDITOR_CLOSED, &myFrame::OnChoiceEditorClosed, this);
...

I tried to strip this down to just what needs to be there for the specific case of creating a grid that will emit a useful event as soon as the GridCellChoiceEditor selection is made. Hopefully I haven't left any code remnants that would be specific to my project (other than those commented) and hopefully I haven't omitted anything as I was editing for this post.

SalmonsSteve
Earned a small fee
Earned a small fee
Posts: 18
Joined: Sun Jun 24, 2012 6:00 pm

Re: Custom events with/from wxComboBox and wxGrid

Post by SalmonsSteve » Mon Feb 11, 2019 12:10 am

ONEEYEMAN wrote:Hi,
Please consider using cell changing event for verification.

It is bad practice to do the verification with every single key press and user usually will be confused when you display errors on every key press.

Thank you.
The cell changing event doesn't fire for wxGridCellChoiceEditor until the wxGridCellChoiceEditor is losing focus and since a cell (and therefore the ChoiceEditor) may not lose focus this seemed like the most reasonable way to proceed.

As for verifying things with every single key press, in the case of something like a wxTextCtrl, I actually don't see the problem. Clearly you would like to keep invalid characters from being displayed. You don't have to display an error, you can use wxBell() when you veto the event so it should be obvious that the keystroke was invalid. Most text controls that I am dealing with display strings that represent a float value and in some cases I need to store a value and use it for a calculation that will live update. I take care of a couple of very simple and very generic validations of a keystroke before either stopping the event from propagating or calling Skip(). Real data validation is still handled by the application and my controls only accept numeric input with a single decimal point and the negative sign if the control value can be negative. When they lose focus they properly format the string for the format of the number by prepending or appending 0 as needed.
Now, I haven't dealt with char events in the grid for cells that should be formatted as floats but I know that I will be adding a check that will disallow '+' when it is entered.

ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 2928
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: Custom events with/from wxComboBox and wxGrid

Post by ONEEYEMAN » Mon Feb 11, 2019 3:37 pm

Hi,
I understand all this. I even agree that the wrong charactewrs should be filtered.
But data validation should be handled either on pressing "OK"/"Accept"/"Apply" button (in case of dialog) or on lose focus event (in case of the TLW).
This is not just because of the performance (event handlers should not execute a long function), but because 99.999999% of the applications do it this way and it will be very surprising for the user.

Thank you.

Post Reply