JavaFx, event interception/consumption - javafx-2

I've got this partial scenegraph tree :
CustomPane (with onMouseClicked Handler)
→ ChildNode (with onMousePressed Handler)
When I catch the MousePressed event in the ChildNode, I can consume it, so that the parent doesn't receive a MousePressed event.
But I would like to consume the associated MouseClicked event. So that pressing the mouse on the Child doesn't fire a MouseClicked event on the Parent.

You can add specific ChildNode#onMouse... handlers which will consume all events.
or provide your own EventDispatcher:
child.setEventDispatcher(new EventDispatcher() {
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
boolean valid = myValidationLogicForEvents(event);
return valid ? tail.dispatchEvent(event) : null;
}
});

Related

Firing a caught event

This question is so obvious, and I am sure that it is so easy to solve that I wonder why no simple and easy to understand answer hes been posted yet. I’ve found answers explaining how to create a *new event, changing the event direction, -bubble, etc. However, I have not found a Q/A regarding catch a signal and then bounce it off to the user class, and then to a class that the is user of that user class and so on and so forth.
Having said that, here is my question.
My class, ClassOne, receive the signal of a button and then has to pass that signal to ClassTwo, which in turn passes the signal to ClassThree, how that heck do I do this in JavaFx.
Please note that the class must not fire a new event, but the same event that has been caught.
Below is a grammatical explanation of what I am trying to do.
Any help, and I mean any input would be most appreciated.
public class MyBoxedButton extends HBox {
private Button btn;
MyBoxedButton(){
// Initialization of all the objects
btn = new Button();
//catch the event emitted by the button
btn.setOnAction((ActionEvent e) ->{
// fire/emit the same received event
?????
});
}
}
/*
This class catches the event emitted by MyBoxedButton and then passes it to ClassThree,
but this does not work, how then can I catch the event and then re-emit it.
*/
class ClassTwo(){
private MyBoxedButton mbb;
public ClassTwo(){
mbb = new MyBoxedButton();
//catch the event emitted by MyBoxedButton
????
// re-emit the caught event
????
}
}
/*
This class catches the event emitted by ClassTwo.
The exersice is ment to show how the messages flow
form one object to a next and then to a next, so on and so forth.
*/
class ClassThree(){
private ClassTwo c2;
public ClassThree(){
c2 = new ClassTwo();
//catch the event emitted by MyBoxedButton
????
// re-emit the caught event
????
}
As long as your wrapper classes also extend Node, fireEvent should do the trick, I think.
MyBoxedButton and ClassTwo will have to provide a way to register a listener.
I've written the SimpleEventHandlerProperty helper class, which makes this easier. Here is a code snippet from a sample class which uses SimpleEventHandlerProperty to allow to register an ActionEvent handler.
private final ObjectProperty<EventHandler<ActionEvent>> onNewSampleAction = new SimpleEventHandlerProperty<>(this,
"onNewSampleAction", ActionEvent.ACTION, this::setEventHandler);
public final EventHandler<ActionEvent> getOnNewSampleAction() {
return onNewSampleActionProperty().get();
}
public final void setOnNewSampleAction(EventHandler<ActionEvent> contentChanged) {
onNewSampleActionProperty().set(contentChanged);
}
public ObjectProperty<EventHandler<ActionEvent>> onNewSampleActionProperty() {
return onNewSampleAction;
}
Then ClassTwo and ClassThree can register a listener as required.
The helper class is available at Maven Central:
<dependency>
<groupId>org.drombler.commons</groupId>
<artifactId>drombler-commons-fx-core</artifactId>
<version>0.13</version>
</dependency>

Controlling the event ElementTypeDuplicated

I am controlling the event "application.ControlledApplication.ElementTypeDuplicated" and this event raise after the name of the new type is imputed, but after that I would like to override the result of the dialog box ( ID: "IDD_SYMBOL_ATTRIB") that were raised before the event ElementTypeDuplicated. I already try to get a Object Args and override the result inside the method that is suubscribing the event ElementTypeDuplicated, but is not working. Is there a way of doing this?
Example:
public void OnElementTypeDuplicated(object o, ElementTypeDuplicatedEventArgs args)
{
//doing things
duplicatingTypeArgs.OverrideResult(0);
}
}
}
public void OnDialogDuplicatingELement(object o, DialogBoxShowingEventArgs args)
{
if (args.DialogId=="IDD_SYMBOL_ATTRIB")
{
duplicatingTypeArgs = args;
}
}
Haven't tested this yet, but how about implementing IUpdater with "Element.GetChangeTypeElementAddition" instead subscribing to the duplicating type event
You could subscribe to the DocumentChanged event before duplicating the symbol. That will provide you with the element ids of all newly created elements. An example of using that is provided by the place family instance sample.
After the duplication, unsubscribe again.
You can use the Idling event to be notified when the duplication has terminated.

Control Events- Revit API

