how can I define a method for slider change in controller of a javaFX program? - slider

I want to create a method in a controller of a fxml file, I want this method acts when the slider is changing.
I have a fxml file like this:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="150.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Slider fx:id="mySlider" blockIncrement="0.1" layoutX="26.0" layoutY="32.0" majorTickUnit="0.5" max="1.0" minorTickCount="1" showTickLabels="true" showTickMarks="true" />
<TextField fx:id="textField" layoutX="100.0" layoutY="99.0" prefHeight="25.0" prefWidth="75.0" />
<Label layoutX="43.0" layoutY="103.0" text="Label" />
</children>
</AnchorPane>
and I want to have a controller like this
package paper.view;
import javafx.fxml.FXML;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
public class RootController
{
#FXML
private Slider mySlider;
#FXML
private TextField textField;
mySlider.valueProperty().addListener((observable, oldValue, newValue) -> {
textFieldOfEp.setText(Double.toString(newValue.doubleValue()) );
});
});
}
I want reflect the changes of slider in the textField.
like this picture:
How can I do this through a controller?
I don't want to do this in a start method of main class.
my start methos is like this:
public void start(Stage primaryStage) throws IOException
{
FXMLLoader loader=new FXMLLoader();
loader.setLocation(mainApp.class.getResource("view/Root.fxml"));
AnchorPane ap=loader.load();
Scene scene=new Scene(ap);
primaryStage.setScene(scene);
primaryStage.show();
}
the proposed answer worked.

You should register the listener in the controller's initialize() method:
package paper.view;
import javafx.fxml.FXML;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
public class RootController
{
#FXML
private Slider mySlider;
#FXML
private TextField textField;
public void initialize() {
mySlider.valueProperty().addListener((observable, oldValue, newValue) -> {
textField.setText(Double.toString(newValue.intValue()));
});
}
}
and make sure you either specify the controller in the FXML file:
<AnchorPane fx:controller="paper.view.RootController" prefHeight="150.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
or set the controller on the FXMLLoader directly in code.
This will automatically update the text field when the intValue() of the slider's value changes:

I just accidentally found that property onValueChange does the trick, needing a ChangeListener<Number> signature for the handler (Double works too):
<Slider onValueChange="#doStuff"/>
#FXML
private void doStuff(ObservableValue<Number> ovn, Number before, Number after) {
System.out.println(before+" "+after);
}

Related

Can't bind property for ContentView control

