Making a smaller JavaFX ComboBox - css

I'm trying to make a smaller version of the ComboBox but the gap between the text and the arrow button is constant no matter what I do.
If I use the css:
.combo-box-base > *.arrow-button {
-fx-padding: 0 0 0 0;
-fx-background-color: pink, pink, pink, pink;
}
the arrow button gets smaller but the ComboBox itself still have the same size, only increasing the gap between the arrow and text to compensate.
If I do
.combo-box > .list-cell {
-fx-padding: 0 0 0 0;
-fx-border-insets: 0 0 0 0;
}
The combo get a smaller height but the width remain fixed.
Is there any way to make the preferred size of the combo smaller by reducing the size between the text and arrow?

I know this question is quite old but i came across the same problem and wanted to solve it. So i started debugging the JavaFX code step by step to get an idea where the additional space comes from.
It took me longer than it should because i overlooked a small piece of code with a huge impact.
The class ComboBoxListViewSkin is by default responsible for the computation of the ComboBox width. Its compute method delegates the computation to the super class and the prefWidth method of the field listView and chooses the greater result.
/** {#inheritDoc} */
#Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
double superPrefWidth = super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset);
double listViewWidth = listView.prefWidth(height);
double pw = Math.max(superPrefWidth, listViewWidth);
reconfigurePopup();
return pw;
}
The listView field is actually an instance of an Anonymous Classes extending ListView which does some ComboBox specific things and overrides some methods. One of these methods is protected double computePrefWidth(double height). It contains the line which causes the problem with extra whitespace we cannot get rid of.
pw = Math.max(comboBox.getWidth(), skin.getMaxCellWidth(rowsToMeasure) + 30);
Adding 30 to the widest cell's width is exactly what we don't want. Because it's hard coded we cannot easily change it. I overlooked it at first glance causing some WTF moments when debugging the code.
Of course you can extend ComboBoxListViewSkin and override its computePrefWidth method but i think the default ComboBoxListViewSkin class should provide the possibility to change this extra whitespace easily. So I filed an issue at GitHub.

Have you tried setting the padding of .combox-box > .text-input ?
According to Oracles ComboBox CSS documentation, this could be another source of padding.
If you plan on developing more CSS-rules, maybe have a look at the CSS inspector of the SceneBuilder2 or use ScenicView. ScenicView also allows live-modification of the CSS, which significantly improves debugging speed.

Did you check the Hgrow property ? you should set it in the layout manager for the combo box
ComboBox field = new ComboBox();
HBox.setHgrow(field, Priority.NEVER);
Also check the preferred and maximum width you should set them to USE_COMPUTED_SIZE (in SceneBuilder, not sure how to do it by code).

Related

How to replace down arrow with text in a combobox in JavaFX

I'm trying to remove the down arrow from a combobox. All the solutions I have found just make the arrow disappear, for example this one.
Is there a way to remove completely the space where the arrow appears and fill the box just with the text of the selected choice?
If you want to completely elimnate the arrow & arrow button space, you can try with the below custom ComboBox.
The below code is setting the arrow button and arrow nodes size to 0 and asking to rerender the comboBox. The null check is to let this changes apply only once.
public class MyComboBox<T> extends ComboBox<T>{
Region arrowBtn ;
#Override
protected void layoutChildren() {
super.layoutChildren();
if(arrowBtn==null){
arrowBtn= (Region)lookup(".arrow-button");
arrowBtn.setMaxSize(0,0);
arrowBtn.setMinSize(0,0);
arrowBtn.setPadding(new Insets(0));
Region arrow= (Region)lookup(".arrow");
arrow.setMaxSize(0,0);
arrow.setMinSize(0,0);
arrow.setPadding(new Insets(0));
// Call again the super method to relayout with the new bounds.
super.layoutChildren();
}
}
}
UPDATE :
Based on the suggestion of #kleopatra, we can get the same behaviour using css as well (without the need to create a new class for ComboBox).
.combo-box .arrow-button,
.combo-box .arrow{
-fx-max-width:0px;
-fx-min-width:0px;
-fx-padding:0px;
}
The below image will tell you the difference of a normal combox box and this custom combo box. The left one is the normal comboBox, you can see the list cell when inspecting with ScenicView. The right one is the custom one. The list cell is completely occupied suppressing the arrow space.

How to make TextInput cursorDelegate wider + taller

Is there a way to style the cursorDelegate on a TextArea/TextInput so that it ends up taller and thicker than the default one?
With the following code, I can currently get one that has the following properties:
Thicker than normal, at 4px wide
Taller than the rest of the text, with bits sticking out above/below the line as wanted. BUT, this only works until the user moves the cursor. Then, the vertical size gets clipped again, and can't be reset.
TextArea {
id: editor
cursorDelegate: Rectangle {
width: 4
property int vpad: 4
y: editor.cursorRectangle.y - (vpad / 2)
height: editor.cursorRectangle.height + vpad
}
}
It looks like the y and height bindings are getting overwritten by whatever sets those automatically internally.
Trying to overwrite these again myself using a onCursorPositionChanged handler on the TextArea fails, as you cannot write to cursorDelegate.
Managed to find a solution. Instead of setting these values in the normal way, you need to set these on the change handlers for those properties.
TextArea {
id: editor
cursorDelegate: Rectangle {
width: 4
property int vpad: 4
onYChanged: editor.cursorRectangle.y - (vpad / 2)
onHeightChanged: editor.cursorRectangle.height + vpad
}
}
This seems to resist the widgets auto behaviour that was overwriting our values.

