How to Pass Custom Component Parameters in Java and Xml

How to pass custom component parameters in java and xml

(Full disclosure: This question is an offshoot of Creating custom view)

You can create constructors beyond the three standard ones inherited from View that add the attributes you want...

MyComponent(Context context, String foo)
{
super(context);
// Do something with foo
}

...but I don't recommend it. It's better to follow the same convention as other components. This will make your component as flexible as possible and will prevent developers using your component from tearing their hair out because yours is inconsistent with everything else:

1. Provide getters and setters for each of the attributes:

public void setFoo(String new_foo) { ... }
public String getFoo() { ... }

2. Define the attributes in res/values/attrs.xml so they can be used in XML.

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyComponent">
<attr name="foo" format="string" />
</declare-styleable>
</resources>

3. Provide the three standard constructors from View.

If you need to pick anything out of the attributes in one of the constructors that takes an AttributeSet, you can do...

TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.MyComponent);
CharSequence foo_cs = arr.getString(R.styleable.MyComponent_foo);
if (foo_cs != null) {
// Do something with foo_cs.toString()
}
arr.recycle(); // Do this when done.

With all that done, you can instantiate MyCompnent programmatically...

MyComponent c = new MyComponent(context);
c.setFoo("Bar");

...or via XML:

<!-- res/layout/MyActivity.xml -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:blrfl="http://schemas.android.com/apk/res-auto"
...etc...
>
<com.blrfl.MyComponent
android:id="@+id/customid"
android:layout_weight="1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center"
blrfl:foo="bar"
blrfl:quux="bletch"
/>
</LinearLayout>

Additional Resource - https://developer.android.com/training/custom-views/create-view

Use Custom View In XML Layout

How can I use this view in XML layout?

..

 <pacakge_of_class.Loading 
android:id="@+id/y_view1"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />

http://developer.android.com/guide/topics/ui/custom-components.html

There is a form of the constructor that are called when the view is created from code and a form that is called when the view is inflated from a layout file. The second form should parse and apply any attributes defined in the layout file.

How can I pass the parameters 

https://stackoverflow.com/a/4495745/804447

Error referencing an inner class View in layout/main.xml

<view class="Your_package.MainClass$Loading" />

Custom component namespace invokation

Your custom component is situated in the package com.site.package.extra(from the code you posted) so you could use it in the xml layout with:

<com.site.package.extra.Extra // ... other attributes

or with:

<view  class="com.site.package.extra.Extra"
// ... other attributes />

The namespace for the custom attributes:

xmlns:extra="http://schemas.android.com/apk/res/com.site.package"

and to use them:

extra:count="3"

Set custom FXML properties as parameters for custom javafx component

First, note that the @FXML annotation on rowsFromPrefs is serving no purpose. @FXML causes a value to be injected for the field when the FXML file for which the current object is the controller has an element with an fx:id attribute whose value matches the field name. Since TableBlock.fxml has no element with fx:id="rowsFromPrefs", this annotation isn't doing anything.

When the FXMLLoader that is loading View.fxml encounters the <TableBlock> element, it creates a TableBlock instance by calling its constructor. Then it will set the values specified by the attributes. So your FXML element

<TableBlock rowsFromPrefs="2" id="IDDQD"/>

is essentially equivalent to

TableBlock tableBlock = new TableBlock();
tableBlock.setRowsFromPrefs("2");
tableBlock.setId("IDDQD");

Of course, the constructor for TableBlock just does what the code says to do: it creates a FXMLLoader, sets the root and controller for that FXMLLoader, and then calls load(). The load process for that FXMLLoader will set the @FXML-injected fields on the controller (the TableBlock object whose constructor is executing), and then call initialize().

So initialize() is invoked as part of the call to FXMLLoader.load() that is in the TableBlock constructor; of course this all happens before setRowsFromPrefs("2"); is invoked.

So in summary, TableBlock.initialize() is called after TableBlock.fxml has been parsed, and any elements defined there injected into their corresponding @FXML-annotated fields, but this happens before View.fxml has been loaded.

One way to fix this is to pass rowsFromPrefs to the TableBlock constructor. To do this, use the @NamedArg annotation:

