Adding more items to page (pagination javafx) - javafx

i am using kotlin to create some small application with it, and tornadofx, however i am unfamiliar with how some of the javafx things translate to kotlin.
I am trying to itterate through a list of buttons, and add button to page, but i managed only to add one button per page, and i want to have X items per page, and i want page count to be dynamic
override val root = tabpane{
setPrefSize(800.0,600.0)
tabs.removeAll()
tabClosingPolicy = TabPane.TabClosingPolicy.ALL_TABS
val buttonList = arrayOf(
Button("Btn1"),
Button("Btn2"),
Button("Btn3"),
Button("Btn4")
)
tab("My Tab 1") {
hbox {
button("Click Me!") {
setOnAction {
replaceWith<TestView>()
}
}
button("Add Tab 2") {
setOnAction {
tab("tab2 ")
{
select()
button("Close") { setOnAction { close() } }
pagination(buttonList.size) { setPageFactory { pageIndex -> (buttonList[pageIndex]) } }
style {
arrowsVisible = false
pageInformationVisible = false
}
}
}
}
}
}
}
i tryed to implement a for loop that would add certain amount of items per page (lets say 2 buttons per page) and then proceed to create page. but i had no luck.
If anyone could help me find a proper solution for kotlin based pagination i would be thankful
pagination(buttonList.size)
with this line i can controll amount of pages, so that is at least something, but i dont see how to control amount items per page.
pageIndex -> (buttonList[pageIndex])
i realize issue is here, but i had no luck implementing proper logic.
lets say i want 4 items per page, i could get amount of pages required by dividing my list with number 4 (items per page). but not sure how to add more then 1 item per page.

