How to bind a domain object to a JavaFX TreeView? - model-view-controller

How can I bind a domain object to a JavaFX TreeView? ComboBox has getItems() and you can add something to that collection. TreeView does not seem to have such a method. I could only build the tree manually by adding TreeItems to the TreeView's root and then using getChildren().add(...) to add children, but there seems no way of just adding an observable tree structure.
The domain object can read itself from a file and write itself to a file. It has methods to modify its contents. How do I best hook this up with a TreeView so that the user can add and delete nodes?
I don't want GUI code (i.e., JavaFX classes) in my domain objects.
Do I need to write an Adapter class that can turn my domain object into a JavaFX tree? Then add listeners to the tree and map the changes back to the domain object? Or is there a better way?

Some time ago I had a similar problem. I've written a custom TreeItem implementation that can handle recursive data structures. I've written a blog post with a detailed explanation here.
The code for the RecursiveTreeItem can be found as gist.
As an example think of a class Task that can contain many sub-tasks and so on.
public class Task {
private ObservableList<Task> subtasks = FXCollections.observableArrayList();
public ObservableList<Task> getSubtasks() {
return subtasks;
}
}
In this case you could use it as follows:
Task root = new Task();
TreeItem<Task> rootItem = new RecursiveTreeItem<Task>(root, Task::getSubtasks);
tree.setRoot(rootItem);
The second parameter is of type Callback<T, ObservableList<T>>: a function that takes an element of T (in our case Task) and returns the child elements for this element. In the example I've used a method reference as a shortcut.
This is a fully reactive implementation i.e. when a new sub item is added it will immediately be shown in the TreeView.
You said you don't like to have JavaFX classes in you domain model. In this case you could write something like this (not tested):
TreeItem<Task> rootItem = new RecursiveTreeItem<Task>(root,
task -> FXCollections.observableArrayList(task.getSubtasks()));
Here getSubtasks() returns a plain List<Task> that is wrapped in an observableList. But of cause in this case the TreeView won't update automatically when your model changes.

Related

ReactiveUI with Redux style state