I have a ContentView called HomePageOrientationViewLoader that I want to use in a ContentPage called HomePage. HomePageOrientationViewLoader will either load a ContentView called HomePageLandscape if the orientation is in Landscape or a ContentView called HomePagePortrait if the orientation is in Portrait.
I am doing this so that I can load a different layout for landscape vs portrait so I can optimize my layout.
My issue is that I use dependency injection to inject my ViewModel HomeViewModel. I inject this into the ContentPage HomePage and I am attempting to pass the HomeViewModel from the ContentPage HomePage's XAML into the markup for HomePageOrientationViewLoader.
Here is my HomePage.xaml.cs code behind for my ContentPage:
using ScoreKeepersBoard.ViewModels;
namespace ScoreKeepersBoard.Views;
public partial class HomePage : ContentPage
{
public HomePage(HomeViewModel homeViewModelInj)
{
HomeViewModel = homeViewModelInj;
BindingContext = homeViewModelInj;
InitializeComponent();
}
HomeViewModel HomeViewModel { get; set; }
protected override void OnNavigatedTo(NavigatedToEventArgs args)
{
((HomeViewModel)BindingContext).CreateInitialGameTypes();
base.OnNavigatedTo(args);
}
}
Here is my HomePage.xaml for the ContentPage:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:ScoreKeepersBoard.ContentViews"
x:Class="ScoreKeepersBoard.Views.HomePage"
Title="HomePage">
<VerticalStackLayout>
<controls:HomePageOrientationViewLoader
BindingContext="{Binding HomeViewModel}"
>
</controls:HomePageOrientationViewLoader>
</VerticalStackLayout>
</ContentPage>
Here is my HomePageOrientationViewLoader.xaml.cs code behind for my ContentView:
using System.Reflection;
using ScoreKeepersBoard.ViewModels;
namespace ScoreKeepersBoard.ContentViews;
public partial class HomePageOrientationViewLoader : ContentView
{
public ContentView homePagePortraitContentView;
public ContentView homePageLandscapeContentView;
public HomeViewModel HomeViewModel { get; set; }
public HomePageOrientationViewLoader()
{
InitializeComponent();
try
{
//homeVM is always null
HomeViewModel homeVM = ((HomeViewModel)BindingContext);
string entryValue = homeVM.EntryValue;
homePagePortraitContentView = new HomePagePortrait(homeVM);
homePageLandscapeContentView = new HomePageLandscape(homeVM);
}
catch (Exception e)
{
string message = e.Message;
}
DeviceDisplay.Current.MainDisplayInfoChanged += Current_MainDisplayInfoChanged;
this.Content = DeviceDisplay.Current.MainDisplayInfo.Orientation == DisplayOrientation.Portrait ? homePagePortraitContentView : homePageLandscapeContentView;
}
private void Current_MainDisplayInfoChanged(object sender, DisplayInfoChangedEventArgs e)
{
if (e.DisplayInfo.Orientation == DisplayOrientation.Landscape)
{
this.Content = homePageLandscapeContentView;
}
else if (e.DisplayInfo.Orientation == DisplayOrientation.Portrait)
{
this.Content = homePagePortraitContentView;
}
else
{
this.Content = homePagePortraitContentView;
}
}
}
The code compiles and runs,
but the issue is that the BindingContext on HomePageOrientationViewLoader is always null. I would expect this to be set from the property defined in the ContentView's markup in HomePage ContentPage.
I also tried to set HomePageOrientationViewLoader's markup in ContentView's markup in HomePage ContentPage as just a normal Property defined on HomePageOrientationViewLoader as such:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:ScoreKeepersBoard.ContentViews"
x:Class="ScoreKeepersBoard.Views.HomePage"
Title="HomePage">
<VerticalStackLayout>
<controls:HomePageOrientationViewLoader
HomeViewModel = "{Binding HomeViewModel}">
</controls:HomePageOrientationViewLoader>
</VerticalStackLayout>
</ContentPage>
But this won't even compile. I get the following error:
/Users/RemoteCommand/Projects/ScoreKeepersBoard/Views/HomePage.xaml(13,13): Error XFC0009: No property, BindableProperty, or event found for "HomeViewModel", or mismatching type between value and property. (XFC0009)
This is obviously not true since HomeViewModel is a Property on both HomePage ContentPage and HomePageOrientationViewLoader ContentView.
I need to be able to have HomeViewModel in HomePageOrientationViewLoader and then to pass this on to HomePageLandscape and HomePagePortrait ContentViews so they can share the same ViewModel when the user switches between Portrait and Landscape view but I am so far not able to get this working. I would appreciate any help.
UPDATE
Jason commented: " If you want the control to have the same BindingContext as the page, you don't need to do anything - that should automatically inherit."
I just tried to remove the binding property on the control markup as such:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:ScoreKeepersBoard.ContentViews"
x:Class="ScoreKeepersBoard.Views.HomePage"
Title="HomePage">
<VerticalStackLayout>
<controls:HomePageOrientationViewLoader>
</controls:HomePageOrientationViewLoader>
</VerticalStackLayout>
</ContentPage>
And when my HomePageOrientationViewLoader page loads the constructor the HomeViewModel from HomePageOrientationViewLoader's BindingContext is still null:
UPDATE 2
Jason said that you should not assume the BindingContext is set in the constructor. He was right. When my HomePageOrientationViewLoader ContentView first loads the ContentView is not set but when I change the orientation and check to see what the BindingContext is as such:
private void Current_MainDisplayInfoChanged(object sender, DisplayInfoChangedEventArgs e)
{
HomeViewModel homeVM = ((HomeViewModel)BindingContext);
homePageLandscapeContentView.BindingContext = homeVM;
homePagePortraitContentView.BindingContext = homeVM;
...
}
homeVM which is set from the BindingContext of HomePageOrientationViewLoader is no longer null. I can then set the BindingContext of HomePageLandscape and HomePagePortrait and their ViewModels are active and bound. The only issue for me is that when the page initially loads HomePageLandscape and HomePagePortrait don't have their BindingContext set.
How can I get around this? Is there some event on ContentView that gets triggered when BindingContext is set so I could then override that event and bind HomePageLandscape and HomePagePortrait's Binding Context to the BindingContext of HomePageOrientationViewLoader?
ContentView has an OnBindingContextChanged method you can override

How to implement Treelist dropdown in Xamarin?

