(JavaFX) SplitPane.getDividerPositions() Inconsistent - javafx

I have a SplitPane in my main GUI that is behaving strangely. I am trying to save the position between executions of the program and do so by writing/reading a simple text file.
However, I am noticing that the getPosition() method returns an incorrect value when saving it to the file.
When exiting the program, I am saving the position with this code:
Settings.setSplitPaneLocation(Global.commentMasterController.getSplitPane().getDividers().get(0).getPosition());
Global is a Singleton that holds references to my main GUI controllers.
Settings is another Singleton that contains the values loaded from the settings file.
Upon loading the program, I set the position of the SplitPane by loading the last saved value from the Settings class:
splitPane.setDividerPosition(0, Settings.getSplitPaneLocation());
The problem is, when I load the program, even if I don't adjust the SplitPane position at all (load the program and immediately exit), the position that is saved is different from the position it was initially set to.
Here is the output, showing 3 consecutive runs of the program. Again, this is just running the program and immediately exiting; I don't adjust the position of any controls at all:
setDividerPosition(): 0.9247730220492867
getDividerPosition(): 0.9105058365758755
setDividerPosition(): 0.9105058365758755
getDividerPosition(): 0.893644617380026
setDividerPosition(): 0.893644617380026
getDividerPosition(): 0.874189364461738
As you can see, getDividerPosition() returns a different value than the actual position of the divider.
I have checked the max/min sizes of the child nodes within the SplitPane and the min size is set to 0.

From the Javadocs for SplitPane:
A divider's position ranges from 0 to 1.0(inclusive). A position of 0
will place the divider at the left/top most edge of the SplitPane plus
the minimum size of the node. A position of 1.0 will place the divider
at the right/bottom most edge of the SplitPane minus the minimum
size of the node. A divider position of 0.5 will place the the divider
in the middle of the SplitPane. Setting the divider position greater
than the node's maximum size position will result in the divider being
set at the node's maximum size position. Setting the divider position
less than the node's minimum size position will result in the divider
being set at the node's minimum size position. Therefore the value
set in setDividerPosition(int, double) and
setDividerPositions(double...) may not be the same as the value
returned by getDividerPositions().
(My emphasis.) Without knowing more about what you are doing, it is hard to be precise about the behavior you are observing, but I suspect that the value you are setting is being adjusted to account for the min/max sizes of the content nodes.

I've had a similar problem and found this post without a real solution, so maybe it helps someone in the future:
Try to use Platform.runLater(). Apparently there's something clashing when you start an application and want to change GUI-elements at the same time.
For your specific question it would be:
Platform.runLater(() -> splitPane.setDividerPosition(0, Settings.getSplitPaneLocation()));
I've found this hint in the following blogpost: http://broadlyapplicable.blogspot.de/2015/03/javafx-capture-restore-splitpane.html
How to use the javafx.application.Platform.runLater method to set the divider position when your application launches. (This technique is necessary due to how the application starts up and initializes)

Related

Artifacts showing when modifying a custom QGraphicsItem

I'm currently developping a small vector drawing program in wich you can create lines and modify them after creation (those lines are based on a custom QGraphicsItem). For instance, the picture below shows what happens when the leftmost (marked yellow) point of the line is dragged to the right of the screen, effectively lengthening the line :
Everything works fine when the point is moved slowly, however, when moved rapidly, some visual artifacts appear :
The piece of code I'm using to call for a repaint is located in the mouseMoveEvent redefined method, which holds the following lines of code :
QRectF br = boundingRect();
x2 = static_cast<int>(event->scenePos().x()-x());
y2 = static_cast<int>(event->scenePos().y()-y());
update(br);
There's apparently no problem with my boundingRect definition, since adding painter->drawRect(boundingRect()) in the paint method shows this :
And there are also no problem when the line is simply moved (flag QGraphicsItem::ItemIsMovable is set), even rapidly.
Does anyone know what is happening here ? My guess is that update is not being called immediately hence mouseMoveEvent can be called multiple times before a repaint occurs, maybe canceling previous calls ? I'm not sure.
Of course the easy fix is to set the viewport mode of the QGraphicsView object holding the line to QGraphicsView::FullViewportUpdate), but that is ugly (and slow).
Without seeing the full function for how you're updating the line, I would guess that you've omitted to call prepareGeometryChange() before updating the bounding rect of the items.
As the docs state: -
Prepares the item for a geometry change. Call this function before changing the bounding rect of an item to keep QGraphicsScene's index up to date.

