How to replace down arrow with text in a combobox in JavaFX - 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.

Related

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.

How to create a transparent Button with JavaFX without decreasing the hitbox?

As soon as I create a new Button in JavaFX and set the background transparent with: myButton.setBackground(Background.EMPTY); or myButton.setStyle("-fx-background-color: transparent;"),
the hitbox will only consist of the text in the button when catching the ActionEvent via :
myButton.setOnAction(newjavafx.event.EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
//handle UI input
}
});
So I have to aim on a letter and click it, which is annoying, especially when having changing text and/ or small text.
How can I keep my hitbox the same while having a transparent background?
Use
myButton.setPickOnBounds(true);
which means that the layout bounds of the button will be used to determine mouse hits on it, rather than its set of non-transparent pixels.

Making a smaller JavaFX ComboBox

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).

Flex 3 - Change box border colors

I have a question that might seem "basic" but I just cannot figure out how to do it...
I have a box and I'd like to change the borderColor. Till there, nothing special. Just a box.bordercolor = xxxxxx...
BUT, I'd like to have the top and bottom border with one color, and the left and right border with another color... And that's the part where I'm stuck.
Any tips? Suggestions?
Thanks for your help and time! ;)
Regards,
BS_C3
#Senz
Hi!
Unfortunately, I won't be able to share the code without making it "incomprehensible"...
But this is the idea... We have 2 main components: ArrowButton and Navigator.
ArrowButton is a hbox containing a label and an image (this image is the arrow tip and it changes depeding on the state of the ArrowButton).
Navigator is a hbox containing a series of ArrowButton. An ArrowButton overlaps the arrowButton on its right in order to create the pointed end of the button.
And then you just create a whole bunch of functionnalities around these components.
I hope this helps... Do not hesitate if you have some more questions =)
Regards.
I noticed you are asking about the Flex 3 SDK. Skins are a good approach. They have changed somewhat in Flex 4(for the better IMHO). If you are wanting to use the Flex Drawing API, then just extend the Box class into a custom class that would look something like this:
public class MultiColorBorderBox extends Box
{
// You could add getters/setters or constructor parameters to be able to change these values.
private var topColor:uint = 0xFF0000;
private var rightColor:uint = 0x00FF00;
private var bottomColor:uint = 0x0000FF;
private var leftColor:uint = 0xFF00FF;
private var borderWidth:Number = 20;
public function MultiColorBorderBox()
{
super();
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
// This just ensures you dont have content under your border
this.setStyle("paddingLeft", borderWidth);
this.setStyle("paddingRight", borderWidth);
this.setStyle("paddingTop", borderWidth);
this.setStyle("paddingBottom", borderWidth);
var g:Graphics = this.graphics; // This creates a new Graphics object and sets it to the MultiColorBorderBox graphics object. Since Box (superclass) descends from a Sprite object, it has a graphics object automatically.
g.clear();
g.moveTo(0,0); // Moves the position to the top left corner
g.lineStyle(borderWidth, topColor); // Sets the line style with the width and color
g.lineTo(unscaledWidth, 0); // Draws the top border from top left to top right corners
g.lineStyle(borderWidth, rightColor); // Changes the line style
g.lineTo(unscaledWidth, unscaledHeight); // Draws the line from top right to bottom right
g.lineStyle(borderWidth, bottomColor); //Changes the bottom border style
g.lineTo(0, unscaledHeight); // Draws the line from bottom right to bottom left
g.lineStyle(borderWidth, leftColor); // Changes the border color
g.lineTo(0,0); // Closes the box by drawing from bottom left to top left
}
I'm pretty sure you're going to have to create a borderSkin to accomplish this. I believe these are created in an external program, such as Flash Professional; but more info is in the docs.
I don't think that Flex makes any distinction between top/bottom borders and left/right borders. Creating a skin would certainly be the nifty-slick way to do it. A programmatic way might be to use box.graphics to draw your border by hand. I'd start by trying to override the updateDisplayList() function to draw your border...
I finally did a pretty simple thing.
I guess I wasn't detailed enough regarding the specifications.
The actual aim was to create a navigator with arrow shaped buttons.
Each button had to be highlighted when it was selected. And this http://www.freeimagehosting.net/uploads/bcd0d762d7.jpg is how the navigator looked like.
Each button is actually an HBox containing a Box (with a label) and an Image (for the arrow tip), with a horizontalGap = 0.
I didn't think about adding a glowfilter to the button. So I was trying to just change the colors of the top and bottom part of the Box...
So, the glowfilter in the button worked pretty well.
Sorry for the lack of explanations about the context >_< And thanks for your answers!!
Regards.

Changing the default color on flex validation errors

The examples I've seen seem to show how to change the color that shows when the user actually hovers over the textinput field.
However when the validation fails, a generic textInput border qill have a red line over it. My CSS file uses a border skin for the textInput, so I can't see this line.
I was hoping there was a way to highlight the text box when it failed validation, or re-enable the red line feature. I don't want to get rid of my CSS cos it'll totally blow my color-scheme, but any tweak allowing the error line to show would be much appreciated.
This is the CSS:
TextInput, TextArea
{
border-skin: Embed(source='/../assets/images/input_bg.png', scaleGridLeft=8, scaleGridRight=20, scaleGridTop=8,scaleGridBottom=9);
padding-top:2;
padding-left:2;
font-size:11;
}
anything that extends UIComponent (both TextInput and TextArea do) has a style called errorColor. It defaults to red. You can change this to whatever you want.
Additionally, if you've got an image that you are using as a border, you should probably remove the pixels from the middle so that it is an actual border instead of an overlay.
The only way I've managed to find, is that Validator will change the component's borderColor style. I don't think it can be achieved using an image- you'll have to embed the image in a basic GraphicRectangularBorder subclass or similar. You can then add this to your skin class:
override public function styleChanged(styleProp:String):void
{
super.styleChanged(styleProp);
if (styleProp == "borderColor")
{
if (getStyle("borderColor") == getStyle("errorColor"))
{
// show error outline
}
else
{
// hide error outline
}
}
}

Resources