I have a tree viewer in my view, which listens to EMF models from the standard Ecore editor and does further things with it. I have already registered a selection listener, which checks whether the selected elements are the types the tree viewer needs as input. So the problem is that if there any changes in the model (e.g. adding a new element or new information to an existing element etc.) the tree viewer shows the changed model only if the user changes the selection, i.e. clicks to any model element etc.
But what I need to do is, that the tree viewer gets directly notified if the underlying model changes and shows the new model element too without being have to click on the model to listen it.
I have found the following eclipse corner article ( https://www.eclipse.org/articles/Article-TreeViewer/TreeViewerArticle.htm#inputChanged ) and from "Responding th change" it seems that the inputChanged() and refresh() methods might be the solution I am looking for, isn't it?
Still I was wondering if there is maybe an easier way to do this without being have to change the model code, but only by making changes in the UI code? Thanks!
You can call the TreeViewer refresh() method to get it to refresh the whole tree from the model, or refresh(Object) to refresh the tree starting at the given model object.
If the tree structure has not changed you can call update(Object) to just update the display of a single object.
There are also add and remove methods for when you add and remove objects from the model tree.
Some of the methods also have Object [] variants so you can modify several objects at once.
Update:
Your model should support generating a model changed event which the content provider can listen to. You would set up this listener in the content provider inputChanged method and remove it in the dispose method. When model change events are received use the various TreeViewer methods to update the tree.
An example of how all this is used are the Eclipse views which show the files in the workspace (such as the Navigator view). The content provider for these uses the workspace resource change listener (IResourceChangeListener) to be notified of changes to the workspace and using the information in the event calls the methods I listed above to update the tree.
Update 2:
An example of using IResourceChangeListener in a content provider, extracted from org.eclipse.ui.views.tasklist.TaskListContentProvider
class TaskListContentProvider
implements IStructuredContentProvider, IResourceChangeListener
{
private TableViewer viewer;
private IResource input;
... other methods ....
public void dispose() {
if (input != null) {
input.getWorkspace().removeResourceChangeListener(this);
input = null;
}
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
if (input != null) {
input.getWorkspace().removeResourceChangeListener(this);
}
input = (IResource) newInput;
if (input != null) {
input.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
}
viewer = (TableViewer) viewer;
}
public void resourceChanged(IResourceChangeEvent event) {
... use resource change event to update viewer
}
}
Related
I am developing an Eclipse RPC application and I have an editor (MainEditor) that contains two pages. The first page (Properties) displays the data of the model using some text fields and the second page (Source) is an editor that is instantiated from a class (SourceCodeEditor) that inherits the CompilationUnitEditor class and displays the source code that contains some annotations and some code.The annotations' values should correspond to the data in the model (the data is stored in variables in the model) .The new class (SourceCodeEditor) does nothing special it only override two super functions and executes the super implementation like so :
#Override
public void doSave(IProgressMonitor progressMonitor) {
super.doSave(progressMonitor);
}
#Override
public void doSaveAs() {
super.doSaveAs();
}
So I would like to add a listener to the instance of the SourceCodeEditor variable that updates the values of the annotations to correspond to the data in the model every time this editor/page is opened. The reason for that is that when I change a text field in the "Properties" page and open the "Source" page without saving the text displays the values before the change not after. If there is a better way to bind the values of the annotations in the source code and the variables of the model,please let me know.
I am learning to design a GUI in GWT
I have RootPanel where i have put all the widgets.
In one panel i have put tree Widget where the treeItems are added on the success of RPC call on the selectionHandler
What I want to do:
When click on treemItem , All the other treeItem and the widgets on the same and different panel should not be selected. Like there is a processing going on so no other things are allowed to do.
Please suggest some idea or sample code or example.
If I correctly understand you, you want to disable user clicking some widgets until specific callback finishes. In order to do that you should have a custom panel with bigger z-index and opacity. For example, something like DialogBox's setGlassEnabled(true) method and try using that technique in your callbacks. When asynchronous request starts show that panel in onSuccess and onFailure you should hide it. By the way, my-gwt has LoadingPanel class which implements such feature. And also another mask live demo in GWT-Ext.
This statement is redundant and unnecessary:
"When click on treemItem , All the other treeItem and the widgets on
the same and different panel should not be selected."
Of course, when you click on a tree node, that is the only node you want activated. No other node would have any activity. That is by convention.
And I really don't know what this statement is all about:
"Like there is a processing going on so no other things are allowed to
do."
Your question should simply be:
How do I add child nodes to a tree Widget, where child nodes are added
only when I click on the node? When I click on a node, if it has no
child nodes, an RPC would be triggered to fetch the nodes. How should
my RPC results update the node? After I click on a node, it should be
highlighted. There should be only one highlighted/selected node on the whole tree.
Extend HorizontalPanel to contain an icon and a title.
It should implement IsTreeItem.
It should implement ClickHandler so that it can be added as clickhandlers to the icon and title label.
public class Node
extends HorizontalPanel
implements ClickHandler, IsTreeItem{
Image img;
Label label;
MyData data;
TreeItem nodeWrapper;
The constructor eats in a MyData record, instantiates the icon image and title label, and associates itself as the clickhandler for the icon and label:
public Node(MyData data){
this.nodeWrapper = new TreeItem(this);
this.img = new Image(data.getIconUrl());
this.label = new Label(data.getTitle());
this.data = data;
this.add(img);
this.add(this.label);
// ensure onclick is triggered either by clicking img or label.
this.img.addClickHandler(this);
this.label.addClickHandler(this);
}
Implementing IsTreeItem requires the node to be able to substantiate itself with a TreeItem:
public TreeItem asTreeItem(){
return this.nodeWrapper;
}
The clickhandling will fetch the list of child records and iteratively create new nodes from the child records and attach them to the current node. This is where the RPC callback action takes place.
public void onClick(ClickEvent e){
this.displayRightPanel(this.data);
if(this.nodeWrapper.getChildCount()==0)
fetchNodeData(
this.nodeWrapper,
this.data.getId(),
new AsyncCallBack<List<MyData>>(){
public void onSuccess(List<MyData> nodeDataList){
for(MyData nodeData : nodeDataList){
Node.this.nodeWrapper.addItem(new Node(nodeData));
}
}
public void onFailure(Throwable sorry){
doWhatever();
}
}
);
}
define other relevant methods before closing the class definition:
//blah ... blah ... blah...
}
Since this is not a tutorial on RPC, you should find out how to write the RPC part. You need to define MyData which is a shared POJO between GWT client and RPC servlet. RPC servlet must return a list of MyData. You need to be educated on at least the 2nd Normal Form of database normalisation to understand why you need a getId() method.
public Interface MyData{
String getIconURL();
String getTitle();
String getId();
// among others ...
}
It is presumed that you have a splitpanel. The left side would be the tree widget and the right panel is some sort of display. So that onclick, besides triggering RPC fetch, would also call displayRightPanel(data), and it is left to you how you wish to display that data.
You would create a first root node and associate it as the root of the tree, and the rest of the tree would be populated by user clicking. So the root node would require you to concoct a MyData record that will supply the iconUrl, title, and some root data.
The tree widget will manage the highlighting of its members and ensure only one member is highlighted.
Let's say we have a domain entity defined this way:
interface MyNode {
MyNode getParent();
void setParent(MyNode node);
List<MyNode> GetChildren();
void AddChild(MyNode node);
void RemoveChild(MyNode node);
String getText();
void setText(String text);
}
I'm trying to implement a GWT web-app to work with these entities. I'm using request factory and editors framework. And I'm having some problems for sure :-)
Since the request factory definitions are trivial, I won't post them here. I'd only say that all the stuff related with children is a set of InstanceRequests.
So, the problem #1
Let's say we want to have a navigator for the whole tree. The idea is, every time we only see one node and we can either navigate to its parent or to one of its children. We'd like this navigator to use editors framework, so we build editors like MyNodeEditor and ChildrenListEditor.
As far as I know, editors only directly applicable to bean-styled entities. So, as long as working with MyNode text property is fine, working with children property (ChildrenListEditor) requires instance request.
My solution is, make MyNodeEditor to be a ValueAwareEditor and when it gets its value set, it initiates an InstanceRequest to get the list of child nodes. That list is then bound to ChildrenListEditor.
Are there any easier solutions? I believe it's quite a basic scenario.
Problem #2
We now decide to make our MyNodeEditor capable of editing. Bean-style properties are fine again, but what about children? Using the code mentioned in problem #1:
#Override public void setValue(MyNodeProxy value) {
...
requestFactory.myNodeRequest().getChildNodes().using(value).fire(new Receiver<List<MyNodeProxy>>() {
#Override public void onSuccess(List<MyNodeProxy> response) {
childrenDriver.display(response);
}
});
...
}
causes "Caused by: java.lang.IllegalArgumentException: Attempting to edit an EntityProxy previously edited by another RequestContext" because I'm having 2 different requests for the same entity here. I don't have access to RequestContext I've constructed at MyNodeEditor, so I'm constructing the new one and it fails. What's the right approach?
It'd be easier if you had a List<MyNodeProxy> getChildren() property on MyNodeProxy to access the children, rather than firing a distinct request.
You can access the RequestContext you passed to the RequestFactoryEditorDriver by implementing HasRequestContext on your editor. But in that case it won't help you, as firing it (from within your editor) would freeze it and thus make it unusable for anything else (such as saving the node after flushing the editor driver). If you cannot add a getChidren to your MyNodeProxy, then I'd suggest getting the children's list before you edit the node in the editor driver (alternatively, you could use a request based on the node's ID, rather than passing the node instance as an argument, or as a using() value, which is what's causing the error).
When the page with the MessagePanel first renders, the message and the approve link render perfectly. When I click the approve link, all the business logic works as desired, the getNextMessage() method returns the appropriate object, but the message panel does not update on the page in the browser. That is, the message body Label does not update.
JPAEntityModel extends LoadableDetachableModel.
What am I missing? And how do I fix it?
public class MessagePanel(String id, IModel<Message> messageModel) extends Panel {
super(id, messageModel);
add(new Label("messageText", new PropertyModel<Message>(getModelObject(), Message.BODY_FIELD)));
add(new IndicatingAjaxFallbackLink<User>("approveLink", new JPAEntityModel<User> (getActiveUser())) {
#Override
public void onClick(AjaxRequestTarget target) {
Message nextMessage = getNextMessage();
MessagePanel.this.setDefaultModel(new JPAEntityModel<Message>(nextMessage));
target.add(MessagePanel.this);
}
});
setOutputMarkupId(true);
}
It is because you're not using the model properly.
This line takes the value of the panel's model object, as it is set during construction, and uses it to create the component model.
add(new Label("messageText", new PropertyModel<Message>(getModelObject(), Message.BODY_FIELD)));
To make matters worse, when you click the link, the panel is given a new model:
MessagePanel.this.setDefaultModel(new JPAEntityModel<Message>(nextMessage));
But this obviously doesn't affect the model of the label, as it is already set to refer to the original value.
So there are two things you need to change to make it work. First off, your label model should use your panel model directly:
new Model<Message>() {
#Override
public Message getObject() {
return MessagePanel.this.getModelObject().getMessage(); //or something similar
}
}
(Note: the code above isn't necessarily the best solution, but it is a working solution that demonstrates how models can be used dynamically.)
And ideally you shouldn't replace the model when you click the link, just change the model object. If you need a custom model class (JPAEntityModel), you shouldn't be accepting a pre-constructed model in the panel constructor anyway, just the first message object. The reason being the current implementation doesn't enforce the use of JPAEntityModel from the start, only after the first click of the link.
Can you try calling MessagePanel.this.modelChanged() before adding it to the target?
You must use call setOutputMarkupId(true) within you MessagePanel. The panel needs to have a markup identifier to be able to update the markup DOM in the browser.
I am using the PropertySheetView component to visualize and edit the properties of a node. This view should always reflect the most recent properties of the object; if there is a change to the object in another process, I want to somehow refresh the view and see the updated properties.
The best way I was able to do this is something like the following (making use of EventBus library to publish and subscribe to changes in objects):
public DomainObjectWrapperNode(DomainObject obj) {
super (Children.LEAF, Lookups.singleton(obj));
EventBus.subscribe(DomainObject.class, this);
}
public void onEvent(DomainObject event) {
// Do a check to determine if the updated object is the one wrapped by this node;
// if so fire a property sets change
firePropertySetsChange(null, this.getPropertySets());
}
This works, but my place in the scrollpane is lost when the sheet refreshes; it resets the view to the top of the list and I have to scroll back down to where I was before the refresh action.
So my question is, is there a better way to refresh the property sheet view of a node, specifically so my place in the property list is not lost upon refresh?
The solution of firePropertySetsChange comes from this thread.
Just to make a clarification about my old answer as an unregistered user: Calling createSheet(null) will raise a NullPointerException. Use setSheet(createSheet()) instead.
The solution is to fire a property change for each of the changed property of the updated object. So, in context of the snippet in the question this could be something like:
public void onEvent(DomainObject event) {
// Do a check to determine if the updated object is the one wrapped by this node;
// if so fire a property sets change
Set<Property> changes = new HashSet<Property>();
// Populate the set from property set of the node using the event
// (or add all properties to force updating all properties)
for (Property change : changes) {
firePropertyChange(change.getName(), null, change.getValue());
}
}
Note that property set should not be changed as that would mess with the property editors. Consequentially, the actual Property objects have to support changing of the domain object behind the property.
You could also set the node's property sheet to null, so the createSheet method is called again.