I want to implement something like below in the Xamarin forms.
I don't see such implementation with Objective C so can't find a way to render it through Native either.
How do I implement this with Xamarin forms?
https://demo.mobiscroll.com/javascript/list/display#
Working Solution: what follows will get your two-column UIPickerView rendered from Xamarin.Forms, belatedly updated from my original post
Option 1: Custom Renderer
I used the documentation #Junior Jiang shared in comments to inspire a quick Custom Renderer that should suit your needs.
First, make a placeholder control in your shared/Xamarin.Forms project:
namespace samples.core.Controls
{
public class MultiPickerControl : Xamarin.Forms.StackLayout
{
public MultiPickerControl()
{
// Just a placeholder
}
}
}
Then, make the renderer in your iOS project:
[assembly: ExportRenderer(typeof(samples.core.Controls.MultiPickerControl), typeof(samples.iOS.Controls.Picker.MultiPickerRenderer))]
namespace samples.iOS.Controls.Picker
{
public class MultiPickerRenderer : ViewRenderer
{
static UIPickerView pickerControl;
static DemoModel pickerModel = new DemoModel(new UILabel());
public MultiPickerRenderer()
{
pickerControl = new UIPickerView(
new CGRect(
UIScreen.MainScreen.Bounds.X - UIScreen.MainScreen.Bounds.Width,
UIScreen.MainScreen.Bounds.Height - 230,
UIScreen.MainScreen.Bounds.Width,
180))
{
Model = pickerModel
};
}
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if(Control != null)
{
Control.Add(pickerControl);
}
if(e.NewElement != null)
{
(e.NewElement as StackLayout).Children.Add(pickerControl);
}
}
}
}
You'll also need a UIPickerViewModel to give to your picker. This example is derived from the documentation above:
public class DemoModel : UIPickerViewModel
{
public Dictionary<string, string[]> options = new Dictionary<string, string[]>()
{
{ "America", new string[] { "Mexico", "USA" } },
{ "Europe", new string[] { "Germany", "France", "Italy"} },
{ "Asia", new string[] { "Korea", "Japan"} },
};
public override nint GetComponentCount(UIPickerView pickerView) => 2; // This determines how many columns are rendered
public override nfloat GetComponentWidth(UIPickerView picker, nint component) => component == 0 ? 120f : 160f;
public override nfloat GetRowHeight(UIPickerView picker, nint component) => 40f;
/// <summary>
/// Determines the number of rows to render in a component
/// </summary>
public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
{
if (component == 0)
return options.Keys.Count;
else
{
var driver = pickerView.SelectedRowInComponent(0);
return options.Values.ElementAt((int)driver).Length;
}
}
/// <summary>
/// Gets the display value for a row in a component
/// </summary>
public override string GetTitle(UIPickerView pickerView, nint row, nint component)
{
if (component == 0)
return options.Keys.ElementAt((int)row);
else
{
var driver = pickerView.SelectedRowInComponent(0);
return options.Values.ElementAt((int)driver).ElementAt((int)row);
}
}
[Export("pickerView:didSelectRow:inComponent:")]
public override void Selected(UIPickerView pickerView, nint row, nint component)
{
// Update the display for column 2 if the value of column 1 has changed
if (component == 0)
{
pickerView.ReloadComponent(1);
pickerView.Select(0, 1, false);
}
}
Lastly, reference your placeholder control in the markup (or code behind). Xamarin will resolve the platform implementation when your code is run.
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:samples.core.Controls;assembly=samples.core"
x:Class="samples.core.Views.EntryMoveNextView"
xmlns:ctrl="clr-namespace:samples.core.Controls;assembly=samples.core">
<ContentPage.Content>
<StackLayout Padding="15,25">
<controls:MultiPickerControl x:Name="MultiPicker"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
The result should look like this:
Be sure to hook into the events available
I've added the above example to a samples repo on my GitHub, if you'd like to browse as a unified source.
Option 2: Native View
You also might be able to accomplish this with a Native View (documentation). The gist would look something like this:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
x:Class="samples.core.Views.EntryMoveNextView"
xmlns:ctrl="clr-namespace:samples.core.Controls;assembly=samples.core">
<ContentPage.Content>
<StackLayout Padding="15,25">
<ios:UIPickerView>
<!-- Manipulate UIPickerView Properties Here -->
</ios:UIPickerView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
As with the linked documentation above, be sure to set GetComponentCount in your UIPickerViewModel to indicate how many columns you'll show.
Good luck!

Using Rg Plugins Popup with Xamarin Forms

I am very new to Xamarin Forms development and I need a popup dialog. I found exactly what I am looking for in https://github.com/rotorgames/Rg.Plugins.Popup, but I cannot for the life of me figure out how to use it. Could someone point me to a working example or provide some direction on use? The README.md on the site is not helping me much.
I want the the popup dialog to appear when a info button is clicked in the top navigation bar. All the popup needs is 1-2 buttons (and labels) for setting user settings.
This is for Xamarin.Forms: iOS and Android.
In simple steps:
Install the plugin in all the projects
Add the PopUp in your
Xaml
Use the methods they provide on the documentacion for Show/Hide the PopUp:
Task PushAsync(PopupPage page, bool animate = true)
Task PopAllAsync(bool animate = true)
They also provide a demo, check it:
https://github.com/rotorgames/Rg.Plugins.Popup/tree/master/src/Demo
Add a reference to the library, i.e. from nuget, to all projects.
Within your Android project, add this Rg.Plugins.Popup.Popup.Init(this, savedInstanceState); inside the MainActivity.cs OnCreate method, before Xamarin Forms Inits.
And the same for the iOS project, inside AppDelegate.cs FinishedLaunching method()
//Android
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Rg.Plugins.Popup.Popup.Init(this, savedInstanceState); /*Must add before the other Xamarin Inits*/
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
Xamarin.Forms.Forms.Init(this, savedInstanceState);
}
//iOS
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Rg.Plugins.Popup.Popup.Init(); /* place at the top*/
....
}
Add a new ContentPage (.xaml) to your Views directory.
<?xml version="1.0" encoding="utf-8" ?>
<pages:PopupPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup"
xmlns:animations="clr-namespace:Rg.Plugins.Popup.Animations; assembly=Rg.Plugins.Popup"
x:Class="MyProjectName.Views.MyContentPageName">
<pages:PopupPage.Animation>
<animations:ScaleAnimation
PositionIn="Center"
PositionOut="Center"
ScaleIn="1.2"
ScaleOut="0.8"
DurationIn="400"
DurationOut="300"
EasingIn="SinOut"
EasingOut="SinIn"
HasBackgroundAnimation="True"/>
</pages:PopupPage.Animation>
<StackLayout HorizontalAlignment="FillAndExpand" VerticalAlignment="FillAndExpand">
<!-- place your layout content here ....fx a close popup button -->
<Button Clicked="CloseBtn_Clicked" Text="Close" />
</StackLayout>
</pages:PopupPage>
In the ContentPage (PopupPage) code behind file, add using Rg.Plugins.Popup.Services; and inherit from the following
using Rg.Plugins.Popup.Services;
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyContentPageName: Rg.Plugins.Popup.Pages.PopupPage
{
public MyContentPageName()
{
InitializeComponent();
}
public void OnAnimationStarted(bool isPopAnimation)
{
// optional code here
}
public void OnAnimationFinished(bool isPopAnimation)
{
// optional code here
}
protected override bool OnBackButtonPressed()
{
// Return true if you don't want to close this popup page when a back button is pressed
return true;
}
// Invoked when background is clicked
protected override bool OnBackgroundClicked()
{
// Return false if you don't want to close this popup page when a background of the popup page is clicked
return false;
}
private async void CloseBtn_Clicked(object sender, EventArgs e)
{
await PopupNavigation.Instance.PopAsync(true);
}
}
From the .xaml.cs page, where you would like to open the popup, add this:
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Rg.Plugins.Popup.Contracts;
using Rg.Plugins.Popup.Services;
public partial class MyOtherPage : ContentPage
{
private IPopupNavigation _popup { get; set; }
private MyContentPageName _modalPage;
public MyOtherPage()
{
_popup = PopupNavigation.Instance;
_modalPage = new MyContentPageName();
}
protected override void OnAppearing()
{
base.OnAppearing();
_popup.Popped += Popup_Popped;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_popup.Popped -= Popup_Popped;
}
private async void Tapped_OpenModal(object sender, EventArgs e)
{
await _popup.PushAsync(_modalPage);
}
/// <summary> Triggered when the MyContentPageName popup is closed "PopAsync()" </summary>
private async void Popup_Popped(object sender, Rg.Plugins.Popup.Events.PopupNavigationEventArgs e)
{
/* add your logic here, if necessary */
}
}
*Note: If your modal simply displays static content, there is no need for a _popped event delegate within the OnAppearing()/OnDisappearing().

