Interesting scroll behaviour when scrolling past end - codenameone

I created a form to check the effect of margin and padding on a scrollable component. Apparently padding is part of the scrolling content - which I didn't find documented anywhere.
Should one reasonably be able to guess or should this be documented?
Anyway - there is a strange behaviour when scrolling down the list past its end where I would have expected it to scroll back to content just like tensile scrolling, but it doesn't. Is it a bug?
The attached code demonstrates this - the +-button adds buttons to the list; the down-button scrolls to the last entry - when clicked the list is in a weird condition:
public class FormMarginPaddingScroll extends Form {
private int entrytally = 0;
public FormMarginPaddingScroll() {
setTitle("FormMarginPaddingScroll");
setScrollable(false);
Container contentPane = getContentPane();
contentPane.setLayout(new BoxLayout(BoxLayout.Y_AXIS));
contentPane.setScrollableY(true);
Style style = contentPane.getAllStyles();
style.setMarginUnit(Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS);
style.setMargin(5, 5, 5, 5);
style.setPaddingUnit(Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS);
style.setPadding(5, 5, 5, 5);
style.setBorder(Border.createDashedBorder(1));
TextArea textArea = new TextArea("This form is for checking margin, border, padding and scrolling behaviour.");
textArea.setEditable(false);
contentPane.add(textArea);
{
FloatingActionButton floatingActionButton = FloatingActionButton.createFAB(FontImage2.MATERIAL_ARROW_DOWNWARD);
floatingActionButton.bindFabToContainer(contentPane, Component.LEFT, Component.BOTTOM);
floatingActionButton.addActionListener((e) -> scrollDown(contentPane));
}
{
FloatingActionButton floatingActionButton = FloatingActionButton.createFAB(FontImage2.MATERIAL_ADD);
floatingActionButton.bindFabToContainer(contentPane, Component.RIGHT, Component.BOTTOM);
floatingActionButton.addActionListener((e) -> newEntry(contentPane));
}
}
private void scrollDown(Container contentPane) {
Component componentLast = contentPane.getComponentAt(contentPane.getComponentCount() - 1);
contentPane.scrollRectToVisible(
componentLast.getX(),
componentLast.getY(),
componentLast.getWidth(),
contentPane.getHeight(),
componentLast);
}
private void newEntry(Container contentPane) {
final int entryid = ++entrytally;
Button button = new Button("" + entryid);
button.addActionListener((e) -> {
ToastBar.Status status = ToastBar.getInstance().createStatus();
status.setMessage("button " + entryid + " pressed");
status.setExpires(3000);
status.show();
});
contentPane.add(button);
contentPane.revalidate();;
}
}

Related

How to correctly implement fix to make dragging a hidden component downwards work?

When a component is hidden, its height is forced to 0 which prevents the scrolling in Component to work when you drag it downwards. Basically, it can be dragged down to edge of the screen, but then no scrolling down happens. This issue was also mentioned earlier in SO here where Shai offered a path for investigation.
Any suggestions for a way to fix this issue?
Thanks
Here's a code sample to reproduce the issue (copy/paste into a Hello World project in replacement of the default start() method):
Image dragImage2;
Component dropPlaceholder;
void saveDragImage(Image dragIm) {
dragImage2 = dragIm;
}
public void start() {
if (current != null) {
current.show();
return;
}
/* To seee the issue: press and hold a button about 1 second to initiate the drag and drop,
then drag down to the lower edge of the screen: the list doesn't scroll down as normally.
When dragging upwards in a list (which must of course alreaday be scrolled down a bit),
it works as expected: When the dragged button reaches to top of the screen, the list scrolls down. */
Form hi = new Form("Hi World", new BorderLayout());
dropPlaceholder = new Label(" ... "); //used to create an empty space in the drop position
dropPlaceholder.setDropTarget(true);
Container list3 = new Container(BoxLayout.y());
for (int i = 0; i < 30; i++) {
Button draggableButton = new Button("Button number " + i) {
#Override
public void longPointerPress(int x, int y) {
setDraggable(true);
setFocusable(true);
dragImage2 = super.getDragImage();
setHidden(true);
Form f = getComponentForm();
f.pointerPressed(x, y);
pointerDragged(x - 1, y - 1); //move the dragged element a few pixels to initiate the drag
pointerDragged(x - 2, y - 2);
}
#Override
protected Image getDragImage() {
return dragImage2;
}
#Override
protected void dragFinished(int x, int y) {
setHidden(false);
if (dropPlaceholder != null)
dropPlaceholder.remove(); //remove the placeholder after drag
}
};
draggableButton.setDropTarget(true);
draggableButton.addDragOverListener((e) -> {
Component dropTarget = e.getDropTarget(); //may be null (in Component.dragFinishedImpl(int x, int y))
if (dropTarget != null) {
Container parent = dropTarget.getParent();
int index = parent.getComponentIndex(dropTarget);
dropPlaceholder.remove(); //remove placeholder from previous position
parent.addComponent(index, dropPlaceholder); //add placeholder at position of dropTarget
}
getCurrentForm().revalidate(); //refresh the screen to show the new position of placeholder
});
list3.add(draggableButton);
}
list3.setScrollableY(true);
Container cont = hi.getContentPane();
cont.add(BorderLayout.CENTER, list3);
hi.show();
}