Gluon ScrollPane refresh issue

I have a messaging application running in Android which has the setup like in setup of the screen
the order is as below
<View>
<BorderPane>
<center>
<ScrollPane>
<content>
<VBox> //issue is here
</content>
<ScrollPane>
<center>
<bottom>
<TextField>
<bottom>
</BorderPane>
</View>
When I add children to VBox with
VBox.getChildren().add(TextLabel);
The ScrollPane gets new VBox and shows that on the screen.
However when i add more children that what current screen can fit i scroll to end of the ScrollPane by setting vvalueProperty();
ScrollPane.vvalueProperty().bind(VBox.heightProperty());
(Above code is essential to recreate the issue)
This works perfectly fine when running it on computer but on mobile i have this weird issue where scrollPane drops VBox when i add more children than what can be fit on the screen. And when i click on the VBox area the screen refreshes and i get the desired content on the screen
Video demonstrating ScrollBar issue in gluon
For convenience i have set following color code
ScrollBar - Red
VBox - Blue
As an alternative to binding I also tried
ScrollBar.setVvalue(1.0);
setVvalue() did not have same issue but this on the other hand was not showing the last message in the view.
Right now i have tried all possible combinations including replacing VBox with FlowPane and observed same behavior.
I can reproduce your issue on an Android device. Somehow, as discussed in the comments above, the binding of the vertical scroll position is causing some race condition.
Instead of trying to find out the cause of that issue, I'd rather remove the binding and propose a different approach to get the desired result: the binding is a very strong condition in this case.
When you try to do in the same pass this:
vBox.getChildren().add(label);
scrollPane.setVvalue(vBox.getHeight());
you already noticed and mentioned that the scroll position wasn't properly updated and you were missing the last item added to the vBox.
This can be explained easily: when you add a new item to the box, there is a call to layoutChildren() that will take some time to be performed. At least it will take another pulse to get the correct value.
But since you try to set immediately the vertical scroll position, the value vBox.getHeight() will still return the old value. In other words, you have to wait a little bit to get the new value.
There are several ways to do it. The most straightforward is with a listener to the box's height property:
vBox.heightProperty().addListener((obs, ov, nv) ->
scrollPane.setVvalue(nv.doubleValue()));
As an alternative, after adding the item to the box, you could use:
vBox.getChildren().add(label);
Platform.runLater(() -> scrollPane.setVvalue(vBox.getHeight()));
But this doesn't guarantee that the call won't be done immediately. So it is better to do a PauseTransition instead, where you can control the timing:
vBox.getChildren().add(label);
PauseTransition pause = new PauseTransition(Duration.millis(30));
pause.setOnFinished(f -> scrollPane.setVvalue(vBox.getHeight()));
pause.play();
As a suggestion, you could also do a nice transition to slide in the new item.
Alternative solution
So far, you are using an ScrollPane combined with a VBox to add a number of items, allowing scrolling to the first item on the list but keeping the scroll position always at the bottom so the last item added is fully visible. While this works fine (with my proposal above to avoid the binding), you are adding many nodes to a non virtualized container.
I think there is a better alternative, with a ListView (or better a CharmListView that will allow headers). With the proper CellFactory you can have exactly the same visuals, and you can directly scroll to the last item. But the main advantage of this control is its virtualFlow, that will manage for you a limited number of nodes while you have many more items added to a list.
This is just a short code snippet to use a ListView control for your chat application:
ListView<String> listView = new ListView<>();
listView.setCellFactory(p -> new ListCell<String>() {
private final Label label;
{
label = new Label(null, MaterialDesignIcon.CHAT_BUBBLE.graphic());
label.setMaxWidth(Double.MAX_VALUE);
label.setPrefWidth(this.getWidth() - 60);
label.setPrefHeight(30);
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null && ! empty) {
label.setText(item);
label.setAlignment(getIndex() % 2 == 0 ? Pos.CENTER_LEFT : Pos.CENTER_RIGHT);
setGraphic(label);
} else {
setGraphic(null);
}
}
});
setCenter(listView);
and to add a new item and scroll to it, you just need:
listView.getItems().add("Text " + (listView.getItems().size() + 1));
listView.scrollTo(listView.getItems().size() - 1);
Of course, with the proper styling you can remove the lines between rows, and create the same visuals as with the scrollPane.

Qt4 expanding tabs in QTabBar