class SomeView : View() {
private val buttonList = arrayOf(
Button("Btn1"),
Button("Btn2"),
Button("Btn3"),
Button("Btn4"),
Button("Btn5")
)
private var pageSize = 3 //Decide this however you want
override val root = tabpane {
setPrefSize(800.0, 600.0)
tabs.removeAll()
tabClosingPolicy = TabPane.TabClosingPolicy.ALL_TABS
tab("My Tab 1") {
hbox {
button("Click Me!") {
setOnAction {
replaceWith<TestView>()
}
}
button("Add Tab 2") {
setOnAction {
tab("tab2 ")
{
select()
button("Close") { setOnAction { close() } }
//The ceiling calc is for rounding up for the size
pagination(ceil(buttonList.size.toDouble() / pageSize).toInt())
{ setPageFactory { pageIndex -> createPage(pageIndex * pageSize, pageSize) } }
style {
arrowsVisible = false
pageInformationVisible = false
}
}
}
}
}
}
}
private fun createPage(startingIndex: Int, pageSize: Int = 1) : Pane {
val pagePane = VBox() //Create and design whatever pane you want
val endingIndex = minOf(startingIndex + pageSize, buttonList.size) //Don't go out of bounds
for(i in startingIndex until endingIndex) { //Assuming starting index is in bounds
pagePane.add(buttonList[i])
}
return pagePane
}
}
Here is a solution for dynamic pages for pagination. Your main problem was that tab panes only hold one child pane/component. The answer is to nest your buttons in a pane. I haven't tested this hard for edge cases or anything, so this is more proof-of-concept. You'll have to account for those if they come up.
Side Notes: As explained above, this is also why your "Close" button isn't appearing. Also, when I was testing your code I noticed the style wasn't implemented unless I clicked a button in the pane. Maybe it would work if you used a stylesheet, created a cssclass, and added that class to your tabpane (Don't forget to import your stylesheet if you choose to do this).

Related

JavaFX tableview auto scroll to selected item when pressing a button to selectNext() or selectPrevious()

I'm writing a JavaFX program with a TableView called 'table' and 2 buttons called 'previous' & 'next'.
Here is part of the code:
previous.setOnAction(event -> {
table.getSelectionModel().selectPrevious();
});
next.setOnAction(event -> {
table.getSelectionModel().selectNext();
});
However, if I keep pressing the buttons, the table will not scroll automatically to keep the selected item visible. So I modified the code like this :
previous.setOnAction(event -> {
table.getSelectionModel().selectPrevious();
table.scrollTo(table.getSelectionModel().getSelectedItem());
});
next.setOnAction(event -> {
table.getSelectionModel().selectNext();
table.scrollTo(table.getSelectionModel().getSelectedItem());
});
But it will always try to keep the selected item at the top of the visible region. If I keep pressing 'next'. The selected item will stay at the top instead of staying at the bottom.
I want to mimic the natural behavior of a tableview in the way that if I press up or down on the keyboard with something selected, the tableview will scroll automatically to keep the selected item visible.
How should I modify the code to make the auto scrolling more natural when I press the buttons?
Thanks
The problem is
missing fine-grained control of scrollTo target location on application level
the (somewhat unfortunate) implementation of virtualizedControl.scrollTo(index) which (ultimately) leads to calling flow.scrollToTop(index)
There's a long-standing RFE (reported 2014!) requesting better control from application code. Actually, VirtualFlow has public methods (scrollToTop, scrollTo, scrollPixels) providing such, only they are not passed on to the control layer (getVirtualFlow in VirtualContainerBase is final protected), so can't be overridden in a custom skin. Since fx12, we can hack a bit, and expose the onSelectXX of Tree/TableViewSkin and use those, either directly in application code (example below) or in a custom TableView.
Example code:
public class TableSelectNextKeepVisible extends Application {
/**
* Custom table skin to expose onSelectXX methods for application use.
*/
public static class MyTableSkin<T> extends TableViewSkin<T> {
public MyTableSkin(TableView<T> control) {
super(control);
}
/**
* Overridden to widen scope to public.
*/
#Override
public void onSelectBelowCell() {
super.onSelectBelowCell();
}
/**
* Overridden to widen scope to public.
*/
#Override
public void onSelectAboveCell() {
super.onSelectAboveCell();
}
}
private Parent createContent() {
TableView<Locale> table = new TableView<>(FXCollections.observableArrayList(Locale.getAvailableLocales())) {
#Override
protected Skin<?> createDefaultSkin() {
return new MyTableSkin<>(this);
}
};
TableColumn<Locale, String> country = new TableColumn<>("Column");
country.setCellValueFactory(new PropertyValueFactory<>("displayLanguage"));
table.getColumns().addAll(country);
Button next = new Button("next");
next.setOnAction(e -> {
table.getSelectionModel().selectNext();
// scrolls to top
// table.scrollTo(table.getSelectionModel().getSelectedIndex());
((MyTableSkin<?>) table.getSkin()).onSelectBelowCell();
});
Button previous = new Button("previous");
previous.setOnAction(e -> {
table.getSelectionModel().selectPrevious();
// scrolls to top
// table.scrollTo(table.getSelectionModel().getSelectedIndex());
((MyTableSkin<?>) table.getSkin()).onSelectAboveCell();
});
BorderPane content = new BorderPane(table);
content.setBottom(new HBox(10, next, previous));
return content;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
try using getSelectedIndex as follows instead of using getSelectedItem
previous.setOnAction(event -> {
table.getSelectionModel().selectPrevious();
table.scrollTo(table.getSelectionModel().getSelectedIndex());
});
Platform.runLater( () -> TABLE_NAME.scrollTo(TABLE_INFORMATION_LIST.getList().size()-index) );
should work if you call it whenever you add information to the table.

Javafx : ComboBoxTableCell - how to select a value in one click?

I have a TableView with a ComboBoxTableCell, when using the default implementation the user have to click three times to select a value from of the ComboBox's list.
I want when the user clicks on the cell to show the combo box list. I based my solution on this one:
JavaFX editable ComboBox in a table view
The cell does get into edit mode (startEdit() is called) but it takes another click to show the list of values, what am I missing?
table.addEventHandler(MouseEvent.MOUSE_CLICKED, (e) ->
{
if (table.getEditingCell() == null)
{
TablePosition focusedCellPos = table.getFocusModel().getFocusedCell();
table.edit(focusedCellPos.getRow(), focusedCellPos.getTableColumn());
}
});
Thanks.
Interesting problem - bubbling up again after quite a while :)
Looks like the approach of the OP is indeed working (as of fx11, some bugs around its editing seem to be fixed) - with a little help from the combo cell:
start editing in a single click handler on the tableView (from OP)
extend ComboBoxTableCell and override its startEdit to open the dropDown
Code snippet:
// set editable to see the combo
table.setEditable(true);
// keep approach by OP
table.addEventHandler(MouseEvent.MOUSE_CLICKED, (e) -> {
TablePosition<Person, ?> focusedCellPos = table.getFocusModel()
.getFocusedCell();
if (table.getEditingCell() == null) {
table.edit(focusedCellPos.getRow(),
focusedCellPos.getTableColumn());
}
});
// use modified standard combo cell shows its popup on startEdit
firstName.setCellFactory(cb -> new ComboBoxTableCell<>(firstNames) {
#Override
public void startEdit() {
super.startEdit();
if (isEditing() && getGraphic() instanceof ComboBox) {
// needs focus for proper working of esc/enter
getGraphic().requestFocus();
((ComboBox<?>) getGraphic()).show();
}
}
});
Maybe not the cleanest solution to this problem, but I found a workaround to make the ComboBoxTableCells drop down its menu in just 1 click:
column.setCellFactory(new Callback<TableColumn<Person, String>, TableCell<Person, String>>() {
#Override
public TableCell<Person, String> call(TableColumn<Person, String> column) {
ComboBoxTableCell cbtCell = new ComboBoxTableCell<>(cbValues);
cbtCell.setOnMouseEntered(new EventHandler<Event>() {
#Override
public void handle(Event event) {
// Without a Person object, a combobox shouldn't open in that row
if (((Person)((TableRow)cbtCell.getParent()).getItem()) != null) {
Robot r = new Robot();
r.mouseClick(MouseButton.PRIMARY);
r.mouseClick(MouseButton.PRIMARY);
}
}
});
return cbtCell;
}
});
PS: I know that this topic is a bit old, but I also stumbled upon this problem recently and could not find any working solution to it online. As I sad, it's not the cleanest workaround, but at least it does its job. ;)

javafx CheckBoxTreeItem update parents programmatically

I have to retrieve some data from my database to dynamically create a TreeView and select some CheckBoxTreeItems from this TreeView. This TreeView represents permissions to a menu structure.
My doubt is when I create the TreeView and select specific items from the Tree according to the user's permissions programmatically, the parents items don't have any status change (selected or indeterminate). But when I select any item directly from the interface, the parents get updated.
For example, here I have my screen when I select the items programmatically:
You can see that I have two menu items selected, but the parents aren't.
On this image, I have selected the same menu items using the screen, and the parents were updated with indeterminate status or selected if I select all children inside the submenu.
I have gone through the documentation, google and here on Stack Overflow, but only found examples to update the children.
Is there a way to update the parents programmatically or to call the event executed from the screen when an item is selected?
EDIT:
All items from the Tree have the independent property set to false.
I came with a workaround for this problem.
I had to first create all the TreeView structure, and change the selected property after using this code snippet:
Platform.runLater(new Runnable() {
#Override
public void run() {
selectItems();
}
});
Here is the code to verify the TreeItems:
private void selectItems(){
TreeItem root = tree.getRoot();
if (root != null) {
selectChildren(root);
}
}
private void selectChildren(TreeItem<TesteVO> root){
for(TreeItem<TesteVO> child: root.getChildren()){
// HERE I CHECK IF THE USER HAS PERMISSION FOR THE MENU ITEM
// IF SO, I CHANGE THE SELECTED PROPERTY TO TRUE
if (child.getValue().id == 4) {
((CheckBoxTreeItem) child).setSelected(true);
}
// IF THERE ARE CHILD NODES, KEEP DIGGING RECURSIVELY
if(!child.getChildren().isEmpty()) {
selectChildren(child);
}
}
}
If there is a simpler way, please let me know!
This is not the case. Parent items do get automatically get set to the indeterminate state when you select a child item. I'm not sure if this is something that got corrected from the time that this question was posted, probably not.
My guess is that there's a programming bug in how the node was selected or how the TableView was constructed and initialized.
Here's some code that shows what I'm doing, and it works! In my case, I'm using a CheckBoxTreeItem<File> for the TreeItem.
How the treeview was created
treeView = new TreeView(root);
treeView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observableValue, Object o, Object t1) {
CheckBoxTreeItem<File> node = (CheckBoxTreeItem<File>)t1;
if (node.getValue() != currentFile) {
setFileDetail(node);
showChildren(node);
}
}
});
treeView.setCellFactory(new CallBackWrapper());
treeView.setShowRoot(false);
Below show the CallBackWrapper class.
private class CallBackWrapper implements Callback<TreeView<File>, TreeCell<File>> {
Callback<TreeView<File>, TreeCell<File>> theCallback;
private CallBackWrapper() {
theCallback = CheckBoxTreeCell.<File>forTreeView(getSelectedProperty, converter);
}
#Override
public TreeCell<File> call(TreeView<File> fileTreeView) {
return theCallback.call(fileTreeView);
}
final Callback<TreeItem<File>, ObservableValue<Boolean>> getSelectedProperty = (TreeItem<File> item) -> {
if (item instanceof CheckBoxTreeItem<?>) {
return ((CheckBoxTreeItem<?>) item).selectedProperty();
}
return null;
};
final StringConverter<TreeItem<File>> converter = new StringConverter<TreeItem<File>>() {
#Override
public String toString(TreeItem<File> object) {
File item = object.getValue();
return fileSystemView.getSystemDisplayName(item);
}
#Override
public TreeItem<File> fromString(String string) {
return new TreeItem<File>(new File(string));
}
};
}
And lastly here some code that the selection was made in:
boolean selectNode(CheckBoxTreeItem<File> parentNode, String name) {
Object[] children = parentNode.getChildren().toArray();
for (Object child : children) {
CheckBoxTreeItem<File> childItem = (CheckBoxTreeItem<File>) child;
if (name.equals(childItem.getValue().getName())) {
childItem.setSelected(true);
//treeView.getSelectionModel().select(child); <-- this does not work!
return true;
}
}
return false;
}