Scrolling on VBox with lots of Textfields

I am having lots of Textfields in VBox in a Scrollpane. When scrolling and touching a textfields it just grabs the focus. So smooth scrolling is not possible. How can I get it nice scrolling without the unwanted focus on any textfield. Do I need to consume the events on the textfield while scrolling?
This is a possible solution for the case where you want to scroll over a long list of textfields, without letting them take the focus, until you stop scrolling at all, and clearly want to select one of the textfields.
It is based in a custom "press and hold" event, inspired by this question.
The idea is to bundle the TextField control in an HBox, and disable the access to the control using the mouse transparent property of the container.
Then, whenever you tap on the container, if you press long enough the container will give access to the control and the keyboard will show up. Otherwise, you will continue scrolling, but without showing the keyboard.
I'll use the KeyboardService referred in this question on iOS only.
public class BasicView extends View {
public BasicView(String name) {
super(name);
setTop(new Button("Button"));
VBox controls = new VBox(15.0);
controls.setAlignment(Pos.CENTER);
ScrollPane pane = new ScrollPane(controls);
controls.prefWidthProperty().bind(pane.widthProperty().subtract(20));
for (int i = 0; i < 100; i++) {
final Label label = new Label("TextField " + (i + 1));
final TextField textField1 = new TextField();
HBox.setHgrow(textField1, Priority.ALWAYS);
HBox box = new HBox(10, label, textField1);
box.setMouseTransparent(true);
box.setAlignment(Pos.CENTER_LEFT);
box.setPadding(new Insets(5));
controls.getChildren().add(box);
}
addPressAndHoldHandler(controls, Duration.millis(300), eStart -> {
for (Node box : controls.getChildren()) {
box.setMouseTransparent(true);
}
}, eEnd -> {
for (Node box : controls.getChildren()) {
if (box.localToScene(box.getBoundsInLocal()).contains(eEnd.getSceneX(), eEnd.getSceneY())) {
box.setMouseTransparent(false);
((HBox) box).getChildren().get(1).requestFocus();
break;
}
}
});
setCenter(pane);
// iOS only
Services.get(KeyboardService.class).ifPresent(keyboard -> {
keyboard.visibleHeightProperty().addListener((obs, ov, nv) -> {
if (nv.doubleValue() > 0) {
for (Node box : controls.getChildren()) {
Node n1 = ((HBox) box).getChildren().get(1);
if (n1.isFocused()) {
double h = getScene().getHeight() - n1.localToScene(n1.getBoundsInLocal()).getMaxY();
setTranslateY(-nv.doubleValue() + h);
break;
}
}
} else {
setTranslateY(0);
}
});
});
}
#Override
protected void updateAppBar(AppBar appBar) {
appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> System.out.println("Menu")));
appBar.setTitleText("Scrolling over TextFields");
}
private void addPressAndHoldHandler(Node node, Duration holdTime,
EventHandler<MouseEvent> handlerStart, EventHandler<MouseEvent> handlerEnd) {
class Wrapper<T> {
T content;
}
Wrapper<MouseEvent> eventWrapper = new Wrapper<>();
PauseTransition holdTimer = new PauseTransition(holdTime);
holdTimer.setOnFinished(event -> handlerEnd.handle(eventWrapper.content));
node.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
handlerStart.handle(event);
eventWrapper.content = event;
holdTimer.playFromStart();
});
node.addEventHandler(MouseEvent.MOUSE_RELEASED, event -> holdTimer.stop());
node.addEventHandler(MouseEvent.DRAG_DETECTED, event -> holdTimer.stop());
}
}
Note that I've added a button on top to get the focus on the first place when you show the view.
Whenever you click/tap on the controls VBox, it sets all the boxes mouse transparent: box.setMouseTransparent(true);, and start a PauseTransition.
If there is a mouse release or a mouse drag before 300 ms (this could be changed at your convenience), the transition will stop. Otherwise, after 300 ms, it will set the box to box.setMouseTransparent(false);, and set the focus on the TextField, and at that moment the keyboard will show up.
Here is a class which I use for the purpose you describe:
public class MouseClickedFilter{
private final Node observableNode;
private BooleanProperty scrolling = new ReadOnlyBooleanWrapper(false);
private EventHandler<? super MouseEvent> dragDetectedFilter = e -> scrolling.set(true);
private EventHandler<? super MouseEvent> mouseExitedHandler = e -> scrolling.set(false);
private EventHandler<? super MouseEvent> mouseClickedFilter = evt ->
{
if (scrolling.get()) {
evt.consume();
scrolling.set(false);
}
};
private boolean listenersEnabled;
public MouseClickedFilter(Node observableNode) {
this.observableNode = observableNode;
}
public void activate() {
if (!listenersEnabled) {
observableNode.addEventFilter(MouseEvent.DRAG_DETECTED, dragDetectedFilter);
observableNode.addEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedHandler);
observableNode.addEventFilter(MouseEvent.MOUSE_CLICKED, mouseClickedFilter);
}
}
public void deactivate() {
if (listenersEnabled) {
observableNode.removeEventFilter(MouseEvent.DRAG_DETECTED, dragDetectedFilter);
observableNode.removeEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedHandler);
observableNode.removeEventFilter(MouseEvent.MOUSE_CLICKED, mouseClickedFilter);
}
}
public final ReadOnlyBooleanProperty scrollingProperty() {
return scrolling;
}
public final boolean isScrolling() {
return scrolling.get();
}
}
ObservableNode is your ScrollPane containing the textFields

