I have the following small issue. A pretty old MFC application being ported to C++11. There is a MFC CListCtrl in report view the data in which is being refreshed by a separate thread. Either manually or via a timer. The current implementation sets m_ListCtrl->SetRedraw(FALSE); deletes all items then populates them back again. This causes a flicker. Also internally the items are sorted via GetItemData DWORD, setting each list items new position after sorting. The state of sorting is internal to CListCtrl.
What is the best design pattern to add new, update or remove deleted list items after the table has been modified? I am thinking of hashing the contents of the list item row, keeping a separate vector<RowObject> table current and after refresh and comparing hashes. Is this a right approach?
Or may be use m_ListCtrl->SetItemText on existing items? But how to deal with sorting in this case?
Related
I have a model that retrieves data from a table in a database from a certain SQL query, and shows the items in a QTreeView. The characteristics are:
the data comes from a table, but has an underlying tree structure (some rows are parents that have rows below them as children)
this tree structure is shown in the QTreeView
the children are selectable in the QTreeView (not so the parents)
the table in the database gets updated continuously
in the updates, a children can be added to any existing parent
periodically (with a QTimer) the QTreeView is updated with the contents of the table
Since the children are added at any time to any parent, the first silly approach when updating the QTreeView is clearing it all, and append all the rows again, in form of parent or children, to the QTreeView. This is a 0-order approximation, and it is indeed terrible inefficient. In particular, the following problems appear:
Any existing selection is gone
Any expanded parent showing its children is collapsed (unless ExpandAll is active)
The view is reset to show the very first row.
What is the best solution to this problem? I mean, the first solution I will try will be not to clear the QTreeView, but instead parse all the returned rows from the table, and check for each of them whether the corresponding item in the QTreeView exists, and add it if not. But I wonder if there is a trickiest solution to engage a given table in a database with a QTreeView (I know this exists for a QTableView, but then the tree structure is gone).
This thread mentions a general approach, but this might get tricky quickly, but I am not sure how this would work if the underlying model is changing constantly (i.e. the QModelIndex becoming invalid).
Worst case is that you will have to write your own mechanism to remember the selection before updating and then re-applying it.
I assume you use some model/view implementation? You could enhance your model with a safe selection handling, in case the example mentioned above does not work for you.
I guess this is the case for a self-answer.
As I presumed, after a careful analysis of what data is retrieved from the database, I had to do the following "upgrades" to the retrieval code:
I retrieve, along with the fields I want to show in the view, two identifiers, one for grouping rows and one for sorting items into groups
I also retrieve the internal record ID (an increasing integer number) from the table in the database, in order to ask only for new records in the next retrieval.
In the model population code, I added the following:
I first scan the initial records that may belong to existing groups in the model
When, in the scanning, I reach the last group in the model, this implies that the rest of retrieved records belong to new groups (remember the records are retrieved sorted such that items that belong to the same group are retrieved together)
Then start creating groups and adding items to each group, until we use all the records retrieved.
Finally, it is very important:
the use beginInsertRows() and endInsertRows() before and after inserting new items in the model
capture the sorting status of the view (with sortIndicatorSection() and sortIndicatorOrder()) and re-apply this sorting status after updating the model (with sortByColumn())
Doing that the current position and selection in the QTreeView receiving the model updates are preserved, and the items in the view are added and the view updated transparently for the user.
I'm currently trying to apply a filter to the wxCheckListBox (for a search of specific elements). So far i have no idea how to do it. The Problem with it is, that I don't want to have a copy of the Control and always delete the unnecessary items from the copy, and as soon as the search changes it has to be copied again from the original and delete the items again. I was wondering if there is a way to simply hide some items and not the entire control
You can't hide the items in a wxListBox or wxCheckListBox. To have this sort of dynamic control over the items appearing in the control you need to use wxListCtrl in virtual mode.
However it's not usually really a problem to delete some items from a wxListBox and then insert them back (or, even simpler, store all the items, delete some of them from the control and then, to revert, clear the control and restore all the initially stored items).
I basically want to have the same virtual performance I can get with a List-View control. With a List-View control you can set an ItemCount and in the LVN_GETDISPINFO notification you then fill in the information for the items once they are scrolled visible.
Now, the virtual functionality the Tree-View provides is good for very deep trees, so you would only add items once a node expands (via TVN_ITEMEXPANDING), and TVN_GETDISPINFO can be used for filling in item information once the item is scrolled visible. But what to do if you have an "always expanded" two-level tree (just for design matters) where TVN_ITEMEXPANDING wouldn't be of any use and only want to add the items once they would be visible. The problem is, there's no such thing as SetItemCount() or similar to already resize the tree.
In my case, the filling of item information (text, image, selected image) isn't the expensive part, but the inserting of items (all at one level) is.
One option would be to only insert the items which would be visible plus one invisible one, once the invisible one gets visible (detected in TVN_GETDISPINFO), I'd insert a few more and so on. But then the scrollbar would always get smaller the more I scroll down, I think that's weird.
Are there any other ideas to achieve what I want except from drawing my own control?
The whole tree would just look like this, pretty much a list, it's just that I like the tree-look.
RootNode
|
|--Item 1
|--Item 2
|--Item 3
|--Item 4
|--Item 5
|--Item 6
|--Item 7
...
|__Item 1000
As stated in many other posts, the really expensive part about the Tree-View control is using InsertItem and DeleteItem. A quick way to improve performance for those operations is making use of SetRedraw. Not only does it hide the flickering but does really speed things up, since the drawing seems to be expensive - even though TVN_GETDISPINFO is used.
Also, it's faster to rename existing items and change their data instead of deleting and adding new ones. So when I have a big list and know that the next update will contain about the same amount +/- a couple ones, I go iterate through the items, rename them, change their lparams and sync (i.e. remove/add) the remaining ones in accordance to the new data. Depending on the size of the list, making those extra calculations can have a huge performance improvement.
The Win32 TreeView control does not support the kind of virtual mode you are looking for. So you will need a custom control.
This question is to understand how to update any row programatically.
Details.
I have a listcrtl, that accepts the data from either from a file or from the edit controls in the dialog. When the items are added I will know its position, that I added, so I can change its subitem texts. I have even implemented the sort functionality in the list, so now the position keeps changing. I have an identifier column for each row, so that I can recognize the row.
Now, from an out side event, if I have to change an other columns value of an ID that I know , I have to first find the position of the item by comparing the id column, then with that position, I have set the subitemtext.
This works fine except that it takes time to find the row first then it need to update the column.
Now, in order to get the row directly, I need some help.
I have gone through
http://msdn.microsoft.com/en-us/library/windows/desktop/hh298346(v=vs.85).aspx
But this does not use MFC. Please help me achieving this.
If you have many items you should consider switching to Virtual Lists. It is the fastest way to access the data. If you do not want to invest time to this, then the easiest way for you will be the following:
When you populate the CListCtrl store the ID of each item in the item data using the SetItemData() method. The ID will always be associated with the item, even after re-sorting.
When you need to locate the required item, just scan all items, but do not use GetItemText(). Use GetItemData() instead. This will be faster
I am using a List Control to display a representation of elements within a vector. When the list is clicked on another control shows information about that element. The index of the element is currently determined by its index in the control, however if I wish to sort or filter the results this will no longer work.
I have been told that I could use a virtual list control, but the MSDN is not very friendly, can someone run me through how I could use a virtual list control for this?
Frankly - tying data (the position in your data vector) to the presentation of the data in the list control (the position in the list ctrl) is something I would stay away from.
In MFC each control has a "Data" DWORD member variable - when coding in MFC I Always called "SetItemData" for each item added and passed in a pointer that the relevant row referred to e.g.
YourListCtrl.SetItemData((DWORDPTR)&YourData);
Then when the ListCtrl item is selected, you just call
DataTypeYouWant* pData = (DataTypeYouWant*)(YourListCtrl.GetItemData(indexofselecteditem));
Or somesuch thing.
Alternatively - if you don't want to use pointers - hold the index of the item in your original vector in the itemdata for your row (just pass it into the above fns).
To use a virtual list control, set the LVS_OWNERDATA style. You then need to handle the LVN_GETDISPINFO notification message (which is sent via WM_NOTIFY).
If you do this, you are entirely responsible for the data, including the order in which it is shown. Therefore it is up to you to handle sorting and so forth.
By far the easiest way is just to use the item data to set/get an ID that can be used to retrieve the original data, whether that's a vector index or a pointer to the data, or even a key into an associative container.
It really depends on the performance you require.
I have personally seen MAJOR increases in performance for lists holding massive amount of data. However it is much more work to implement, thus for simple uses with not so many data I recommend staying away from it.
Basically, what happens with virtual list controls is that you have your data somewhere in some data structure of your own. Since the list view shows only a small subset of the whole data, it queries you for the content to display when ever something happens (redraw necessary, scroll up or down, change the sorting, etc.).
I don't have handy examples for you. But you can look on codeguru, I am quite sure there are very good examples to start from.
The purpose of virtual list controls is totally different: You should use it for performance reason when you have A LOT of items in your list (I'd say 2500+).
In your case, all you need is to store the vector index in the list item data as NotJarvis explains.