I would like to control events of Load Families and Create Type with revit api. Someone can give me a direction ? I don't understand very well the documentation that I read.
First you need to subscribe to an event by creating an event listener in the IExternalApplication OnStartup method.
public class AppCommand : IExternalApplication
{
public Result OnStartup(UIControlledApplication application)
{
application.ControlledApplication.FamilyLoadedIntoDocument += OnFamilyLoaded;
return Result.Succeeded;
}
}
Next you need a handler for that event:
private void OnFamilyLoaded(object sender, FamilyLoadedIntoDocumentEventArgs args)
{
// do work here
}
When finished you need to unregister the event handler:
public Result OnShutdown(UIControlledApplication application)
{
application.FamilyLoadedIntoDocument -= OnFamilyLoaded;
return Result.Succeeded;
}
The other events available that you can subscribe to are these:
http://www.revitapidocs.com/2018/b69e9d33-3c49-e895-3267-7daabab85fdf.htm
Cheers!

Is the operation of toggling a button in JavaFX atomic?

A toggle button in a JavaFX operation will be accessed by 2 separate threads.
1.
One thread will be invoked as soon as user clicks (toggles button state) and will
a) do something in the OS
b) check if (a) succeeded
c) exit on success / exit and return toggle button to previous state on failure
2
The other thread will monitor events asynchronous to the previous operation(s) and in case of a particular event it will change the button state.
Do I need to provide synchronization between threads 1 and 2 in terms of locking the button state?
EDIT: The idea proposed by James_D seems reasonable, but I just wanted to propose an alternative (whose effectiveness remains to be proved however).
How about using synchronized code blocks, and using as lock the reference to the particular button, i.e. something like:
// getting the reference to the button
#FXML
private ToggleButton tButtonToBeSynchronized
// Thread1
synchronized(tButtonToBeSynchronized) {
// do stuff with button upon user click
}
// Thread2
synchronized(tButtonToBeSynchronized) {
// poll system every X seconds
// when asynchronous event occurs (not related to UI events)
// update tButtonToBeSynchronized state
}
Would that work in case these are called by different Controller classes? (assuming the reference to the tButtonToBeSynchronized is passed by reference - and not by value by the FXML framework?
Like most UI toolkits, JavaFX assumes a single threaded model. You should only ever access the state of nodes that are part of a scene graph from the FX Application Thread. So, toggling a button is not an atomic operation, and the code you describe is not guaranteed to work as you currently have it set up. In Java 8, it will likely throw a RuntimeException.
JavaFX provides functionality to enable interoperability with background threads. The lowest level of these is Platform.runLater(Runnable r), which executes r on the FX Application Thread. So, your monitor thread (item 2 in your question) should change the state of the toggle button with
Platform.runLater( () -> toggleButton.setSelected(...) );
There is also a javafx.concurrent API. This provides a Task class, among others, which acts as both a Runnable and a java.util.concurrent.FutureTask, and additionally has a collection of callback methods for submitting code to be executed on the FX Application Thread at various points in the Task's lifecycle.
So you should implement item 1 in your question as:
ExecutorService exec = ... ; // e.g. Executors.newCachedThreadPool();
toggleButton.selectedItemProperty().addListener((obs, wasSelected, isNowSelected) -> {
if (isNowSelected) {
Task<Void> task = new Task<Void>() {
#Override
public Void call() throws Exception {
// do something on OS
// throw exception if failed
return null ;
}
};
task.setOnFailed(event -> toggleButton.setSelected(wasSelected));
exec.submit(task);
}
});
If you prefer to return a value indicating success or failure, you can do
Task<Boolean> task = new Task<Boolean>() {
#Override
public Boolean call() {
// do work...
boolean successful = ... ;
return successful ;
}
};
task.setOnSucceeded( event -> {
boolean wasSuccessful = task.getValue();
// ...
});

Show modal dialog (top most) in new thread

A modal dialog with top most property set to true, doesn't appear as top most when shown within a new thread. Example code:
Thread thread = new Thread(KickOffForm);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
private void KickOffForm(object state)
{
Thread.Sleep(5000); // Mimics logic that takes place before form is shown
var form = new Form2();
form.ShowDialog();
}
The modal dialog appears as top most if the form is instantiated at the beginning of the thread. Example code:
Thread thread = new Thread(KickOffForm);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
private void KickOffForm(object state)
{
var form = new Form2();
Thread.Sleep(5000); // Mimics logic that takes place before form is shown
form.ShowDialog();
}
The above code is executed within a class that is instantiated when exe starts.
Why would the form appear as top most when instantiated at the beginning of the thread and not if instantiated later on?
Forms can only be modal to the thread they are created and owned by.
If you want to display a modal dialog that stops interaction with your main form, you must create the dialog on the main UI thread.
This must be so, because each thread runs it's own message loop. One thread knows nothing about any message loop in another thread.
Maybe you could invoke the dialog window in a correct thread:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click( object sender, EventArgs e )
{
Thread thread = new Thread( KickOffForm );
thread.SetApartmentState( ApartmentState.STA );
thread.Start();
}
private void KickOffForm( object state )
{
var form = new Form2();
Thread.Sleep( 5000 ); // Mimics logic that takes place before form is shown
this.Invoke( (Action)(() => { form.ShowDialog(); }) );
}
}

Resources