Implement sorting in wxTreeListCtrl.

Allow the user to sort the control contents by clicking on the columns with
wxCOL_SORTABLE flag and also provide SetSortColumn() method to sort the
control programmatically.

Also add wxTreeListItemComparator class and SetItemComparator() method to
allow customizing the way the items are compared.

Update the sample to show how to define a custom comparator.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@69176 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin 2011-09-21 15:07:53 +00:00
parent 15b8afdcb8
commit da2e758f83
4 changed files with 429 additions and 22 deletions

View File

@ -28,6 +28,7 @@ class WXDLLIMPEXP_FWD_ADV wxDataViewEvent;
extern WXDLLIMPEXP_DATA_CORE(const char) wxTreeListCtrlNameStr[];
class wxTreeListCtrl;
class wxTreeListModel;
class wxTreeListModelNode;
@ -77,6 +78,33 @@ typedef wxVector<wxTreeListItem> wxTreeListItems;
extern WXDLLIMPEXP_DATA_ADV(const wxTreeListItem) wxTLI_FIRST;
extern WXDLLIMPEXP_DATA_ADV(const wxTreeListItem) wxTLI_LAST;
// ----------------------------------------------------------------------------
// wxTreeListItemComparator: defines order of wxTreeListCtrl items.
// ----------------------------------------------------------------------------
class wxTreeListItemComparator
{
public:
wxTreeListItemComparator() { }
// The comparison function should return negative, null or positive value
// depending on whether the first item is less than, equal to or greater
// than the second one. The items should be compared using their values for
// the given column.
virtual int
Compare(wxTreeListCtrl* treelist,
unsigned column,
wxTreeListItem first,
wxTreeListItem second) = 0;
// Although this class is not used polymorphically by wxWidgets itself,
// provide virtual dtor in case it's used like this in the user code.
virtual ~wxTreeListItemComparator() { }
private:
wxDECLARE_NO_COPY_CLASS(wxTreeListItemComparator);
};
// ----------------------------------------------------------------------------
// wxTreeListCtrl: a control combining wxTree- and wxListCtrl features.
// ----------------------------------------------------------------------------
@ -323,6 +351,34 @@ public:
// Sorting.
// --------
// Sort by the given column, either in ascending (default) or descending
// sort order.
//
// By default, simple alphabetical sorting is done by this column contents
// but SetItemComparator() may be called to perform comparison in some
// other way.
void SetSortColumn(unsigned col, bool ascendingOrder = true);
// If the control contents is sorted, return true and fill the output
// parameters with the column which is currently used for sorting and
// whether we sort using ascending or descending order. Otherwise, i.e. if
// the control contents is unsorted, simply return false.
bool GetSortColumn(unsigned* col, bool* ascendingOrder = NULL);
// Set the object to use for comparing the items. It will be called when
// the control is being sorted because the user clicked on a sortable
// column.
//
// The provided pointer is stored by the control so the object it points to
// must have a life-time equal or greater to that of the control itself. In
// addition, the pointer can be NULL to stop using custom comparator and
// revert to the default alphabetical comparison.
void SetItemComparator(wxTreeListItemComparator* comparator);
// View window functions.
// ----------------------
@ -356,10 +412,14 @@ private:
int imageOpened,
wxClientData* data);
// Send wxTreeListEvent corresponding to the given wxDataViewEvent.
// Send wxTreeListEvent corresponding to the given wxDataViewEvent for an
// item (as opposed for column-oriented events).
//
// Also updates the original event "skipped" and "vetoed" flags.
void SendEvent(wxEventType evt, wxDataViewEvent& event);
void SendItemEvent(wxEventType evt, wxDataViewEvent& event);
// Send wxTreeListEvent corresponding to the given column wxDataViewEvent.
void SendColumnEvent(wxEventType evt, wxDataViewEvent& event);
// Called by wxTreeListModel when an item is toggled by the user.
@ -371,6 +431,7 @@ private:
void OnItemExpanded(wxDataViewEvent& event);
void OnItemActivated(wxDataViewEvent& event);
void OnItemContextMenu(wxDataViewEvent& event);
void OnColumnSorted(wxDataViewEvent& event);
void OnSize(wxSizeEvent& event);
wxDECLARE_EVENT_TABLE();
@ -379,6 +440,8 @@ private:
wxDataViewCtrl* m_view;
wxTreeListModel* m_model;
wxTreeListItemComparator* m_comparator;
// It calls our inherited protected wxWithImages::GetImage() method.
friend class wxTreeListModel;
@ -393,12 +456,16 @@ private:
class wxTreeListEvent : public wxNotifyEvent
{
public:
// The item affected by the event.
// The item affected by the event. Valid for all events except
// column-specific ones such as COLUMN_SORTED.
wxTreeListItem GetItem() const { return m_item; }
// The previous state of the item checkbox for ITEM_CHECKED events only.
wxCheckBoxState GetOldCheckedState() const { return m_oldCheckedState; }
// The index of the column affected by the event. Currently only used by
// COLUMN_SORTED event.
unsigned GetColumn() const { return m_column; }
virtual wxEvent* Clone() const { return new wxTreeListEvent(*this); }
@ -411,6 +478,10 @@ private:
m_item(item)
{
SetEventObject(treelist);
m_column = static_cast<unsigned>(-1);
m_oldCheckedState = wxCHK_UNDETERMINED;
}
// Set the checkbox state before this event for ITEM_CHECKED events.
@ -419,11 +490,19 @@ private:
m_oldCheckedState = state;
}
// Set the column affected by this event for COLUMN_SORTED events.
void SetColumn(unsigned column)
{
m_column = column;
}
const wxTreeListItem m_item;
wxCheckBoxState m_oldCheckedState;
unsigned m_column;
friend class wxTreeListCtrl;
wxDECLARE_ABSTRACT_CLASS(wxTreeListEvent);
@ -468,6 +547,10 @@ wxDECLARE_TREELIST_EVENT(ITEM_CONTEXT_MENU);
#define EVT_TREELIST_ITEM_CONTEXT_MENU(id, fn) \
wxEVT_TREELIST_GENERIC(ITEM_CONTEXT_MENU, id, fn)
wxDECLARE_TREELIST_EVENT(COLUMN_SORTED);
#define EVT_TREELIST_COLUMN_SORTED(id, fn) \
wxEVT_TREELIST_GENERIC(COLUMN_SORTED, id, fn)
#undef wxDECLARE_TREELIST_EVENT
#endif // wxUSE_TREELISTCTRL