How to get current radio button index value from a radio button group in codename one?

I have some radio button which I put in radio button group.
So, how can I get the radio button index value or selected index value when I going to click on a particular radio button.
Here is my code
bg=new ButtonGroup();
for(i=0;i<8;i++){
rbt =new RadioButton();
rbt.setName("rbt"+i);
radioList.add(rbt);
}
for(int i=0;i<radioList.size();i++){
radioList.get(i).addPointerPressedListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
for(int i=0;i<radioList.size();i++){
Log.p("================"+bg.getSelectedIndex);
}
}
});
}
In the above code I am getting the previously selected value not the current. value
Thanks in Advance
pointerPressed is called before the radio button has processed the event. Use addActionListener instead, which is fired after the state is changed, so the selected index should be the currently-selected radio button.
You should add the radiobutton in container as well as buttongroup and call getselectedIndex of buttongroup to get current index as shown in below code
void testRadio(Form form) {
Container radioList = new Container(new BoxLayout(BoxLayout.Y_AXIS));
ButtonGroup bg = new ButtonGroup();
for (int i = 0; i < 8; i++) {
RadioButton rbt = new RadioButton();
rbt.setName("rbt" + i);
rbt.setText("rbt" + i);
**radioList.add(rbt);
bg.add(rbt);**
}
bg.setSelected(0);
form.add(radioList);
Button getIndexButton = new Button("Get Radio Index");
getIndexButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
Log.p("================" + bg.getSelectedIndex());
}
});
form.add(getIndexButton);
}

Strange placement of multiple FloatingActionButton instances bound to the content pane