Detecting which UI button was pressed within canvas?

I have like 10 buttons on my UI and I gotta check which one was touched. I was using the following logic and it was working fine, but now I am getting this error for some reason:
NullReferenceException: Object reference not set to an instance of an object
DetectButton.Start () (at Assets/Scripts/DetectButton.cs:14)
Any ideas what could be going on? Here is my code (attached to the canvas), and I am using Unity version 5.1.0f3. If you need any other info I will gladly provide, thanks in advance
void Start()
{
this.GetComponent<Button>().onClick.AddListener(() =>
{
if (this.name == "btnJogadores2")
{
print ("2 jogadores");
jogadores = 2;
}
//QuantidadeJogadores(this.name);
//QuantidadePartidas(this.name);
});
}
You don't have to all this the way you are doing.
An Easier and good practice would be to create 10 separate GameObjects for each button inside your canvas. and then create a single script with 10 separate functions for all those buttons in it. Attach that script to you canvas. and then on the button GameObject select the script on the desired function. Sample below
void Start() { }
void Update() { }
public void button1()
{
Debug.Log("Button3");
}
public void button2()
{
Debug.Log("Button1");
}
public void button3()
{
Debug.Log("Button3");
}
NOTE: button1, button2 and button3 are the functions for 3 separate buttons
Then inside your unity Inspector:
Select your script with you button functions.
Assign you desired method to you button.
After this run your scene and your button will call the assigned methods properly.
Code is not tested, but it should get you started to get all the Buttons.
void Start() {
var buttons = this.GetComponents<Button> ();
foreach(var button in buttons) {
button.onClick.AddListener(() = > {
if (this.name == "btnJogadores2") {
print("2 jogadores");
jogadores = 2;
}
//QuantidadeJogadores(this.name);
//QuantidadePartidas(this.name);
});
}
}
Actually it will be hard to distinguish between the buttons.
The more practical aproach would be to make 10 GameObjects (Child of the Canvas) and attach your Script to everyone of them.

