wxGrid

From WxWiki
Jump to: navigation, search
Official Classes SmallBlocks.png Archive Containers Controls Data Structures Database Date & Time Debug Device Contexts Dialogs Document & Views Drag & Drop Events Filesystem Frames Graphics Grid Cell Help HTML Logging Miscellaneous Networking Printing Sizers Streams Threading Windows

wxGrid and its related classes are used for displaying and editing tabular data.

They provide a rich set of features for display, editing, and interacting with a variety of data sources. For simple applications, and to help you get started, wxGrid is the only class you need to refer to directly. It will set up default instances of the other classes and manage them for you. For more complex applications you can derive your own classes for custom grid views, grid data tables, cell editors and renderers. The wxGrid Overview has examples of simple and more complex applications, explains the relationship between the various grid classes and has a summary of the keyboard shortcuts and mouse functions provided by wxGrid.

A wxGridTableBase class holds the actual data to be displayed by a wxGrid class. One or more wxGrid classes may act as a view for one table class. The default table class is called wxGridStringTable and holds an array of strings. An instance of such a class is created by CreateGrid().

Improving the functionality of comboboxes

The grid in wxWidgets allows you to insert a combobox in a cell, which is a feature that very few other toolkits offer. However, the default behaviour is somewhat lacking.

  • First of all, there is no visual indication of which cell has a combobox until the cell has focus.
  • Secondly, when you click on a cell to edit it you have to click three times: once to give the cell focus, once to activate the editor and a third to actually open the combobox - not very nice if you have a few dozen items to edit!
  • Lastly, you will often want a message passed to your application immediately when the combobox value has changed (normally you only get notification after the grid cell looses focus)

I wrote some classes for my application to rectify these problems, unfortunately I can't post code on this Wiki, however I post the links to the code on my Sourceforge project page below:

wxGridCellChoiceRenderer displays the combobox buttons when cell not active.

   http://nomadsync.cvs.sourceforge.net/nomadsync/nomadsync/src/GridCellChoiceRenderer.cpp?view=markup
   http://nomadsync.cvs.sourceforge.net/nomadsync/nomadsync/src/GridCellChoiceRenderer.h?view=markup

ezGrid activates cells in the grid with just one click

   http://nomadsync.cvs.sourceforge.net/nomadsync/nomadsync/src/EzGrid.cpp?view=markup
   http://nomadsync.cvs.sourceforge.net/nomadsync/nomadsync/src/EzGrid.h?view=markup

Note: the function that does the work is OnCellLeftClick. You may not need the other stuff.

wxFastComboEditor Opens the comboxbox immediately if you clicked on the button, Also, contains the class wxSComboBox, which sends a wxGridEvent when the comboxbox changes.

   http://nomadsync.cvs.sourceforge.net/nomadsync/nomadsync/src/FastComboEditor.cpp?view=markup
   http://nomadsync.cvs.sourceforge.net/nomadsync/nomadsync/src/FastComboEditor.h?view=markup


Insert the combobox into the cell like this:

#include "EzGrid.h"
#include "GridCellChoiceRenderer.h"
#include "FastComboEditor.h"

// ...

EzGrid *grid;
grid = new EzGrid( this, -1, wxPoint( 0, 0 ), wxSize( 400, 300 ) );
grid->CreateGrid( 4, 4 );   
grid->SetCellRenderer(1, 1, new wxGridCellChoiceRenderer);
wxString strChoices[3] = {"one", "two", "three"};
grid->SetCellEditor(1, 1, new wxFastComboEditor(3, strChoices, true));

Notes: EzGrid will put all cells into edit mode with just one click, not just the comboboxes. Normally this is desirable because you can edit the cells quicker. The code works under both Windows and Linux (wxGTK).

Validation

If you want to validate the contents of wxGrid, make sure all cell values have been saved by using wxGrid::SaveEditControlValue.

Change in-place-edit dynamically

Why would anybody want to change the edit control dynamically? Suppose you use a wxGridCellChoiceEditor and the choices vary dependent on some other data. Or suppose your customer wants some advanced customization like choosing his color of the week for the cell editor's background ;-)

This is not only possible but quite simple: Trigger the event which results in the need for a different cell editor and replace the editor:

void MyDialog::setCellEditorMyColumn(int row)
{
	// ...

	// recompute choices
	m_gridPtr->SetCellEditor(row, MyColumn,
		new wxGridCellChoiceEditor(number, choices, true));
}

note: shouldn't you delete these wxGridCellChoiceEditor objects somewhere? --ARN

I don't think so. It's reference counted and managed by wxGridCellAttr. You only have to delete the choices yourself. -- SDoe

As listed in grid.h

   void SetEditor(wxGridCellEditor* editor)
       { wxSafeDecRef(m_editor); m_editor = editor; }

so assuming the reference counting is done properly, it should be deleted. - Spanky

Update wxGrid display on wxGridTableBase data changes

How to make changes in wxGridTableBase derived classes to change the wxGrid display - do the following when a change is made.

if ( GetView() )
{
	wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED,
	                        position, rowsInserted );
	GetView()->ProcessTableMessage( msg );
}