When binding mor than one FloatingActionButton instances to the content pane I notice a strange grouping effect.
Adding a the first at bottom left and the second at bottom right they are both grouped right.
Adding the first at bottom right and the second at bottom left they are grouped left in creation order.
Is this behaviour to be expected and why then or is it a bug?
Here's the code:
public class FormMultipleFloatingButtons extends Form {
public FormMultipleFloatingButtons() {
this(true);
}
public FormMultipleFloatingButtons(boolean aLeftBeforeRight) {
setTitle("Button Placement");
setScrollable(false);
Container contentPane = getContentPane();
contentPane.setLayout(new BoxLayout(BoxLayout.Y_AXIS));
contentPane.setScrollableY(true);
Style style = contentPane.getAllStyles();
style.setMarginUnit(Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS);
style.setMargin(5, 5, 5, 5);
style.setBorder(Border.createDashedBorder(1));
TextArea textArea = new TextArea(
"The placement of FloatingActionButtons when adding more than one of those.\n\n"
+ "Tap the right toolbar button to recreate the form with swapped creation order "
+ "of the two FloatingActionButton instances.");
textArea.setEditable(false);
contentPane.add(textArea);
Runnable runnableLeft = () -> {
FloatingActionButton floatingActionButton = FloatingActionButton.createFAB(
FontImage2.MATERIAL_CALL_RECEIVED);
floatingActionButton.bindFabToContainer(contentPane, Component.LEFT, Component.BOTTOM);
};
Runnable runnableRight = () -> {
FloatingActionButton floatingActionButton = FloatingActionButton.createFAB(
FontImage2.MATERIAL_SUBDIRECTORY_ARROW_RIGHT);
floatingActionButton.bindFabToContainer(contentPane, Component.RIGHT, Component.BOTTOM);
};
if (aLeftBeforeRight) {
runnableLeft.run();
runnableRight.run();
} else {
runnableRight.run();
runnableLeft.run();
}
getToolbar().addCommandToRightBar(new Command(
"",
FontImage.createMaterial(FontImage.MATERIAL_SWAP_HORIZ, style)) {
#Override
public void actionPerformed(ActionEvent evt) {
FormMultipleFloatingButtons formMultipleFloatingButtons = new FormMultipleFloatingButtons(!aLeftBeforeRight);
formMultipleFloatingButtons.show();
}
});
}
}
The content pane only supports one FloatingActionButton. Since adding a fab to the content pane places it in the layered pane and that is an exclusive position adding two fabs cause a collision.
This isn't something we designed for as the UX of FAB is pretty clear that there should be just one and if more functionality is needed you can add it below.
If you still want to do this you can use a Container within the content pane and bind to that.
To have a "next" and "previous" FloatingActionButton to scroll through my tabs, I did the following:
private FloatingActionButton fabRight = FloatingActionButton.createFAB(FontImage.MATERIAL_KEYBOARD_ARROW_RIGHT);
private FloatingActionButton fabLeft = FloatingActionButton.createFAB(FontImage.MATERIAL_KEYBOARD_ARROW_LEFT);
tabs.addSelectionListener((i1, i2) -> {
fabRight.setVisible(i2 != tabs.getTabCount() - 1);
fabLeft.setVisible(i2 != 0);
});
fabRight.addActionListener(e -> {
int index = tabs.getSelectedIndex() + 1;
tabs.setSelectedIndex(index);
fabRight.setVisible(index != tabs.getTabCount() - 1);
});
fabLeft.addActionListener( e -> {
int index = tabs.getSelectedIndex() -1;
tabs.setSelectedIndex(index);
fabLeft.setVisible(index != 0);
});
Container cntRight = fabRight.bindFabToContainer(tabs,Component.RIGHT, Component.BOTTOM);
Container cntLeft = fabLeft.bindFabToContainer(cntRight,Component.LEFT, Component.BOTTOM);
BorderLayout bl = new BorderLayout();
bl.setCenterBehavior(BorderLayout.CENTER_BEHAVIOR_CENTER);
setLayout(bl);
add(BorderLayout.CENTER, cntLeft);
add(BorderLayout.SOUTH, cntButtons);

How to draw a Plus/Minus on a Toggle Button based on Toggle State in WinForms

public partial class Form1 : Form
{
CheckBoxExt checkBox;
public Form1()
{
InitializeComponent();
checkBox = new CheckBoxExt();
this.Controls.Add(checkBox);
}
}
public class CheckBoxExt : CheckBox
{
public CheckBoxExt()
{
this.Size = new Size(20, 20);
this.Location = new Point(200, 200);
this.Appearance = Appearance.Button;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var point = this.PointToScreen(new Point(0, 0));
e.Graphics.DrawLine(new Pen(ColorTranslator.FromHtml("#666666"), 2), new Point(point.X + 5, point.Y + 10), new Point(point.X + 15, point.Y + 10));
if (this.Checked)
e.Graphics.DrawLine(new Pen(ColorTranslator.FromHtml("#666666"), 2), new Point(point.X + 10, point.Y + 5), new Point(point.X + 10, point.Y + 15));
}
/// <summary>
/// to remove the focus dotted border over the control
/// </summary>
protected override bool ShowFocusCues
{
get
{
return false;
}
}
}
Here is my code, i customized CheckBox and sets it Appearance as Button so that it will act as Toggle button.
i need to draw a small horizontal line which will display as minus on the Button, and draw a small horizontal and vertical line which will display as Plus button. Hence it need to toggled, if it's in checked state plus need to be shown and if it's unchecked minus need to be shown over the toggle button
Thanks in Advance
I would use an imageList control and populated it with plus and minus images respectively with desired resolution.
The BackgroundImage property of the control can be used to set "+" and "-" images.
And, don't forget to register CheckedChanged event of the control to toggle the BackgroundImage of the control.
Something like this for example -
public Form1()
{
InitializeComponent();
var checkBox = new CheckBoxExt();
//My imageList contain two images, for "+" and "-"
//Register chacked changed event for the control
checkBox.CheckedChanged += checkBox_CheckedChanged;
//Set the initial image as "-"
checkBox.BackgroundImage = this.imageList1.Images[1];
this.Controls.Add(checkBox);
}
void checkBox_CheckedChanged(object sender, EventArgs e)
{
if (((CheckBox)sender).Checked)
{
((CheckBox)sender).BackgroundImage = this.imageList1.Images[0];
}
else
{
((CheckBox)sender).BackgroundImage = this.imageList1.Images[1];
}
}

Resources