Merge branch 'grid-enhance'

Many enhancement to wxGrid UI: fix redraw and (some) flicker bugs; fix
bugs with mouse handling; improve editors positioning etc.

See https://github.com/wxWidgets/wxWidgets/pull/1902
This commit is contained in:
Vadim Zeitlin
2020-06-26 22:20:41 +02:00
8 changed files with 253 additions and 121 deletions

View File

@@ -339,6 +339,16 @@ protected:
// the dtor is private because only DecRef() can delete us
virtual ~wxGridCellEditor();
// Helper for the derived classes positioning the control according to the
// attribute alignment if the desired control size is smaller than the cell
// size, or centering it vertically if its size is bigger: this looks like
// the best compromise when the editor control doesn't fit into the cell.
void DoPositionEditor(const wxSize& size,
const wxRect& rectCell,
int hAlign = wxALIGN_LEFT,
int vAlign = wxALIGN_CENTRE_VERTICAL);
// the actual window we show on screen (this variable should actually be
// named m_window, but m_control is kept for backward compatibility)
wxWindow* m_control;
@@ -1675,7 +1685,27 @@ public:
void DisableRowResize(int row) { DoDisableLineResize(row, m_setFixedRows); }
void DisableColResize(int col) { DoDisableLineResize(col, m_setFixedCols); }
// these functions return whether the given row/column can be
// These function return true if resizing rows/columns by dragging
// their edges inside the grid is enabled. Note that this doesn't cover
// dragging their separators in the label windows (which can be enabled
// for the columns even if dragging inside the grid is not), nor checks
// whether a particular row/column is resizeable or not, so you should
// always check CanDrag{Row,Col}Size() below too.
bool CanDragGridRowEdges() const
{
return m_canDragGridSize && m_canDragRowSize;
}
bool CanDragGridColEdges() const
{
// When using the native header window we can only resize the columns by
// dragging the dividers in the header itself, but not by dragging them
// in the grid because we can't make the native control enter into the
// column resizing mode programmatically.
return m_canDragGridSize && m_canDragColSize && !m_useNativeHeader;
}
// These functions return whether the given row/column can be
// effectively resized: for this interactive resizing must be enabled
// and this index must not have been passed to DisableRow/ColResize()
bool CanDragRowSize(int row) const

View File

@@ -60,10 +60,6 @@ public:
wxEvtHandler* evtHandler) wxOVERRIDE;
virtual void SetSize(const wxRect& rect) wxOVERRIDE;
virtual void PaintBackground(wxDC& dc,
const wxRect& rectCell,
const wxGridCellAttr& attr) wxOVERRIDE;
virtual bool IsAcceptedKey(wxKeyEvent& event) wxOVERRIDE;
virtual void BeginEdit(int row, int col, wxGrid* grid) wxOVERRIDE;
virtual bool EndEdit(int row, int col, const wxGrid* grid,
@@ -307,10 +303,6 @@ public:
virtual void SetSize(const wxRect& rect) wxOVERRIDE;
virtual void PaintBackground(wxDC& dc,
const wxRect& rectCell,
const wxGridCellAttr& attr) wxOVERRIDE;
virtual void BeginEdit(int row, int col, wxGrid* grid) wxOVERRIDE;
virtual bool EndEdit(int row, int col, const wxGrid* grid,
const wxString& oldval, wxString *newval) wxOVERRIDE;

View File

@@ -152,6 +152,14 @@ public:
(owner->CanHideColumns() ? wxHD_ALLOW_HIDE : 0) |
(owner->CanDragColMove() ? wxHD_ALLOW_REORDER : 0))
{
m_inResizing = 0;
}
// Special method to call from wxGrid::DoSetColSize(), see comments there.
void UpdateIfNotResizing(unsigned int idx)
{
if ( !m_inResizing )
UpdateColumn(idx);
}
protected:
@@ -259,7 +267,20 @@ private:
void OnResizing(wxHeaderCtrlEvent& event)
{
// Calling wxGrid method results in a call to our own UpdateColumn()
// because it ends up in wxGrid::SetColSize() which must indeed update
// the column when it's called by the program -- but in the case where
// the size change comes from the column itself, it is useless and, in
// fact, harmful, as it results in extra flicker due to the inefficient
// implementation of UpdateColumn() in wxMSW wxHeaderCtrl, so skip
// calling it from our overridden version by setting this flag for the
// duration of this function execution and checking it in our
// UpdateIfNotResizing().
m_inResizing++;
GetOwner()->DoHeaderDragResizeCol(event.GetWidth());
m_inResizing--;
}
void OnEndResize(wxHeaderCtrlEvent& event)
@@ -281,6 +302,9 @@ private:
wxVector<wxGridHeaderColumn> m_columns;
// The count of OnResizing() call nesting, 0 if not inside it.
int m_inResizing;
wxDECLARE_EVENT_TABLE();
wxDECLARE_NO_COPY_CLASS(wxGridHeaderCtrl);
};

