JavaFX: execute events in Sub- and in SuperClass - javafx

if I implement an onMouseClicked-EventHandler in a SuperClass like this:
public class SuperClass {
public SuperClass() {
setOnMouseClicked(new EventHandler<Event>() {
#Override
public void handle(Event event) {
System.out.println("Super reacted");
}
});
}
And I implement an onMouseClicked-EventHandler in a SubClass of SuperClass like this:
public class SubClass extends SuperClass{
public SubClass() {
setOnMouseClicked(new EventHandler<Event>() {
#Override
public void handle(Event event) {
System.out.println("Sub reacted");
});
}
}
After clicking on a instance of SubClass the result in the console will be "Sub reacted". So the SuperClass does not notice the event.
How can I forward the event in a way, that all super classes react too? I thought of manually firing an event in the sub class, but it dosnt work.
Thank you for your help.

There exists two ways to register with widgets in JavaFX:
setOnMouseClicked (all the setOn... methods). The handler associated with setOnMouseClicked is unique: in your case the sub-class registration replaces the registration made in the super class.
addEventHandler (or AddEventFilter). In this case you can have multiple handlers that register with the same widget. These methods take as arguments: the type of event you want to listen; a instance of the type EventHandler<> that you can write as a callback or an anonymous class.
In your case:
addEventHandler(MouseEvent.MOUSE_CLICKED, evt -> {
// ...
});
or using an anonymous class:
addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(final MouseEvent evt) {
}
});
Where evt is the triggered mouse click event.
The use of addEventHandler should fix your issue.

Related

Javafx and the Observer pattern - updating a UI