View File

@ -38,6 +38,64 @@ public:
bool IsOk() const;
};
/**
Class defining sort order for the items in wxTreeListCtrl.
@see wxTreeListCtrl
@library{wxadv}
@category{ctrl}
@since 2.9.3
*/
class wxTreeListItemComparator
{
public:
/**
Default constructor.
Notice that this class is not copyable, comparators are not passed by
value.
*/
wxTreeListItemComparator();
/**
Pure virtual function which must be overridden to define sort order.
The comparison function should return negative, null or positive value
depending on whether the first item is less than, equal to or greater
than the second one. The items should be compared using their values
for the given column.
@param treelist
The control whose contents is being sorted.
@param column
The column of this control used for sorting.
@param first
First item to compare.
@param second
Second item to compare.
@return
A negative value if the first item is less than (i.e. should appear
above) the second one, zero if the two items are equal or a
positive value if the first item is greater than (i.e. should
appear below) the second one.
*/
virtual int
Compare(wxTreeListCtrl* treelist,
unsigned column,
wxTreeListItem first,
wxTreeListItem second) = 0;
/**
Trivial but virtual destructor.
Although this class is not used polymorphically by wxWidgets itself,
provide virtual dtor in case it's used like this in the user code.
*/
virtual ~wxTreeListItemComparator();
};
/**
Container of multiple items.
*/
@ -81,6 +139,19 @@ extern const wxTreeListItem wxTLI_LAST;
the other columns.
Unlike wxTreeCtrl or wxListCtrl this control can sort its items on its own.
To allow user to sort the control contents by clicking on some column you
should use wxCOL_SORTABLE flag when adding that column to the control. When
a column with this flag is clicked, the control resorts itself using the
values in this column. By default the sort is done using alphabetical order
comparison of the items text, which is not always correct (e.g. this
doesn't work for the numeric columns). To change this you may use
SetItemComparator() method to provide a custom comparator, i.e. simply an
object that implements comparison between the two items. The treelist
sample shows an example of doing this. And if you need to sort the control
programmatically, you can call SetSortColumn() method.
Here are the styles supported by this control. Notice that using
wxTL_USER_3STATE implies wxTL_3STATE and wxTL_3STATE in turn implies
wxTL_CHECKBOX.
@ -132,6 +203,11 @@ extern const wxTreeListItem wxTLI_LAST;
@event{EVT_TREELIST_ITEM_CONTEXT_MENU(id, func)}
Process @c wxEVT_COMMAND_TREELIST_ITEM_CONTEXT_MENU event indicating
that the popup menu for the given item should be displayed.
@event{EVT_TREELIST_COLUMN_SORTED(id, func)}
Process @c wxEVT_COMMAND_TREELIST_COLUMN_SORTED event indicating that
the control contents has just been resorted using the specified column.
The event doesn't carry the sort direction, use GetSortColumn() method
if you need to know it.
@endEventTable
@library{wxadv}
@ -247,8 +323,9 @@ public:
@param align
Alignment of both the column header and its items.
@param flags
Column flags, currently can only include wxCOL_RESIZABLE to allow
the user to resize the column.
Column flags, currently can include wxCOL_RESIZABLE to allow the
user to resize the column and wxCOL_SORTABLE to allow the user to
resort the control contents by clicking on this column.
@return
Index of the new column or -1 on failure.
*/
@ -665,6 +742,71 @@ public:
//@}
/**
Sorting.
If some control columns were added with wxCOL_SORTABLE flag, clicking
on them will automatically resort the control using the custom
comparator set by SetItemComparator() or by doing alphabetical
comparison by default.
In any case, i.e. even if the user can't sort the control by clicking
on its header, you may call SetSortColumn() to sort it programmatically
and call GetSortColumn() to determine whether it's sorted now and, if
so, by which column and in which order.
*/
//@{
/**
Set the column to use for sorting and the order in which to sort.
Calling this method resorts the control contents using the values of
the items in the specified column. Sorting uses custom comparator set
with SetItemComparator() or alphabetical comparison of items texts if
none was specified.
Notice that currently there is no way to reset sort order.
@param col
A valid column index.
@param ascendingOrder
Indicates whether the items should be sorted in ascending (A to Z)
or descending (Z to A) order.
*/
void SetSortColumn(unsigned col, bool ascendingOrder = true);
/**
Return the column currently used for sorting, if any.
If the control is currently unsorted, the function simply returns
@false and doesn't modify any of its output parameters.
@param col
Receives the index of the column used for sorting if non-@NULL.
@param ascendingOrder
Receives @true or @false depending on whether the items are sorted
in ascending or descending order.
@return
@true if the control is sorted or @false if it isn't sorted at all.
*/
bool GetSortColumn(unsigned* col, bool* ascendingOrder = NULL);
/**
Set the object to use for comparing the items.
This object will be used when the control is being sorted because the
user clicked on a sortable column or SetSortColumn() was called.
The provided pointer is stored by the control so the object it points
to must have a life-time equal or greater to that of the control
itself. In addition, the pointer can be @NULL to stop using custom
comparator and revert to the default alphabetical comparison.
*/
void SetItemComparator(wxTreeListItemComparator* comparator);
//@}
/**
View window.
@ -722,6 +864,14 @@ public:
wxTreeListCtrl::GetCheckedState().
*/
wxCheckBoxState GetOldCheckedState() const;
/**
Return the column affected by the event.
This is currently only used with @c wxEVT_COMMAND_TREELIST_COLUMN_SORTED
event.
*/
unsigned GetColumn() const;
};

