JavaFx TreeTableView with Checkboxes, where to put listener? - javafx

I am building up a playlist manager.
Which works well but has deep flaws in the design, and thus, is difficult to UPGRADE / MAINTAIN.
In that context I now have a javafx TreeTableView having a column as Checkbox.
To get a visual idea, here is what it looks like :
In order to manage the check/uncheck children and parents of checkboxes, I implemented a listener, applied in mass AFTER the tree is built :
private void addMarkListenerToAllChildren(TreeItem<PlaylistTtRow> parentTreeItem) {
parentTreeItem.getChildren().forEach(playlist -> {
playlist.getValue().isMarked.addListener((ChangeListener<Boolean>) (obs, isPrevStatus, isNowStatus) -> {
if (!isStopListen) {
chbxOnChangeMarkTreeItem(playlist, isNowStatus);
logDLI.append(LogCategory.DEVELOPMENT, LogSeverity.INFO, LogGroup.A, "Value of " + playlist.getValue().playlistIconAndName.getValue().getPlaylistName() + " changed to " + playlist.getValue().isMarked.getValue());
}
});
logDLI.append(LogCategory.DEVELOPMENT, LogSeverity.INFO, LogGroup.A, "Listener added to " + playlist.getValue().playlistIconAndName.getValue().getPlaylistName());
if (!playlist.getChildren().isEmpty()) {
addMarkListenerToAllChildren(playlist);
}
});
}
This is in my controller at the moment !!! It's obviously in the wrong place as design... where should it go ?
And here is the process of a 'change' in checkbox value... Which is at moment in my Controller as well... again wrongly placed :
// TODO : The complete processing linked to Makred row, should be placed in the corresponding Object ! Or better, in a parent Object to be extended to TtvPlaylist / TtvTrack / ...
void chbxOnChangeMarkTreeItem(TreeItem<PlaylistTtRow> tiPlaylist, boolean isSetTo) {
if (!isUserClicked) {
isUserClicked = true;
tiPlaylistUserClicked = tiPlaylist;
}
logDLI.append(LogCategory.DEVELOPMENT, LogSeverity.INFO, LogGroup.A, "Value of '" + tiPlaylist.getValue().playlistIconAndName.getValue().getPlaylistName() + "' changed to : " + tiPlaylist.getValue().getIsMarked());
if (tbtnAutoMarkChildren.isSelected()) {
tiPlaylist.getChildren().forEach(child -> {
if (!isStopListen) {
child.getValue().setIsMarked(isSetTo);
} else {
logDLI.append(LogCategory.DEVELOPMENT, LogSeverity.INFO, LogGroup.A, "Value of '" + tiPlaylist.getValue().playlistIconAndName.getValue().getPlaylistName() + "' not changed because it has 'StopListen' set to true.");
}
});
}
// If playlist has leafs, and 'OnlyLeafs' are to be checked, force playlist as unchecked.
if (!tiPlaylist.getChildren().isEmpty() && configs.isMarkOnlyLeafs) {
isStopListen = true;
tiPlaylist.getValue().isMarked.set(false);
isStopListen = false;
}
// If There are parents, and 'OnlyLeafs' is NOT set, set parents to be checked (without auto-selecting children, of course).
TreeItem<PlaylistTtRow> tiTested = tiPlaylist;
while ((tiTested.getParent() != null) && (isSetTo)) {
isStopListen = true;
tiTested.getParent().getValue().isMarked.set(true);
isStopListen = false;
tiTested = tiTested.getParent();
}
// TODO : If the last of children is 'unmarked', unmark the parent.
if (tiPlaylist.equals(tiPlaylistUserClicked)) {
getInfosOnCheckedPlaylistsFromNode(tiPlaylist);
isUserClicked = false;
}
}
My aim is :
Have heritage of my PLAYLIST tree, for my TRACKS tree, which will also need marked row etc...
Add some more listeners to other columns
Be able to implement SPECIFIC methods on certain events
Any help would be appreciated. Thanks in advance...

Related

Trying to understand sorting in the Table widget for a Page