(Alter the above depending on insert or removal of row or column. I found that

 wxGridTableMessage msg( this, wxGRIDTABLE_REQUEST_VIEW_GET_VALUES );

didn't work so you have to specify what changed).

From examining src/generic/grid.cpp for 2.5.3, it appears that wxGRIDTABLE_REQUEST_VIEW_GET_VALUES and wxGRIDTABLE_REQUEST_VIEW_SEND_VALUES are for changing cell contents rather than the number of cells, which may explain why it didn't work here. The messages to use are:

       wxGRIDTABLE_NOTIFY_ROWS_INSERTED, position, numRowsInserted
       wxGRIDTABLE_NOTIFY_ROWS_APPENDED, numRowsAppended
       wxGRIDTABLE_NOTIFY_ROWS_DELETED, position, numRowsDeleted 
       wxGRIDTABLE_NOTIFY_COLS_INSERTED, position, numColsInserted
       wxGRIDTABLE_NOTIFY_COLS_APPENDED, numColsAppended
       wxGRIDTABLE_NOTIFY_COLS_DELETED, position, numColsDeleted

The above code, with the appropriate message, must be included in the wxGridTableBase derived class's AppendRows, DeleteRows, InsertRows and the corresponding Cols functions for those functions to work.

Changing/Disabling Cell Highlight

If you want to change the way the "Current Cell"-highlight is drawn, simply derive a class from wxGrid and override

 void DrawCellHighlight(wxDC& dc, const wxGridCellAttr *attr);

Leave the method body empty to disable cell highlighting. -- chris, advised by Scott on the wx forum.

Catching WXK_RETURN from the cell editor

The cell editor sends WXK_RETURN and WXK_NUMPAD_ENTER events to the grid. To catch these, you'll have to dynamically connect a handler function to the event handler of the grid, using the static event table doesn't work.

grid->GetEventHandler()->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainFrame::OnGridKeyDown));

void MainFrame::OnGridKeyDown(wxKeyEvent& event)
{
	if (event.GetKeyCode() == WXK_RETURN || event.GetKeyCode() == WXK_NUMPAD_ENTER)
	{
		cout << "return pressed" << endl;
	}
	event.Skip();
}

Acting on all selected cells

As far as I can see, there is no easy way to iterate over all the selected cells in a grid:

http://groups.google.co.uk/group/comp.soft-sys.wxwindows/msg/42ad8098fae26f80?hl=en&

We have to iterate over several arrays. In C++, this is a good place to use functors:

/** Applies a functor to all selected cells. Returns the number of selected cells. */
template<typename Functor>
void forAllSelectedCells(wxGrid& grid, Functor& f)
{
	// Singly selected cells.
	const wxGridCellCoordsArray& cells(grid.GetSelectedCells());
	for (size_t i = 0; i < cells.size(); ++i)
	{
		const wxGridCellCoords& c = cells[i];
		f(grid, c.GetRow(), c.GetCol());
	}

	// Whole selected rows.
	int colCount = grid.GetNumberCols();
	const wxArrayInt& rows(grid.GetSelectedRows());
	for (size_t i = 0; i < rows.size(); ++i)
	{
		for (int col = 0; col < colCount; ++col)
			f(grid, rows[i], col);
	}

	// Whole selected columns.
	int rowCount = grid.GetNumberRows();
	const wxArrayInt& cols(grid.GetSelectedCols());
	for (size_t i = 0; i < cols.size(); ++i)
	{
		for (int row = 0; row < rowCount; ++row)
			f(grid, row, cols[i]);
	}

	// Blocks. We always expect btl and bbr to have the same size, since their
	// entries are supposed to correspond.
	const wxGridCellCoordsArray& btl(grid.GetSelectionBlockTopLeft());
	const wxGridCellCoordsArray& bbr(grid.GetSelectionBlockBottomRight());
	size_t blockCount = btl.size();
	if (blockCount != bbr.size()) return; // bail instead of crashing
	for (size_t i = 0; i < blockCount; ++i)
	{
		const wxGridCellCoords& tl = btl[i];
		const wxGridCellCoords& br = bbr[i];
		for (int row = tl.GetRow(); row <= br.GetRow(); ++row)
		{
			for (int col = tl.GetCol(); col <= br.GetCol(); ++col)
				f(grid, row, col);
		}
	}
}

For example, if you have a matrix of boolean values and you want to let the user tick or untick all boxes in a selection (unless they're on the diagonal, in my application):

struct SetNonDiagonalCellValueFunctor
{
public:

	SetNonDiagonalCellValueFunctor(const wxString& value)
	{
		this->value = value;
	}

	void operator()(wxGrid& grid, int row, int col)
	{
		if (row == col) return;
		grid.SetCellValue(row, col, this->value);
	}

private:

	wxString value;
};

To set all entries to true:

forAllSelectedCells(*(this->grid), SetNonDiagonalCellValueFunctor(wxT("1")));

Or false:

forAllSelectedCells(*(this->grid), SetNonDiagonalCellValueFunctor(wxT("")));

Bool editor sending immediate change event

The standard behaviour of grid cells is that the change event is sent after the grid cell loses the focus.

This can be a little unhandy if you use bool editors within your grid, and want an immediate action according the current state change of this bool editor cell (e.g. change background colour of 'activated' rows or columns at once).

Add the following class:

class MyGridCellBoolEditor : public wxGridCellBoolEditor
{
public:
	void BeginEdit (int row, int col, wxGrid* grid)
	{
		wxGridCellBoolEditor::BeginEdit(row, col, grid);

		wxFocusEvent event (wxEVT_KILL_FOCUS);
		if (m_control)
		{
			m_control->GetEventHandler()->AddPendingEvent(event);
		}
	}
};

And within the grid do:

SetCellEditor(row, col, new MyGridCellBoolEditor);

See Also