JavaFX application has a tree menu and clicking on a menu item loads an fxml file image attached
Some of the FXMLs are nested and are taking up to 2-3 seconds to load. I would like to show a progress indicator while FXMLs are getting loaded and initialized. Controller's initialization code can be executed as a Task and bind it's progress to a progress bar control, but the problem is that some of code needs to be executed in FX Application thread using Platform.runLater(). If I do that, there is no way to know when the initialization is completed.
Is there a way to handle this seemingly simple task ?
I cannot share the controller code which is taking long time to load, but for the purpose of this question, please assume the following initialization code is the culprit. I cannot execute this block of code in a Task thread as UI changes cannot be made from threads other than JavaFX Application thread. If I wrap the part of code that changes UI inside Platform.runLater(), task may finish it's execution even before the UI changes are completed.
public void initialize(URL url, ResourceBundle rb) {
if (webEngine == null) {
webEngine = helpView.getEngine();
}
try {
Path filepath = Paths.get("docs/index.html");
webEngine.load(filepath.toUri().toString());
} catch (Exception ex) {
Alert alert = new Alert(Alert.AlertType.WARNING, "Failed to read doc files", ButtonType.OK);
alert.show();
}
}
Raj
Related
I have written an application that uses background workers for long running tasks. At times, after the task is completed, the application will freeze. It doesn't do it right away, it will do it after the application sits idle for a little bit of time.
To try to find out where it is hanging, in my development environment I ran it and waited for it to freeze. I then went to Debug > Break All. It is hanging in the Main() method in Program.cs:
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Main());
}
}
The Application.Run line is highlighted as where the application is hung. When I hover my cursor over the carat in the left border I get a tool tip saying "This is the next statement to execute when this thread returns from the current function."
In looking at this code I realized that it is calling the "main" form of the application, which I named "Main." So my first question is does this matter since the current method is named "Main" also? If so, what are the ramifications of renaming the form, if that is possible?
If that is not an issue, then it would go back to the background worker I would imagine. The application never freezes if those long running tasks are never ran. I know that you should never try to access the UI thread from a background worker thread and I don't think I'm doing that but here is some code that hopefully someone may spot something:
First I start the thread from the UI thread passing in an argument:
bgwInternal.RunWorkerAsync(clients)
In the DoWork method it calculates and creates invoices for the passed in argument (clients). It creates PDF files and saves them to disk. None of that work tries to access the UI. It does use the ProgressChanged event handler to update a progress bar and a label in the UI:
private void bgwInternal_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pgbProgress.Value = e.ProgressPercentage;
lblProgress.Text = e.ProgressPercentage.ToString();
}
And finally the RunWorkerCompleted event handler:
private void bgwInternal_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show("Error occurred during invoice creation.\n\r\n\rError Message: " + e.Error.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else if (!e.Cancelled)
{
MessageBox.Show("Invoice Creation Complete", "Complete", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
MessageBox.Show("Invoice Creation Cancelled", "Cancelled", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
btnCreateInv.Enabled = true;
btnClose.Enabled = true;
btnCancel.Enabled = false;
}
Could it be hanging because I'm accessing UI elements in this event handler?
One final note, I was using Application.DoEvents():
while (bgwInternal.IsBusy)
{
Application.DoEvents();
}
But I commented that out to see if it would make a difference and it did not.
Not having a lot of multithreading experience I chose to use background worker threads because they are simple and straightforward. Other than using Debug > Break All I really don't know how to track down the exact reason this is happening.
Any thoughts / ideas would be greatly appreciated.
Thanks.
Problem:
I am working on a client server desktop application using JavaFx, everything works fine except that I have found that when I open a new stage clicking a button on the Home Page, the new Stage loads data only the first time I open it. The initialize method of the new stage sends a request to server and receives back an object whose fields are loaded on the new stage. None exception is thrown, the server always send the object correctly (tested by printing its vales on console).
My suspicion is that when I click the button to open a new stage, it creates a new instance of the FXML Loader each time. What I don't get is dued to the fact that the execution cycle is always the same both the first time I open the stage and the following ones.
I finally tried to insert a button in the second window which, when clicked, loads the object on the window, and this works correctly also when I open the window multiple times, but obviously I don't like this solution, which would force the user to click the button to retreive data.
Here is the HomePage Controller method that allows to open the new stage:
#FXML
void showSecondView(MouseEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("/View_FXML/generaRichiesta.fxml"));
Pane secondPageLayout = loader.load();
SecondController secCon = loader.getController();
stage = new Stage();
stage.setScene(new Scene(secondPageLayout));
stage.setTitle("Second Stage");
stage.show();
}
Here is the Initialize method for the Second Stage
public void initialize(URL url, ResourceBundle resourceBundle) {
//sending request to server and calling the following function to update the
//view withe the received object
Platform.runLater(
() -> {
//setting the view node "textField" with the object received from server
//if I print this value on console I always get the object correctly but
//it is loaded only the first time. When i call this function from a button it always
//works but I should do it manually
textField.setText().receivedObject.toString();
}
);
}
I really don't get what the problem could be, I think it may be because when I create every time and FXMLLoader object.
Any suggestions?
Thanks
I found the error, basically I'm using the Observable Pattern, every time I opened a controller, I associated the controller object in a hashmap and didn't accept other insertion of objects of the same class. Also each time I closed the second stage, I omitted to remove to remove the first instance of the controllere from the map, so the Observable Pattern sent notifications of changes only and always to the first instance of the controller created and not to the subsequent ones that were created by reopening the window
I have 2 windows (login and main) with appropriate controllers.
I got this piece of code. This is located in LoginController.java. When I run app, it will open login window from /fxml/login.fxml where the LoginController is set with fx:controller. Then there is Sign In button. Method signIn() is asociated with this button. When i click it, it will close Login window, get controller of main window and open main window from specific fxml file (/fxml/main.fxml). Meanwhile it will get strings from Login window's text fields and pass them to the MainController (those credentials are used in execution of SQL statements).
public void signIn(ActionEvent event) {
MngApi.closeWindow(btn); //closes parent window of specified node
try{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/main.fxml"));
Parent root1 = fxmlLoader.load();
Stage stage = new Stage();
stage.setTitle("PrintED");
stage.setScene(new Scene(root1, 1058, 763));//1058, 763
MainController ctrl = fxmlLoader.getController();
ctrl.setCredentials(uName.getText(), uPasswd.getText(),
ipAddress.getText(), dbName.getText());
stage.show();
}catch (IOException e){
}
}//signIn end
Now this is the problem: When i click Sign In button, the signIn() method will execute, Login window will close and SUCCESSFUL BUILD will pop up. No other window will open. After debugging I found out that program will stop at this line (everything before is done)
Parent root1 = fxmlLoader.load();
No error, no information, only successfull build. The most mysterious thing for me is that it worked before! Login wondow got closed and main windows appeared. I only added some new methods, action events and stuff. I made no changes to LoginController and fxml file. I only made changes to MainController (added methods and action event for buttons in main.fxml).
I have no idea what's bad and i am clueless. Please help!
Okay so I found out whats wrong. The error was in initilize() method. There i put a method which should initialize columns in my TreeTableView. I thought that when i run program, initialize wil execute (after Controller constructor) and it will run code line by line. so i put there initializeColumns() method, si it will execute first and only once. Looked like this:
public void initialize(){
initializeOrdersColumns();
//this listener will load (refresh) orders automaticaly when Orders Tab is selected
tabOrders.setOnSelectionChanged((event) -> {
if (tabOrders.isSelected()) {
List<TreeItem<Order>> allOrdersList = new ArrayList<>();
stackItems(allOrdersList, root);
root = new TreeItem<>();
ttwOrders.setRoot(root);
ttwOrders.setShowRoot(false);
}
});
//When we click refresh button, orders tab will reload Orders
btnOrdersRefresh.setOnAction((event) -> {
List<TreeItem<Order>> allOrdersList = new ArrayList<>();
stackItems(allOrdersList, root);
root = new TreeItem<>();
ttwOrders.setRoot(root);
ttwOrders.setShowRoot(false);
});
}//end of initialize()
After I deleted content of initialize() method, everything went well, so it was obvious that error code is in there. Then I commented all code except first method (initializeColumns()) and program went wrong. When I commented only initializeColumns() method the program went well again. So it was clear, initializeColumns() cannot be in initialize() method. Don't know why yet, but i will find out.
I am making a simple networking game. The scene has a button named 'ReloadButton' and i am trying to find this button and add a listener to it through my script attached on the player.
private Button reloadBtn;
void Start()
{
GameObject tempGO = GameObject.Find("ReloadButton");
if (tempGO != null)
{
reloadBtn = tempGO.GetComponent<Button>();
reloadBtn.onClick.AddListener(weaponManager.Reload);
}
}
I am doing it this way because direct referencing of 'ReloadButton' to the script through public Buttonvariable is not possible.
The code works fine on the server, the listener is also added correctly. but on the client, the GameObject.Find("ReloadButton") throws a NullReferenceException.
Seems like the client cannot find the Button itself.
I cannot proceed further in my project without solving this problem, I hope some of you can point me towards the problem.
I am showing activity indicator after clicking login button until redirecting the user to another page, to make them understand some progress is going on. But after clicking login button Activity Indicator is not shown immediately, it is shown after few seconds,
Why its so? To reduce that delay only I am putting activity indicator...
My Code:
async void loginButtonGesture_Tapped(object sender, EventArgs e)
{
Device.BeginInvokeOnMainThread(() =>
{
loadingPanel.IsRunning = true;
loadingPanel.IsVisible = true;
});
}
Does the method have to be async void? It seems like this particular scheduling anything on the main thread shouldn't need to be async. Try that to see if it changes anything. Also you could try to set breakpoints on the Device.BeginInvokeOnMainThread line, and the loadingPanel.IsRunning... line to see where the delay happens.
First of all, loginButtonGesture_Tapped() event handler is triggered by UI thread so you don't need to use Device.BeginInvokeOnMainThread(), it is already in UI thread. But since you used Device.BeginInvokeOnMainThread() here, the reason for the delay is because on Android, your code inside of BeginInvokeOnMainThread() is added to MainLooper's message queue,(your code is not executed immediately) and is executed when the UI thread is scheduled to handle its messages.
The detailed answer can be found in Xamarin document:
For iOS:
IOSPlatformServices.BeginInvokeOnMainThread() Method simply calls NSRunLoop.Main.BeginInvokeOnMainThread
public void BeginInvokeOnMainThread(Action action)
{
NSRunLoop.Main.BeginInvokeOnMainThread(action.Invoke);
}
https://developer.xamarin.com/api/member/Foundation.NSObject.BeginInvokeOnMainThread/p/ObjCRuntime.Selector/Foundation.NSObject/
You use this method from a thread to invoke the code in the specified object that is exposed with the specified selector in the UI thread. This is required for most operations that affect UIKit or AppKit as neither one of those APIs is thread safe.
The code is executed when the main thread goes back to its main loop for processing events.
For Android:
Many People think on Xamarin.Android BeginInvokeOnMainThread() method use Activity.runOnUiThread(), BUT this is NOT the case, and there is a difference between using runOnUiThread() and Handler.Post():
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);//<-- post message delays action until UI thread is scheduled to handle messages
} else {
action.run();//<--action is executed immediately if current running thread is UI thread.
}
}
The actual implementation of Xamarin.Android BeginInvokeOnMainThread() method can be found in AndroidPlatformServices.cs class
public void BeginInvokeOnMainThread(Action action)
{
if (s_handler == null || s_handler.Looper != Looper.MainLooper)
{
s_handler = new Handler(Looper.MainLooper);
}
s_handler.Post(action);
}
https://developer.android.com/reference/android/os/Handler.html#post(java.lang.Runnable)
As you can see, you action code is not executed immediately by Handler.Post(action). It is added to the Looper's message queue, and is handled when the UI thread's scheduled to handle its message.