使用动态数据设计JavaFX自定义控件

时间:2018-08-03 18:48:40

标签: java user-interface javafx custom-controls

我正在尝试在JavaFX中创建自己的控件。 我的问题是我的控件不是静态的(例如,标签,按钮...)而是动态的(例如,ListView),因此会不断添加和删除新记录。 我不确定如何在JavaFX中实现它,因此,为了获得一种感觉,我想重新创建一个类似于ListView的控件。 在下面,我写了一个小例子。

  • MyList
    与ListView类似的实际控件。
  • MyListItem
    存储在MyList中的对象仅包含一个要显示的字符串。
  • MyListSkin
    负责图形表示的皮肤。

该示例可以正常工作,但感觉有点脏。 为了使所有内容保持简洁,我不想使用CSS和FXML。

我该如何实施此清理程序,尤其是在更复杂的情况下?

  • 如何将数据,逻辑和图形分开?
    • 我应该使用BehaviorBase吗,如果要怎么用?
  • 如何跟踪MyListItem对象及其对应的图形表示/节点?
    • 我应该如何处理初始化?
    • 如何使它们保持同步?

Example application screenshot.

MyList.java

public class MyList extends Control
{
    private ListProperty<MyListItem> mItems;

    public MyList()
    {
        mItems = new SimpleListProperty<>(FXCollections.observableArrayList());
    }

    @Override
    protected Skin<?> createDefaultSkin() 
    {
        return new MyListSkin(this);
    }

    public ObservableList<MyListItem> getItems()
    {
        return mItems.get();
    }

    public void setItems(ObservableList<MyListItem> pItems)
    {
        mItems.set(pItems);
    }

    public ListProperty<MyListItem> itemsProperty()
    {
        return mItems;
    }
}

MyListItem.java

public class MyListItem 
{
    private StringProperty mName;


    public MyListItem(String pName)
    {
        mName = new SimpleStringProperty(pName);
    }


    public String getName()
    {
        return mName.get();
    }

    public void setName(String pName)
    {
        mName.set(pName);
    }

    public StringProperty nameProperty()
    {
        return mName;
    }
}

MyListSkin.java

public class MyListSkin extends SkinBase<MyList>
{
    private VBox mVBox;
    private Map<MyListItem, Label> mNodes;

    protected MyListSkin(MyList pControl)
    {
        super(pControl);

        mVBox = new VBox();
        mNodes = new HashMap<>();

        getChildren().add(mVBox);

        getSkinnable().getItems().addListener(new ListChangeListener<MyListItem>()
        {
            @Override
            public void onChanged(Change<? extends MyListItem> pChange)
            {
                removeItems(pChange.getRemoved());
                addItems(pChange.getAddedSubList());
            }
        });

        getSkinnable().itemsProperty().addListener(new ChangeListener<ObservableList<MyListItem>>() 
        {

            @Override
            public void changed(ObservableValue<? extends ObservableList<MyListItem>> pObservable,
                    ObservableList<MyListItem> pOldValue, ObservableList<MyListItem> pNewValue) 
            {
                clearItems();
                addItems(pNewValue);
            }
        });

        addItems(getSkinnable().getItems());
    }

    private void addItems(List<? extends MyListItem> list)
    {
        for(MyListItem lItem : list)
        {
            Label lLabel = new Label();
            lLabel.setMinWidth(Control.USE_PREF_SIZE);
            lLabel.setPrefWidth(200);
            lLabel.textProperty().bind(lItem.nameProperty());
            mVBox.getChildren().add(lLabel);
            mNodes.put(lItem, lLabel);
        }
    }

    private void removeItems(List<? extends MyListItem> list)
    {
        for(MyListItem lItem : list)
        {
            mVBox.getChildren().remove(mNodes.get(lItem));
            mNodes.remove(lItem);
        }
    }

    private void clearItems()
    {
        for(Map.Entry<MyListItem, Label> lEntry : mNodes.entrySet())
        {
            mVBox.getChildren().remove(lEntry.getValue());
            mNodes.remove(lEntry.getKey());
        }
    }
}

MainApplication.java

public class MainApplication extends Application
{
    @Override
    public void start(Stage pPrimaryStage)
    {
        MyList lControl = new MyList();
        lControl.getItems().add(new MyListItem("Test 1"));
        lControl.getItems().add(new MyListItem("Test 2"));
        lControl.getItems().add(new MyListItem("Test 3"));
        lControl.getItems().add(new MyListItem("Test 4"));

        Pane lRoot = new Pane();
        lRoot.getChildren().add(lControl);

        pPrimaryStage.setScene(new Scene(lRoot, 800, 800));
        pPrimaryStage.show();
    }


    public static void main(String[] pArguments)
    {
        launch(pArguments);
    }
}

1 个答案:

答案 0 :(得分:0)

  

如何将数据,逻辑和图形分开?

单独的类就是这样做的,MyListItem是您的数据,MyList是您的逻辑,MyListSkin是您的图形。

  
    

我应该使用BehaviorBase吗?

  

取决于您,如果您愿意,我建议阅读ListViewBehavior并可能从中扩展。还要检查CellBehaviorBase及其子类。

  

如何跟踪MyListItem对象及其对应的图形表示/节点?

不太清楚您要问的是什么,请在Control中跟踪它们,并在Skin中跟踪图形表示,好像您在做什么。

  
    

我应该如何处理初始化?

  

您的工作方式似乎不错。

  
    

如何使其保持同步?

  

您可以通过其他方式使其他所有内容保持同步,监听Control中的更改并更新Skin,就像您在做的那样,这行不通吗?


如果您要为列表中的各个Label自定义图形,则可以通过MyListSkin处理某些一般情况下的图形,例如。为偶数或奇数索引元素设置PseudoClass