Skip to content

Commit fbb6732

Browse files
authored
Merge pull request #2615 from ControlSystemStudio/alarm_tree_fixes
Alarm tree cell refresh fix
2 parents 663ee36 + 78058ac commit fbb6732

File tree

3 files changed

+148
-6
lines changed

3 files changed

+148
-6
lines changed

app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeView.java

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import static org.phoebus.applications.alarm.AlarmSystem.logger;
1111

12+
import java.util.ArrayList;
1213
import java.util.Arrays;
1314
import java.util.Collections;
1415
import java.util.LinkedHashSet;
@@ -41,7 +42,6 @@
4142
import org.phoebus.ui.javafx.PrintAction;
4243
import org.phoebus.ui.javafx.Screenshot;
4344
import org.phoebus.ui.javafx.ToolbarHelper;
44-
import org.phoebus.ui.javafx.TreeHelper;
4545
import org.phoebus.ui.javafx.UpdateThrottle;
4646
import org.phoebus.ui.selection.AppSelection;
4747
import org.phoebus.ui.spi.ContextMenuEntry;
@@ -466,9 +466,10 @@ public void itemUpdated(final AlarmTreeItem<?> item)
466466
}
467467

468468
/** Called by throttle to perform accumulated updates */
469+
@SuppressWarnings("unchecked")
469470
private void performUpdates()
470471
{
471-
final TreeItem<?>[] view_items;
472+
final TreeItem<AlarmTreeItem<?>>[] view_items;
472473
synchronized (items_to_update)
473474
{
474475
// Creating a direct copy, i.e. another new LinkedHashSet<>(items_to_update),
@@ -480,8 +481,38 @@ private void performUpdates()
480481
items_to_update.clear();
481482
}
482483

483-
for (final TreeItem<?> view_item : view_items)
484-
TreeHelper.triggerTreeItemRefresh(view_item);
484+
// How to update alarm tree cells when data changed?
485+
// `setValue()` with a truly new value (not 'equal') should suffice,
486+
// but there are two problems:
487+
// Since we're currently using the alarm tree model item as a value,
488+
// the value as seen by the TreeView remains the same.
489+
// We could use a model item wrapper class as the cell value
490+
// and replace it (while still holding the same model item!)
491+
// for the TreeView to see a different wrapper value, but
492+
// as shown in org.phoebus.applications.alarm.TreeItemUpdateDemo,
493+
// replacing a tree cell value fails to trigger refreshes
494+
// for certain hidden items.
495+
// Only replacing the TreeItem gives reliable refreshes.
496+
for (final TreeItem<AlarmTreeItem<?>> view_item : view_items)
497+
// Top-level item has no parent, and is not visible, so we keep it
498+
if (view_item.getParent() != null)
499+
{
500+
// Locate item in tree parent
501+
final TreeItem<AlarmTreeItem<?>> parent = view_item.getParent();
502+
final int index = parent.getChildren().indexOf(view_item);
503+
504+
// Create new TreeItem for that value
505+
final AlarmTreeItem<?> value = view_item.getValue();
506+
final TreeItem<AlarmTreeItem<?>> update = new TreeItem<>(value);
507+
// Move child links to new item
508+
final ArrayList<TreeItem<AlarmTreeItem<?>>> children = new ArrayList<>(view_item.getChildren());
509+
view_item.getChildren().clear();
510+
update.getChildren().addAll(children);
511+
update.setExpanded(view_item.isExpanded());
512+
513+
path2view.put(value.getPathName(), update);
514+
parent.getChildren().set(index, update);
515+
}
485516
}
486517

487518
/** Context menu, details depend on selected items */