public class TableBlock extends VBox{

private final String rowsFromPrefs;

@FXML
private Label label;

public TableBlock(@NamedArg("rowsFromPrefs") String rowsFromPrefs) {

this.rowsFromPrefs = rowsFromPrefs ;
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("TableBlock.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
}

@FXML
public void initialize() {
this.table = createTable(rowsFromPrefs);
}

public String getRowsFromPrefs() {
System.out.println("getRowsFromPrefs");
return rowsFromPrefs;
}

}

Now your attribute in the FXML will be passed to the constructor instead of to a set method, so rowsFromPrefs will be initialized before you call fxmlLoader.load(), as required.

The other option, of course, would simply be to move the code from the initialize() method to the setRowsFromPrefs(...) method. I would use the option described above if you intend rowsFromPrefs to be fixed for each TableBlock instance, and use the second option only if you want to be able to change rowsFromBlocks during the lifecycle of an individual TableBlock instance.

How to pass data binding variable as xml attribute to custom view

Try using @BindingAdapter("...") instead of the @Bindable annotation.

I made a sample repo that reflects and solves your problem, maybe have a look.

https://github.com/phamtdat/BindingAdapterSample

Pass/bind a collection to a custom component (extended from vbox) in FXML via parameters

There are two issues with the code as you have it:

  1. For FXML expression binding, you need to expose properties from your class, not just the values themselves. This applies to ObservableLists as well as regular values. So your TodoItemsVBox class needs to expose a ListProperty todoItemsProperty()
  2. FXML expression bindings (i.e. ${todoTasks}) reference the FXMLLoader's namespace, not the controller. The controller is automatically injected into the namespace (with key "controller"), so, given that the task list is stored in your controller (which is not necessarily a good idea) you can use ${controller.todoTasks} here.

Here's a minimal, complete version of your app which works.

A basic TodoItem.java:

public class TodoItem {

private final String name ;
public TodoItem(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
}

A TodoItemsVBox that exposes the list as a property:

import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;

public class TodoItemsVBox extends VBox {

private ListProperty<TodoItem> todoItems = new SimpleListProperty<>();

public TodoItemsVBox() {
// not efficient but works for demo:
todoItems.addListener((Change<? extends TodoItem> c) -> rebuildView());
}

private void rebuildView() {
getChildren().clear();
todoItems.stream()
.map(TodoItem::getName)
.map(Label::new)
.forEach(getChildren()::add);
}

public ListProperty<TodoItem> todoItemsProperty() {
return todoItems ;
}

public ObservableList<TodoItem> getTodoItems() {
return todoItemsProperty().get() ;
}

}

A simple controller:

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;

public class TodoListController {
private ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList();

// not actually needed...
@FXML
private TodoItemsVBox todoItemsVBox;

@FXML
private TextField input ;

public ObservableList<TodoItem> getTodoTasks() {
return todoTasks;
}

@FXML
private void addTask() {
todoTasks.add(new TodoItem(input.getText()));
}
}

The FXML file (TodoList.fxml):

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>

<?import org.jamesd.examples.TodoItemsVBox ?>

<BorderPane prefHeight="600" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.todolist.controller.TodoListController"
>

<top>
<HBox spacing="10.0">
<TextField fx:id="input" promptText="Enter todo task" HBox.hgrow="ALWAYS" onAction="#addTask"/>
<Button onAction="#addTask" text="Add" HBox.hgrow="ALWAYS" />
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</HBox>
</top>
<center>
<ScrollPane fitToWidth="true" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${controller.todoTasks}"/>
</ScrollPane>
</center>
</BorderPane>

And finally the application class:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class TodoApp extends Application {

@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("TodoList.fxml"));
Scene scene = new Scene(loader.load());
primaryStage.setScene(scene);
primaryStage.show();
}

public static void main(String[] args) {
Application.launch(args);
}

}

Really, a controller is no place to be storing data; you should have a separate model class to do that, which is shared between the controller and view. This is reasonably straightforward to do here; you just need to do a little more work with the FXMLLoader (namely putting the model in the namespace, and manually creating and setting the controller).

For example:

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

public class TodoModel {

private ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList();

public ObservableList<TodoItem> getTodoTasks() {
return todoTasks;
}
}

Then your controller becomes:

import javafx.fxml.FXML;
import javafx.scene.control.TextField;

public class TodoListController {

// not actually needed...
@FXML
private TodoItemsVBox todoItemsVBox;

@FXML
private TextField input ;

private TodoModel model ;

public TodoModel getModel() {
return model;
}

public void setModel(TodoModel model) {
this.model = model;
}

@FXML
private void addTask() {
model.getTodoTasks().add(new TodoItem(input.getText()));
}
}

Modify the FXML to use

<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${model.todoTasks}"/>

And finally assemble the application with

public void start(Stage primaryStage) throws Exception {

TodoModel model = new TodoModel();

FXMLLoader loader = new FXMLLoader(getClass().getResource("TodoList.fxml"));
loader.getNamespace().put("model", model);
Scene scene = new Scene(loader.load());

TodoListController controller = loader.getController();
controller.setModel(model);

primaryStage.setScene(scene);
primaryStage.show();
}

The advantage to this approach is that your data are now separated from the UI (both view and controller), which becomes essential if you want to access the same data in another part of the UI (which would use another FXML and another controller).



Related Topics



Leave a reply



Submit