Javafx: How to add eventhandlers to another seperate class - user-interface

I have two buttons within in a pane on the scene. I have added the event handler like this:
Button reset=new Button ("Reset");
reset.addEventHandler(MouseEvent.MOUSE_CLICKED,new EventHandler<MouseEvent>(){
public void handle(MouseEvent event){
System.out.println("reseting");
model.reset();
Play(stage);
}});
Button guess=new Button ("Guess");
guess.addEventHandler(MouseEvent.MOUSE_CLICKED,new EventHandler<MouseEvent>(){
public void handle(MouseEvent event){
ArrayList<Integer> FullGuess=new ArrayList<Integer>();
boolean condition=true;
for (int y:fullGuess){
if (y==0){
condition=false;
}
}
}});
How can I do the event handling part in another class rather than doing it directly in the class as I have done?

Your fragment uses an anonymous class to handle events, but you can promote your anonymous handler to a member of the enclosing class or a separate class having package-private access. In either case, you'll want to pass the required List<Integer> as a parameter.
List<Integer> fullGuess = new ArrayList<Integer>();
…
Button guess = new Button("Guess");
guess.addEventHandler(MouseEvent.MOUSE_CLICKED, new MyEventHandler(fullGuess));
In another file:
class MyEventHandler implements EventHandler<MouseEvent> {
private final List<Integer> fullGuess;
public MyEventHandler(List<Integer> fullGuess) {
this.fullGuess = fullGuess;
}
#Override
public void handle(MouseEvent event) {
boolean condition = true;
for (int y : fullGuess) {
…
}
}
}

Related

Toast is shown every time when device is rotate

In my Android app I use AAC.
Here my activity:
public class AddTraderActivity extends AppCompatActivity {
AddTraderViewModel addTraderViewModel;
private static final String TAG = AddTraderActivity.class.getName();
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AddTraderActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.add_trader_activity);
binding.setHandler(this);
init();
}
private void init() {
ViewModelProvider viewViewModelProvider = ViewModelProviders.of(this);
addTraderViewModel = viewViewModelProvider.get(AddTraderViewModel.class);
Observer<String> () {
#Override
public void onChanged (String message){
Debug.d(TAG, "onChanged: message = " + message);
Toast.makeText(AddTraderActivity.this, message, Toast.LENGTH_LONG).show();
}
});
}
public void onClickStart() {
EditText baseEditText = findViewById(R.id.baseEditText);
EditText quoteEditText = findViewById(R.id.quoteEditText);
addTraderViewModel.doClickStart(baseEditText.getText().toString(), quoteEditText.getText().toString());
}
}
Here my ViewModel:
public class AddTraderViewModel extends AndroidViewModel {
private MutableLiveData<String> messageLiveData = new MutableLiveData<>();
private static final String TAG = AddTraderViewModel.class.getName();
public AddTraderViewModel(#NonNull Application application) {
super(application);
}
public void doClickStart(String base, String quote) {
Debug.d(TAG, "doClickStart: ");
if (base.trim().isEmpty() || quote.trim().isEmpty()) {
String message = getApplication().getApplicationContext().getString(R.string.please_input_all_fields);
messageLiveData.setValue(message);
return;
}
}
public LiveData<String> getMessageLiveData() {
return messageLiveData;
}
}
So when I click on button on Activity call method onClickStart()
If any fields is empty the show toast. In the activity call method:
onChanged (String message)
Nice. It's work fine.
But the problem is, when I rotate the device in the activity method onChanged(String message) is called AGAIN and as result show toast. This happened on every rotation.
Why?
This is the expected behaviour. If you want to avoid this you must set message = "" and keep an empty check before showing the toast.
A better way to use it is something like Event Wrapper or SingleLiveEvent
Highly recommend you to read this article. This explains why you are facing this and what are your options in detail.

JavaFX. Register eventHandler in custom class