I am trying to understand how sorting works in Table widgets works when loading a page. For most of my pages using the Table widget, the page loads sorted by the first column.
I do see the below code in the onAttach event for the Table panel in a page. I am wondering if this is the code that sets the sorting when a page loads.
// GENERATED CODE: modify at your own risk
window._am = window._am || {};
if (!window._am.TableState) {
window._am.TableState = {};
window._am.inFlight = false;
}
if (!window._am.sortTableBy) {
window._am.sortTableBy = function(datasource, field, fieldHeader, tableState) {
if (!field) {
throw "Can't sort the table because specified field was not found.";
}
tableState.inFlight = true;
if (tableState.sortByField === field.name) {
tableState.ascending = !tableState.ascending;
} else {
if (tableState.fieldHeader) {
tableState.fieldHeader.text = tableState.fieldHeaderText;
tableState.fieldHeader.ariaLabel = "";
}
tableState.sortByField = field.name;
tableState.ascending = true;
tableState.fieldHeader = fieldHeader;
tableState.fieldHeaderText = fieldHeader.text;
}
datasource.query.clearSorting();
var sortDirection = tableState.ascending ? "ascending" : "descending";
datasource.query.sorting[field.name]["_" + sortDirection]();
datasource.query.pageIndex = 1;
datasource.load(function() {
tableState.inFlight = false;
fieldHeader.text = fieldHeader.text.replace(/ (\u25B2|\u25BC)/g, "");
if (tableState.sortByField === field.name) {
fieldHeader.ariaLabel = fieldHeader.text + " sort " + sortDirection;
fieldHeader.text = fieldHeader.text + (tableState.ascending ? " \u25B2" : " \u25BC");
app.accessibility.announce(fieldHeader.ariaLabel);
}
});
};
}
Sorting is set on your datasource.
Click the model (top left) that your table is using.
Click Datasources and expand your Datasource (there will only be one, unless you have created additional ones).
Once you choose the field you want the table to use for sorting, you can choose "Ascending" or "Descending".
The problem is with adding new records from a separate create form. The new records always appear at the end of the list , until a subsequent 'load' is performed.

Detect When A Node is Visible in a Scene

I am trying to find a way to detect (or receive notification) that a Node has been added to a Scene and is visible.
I am creating Node objects off the main JavaFx thread and add them to the Stage and Scene using Platform.runLater(). However I would like the Node object to receive notification that is has been added to the Scene and is visible, for example I wish to trigger an animation to start.
I can't seem to find any property or method to add a listener to capture such an event. Any suggestions?
The third-party JavaFX library ReactFX has a mechanism for this, and this exact use case is cited in the blog. In short, you can do
Val<Boolean> showing = Val.flatMap(node.sceneProperty(), Scene::windowProperty)
.flatMap(Window::showingProperty);
and then of course
showing.addListener((obs, wasShowing, isNowShowing) -> {
if (isNowShowing) {
// node is showing
} else {
// node is not showing
}
});
The standard library has a version of this, but it is very badly written. (It is not typesafe, has no compile-time checking that the properties exist, and also pipes a lot of unnecessary warnings to standard error if any of the properties in the "chain" are null, even though the API docs indicate this is a supported use case.) If you want to do this with the standard JavaFX library, you can do
BooleanBinding showing = Bindings.selectBoolean(node.sceneProperty(), "window", "showing");
and then use the binding the same way as above.
Finally, you could do all this by hand, but it gets a bit ugly to manage the listeners properly:
BooleanProperty showing = new SimpleBooleanProperty();
ChangeListener<Window> windowListener = (obs, oldWindow, newWindow) -> {
showing.unbind();
if (newWindow != null) {
showing.bind(newWindow.showingProperty());
} else {
showing.set(false);
}
};
ChangeListener sceneListener = (obs, oldScene, newScene) -> {
showing.unbind();
if (oldScene != null) {
oldScene.windowProperty().removeListener(windowListener);
}
if (newScene == null) {
showing.set(false);
} else {
newScene.windowProperty().addListener(windowListener);
if (newScene.getWindow() == null) {
showing.set(false);
} else {
showing.bind(newScene.getWindow().showingProperty());
}
}
};
node.sceneProperty().addListener(sceneListener);
if (node.getScene() == null) {
showing.set(false);
} else {
node.getScene().windowProperty().add(windowListener);
if (node.getScene().getWindow() == null) {
showing.set(false);
} else {
showing.bind(node.getScene().getWindow().showingProperty());
}
}
You can add a listener to the children property of container node into which you are adding the new node.
grid.getChildren().addListener((ListChangeListener<? super Node>) change -> {
System.out.println(change.getList().get(0).getTypeSelector());
});
change.getList().get(0) returns the first node that is added to grid object.
After James's comment, I have looked up and yes, it is possible to do it from node's perspective as well. You can listen to parentProeprty's changes on the node. Following snippet shows the way to do it.
Button b = new Button("Test");
b.parentProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("added to a container " + newValue);
});
answerPane.getChildren().add(b);

