Virtual CListCtrl Auto Size - c++

I want to resize the columns of a virtual ClistCtrl (LVS_OWNERDATA flag) automatically.
I found in some forums that virtual lists can not use the "LVSCW_AUTOSIZE" option. Some suggest to implement an algorithm instead.
But once loaded my ClistCtrl without any resize option, a double-click on the header divider correctly resizes the visible columns.
So, how I can perform the function that is called by "HDN_DIVIDERDBLCLICKW"?

The autosizing suggested by Clements works for normal list controls, but not for virtual ones (because the control doesn't know anything about the column data). You have to provide the data column width yourself.

From this Codeproject article, you should be able to autosize a column with something like:
pListCtrl->SetColumnWidth(i, LVSCW_AUTOSIZE);
int nColumnWidth = pListCtrl->GetColumnWidth(i);
pListCtrl->SetColumnWidth(i, LVSCW_AUTOSIZE_USEHEADER);
int nHeaderWidth = pListCtrl->GetColumnWidth(i);
pListCtrl->SetColumnWidth(i, max(nColumnWidth, nHeaderWidth));
You may need to handle the LVN_GETDISPINFO notification to provide the necessary data to the virtual list control, though...

Related

QTableView slow scrolling when many cells are visible at once

Background: I'm developing application using Qt 5.5.1, compiling with msvc2013. In this app I use my own implementation of QTableView, along with custom QStyledItemDelegate (needed custom cell editing) and QAbstractTableModel. I intend this view to work with massive amount of data that I wrap inside mentioned model. I allow the user few data editing options, custom sorting, 'invalid' rows windup etc.
The problem: scrolling speed of my QTableView subclass is slow - it gets slower the more table is shown (by resizing window), e.g. ~250 cells shown (in fullscreen) = slow, ~70 cells shown (small window) = fast.
Whad did I try so far:
First was to check if my model is slowing things down - I have measured times (using QTime::elapsed()) reading 10k samples and it shown 0 or 1ms. Then I have simply altered QTableView::data method to always return predefined string and not acquire any real data.
QVariant DataSet_TableModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::ItemDataRole::DisplayRole) {
return QVariant("aRatherLongString"); //results in slow scrolling
//return QVariant("a"); // this instead results in fast scrolling
}
else return QVariant();
}
As you can see, the speed seems to be affected by number of characters vieved per cell, and not by underlying connections to data source.
In my custom implementation of QStyledItemDelegate I have tried same 'trick' as above - this time overriging displayText method:
QString DataSet_TableModel_StyledItemDelegate::displayText(const QVariant &value, const QLocale &locale) const
{
return "a" //fast
// return "aRatherLongString"; //slow
// return QStyledItemDelegate::displayText(value, locale); //default
}
After some thought with a friend we concluded that maybe we could disable drawing/painting/updating of cells until whole scroll action is done. It might cause some flickering, but it's worth a try. Unfortunately we dont really know how to aproach this. We have everriden QTableView methods: scrollContentsBy(int dx, int dy) and verticalScrollbarAction(int action) - we have captured scroll action properly (either method intercepts it) and tried to somehow disable repainting like this:
void DataSet_TableView::verticalScrollbarAction(int action) {
this->setUpdatesEnabled(false);
QTableView::verticalScrollbarAction(action);
this->setUpdatesEnabled(true);
}
...but it did not have any visible effect.
How should we approach it? Do we need to use setUpdatesEnabled() on items that are put inside cells directly? (not sure what those are - widgets?)
Here are screenshots taken as part of testing this problem:
Predefined text, no calls to underlying data structure - slow scrolling, 'full screen'
Predefined text, no calls to underlying data structure - fast scrolling, windowed
Request: Could you kindly help me pinpoint the cause of this and suggest solution if possible? Is it limitation of the classes that I use?
First of all, you should also run your application in release mode to check your perfomance, in my experience, the performance decreases greatly when using debug mode.
Secondly, you need to be aware that the model data method and delegates methods are called every time you resize, scroll, focus out, right click etc. These actions trigger those methods to be called for each displayed cell, therefore you would need to make sure that you don't do any unnecessary processing.
The items inside cells are delegates that call their own methods (eg: paint).
Some C++ specific optimisations would be helpful in the implementation of these methods, like using a switch instead of an if statement, see explanation here and here. The usage of Conditional (Ternary) Operators might also speed up the things, more information here, here and some information about expensive checkings here.
Also, QVariant handles text in different ways, as exemplified below, you should try both ways and check if there is any difference in speed. Some conversions are more expensive than others.
v = QVariant("hello"); // The variant now contains a QByteArray
v = QVariant(tr("hello")); // The variant now contains a QString

How to implement delegate in QHeaderView

I have created one table by using QTableview and QAbstractTableModel . i have added some vertical header by using QHeaderView . In one of the header cell i want to use delegate ..
I am using the delegate but it does not have any impact ..
Is anywhere i am doing wrong ?
Had this issue myself. The answer from the Qt documentation is simple and annoying:
Note: Each header renders the data for each section itself, and does
not rely on a delegate. As a result, calling a header's
setItemDelegate() function will have no effect.
In other words you cannot use delegates with QHeaderView.
For the record, if you want to style a QHeaderView section, you'll have to do it either via the header data model (changing Qt::FontRole, etc.) or derive your own QHeaderView (don't forget to pass it to your table with "setVerticalHeader()") and overwrite the its paintSection()-function.
i.e.:
void YourCustomHeaderView::paintSection(QPainter* in_p_painter, const QRect& in_rect, int in_section) const
{
if (nullptr == in_p_painter)
return;
// Paint default sections
in_p_painter->save();
QHeaderView::paintSection(in_p_painter, in_rect, in_section);
in_p_painter->restore();
// Paint your custom section content OVER a specific, finished
// default section (identified by index in this case)
if (m_your_custom_section_index == in_section)
{
QPen pen = in_p_painter->pen();
pen.setWidthF(5.5);
pen.setColor(QColor(m_separator_color));
in_p_painter->setPen(pen);
in_p_painter->drawLine(in_rect.right(), in_rect.top(), in_rect.right(), in_rect.bottom());
}
}
This simplified example could of course easily be done via a stylesheet instead, but you could theoretically draw whatever you like using this method.

