Using extended custom class from FXML with custom ControllerFactory - user-interface

I want to embed my own custom controls that extend from other controls. I want to extend them within Java via extends, not from FXML via fx:root type="..."! As I want to use MVC pattern I use a custom ControllerFactory so that models can be passed to constructors of my controls (see AppStarter.java). In this simple example AppStarter.java loads container.fxml that includes two TextFields. These are bound to my model that is passed to them by ControllerFactory. If text is typed in 'textFieldIn' the model is updated and output will be displayed in 'textFieldOut'.
This works fine but I am not able to use a control that extends from another control. I extended TextField (see ExtendedTextField.java) and tried to embed it in fxml (see container.fxml) and got a "unable to instantiate"-error.
How can I solve this problem?
AppStarter.java
public class AppStarter extends Application {
private Model model;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
this.model = new Model("model data (string)");
Parent root = null;
try {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("/fxml/container.fxml"));
fxmlLoader.setControllerFactory((Class<?> type) -> {
try {
for (Constructor<?> c : type.getConstructors()) {
if (c.getParameterCount() == 1 && c.getParameterTypes()[0] == Model.class) {
return c.newInstance(model);
}
}
// default behavior: invoke no-arg constructor:
return type.newInstance();
} catch (Exception exc) {
throw new RuntimeException(exc);
}
});
root = fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
Scene scene = new Scene(root, 300, 100);
stage.setScene(scene);
stage.show();
}
}
container.fxml
<SplitPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml">
<Pane>
<fx:include source="textFieldIn.fxml" fx:id="textFieldIn"/>
</Pane>
<Pane>
<fx:include source="textFieldOut.fxml" fx:id="textFieldOut"/>
<!-- <ExtendedTextField/> **HERE I WANT TO EMBED MY EXTENDED CONTROL INSTEAD** -->
</Pane>
</SplitPane>
textFieldIn.fxml
<TextField xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
fx:controller="gui.TextFieldInController" fx:id="textFieldIn">
</TextField>
textFieldOut.fxml
<TextField xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
fx:id="textFieldOut" fx:controller="gui.TextFieldOutController">
</TextField>
ExtendedTextField.java
public class ExtendedTextField extends TextField {
public ExtendedTextField(Model model) {
this.setText("ExtendedTextField");
}
}

Related

Why when I try to set ActionBar.CustomView.SetBackgroundColor (Color.White); Does the application crash?

Why when I try to set ActionBar.CustomView.SetBackgroundColor (Color.White); Does the application crash?
I tried installing the ToolbarItems background via style, toolbar.axml via ResourceDictionary but not one of these methods works for me - I think because I'm too dumb.
Now this option is the easiest for me, since there is only one line of code, but somewhere you can see the conflict comes from style or toolbar.axml or something else.
ActionBar.CustomView.SetBackgroundColor (Color.White);
my app.xaml.cs
using System;
using System.IO;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using MyApp1.Services;
using MyApp1.Views;
namespace MyApp1
{
public partial class App : Application
{
static Data.TodoItemDatabase database;
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
protected override void OnStart()
{
// Handle when your app start
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
public static Data.TodoItemDatabase Database
{
get
{
if (database == null)
{
database = new Data.TodoItemDatabase(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "TodoSQLite.db3"));
}
return database;
}
}
public int ResumeAtTodoId { get; set; }
}
}
Solution 1:
You can set the style of TitleView in Forms . You can create a base Navigation Pageand set the style of TitleView
<NavigationPage.TitleView>
<StackLayout Orientation="Horizontal" BackgroundColor="White" >
//...
</StackLayout>
</NavigationPage.TitleView>
Solution 2
You can try creating a Custom Renderer and set it as the base class for all pages in the app. Within this custom renderer you can try to set the background color and other style as you want in CustomTitleView .
[assembly: ExportRenderer(typeof(Page), typeof(AndroidNavigationPageRenderer )]
public class AndroidNavigationPageRenderer : PageRenderer
{
private CustomTitleView _titleView;
public NavigationSearchRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
var activity = this.Context as FormsAppCompatActivity;
if (activity == null)
return;
var toolbar = activity.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
_titleView= new TitleView(Context);
toolbar.AddView(_titleView);
}
}

javafx User interface not updating?

I'm new to JavaFX.
I'm building a GUI for a project of mine, and I have a problem with it -
it seems that once I show my UI it stops updating.
In my case, it supposed to update a fill colour for a circle but it does not.
I tried to move the line:
someCircleId.setFill(color));
to be above the line:
Main.stage.show();
in the initialize function in my code and it did change the color of
the certain circle.
public class UIController implements Initializable {
public UIController() {
fillMap();
}
#Override
public void initialize(URL location, ResourceBundle resources) {
Main.stage.show();
}
#FXML
public void pressExitButton() {
Main.dropDBSchema();
System.exit(0);
}
public void changeCircleColor(String circleKey, Color color) {
Platform.runLater(
() -> {
Circle circle = this.circles.get(circleKey);
PauseTransition delay = new PauseTransition(Duration.seconds(10));
delay.setOnFinished(event -> circle.setFill(color));
delay.play();
}
);
}
}
And this is the class that uses these functions:
public class MonitoringLogicImpl implements MonitoringLogic {
public void updateUI(UUID flowUUID, String sender, String receiver, DATA_STATUS status){
String key = flowUUID.toString()+"_"+sender+"_"+receiver;
Color color = this.uiController.getColorAccordingToStatus(status);
this.uiController.changeCircleColor(key, color);
}
}
This is the FXML initialization :
public void start(Stage stage){
this.stage = stage;
FXMLLoader loader = new FXMLLoader();
try {
loader.setLocation(getClass().getResource("/UserInterface.fxml"));
this.root = loader.load();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e){
e.printStackTrace();
}
stage.setTitle("Troubleshooting project");
stage.setScene(new Scene(root, 900, 700));
stage.show();
}
I created my UI using Scene-Builder tool.