I'm trying to use ReactiveUI with a redux style model.
I want to have a property that looks like
public string Code
{
get => _state.Code;
set => _state.Dispatch(new SetCodeMessage(value));
}
and have the _state determine what properties changed when the message gets processed.
I can't figure out how to setup the binding and subscription to make this happen.
Right now I don't even have the Dispatch method being called, but the view still displays whatever gets typed into the textbox this property is bound to. I tried adding RaisePropertyChanged to the setter which ends up having the getter called, but the UI still doesn't change.
First of all, what is the type of _state.Code?
Then, instead of inventing the wheel on your own (unless that's what you want to do), use something ready and tested, like redux.NET.
When using RxUI, try to keep in line with best practices, which would be to have properties be as simple as possible.
public string Code {
get => _code;
set => this.RaiseAndSetIfChanged(ref _code, value);
}
// in VM contructor
this.WhenAnyValue(x => x.Code).Do(_state.Dispatch); // you can hook this up in WhenActivated to dispose it when necessary
_state.CodeChanged // this is analog to computed properties from Redux, should be of type IObservable<string>
.Do(c => this.Code = c);
This way you can simply bind a textbox to ViewModel.Code and the rest will be handled.
One piece of advice, don't do that :)
The central store should only contain values that you want to share between multiple ViewModels. So, unless you want to update other control as user types, push the value to the global only when user clicks OK.
All properties that are bound to textboxes etc should rather be handled locally, in the ViewModel.

Is it bad to use ViewModelLocator to Grab other VM's for use in another Vm?

I am using MVVM light and figured out since that the ViewModelLocator can be used to grab any view model and thus I can use it to grab values.
I been doing something like this
public class ViewModel1
{
public ViewModel1()
{
var vm2 = new ViewModelLocator().ViewModel2;
string name = vm2.Name;
}
}
This way if I need to go between views I can easily get other values. I am not sure if this would be best practice though(it seems so convenient makes me wonder if it is bad practice lol) as I know there is some messenger class thing and not sue if that is the way I should be doing it.
Edit
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<ViewModel1>();
SimpleIoc.Default.Register<ViewModel2>();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public ViewModel1 ViewModel1
{
get
{
return ServiceLocator.Current.GetInstance<ViewModel1 >();
}
}
Edit
Here is a scenario that I am trying to solve.
I have a view that you add price and store name to. When you click on the textbox for store name you are transferred to another view. This view has a textbox that you type the store you are looking for, as you type a select list get populated with all the possible matches and information about that store.
The user then chooses the store they want. They are transferred back to the view where they "add the price", now the store name is filled in also.
If they hit "add" button it takes the price, the store name, and the barcode(this came from the view BEFORE "add price view") and sends to a server.
So as you can see I need data from different views.
I'm trying to understand what your scenario is. In the MVVMlight forum, you added the following context to this question:
"I have some data that needs to be passed to multiple screens and possibly back again."
For passing data between VMs, I would also - as Matt above - use the Messenger class of MVVMLight as long as it is "fire and forget". But it is the "possibly back again" comment that sounds tricky.
I can imagine some scenarios where this can be needed. Eg. a wizard interface. In such a case I would model the data that the wizard is responsible for collecting and then bind all Views to the same VM, representing that model object.
But that's just one case.
So maybe if you could provide a little more context, I would be happy to try and help.
Yes, you can do this, in as much as the code will work but there is a big potential issue you may run into in the future.
One of the strong arguments for using the MVVM pattern is that it makes it easier to write code that can be easily tested.
With you're above code you can't test ViewModel1 without also having ViewModelLocator and ViewModel2. May be that's not too much of a bad thing in and of itself but you've set a precedent that this type of strong coupling of classes is acceptable. What happens, in the future, when you
From a testing perspective you would probably benefit from being able to inject your dependencies. This means passing, to the constructor--typically, the external objects of information you need.
This could mean you have a constructor like this:
public ViewModel1(string vm2Name)
{
string name = vm2Name;
}
that you call like this:
var vm1 = new ViewModel1(ViewModelLocator.ViewModel2.name);
There are few other issues you may want to consider also.
You're also creating a new ViewModelLocator to access one of it's properties. You probably already have an instance of the locator defined at the application level. You're creating more work for yourself (and the processor) if you're newing up additional, unnecessary instances.
Do you really need a complete instance of ViewModel2 if all you need is the name? Avoid creating and passing more than you need to.
Update
If you capture the store in the first view/vm then why not pass that (ID &/or Name) to the second VM from the second view? The second VM can then send that to the server with the data captured in the second view.
Another approach may be to just use one viewmodel for both views. This may make your whole problem go away.
If you have properties in 1 view or view model that need to be accessed by a second (or additional) views or view models, I'd recommend creating a new class to store these shared properties and then injecting this class into each view model (or accessing it via the locator). See my answer here... Two views - one ViewModel
Here is some sample code still using the SimpleIoc
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<IMyClass, MyClass>();
}
public IMyClass MyClassInstance
{
get{ return ServiceLocator.Current.GetInstance<IMyClass>();}
}
Here is a review of SimpleIOC - how to use MVVMLight SimpleIoc?
However, as I mentioned in my comments, I changed to use the Autofac container so that my supporting/shared classes could be injected into multiple view models. This way I did not need to instantiate the Locator to access the shared class. I believe this is a cleaner solution.
This is how I registered MyClass and ViewModels with the Autofac container-
var builder = new ContainerBuilder();
var myClass = new MyClass();
builder.RegisterInstance(myClass);
builder.RegisterType<ViewModel1>();
builder.RegisterType<ViewModel2>();
_container = builder.Build();
ServiceLocator.SetLocatorProvider(() => new AutofacServiceLocator(_container));
Then each ViewModel (ViewModel1, ViewModel2) that require an instance of MyClass just add that as a constructor parameter as I linked initially.
MyClass will implement PropertyChanged as necessary for its properties.
Ok, my shot at an answer for your original question first is: Yes, I think it is bad to access one VM from another VM, at least in the way it is done in the code example of this question. For the same reasons that Matt is getting at - maintainability and testability. By "newing up" another ViewModelLocator in this way you hardcode a dependency into your view model.
So one way to avoid that is to consider Dependency Injection. This will make your dependencies explicit while keeping things testable. Another option is to use the Messenger class of MVVMLight that you also mention.
In order to write maintainable and testable code in the context of MVVM, ViewModels should be as loosely coupled as possible. This is where the Messenger of MVVMLight can help. Here's a quote from Laurent on what Messenger class was intended for:
I use it where decoupled communication must take place. Typically I use it between VM and view, and between VM and VM. Strictly speaking you can use it in multiple places, but I always recommend people to be careful with it. It is a powerful tool, but because of the very loose coupling, it is easy to lose the overview on what you are doing. Use it where it makes sense, but don't replace all your events and commands with messages.
So, to answer the more specific scenario you mention, where one view pops up another "store selection" view and the latter must set the current store when returning back to the first view, this is one way to do it (the "Messenger way"):
1) On the first view, use EventToCommand from MVVMLight on the TextBox in the first view to bind the desired event (eg. GotFocus) to a command exposed by the view model. Could be eg. named OpenStoreSelectorCommand.
2) The OpenStoreSelectorCommand uses the Messenger to send a message, requesting that the Store Selector dialog should be opened. The StoreSelectorView (the pop-up view) subscribes to this message (registers with the Messenger for that type of message) and opens the dialog.
3) When the view closes with a new store selected, it uses the Messenger once again to publish a message that the current store has changed. The main view model subscribes to this message and can take whatever action it needs when it receives the message. Eg. update a CurrentStore property, which is bound to a field on the main view.
You may argue that this is a lot of messaging back and forth, but it keeps the view models and views decoupled and does not require a lot code.
That's one approach. That may be "old style" as Matt is hinting, but it will work, and is better than creating hard dependencies.
A service-based approach:
For a more service-based approach take a look at this recent MSDN Magazine article, written by the inventor of MVVMLight. It gives code examples of both approaches: The Messenger approach and a DialogService-based approach. It does not, however, go into details on how you get values back from a dialog window.
That issue is tackled, without relying on the Messenger, in this article. Note the IModalDialogService interface:
public interface IModalDialogService
{
void ShowDialog<TViewModel>(IModalWindow view, TViewModel viewModel, Action<TViewModel> onDialogClose);
void ShowDialog<TDialogViewModel>(IModalWindow view, TDialogViewModel viewModel);
}
The first overload has an Action delegate parameter that is attached as the event handler for the Close event of the dialog. The parameter TViewModel for the delegate is set as the DataContext of the dialog window. The end result is that the view model that caused the dialog to be shown initially, can access the view model of the (updated) dialog when the dialog closes.
I hope that helps you further!

