Is it possible to cancel the creation of a view in the #PostConstruct phase? I have:
#PostConstruct
public void createPartControl(Composite parent) {
try {
// do something where an exception is thrown
} catch (Exception e) {
// I want to cancel construction, close the view and show an error dialog
}
}
You can run the part service hide part immediately after the part creation has finished using something like:
#PostConstruct
public void postConstruct(Composite parent, UISynchronize uiSync,
EPartService partService, MPart part)
{
// Other code
// Run hidePart as soon as possible after part creation has finished
uiSync.asyncExec(() -> partService.hidePart(part));
}
(Above is using Java 8 lambda).
Just close the view, e4 style
MPart part = partService.findPart(viewId);
part.setVisible(true);
Related
When reading javafx 8 tutorials, this seems to be the main work flow:
public class Test extends Application{
public static void main(String[] args){
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader(TestFXController.class.getResource("test.fxml"));
Parent root;
try {
root = fxmlLoader.load();
Scene scene = new Scene(root, 1200, 800);
primaryStage.setScene(scene);
primaryStage.show();
TestFXController controller = fxmlLoader.getController();
controller.plotSomething();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Let's say that I have an Algorithm which I want to run. After starting the above application I may end up with an interface containing a "run algorithm" button. After pressing the button, an action handler invokes the algorithm.
I then have: start java application -> build interface -> press button to solve Algorithm -> display solution. All that separates the graphical stuff from the algorithm is a button. In fact, the graphical interface 'drives' the application in the sense that it is responsible for launching the algorithm.
What I would prefer however is something like this:
public class Test2{
public void main(String[] args){
Algorithm alg=new Algorithm();
alg.solve();
GUI gui =new GUI(); //Spawns a Javafx 8 Graphical User Interface
gui.displaySolution(alg.getSolution());
}
}
To me, this seems a lot cleaner? I'm however not sure how to do this with javafx 8, or whether this is even possible? Any examples or references are highly appreciated. What should I put in the GUI class such that it launches a javafx 8 interface?
The example in Test2 would also open up possibilities to use a clean Observer Design Pattern like this:
public class Test3{
public void main(String[] args){
Algorithm alg=new Algorithm();
alg.addListener(new GUI()); //Add a Javafx 8 GUI as a listener.
alg.addListener(new TextualLogger());
alg.solve();
}
}
Notice that in the classes Test2 and Test3, the GUI no longer drives the application.
To clarify, my main question would be: what should be the implementation of the GUI class if I would run the code in Test2? Something like this:
public class GUI extends Application{
public GUI(){
//What should I put here? Perhaps launch(new String[]); ?
}
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader(TestFXController.class.getResource("test.fxml"));
Parent root;
try {
root = fxmlLoader.load();
Scene scene = new Scene(root, 1200, 800);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
public void displaySolution(Solution sol){
...
}
}
In a JavaFX application, you should think of the start(...) method essentially as the equivalent of the main(...) method in a "regular" Java application. (In fact, in Java 8, a JavaFX application does not need a main(...) method at all.) This mechanism for launching a JavaFX application was introduced in order to force the programmer, as much as possible, to initialize the UI on the correct thread (in comparison to Swing, where there is a large amount of code published which launches the GUI incorrectly). For convenience, the start(...) method is passed an initial stage, but there is no requirement for you to use it if you prefer to use a different one.
So you can just do
public class Test2 extends Application {
#Override
public void start(Stage primaryStage) {
Algorithm alg = new Algorithm();
alg.solve();
GUI gui = new GUI();
gui.displaySolution(alg.getSolution());
}
// included for the benefit of IDEs that do not support
// launching an Application without a main method:
public static void main(String[] args) { launch(args); }
}
and now GUI is not an Application subclass (which makes sense, because it represents the GUI, not an application):
public class GUI {
public GUI(){
FXMLLoader fxmlLoader = new FXMLLoader(TestFXController.class.getResource("test.fxml"));
Parent root;
try {
root = fxmlLoader.load();
Scene scene = new Scene(root, 1200, 800);
Stage stage = new Stage();
stage.setScene(scene);
stage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
public void displaySolution(Solution sol){
...
}
}
One thing to bear in mind here is that start(...) is executed on the FX Application Thread. Consequently, once you have shown the UI, any further long-running processes that need to be performed should be executed on background threads. In the use-case you've described, all the heavy lifting is done before you show the UI, so this is not an issue, but you may need to consider this if you try to extend this pattern at all.
I am not sure I follow exactly what you are attempting to do - are you trying to open a second JavaFX Window?
Would something like this work?
Scene resultScene = algorithm.getSolution();
Stage resultStage = new Stage();
resultStage.setScene(resultScene);
resultStage.addEventHandler() or addEventFilter()
resultStage.show();
This stage can be it's own window or a child of the primaryStage so that if you close the parent, it will also close.
If you really want to open second JavaFX window from one already running. You can visit.
Launch JavaFX application from another class
It will solve your issue.
below is the code how to run new JavaFx Application
Platform.runLater(new Runnable(){
#Override
public void run(){
new MainApp().start(new Stage()); // MainApp is the class name of your second Application
}
});
Make your class extend Application and Implement Runnable and add below mention code in that class
#Override
public void run(){
launch();
}
launch() method will be called from run() method. Do not use Main() method in the second class, else it with throw exception.
The default behaviour when creating a new Eclipse ViewPart is to show the new tab regardless of what happens in the createPartControl function. For example, if didn't create anything, no widgets, nothing, a blank tab will be shown. I don't like this behaviour. I want to close that tab if initialization in createPartControl fails.
Now, I have a mouse-button-context-menu handler that can do this, e.g.
public class MyPartMB3Handler extends AbstractHandler {
#Override
public Object execute(final ExecutionEvent event)
throws ExecutionException {
// Create a view and show it.
IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindow(event);
IWorkbenchPage page = window.getActivePage();
try {
MyPart viewPart = (MyPart)page.showView(MyPart.ID);
if(!viewPart.isCreated()) {
page.hideView(viewPart);
}
}
catch(PartInitException e) {
e.printStackTrace();
}
return null;
}
}
The isCreated function is a little hack that lets me know if my ViewPart initialization fails, e.g.
public class MyPart extends ViewPart {
public static final String ID = "com.myplugin.MyPart";
private Composite _parent = null;
#Override
public void createPartControl(Composite parent) {
if(!MyPlugin.createPartControl(parent) { // Some common part creation code I use.
//PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().hideView(this);
return;
}
_parent = parent;
}
#Override
public void setFocus() {
}
public boolean isCreated() {
return _parent != null;
}
}
The problem arises when I launch this ViewPart from the Eclipse "Quick Access" field. I don't own the handler now. From an exception I forced, the handler might be org.eclipse.ui.internal.e4.compatibility.CompatibilityPart.createPartControl or org.eclipse.ui.internal.e4.compatibility.CompatibilityView.createPartControl or org.eclipse.ui.internal.e4.compatibility.CompatibilityPart.create.
I tried hiding the view inside the createPartControl function (see the commented line above), but Eclipse did not like that and spewed a pile of exceptions.
I thought maybe I could throw a PartInitException in createPartControl, but Eclipse tells me I'm not allowed to do that.
So, how do I get my menu handler behaviour when launching from "Quick Access"?
An underlying question might be, is there a better/proper way to achieve this behaviour?
You can close the view by running the hideView asynchronously after the createPartControl has finished - like this:
#Override
public void createPartControl(Composite parent) {
parent.getDisplay().asyncExec(new Runnable() {
#Override
public void run()
{
getSite().getPage().hideView(MyPart.this);
}
});
Is it ok to update an Activity when it is not in the foreground. I am not asking if it can be done from a background thread.
Consider this:
I have two activities Activity-A and Activity-B.
I start an AsyncTask from Activity-A and then go to Activity-B. Now after sometime, the AsyncTask finishes and in the onPostExecute() method, I try to update the images that are in Activity-A. All this is happening when Activity-B is in the foreground.
Is the above scenario reasonable or do I have to wait till Activity-A is in the foreground to update its UI?
If I can safely update the UI in the above scenario, what should I do when Activity-A is killed or finished and the AsyncTask still completes and tries to update the UI? [Assuming I have to do a check for isFinishing before updating the UI]
The above is a simplified version of what I am trying to do. I actually have a Custom ImageView that loads images from the server and updates itself when the request is done. So I am wondering what scenarios I have to worry about if the view is updating itself when the activity is not in foreground or has finished/destroyed.
EDIT:
Here is a sample that is working.
public class MainActivity extends Activity implements OnClickListener {
ImageView mImageView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.iv_image);
findViewById(R.id.btn_activity_2).setOnClickListener(this);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_activity_2:
new BackGroundTask().execute();
startActivity(new Intent(MainActivity.this, Activity2.class));
break;
}
}
private class BackGroundTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
mImageView.setImageResource(R.drawable.ic_launcher);
}
}
}
The Layout is just a LinearLayout with button and image.
No, it's not. You can't update a UI that's not visible :) Activity A is onPause (or Stopped if needed).
You have to implement a reasonable Activity LIfeCycle so ActivityA can update its UI during onResume(); The AsyncTask should only touch the data that the UI needs to draw itself.
Your "Custom IMage View" has to be able to load the image from a place outside the Activity.
If your CustomImageView is (or can pass as a regular ImageView), you can use something like Picasso to offload the Bitmap handling the correct way.
I have implemented a wizard for my Eclipse plug-in, showing several pages. One of these pages needs some lengthy initialization, that means it consists of a SWT table, which needs to be populated by information coming from an external source. This source needs to be activated first (one single method call that returns after a couple of seconds - I can not know in advance how long it will take exactly), before it can be used as input for for the table viewer. This initialization is currently done by the table model provider when it needs to access the external source for the first time.
Therefore, when I enter the wizard page, I would like to show a dummy progress bar that just counts up for a while. My approach was the following, but unfortunately does not work at all:
private void initViewer() {
IRunnableWithProgress runnable = new IRunnableWithProgress() { // needed to embed long running operation into the wizard page
#Override
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
SubMonitor progress = SubMonitor.convert(monitor);
Thread thread = new Thread() {
#Override
public void run() {
Display.getDefault().syncExec(new Runnable() {
public void run() {
viewer.setInput(ResourcesPlugin.getWorkspace().getRoot()); // this will make the table provider initialize the external source.
}
});
}
};
thread.start();
while(thread.isAlive()) {
progress.setWorkRemaining(10000);
progress.worked(1);
}
progress.done();
}
};
try {
getContainer().run(false, false, runnable);
} catch(Exception e) {
throw new Exception("Could not access data store", e);
}
}
This method gets then invoked when the wizard page's setVisible()-method is called and should, after a couple of seconds, set the viewer's input. This, however, never happens, because the inner-most run()-method never gets executed.
Any hints on how to deal with long-running (where an exact estimate is not available) initializations in Eclipse wizards would be very appreciated!
I have given below a simple example on how to use IRunnableWithProgress along with a ProgressMonitorDialog to perform a task of unknown quantity. To start with, have an implementation to IRunnableWithProgress from where the actual task is performed. This implementation could be an inner class.
public class MyRunnableWithProgress implements IRunnableWithProgress {
private String _fileName;
public MyRunnableWithProgress(String fileName) {
_fileName = fileName;
}
#Override
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
int totalUnitsOfWork = IProgressMonitor.UNKNOWN;
monitor.beginTask("Performing read. Please wait...", totalUnitsOfWork);
performRead(_fileName, monitor); // This only performs the tasks
monitor.done();
}
}
Now, a generic implementation to ProgressMonitorDialog can be created as below which could be used for other places where a progress monitor dialog is required.
public class MyProgressMonitorDialog extends ProgressMonitorDialog {
private boolean cancellable;
public MyProgressMonitorDialog(Shell parent, boolean cancellable) {
super(parent);
this.cancellable = cancellable;
}
#Override
public Composite createDialogArea(Composite parent) {
Composite container = (Composite) super.createDialogArea(parent);
setCancelable(cancellable);
return container;
}
}
Having got the required implementation, the task can be invoked as below to get it processed with a progress dialog.
boolean cancellable = false;
IRunnableWithProgress myRunnable = new MyRunnableWithProgress(receivedFileName);
ProgressMonitorDialog progressMonitorDialog = new MyProgressMonitorDialog(getShell(), cancellable);
try {
progressMonitorDialog.run(true, true, myRunnable);
} catch (InvocationTargetException e) {
// Catch in your best way
throw new RuntimeException(e);
} catch (InterruptedException e) {
//Catch in your best way
Thread.currentThread().interrupt();
}
Hope this helps!
I assume the reason why it's "not working" for you is that the preparation of input is done in UI thread meaning that the progress bar cannot be updated. A better approach is to prepare input in advance and only set input to viewer after that.
I am developing an Eclipse plug-in that has currently 2 views. In my first view I have a list of connections displayed in a TableViewer (name and connection status).In my second view I want to load the tables in a database (the connection). This loading will be done by clicking a menu item on a connection ("view details"). These tables will be displayed in a TreeViewer because they can also have children. I have tried to do it this way:
My View class:
public class DBTreeView extends ViewPart {
private TreeViewer treeViewer;
private Connection root = null;
public DBTreeView() {
Activator.getDefault().setDbTreeView(this);
}
public void createPartControl(Composite parent) {
treeViewer = new TreeViewer(parent);
treeViewer.setContentProvider(new DBTreeContentProvider());
treeViewer.setLabelProvider(new DBTreeLabelProvider());
}
public void setInput(Connection conn){
root = conn;
treeViewer.setInput(root);
treeViewer.refresh();
}
}
I made a setInput method that is called from the action registered with the menu item in the connections view with the currently selected connection as argument:
MViewContentsAction class:
public void run(){
selectedConnection = Activator.getDefault().getConnectionsView().getSelectedConnection();
Activator.getDefault().getDbTreeView().setInput(selectedConnection);
}
In my ContentProvider class:
public Object[] getChildren(Object arg0) {
if (arg0 instanceof Connection){
return ((Connection) arg0).getTables().toArray();
}
return EMPTY_ARRAY;
}
where EMPTY_ARRAY is an...empty array
The problem I'm facing is that when in debug mode, this piece of code is not executed somehow:
Activator.getDefault().getDbTreeView().setInput(selectedConnection);
And also nothing happens in the tree view when clicking the menu item. Any ideas?
Thank you
Huh. Ok, what you're doing here is.. not really the right way. What you should be doing is registering your TableViewer as a selection provider.
getSite().setSelectionProvider(tableViewer);
Then, define a selection listener and add it to the view with the tree viewer like this:
ISelectionListener listener = new ISelectionListener() {
public void selectionChanged(IWorkbenchPart part, ISelection sel) {
if (!(sel instanceof IStructuredSelection))
return;
IStructuredSelection ss = (IStructuredSelection) sel;
// rest of your code dealing with checking whether selection is what is
//expected and if it is, setting it as an input to
//your tree viewer
}
};
public void createPartControl(Composite parent) {
getSite().getPage().addSelectionListener(listener);
}
Now your tree viewer's input will be changed according to what is selected in the table viewer (btw, don't forget to call treeviewer.refresh() after you set new input).
See an example here.