Adding Custom Dialog to Javafx with Spring DI

I have a gui built with javafx the controllers are loaded from fxml and created as Beans with spring so I can access my model. But that is predefined in fxml and loaded at start. Now I would like to load components, defined in fxml at runtime, but I could not yet find a working example, and no matter how I try it doesn't work.
So my question:
How can I create a custom Dialog (or any custom component) in runtime , that is loaded from .fxml and is aware of (Spring application) context?
Edit
So it loads but some fields are not initialized.
This is my custom DialogPane,
#Controller
#Scope("prototype")
public class NewProgramDialogPane extends DialogPane implements Initializable {
public static final ButtonType buttonTypeOk = new ButtonType("Create", ButtonBar.ButtonData.OK_DONE);
public static final ButtonType buttonTypeCancel = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
public TextField nameField;
public TextField data1Field;
public TextField data2Field;
public RegexValidator requiredField1;
public RequiredField requiredField2;
public RequiredField requiredField3;
public ErrorLabel duplicateProjectErrorLabel;
private SimpleBooleanProperty match = new SimpleBooleanProperty(false);
#Autowired
MainService mainService;
public NewProgramDialogPane() {
URL url = getClass().getClassLoader().getResource("com/akos/fxml/NewProgramDialog.fxml");
FXMLLoader loader = new FXMLLoader();
loader.setLocation(url);
loader.setRoot(this);
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void initialize(URL location, ResourceBundle resources) {
this.lookupButton(buttonTypeOk).addEventHandler(ActionEvent.ACTION, event -> {
if (!validate()) {
event.consume();
}
});
duplicateProjectErrorLabel.visibleProperty().bind(match);
}
public boolean validate() {
requiredField1.eval();
requiredField2.eval();
requiredField3.eval();
match.set(mainService.getPrograms().stream().anyMatch(
program -> program != null && program.getName().equals(nameField.getText())));
return !match.get() &&
!requiredField1.getHasErrors() &&
!requiredField2.getHasErrors() &&
!requiredField3.getHasErrors();
}
}
And when I try to read the nameField, it is null.
public class NewProgramDialog extends Dialog<Program> {
public NewProgramDialog() {
this.setDialogPane(new NewProgramDialogPane());
this.setTitle("New program");
this.initModality(Modality.APPLICATION_MODAL);
this.initStyle(StageStyle.DECORATED);
this.setResultConverter(param -> {
if (param == NewProgramDialogPane.buttonTypeOk) {
int x = 0;
return new Program(((NewProgramDialogPane) getDialogPane()).nameField.getText());
}
return null;
});
}
}
Define your custom dialog using the custom component FXML pattern; then just expose the custom component as a (prototype-scoped) spring bean.

FXMLLoader explained

I am using javafx combined with FXML.
I want to apply the MVC pattern. For that I want my Model.java class to be the model, which launches the View.fxml and the controller of that view would be viewController.java.
I need to bring Model.java and Controller.java to communicate at some point. So let's say ViewController.java looks this way:
public class ViewController implements Initializable {
private String parameter = "hello";
#FXML
private Label label;
#FXML
private Accordion acccord;
public String getParemeter() {
return this.parameter;
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
ViewController has a private string and its own methods.
And Model.java :
public class Model extends Application {
#Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("View.fxml") );
Parent root = loader.load(); // Here the View is loaded and the Contoller is created along.
loader.getController(); // ?
//Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
How can I access the ViewContoller parameters / methods (such as getPamareter() ) ?
I tried to get the controller with loader.getController() but it returns a generic type, what should I do with it, provided it has something to do with it? I went to the oracle documentation but I am not quite sure to understan, does getController() return an instance of my ViewController.java ?
HOw can I access the Model from the ViewController?
For instance a buton is triggered, the vieController would update a value in Model.java.

JavaFX 2.0 - create action handler for custom component in FXML

I want to add a custom action in my new component.
How to do this?
Example code:
Component
public class MyCustomComponent extends Region {
public MyCustomComponent(){
super();
this.setOnMouseClicked(new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent event) {
/* throw my custom event here and handle it in my FXML controller - but how? :-( */
}
});
}
}
Controller
public class MyController {
#FXML protected void myCustomAction(ActionEvent event) {
// do something
}
}
FXML:
<BorderPane fx:controller="fxmlexample.MyController"
xmlns:fx="http://javafx.com/fxml">
<top>
<MyCustomComponent onAction="#myCustomAction">
</MyCustomComponent>
</top>
</BorderPane>
Thx for help
You need to implement property in your custom component, which will store your action.
public class MyCustomComponent extends Region {
public MyCustomComponent(){
super();
// just to find out where to click
setStyle("-fx-border-color:red;");
setPrefSize(100, 100);
this.setOnMouseClicked(new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent event) {
onActionProperty().get().handle(event);
}
});
}
// notice we use MouseEvent here only because you call from onMouseEvent, you can substitute any type you need
private ObjectProperty<EventHandler<MouseEvent>> propertyOnAction = new SimpleObjectProperty<EventHandler<MouseEvent>>();
public final ObjectProperty<EventHandler<MouseEvent>> onActionProperty() {
return propertyOnAction;
}
public final void setOnAction(EventHandler<MouseEvent> handler) {
propertyOnAction.set(handler);
}
public final EventHandler<MouseEvent> getOnAction() {
return propertyOnAction.get();
}
}
and don't forget to add import in your fxml file:
<?import my.package.MyCustomComponent?>

Resources