I try register eventHandler in my custom class. I don't know what interface or methods I have to implement for having addEventHandler method in my custom class. For this reason my Model class extends Rectangle (Rectangle class has addEventHandler mechanism).
Also I don't know why assigned source object not working (please see comment in Controller class).
Creating custom events I make by this tutorial: https://stackoverflow.com/a/27423430/3102393.
Project Structure
Controller
package sample;
import javafx.event.Event;
public class Controller {
private Model model;
public Controller() {
model = new Model();
model.addEventHandler(MyEvent.ROOT_EVENT, this::handler);
}
private void handler(MyEvent event) {
if(event.getEventType().equals(MyEvent.INSTANCE_CREATED)) {
// Why is event.getSource() instence of Rectangle and not instance of assigned MyObject?
Object obj = event.getSource();
System.out.println(event.getMyObject().getText());
}
}
public void clickedCreate(Event event) {
model.makeEvent();
}
}
Model
package sample;
import javafx.scene.shape.Rectangle;
import java.util.ArrayList;
public class Model extends Rectangle {
private ArrayList<MyObject> objects = new ArrayList<>();
private Integer counter = 0;
public void makeEvent() {
MyObject object = new MyObject((++counter).toString() + "!");
objects.add(object);
fireEvent(new MyEvent(object, null, MyEvent.INSTANCE_CREATED));
}
}
Custom event MyEvent
package sample;
import javafx.event.Event;
import javafx.event.EventTarget;
import javafx.event.EventType;
public class MyEvent extends Event {
public static final EventType<MyEvent> ROOT_EVENT = new EventType<>(Event.ANY, "ROOT_EVENT");
public static final EventType<MyEvent> INSTANCE_CREATED = new EventType<>(ROOT_EVENT, "INSTANCE_CREATED ");
public static final EventType<MyEvent> INSTANCE_DELETED = new EventType<>(ROOT_EVENT, "INSTANCE_DELETED");
private MyObject object;
public MyEvent(MyObject source, EventTarget target, EventType<MyEvent> eventType) {
super(source, target, eventType);
object = source;
}
public MyObject getMyObject() {
return object;
}
}
And finally MyObject
package sample;
public class MyObject {
private String text;
MyObject(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
Note (and question): I also tried using a ObservableList of instances of MyObjects, but I think that there is no notify for updating instance attribute.
Basics of Events
Events are fired using Event.fireEvent which works in 2 steps:
Build the EventDispatchChain using EventTarget.buildEventDispatchChain.
Pass the Event to the first EventDispatcher in the resulting EventDispatchChain.
This code snippet demonstrates the behaviour:
EventTarget target = new EventTarget() {
#Override
public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
return tail.append(new EventDispatcher() {
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
System.out.println("Dispatch 1");
tail.dispatchEvent(event);
return event;
}
}).append(new EventDispatcher() {
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
System.out.println("Dispatch 2");
tail.dispatchEvent(event);
return event;
}
});
}
};
Event.fireEvent(target, new Event(EventType.ROOT));
It prints
Dispatch 1
Dispatch 2
As you can see, the way the EventTarget constructs the EventDispatchChain is totally up to the EventTarget.
This explains why you have to implement addEventHandler ect. yourself.
How it's done for Nodes
This is described in detail in the article JavaFX: Handling Events - 1 Processing Events on the Oracle website.
The important details are:
Different source objects are used during the event handling.
EventHandlers / EventFilters are used during the event dispatching (2.).
This explains why the source value is unexpected.
How to implement addEventHandler
It's not that hard to do this, if you leave out the event capturing and bubbling. You just need to store the EventHandlers by type in a Map<EventType, Collection>> and call the EventHandlers for each type in the EventType hierarchy:
public class EventHandlerTarget implements EventTarget {
private final Map<EventType, Collection<EventHandler>> handlers = new HashMap<>();
public final <T extends Event> void addEventHandler(EventType<T> eventType, EventHandler<? super T> eventHandler) {
handlers.computeIfAbsent(eventType, (k) -> new ArrayList<>())
.add(eventHandler);
}
public final <T extends Event> void removeEventHandler(EventType<T> eventType, EventHandler<? super T> eventHandler) {
handlers.computeIfPresent(eventType, (k, v) -> {
v.remove(eventHandler);
return v.isEmpty() ? null : v;
});
}
#Override
public final EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
return tail.prepend(this::dispatchEvent);
}
private void handleEvent(Event event, Collection<EventHandler> handlers) {
if (handlers != null) {
handlers.forEach(handler -> handler.handle(event));
}
}
private Event dispatchEvent(Event event, EventDispatchChain tail) {
// go through type hierarchy and trigger all handlers
EventType type = event.getEventType();
while (type != Event.ANY) {
handleEvent(event, handlers.get(type));
type = type.getSuperType();
}
handleEvent(event, handlers.get(Event.ANY));
return event;
}
public void fireEvent(Event event) {
Event.fireEvent(this, event);
}
}