Display a message to a specific player in a networked game using Photon Unity Networking

On entering a trigger zone of an object a message is displayed to the player (which is found by tag). If the player then presses the pickup button, the item is added to their inventory. It works fine in single-player games.
When I use PUN (Photon Networking) though it displays it to all players (as all player's script detect the event) and all players can pickup the item. How I can fix this problem?
I believe fixing this piece will enable me to understand how to fix everything else:
void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "Player")
{
displayMessage = true;
}
}
void OnTriggerExit(Collider col)
{
if (col.gameObject.tag == "Player")
{
displayMessage = false;
}
}
bool stop = false;
void OnGUI()
{
if (displayMessage)
{
if (GameObject.FindGameObjectWithTag("UInventory").GetComponent<UInventory>().inventoryMode == "Weight")
{
GUI.Label(new Rect((Screen.width/2)-200, (Screen.height/2)-30, 200, 45), "Press " + pickUpItem.ToString() + " To Take " + itemName + "(" + itemWeight + "kg)");
}
else
{
GUI.Label(new Rect((Screen.width/2)-200, (Screen.height/2)-30, 200, 45), "Press " + pickUpItem.ToString() + " To Take " + itemName);
}
}
}
Trigger code should be executed only if client owns network object. Use 'isMine' property of 'PhotonView' component of the object to check if this is true.

Show static non-clickable heading in AutoCompleteExtender list