I am trying to implement the Observer pattern in a JavaFx application. I've never asked a question here but this is driving me a bit crazy.
Essentially I'm trying to use the Observer pattern to monitor a class that's parsing a file of phone numbers, and update the UI automatically as the file is parsed.
Before I get to my questions, here is my code:
Abstract class Observer.java
public abstract class Observer
{
public PhoneBook numbers;
public abstract void update();
}
I have a class that implements this:
public class PhoneBookObserver extends Observer {
public PhoneBookObserver(PhoneBook numbers)
{
this.numbers = numbers;
this.numbers.attach(this);
}
#Override
public void update()
{
System.out.println(""NUMBER - : " + numbers.GetNumbers());
}
}
In the class doing the parsing, I've created a new PhoneBookObserver
public PhoneBook ParsePhoneBook()
{
PhoneBook nums= new PhoneBook();
PhoneBookObserver p = new PhoneBookObserver(nums);
// ... Parsing of file - works fine
return nums;
}
Currently this runs and my println from update() in PhoneBookObserver is output.
My questions are:
Can the update method of PhoneBookObserver update my UI for me? How would it access JavaFx elements in my controller?
Can I just make my controller an observer, override update() and use that to update my UI elements from within my controller? Is that bad?
To directly answer your question, I would probably implement the Observer as an inner class in the controller. Then it has access to everything in the controller.
Assuming here PhoneBook defines a method of the form
public List<PhoneNumber> getPhoneNumbers() ;
then you could do:
public class Controller {
#FXML
private ListView<PhoneNumber> phoneNumberList ;
private PhoneBook numbers = new PhoneBook() ; // or initialize from elsewhere
public void initialize() {
numbers.attach(new PhoneBookObserver(numbers));
// ...
}
private class PhoneBookObserver extends Observer {
PhoneBookObserver(PhoneBook numbers) {
this.numbers = numbers ;
}
#Override
public void update() {
phoneNumberList.getItems().setAll(numbers.getPhoneNumbers());
}
}
}
Note that in
public abstract class Observer
{
public PhoneBook numbers;
public abstract void update();
}
the field numbers really serves no purpose, as the only method doesn't use it. So you could remove it (subclasses can define such a field if they need). Then you may as well make it an interface, and since it only has one method, it's a #FunctionalInterface:
#FunctionalInterface
public interface Observer {
public void update() ;
}
and now it can be implemented with a lambda expression, so the implementation is so thin that you basically stop having any issues with "accessing the UI":
public class Controller {
#FXML
private ListView<PhoneNumber> phoneNumberList ;
private PhoneBook numbers = new PhoneBook() ; // or initialize from elsewhere
public void initialize() {
numbers.attach(() -> phoneNumberList.getItems().setAll(numbers.getPhoneNumbers());
// ...
}
}
Finally, note that JavaFX Properties and observable lists basically already provide an implementation of the observer pattern, so you're pretty much reinventing the wheel here. You could just have
public class PhoneBook {
private final ObservableList<PhoneNumber> numbers;
public ObservableList<PhoneNumber> getPhoneNumbers() {
return numbers ;
}
}
and then
public class Controller {
#FXML
private ListView<PhoneNumber> phoneNumberList ;
private PhoneBook numbers = new PhoneBook() ; // or initialize from elsewhere
public void initialize() {
phoneNumberList.setItems(numbers.getPhoneNumbers());
}
}
and the list view will observe the (already-observable) list of numbers for you. There is no real need for your Observer or PhoneBookObserver.

JavaFX Property's invalidate() method not called with binding

I'm trying to make a custom control in JavaFX using some binding feature. This is my problem: I have a class with a DoubleProperty that I use to calculate the position of an element inside my custom control. Here is the code:
public class CustomControl extends Region {
private DoubleProperty positionProperty;
public CustomControl() {
positionProperty= new DoublePropertyBase(0.0) {
#Override public Object getBean() { return CustomControl.this; }
#Override public String getName() { return "position"; }
#Override protected void invalidated() { updatePostion(); }
};
}
public DoubleProperty positionProperty() { return positionProperty; }
public double getPosition() { return positionProperty.get(); }
public void setPosition(double value) { positionProperty.set(value); }
private void updatePosition() {
double value = doubleProperty.get();
//compute the new position using value
}
}
In my application i have two CustomControls and i want that when i call the method setPosition() on the first of them, the second one updates the position of its component as well. To do so I binded the positionProperty of the two CustomControls like this:
CustomControl control1 = new CustomControl();
CustomControl control2 = new CustomControl();
control2.positionProperty.bind(control1.positionProperty);
Then when I call for example
control1.setPosition(50.0);
only the position of the component of control1 is updated, indeed when i call the method setPosition(), the method invalidated() of the positionProperty of control1 is actually called, but not the one of the positionProperty of contol2 as I espected. How shoul i achieve what i want? Thank you!
PS: I also noticed that using the method bindBidirectional() instead of bind() works, but shouldn't it work using only bind() too?
EDIT: example code is available here: https://luca_bertolini#bitbucket.org/luca_bertolini/customcontrolexample.git
JavaFX uses lazy evaluation for all bindings, which means that when a change occurs on your out.positionProperty object, the new value is not immediately taken into account. This happens later, if and only when the value is subsequently requested.
Try this:
out.positionProperty().addListener(new InvalidationListener() {
#Override
public void invalidated(final Observable observable) {
// System.out.println("It must go now.");
}
});
and you will see that this time your code works as you want.

Toolbar with SearchView temporary filtering RecyclerView

I need to implement search functionality inside my android app which uses toolbar, SlidingTabLayout and ViewPager that holds fragments. Inside each fragment there is a RecyclerView with list of items.
RecyclerView data is static defined in separate class (DataAccess.java) and those lists are updated and RecyclerView gets refreshed just by calling (without passing new data)
mRecyclerView.getAdapter().notifyDataSetChanged();
Is there any simple way to temporary filter RecyclerView without changing the data and after the user presses return button inside Toolbar to remove the filter and show inital list.
Before pressing Search icon inside toolbar menu:
So when the user is typing "Josip..." the filter will be active
and after he presses the X button in SearchView the user will get the same data as before without filter.
#Override
public boolean onQueryTextChange(String newText) {
// filter data (temporary remove all items from DataAccess.list that don't .startsWith(newText)
}
#Override
public boolean onQueryTextSubmit(String query)
// Doesn't help if I revert deleted items here
}
#Override
public boolean onQueryTextSubmit(String query){
((ItemAdapter) myRecList.getAdapter()).setFilter(query)
}
public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ViewHolder> {
private List<String> visibleObjects;
private List<String> allObjects;
.....
public void flushFilter(){
visibleObjects=new ArrayList<>();
visibleObjects.addAll(allObjects);
notifyDataSetChanged();
}
public void setFilter(String queryText) {
visibleObjects = new ArrayList<>();
constraint = constraint.toString().toLowerCase();
for (String item: allObjects) {
if (item.toLowerCase().contains(queryText))
visibleObjects.add(item);
}
notifyDataSetChanged();
}
}
I wanted to add as comment but due to less reputation...I am answering post.
This method works fine if (item.toLowerCase().contains(queryText)) but what to do if match is not found in first iteration.then it will go in else part without looping throughout allObjects list...
for (RouteByATMList.Route_ATM item: Main_ATMItemList)
{
if (item.ATMNumber.contains(queryText)) {
visibleObjects.add(item);
}else {
Toast.makeText(mContext,"No search result found!",Toast.LENGTH_SHORT).show();
break;
}
}
I got the answer from my superior ,hope it helps.
public void setFilter(String queryText) {
visibleObjects = new ArrayList<>();
for (RouteByATMList.Route_ATM item: Main_ATMItemList)
{
if (item.ATMNumber.contains(queryText))
{
visibleObjects.add(item);
}
}
if(visibleObjects.size()==0){
Toast.makeText(mContext,"No search result found!",Toast.LENGTH_SHORT).show();
}
notifyDataSetChanged();
Log.e("dataset changed","dataset changed");
}
I don't oppose the given and accepted answer. There is a room for possible performance pitfalls. One should make use of Filterabe interface. Having this implemented will behave as ol'good ListView that did the filtering asynchronously. Then all you need is to write your own Filter and instantiate it in the overridden getFilter() method;
In my case I used Filter to sort an adapter of many (yeah, many many) items. It was junky sorting it on UI-thread.
So I had this abstract class
public abstract class BaseFilterableRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> implements Filterable {
private Context mContext;
public BaseFilterableRecyclerViewAdapter(Context context) {
this.mContext = context;
}
public abstract void sort(SortingFilter.Sort sortingStrategy);
//...Other methods
}
And the class that inherit:
public class ItemAdapter extends BaseFilterableRecyclerViewAdapter<RecyclerView.ViewHolder>{
//... RecyclerView methods
#Override
public Filter getFilter() {
return new SortingFilter(mData) {
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results.values != null) {
int last = mData.size();
mData = (List<Product>) results.values;
notifyDataSetChanged();
}
}
};
}
#Override
public void sort(SortingFilter.Sort sortingStrategy) {
getFilter().filter(sortingStrategy.toString());
}
}

