I am developing an Eclipse plug-in that has currently 2 views. In my first view I have a list of connections displayed in a TableViewer (name and connection status).In my second view I want to load the tables in a database (the connection). This loading will be done by clicking a menu item on a connection ("view details"). These tables will be displayed in a TreeViewer because they can also have children. I have tried to do it this way:
My View class:
public class DBTreeView extends ViewPart {
private TreeViewer treeViewer;
private Connection root = null;
public DBTreeView() {
Activator.getDefault().setDbTreeView(this);
}
public void createPartControl(Composite parent) {
treeViewer = new TreeViewer(parent);
treeViewer.setContentProvider(new DBTreeContentProvider());
treeViewer.setLabelProvider(new DBTreeLabelProvider());
}
public void setInput(Connection conn){
root = conn;
treeViewer.setInput(root);
treeViewer.refresh();
}
}
I made a setInput method that is called from the action registered with the menu item in the connections view with the currently selected connection as argument:
MViewContentsAction class:
public void run(){
selectedConnection = Activator.getDefault().getConnectionsView().getSelectedConnection();
Activator.getDefault().getDbTreeView().setInput(selectedConnection);
}
In my ContentProvider class:
public Object[] getChildren(Object arg0) {
if (arg0 instanceof Connection){
return ((Connection) arg0).getTables().toArray();
}
return EMPTY_ARRAY;
}
where EMPTY_ARRAY is an...empty array
The problem I'm facing is that when in debug mode, this piece of code is not executed somehow:
Activator.getDefault().getDbTreeView().setInput(selectedConnection);
And also nothing happens in the tree view when clicking the menu item. Any ideas?
Thank you
Huh. Ok, what you're doing here is.. not really the right way. What you should be doing is registering your TableViewer as a selection provider.
getSite().setSelectionProvider(tableViewer);
Then, define a selection listener and add it to the view with the tree viewer like this:
ISelectionListener listener = new ISelectionListener() {
public void selectionChanged(IWorkbenchPart part, ISelection sel) {
if (!(sel instanceof IStructuredSelection))
return;
IStructuredSelection ss = (IStructuredSelection) sel;
// rest of your code dealing with checking whether selection is what is
//expected and if it is, setting it as an input to
//your tree viewer
}
};
public void createPartControl(Composite parent) {
getSite().getPage().addSelectionListener(listener);
}
Now your tree viewer's input will be changed according to what is selected in the table viewer (btw, don't forget to call treeviewer.refresh() after you set new input).
See an example here.
Related
I am developing a RCP application with a TreeViewer. While there are good number of articles to explain how to add editing support to the Viewer (and how changes in view are updated in the model), I don't find much for updating the Treeview when the underlaying model changes. my question in short:
TreeView ----> Model updation ------ there are lots of examples
Model ----> Treeview updation ----- this is my question
Edit:
This is what I tried and it works. comments please
viewer.getTree().addKeyListener(new KeyListener(){
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
if(e.keyCode==SWT.F3){
System.out.println("F3 pressed... new element will be added");
TreeParent root = (TreeParent) viewer.getInput();
TreeParent activityRoot = (TreeParent) root.getChildren()[0];
activityRoot.addChild(new TreeObject("NEW_ACTIVITY"));
//viewer.update(root, null);
viewer.refresh();
}
}
});
The data model is provided by your content provider, TreeViewer does not provide any means of changing this data - you must do that it your own code. When you have changed to model you can use the following methods to tell the TreeViewer about the change:
If you have just changed what needs to be shown for a single item in the tree use
TreeViewer.update(object, null);
to get that item in the tree updated. There is also an array version of this to update multiple objects.
If you have added or removed objects in the tree use
TreeViewer.refresh();
to rebuild the whole tree or
TreeViewer.refresh(object);
to refresh the part of the tree start at object.
To tell the tree about adding and removing objects there are
TreeViewer.add(parent, object);
TreeViewer.remove(object);
there are also array variants of these.
To help the TreeViewer find the objects call
TreeViewer.setUseHashlookup(true);
(must be called before TreeViewer.setInput). Since this uses a hash table the objects should have sensible hashCode and equals methods. You can also use TreeViewer.setComparer to specify a different class to do the hash code and comparison.
Based on the comments in this thread,one of the eclipse corner articles on using TreeViewer and few experimenting I had created a working model.
Here are the steps:
Create a listener interface like the following
public interface TreeModelListener extends EventListener {
public void onDelete(TreeObject obj);
}
Let the tree Content provider to add listeners to each tree model item and implement this interface like below
public class TreeContentProvider implements IStructuredContentProvider,ITreeContentProvider,TreeModelListener {
TreeViewer tv;
public TreeContentProvider(TreeViewer tv){
this.tv=tv;
}
int cnt=0;
public void inputChanged(Viewer v, Object oldInput, Object newInput) {
cnt ++;
System.out.println("inputChanged() called "+oldInput+" new: "+newInput);
if(newInput!=null){
((TreeParent)newInput).setListener(this);
TreeObject []items = ((TreeParent)newInput).getChildren();
for(TreeObject obj : items){
if(obj instanceof TreeParent){
((TreeParent) obj).setListener(this);
}
}
}
}
....
#Override
public void onDelete(TreeObject obj) {
System.out.println("Delete of "+obj+" handled by content handler ");
TreeParent parent = obj.getParent();
if(parent.getChildren().length<=1){
return;
}
parent.removeChild(obj);
this.tv.refresh();
}
}
Add a method to the TreeModel class as below . And obviously TreeParent class should have an ArrayList of listeners that is being used in #1 above
public void fireChildDelete(final TreeObject obj){
if(this.listener!=null){
new Runnable(){
#Override
public void run() {
System.out.println("New thread spawned with ID "+Thread.currentThread().getId());
listener.onDelete(obj);
}
}.run();
}
}
Finally add KeyListener to the TreeViewer Object to handle Delete key as below:
tv.getTree().addKeyListener(new KeyListener(){
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
if(e.keyCode==SWT.F3){
System.out.println("F3 pressed... new element will be added");
TreeParent root = (TreeParent) tv.getInput();
TreeParent activityRoot = (TreeParent) root.getChildren()[0];
activityRoot.addChild(new TreeObject("NEW_ACTIVITY"));
//viewer.update(root, null);
tv.refresh();
}
if(e.keyCode==SWT.DEL){
System.out.println("DEL key pressed... element will be deleted "+((Tree)e.getSource()).getSelection().length);
if(((Tree)e.getSource()).getSelection().length>0){
final IStructuredSelection selection = (IStructuredSelection) tv
.getSelection();
System.out.println("DEL#2 key pressed... element will be deleted "+selection.getFirstElement().getClass());
TreeParent parent = ((TreeObject)selection.getFirstElement()).getParent();
parent.fireChildDelete((TreeObject) selection.getFirstElement());
//tv.remove(selection.getFirstElement());
//viewer.update(viewer.getInput(),null);
//tv.refresh();
}
}
}
});
I have an ObservableList of model items. The model item is enabled for property binding (the setter fires a property changed event). The list is the content provider to a TableViewer which allows cell editing. I also intend to add a way of adding new rows (model items) via the TableViewer so the number of items in the list may vary with time.
So far, so good.
As this is all within an eclipse editor, I would like to know when the model gets changed. I just need one changed event from any changed model item in order to set the editor 'dirty'. I guess I could attach some kind of listener to each individual list item object but I wonder if there is a clever way to do it.
I think that I might have a solution. The following class is an inline Text editor. Changes to the model bean (all instances) are picked up using the listener added in doCreateElementObservable. My eclipse editor just needs to add its' own change listener to be kept informed.
public class InlineEditingSupport extends ObservableValueEditingSupport
{
private CellEditor cellEditor;
private String property;
private DataBindingContext dbc;
IChangeListener changeListener = new IChangeListener()
{
#Override
public void handleChange(ChangeEvent event)
{
for (ITableEditorChangeListener listener : listenersChange)
{
listener.changed();
}
}
};
public InlineEditingSupport(ColumnViewer viewer, DataBindingContext dbc, String property)
{
super(viewer, dbc);
cellEditor = new TextCellEditor((Composite) viewer.getControl());
this.property = property;
this.dbc = dbc;
}
protected CellEditor getCellEditor(Object element)
{
return cellEditor;
}
#Override
protected IObservableValue doCreateCellEditorObservable(CellEditor cellEditor)
{
return SWTObservables.observeText(cellEditor.getControl(), SWT.Modify);
}
#Override
protected IObservableValue doCreateElementObservable(Object element, ViewerCell cell)
{
IObservableValue value = BeansObservables.observeValue(element, property);
value.addChangeListener(changeListener); // ADD THIS LINE TO GET CHANGE EVENTS
return value;
}
private List<ITableEditorChangeListener> listenersChange = new ArrayList<ITableEditorChangeListener>();
public void addChangeListener(ITableEditorChangeListener listener)
{
listenersChange.remove(listener);
listenersChange.add(listener);
}
public void removeChangeListener(ITableEditorChangeListener listener)
{
listenersChange.remove(listener);
}
}
Please help me, as I will go mad with this soon:
When I run the code, on first occasion loadNewPoint() is executed and displays some data from global variable - allPointsAndPlaces
However when I click a button (from a child class), the same method loadNewPoint() gives me null pointer for allPointsAndPlaces.
I have changed the code structure a lot from an original trying to solve this issue, and moved this method (loadNewPoint()) to a parent class to see, if it would solve the issue.
Parent class:
public class CabbieApp implements EntryPoint {
private GetLocationsServiceAsync getAllLocationsService = GWT.create(GetLocationsService.class);
CabbiePoint[] allPointsAndPlaces;
PointsQuiz quiz;
/**
* Entry point method.
*/
public void onModuleLoad() {
//Get all the required data from DB
getAllPointsAndLocations();
}
private void loadAppPages(){
// Associate the Main panel with the HTML host page.
RootPanel rootPanel = RootPanel.get("pointsList");
quiz = new PointsQuiz();
rootPanel.setStyleName("GWTapp");
rootPanel.add(quiz.getMainPanel());
loadNewPoint();
}
private void getAllPointsAndLocations() {
// Initialize the service proxy.
if (getAllLocationsService == null) {
getAllLocationsService = GWT.create(GetLocationsService.class);
}
// Set up the callback object.
AsyncCallback<CabbiePoint[]> callback = new AsyncCallback<CabbiePoint[]>() {
public void onFailure(Throwable caught) {
System.out.println(caught.getMessage());
}
public void onSuccess(CabbiePoint[] result) {
//allPointsAndPlaces = result;
System.out.println(result.length);
allPointsAndPlaces = result;
loadAppPages();
}
};
// Make the call to the service.
getAllLocationsService.getLocations(callback);
}
void loadNewPoint(){
int r = Random.nextInt(allPointsAndPlaces.length);
quiz.CurrentPlace = allPointsAndPlaces[r].getPlaceName();
quiz.CurrentLocation = allPointsAndPlaces[r].getPlaceLocation();
quiz.point.setText(quiz.CurrentPlace);
quiz.location.setText(quiz.CurrentLocation);
quiz.location.setStyleName("invisibleText");
}
}
Child class:
public class PointsQuiz extends CabbieApp{
VerticalPanel mainPanel = new VerticalPanel();
HorizontalPanel navigation = new HorizontalPanel();
TextBox point = new TextBox();
TextBox location = new TextBox();
Button showLocation = new Button("Show Location");
Button nextPoint = new Button("Next Point");
String CurrentPlace, CurrentLocation;
public PointsQuiz() {
// Assemble Add Stock panel.
navigation.add(showLocation);
navigation.add(nextPoint);
navigation.setCellHorizontalAlignment(nextPoint, HasHorizontalAlignment.ALIGN_RIGHT);
navigation.addStyleName("addPanel");
mainPanel.setSpacing(5);
mainPanel.setStyleName("body");
mainPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
mainPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
// Assemble Main panel.
mainPanel.add(point);
point.setWidth("200px");
mainPanel.add(location);
location.setWidth("200px");
mainPanel.add(navigation);
navigation.setWidth("200px");
// Move cursor focus to the input box.
showLocation.setFocus(true);
// Listen for mouse events on the show location button.
showLocation.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
showCurrentLocation();}
});
// Listen for mouse events on the next point button.
nextPoint.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
loadNewPoint();
}
});
}
private void showCurrentLocation(){
location.setStyleName("visibleText");
}
public VerticalPanel getMainPanel() {
return mainPanel;
}
}
I managed to find a solution to this problem with Bhumika's help.
To make this work I had to change CabbiePoint[] allPointsAndPlaces to static.
This would solve the reference problem one way - from child to parent.
Also I managed to find out trough debugging, that this reference
quiz = new PointsQuiz();
is also null on a second run of loadNewPoint(). So this child reference (PointsQuiz quiz;) and any other references to children were set also to static.
You are getting null pointer error because of allPointsAndPlaces is null. As per your coding The value of allPointsAndPlaces is assigned after completion of RPC call in getAllPointsAndLocations() method. so the allPointsAndPlaces has some assigned values.
Here you try to directly access loadNewPoint() method in child class. At a time, allPointsAndPlaces is not assigned.
Within a XAML user control, the Frame object is null:
this.Frame.Navigate(typeof(FaxPropertiesPage));
How do I navigate between pages with a Windows 8 XAML User Control? I have placed the control within a Callisto Flyout on a XAML page.
The search button below must navigate the user to another XAML page.
I've successfully used the code from app.xaml.cs
Frame frame = Window.Current.Content as Frame;
and then used the standard Navigate code.
There's the nice way and the not-so-nice way:
Both of them start with a navigation service:
public interface INavigationService
{
bool CanGoBack { get; }
void GoBack();
void GoForward();
bool Navigate<T>(object parameter = null);
bool Navigate(Type source, object parameter = null);
void ClearHistory();
event EventHandler<NavigatingCancelEventArgs> Navigating;
}
public class NavigationService : INavigationService
{
private readonly Frame _frame;
public NavigationService(Frame frame)
{
_frame = frame;
frame.Navigating += FrameNavigating;
}
#region INavigationService Members
public void GoBack()
{
_frame.GoBack();
}
public void GoForward()
{
_frame.GoForward();
}
public bool Navigate<T>(object parameter = null)
{
Type type = typeof (T);
return Navigate(type, parameter);
}
So, where do I get the Frame? In App.xaml.cs
protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
// Do not repeat app initialization when already running, just ensure that
// the window is active
if (args.PreviousExecutionState == ApplicationExecutionState.Running)
{
Window.Current.Activate();
return;
}
// Create a Frame to act as the navigation context and navigate to the first page
var rootFrame = new Frame();
if (DesignMode.DesignModeEnabled)
SimpleIoc.Default.Register<INavigationService, DesignTimeNavigationService>();
else
SimpleIoc.Default.Register<INavigationService>(() => new NavigationService(rootFrame));
I'm using MVVM Light here. This makes life easy because all my viewmodels get created using dependency injection and have their services injected into them.
If you're not using something like MVVM Light and rely on code-behind then you can still make this work: Just make the navigation service static
public class NavigationService : INavigationService
{
public static INavigationService Current{
get;set;}
blah blah blah
}
And change App.xaml.cs to:
protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
// Do not repeat app initialization when already running, just ensure that
// the window is active
if (args.PreviousExecutionState == ApplicationExecutionState.Running)
{
Window.Current.Activate();
return;
}
// Create a Frame to act as the navigation context and navigate to the first page
var rootFrame = new Frame();
NavigationService.Current= new NavigationService(rootFrame));
}
And you can then access your main Frame anywhere in the app by saying:
NavigationService.Current.Navigate<MyView>();
simple code ( may not be 100% efficient) is :
Frame frame = new Frame();
frame.Navigate(typeof(ExerciseAddPage)
I am Trying to create Eclipse Plugin which has a composite with two TreeViewer side by side. On click of each TreeViewer content Eclipse property view should give appropriate information. Now I wanted to set Selection provider for both of this treeviewer hence I used
setSelectionProvider(treeViewer1)
setSelectionProvider(treeviewer2)
But only the second added treeviewer get set since the first one is overwritten. I am intiating this two treeviewer from class Queue.java. Hence I implemented the interface ISelectionProvider over Queue.java as below:
public void addSelectionChangedListener(ISelectionChangedListener listener)
{
selectionChangedListeners.add(listener);
}
public void
removeSelectionChangedListener(ISelectionChangedListener listener)
{
selectionChangedListeners.remove(listener);
}
private void fireSelectionChanged(final SelectionChangedEvent event)
{
Object[] listeners = selectionChangedListeners.getListeners();
for (int i = 0; i < listeners.length; ++i)
{
final ISelectionChangedListener l =
(ISelectionChangedListener) listeners[i];
Platform.run(new SafeRunnable()
{
public void run()
{
l.selectionChanged(event);
}
#Override
public void handleException(Throwable e)
{
removeSelectionChangedListener(l);
}
});
}
}
public void setSelection(ISelection selection)
{
fireSelectionChanged(new SelectionChangedEvent(this, selection));
}
public ISelection getSelection()
{
ArrayList<Object> list = new ArrayList<Object>();
Object o = getProperties();
if (o instanceof IPropertySource)
list.add(o);
return new StructuredSelection(list);
}
Can anyone help me how to resolve this issue. I will be grateful. thanks in advance. Tor.
Your view would have to write a selection provider wrapper or mediator that would delegate to the viewer that currently had focus. Then your view would set it up something like this:
SelectionProviderWrapper wrapper = new SelectionProviderWrapper();
wrapper.addViewer(treeViewer1);
wrapper.addViewer(treeViewer2);
getSite().setSelectionProvider(wrapper);
I would check out org.eclipse.jdt.internal.ui.viewsupport.SelectionProviderMediator for an example of a selection provider for multiple JFace viewers.