View File

@@ -4322,6 +4322,26 @@ public:
*/
bool CanDragColSize(int col) const;
/**
Return @true if column edges inside the grid can be dragged to resize
the rows.
@see CanDragGridSize(), CanDragColSize()
@since 3.1.4
*/
bool CanDragGridColEdges() const;
/**
Return @true if row edges inside the grid can be dragged to resize the
rows.
@see CanDragGridSize(), CanDragRowSize()
@since 3.1.4
*/
bool CanDragGridRowEdges() const;
/**
Return @true if the dragging of grid lines to resize rows and columns
is enabled or @false otherwise.

View File

@@ -543,12 +543,14 @@ GridFrame::GridFrame()
// Some numeric columns with different formatting.
grid->SetColFormatFloat(6);
grid->SetReadOnly(0, 6);
grid->SetCellValue(0, 6, "Default\nfloat format");
grid->SetCellValue(1, 6, wxString::Format("%g", 3.1415));
grid->SetCellValue(2, 6, wxString::Format("%g", 1415.0));
grid->SetCellValue(3, 6, wxString::Format("%g", 12345.67890));
grid->SetColFormatFloat(7, 6, 2);
grid->SetReadOnly(0, 7);
grid->SetCellValue(0, 7, "Width 6\nprecision 2");
grid->SetCellValue(1, 7, wxString::Format("%g", 3.1415));
grid->SetCellValue(2, 7, wxString::Format("%g", 1415.0));
@@ -556,22 +558,28 @@ GridFrame::GridFrame()
grid->SetColFormatCustom(8,
wxString::Format("%s:%i,%i,%s", wxGRID_VALUE_FLOAT, -1, 4, "g"));
grid->SetReadOnly(0, 8);
grid->SetCellValue(0, 8, "Compact\nformat");
grid->SetCellValue(1, 8, "31415e-4");
grid->SetCellValue(2, 8, "1415");
grid->SetCellValue(3, 8, "123456789e-4");
grid->SetColFormatNumber(9);
grid->SetReadOnly(0, 9);
grid->SetCellValue(0, 9, "Integer\ncolumn");
grid->SetCellValue(1, 9, "17");
grid->SetCellValue(2, 9, "0");
grid->SetCellEditor(2, 9, new wxGridCellNumberEditor(0, 100));
grid->SetCellValue(2, 10, "<- This cell uses [0, 100] range");
grid->SetCellValue(3, 9, "-666");
grid->SetCellAlignment(3, 9, wxALIGN_CENTRE, wxALIGN_TOP);
grid->SetCellValue(3, 10, "<- This numeric cell should be centred");
grid->SetReadOnly(0, 13);
grid->SetCellValue(0, 13, "Localized date\ncolumn");
grid->SetColFormatDate(13); // Localized by default.
grid->SetCellValue(1, 13, "Today");
grid->SetReadOnly(0, 14);
grid->SetCellValue(0, 14, "ISO 8601 date\ncolumn");
grid->SetColFormatDate(14, "%Y-%m-%d"); // ISO 8601 date format.
grid->SetCellValue(1, 14, "Tomorrow");

View File

@@ -3015,22 +3015,17 @@ void wxGrid::CalcDimensions()
// take into account editor if shown
if ( IsCellEditControlShown() )
{
int w2, h2;
int r = m_currentCellCoords.GetRow();
int c = m_currentCellCoords.GetCol();
int x = GetColLeft(c);
int y = GetRowTop(r);
// how big is the editor
wxGridCellAttrPtr attr = GetCellAttrPtr(r, c);
wxGridCellEditorPtr editor = attr->GetEditorPtr(this, r, c);
editor->GetWindow()->GetSize(&w2, &h2);
w2 += x;
h2 += y;
if ( w2 > w )
w = w2;
if ( h2 > h )
h = h2;
const wxRect rect = editor->GetWindow()->GetRect();
if ( rect.GetRight() > w )
w = rect.GetRight();
if ( rect.GetBottom() > h )
h = rect.GetBottom();
}
wxPoint offset = GetGridWindowOffset(m_gridWin);
@@ -4577,8 +4572,20 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event,
MakeCellVisible(coords);
}
}
else if ( XToEdgeOfCol(pos.x) < 0 && YToEdgeOfRow(pos.y) < 0 )
else
{
// Clicking on (or very near) the separating lines shouldn't change the
// selection when it's used for resizing -- but should still do it if
// resizing is disabled (notice that we intentionally don't check for
// it being disabled for a particular row/column as it would be
// surprising to have different mouse behaviour in different parts of
// the same grid, so we only check for it being globally disabled).
if ( CanDragGridColEdges() && XToEdgeOfCol(pos.x) != wxNOT_FOUND )
return;
if ( CanDragGridRowEdges() && YToEdgeOfRow(pos.y) != wxNOT_FOUND )
return;
DisableCellEditControl();
MakeCellVisible( coords );
@@ -4709,28 +4716,10 @@ wxGrid::DoGridMouseMoveEvent(wxMouseEvent& WXUNUSED(event),
int dragRow = YToEdgeOfRow( pos.y );
int dragCol = XToEdgeOfCol( pos.x );
// Dragging on the corner of a cell to resize in both
// directions is not implemented yet...
//
if ( dragRow >= 0 && dragCol >= 0 )
{
ChangeCursorMode(WXGRID_CURSOR_RESIZE_ROW, gridWindow, false);
return;
}
if ( dragRow >= 0 && CanDragGridSize() && CanDragRowSize(dragRow) )
{
if ( m_cursorMode == WXGRID_CURSOR_SELECT_CELL )
{
DoStartResizeRowOrCol(dragRow);
ChangeCursorMode(WXGRID_CURSOR_RESIZE_ROW, gridWindow, false);
}
}
// When using the native header window we can only resize the columns by
// dragging the dividers in it because we can't make it enter into the
// column resizing mode programmatically
else if ( dragCol >= 0 && !m_useNativeHeader &&
CanDragGridSize() && CanDragColSize(dragCol) )
// Dragging on the corner of a cell to resize in both directions is not
// implemented, so choose to resize the column when the cursor is over the
// cell corner, as this is a more common operation.
if ( dragCol >= 0 && CanDragGridColEdges() && CanDragColSize(dragCol) )
{
if ( m_cursorMode == WXGRID_CURSOR_SELECT_CELL )
{
@@ -4738,6 +4727,14 @@ wxGrid::DoGridMouseMoveEvent(wxMouseEvent& WXUNUSED(event),
ChangeCursorMode(WXGRID_CURSOR_RESIZE_COL, gridWindow, false);
}
}
else if ( dragRow >= 0 && CanDragGridRowEdges() && CanDragRowSize(dragRow) )
{
if ( m_cursorMode == WXGRID_CURSOR_SELECT_CELL )
{
DoStartResizeRowOrCol(dragRow);
ChangeCursorMode(WXGRID_CURSOR_RESIZE_ROW, gridWindow, false);
}
}
else // Neither on a row or col edge
{
if ( m_cursorMode != WXGRID_CURSOR_SELECT_CELL )
@@ -7262,24 +7259,11 @@ void wxGrid::ShowCellEditControl()
m_currentCellCoords.SetCol( col );
}
// erase the highlight and the cell contents because the editor
// might not cover the entire cell
wxClientDC dc( gridWindow );
PrepareDCFor(dc, gridWindow);
wxGridCellAttrPtr attr = GetCellAttrPtr(row, col);
dc.SetBrush(wxBrush(attr->GetBackgroundColour()));
dc.SetPen(*wxTRANSPARENT_PEN);
dc.DrawRectangle(rect);
rect.Offset(-GetGridWindowOffset(gridWindow));
// convert to scrolled coords
CalcGridWindowScrolledPosition( rect.x, rect.y, &rect.x, &rect.y, gridWindow );
int nXMove = 0;
if (rect.x < 0)
nXMove = rect.x;
#ifdef __WXQT__
// Substract 1 pixel in every dimension to fit in the cell area.
// If not, Qt will draw the control outside the cell.
@@ -7287,6 +7271,7 @@ void wxGrid::ShowCellEditControl()
rect.Deflate(1, 1);
#endif
wxGridCellAttrPtr attr = GetCellAttrPtr(row, col);
wxGridCellEditorPtr editor = attr->GetEditorPtr(this, row, col);
if ( !editor->IsCreated() )
{
@@ -7328,10 +7313,6 @@ void wxGrid::ShowCellEditControl()
maxWidth = rect.width;
}
int client_right = gridWindow->GetClientSize().GetWidth();
if (rect.x + maxWidth > client_right)
maxWidth = client_right - rect.x;
if ((maxWidth > rect.width) && (col < m_numCols) && m_table)
{
GetCellSize( row, col, &cell_rows, &cell_cols );
@@ -7350,17 +7331,35 @@ void wxGrid::ShowCellEditControl()
else
break;
}
if (rect.GetRight() > client_right)
rect.SetRight( client_right - 1 );
}
editor->SetCellAttr( attr.get() );
editor->SetSize( rect );
if (nXMove != 0)
editor->GetWindow()->Move(
editor->GetWindow()->GetPosition().x + nXMove,
editor->GetWindow()->GetPosition().y );
// Note that the actual rectangle used by the editor could be
// different from the one we proposed.
rect = editor->GetWindow()->GetRect();
// Ensure that the edit control fits into the visible part of the
// window by shifting it if necessary: we don't want to truncate
// any part of it by trying to position it too far to the left or
// top and we definitely don't want to start showing scrollbars by
// positioning it too far to the right or bottom.
const wxSize sizeMax = gridWindow->GetClientSize();
if ( !wxRect(sizeMax).Contains(rect) )
{
if ( rect.x < 0 )
rect.x = 0;
if ( rect.y < 0 )
rect.y = 0;
if ( rect.x > sizeMax.x - rect.width )
rect.x = sizeMax.x - rect.width;
if ( rect.y > sizeMax.y - rect.height )
rect.y = sizeMax.y - rect.height;
editor->GetWindow()->Move(rect.x, rect.y);
}
editor->Show( true, attr.get() );
// recalc dimensions in case we need to
@@ -9757,7 +9756,16 @@ void wxGrid::DoSetColSize( int col, int width )
return;
if ( m_useNativeHeader )
GetGridColHeader()->UpdateColumn(col);
{
// We have to update the native control if we're called from the
// program (directly or indirectly, e.g. via AutoSizeColumn()), but we
// want to avoid doing it when the column is being resized
// interactively, as this is unnecessary and results in very visible
// flicker, so take care to call the special method of our header
// control checking for whether it's being resized interactively
// instead of the usual UpdateColumn().
static_cast<wxGridHeaderCtrl*>(m_colLabelWin)->UpdateIfNotResizing(col);
}
//else: will be refreshed when the header is redrawn
for ( int colPos = GetColPos(col); colPos < m_numCols; colPos++ )
@@ -9836,7 +9844,10 @@ void wxGrid::DoSetColSize( int col, int width )
FurtherWindowPartRefresher refreshFurtherPart(x);
refreshFurtherPart(m_colLabelWin);
// Refreshing the native header is unnecessary, as it updates
// itself correctly anyhow, and just results in extra flicker.
if ( !IsUsingNativeHeader() )
refreshFurtherPart(m_colLabelWin);
refreshFurtherPart(m_gridWin);
if ( m_frozenRowGridWin )

View File

@@ -331,6 +331,62 @@ void wxGridCellEditor::SetSize(const wxRect& rect)
m_control->SetSize(rect, wxSIZE_ALLOW_MINUS_ONE);
}
void wxGridCellEditor::DoPositionEditor(const wxSize& size,
const wxRect& rectCell,
int hAlign,
int vAlign)
{
wxRect rect(rectCell.GetPosition(), size);
// We center the control around the cell if it doesn't fit into it in one
// or both of directions, as this seems to look the best (difference is
// typically relatively small and by centering it, we divide it by two on
// each side, making it even smaller).
//
// For now just remember in which direction to do it in this variable and
// then do it at the end.
int centerDir = 0;
// We're only going to need the alignment if the control is smaller than
// the cell in at least one direction.
if ( size.x < rectCell.width || size.y < rectCell.height )
{
if ( GetCellAttr() )
GetCellAttr()->GetNonDefaultAlignment(&hAlign, &vAlign);
}
if ( size.x < rectCell.width )
{
if ( hAlign == wxALIGN_CENTER_HORIZONTAL )
centerDir |= wxHORIZONTAL;
else if ( hAlign == wxALIGN_RIGHT )
rect.x = rectCell.x + rectCell.width - rect.width;
//else: nothing to do for the left alignment
}
else
{
centerDir |= wxHORIZONTAL;
}
if ( size.y < rectCell.height )
{
if ( vAlign == wxALIGN_CENTRE_VERTICAL )
centerDir |= wxVERTICAL;
else if ( vAlign == wxALIGN_BOTTOM )
rect.y = rectCell.y + rectCell.height - rect.height;
//else: nothing to do for the top alignment
}
else
{
centerDir |= wxVERTICAL;
}
if ( centerDir )
rect = rect.CenterIn(rectCell, centerDir);
wxGridCellEditor::SetSize(rect);
}
void wxGridCellEditor::HandleReturn(wxKeyEvent& event)
{
event.Skip();
@@ -426,14 +482,6 @@ void wxGridCellTextEditor::DoCreate(wxWindow* parent,
wxGridCellEditor::Create(parent, id, evtHandler);
}
void wxGridCellTextEditor::PaintBackground(wxDC& WXUNUSED(dc),
const wxRect& WXUNUSED(rectCell),
const wxGridCellAttr& WXUNUSED(attr))
{
// as we fill the entire client area,
// don't do anything here to minimize flicker
}
void wxGridCellTextEditor::SetSize(const wxRect& rectOrig)
{
wxRect rect(rectOrig);
@@ -703,22 +751,7 @@ void wxGridCellNumberEditor::SetSize(const wxRect& rectCell)
if ( size.y <= 0 )
size.y = rectCell.GetHeight();
wxRect rectSpin(rectCell.GetPosition(), size);
// If possible, i.e. if we're not editing the topmost or leftmost cell,
// center the control rectangle in the cell.
if ( rectCell.GetTop() > 0 )
{
rectSpin.SetTop(rectCell.GetTop() -
(rectSpin.GetHeight() - rectCell.GetHeight()) / 2);
}
if ( rectCell.GetLeft() > 0 )
{
rectSpin.SetLeft(rectCell.GetLeft() -
(rectSpin.GetWidth() - rectCell.GetWidth()) / 2);
}
wxGridCellEditor::SetSize(rectSpin);
DoPositionEditor(size, rectCell);
}
else
#endif // wxUSE_SPINCTRL
@@ -836,10 +869,19 @@ bool wxGridCellNumberEditor::IsAcceptedKey(wxKeyEvent& event)
if ( wxGridCellEditor::IsAcceptedKey(event) )
{
int keycode = event.GetKeyCode();
if ( (keycode < 128) &&
(wxIsdigit(keycode) || keycode == '+' || keycode == '-'))
switch ( keycode )
{
return true;
// Accept +/- because they can be part of the number and space just
// because it's a convenient key to start editing with and is also
// consistent with many (all?) other editors, which allow starting
// editing using it.
case '+':
case '-':
case ' ':
return true;
default:
return (keycode < 128) && wxIsdigit(keycode);
}
}
@@ -1451,25 +1493,14 @@ void wxGridCellChoiceEditor::SetSize(const wxRect& rect)
wxASSERT_MSG(m_control,
wxT("The wxGridCellChoiceEditor must be created first!"));
// Check that the rectangle is big enough to fit the combobox, we can't
// afford truncating it.
wxSize size = rect.GetSize();
size.IncTo(m_control->GetBestSize());
// Use normal wxChoice size, except for extending it to fill the cell
// width: we can't be smaller because this could make the control unusable
// and we don't want to be taller because this looks unusual and weird.
wxSize size = m_control->GetBestSize();
if ( size.x < rect.width )
size.x = rect.width;
wxGridCellEditor::SetSize(wxRect(size).CentreIn(rect));
}
void wxGridCellChoiceEditor::PaintBackground(wxDC& dc,
const wxRect& rectCell,
const wxGridCellAttr& attr)
{
// as we fill the entire client area, don't do anything here to minimize
// flicker
// TODO: It doesn't actually fill the client area since the height of a
// combo always defaults to the standard. Until someone has time to
// figure out the right rectangle to paint, just do it the normal way.
wxGridCellEditor::PaintBackground(dc, rectCell, attr);
DoPositionEditor(size, rect);
}
void wxGridCellChoiceEditor::BeginEdit(int row, int col, wxGrid* grid)
@@ -1780,18 +1811,18 @@ void wxGridCellDateEditor::SetSize(const wxRect& r)
{
wxASSERT_MSG(m_control, "The wxGridCellDateEditor must be created first!");
const wxSize bestSize = DatePicker()->GetBestSize();
wxSize size = DatePicker()->GetBestSize();
wxRect rect(r.GetPosition(), bestSize);
// Allow edit picker to become a bit wider, if necessary, but no more than
// twice as wide as the best width, otherwise they just look ugly.
if ( r.GetWidth() > bestSize.GetWidth() )
// Allow date picker to become a bit wider, if necessary, but not too wide,
// otherwise it just looks ugly.
if ( r.GetWidth() > size.GetWidth()
&& r.GetWidth() < 3*size.GetWidth()/2 )
{
rect.SetWidth(wxMin(r.GetWidth(), 2*bestSize.GetWidth()));
size.x = r.GetWidth();
}
wxGridCellEditor::SetSize(rect);
// Use right alignment by default for consistency with the date renderer.
DoPositionEditor(size, r, wxALIGN_RIGHT);
}
void wxGridCellDateEditor::BeginEdit(int row, int col, wxGrid* grid)

View File

@@ -290,6 +290,22 @@ WXLRESULT wxTopLevelWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WX
DoRestoreLastFocus();
}
else if ( id == SC_KEYMENU )
{
// Alt-Backspace is understood as an accelerator for "Undo"
// by the native EDIT control, but pressing it results in a
// beep by default when the resulting SC_KEYMENU is handled
// by DefWindowProc(), so pretend to handle it ourselves if
// we're editing a text control to avoid the annoying beep.
if ( lParam == VK_BACK )
{
if ( wxWindow* const focus = FindFocus() )
{
if ( focus->WXGetTextEntry() )
processed = true;
}
}
}
#ifndef __WXUNIVERSAL__
// We need to generate events for the custom items added to the