I am subclassing QTabWidget to add a QTabBar, who's tabs stretch over the whole width of the tabBar. Therefore I am setting the expanding property to true. This doesn't seem to change anything about the behavior of the tabs.
Did anyone encounter the same problem? I use Qt 4.6 in combination with
TabWidget::TabWidget(QWidget *parent)
{
tabBar = new QTabBar(this);
tabBar->setIconSize(QSize(160,160));
tabBar->setExpanding(true);
setTabBar(tabBar);
}
EDIT: has been solved, here is how I implemented it, in case anyone is interested:
tabBar = new QTabBar(this);
tabBar->setExpanding(true);
layout = new QVBoxLayout(this);
setLayout(layout);
stackedLayout = new QStackedLayout();
layout->addWidget(tabBar);
layout->addLayout(stackedLayout);
connect(tabBar, SIGNAL(currentChanged(int)), stackedLayout, SLOT(setCurrentIndex(int)));
void MainWindow::addTab(QWidget *widget, const QIcon &icon, const QString &label) {
tabBar->addTab(icon, label);
stackedLayout->addWidget(widget);
}
From the QTabBar source code:
// ... Since we don't set
// a maximum size, tabs will EXPAND to fill up the empty space.
// Since tab widget is rather *ahem* strict about keeping the geometry of the
// tab bar to its absolute minimum, this won't bleed through, but will show up
// if you use tab bar on its own (a.k.a. not a bug, but a feature).
To get around this "feature", you can create your own tab widget using a QTabBar above a widget with a QStackedLayout.
5.2.0 onwards
QTabWidget::tab-bar {
min-width: 999999px;
}
It will work. No need to use any combination. You can use QTabWidget. Daniel ans is correct.
Building on #baysmith's answer, an easier way to force the QTabWidget to let the QTabBar expand, is to set a stylesheet on your QTabWidget that looks something like this:
QTabWidget::tab-bar {
width: 999999px;
}
Or another ridiculously large number. If your QTabWidget has the tabs going vertically instead of horizontally, use 'height' instead:
QTabWidget::tab-bar {
height: 999999px;
}
This seems to work fine for me, with Qt 5.0.1. The tabs expand to fill the space, each getting an equal portion. However, it seems like they intentionally leave enough empty space for one more tab, regardless. But the rest of the space is filled as desired. The empty space might be reserved for the tear/scroll buttons, incase too many tabs get added - but I'm not sure.

Flex/AS3: changing width/height doesn't affect contents

I thought I had a handle on AS3, DisplayObjectContainers, etc. but this basic thing is really confusing me: changing the width/height of a sprite does not affect it's visual contents - either graphics drawn within it or any children it may have.
I have searched around and found an Adobe page that represents my own little test code. From that page, I would expect the sprite to increase in visual size as it's width increases. For me, it doesn't. (http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/flash/display/DisplayObject.html#width)
width property
width:Number [read-write]
Indicates the width of the display object, in pixels. The width is calculated based on the bounds of the content of the display object. When you set the width property, the scaleX property is adjusted accordingly, as shown in the following code:
My code below doesn't affect the visual display at all - but it does set the width/height, at least according to the trace output. It does not affect the scaleX/scaleY.
What the heck am I missing here??
My setup code:
testSprite = new SpriteVisualElement();
var childSprite:SpriteVisualElement = new SpriteVisualElement();
childSprite.graphics.beginFill(0xFFFF00, 1);
childSprite.graphics.drawRect(0, 0, 200, 100);
childSprite.graphics.endFill();
childSprite.name = "child";
testSprite.addChild(childSprite);
container.addElement(testSprite);
testSprite.addEventListener(MouseEvent.CLICK, grow);
}
public function grow(event:MouseEvent):void
{
event.target.width += 5;
event.target.height += 5;
trace("grow", event.target.width);
}
If I understand the code correctly; you are changing the width / height of the sprite. But you are doing nothing to change the width/ height of the sprite's children.
In the context of a Flex Application, you can use percentageWidth and percentageHeight on the child to resize the child when the parent is resized. You could also add a listener to the the resize event and adjust sizing that way; preferably tying in to the Flex Component LifeCycle methods somehow.
I believe these approaches are all Flex specific, and dependent upon the Flex Framework. Generic Sprites, as best I understand, do not automatically size themselves to percentages of their parent container; and changing the parent will not automatically resize the parent's children.
I bet something like this would work:
public function grow(event:MouseEvent):void
{
event.target.width += 5;
event.target.height += 5;
childSprite.width += 5;
childSprite.height += 5;
trace("grow", event.target.width);
}
First, if you have a problem with a flex component, you can look over its source code.
In my environment, (as I installed flex SDK to C:\flex\flex_sdk_4.1), the source code for SpriteVisualElement is located at
C:\flex\flex_sdk_4.1\frameworks\projects\spark\src\spark\core\SpriteVisualElement.as
In the source code, you'll find that width property is overridden :
/**
* #private
*/
override public function set width(value:Number):void
{
// Apply to the current actual size
_width = value;
setActualSize(_width, _height);
// Modify the explicit width
if (_explicitWidth == value)
return;
_explicitWidth = value;
invalidateParentSizeAndDisplayList();
}
So, you cannot expect the auto-scaling of the component.
Making custom components will be one solution.
Here is a sample implementation of custom component.
Custom Component Example - wonderfl build flash online

Resources