Using one event handler for multiple actions

I was doing some homework today and I've accomplished all of the goals of the assignment, which I'm sure will get me full points.
In an earlier class, however, we used the same Event Handler for more than one action (in this example, you either type a color in the text field, or click a button to change the background color of the box).
I can't figure out how I would do that in this case... do I have to choose a Type in the constructor? If the first parameter could be a button or a textfield then I think that would help.
I'm just trying to figure out how to apply DRY (Don't Repeat Yourself), where ever I can.
public class ColorChooserApplication extends Application
{
#Override
public void start(Stage stage)
{
// Create all UI components
VBox backgroundBox = new VBox(10);
backgroundBox.setPadding(new Insets(10));
HBox topBox = new HBox(10);
HBox bottomBox = new HBox(10);
TextField colorPrompt = new TextField();
colorPrompt.setOnAction(new ColorHandler(colorPrompt, backgroundBox));
Button redButton = new Button("Red");
redButton.setOnAction(new ButtonHandler(redButton, backgroundBox));
Button whiteButton = new Button("White");
whiteButton.setOnAction(new ButtonHandler(whiteButton, backgroundBox));
Button blueButton = new Button("Blue");
blueButton.setOnAction(new ButtonHandler(blueButton, backgroundBox));
// Assemble
topBox.getChildren().add(colorPrompt);
bottomBox.getChildren().addAll(redButton, whiteButton, blueButton);
backgroundBox.getChildren().addAll(topBox, bottomBox);
backgroundBox.setAlignment(Pos.CENTER);
topBox.setAlignment(Pos.CENTER);
bottomBox.setAlignment(Pos.CENTER);
// Set scene and show
stage.setScene(new Scene(backgroundBox));
stage.show();
}
class ColorHandler implements EventHandler<ActionEvent>
{
TextField colorTf;
VBox bgVbox;
public ColorHandler(TextField colorTf, VBox bgVbox)
{
this.colorTf = colorTf;
this.bgVbox = bgVbox;
}
#Override
public void handle(ActionEvent event)
{
String color = colorTf.getText();
bgVbox.setStyle("-fx-background-color:" + color);
}
}
class ButtonHandler implements EventHandler<ActionEvent>
{
Button colorButton;
VBox bgVbox;
public ButtonHandler(Button colorButton, VBox bgVbox)
{
this.colorButton = colorButton;
this.bgVbox = bgVbox;
}
#Override
public void handle(ActionEvent event)
{
String color = colorButton.getText();
bgVbox.setStyle("-fx-background-color:" + color);
}
}
public static void main(String[] args)
{
launch(args);
}
}
If you're using Java 8, you can do
class ColorHandler implements EventHandler<ActionEvent> {
Supplier<String> colorSupplier ;
VBox bgVbox ;
public ColorHandler(Supplier<String> colorSupplier, VBox bgVbox) {
this.colorSupplier = colorSupplier ;
this.bgVbox = bgVbox ;
}
#Override
public void handle(ActionEvent event) {
String color = colorSupplier.get();
bgVbox.setStyle("-fx-background-color: "+color);
}
}
and then
colorPrompt.setOnAction(new ColorHandler(colorPrompt::getText, backgroundBox));
redButton.setOnAction(new ColorHandler(redButton::getText, backgroundBox));
Note that all you need to provide for the first parameter is some function that returns the correct string for use in the css. So you can do things like
whiteButton.setOnAction(new ColorHandler(() -> "#ffffff", backgroundBox));
blueButton.setOnAction(new ColorHandler(() -> "cornflowerblue", backgroundBox));
etc.

