TilePane在JavaFX中自动拉伸切片 [英] TilePane with automatically stretching tiles in JavaFX

查看:158
本文介绍了TilePane在JavaFX中自动拉伸切片的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

JavaFX中有没有办法从TilePane或FlowPane GridPane中获得最佳效果?

这是我想要实现的目标:

Is there any way in JavaFX to take best from both TilePane or FlowPane and GridPane?
Here's what I'd like to achieve:

首先,我喜欢GridPane的想法,我可以设置一个M×N网格,它在父容器中自动调整大小,将空间平均分成M列和N行。然后我可以放置一些完全填充每个单元格的子元素,它们将随网格一起拉伸。这很酷。

但是有一个缺点:我需要通过设置它的行和放大器来明确指定每个控件应该去哪里。列号。

First, I like the idea of GridPane where I can set up a M×N grid which automatically resizes within its parent container to divide the space equally into M columns and N rows. Then I can put some child elements which fill each cell completely, and they will stretch along with the grid. This is cool.
But there's one drawback: I need to explicitly specify where should each control go, by setting its row & column number.

然后,有一些布局容器,如FlowPane或TilePane,当父容器更改其大小时,它们会自动重排其子元素。当我添加另一个子元素时,它会自动附加到列表的末尾,并且元素列表在到达容器边缘后会自动换行,因为空间太小而无法容纳另一个元素。

但这也是一个缺点:子元素只能有严格的预定义大小。它们不会延伸其父元素。

Then, there are layout containers such as FlowPane or TilePane which automatically reflow their child elements when the parent container changes its size. When I add another child element, it is automatically attached at the end of the list, and the list of elements automatically wraps after reaching the edge of the container when there's too few space to fit another element in there.
But here's a drawback as well: the child elements can only have rigid, pre-defined sizes. They won't stretch with its parent element.

这就是我需要的东西:

我需要两个容器中最好的,是的,我想要一个M乘N格(比如4×4),其中每个单元格是ParentWight / N的ParentWidth / M并随窗口一起伸展,因此它总是4×4个单元格,但是它们的大小(以及其内容的大小)相应地延伸。
但是我不想明确地告诉容器哪一行和一行。列添加我添加的每个新孩子。相反,我想只是添加它,让容器找出要放入的第一个空单元格,从左到右填充单元格,如果当前行中没有空单元格,则从上到下填充。

