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
Related
I am looking for an efficient way to view a list of thumbnails of jpegs in Qt 5.8 (possibly several thousands).
My current approach is to use a QListWidget (see below, and this question). This turns out to be way too slow as it takes forever to assemble, even for a few images.
I am looking:
To understand why my current approach is so slow.
For a more efficient approach, which still requires only little code by relying as much as possible on Qt's features.
Current approach:
ui->listWidget->setViewMode (QListWidget::IconMode);
ui->listWidget->setIconSize (QSize(200,200) );
ui->listWidget->setResizeMode(QListWidget::Adjust );
for ( auto &i : files )
ui->listWidget->addItem(new QListWidgetItem(QIcon(i),i));
(whereby files is of the std::vector<QString> type)
A quick solution might be to create a simple class that inherits from QListWidgetItem and fixes up the thumbnails on demand. You could try something like (note: untested)...
class thumbnail_item: public QListWidgetItem {
using super = QListWidgetItem;
public:
explicit thumbnail_item (const QString &filename)
: super(filename)
{}
virtual QVariant data (int role) const override
{
if (role == Qt::DecorationRole) {
QIcon i = super::icon();
if (i.isNull()) {
/*
* The existing thumbnail (if any) is empty so create one
* from the file name and use it -- remembering to call
* setIcon so we don't need to go through all this again
* next time.
*/
i = QIcon(data(Qt::DisplayRole));
setIcon(i);
}
return(i);
}
return(super::data(role));
}
};
You still need the loop to create an item for each file but at least it defers the thumbnail construction.
A more elegant solution would probably involve using fully separated model and view objects with a custom proxy model performing the thumbnail generation and caching.
I have low rep to comment on G.M.'s answer, so here is my addition:
The initialisation may take long time due to 2 reasons:
The access to image files and their decoding can take up significant time. It is nice to have some buffering/precaching or defering, as G.M. suggested.
I believe, images are loaded "as is" (in high resolution), but resized to tiny resolution for thumbnail view inside QIcon. This can be optimised away by using QImageReader class for image reading, specifically its method setScaledSize(const QSize &size).
I've got a pretty old MFC application that's been touched by many people over the years (most of them probably not even CS guys) and it follows, what I like to call the "anarchy design pattern."
Anyway, one of the dialogs has a series of 56 vertical sliders and check boxes. However, there are additional sliders and checkboxes on the dialog as shown below.
Now, the problem is that the additional sliders and checkboxes take on IDs that are in sequence with the slider/checkbox series of the dialog. My task is to add more sliders and checkboxes to the series (in the blank space in the Slider Control group box) Unfortunately, since IDC_SLIDER57 through IDC_SLIDER61 are already in the dialog (same goes for the checkboxes), existing code, such as the snippet below will break:
pVSlider = (CSliderCtrl *)GetDlgItem(IDC_SLIDER1+i);
Is there a better way to modify the resource file without doing it manually? I've seen a third party tool called ResOrg that looks like it'll help do what I want, but the software is a bit pricey, especially since I'll only use it once. I guess I can give the demo a try, but the limitations might restrict me.
FYI, I'm using Visual C++ 6.0 (yes...I know, don't laugh, it's being forced upon me).
Instead of writing:
pVSlider = (CSliderCtrl *)GetDlgItem(IDC_SLIDER1+i);
you could write:
pVSlider = (CSliderCtrl *)GetDlgItem(GetSliderID(i));
where GetSlider is a function that returns the id of slider number i.
GetSlider function
int GetSliderID(int nslider)
{
static int sliderids[] = {IDC_SLIDER1, IDC_SLIDER2, IDC_SLIDER3, .... IDC_SLIDERn};
ASSERT(nslider < _countof(sliderids));
return sliderids[nslider];
}
With this method the IDC_SLIDERn symbols dont need to have sequential values.
I've run into a bit of an issue related to a whitelist Web Browser my company has been developing / maintaining for one of our product lines. The browser runs on top of Qt 4.8.6, using qtwebkit (Migration to 5.X would be ideal, but the embedded Linux OS we're using is too old to support the newer versions based on our testing, and upgrading to a newer OS is too costly to us / our customers). The primary interface to the browser is a 6x8 touchscreen, mounted inside an aircraft cockpit.
For sites that have things like scrollable/embedded maps (ex. Google Maps), the users of the browser want the ability to drag the entire page when they are selecting something outside of the map, and drag just the map (without the entire page scrolling) when the map is selected (Ala most of the popular mobile browsers).
Thus far, I am able to do one or the other, but not both:
When I hook mouse handlers into a QWebView or QGraphicsWebView, I can turn the cursor into a hand and very easily support dragging of the entire web page. However, that inhibits the page's ability to handle the mouse events for when a user is pulling over a map (i.e. When a user drags over a map, it drags the entire page without moving the map).
When I don't add in the hooks to handle mouse events, things like maps are scrollable by grapping/dragging them, but of course the user loses the ability to drag the entire page.
Right now, the browser uses the later, with scroll bars disabled and a directional-arrow overlay to allow the user to scroll the entire page (as the display size is limited, and scrollbars take up too much space when they are sized large enough for the user to interact with them)...but this is not ideal.
My Question: Is there any easy way to make it so that the page, and elements in a page, can be scrolled seamlessly?
Thanks!
Rob
Seems to me like you need to check if you are over such a map and ignore(pass along) the event in that case. I think you should be able to do something like this:
bool GraphicsWebView::isOverMap(QPoint pos) {
QWebPage* webPage = this->page();
if (webPage) {
QWebFrame* webFrame = webPage->frameAt(pos);
if (webFrame) {
QString selectorQuery = "#map-canvas"; // Based on https://developers.google.com/maps/tutorials/fundamentals/adding-a-google-map
QList<QWebElement> list = webFrame->findAllElements(selectorQuery).toList(); // Find all the maps!
foreach(QWebElement element, list) {
if (element.geometry().contains(pos)) {
return true; // Cursor is over a map
}
}
}
}
return false; // No match
}
Obviously this is a pretty specific function but there is probably a way to come up with a better selector query that will apply to all those kinds of QWebElement.
Assuming you hook mouse events by subclassing QGraphicsWebView and reimplementing void mouseMoveEvent(QGraphicsSceneMouseEvent * event), I suggest you do something like:
void GraphicsWebView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
if (isOverMap(mapFromScene(event->scenePos()).toPoint())) { // We got a map!
event.ignore(); // Clear the accept flag
return; // Return, we're done here
}
handleMoveView(); // Not over any maps, let's scroll the page
}
This part of the doc explains how events are handled with regard to the topmost item. I especially recommend you read the third paragraph.
Hope that helps!
EDIT: Did a bit more research and it looks like something like that could be more generic:
graphicsView.focusItem()->flags().testFlag(QGraphicsItem::ItemIsMovable);
It's at the very least worth investigating as a replacement to isOverMap()
EDIT: Gotcha, here is something you can try then.
Start by subclassing QGraphicsSceneMouseEvent and add a signal called void destroyedWithoutAccept() that's emitted in the destructor if the event has not been accepted.
Then modify mouseMoveEvent to look like this:
void GraphicsWebView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
MyEvent myEvent = new MyEvent(event); // Copy event
event.accept(); // accept original event
connect(myEvent, SIGNAL(destroyedWithoutAccept),
this, SLOT(handleMoveView)); // Callback if unused
QGraphicsWebView::mouseMoveEvent(myEvent); // Pass it to Base class
}
If that works, it might introduce a bit of delay if deleteLater is used to destroy it. But in that case reimplement it as well.
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.
We're using Infragistics grid (most probably, we'll have 8.2 version at the end) and we want to configure row/cells appearances "on-demand" in order to be able to provide sort of "dynamic appearance".
For example, I want some cell to be red or green, depending on its value. We might want to tweak other characteristics as well (font, size, image, etc).
A perfect place to do it would be some event, that happen before a cell gets repainted... But it seems there is no such event in Infragistics...
Or am I wrong? Any help?
Clarification: I'm talking about WinForms Infragistics UltraGrid
I had to do exactly this with the IG WebGrid a few years back, and it was ... shall we say ... painful. However, the WebGrid had the advantage of a single render point -- once the HTML was emitted, we were set!
For dealing with this in WinGrid, I tried a variety of different events, both on the grid and the datasource, and met with abject failure every step of the way. The only event I got to work was Paint, which will likely create a performance issue.
For Paint, here's what I hacked together. I'm not proud of this code, and I likely wouldn't put it in production, but here it is anyway (C#):
private void UltraGrid1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
foreach (UltraGridRow r in UltraGrid1.Rows)
{
foreach (UltraGridCell c in r.Cells)
{
if (c.Text == "foo")
c.Appearance.BackColor = Color.Green;
}
}
}
and VB:
Private Sub UltraGrid1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles UltraGrid1.Paint
For Each r As UltraGridRow In UltraGrid1.Rows
For Each c As UltraGridCell In r.Cells
If c.Text = "foo" Then
c.Appearance.BackColor = Color.Green
End If
Next
Next
End Sub
There is an event. I don't remember exactly what it's called, but it's got to be something like 'DataRowBound' or 'ItemDataBinding', etc..
Also, this article might help.
Not that this has anything to do with your question, but I'd stay away from heavy use of Infragistics controls - they're very heavy and will slow down the page rendering process considerably. Just my $0.02.
We have finally come up with two solutions for that problem.
For some of the dynamic content we use grid elements appearance and reinitialize it "on-demand".
For the extremely resource-critical appearance we use UltraGrid.DrawFilter (see also IUIElementDrawFilter interface).