Gridview onScroll method gets called always, without user scroll

I have a customized gridview where i'm checking onScroll method to find the end of the list. If the scroll reaches the end of the list, it will again add few elements in to the list.
gridview.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView arg0, int arg1) {
}
#Override
public void onScroll(AbsListView arg0, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
int lastInScreen = firstVisibleItem + visibleItemCount;
//is the bottom item visible & not loading more already ? Load more !
if((lastInScreen == totalItemCount) && (!loadingMore))
{
new LoadDataTask().execute();
}
}
});
And this is my Asynchronous task class..
private class LoadDataTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
if (isCancelled()) {
return null;
}
loadingMore = true;
for (int i = 0; i < mNames.length; i++)
mListItems.add(mNames[i]);
return null;
}
#Override
protected void onPostExecute(Void result) {
mListItems.add("Added after load more");
loadingMore=false;
adapter.notifyDataSetChanged();
super.onPostExecute(result);
}
#Override
protected void onCancelled() {
}
}
Now the issue is that the onScroll method keep on calling. It doesn't stop even when the user not scrolling. Can anyone have a solution ?
Please check the answer for this question: onScroll gets called when I set listView.onScrollListener(this), but without any touch .
The same is true for the GridView, since it has AbsListView as superclass just as ListView does.

Update UI from an AsyncTaskLoader