How to calculate figure's size including all sub figures (that have separate edit parts) in GEF?

I'm trying to draw diagram that contains a single entity which holds multiple elements inside.
My MVC structure looks something like this:
Model: contains EntityModel.java and ElementModel.java which represents my model objects.
View: EntityFigure.java and ElementFigure.java
Controller: EntityEditPart.java and ElementEditPart.java
I'm overriding getModelChildren() in EntityEditPart.java to return list of ElementModel.java so that is how GEF knows that an element "belongs" to an entity.
Since I would like to calculate my entity's figure size and include the embedded elements in this calculation, I cannot call entityFigure.getPreferredSize() during createFigure() in EntityEditPart.java since at this point - the elements figures do not exists (createFigure() in ElementEditPart.java is not invoked yet).
I'm looking for a place to set my entity figure after all child figures were created.
I though about overriding addNotify() in ElementEditPart.java, however, it is being called after creating a specific inner element and not after all elements created.
Any ideas?
Hope I was clear enough...
You can do it in an extension of
refreshChildren()
method of an edit part, since all the child creation is done in refreshChildren() of superclass's (AbstractEditPart) refresh method:
public void refresh() {
refreshVisuals();
refreshChildren();
}
Or, you can just extend
refresh()

QTreeView: Filtering contents - looking for best practices