app/alarm/ui/src/main/java/org/phoebus/applications/alarm/ui/tree/AlarmTreeViewCell.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,31 @@ class AlarmTreeViewCell extends TreeCell<AlarmTreeItem<?>>
3838
private final Label label = new Label();
3939
private final ImageView image = new ImageView();
4040
private final HBox content = new HBox(image, label);
41-
41+
42+
// TreeCell optimizes redraws by suppressing updates
43+
// when old and new values match.
44+
// Since we use the model item as a value,
45+
// the cell sees no change, in fact an identical reference.
46+
// In the fullness of time, a complete redesign might be useful
47+
// to present changing values to the TreeCell, but also note
48+
// the issue shown in org.phoebus.applications.alarm.TreeItemUpdateDemo
49+
//
50+
// So for now we simply force redraws by always pretending a change.
51+
// This seems bad for performance, but profiling the alarm GUI for
52+
// a large configuration like org.phoebus.applications.alarm.AlarmConfigProducerDemo
53+
// with 1000 'sections' of 10 subsections of 10 PVs,
54+
// the time spent in updateItem is negligible.
55+
@Override
56+
protected boolean isItemChanged(final AlarmTreeItem<?> before, final AlarmTreeItem<?> after)
57+
{
58+
return true;
59+
}
60+
4261
@Override
4362
protected void updateItem(final AlarmTreeItem<?> item, final boolean empty)
4463
{
4564
super.updateItem(item, empty);
46-
65+
4766
if (empty || item == null)
4867
setGraphic(null);
4968
else
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Oak Ridge National Laboratory.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*******************************************************************************/
8+
package org.phoebus.applications.alarm;
9+
10+
import org.phoebus.ui.javafx.ApplicationWrapper;
11+
12+
import javafx.scene.Scene;
13+
import javafx.scene.control.Button;
14+
import javafx.scene.control.TreeItem;
15+
import javafx.scene.control.TreeView;
16+
import javafx.scene.layout.HBox;
17+
import javafx.scene.layout.Priority;
18+
import javafx.scene.layout.VBox;
19+
import javafx.stage.Stage;
20+
21+
/** Demo of alarm tree cell update
22+
*
23+
* Demonstrates refresh issue for tree cells that are
24+
* not visible because they scrolled below the window bottom.
25+
*
26+
* Only replacing the TreeItem results in reliable tree updates.
27+
*
28+
* 1) Start this program, click the "On" and "Off" buttons, and observe how item "Two" is updated
29+
* 2) Click "Off", reduce the window height so that the items under "Top" are hidden,
30+
* click "On", increase window height to again show all items.
31+
* Would expect to see "On", but tree still shows "Off"
32+
* until the tree is collapsed and again expanded.
33+
* 3) Click "On", reduce the window height so that the items under "Top" are hidden,
34+
* click "Off", increase window height to again show all items.
35+
* Tree should indeed show "Off".
36+
*
37+
* Conclusion is that TreeItem.setValue() does not trigger updates of certain hidden items.
38+
* Basic google search suggests that the value must change, but "On" and "Off"
39+
* are different object references and they are not 'equal'.
40+
*
41+
* When instead replacing the complete TreeItem, the tree is always updated.
42+
*
43+
* Tested with JavaFX 15, 19, 20
44+
*
45+
* @author Kay Kasemir
46+
*/
47+
public class TreeItemUpdateDemo extends ApplicationWrapper
48+
{
49+
private TreeView<String> tree_view = new TreeView<>();
50+
51+
@Override
52+
public void start(final Stage stage)
53+
{
54+
final Button on = new Button("On");
55+
on.setOnAction(event ->
56+
{ // Just updating the value of TreeItem is ignored for hidden items?
57+
tree_view.getRoot().getChildren().get(1).setValue("On");
58+
});
59+
final Button off = new Button("Off");
60+
off.setOnAction(event ->
61+
{ // Replacing the TreeItem always "works"?
62+
// (Note this would be more work for intermediate items
63+
// that have child nodes, since child nodes need to be moved/copied)
64+
tree_view.getRoot().getChildren().set(1, new TreeItem<>("Off"));
65+
});
66+
final HBox bottom = new HBox(on, off);
67+
on.setMaxWidth(1000);
68+
off.setMaxWidth(1000);
69+
HBox.setHgrow(on, Priority.ALWAYS);
70+
HBox.setHgrow(off, Priority.ALWAYS);
71+
72+
final VBox root = new VBox(tree_view, bottom);
73+
VBox.setVgrow(tree_view, Priority.ALWAYS);
74+
75+
TreeItem<String> top = new TreeItem<>("Top");
76+
tree_view.setRoot(top);
77+
78+
top.getChildren().add(new TreeItem<>("One"));
79+
top.getChildren().add(new TreeItem<>("Two"));
80+
top.getChildren().add(new TreeItem<>("Three"));
81+
top.setExpanded(true);
82+
83+
final Scene scene = new Scene(root, 300, 300);
84+
stage.setScene(scene);
85+
stage.show();
86+
}
87+
88+
public static void main(final String[] args)
89+
{
90+
launch(TreeItemUpdateDemo.class, args);
91+
}
92+
}

0 commit comments

Comments
 (0)