How do I keep two parallel Qsplitters equal sizes?

I have a Qt widget with four partitions, separated with splitters.
The top level is a vertical splitter, changing the heights of two horizontal splitters, topSplitter and bottomSplitter.
How can I keep both horizontal splitters positions equal, as if it was just one horizontal splitter?
I looked at linking signal for splitterMoved, and connecting it to a slot on the other splitter but there are no equivalent slots in the splitter class.
This would obviously have to avoid the issue of an infinite loop where one splitter's position updates the second, which updates the first.
It's pretty simple. Initialization (splitter1 and splitter2 are the splitters that need to be syncronized):
connect(ui->splitter1, SIGNAL(splitterMoved(int,int)), this, SLOT(splitterMoved()));
connect(ui->splitter2, SIGNAL(splitterMoved(int,int)), this, SLOT(splitterMoved()));
The slot:
void MainWindow::splitterMoved() {
QSplitter* senderSplitter = static_cast<QSplitter*>(sender());
QSplitter* receiverSplitter = senderSplitter == ui->splitter1 ?
ui->splitter2 : ui->splitter1;
receiverSplitter->blockSignals(true);
receiverSplitter->setSizes(senderSplitter->sizes());
receiverSplitter->blockSignals(false);
}
blockSignals ensures that calls will not go to infinite recursion. Actually, setSizes doesn't cause emitting splitterMoved, so you can remove both blockSigals calls and the code will still work. However, there is no note about this in the documentation, so I wouldn't rely on that.
In the containing widget you can create slots to connect to the splitterMoved signal.
There needs to be one slot for each splitter, where it needs to check if the splitter is already the correct size (to avoid the infinite loop) then update the size if necessary.
I am only including one of the example slots and connections, but one will be needed for each splitter that needs to be linked.
Putting the following content into the new slots for updating the splitter positions.
QList<int> OtherSize,Current;
OtherSize=topSplitter->sizes();
Current=bottomSplitter->sizes();
if(OtherSize!=Current)
{
bottomSplitter->setSizes(OtherSize);
}
This will create two lists ready to hold the sizes for the splitters.
It gets the current sizes of both splitters, and compares them.
The comparison is necessary to avoid the infinite loop.
Then, if they are different, it sets the sizes to match that of the other splitter.
Connecting that slot to the appropriate splitterMoved signal should work.
This connection is used in the constructor of the containing widget (where you created the new slots).
connect(topSplitter,SIGNAL(splitterMoved(int,int)),this,SLOT(updateBottomSplitter()));
It is safe to ignore the position and index supplied by the signal, because we check the sizes in a different way in this slot.
This will set all sizes, so all splitter bars will match position.
If there are only two, it doesn't matter but if there are more than two, when any bar is moved, all will be updated to match.

Qt - when has the UI finished initialization? So scroll bar positions etc can be set?

I have a simple use case in a Qt app:
When the UI has been constructed and a QGraphicsView (or any other QWidget) has been populated, scroll the graphics view to a given location, based on the graphics views width and height (or any other attribute set by the layout engine).
However I can't find a reliable or known place to do this. In the constructors the layout has not yet been applied, so the width isn't the final width that the user sees once the UI is "up". Most suggestions seem to consist of hacks such as:
QTimer::singleShot(0, this, SLOT(initWidget()));
In my case this doesn't seem to work unless I set the delay to around 10msec which seems even more risky and hacky. Surely there must be something like win32's WM_INITDIALOG or the likes?
You can override showEvent() or resizeEvent() of the relevant class.
After a bit of experimenting, since you say you're using showMaximized(), you actually may want to override both, and then get the size from first resizeEvent() after first showEvent() (or 2nd resizeEvent() overall). Output from my test app which used showMaximized():
Starting /home/hyde/test/build-eventTest-Desktop-Debug/eventTest...
resizeEvent size QSize(200, 100) oldSize QSize(-1, -1)
showEvent geometry QRect(0,0 200x100)
resizeEvent size QSize(1440, 851) oldSize QSize(640, 480)
/home/hyde/test/build-eventTest-Desktop-Debug/eventTest exited with code 0
Then do what you need to do there. If you want to do it just once, then add a boolean member variable like
bool mFullyInited;
and initialize it to false, and then in the overriden event handler method, test it and set it to true when you've done the initialization.