How to prevent a component from being dragged out of the stage in Flex 3

I think there is a simple solution to this question, just not simple enough for me to find it.
Question:
How do you constrain a TitleWindow in Flex 3 from being dragged off the screen/stage? Is there a way to restrict the TitleWindow to the viewing area?
Example: Let's say I have an application that take 100% of the screen. Next, I create a TitleWindow via the PopUpManager. I can then proceed to click and hold (drag) that window off the screen, then release the mouse button. That window is now lost off-screen somewhere. Is there a way to keep the window from being dragged beyond the viewing area?
Thanks for the help in advance.
this is a very old post, but here's another way of doing it:
Whether you are extending the component or not, in the TitleWindow definition add the following line: move:"doMove(event)"
Import the Application library (import mx.core.Application;)
and add the doMove function:
private function doMove(event:Event):void
{//keeps TW inside layout
var appW:Number=Application.application.width;
var appH:Number=Application.application.height;
if(this.x+this.width>appW)
{
this.x=appW-this.width;
}
if(this.x<0)
{
this.x=0;
}
if(this.y+this.height>appH)
{
this.y=appH-this.height;
}
if(this.y<0)
{
this.y=0;
}
}
For flex 4 the answer is here: http://blog.flexexamples.com/2010/01/20/constraining-the-movement-on-a-spark-titlewindow-container-in-flex-4/
You can set its isPopUp property to false to prevent it from being dragged in the first place.
var popupWin:TitleWindow = PopUpManager.createPopUp(this, TitleWindow);
PopUpManager.centerPopUp(popupWin);
popupWin.isPopUp = false;
I don't know if the DragManager class in flex supports bounds checking, but if you really want to allow dragging but limit its bounds, you can still set isPopUp to false and implement the dragging code yourself so that the component never goes outside the limits specified by you. Check startDrag() method for an example. Bounds rectangle is the key.
Flex 4
<s:TitleWindow xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
windowMoving="windowMovingHandler(event)">
.
.
.
protected function windowMovingHandler(event:TitleWindowBoundsEvent):void
{
var appBounds:Rectangle = parentApplication.getBounds(DisplayObject(parentApplication));
if(!appBounds.containsRect(event.afterBounds)){
event.preventDefault();
}
}
// for better precision, corect appBounds manualy, or, instead of "parentApplication.getBounds..." create new rectangle of your application size
Subclass the TitleWindow and add a canvas over the title bar as a drag proxy. Then you can explicity call startDrag with a boundary rectangle.
This is pretty skeletal, but should put you on the path...
The reason for the proxy is you may get some weird behavior when you click the titleBar label if you don't have the canvas over it.
public class MyTitleWindow extends TitleWindow
{
public var titleBarOverlay:Canvas;
override protected function createChildren():void
{
super.createChildren();
if(!titleBarOverlay)
{
titleBarOverlay = new Canvas();
titleBarOverlay.width = this.width;
titleBarOverlay.height = this.titleBar.height;
titleBarOverlay.alpha = 0;
titleBarOverlay.setStyle("backgroundColor", 0x000000);
rawChildren.addChild(titleBarOverlay);
}
addListeners();
}
override protected function updateDisplayList(w:Number, h:Number):void
{
super.updateDisplayList(w, h);
titleBarOverlay.width = this.width;
titleBarOverlay.height = this.titleBar.height;
}
private function addListeners():void
{
titleBarOverlay.addEventListener(MouseEvent.MOUSE_DOWN, onTitleBarPress, false, 0, true);
titleBarOverlay.addEventListener(MouseEvent.MOUSE_UP, onTitleBarRelease, false, 0, true);
}
private function onTitleBarPress(event:MouseEvent):void
{
// Here you can set the boundary using owner, parent, parentApplication, etc.
this.startDrag(false, new Rectangle(0, 0, parent.width - this.width, parent.height - this.height));
}
private function onTitleBarRelease(event:Event):void
{
this.stopDrag();
}
}
You could simply override the move function and prevent "illegal" movement (it is called internally by the Panel drag management).
I think that you also should listen on stage resize, because reducing it (e.g. if the user resize the browser window) could send your popup out of stage even without actually moving it.
public class MyTitleWindow extends TitleWindow {
public function MyTitleWindow() {
// use a weak listener, or remember to remove it
stage.addEventListener(Event.RESIZE, onStageResize,
false, EventPriority.DEFAULT, true);
}
private function onStageResize(event:Event):void {
restoreOutOfStage();
}
override public function move(x:Number, y:Number):void {
super.move(x, y);
restoreOutOfStage();
}
private function restoreOutOfStage():void {
// avoid the popup from being positioned completely out of stage
// (use the actual width/height of the popup instead of 50 if you
// want to keep your *entire* popup on stage)
var topContainer:DisplayObjectContainer =
Application.application.parentDocument;
var minX:int = 50 - width;
var maxX:int = topContainer.width - 50;
var minY:int = 0;
var maxY:int = topContainer.height - 50;
if (x > maxX)
x = maxX
else if (x < minX)
x = minX;
if (y > maxY)
y = maxY
else if (y < minY)
y = minY;
}
}
In your TitleWindow's creationComplete handler add the following:
this.moveArea.visible=false;
This will do the job.
On the other hand, if you have a custom skin, you can remove the "moveArea" part. This should work, too.

Resources