I've converted my AsyncTask to an AsyncTaskLoader (mostly to deal with configuration changes). I have a TextView I am using as a progress status and was using onProgressUpdate in the AsyncTask to update it. It doesn't look like AsyncTaskLoader has an equivalent, so during loadInBackground (in the AsyncTaskLoader) I'm using this:
getActivity().runOnUiThread(new Runnable() {
public void run() {
((TextView)getActivity().findViewById(R.id.status)).setText("Updating...");
}
});
I am using this in a Fragment, which is why I'm using getActivity(). This work pretty well, except when a configuration change happens, like changing the screen orientation. My AsyncTaskLoader keeps running (which is why I'm using an AsyncTaskLoader), but the runOnUiThread seems to get skipped.
Not sure why it's being skipped or if this is the best way to update the UI from an AsyncTaskLoader.
UPDATE:
I ended up reverting back to an AsyncTask as it seems better suited for UI updates. Wish they could merge what works with an AsyncTask with an AsyncTaskLoader.
It's actually possible. You essentially need to subclass the AsyncTaskloader and implement a publishMessage() method, which will use a Handler to deliver the progress message to any class that implements the ProgressListener (or whatever you want to call it) interface.
Download this for an example: http://www.2shared.com/file/VW68yhZ1/SampleTaskProgressDialogFragme.html (message me if it goes offline) - this was based of http://habrahabr.ru/post/131560/
Emm... you shouldn't be doing this.
because how an anonymous class access parent class Method or Field is by storing an invisible reference to the parent class.
for example you have a Activity:
public class MyActivity
extends Activity
{
public void someFunction() { /* do some work over here */ }
public void someOtherFunction() {
Runnable r = new Runnable() {
#Override
public void run() {
while (true)
someFunction();
}
};
new Thread(r).start(); // use it, for example here just make a thread to run it.
}
}
the compiler will actually generate something like this:
private static class AnonymousRunnable {
private MyActivity parent;
public AnonymousRunnable(MyActivity parent) {
this.parent = parent;
}
#Override
public void run() {
while (true)
parent.someFunction();
}
}
So, when your parent Activity destroys (due to configuration change, for example), and your anonymous class still exists, the whole activity cannot be gc-ed. (because someone still hold a reference.)
THAT BECOMES A MEMORY LEAK AND MAKE YOUR APP GO LIMBO!!!
If it was me, I would implement the "onProgressUpdate()" for loaders like this:
public class MyLoader extends AsyncTaskLoader<Something> {
private Observable mObservable = new Observable();
synchronized void addObserver(Observer observer) {
mObservable.addObserver(observer);
}
synchronized void deleteObserver(Observer observer) {
mObservable.deleteObserver(observer);
}
#Override
public void loadInBackground(CancellationSignal signal)
{
for (int i = 0;i < 100;++i)
mObservable.notifyObservers(new Integer(i));
}
}
And in your Activity class
public class MyActivity extends Activity {
private Observer mObserver = new Observer() {
#Override
public void update(Observable observable, Object data) {
final Integer progress = (Integer) data;
mTextView.post(new Runnable() {
mTextView.setText(data.toString()); // update your progress....
});
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreated(savedInstanceState);
MyLoader loader = (MyLoader) getLoaderManager().initLoader(0, null, this);
loader.addObserver(mObserver);
}
#Override
public void onDestroy() {
MyLoader loader = (MyLoader) getLoaderManager().getLoader(0);
if (loader != null)
loader.deleteObserver(mObserver);
super.onDestroy();
}
}
remember to deleteObserver() during onDestroy() is important, this way the loader don't hold a reference to your activity forever. (the loader will probably be held alive during your Application lifecycle...)
Answering my own question, but from what I can tell, AsyncTaskLoader isn't the best to use if you need to update the UI.
In the class in which you implement LoaderManager.LoaderCallback (presumably your Activity), there is an onLoadFinished() method which you must override. This is what is returned when the AsyncTaskLoader has finished loading.
The best method is to use LiveData, 100% Working
Step 1: Add lifecycle dependency or use androidx artifacts as yes during project creation
implementation "androidx.lifecycle:lifecycle-livedata:2.1.0"
Step 2: Create the loader class as follow, in loader create in public method to set the livedata that can be observed from activity or fragment. see the setLiveCount method in my loader class.
package com.androidcodeshop.asynctaskloaderdemo;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.MutableLiveData;
import androidx.loader.content.AsyncTaskLoader;
import java.util.ArrayList;
public class ContactLoader extends AsyncTaskLoader<ArrayList<String>> {
private MutableLiveData<Integer> countLive = new MutableLiveData<>();
synchronized public void setLiveCount(MutableLiveData<Integer> observer) {
countLive = (observer);
}
public ContactLoader(#NonNull Context context) {
super(context);
}
#Nullable
#Override
public ArrayList<String> loadInBackground() {
return loadNamesFromDB();
}
private ArrayList<String> loadNamesFromDB() {
ArrayList<String> names = new ArrayList<>();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
names.add("Name" + i);
countLive.postValue(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return names;
}
#Override
protected void onStartLoading() {
super.onStartLoading();
forceLoad(); // forcing the loading operation everytime it starts loading
}
}
Step 3: Set the live data from activity and observe the change as follows
package com.androidcodeshop.asynctaskloaderdemo;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.MutableLiveData;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity implements
LoaderManager.LoaderCallbacks<ArrayList> {
private ContactAdapter mAdapter;
private ArrayList<String> mNames;
private MutableLiveData<Integer> countLiveData;
private static final String TAG = "MainActivity";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNames = new ArrayList<>();
mAdapter = new ContactAdapter(this, mNames);
RecyclerView mRecyclerView = findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mAdapter);
countLiveData = new MutableLiveData<>();
countLiveData.observe(this, new androidx.lifecycle.Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
Log.d(TAG, "onChanged: " + integer);
Toast.makeText(MainActivity.this, "" +
integer,Toast.LENGTH_SHORT).show();
}
});
// initialize the loader in onCreate of activity
getSupportLoaderManager().initLoader(0, null, this);
// it's deprecated the best way is to use viewmodel and livedata while loading data
}
#NonNull
#Override
public Loader onCreateLoader(int id, #Nullable Bundle args) {
ContactLoader loader = new ContactLoader(this);
loader.setLiveCount(countLiveData);
return loader;
}
#Override
public void onLoadFinished(#NonNull Loader<ArrayList> load, ArrayList data) {
mNames.clear();
mNames.addAll(data);
mAdapter.notifyDataSetChanged();
}
#Override
public void onLoaderReset(#NonNull Loader loader) {
mNames.clear();
}
#Override
protected void onDestroy() {
super.onDestroy();
}
}
Hope this will help you :) happy coding

Resources