JavaFX: Collection binding from FXML and Item template

I decided to write this question due to missing tutorials and incomplete examples of the following problem. I will be glad if the answer to this question becomes a working example for solving similar problems.
Based on: JavaFX8 list bindings similar to xaml
TASK
Let's make a GUI application using a JavaFX technology (with FXML as a part of this technology for making graphical view) that show collection of users and for every user also his/her collection of cars for example. Let's also use JavaFX properties and bindig mechanisms for synchronize model (data) with GUI.
IMPLEMENTATION
I started with the creation of classes for the user and the car.
User.java
package example;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class User {
public StringProperty firstName;
public StringProperty lastName;
private ObservableList<Car> cars;
public User(String firstName, String lastName, Car[] cars) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
this.cars = FXCollections.observableArrayList(cars);
}
public String getFirstName() {
return firstName.get();
}
public StringProperty firstNameProperty() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public String getLastName() {
return lastName.get();
}
public StringProperty lastNameProperty() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
public ObservableList<Car> getCars() {
return cars;
}
}
Car.java
package example;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Car {
public StringProperty modelName;
public StringProperty manufacturer;
public Car(String modelName, String manufacturer) {
this.modelName = new SimpleStringProperty(modelName);
this.manufacturer = new SimpleStringProperty(manufacturer);
}
public String getModelName() {
return modelName.get();
}
public StringProperty modelNameProperty() {
return modelName;
}
public void setModelName(String modelName) {
this.modelName.set(modelName);
}
public String getManufacturer() {
return manufacturer.get();
}
public StringProperty manufacturerProperty() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer.set(manufacturer);
}
}
Than I prepare Controller for FXML GUI view with sample collection of users.
Controller.java
package example;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class Controller {
private ObservableList<User> users = FXCollections.observableArrayList(
new User("John", "Smith", new Car[] {
new Car("LaFerrari", "Ferrari"),
new Car("FG X Falcon", "Ford")
}),
new User("Ariel", "England", new Car[] {
new Car("ATS", "Cadillac"),
new Car("Camaro", "Chevrolet"),
new Car("458 MM Speciale", "Ferrari")
}),
new User("Owen", "Finley", new Car[] {
new Car("Corsa", "Chevrolet"),
})
);
}
And finally, I also include the generated Main.java.
package example;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Users -- example application");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
QUESTION
The challenge is make FXML GUI view that use JavaFX binding and show prepared collection of users from coresponding Controller. Probably using ListView.
Also I would like specify ListView item design/look in FXML and not in code – because it is part of GUI design. Appropriate JavaFX FXML alternative to .NET XAML ItemTemplate. What about ListCell?
Something in a way of this pseudocode:
users.fxml
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.Label?>
<GridPane fx:controller="example.Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
<ListView items="${users}">
<ListView.ItemTemplate>
<VBox>
<Label Text="${firstName}" />
<Label Text="${lastName}" Style="-fx-background-color: yellow" />
<ListView items="${cars}">
<ListView.ItemTemplate>
<HBox>
<Label Text="${manufacturer}" />
<Label Text=": " />
<Label Text="${modelName}" />
</HBox>
</ListView.ItemTemplate>
</ListView>
</VBox>
</ListView.ItemTemplate>
</ListView>
</GridPane>
I am always sceptical of questions of the form: "I am familiar with technology A, I am learning technology B and want to use it exactly the same way I use technology A". Each technology (toolkit, library, language, whatever...) has its own intended usage and idioms, and it's always better to use the technology the way it was intended. There will always be things you like and things you don't like about any given technology: if the latter outweigh the former, then just don't use it.
JavaFX is really designed that bindings are made in the controller, rather than in the FXML, and consequently there is no baked-in templating mechanism. So I would probably not really recommend this approach.
That said, you can probably achieve something akin to what you are trying to do with a little creativity and a little compromise. In particular, this solution involves:
moving the definitions of the cell "templates" to different FXML files, and
writing one (reusable) Java class to wire everything together.
This might not be the best or most efficient approach, but it should give you something to work from.
I first just refactored the data into a DataAccessor class, and instantiated it in the FXML, injecting it into the controller. This is a convenient way to give access to the items in the FXML, but there are other ways of doing this if it offends your MVC/MVP sensibilities :)
package example;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class DataAccessor {
private ObservableList<User> users = FXCollections.observableArrayList(
new User("John", "Smith", new Car[]{
new Car("LaFerrari", "Ferrari"),
new Car("FG X Falcon", "Ford")
}),
new User("Ariel", "England",new Car[]{
new Car("ATS", "Cadillac"),
new Car("Camaro", "Chevrolet"),
new Car("458 MM Speciale", "Ferrari")
}),
new User("Owen", "Finley", new Car[]{
new Car("Corsa", "Chevrolet")
})
);
public ObservableList<User> getUsers() {
return users ;
}
}
and
package example;
import javafx.fxml.FXML;
public class Controller {
#FXML
private DataAccessor dataAccessor ;
}
The basic idea is going to be to define a general cell factory implementation that creates cells whose graphic property is loaded from a specified FXML file:
package example;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javafx.beans.NamedArg;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.util.Callback;
public class FXMLListCellFactory implements Callback<ListView<Object>, ListCell<Object>> {
private final URL fxmlSource ;
public FXMLListCellFactory(#NamedArg("fxmlSource") String fxmlSource) throws MalformedURLException {
this.fxmlSource = new URL(fxmlSource) ;
}
#Override
public ListCell<Object> call(ListView<Object> lv) {
return new ListCell<Object>() {
#Override
protected void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
} else {
try {
FXMLLoader loader = new FXMLLoader(fxmlSource);
loader.getNamespace().put("item", item);
setGraphic(loader.load());
} catch (IOException e) {
e.printStackTrace();
setGraphic(null);
}
}
}
};
}
}
And now you can create a FXML file that uses this. This version has a "master-detail" UI (list of users, select a user and the second list shows their list of cars).
sample.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.HBox?>
<?import example.DataAccessor?>
<?import example.FXMLListCellFactory?>
<?import javafx.scene.control.ListView?>
<HBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="example.Controller" spacing="10">
<fx:define>
<DataAccessor fx:id="dataAccessor" />
</fx:define>
<ListView fx:id="userList" items="${dataAccessor.users}">
<cellFactory>
<FXMLListCellFactory fxmlSource="#userListCell.fxml"/>
</cellFactory>
</ListView>
<ListView fx:id="carList" items="${userList.selectionModel.selectedItem.cars}">
<cellFactory>
<FXMLListCellFactory fxmlSource="#carListCell.fxml"/>
</cellFactory>
</ListView>
</HBox>
This references two other FXML files, one for each of the list views:
userListCell.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Label?>
<?import javafx.beans.property.SimpleObjectProperty?>
<HBox xmlns:fx="http://javafx.com/fxml/1" spacing="5">
<Label text="${item.firstName}"/>
<Label text="${item.lastName}"/>
</HBox>
and carListCell.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.HBox?>
<?import javafx.beans.property.SimpleObjectProperty?>
<?import javafx.scene.control.Label?>
<HBox xmlns:fx="http://javafx.com/fxml/1">
<Label text="${item.manufacturer+' '+item.modelName}"/>
</HBox>
The Main and model classes are exactly as in your question.
There's probably a way to do this without factoring the FMXL for the cell graphic into separate files, e.g. persuading (somehow...) the FXMLLoader to parse the content as a literal string and pass it to the cell factory implementation; then in the cell factory implementation convert the string to a stream and use the FXMLLoader.load(...) method taking a stream. I'll leave that as an exercise to the reader though.
Finally note that loading and parsing an FXML file in the cell's updateItem(...) method is not a particularly efficient approach; I could not find a way to work around this quickly, though it may be possible too.

Injecting an element of an Arraylist in Scene Builder

I have an arraylist thats lentgh is dynamic and that contains a couple of labels, e.g.
In the controller class I've declared
#FXML private ArrayList<Label> a;
initialization within the controller class...
#Override
public void initialize(URL url, ResourceBundle rb) {
b.add(new Label("label 1"));
b.add(new Label("label 2"));
b.add(new Label("label 3")); //etc...
}
Do I have the chance to inject each element of a in the Scene Builder via fx:id? a.get(0) instead of a...
You can do the reverse, i.e. declare an ObservableList of Label in FXML and inject each of the elements into the controller.
<FXCollections fx:factory="observableArrayList">
<Label fx:id="light" text="Light" />
<Label fx:id="dark" text="Dark" />
</FXCollections>
In the controller :
public class YourControllerName implements Initializable {
...
#FXML
private Label light;
#FXML
private Label dark;
...
}