Binding a CPen to a list box

Does anyone know how to bind a CPen object to a listbox in VS2005 C++?
Can I do it as a ToString with some sort of conversion?
I am creating a custom list of different pens for the user to select.
Thanks.
COLORREF rgbRED = (255,0,0);
CPen penRed(PS_SOLID,3,rgbRED);
CDialog::OnInitDialog();
ShowWindow(SW_SHOW);
UpdateData();
lbLineWeight.InsertString(penRed);
2 options.
(simple) Use a normal CListBox with strings as the items, and keep the link between the string to the actual CPen as free functions (or member of some other classes) and you will have to do a one-to-one association between the current selected item (usually an index number) and the CPen information you have.
(a bit more complex) Derive your own class from CListBox and keep the CPen data internally, you will still have to keep a list of valid CPen in that new class, and do the one-to-one association between the selected item and the actual CPen; as a bonus you can make you derived CListBox owner-drawn and instead of using string, you could draw some sort of representation of each pen in the list items.
Another tought, you could add the CPen as a user data to each CListBox item (CListBox::SetItemData) to make the link between the item and the actual item a bit more easy.
Good luck.
Max.
Assuming I understand you correctly you want to have a CListBox which allows the user to select a CPen for use elsewhere.
I would probably make a little helper class:
struct PenHelper
{
CString m_displayName;
LOGPEN m_penProps;
bool CreatePen(CPen* pPen)
{
return pPen->CreatePenIndirect(&m_penProps) == 1;
}
};
The idea being you could have a container like std::map of multiple PenHelper each with a names like "Solid Red" and a corresponding LOGPEN struct with properties that match the name. In the CListBox you add the display name string. When they select one you can look it up by name and use the create function to actually make the corresponding CPen
Just one of a million ways to skin a cat.
Edit: Quick note. In order to handle ON_LBN_SELCHANGE in your message map for when they make a selection in your CListBox make sure you gave it the LBS_NOTIFY style in the Create call otherwise it won't fire.

MFC: Displaying a tabulated display of text items

This should be simple it seems but I can't quite get it to work. I want a control (I guess CListBox or CListCtrl) which displays text strings in a nice tabulated way.
As items are added, they should be added along a row until that row is full, and then start a new row. Like typing in your wordprocessor - when the line is full, items start being added to the next line, and the control can scroll vertically.
What I get when trying with a list-mode CListCtrl is a single row which just keeps growing, with a horizontal scroll bar. I can't see a way to change that, there must be one?
You probably need a list control wth LVS_REPORT. If you expect the user to add items interactively using a keyboard, you probably need a data grid, not a list. Adding editing to list control subitems is not easy, and it would be easier to start from CWnd. Search "MFC Data Grid" to find some open source class libraries that implemented the feature.
If you can afford adding /clr to your program, you can try the data grid classes in Windows Forms using MFC's Windows Form hosting support. You will find a lot more programming resources on data grid classes in Windows Forms than any other third-party MFC data grid class library.
If you use CRichEditCtrl you can set it to word-wrap, take a look at this snippet extracted from:
http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.ui/2004-03/0111.html
(I've derived my own QRichEditCtrl from the MFC CRichEditCtrl,
and here's the relevant code:)
void QRichEditCtrl::SetWordWrap(bool bWrap)
{
RECT r;
GetWindowRect(&r);
CDC * pDC = GetDC();
long lLineWidth = 9999999; // This is the non-wrap width
if (bWrap)
{
lLineWidth = ::MulDiv(pDC->GetDeviceCaps(PHYSICALWIDTH),
1440, pDC->GetDeviceCaps(LOGPIXELSX));
}
SetTargetDevice(*GetDC(), lLineWidth);
}

How to (fast) fill a CListCtrl in C++ (MFC)?

in my application I have a few CListCtrl tables. I fill/refresh them with data from an array with a for-loop. Inside the loop I have to make some adjustments on how I display the values so data binding in any way is not possible at all.
The real problem is the time it takes to fill the table since it is redrawn row by row. If I turn the control invisible while it is filled and make it visible again when the loop is done the whole method is a lot faster!
Now I am looking for a way to stop the control from repainting until it is completely filled. Or any other way to speed things up.
Look into the method SetRedraw. Call SetRedraw(FALSE) before starting to fill the control, SetRedraw(TRUE) when finished.
I would also recommend using RAII for this:
class CFreezeRedraw
{
public:
CFreezeRedraw(CWnd & wnd) : m_Wnd(wnd) { m_Wnd.SetRedraw(FALSE); }
~CFreezeRedraw() { m_Wnd.SetRedraw(TRUE); }
private:
CWnd & m_Wnd;
};
Then use like:
CFreezeRedraw freezeRedraw(myListCtrl);
//... populate control ...
You can create an artificial block around the code where you populate the list control if you want freezeRedraw to go out of scope before the end of the function.
If you have a lot of records may be it is more appropriate to use virtual list style (LVS_OWNERDATA). You could find more information here.