Get QWidget absolute position in the MainWindow constructor

I want to get the absolute x and y coordinates of a frame control. I know that there is a QWidget::mapToGlobal function that should give me these coordinates - but it always returns the relative points.
My code is:
int y = ui->frame->mapToGlobal(ui->frame->window()->pos()).y();
Can anyone please help me to understand what I am doing wrong?
EDIT:
Found the problem - I've called this line in the main window class, before it is displayed. So now my Q is how can I get the position that the main window will be displayd in its constructor?
I think you can't, not reliably and portably anyway. The position is not decided yet, and decision is often made by window manager, not the application.
So, what you should do is override moveEvent(), resizeEvent() and/or showEvent() Those are the right place to do stuff like that, instead of constructor.

Flash/Flex: "Warning: filter will not render" problem

In my flex application, I have a custom TitleWindow that pops up in modal fashion. When I resize the browser window, I get this warning:
Warning: Filter will not render. The DisplayObject’s filtered dimensions (1286, 107374879) are too large to be drawn.
Clearly, I have nothing set with a height of 107374879.
After that, any time I mouse over anything in the Flash Player (v. 10), the CPU churns at 100%. When I close the TitleWindow, the problem subsides. Sadly, the warning doesn't seem to indicate which DisplayObject object is too large to draw. I've tried attaching explicit height/widths to the TitleWindow and the components within, but still no luck.
[Edit]
The plot thickens:
I found that the problem only occures when I set the PopUpManager's createPopUp modal parameter to "true." I don't see the behavior when modal is set to "false." It's failing while applying the graying filter to other components that comes from being modal. Any ideas how I can track down the one object that has not been initialized but is being filter during the modal phase?
Thanks for reading.
This might not be the case in your application, but I have come across similar cases where a height or width of an object has been set to some unreasonable big number as the result of misuse of an unsigned integer in calculations for positioning, height or width.
Schematic code for such a scenario could be like this:
var offset:uint = 30;
var position:uint = txt.textHeight - offset;
divider.y = position;
The code wrongfully assumes that txt.textHeight will always be bigger than 30. If it is not, txt.textHeight - offset will be a negative number, that when stored in an uint will instead become a very large number.
Let's say for example that the text in txt, presumed to be a long multiline text, instead is a single line that is 20 pixels heigh. The result will then be -10, and when stored in the uint var position, the value of position will be 4294967286.
The above is crappy code, an example, but in a real world situation the uint problem can occur in some more complex way, that might be harder to spot right away. I guess it is seldom a good idea to use an unsigned integer for stuff like x and y positions, that can have negative values.
You could write some code to recursively step down the hierarchy of DisplayObjectContainer and DisplayObject objects and check for the large height.
Should be pretty simple to write. A function something like this should do the trick:
function RecurseDisplayObjects(DisplayObject obj):void
{
//check for height and do a trace() or whatever here
if(obj is DisplayObjectContainer)
{
var container:DisplayObjectContainer = obj as DisplayObjectContainer;
for(var i:int=0; i<container.numChildren; i++)
{
RecurseDisplayObjects(container.getChildAt(i);
}
}
}
You would need to start this off by passing it the top level DisplayObject in your application. (possibly obtained with DisplayObject.root)
The other option you have is to get the Flex framework source and modify it to give you a more meaningful error.
The problem is probably not in your TitleWindow, but in objects below it. The filter failing to render is probably the blur filter flash applies over everything below the modal dialog. If one of the objects on the stage is too big to apply blur on it in real time, you get the error you mentioned.
I solved that problem by applying a mask to the object below the titlewindow, set to the size of the stage. That will probably solve your problem but you should definitely look into why something gets to that size, doesn't sound healthy. :-)
Sadly I have no idea, but we're trying to track down a similar issue in ours. Maybe this will help?
http://www.mail-archive.com/flashcoders#chattyfig.figleaf.com/msg48091.html
I had a similar issue, tracked it down to an alpha filter applied to an object scaled to -0.23453422334. Once I rounded the scale to 2 significant digits everything worked fine. A difficult error to track down however.

Resources