I have an AutoCompleteExtender from the Ajax Control Toolkit. I need to have a heading in the dropdown list that shows how many items found, but it should not be selectable as an item.
I have tried this using jQuery, but even when I just add as a div, it is still selected as an item into the text box when I click on it:
function clientPopulated(sender, e) {
var completionList = $find("AutoCompleteEx").get_completionList();
var completionListNodes = completionList.childNodes;
for (i = 0; i < completionListNodes.length; i++) {
completionListNodes[i].title = completionListNodes[i]._value.split(':')[2];
}
var resultsHeader;
if(completionListNodes.length==1000)
resultsHeader = 'Max count of 1000 reached.<br/>Please refine your search.';
else if(completionListNodes.length>0)
resultsHeader = completionListNodes.length + ' hits.';
else
resultsHeader = msg_NoObjectsFound ;
jQuery(completionListNodes[0]).before('<div>' + resultsHeader + '</div>');
}
Add OnClientItemSelected and OnClientShowing events handlers and try script below:
function itemSelected(sender, args) {
if (args.get_value() == null) {
sender._element.value = "";
}
}
function clientShowing() {
var extender = $find("AutoCompleteEx");
var optionsCount = extender.get_completionSetCount();
var message = "";
if (optionsCount == 1000) {
message = 'Max count of 1000 reached.<br/>Please refine your search.';
}
else if (optionsCount > 0) {
message = optionsCount + " hits."
}
else {
message = "oops."
}
jQuery(extender.get_completionList()).prepend("<li style='background-color:#ccc !important;'>" + message + "</li>");
}
Added:
you even can do this without OnClientItemSelected handler:
function clientShowing() {
var extender = $find("AutoCompleteEx");
var oldSetText = extender._setText;
extender._setText = function (item) {
if (item.rel == "header") {
extender._element.value = "";
return;
}
oldSetText.call(extender, item);
};
var optionsCount = extender.get_completionSetCount();
var message = "";
if (optionsCount == 1000) {
message = 'Max count of 1000 reached.<br/>Please refine your search.';
}
else if (optionsCount > 0) {
message = optionsCount + " hits."
}
else {
message = "oops."
}
jQuery(extender.get_completionList()).prepend("<li rel='header' style='background-color:#ccc !important;'>" + message + "</li>");
}
We can give a better answer if you post the output html of your autocomplete control. Anyway if its a dropdown control;
jQuery(completionListNodes[0]).before('
<option value="-99" disabled="disabled">your message here</option>'
);
The answer by Yuriy helped me in solving it so I give him credit although his sollution needed some changes to work.
First of all, the clientShowing event (mapped by setting OnClientShowing = "clientShowing" in the AutoExtender control) is executed on initialization. Here we override the _setText method to make sure nothing happens when clicking on the header element. I have used the overriding idea from Yuriy's answer that really did the trick for me. I only changed to check on css class instead of a ref attribute value.
function clientShowing(sender, e) {
var extender = sender;
var oldSetText = extender._setText;
extender._setText = function (item) {
if (jQuery(item).hasClass('listHeader')) {
// Do nothing. The original version sets the item text to the search
// textbox here, but I just want to keep the current search text.
return;
}
// Call the original version of the _setText method
oldSetText.call(extender, item);
};
}
So then we need to add the header element to the top of the list. This has to be done in the clientPopulated event (mapped by setting OnClientPopulated = "clientPopulated" in the AutoExtender control). This event is executed each time the search results have been finished populated, so here we have the correct search count available.
function clientPopulated(sender, e) {
var extender = sender;
var completionList = extender.get_completionList();
var completionListCount = completionList.childNodes.length;
var maxCount = extender.get_completionSetCount();
var resultsHeader;
if(completionListCount == maxCount)
resultsHeader = 'Max count of ' + maxCount + ' reached.<br/>'
+ 'Please refine your search.';
else if(completionListCount > 0)
resultsHeader = completionListCount + ' hits.';
else
resultsHeader = 'No objects found';
jQuery(completionList).prepend(
'<li class="listHeader">' + resultsHeader + '</li>');
}
I have also created a new css class to display this properly. I have used !important to make sure this overrides the mousover style added from the AutoExtender control.
.listHeader
{
background-color : #fafffa !important;
color : #061069 !important;
cursor : default !important;
}

AS3/Flex 4: Most Practical Way To Find Nested Children

