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);
Related
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();
}
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
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();;
}
}
I'm trying to implement an Android style side menu and I'm having an issue implementing the rounded icon on top and labels below it before the sideCommands are added.
How do I implement this please?
You can use Toolbar API which allows you to add components to the Sidemenu.
Have a look at Flickr demo.
Instead of using tool.addCommandToSideMenu(Command) you should use tool.addComponentToSideMenu(yourComponent, CommandToPerform)
Example:
#Override
protected void beforeMain(Form f) {
//Store your commands before setting toolbar
List<Command> cmds = new ArrayList();
for (int i = 0; i < f.getCommandCount(); i++) {
cmds.add(f.getCommand(i));
}
Toolbar toolbar = new Toolbar();
f.setToolBar(toolbar);
Label lblTitle = new Label("My Form", "Title");
lblTitle.setEndsWith3Points(false);
toolbar.setTitleComponent(lblTitle);
// Use your stored commands after setting toolbar
for (Command cmd : cmds) {
toolbar.addCommandToSideMenu(cmd);
}
Container CustomContainer = ...
toolbar.addComponentToSideMenu(CustomContainer, new Command("") {
#Override
public void actionPerformed(ActionEvent evt) {
//What CustomContainer should do (if any)
}
});
f.revalidate();
}
Silverlight 4 now include the option for creating a context menu upon right clicking. Can anyone provide me with an example of a treeview with a right click context menu for the treeview?
Ultimately I want a the menu to show different options depending upon the node depth selected - bonus points if the example includes this!
You can use this open source menu for this:
http://sl4popupmenu.codeplex.com
The control supports right click on TreeViews out of the box. The code has been adapted from the sample code on the homepage to use a TreeView instead of a DataGrid:
private void GenerateMenu()
{
var data = new ObservableCollection<string>("Item 1,Item 2,Item 3,Item 4,Item 6,Item 7,Item 8".Split(','));
TreeView treeView1 = new TreeView() { Margin = new Thickness(50), ItemsSource = data };
this.LayoutRoot.Children.Add(dataGrid1);
// Create the submenu
var pmTimeSub = new PopupMenu();
pmTimeSub.AddItem("Time Now", null);
// Create the main menu
var pm = new PopupMenu();
pm.AddItem("Delete row", delegate { data.RemoveAt(dataGrid1.SelectedIndex); });
pm.AddSeparator();
pm.AddSubMenu(pmTimeSub, "Get Time ", "images/arrow.png", null, null, false, null);
// Attach the submenu pmTimeSub
pm.AddSeparator();
pm.AddItem("Demo2", delegate { this.Content = new Demo2(); });
// Set dataGrid1 as the trigger element
pm.AddTrigger(TriggerTypes.RightClick, treeView1);
// Showing main menu
pm.Showing += (sender, e) =>
{
pm.PopupMenuItem(0).Header = "Delete " + treeView1.SelectedItem;
TreeViewItem tvi = pm.GetClickedElement<TreeViewItem>();
// Add code to calculate the node depth here using the GetParentTreeViewItem method
// Add code to modify the menu items according to the node depth value.
pm.PopupMenuItem(0).IsVisible =
pm.PopupMenuItem(1).IsVisible = tvi != null;
};
// Showing submenu
pmTimeSub.Showing += delegate
{
pmTimeSub.PopupMenuItem(0).Header = DateTime.Now.ToLongTimeString();
};
}
Note that the code does not allow you to show different menus upon the node depth yet. To do this you can use the following method to get the parent of the TreeViewItem that was clicked:
private static TreeViewItem GetParentTreeViewItem(DependencyObject item)
{
if (item != null)
{
DependencyObject parent = VisualTreeHelper.GetParent(item);
TreeViewItem parentTreeViewItem = parent as TreeViewItem;
return parentTreeViewItem ?? GetParentTreeViewItem(parent);
}
return null;
}
From there you can determine depth of the node by calling the GetParentTreeViewItem function in a loop until the parent is null. You would place this code in the event where the menu is being shown and then add the necessary code in there to show the appropriate menu.
Hope this helps.
So, I tried the above code, downloaded and attempted to include within my Existing Silverlight Application. I was able to find an easier solution. This will add a Context Menu allowing Right-Clicks on the Branches (Headers, or Parent Nodes).
private ContextMenu menu;
foreach(var model in models)
{
// Populate the Tree View Control
var cb = new CheckBox {Content = model.Value};
cb.Click += new RoutedEventHandler(cb_Click);
var header = new TreeViewItem {Header = cb};
// Menu for Header
menu = new ContextMenu();
MenuItem setAsRows = new MenuItem();
setAsRows.Header = "Set as Rows";
setAsRows.Click += new RoutedEventHandler(setAsRows_Click);
menu.Items.Add(setAsRows);
MenuItem addToRows = new MenuItem();
addToRows.Header = "Add to Rows";
addToRows.Click += new RoutedEventHandler(addToRows_Click);
menu.Items.Add(addToRows);
MenuItem setAsCols = new MenuItem();
setAsCols.Header = "Set as Columns";
menu.Items.Add(setAsCols);
MenuItem addToCols = new MenuItem();
addToCols.Header = "Add to Columns";
menu.Items.Add(addToCols);
header.ContextMenu = menu;
treeView1.Items.Add(header);
var thisItem = treeView1.Items;
// Model Contexts
var contexts = myFramework.GetConceptsOfModel(model.Key);
// Add Leafs To Branch
foreach(var context in contexts)
{
cb = new CheckBox {Content = context.Value.ToString()};
header.Items.Add(cb);
}
}