View File

@ -50,9 +50,10 @@
#endif
// ----------------------------------------------------------------------------
// Constants for menu items
// Constants
// ----------------------------------------------------------------------------
// Menu items.
enum
{
Id_MultiSelect = 100,
@ -72,6 +73,89 @@ enum
Id_Select_HTMLDocs
};
// Tree list columns.
enum
{
Col_Component,
Col_Files,
Col_Size
};
// ----------------------------------------------------------------------------
// Custom comparator for tree list items comparison
// ----------------------------------------------------------------------------
// This is a toy class as in a real program you would have the original numeric
// data somewhere and wouldn't need to parse it back from strings presumably.
// Nevertheless it shows how to implement a custom comparator which is needed
// if you want to sort by a column with non-textual contents.
class MyComparator : public wxTreeListItemComparator
{
public:
virtual int
Compare(wxTreeListCtrl* treelist,
unsigned column,
wxTreeListItem item1,
wxTreeListItem item2)
{
wxString text1 = treelist->GetItemText(item1, column),
text2 = treelist->GetItemText(item2, column);
switch ( column )
{
case Col_Component:
// Simple alphabetical comparison is fine for those.
return text1.CmpNoCase(text2);
case Col_Files:
// Compare strings as numbers.
return GetNumFilesFromText(text1) - GetNumFilesFromText(text2);
case Col_Size:
// Compare strings as numbers but also take care of "KiB" and
// "MiB" suffixes.
return GetSizeFromText(text1) - GetSizeFromText(text2);
}
wxFAIL_MSG( "Sorting on unknown column?" );
return 0;
}
private:
// Return the number of files handling special value "many". Notice that
// the returned value is signed to allow using it in subtraction above.
int GetNumFilesFromText(const wxString& text) const
{
unsigned long n;
if ( !text.ToULong(&n) )
{
if ( text == "many" )
n = 9999;
else
n = 0;
}
return n;
}
// Return the size in KiB from a string with either KiB or MiB suffix.
int GetSizeFromText(const wxString& text) const
{
wxString size;
unsigned factor = 1;
if ( text.EndsWith(" MiB", &size) )
factor = 1024;
else if ( !text.EndsWith(" KiB", &size) )
return 0;
unsigned long n = 0;
size.ToULong(&n);
return n*factor;
}
};
// ----------------------------------------------------------------------------
// Application class
// ----------------------------------------------------------------------------
@ -140,6 +224,8 @@ private:
wxTreeListCtrl* m_treelist;
MyComparator m_comparator;
wxTreeListItem m_itemHTMLDocs;
wxLog* m_oldLogTarget;
@ -304,20 +390,18 @@ wxTreeListCtrl* MyFrame::CreateTreeListCtrl(long style)
style);
tree->SetImageList(m_imageList);
enum
{
Col_Component,
Col_Files,
Col_Size
};
tree->AppendColumn("Component");
tree->AppendColumn("Component",
wxCOL_WIDTH_AUTOSIZE,
wxALIGN_LEFT,
wxCOL_RESIZABLE | wxCOL_SORTABLE);
tree->AppendColumn("# Files",
tree->WidthFor("1,000,000"),
wxALIGN_RIGHT);
wxALIGN_RIGHT,
wxCOL_RESIZABLE | wxCOL_SORTABLE);
tree->AppendColumn("Size",
tree->WidthFor("1,000,000 KiB"),
wxALIGN_RIGHT);
wxALIGN_RIGHT,
wxCOL_RESIZABLE | wxCOL_SORTABLE);
// Define a shortcut to save on typing here.
#define ADD_ITEM(item, parent, files, size) \
@ -351,6 +435,9 @@ wxTreeListCtrl* MyFrame::CreateTreeListCtrl(long style)
// Remember this one for subsequent tests.
m_itemHTMLDocs = HTML;
// Set a custom comparator to compare strings containing numbers correctly.
tree->SetItemComparator(&m_comparator);
return tree;
}