And here's what I need:
I need the best from both of these containers, that is, I want a M by N grid (let's say 4×4) where each cell is ParentWidth/M by ParentHeight/N and stretches along with the window, so that it is always 4×4 cells, but their sizes (along with the sizes of their contents) stretches accordingly. But I don't want to tell the container explicitly in which row & column to put every new child I add there. Instead, I want to just add it and let the container figure out the first empty cell to put it in, filling the cells from left to right, then top to bottom if there's no empty cell left in the current row.

这些预定义容器的属性是否有一些神奇的设置可以让我实现这个目的?或者我是否需要自己编写这样的容器?

Is there some magical setup for any of these predefined containers' attributes which would allow me to achieve this? Or do I need to write such a container myself?

推荐答案

好的,这是我自己尝试的解决方案:

OK here's my own attempt at the solution:

由于 GridPane 按照我需要的方式工作几乎,只是不会自动流动其内容,我决定子类 GridPane 并添加自定义代码以自动流动其子控件。 (感谢@jns提示JavaFX库控件类可以被子类化。)

Since the GridPane works almost the way I need, just doesn't automatically flow its contents, I decided to subclass GridPane and add custom code for automatic flowing its child controls. (Thanks to @jns for the hint that JavaFX library control classes can be subclassed.)

所以我将 GridPane 并为其内部子项列表添加了一个侦听器,这样无论何时从该列表中添加或删除子项,或者列表以某种方式更改(例如,我们对子项重新排序),都会调用私有方法来重排子项通过在列表中的位置将它们分配给正确的列和行来划分网格。我还添加了两个私有辅助函数来将列表中的位置转换为行和放大器。列号和后面。

So I subclassed the GridPane and added a listener for its internal list of children, so that whenever a child is added or removed from that list, or the list changes in some way (e.g. we reorder the children), a private method is called to reflow the children in the grid by assigning them to correct columns and rows by their position in the list. I also added two private helper functions to convert the position in the list into row & column numbers and back.

我知道这不完美也不优雅,但是嘿,它有效,并且它足以满足我的需求:P所以我发布了这里的代码以防其他人遇到同样的问题:

I know that this isn't perfect nor elegant, but hey, it works, and it works well enough for my needs :P so I publish the code here in case someone else had the same problem:

package flowgrid;

import javafx.collections.ObservableList;
import javafx.collections.ListChangeListener;

import javafx.scene.Node;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.Priority;
import javafx.geometry.HPos;
import javafx.geometry.VPos;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.NamedArg;

/**
 * This class subclasses the GridPane layout class.
 * It manages its child nodes by arranging them in rows of equal number of tiles.
 * Their order in the grid corresponds to their indexes in the list of children
 * in the following fashion (similarly to how FlowPane works):
 * 
 *      +---+---+---+---+
 *      | 0 | 1 | 2 | 3 |
 *      +---+---+---+---+
 *      | 4 | 5 | 6 | 7 |
 *      +---+---+---+---+
 *      | 8 | 9 | … |   |
 *      +---+---+---+---+
 *  
 * It observes its internal list of children and it automatically reflows them
 * if the number of columns changes or if you add/remove some children from the list.
 * All the tiles of the grid are of the same size and stretch accordingly with the control.
 */
public class FlowGridPane extends GridPane
{
   // Properties for managing the number of rows & columns.
   private IntegerProperty rowsCount;
   private IntegerProperty colsCount;

   public final IntegerProperty colsCountProperty() { return colsCount; }
   public final Integer getColsCount() { return colsCountProperty().get(); }
   public final void setColsCount(final Integer cols) {
      // Recreate column constraints so that they will resize properly.
      ObservableList<ColumnConstraints> constraints = getColumnConstraints();
      constraints.clear();
      for (int i=0; i < cols; ++i) {
         ColumnConstraints c = new ColumnConstraints();
         c.setHalignment(HPos.CENTER);
         c.setHgrow(Priority.ALWAYS);
         c.setMinWidth(60);
         constraints.add(c);
      }
      colsCountProperty().set(cols);
      reflowAll();
   }

   public final IntegerProperty rowsCountProperty() { return rowsCount; }
   public final Integer getRowsCount() { return rowsCountProperty().get(); }
   public final void setRowsCount(final Integer rows) {
      // Recreate column constraints so that they will resize properly.
      ObservableList<RowConstraints> constraints = getRowConstraints();
      constraints.clear();
      for (int i=0; i < rows; ++i) {
         RowConstraints r = new RowConstraints();
         r.setValignment(VPos.CENTER);
         r.setVgrow(Priority.ALWAYS);
         r.setMinHeight(20);
         constraints.add(r);
      }
      rowsCountProperty().set(rows);
      reflowAll();
   }

   /// Constructor. Takes the number of columns and rows of the grid (can be changed later).
   public FlowGridPane(@NamedArg("cols")int cols, @NamedArg("rows")int rows) {
      super();
      colsCount = new SimpleIntegerProperty();  setColsCount(cols);
      rowsCount = new SimpleIntegerProperty();  setRowsCount(rows);
      getChildren().addListener(new ListChangeListener<Node>() {
         public void onChanged(ListChangeListener.Change<? extends Node> change) {
            reflowAll();
         }
      } );
   }

   // Helper functions for coordinate conversions.
   private int coordsToOffset(int col, int row) { return row*colsCount.get() + col; }
   private int offsetToCol(int offset) { return offset%colsCount.get(); }
   private int offsetToRow(int offset) { return offset/colsCount.get(); }

   private void reflowAll() {
      ObservableList<Node> children = getChildren();
      for (Node child : children ) {
         int offs = children.indexOf(child);
         GridPane.setConstraints(child, offsetToCol(offs), offsetToRow(offs) );
      }
   }
}

如你所见,我还使用了属性作为行数和列数,使用getter和setter。当使用setter更改行数或列数时,正在重新创建 GridPane 中的列/行约束的内部列表,以便它们正确调整大小,并且内容为当列数发生变化时被重新播放。

As you can see, I also used properties for the number of rows and columns, with getters and setters. When one uses a setter to change the number of rows or columns, the internal lists of column/row constraints inside GridPane are being recreated so that they resized properly, and the content is being reflown when the number of columns changes.

构造函数中还有 @NamedArg 注释,因此可以使用初始行数和行数创建此控件。 FXML文件中描述的布局中的列。

There are also @NamedArg annotations in the constructor, so that one could create this control with an initial number of rows & columns from the layout described in an FXML file.

因为它在某种程度上解决了我的问题,所以我将答案标记为已接受。但如果有人会发布更好的解决方案,那么我很乐意接受他的答案。

Since it pretty much "solved" my problem in a way, I'm marking my answer as accepted. But if anyone will post a better solution, I'll gladly accept his answer then.

也随时告诉我你是否有任何答案有关改进此代码的建议,或者如果您认为某些事情可以更优雅地完成,那么 - 正如我所说 - 它几乎不是完美的。

Also feel free to tell me if you have any suggestions about improvements of this code, or if you think something could be done more elegantly, since – as I said – it is hardly perfect yet.

这篇关于TilePane在JavaFX中自动拉伸切片的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