How could i refresh Android Fragment/s content onBack Press?

How could i refresh Fragment/s content onBack Press ?
FragmentManager back = getFragmentManager();
back.beginTransaction().replace(R.id.left, new ArticleFragment()).commit();
back.beginTransaction().replace(R.id.middle, new LocationFragment()).commit();
refresh the contents of a fragment,
you could keep a reference to the fragment, and call a public refresh() method in that fragment.
when you create the tab fragment,
like the PupulareFragment, use the pupulareFragment instance variable to store the fragment. So it is available in the refresh button onClick method.
This way you are not replacing/creating any new fragments when refreshing. So going back to tabActivity after going to de article details activity, would probably work fine aswell.
Example:
public class ArticleTabFragmentActivity extends FragmentActivity{
private PopulareFragment populareFragment;
private RecomandateFragment recomandateFragment;
<the code...>
private void bindView(){
Button btn_refresh = (Button) findViewById(R.id.btn_refresh);
btn_refresh.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String current_tab = mTabHost.getCurrentTabTag();
if(current_tab.contains("POPULARE")){
populareFragment.refresh();
}
else if(current_tab.contains("RECOMANDATE")){
recomandateFragment.refresh();
}
});
}
}
public class PopulareFragment extends Fragment{
public void refresh(){
<refresh the contents of the fragment>
}
}

Flex Event Dispatcher Questions

I am trying to dispatch an custom YouTubeEvent from my Player.as and wish my Main.as would listen and create the video player...Apparently my eventHandler can't catch the event to create the videoplayer.......My flex debug mode is so screw up I can't even use it...My code is as follow..I really appreciate any reply or help.....
My custom event..
package com.youtube.events {
import flash.events.Event;
public class YouTubeEvent extends Event{
public static const PLAYER_READY:String="PLAYER_READY";
public function YouTubeEvent(type:String){
super(type);
}
}
}
My Main.as
public class SearchYoutube extends Sprite
{
private var videoPlayer:Player;
public function SearchYoutube()
{
/*********************Load Video Player****************************/
loadPlayer();
}
private function loadPlayer():void{
videoPlayer= new Player();
videoPlayer.addEventListener(YouTubeEvent.PLAYER_READY, playerReady);
//playReady would never be excuted....
}
private function playerReady(event:YouTubeEvent):void{
videoPlayer.createPlayer(); //This handler would never be executed...
addChild(videoPlayer); //This handler would never be executed...
}
}
Player.as
//only show part of codes here
public function Player(){
}
public function createPlayer():void{
_loader = new Loader();
_loader.contentLoaderInfo.addEventListener(Event.INIT, onLoaderInit);
}
private function onLoaderInit(event:Event):void {
_loader.content.addEventListener("onReady", onPlayerReady);
}
private function onPlayerReady(event:Event):void {
dispatchEvent(new YouTubeEvent(YouTubeEvent.PLAYER_READY));
}
YouTubeEvent.PLAYER_READY is dispatched some time after calling createPlayer(). You should call createPlayer() after videoPlayer.addEventListener(YouTubeEvent.PLAYER_READY, playerReady):
private function loadPlayer():void
{
videoPlayer= new Player();
videoPlayer.addEventListener(YouTubeEvent.PLAYER_READY, playerReady);
videoPlayer.createPlayer();
}
This short tutorial will get you on the right path on using custom events with Flex.

Resources