View File

@ -378,6 +378,10 @@ public:
virtual unsigned GetChildren(const wxDataViewItem& item,
wxDataViewItemArray& children) const;
virtual bool IsListModel() const { return m_isFlat; }
virtual int Compare(const wxDataViewItem& item1,
const wxDataViewItem& item2,
unsigned col,
bool ascending) const;
private:
// The control we're associated with.
@ -953,6 +957,27 @@ wxTreeListModel::GetChildren(const wxDataViewItem& item,
return numChildren;
}
int
wxTreeListModel::Compare(const wxDataViewItem& item1,
const wxDataViewItem& item2,
unsigned col,
bool ascending) const
{
// Compare using default alphabetical order if no custom comparator.
wxTreeListItemComparator* const comp = m_treelist->m_comparator;
if ( !comp )
return wxDataViewModel::Compare(item1, item2, col, ascending);
// Forward comparison to the comparator:
int result = comp->Compare(m_treelist, col, FromDVI(item1), FromDVI(item2));
// And adjust by the sort order if necessary.
if ( !ascending )
result = -result;
return result;
}
// ============================================================================
// wxTreeListCtrl implementation
// ============================================================================
@ -963,6 +988,7 @@ BEGIN_EVENT_TABLE(wxTreeListCtrl, wxWindow)
EVT_DATAVIEW_ITEM_EXPANDED(wxID_ANY, wxTreeListCtrl::OnItemExpanded)
EVT_DATAVIEW_ITEM_ACTIVATED(wxID_ANY, wxTreeListCtrl::OnItemActivated)
EVT_DATAVIEW_ITEM_CONTEXT_MENU(wxID_ANY, wxTreeListCtrl::OnItemContextMenu)
EVT_DATAVIEW_COLUMN_SORTED(wxID_ANY, wxTreeListCtrl::OnColumnSorted)
EVT_SIZE(wxTreeListCtrl::OnSize)
END_EVENT_TABLE()
@ -975,6 +1001,7 @@ void wxTreeListCtrl::Init()
{
m_view = NULL;
m_model = NULL;
m_comparator = NULL;
}
bool wxTreeListCtrl::Create(wxWindow* parent,
@ -1454,11 +1481,48 @@ wxTreeListCtrl::AreAllChildrenInState(wxTreeListItem item,
return true;
}
// ----------------------------------------------------------------------------
// Sorting
// ----------------------------------------------------------------------------
void wxTreeListCtrl::SetSortColumn(unsigned col, bool ascendingOrder)
{
wxCHECK_RET( col < m_view->GetColumnCount(), "Invalid column index" );
m_view->GetColumn(col)->SetSortOrder(ascendingOrder);
}
bool wxTreeListCtrl::GetSortColumn(unsigned* col, bool* ascendingOrder)
{
const unsigned numColumns = m_view->GetColumnCount();
for ( unsigned n = 0; n < numColumns; n++ )
{
wxDataViewColumn* const column = m_view->GetColumn(n);
if ( column->IsSortKey() )
{
if ( col )
*col = n;
if ( ascendingOrder )
*ascendingOrder = column->IsSortOrderAscending();
return true;
}
}
return false;
}
void wxTreeListCtrl::SetItemComparator(wxTreeListItemComparator* comparator)
{
m_comparator = comparator;
}
// ----------------------------------------------------------------------------
// Events
// ----------------------------------------------------------------------------
void wxTreeListCtrl::SendEvent(wxEventType evt, wxDataViewEvent& eventDV)
void wxTreeListCtrl::SendItemEvent(wxEventType evt, wxDataViewEvent& eventDV)
{
wxTreeListEvent eventTL(evt, this, m_model->FromDVI(eventDV.GetItem()));
@ -1474,6 +1538,23 @@ void wxTreeListCtrl::SendEvent(wxEventType evt, wxDataViewEvent& eventDV)
}
}
void wxTreeListCtrl::SendColumnEvent(wxEventType evt, wxDataViewEvent& eventDV)
{
wxTreeListEvent eventTL(evt, this, wxTreeListItem());
eventTL.SetColumn(eventDV.GetColumn());
if ( !ProcessWindowEvent(eventTL) )
{
eventDV.Skip();
return;
}
if ( !eventTL.IsAllowed() )
{
eventDV.Veto();
}
}
void
wxTreeListCtrl::OnItemToggled(wxTreeListItem item, wxCheckBoxState stateOld)
{
@ -1485,27 +1566,32 @@ wxTreeListCtrl::OnItemToggled(wxTreeListItem item, wxCheckBoxState stateOld)
void wxTreeListCtrl::OnSelectionChanged(wxDataViewEvent& event)
{
SendEvent(wxEVT_COMMAND_TREELIST_SELECTION_CHANGED, event);
SendItemEvent(wxEVT_COMMAND_TREELIST_SELECTION_CHANGED, event);
}
void wxTreeListCtrl::OnItemExpanding(wxDataViewEvent& event)
{
SendEvent(wxEVT_COMMAND_TREELIST_ITEM_EXPANDING, event);
SendItemEvent(wxEVT_COMMAND_TREELIST_ITEM_EXPANDING, event);
}
void wxTreeListCtrl::OnItemExpanded(wxDataViewEvent& event)
{
SendEvent(wxEVT_COMMAND_TREELIST_ITEM_EXPANDED, event);
SendItemEvent(wxEVT_COMMAND_TREELIST_ITEM_EXPANDED, event);
}
void wxTreeListCtrl::OnItemActivated(wxDataViewEvent& event)
{
SendEvent(wxEVT_COMMAND_TREELIST_ITEM_ACTIVATED, event);
SendItemEvent(wxEVT_COMMAND_TREELIST_ITEM_ACTIVATED, event);
}
void wxTreeListCtrl::OnItemContextMenu(wxDataViewEvent& event)
{
SendEvent(wxEVT_COMMAND_TREELIST_ITEM_CONTEXT_MENU, event);
SendItemEvent(wxEVT_COMMAND_TREELIST_ITEM_CONTEXT_MENU, event);
}
void wxTreeListCtrl::OnColumnSorted(wxDataViewEvent& event)
{
SendColumnEvent(wxEVT_COMMAND_TREELIST_COLUMN_SORTED, event);
}
// ----------------------------------------------------------------------------
@ -1572,6 +1658,7 @@ wxDEFINE_TREELIST_EVENT(ITEM_EXPANDED);
wxDEFINE_TREELIST_EVENT(ITEM_CHECKED);
wxDEFINE_TREELIST_EVENT(ITEM_ACTIVATED);
wxDEFINE_TREELIST_EVENT(ITEM_CONTEXT_MENU);
wxDEFINE_TREELIST_EVENT(COLUMN_SORTED);
#undef wxDEFINE_TREELIST_EVENT