I have a UI element, which appears while I do some calculation and disappears when the calculation finished. The appearing and disappearing is animated. The animation animates the item's implicitHeight from 0 to X and vice-versa. These items live in a ListView as delegates. The ListView is wrapped in an Item as the root element of this component. The implicitHeight of this root element depends on, aka is binded to the contentHeight of the ListView. The width of this root element is set where the component is used. Here you can see my explanation in code:
Item {
id: _root
implicitHeight: _listView.contentHeight
ListView {
id: _listView
width: _root.width
height: contentHeight
delegates: AnimatingItem {
// ...
}
}
}
The AnimatingItems in the code have predefined height (not necessarily the same for all). I wrote a small debug code piece which basically writes the ListView's contentHeight, childrenRect.height, height and the root item's implicitHeight to the console on a button press.
In most cases when ever I press this button to print out those values, like during a calculation when the item is present (= X) or when there is no calculation therefore the item is hidden (= 0), they all match.
In certain scenarios when I do some really heavy calculation though, when even the UI freezes/drops frames and the calculation finishes and I press the debug button all the ListView's values match (= 0), but the items's implicitHeight differs (= X). The weird thing is that the AnimatingItem's or in other words the ListView disappears thus there is no visible item, but if I anchor a Rectangle to the top of this item then it will float in the air instead of moving down as the ListView disappears.
Is it possible that if I have a really heavy calculation one or more bindings can "forget" to update due to dropping frames?
On some platforms animations don't run in a dedicated thread, but I don't think that's the case of Mac OS. So dropping frames due to lack of graphics performance should not cause skips in binding evaluations. Now if you have a CPU hotspot as a cause of the dropped frame, that's a different story. The animation is synced to the event loop rate as well as the scenegraph rate, so if your event loop is stalling, then it is simply not making the value change, which is why you don't get reevaluations.
As a rule of thumb, you shold never ever do heavy calculations in the main/gui thread. If it causes the GUI to freeze for more than 10 msec you need to offload it to a dedicated thread, and update the results asynchronously. Don't stall or block the gui thread!
Also, binding to contentHeight hasn't work as expected for me in many of the cases. What has proven to work is binding to contentItem.childrenRect.height. Also, the root item is completely redundant if it is going to be just an empty item, but even if it will have direct children, you can do that with the list view which is also an Item.
If you scale the view to the full height, I'd recommend to use a Column with a Repeater instead. A simple repeater will take advantage of all the model features and efficiencies too, so no worries there.
Related
I have a set of controls which have bindings to frequently changing data values. The data comes from a limited hardware-bus. Therefore, it would be better to disable the binding while the control is not visible on screen. The item's visible-property doesn't help in this case. So, how to determine if an Item-based QML widget is currently visible on screen (and not hidden by an overlay or currently outside the visible area)?
Source: https://forum.qt.io/topic/54116/how-to-check-if-a-item-is-currently-visible-on-screen
I have almost the same problem. Hoping someone here has a solution.
Here's what I would try to get working:
First, I presume there is a ScrollView or Flickable in play here? If so, then hook to signals like Flickable::movementEnded().
Second, when that signal fires, use Item::mapToItem() to check if each of your Item's visible rectangle (based on x, y, width, height) intersects your window's contentItem rectangle. Set the result as a boolean on each of your items and make sure the data retrieval is disabled when it is false (using && or a tertiary JS expression).
Or if more convenient, remove the binding when false and reapply it with Qt.binding() when true.
I have a list of lets say 20 buttons (this number can change dynamically) in a listView which I want to display using a grid on a swipeView or gridView. Only 6 buttons should be visible at once. In addition, it should be possible to exchange the position of buttons via drag and drop (see http://doc.qt.io/qt-5/qtquick-draganddrop-example.html). In case gridView is the way to go, is it possible to have it behave like swipeView, so that the buttons can not be moved to an arbitrary position but lock onto their column?
You might want to give snapMode a try.
You have the choice of:
GridView.NoSnap (default) - the view stops anywhere within the visible area.
GridView.SnapToRow - the view settles with a row (or column for GridView.FlowTopToBottom flow) aligned with the start of the view.
GridView.SnapOneRow - the view will settle no more than one row (or column for GridView.FlowTopToBottom flow) away from the first visible row at the time the mouse button is released. This mode is particularly useful for moving one page at a time.
You need to make sure, that the size of the GridView really fits your desired amount of delegates, for it will only make sure, that the first element is aligned, not that there is no element paritally protruding the view.
If, on my MouseArea, I've enabled the property hoverEnabled, I'm notified of the entered signal, even if no mouse button is held at that time (just as I want).
But then a lower-z MouseArea will not receive positionChanged events on the first MouseArea's space, since they're eaten up by the first MouseArea.
The Qt docs on positionChanged specifically say this:
When handling this signal, changing the accepted property of the mouse parameter has no effect.
Example code:
Item {
width: 100
height: 100
MouseArea {
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
console.log("MouseArea 1");
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
console.log("MouseArea 2");
}
}
}
"MouseArea 1" is never printed. I want both strings to be printed.
Is there a way to do it?
Related question note: This question is very similar but the asker there has 2 differently-sized overlapping MouseAreas, and he wants the smaller one (which is has higher z) to eat up events when the events fall within both. I want both overlapping MouseAreas to handle events that fall within both.
At a first glance, it doesn't look like this is possible, most likely because an implementation detail limitation. Normally, it is effortless to propagate events, what you need is to propagateComposedEvents: true and to mouse.accepted = false in the handler.
However, while that works as expected for clicks, it doesn't work for onEntered. My first thought was that it was the lack of the mouse signal parameter for the entered signal, however positionChanged has it but it still doesn't work as expected, as the documentation says, it has no effect. And that's actually true for positionChanged unlike for clicked, for which the documentation also says should have no effect but it most certainly does.
Furthermore, even if you make the top mouse area smaller, you will be able to register the cursor entering for both, but position changes will only register for the top-most mouse area that is under the cursor.
I wouldn't say that such functionality is an unreasonable feature, so you might want to file a bug report with a request to fix it so that you can get "common sense" behavior. However, such a fix will likely not come any time soon, if ever.
Depending on your actual usage scenario there might be some workarounds possible.
Worst case scenario, you can always have one single mouse area for the entire application window and manually propagate events to underlying objects, it is really not as tedious as it may sound, various similar limitations in the way things work in QML has driven me to do exactly that for both mouse and keyboard events.
I've noticed that the default behaviour for a DataGrid's vertical scroll bar is to scroll one row at a time. This is all well and good when the rows are all uniform and small (e.g. displaying a single line of text), but gets really ugly as soon as you have rows with variable heights.
I'm curious, is there a way to make DataGrid scrolling "smooth"? For instance, is there a way to have the DataGrid scroll by a set number of pixels, lines of text, etc. rather than scrolling one row at a time?
So far, the only solution I've managed to come up with is to place the DataGrid in a Canvas and have the Canvas do the scrolling instead of the DataGrid. The issue with this approach, though, is that as soon as the Canvas scrolls far enough, the DataGrid headers scroll off-screen. Ideally, I'd like to get the smooth-scrolling nature of the Canvas, but also keep the DataGrid headers visible. Is that possible?
The way that ItemRenderer's work in Flex 3 makes smooth scrolling difficult to achieve. Basically Flex recycles item renderers scrolled off of the top of the list as the display objects used for new data at the bottom of the list. Adobe's implementation of most list components in Flex 3 creates and adds these items as they come on to the screen rather than just off the screen, so they "pop in" and smooth scrolling isn't available. I'm not sure why they couldn't have done it in a similar manner for items +/- one position above or below the current scroll pane, but they didn't, and we're stuck with sticky scrolling by default.
Work-arounds do exist, though the one you've noted (dropping the datagrid into a canvas) negates the display-object saving intention of item renderers and incurs a performance cost. This will be fixed for most list-based Flex components in Flex 4, though it won't be fixed immediately for DataGrid. The DataGrid / AdvancedDataGrid component is maintained by a separate team based in India, last time I heard, and so it tends to be a bit behind the rest of the SDK.
I'd recommend trying something similar to this implementation of a smooth-scrolling list by Alex Harui. I'm not sure exactly how well it'd work for DataGrid or AdvancedDataGrid, but this is the most intuitive technique I can think of for making the list scroll correctly.
Try this... It's still based on Alex's code that was mentioned above. His should still be a great start for removing the snap-to-row behavior. Original source:
http://blogs.adobe.com/aharui/2008/03/smooth_scrolling_list.html
Alex's original some code for smooth vertical scrolling but that was not an issue I had with the DataGrid. It was smooth scrolling horizontally that I needed. I am using the DataGrid in an unorthodox manner for analyzing plain text reports output by our database (great way of providing visual feedback on a document). The code below allows content to go off screen and the user can scroll without that snap-to-column behavior.
You can adapt this to use the same math routines for vertical scrolling and then it will make scrolling possible and ignore the snap to row behavior. In particular switch the usage of the listContent.move method to move the contents vertically and use a inverse of the rounded pixel value you calculate from the vertical scroll bar (as opposed to my using the horizontal).
This method is bit simpler than Alex's method from the link above - a lot less code so try adapting and see how it works.
override protected function scrollHandler(event:Event):void
{
// Override the default scroll behavior to provide smooth horizontal scrolling and not the usual "snap-to-column" behavior
var scrEvt:ScrollEvent = event as ScrollEvent;
if(scrEvt.direction == ScrollEventDirection.HORIZONTAL) {
// Get individual components of a scroll bar for measuring and get a horizontal position to use
var scrDownArrow:DisplayObject = horizontalScrollBar.getChildAt(3);
var sctThumb:DisplayObject = horizontalScrollBar.getChildAt(2);
// I replaced maxHorizontalScrollPosition in Alex's code with "1300" to fix my exact application. In other situations you may finding using some property or different value is more appropriate. Don't rely on my choice.
var hPos:Number = Math.round((sctThumb.y - scrDownArrow.height) / (scrDownArrow.y - sctThumb.height - scrDownArrow.height) * 1300);
// Inverse the position to scroll the content to the left for large reports
listContent.move(hPos * -1, listContent.y);
}
// Go ahead and use the default handler for vertical scrolling
else {
super.scrollHandler(event);
}
}
When a Flex component moves directly, so that its x and y properties change, then a "move" event is dispatched on the component. That's all fine.
However, a component may also move as a result of its parent component moving — or its parent moving, and so on. Now when a parent component moves, its children just "move along" without any properties changing or move events being dispatched on the children. Still, the children are moving on the screen. So how can you detect this kind of generalized movement?
One workaround is to capture all move events in the application:
Application.application.addEventListener
(MoveEvent.MOVE, handleMove, true, 0, true);
The third argument is required because move events do not bubble, and so instead have to be captured. The fourth argument is unimportant, and the fifth argument turns on weak references, which is a good idea in this case because we are creating a global reference from Application.application to handleMove — a recipe for memory leaks.
Of course, this will fire too often (once each time anything whatsoever in the application moves), and in a large application could lead to performance problems. If you know that there is some component higher up in the hierarchy that’s sure to stay still, you can put the listener at that point instead of globally, which could reduce the problem.
Still, it would be nice to have a cleaner way to solve this.
Well, you've already suggested the most general solution, but I think it's possible for the child to go through parents/grandparents until it reaches one that is stationary (set by some dynamic property you set), at least this would save you some trouble in figuring out which parent handles which child.
private function addHandlerToStationaryParent(handler:function):void
{
var currentParent:DisplayObjectContainer = parent;
while(currentParent != null)
{
if(currentParent["stationary"] == true)
{
currentParent.addEventListener(MoveEvent.MOVE, handler);
return;
}
}
}
I guess it would be your preference as to whether or not this would be a better solution.