I'm sort of jumping in headfirst to some Flex/AIR stuff. I have a pretty solid background with AS3, but given the inherent hierarchal complexity of Flex (compared to regular Flash), I'm running into an issue.
Let's assume that you have an app where pretty much everything is event driven (common). Accessing elements in the near vicinity of the event target, or the event target itself, is trivial. I'm trying to find, however, the most practical (read: best, most efficient) way to find children that are far removed from the current context.
I know there are functions like getChildAt() and getChildByName(), but that assumes a parent context; what if the element (Flex) you're looking for is several parents up, in a sibling, and then several children down? We take for granted things like jQuery that do this easily, but obviously we don't have that luxury in AS3.
Are any of the following valid? Is there a better way?
Iterate through parents and parents' parents until you find a stop point, find the sibling, and iterate through children and their children until you find your target;
Keep key objects in a global object store (sic) and reference them as necessary (yech)
Use specific dot notation to reach the target, including elements (like skins and their containers - yech again)
Any thoughts would be appreciated.
Edit:
To clarify, let's take an empty Flex 4 AIR app. We have WindowedApplication as the root, obviously, and let's add two SkinnableContainer children with IDs navContainer and mainContainer, respectively. Both have custom skins. Within mainContainer, we have another SkinnableContainer with a vertical layout and ID mainContent, and as one of its children, it has an object (any will do - a spark BorderContainer, maybe) with the ID animatedBox, for example. Within the navContainer, we have a spark Button, which has a listener bound for MouseEvent.CLICK. Within that function, we are going to want to access animatedBox (nativeWindow.mainContainer.mainContent.animatedBox) and animate it to change, say, it's width.
The goal is to access that distant DisplayObject (animatedBox) in a way that is as unobtrusive and efficient as possible, while still conforming to Flex standards that I clearly have yet to possess. :)
in my implementation it is easy to do (however it's in pure AS3):
in display object which handles the click:
private function onClick(e:MouseEvent):void{
Radio.broadcast(new CustomEvent(id, ..params));
}
in animatedBox:
Radio.addListener(id, new Reciever(uid, animate));
private function animate(e:CustomEvent) {
//needed code and access of CustomEvent props you pass
}
upd:
package lazylib.broadcast
{
/**
* ...
* #author www0z0k
*/
public class Reciever
{
private var id: String;
private var toRun: Function;
/*#param nm - unique listener id - required
*#param fn - event handler function - required*/
public function Reciever(nm:String, fn:Function)
{
id = nm;
toRun = fn;
}
public function onEvent(e:* = null):String {
if (e == null) { return id; }
toRun(e);
return id;
}
public function get ID():String { return id; }
}
}
and
package lazylib.broadcast
{
import flash.events.Event;
import flash.events.EventDispatcher;
/**
* ...
* #author www0z0k
*/
public final class Radio extends EventDispatcher
{
private static var listeners: Object = new Object();
private static var archive: Array = new Array();
private static var forSlowpokes: Object = new Object();
public static function get ForSlowpokes():Object { return forSlowpokes; }
public static function addListener(type: String , listener: Reciever):Boolean {
listeners['anchor'] = null;
if (!listeners[type]) {
var o: Object = new Object();
listeners[type] = o;
}
if (!listeners[type][listener.ID]) {
listeners[type][listener.ID] = listener;
return true;
}else {
return false;
}
}
public static function broadcast(evt: * , singleUse:Boolean = false):void {
var type:String = (evt as Event).type;
if (listeners[type]) {
var returned: Array = new Array();
for (var i: String in listeners[type]) {
if(listeners[type][i]){
var fnRetVal: String = listeners[type][i].onEvent(evt);
returned.push(fnRetVal);
}else{
//trace("no listener for id = " + i + ' , type = ' + type);
}
}
}else {
//trace("nobody's interested in : \"" + type + "\"");
}
if (singleUse) {
forSlowpokes[type] = 'you missed it realtime';
delete listeners[type];
}
}
public static function clearDeadFuncs(namez:Object):void {
for (var a:String in namez) {
if (a != 'anchor') {
killListener(a, namez[a]);
}
}
}
public static function killListener(type: String , id: String):Boolean {
if (!listeners[type]) {
//trace("there are no listeners for event : " + "\"" + type + "\"");
return false;
}else {
if (!listeners[type][id]) {
//trace("there is no \"" + id + "\" listener for event : " + "\"" + type + "\"");
return false;
}else {
listeners[type][id] = null;
//trace("removed listener \"" + id + "\" for event : " + "\"" + type + "\"");
var evt2kill: Number = 0;
for (var str: String in listeners[type]) {
if (listeners[type][str]) {
evt2kill++;
}
}
if (evt2kill == 0) {
delete listeners[type];
//trace("no more listeners for event : " + "\"" + type + "\"");
return true;
}
return true;
}
}
}
}
}
delivered as is ;)
We take for granted things like jQuery that do this easily, but obviously we don't have that luxury in AS3.
well there is this: http://tech.nitoyon.com/blog/2008/01/as3query_alpha.html
I asked myself this question also a lot of times. Still haven't figured out an ultimate solution to the problem. Iterating through parents and parents is definately a way but has to be taken with caution, cause relations might change in your application during runtime. I wrote a simple method a few days ago that lets you iterate through all parents of a given object. Definitely not an elegant solution but it works so far. the SWIZ framework also offers good methods to facilitate the communication between objects via code injection and Event mediation. Maybe worth a look...

Resources