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:
- For FXML expression binding, you need to expose properties from your class, not just the values themselves. This applies to
ObservableList
s as well as regular values. So yourTodoItemsVBox
class needs to expose aListProperty todoItemsProperty()
- FXML expression bindings (i.e.
${todoTasks}
) reference theFXMLLoader
'snamespace
, 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
How to Create a Dialogfragment Without Title
Call Getlayoutinflater() in Places Not in Activity
Android 4.2: Back Stack Behaviour with Nested Fragments
To Draw an Underline Below the Textview in Android
Android - Firebase Quickstart Email/Password Auth Demo Doesn't Work
Canonical Registration Id and Message Id Format
Android Usb Host API and Usb Storage
How to Apply Corner Radius to Linearlayout
Android Httpclient:Networkonmainthreadexception
Problem Loading Swf File in Android
Format Credit Card in Edit Text in Android
Android Design Considerations: Asynctask VS Service (Intentservice)
Center Align Title in Action Bar Using Styles in Android
What Is the Meaning of Android.Intent.Action.Main
Android Spinner Error:Android.View.Windowmanager$Badtokenexception: Unable to Add Window