I have a QTreeView in which I wish to filter the contents. I only wish to filter these contents on the top level children (the ones immediately below the root index). Currently I am accomplishing this by creating a simple filtering method in my QTreeView subclass and selectively hiding those rows that do not match.
While the above approach seems to work fine, I am wondering whether I should re-implement this using a QSortFilterProxyModel. If so, what would be the advantages?
If I change to using the QSortFilterProxyModel, I have a few (hopefully small) questions:
1) Since I am filtering only on the top-level children, I would have to re-implement whatever method was actually doing the sorting so that it would leave all the grandchildren alone, right?
2) My data model has a number of custom methods in it that are responsible for unique keyboard navigation and the like. Do I re-implement these in the proxy model and have them point to my data model's methods? If so, how to I reference the model? I can't seem to find any thing comparable to a QTreeView's model() method.
Thanks!
Using a derived class from QSortFilterProxyModel is better. You keep responsibilities of sorting outside of your tree view.
To reuse at maximum the existing code, you can override filterAcceptsRow like this
bool MySortFilterProxyModel::filterAcceptsRow(int sourceRow,
const QModelIndex &sourceParent) const
{
if( sourceParent.IsValid() ) return true; // Don't filter other than top level
return QSortFilterProxyModel( sourceRow, sourceParent );
}
For the custom methods, you will need to implement them in your proxy. Then for the navigation, you may need to used mapToSource and mapFromSource to convert proxy index to orignal model index

Mimicking SQL Insert Trigger with LINQ-to-SQL

Using LINQ-to-SQL, I would like to automatically create child records when inserting the parent entity. Basically, mimicking how an SQL Insert trigger would work, but in-code so that some additional processing can be done.
The parent has an association to the child, but it seems that I cannot simply add new child records during the DataContext's SubmitChanges().
For example,
public partial class Parent
{
partial void OnValidate(System.Data.Linq.ChangeAction action)
{
if(action == System.Data.Linq.ChangeAction.Insert)
{
Child c = new Child();
... set properties ...
this.Childs.Add(c);
}
}
}
This would be ideal, but unfortunately the newly created Child record is not inserted to the database. Makes sense, since the DataContext has a list of objects/statements and probably doesn't like new items being added in the middle of it.
Similarly, intercepting the partial void InsertParent(Parent instance) function in the DataContext and attempting to add the Child record yields the same result - no errors, but nothing added to the database.
Is there any way to get this sort of behaviour without adding code to the presentation layer?
Update:
Both the OnValidate() and InsertParent() functions are called from the DataContext's SubmitChanges() function. I suspect this is the inherent difficulty with what I'm trying to do - the DataContext will not allow additional objects to be inserted (e.g. through InsertOnSubmit()) while it is in the process of submitting the existing changes to the database.
Ideally I would like to keep everything under one Transaction so that, if any errors occur during the insert/update, nothing is actually changed in the database. Hence my attempts to mimic the SQL Trigger functionality, allowing the child records to be automatically inserted through a single call to the DataContext's SubmitChanges() function.
If you want it to happen just before it is saved; you can override SubmitChanges, and call GetChangeSet() to get the pending changes. Look for the things you are interested in (for example, delta.Inserts.OfType<Customer>(), and make your required changes.
Then call base.SubmitChanges(...).
Here's a related example, handling deletes.
The Add method only sets up a link between the two objects: it doesn't mark the added item for insertion into the database. For that, you need call InsertOnSubmit on the Table<Child> instance contained within your DataContext. The trouble, of course, is that there's no innate way to access your DataContext from the method you describe.
You do have access to it by implementing InsertParent in your DataContext, so I'd go that route (and use InsertOnSubmit instead of Add, of course).
EDITED I assumed that the partial method InsertParent would be called by the DataContext at some point, but in looking at my own code that method appears to be defined but never referenced by the generated class. So what's the use, I wonder?
In linq to sql you make a "trigger" by making a partial class to the dbml file, and then inserting a partial method. Here is an example that wouldn't do anything because it calls the build-in deletion.
partial void DeleteMyTable(MyTable instance)
{
//custom code here
ExecuteDynamicDelete(instance);
//or here :-)
}

Resources