I need to present an object differently, twice.
as a node in a TreeView (navigation/rename)
as 2 TextBoxes (rename/edit content)
public class Item
{
public string Name{get;set;}
public string Content{get;set;}
}
My first solution was to keep things simple:
public class MainViewModel
{
// collection of items (treeview navigation)
public BindingList<ItemViewModel> Items{get;set;}
// selected item (from treeview navigation)
// used for textbox edit
public ItemViewModel SelectedItem{get;set;}
}
public class ItemViewModel
{
// Used for treeview navigation
public bool IsSelected{get;set;}
public bool IsExpanded{get;set;}
public bool IsInEditNameMode{get;set;}
public BindingList<ItemViewModel> Children{get;set;}
public void BuildChildren();
// Used for treeview display/rename
// Used for textbox display/rename
public string Name{get;set;}
// Used for textbox edit
public string Content{get;set;}
}
This works well for a while.
But as the application grows more complex, the view model gets "polluted" more and more.
For example, adding additional presentations for the same view model (Advanced properties, Graph representation, etc)
public class ItemViewModel
{
// Used for Advanced properties
public BindingList<PropertyEntry> Properties {get;set;}
public PropertyEntry SelectedProperty{get;set;}
// Used for graph relationship
public BindingList<ItemViewModel> GraphSiblings{get;set;}
public bool IsGraphInEditNameMode{get;set;}
public bool IsSelectedGraphNode {get;set;}
public void BuildGraphSiblings();
// Used for treeview navigation
public bool IsNavigationInEditNameMode{get;set;}
public bool IsSelectedNavigationNode{get;set;}
public bool IsExpandedNavigationNode{get;set;}
public BindingList<ItemViewModel> NavigationChildren{get;set;}
public void BuildNavigationChildren();
// Used for treeview display/rename
// Used for textbox display/rename
// Used for graph display
// Used for Advanced properties display
public string Name{get;set;}
// Used for textbox edit
public string Content{get;set;}
}
Currently, I'm still using a single view model for multiple presentations, because it keeps the selected item in-sync across all presentation.
Also, I do not have to keep duplicating properties (Name/Content).
And finally, PropertyChanged notification helps updates all presentation of the item (ie, changing Name in navigation updates TextBox/Graph/Advanced properties/etc).
However, it also feels like a violation of several principles (single responsibility, least privilege, etc).
But I'm not quite sure how to refactor it, without writing a lot of code to keep the sync/property notification working/duplicating the model's properties across each new view model/etc)
What I would like to know:
If it were up to you, how would you have solved this?
At the moment, everything is still working. I just feel like the code could be further improved, and that's what I need help with.
How about using inheritance? Have a basic ItemViewModel, then subclass it to create a TreeViewItemViewModel, where you add the properties that relate to the tree-view rendering of this item within the subclass.
Could we,
try separating-out various view-specific-behaviors from the ItemViewModel class.
place/encapsulate the view-specific-behaviors in separate class (Behavior classes).
This gives you flexibility at run-time to instantiate/inject/switch behaviors.
Yes, try to use Strategy pattern for making a cleaner, single responsible, easy to maintain code.
Related
I'm having two properties, one is a collection named Items and the other is an Item. The collection is bind to a Datagrid and when I double click on the datagrid, the selection is loaded into Item that is binded to a textbox via Item.Name. When I modify the text into the textbox, the changes are reflected into the Datagrid thanks to UpdateSourceTrigger=PropertyChanged. My problem is that when I cancel this changes, I reload the entity from db with _context.Entry(Item).Reload(); but the OnPropertyChanged it's never triggered. I also tried to call OnPropertyChanged(nameof(Item)) after the reload but with no succes. The only thing that seems to work is the fallowing:
_itemRepository.Reload(Item);
Item.Name = Item.Name;
//OnPropertyChanged(nameof(Post));
Is this a bug or a feature? How can I update my UI without using this hack.
I'm using WPF on .NET 5 with EF Core 5.
Item Model:
public class Item : BaseEntity
{
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
Update:
Until I have some time to test in a minimal environment, like BionicCode sugested, I got a quick fix:
Datagrid.Items.Refresh();
Entity Framework is disconnecting the entity from the PropertyChanged infrastructure to improve performance during internal entity manipulations. You would have to enable property change propagation explicitly by configuring the DbContext and its associated model(s) to use the appropriate tracking strategy.This is done by using the ModelBuilder.
For example, if your entity is implementing INotifyPropertyChanged alone, setting the change tracking strategy to ChangeTrackingStrategy.ChangedNotifications would be sufficient. If it also implements INotifyPropertyChanging use ChangeTrackingStrategy.ChangingAndChangedNotifications or any other appropriate enumeration value that includes enabling changed notifications. See ChangeTrackingStrategy enum to find more available configuration values or to get an explanation.
To use the ModelBuilder you must override the virtual DbContext.OnModelCreating method in your DbContext:
public class ItemsContext : DbContext
{
public DbSet<Item> Items { get; set; }
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Item>()
.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications);
}
}
I'm using the MVVM pattern in our WPF application to allow for comprehensive unit testing. The MVVM pattern itself is working great, however I'm struggling to adapt the pattern in a way that means I can use the design-time data support of WPF.
As I'm using Prism the ViewModel instances are generally injected into the constructor of the view, like so
public MyView(MyViewModel viewModel)
{
DataContext = viewModel;
}
Dependencies for the ViewModel are then injected into the constructor, like so
public class MyViewModel
{
public MyViewModel(IFoo foo, IBar bar)
{
// ...
}
// Gets and sets the model represented in the view
public MyModel { get; set; }
// Read-only properties that the view data binds to
public ICollectionView Rows { get; }
public string Title { get; }
// Read-write properties are databound to the UI and are used to control logic
public string Filter { get; set; }
}
This is generally working really well except when it comes to design data - I wanted to avoid compiling design-data specific classes into my released assembly and so I opted to use the {d:DesignData} approach instead of the {d:DesignInstance} approach, however in order for this to work correctly my ViewModel now needs to have a parameterless constructor. In addition, I also often need to change additional properties either to have setters or to be modifiable collections in order to be able to set these properties in XAML.
public class MyViewModel
{
public MyViewModel()
{
}
public MyViewModel(IFoo foo, IBar bar)
{
// ...
}
// Gets and sets the model represented in the view
public MyModel { get; set; }
// My read-only properties are no longer read-only
public ObservableCollection<Something> Rows { get; }
public string Title { get; set; }
public string Filter { get; set; }
}
This is worrying me:
I have a parameterless constructor that is never intended to be called and isn't unit tested
There are setters for properties that only the ViewModel itself should be calling
My ViewModel is now a jumbled mixture of properties that should be modified by the view, and those that shouldn't - this makes it tricky to tell at a glance which piece of code is responsible for maintaining any given property
Setting certain properties at design time (e.g. to see styling on the Filter text) can actually end up invoking ViewModel logic! (so my ViewModel also needs to be tollerant of otherwise mandatory dependencies being missing at design time)
Is there a better way to get design-time data in a WPF MVVM application in a way that doesn't compromise my ViewModel in this way?
Alternatively should I be building my ViewModel differently so that it has more simple properties with the logic separated out somewhere else.
First, I would recommend you to have a look at this video where Brian Lagunas provides several best practices about MVVM. Brian is - at least - involved in the development of Prism, as his name appears in the nuget packages information. Didn't check further.
On my side I only use bits of Prism, and my Model and ViewModel always offer blank constructors (like what Brian shows), the data context is assigned in the view's XAML, and I set the properties values like :
<MyView.DataContext>
<MyViewModel />
</MyView.DataContext>
and
public void BringSomethingNew()
{
var myView = new View();
(myView.DataContext as ViewModel).Model = myModel;
UseMyView();
}
Another benefit with this approach is that the ViewModel is created once, with the same path at design and run time, so you create less objects and save GC efforts. I find this elegant.
With regards to the setters, the design data will still work if you make them private, like:
public string MyProp { get; private set; }
Ok, customize it to manage NotifyPropertyChange at your convenience, but you've got the idea.
Now, I don't have yet a solution to manage ObesrvableCollections (I face the same problem, although putting multiple values in XAML sometimes work... ???), and yes, I agree that you have to manage the case when the properties are not set, like setting default values in the constructor.
I hope this helps.
I too have worked with NUnit testing with WPF and MVVM implementation. However, my version is reversed from yours. You are creating the view first, then creating the model to control it.
In my version, I create the MVVM model FIRST and can unit test it till the cows come home and not worry about any visual design... if the model is broken, so too will the visual implementation.
in my MVVM model, I have a method to "GetTheViewWindow". So, when I derive from my MVVM baseline, each view model has its own view its responsible for. So via a virtual method, each instance will do its own new view window when being applied for production.
public class MyMVVMBase
{
private MyViewBaseline currentView;
public MyMVVMBase()
{ // no parameters required }
public virtual void GetTheViewWindow()
{ throw new exception( "You need to define the window to get"; ) }
}
public class MyXYZInstanceModel : MyMVVMBase
{
public override void GetTheViewWindow()
{
currentView = new YourActualViewWindow();
}
}
Hope this helps as an alternative to what you are running into.
I have recently started learning wpf and am trying to use mvvm.
My understanding is that in the mvvm neither the view or the model should know the other exists.
What I am trying to do is show a list of customers on the screen. But if I code the viewModel as shown below. which is similar to many examples I see on the net, then I end up with some code looking like this
class Customer
{
public String Name {get;set;}
public String Address {get;set;} }
}
class MainWindowViewModel
{
ObservableCollection<Customer> customers = new ObservableCollection<Customer>();
public ObservableCollection<Customer> Customer
{
get {return customers;}
}
public MainWindowViewModel()
{
//cust1 and cust2 are Customer objets
customers.Add(cust1);
customers.Add(cust2);
}
}
Now if I create an instance of my MainWindowViewModel and set it as the datacontext of my MainWindowView (my view) and i further bind the viewmodels Customers property to a listBox, then the view will need a reference to the assembly that contains my Models.
So my questions are.
1) Is adding a reference to Models assembly allowable in MVVM, since this would mean the view knows about the model.
2) would a better solution be to wrap each Customer object in a CustomerViewModel and have the MainWindowViewModel contain ObservableCollection of CustomerViewModel
instead of ObservableCollection of Customer. This would separate the models completely from the view.
I'm not sure why you think the project containing your views requires a reference to your model project? There is nothing in your view which references your models directly - your binding expressions in XAML are linked by name only, and to properties on your view model anyway, not your model.
Wrapping your model in a view model is a good option if your view requires additional data than your model provides, and it is undesirable to change your model. For example, you view may need to display the Age of a User type, but User only has a DateOfBirth property. Creating a UserViewModel with an Age property would be a good option if you didn't want to alter your model.
Answers to your questions:
What is bad about the View referring the Model? This is absolutely ok when it simplifies the code. Just the other way around (Model -> View) is bad practice.
You don't need to wrap each Customer object in a CustomerViewModel when you don't have special needs. I would suggest to follow a pragmatic way and keep the code simple.
You might be interested in the BookLibrary sample application of the WPF Application Framework (WAF) which shows the scenario you describe here.
We usually create a CustomerViewModel. That is enforced by our generic CollectionViewModelBase class. This unsures that every part the user interface uses is exspecially created to be displayed and we don't have any UI related code in the models which are often serializable POCOs.
The MVVM pattern is similar to any other MVx pattern (MVC, MVP, ...) in that it encourages separation of concerns (SoC), which in turn improve maintainability / testability of your code. Beyond the usual SoC, MVVM gives the following:
Unit testing of your view logic; this is because you move logic from your view into your view-model, making your view as dumb as possible.
Developer-designer workflow; because the view is 'dumb', it is easier to work with the XAML without the logic behind it.
Regarding visibility, i.e. what is visible to what, it is strictly as follows:
Model <= ViewModel <= View
In other words, the ViewModel can see the Model, but the Model cannot see the ViewModel. Likewise, the View can see the ViewModel, but not vice-versa.
Because the ViewModel has no reference to the View, it enables your code to be executed without any view components present, this enables (1) above.
The purpose of your ViewModel is to 'shape' your Model to make binding to the View easier. If your View is simple, then it is quite acceptable to do the following:
Model <= View
This still allows (1) unit testing, (2) developer-designer workflow.
It is also fine to use a hybrid approach, sometimes exposing your Model to your view, other times wrapping it in a ViewModel. For example:
http://www.scottlogic.co.uk/blog/colin/2009/08/the-mini-viewmodel-pattern/
Please don't create a bunch of boiler-plate ViewModel code, just because you think you have to!
You will definitively want to wrap your models in view only objects like below :
/// <summary>
/// Business model object : Should be in your separate business model only library
/// </summary>
public class BusinessModelObject
{
public string Prop1 { get; set; }
public int Prop2 { get; set; }
}
/// <summary>
/// Base notifying object : Should be in your GUI library
/// </summary>
public abstract class NotifyingObject<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
private static readonly PropertyChangedEventArgs ModelPropertyChanged = new PropertyChangedEventArgs("Model");
private T _model;
public T Model
{
get { return _model; }
set
{
_model = value;
NotifyPropertyChanged(ModelPropertyChanged);
}
}
}
/// <summary>
/// Model decorator : Should be in your GUI library
/// </summary>
public class BusinessModelObjectAdapter : NotifyingObject<BusinessModelObject>
{
public BusinessModelObjectAdapter(BusinessModelObject model)
{
this.Model = Model;
}
private static readonly PropertyChangedEventArgs Prop1PropertyChanged = new PropertyChangedEventArgs("Prop1");
private string _prop1;
public string Prop1
{
get { return Model.Prop1; }
set
{
Model.Prop1 = value;
NotifyPropertyChanged(Prop1PropertyChanged);
}
}
private static readonly PropertyChangedEventArgs Prop2PropertyChanged = new PropertyChangedEventArgs("Prop2");
private int _prop2;
public int Prop2
{
get { return Model.Prop2; }
set
{
Model.Prop2 = value;
NotifyPropertyChanged(Prop1PropertyChanged);
}
}
//and here you can add whatever property aimed a presenting your model without altering it at any time
}
Using Linq pad I created a view on data in the database which I now hope to replicate in a WPF Application.
I took advantage of the Linq Dump() method. By implementing ICustomMemberProvider I could provide the column headers, types and values which I wanted to be output. The three methods I needed to implement were;
public IEnumerable<string> GetNames()
public IEnumerable<Type> GetTypes()
public IEnumerable<object> GetValues()
This was a simple, quick and clean way of describing what the Dump()' for single or multiple rows should be.
For the life of me I cannot find anything as straight forward in WPF. I have a dynamic (per run not per row) number of Columns and so I can't hard code Column titles and binding paths, there may be 5 columns and there may be 20.
I was pointed towards ICustomTypeDescriptor but I need a concrete example of how that would work as there are so many methods in that interface.
I'm really hoping there's something simpler that I've missed which will allow me to implement dynamically what the rows and columns should contain given an IEnumerable of my custom class.
Any links to a tutorial or overview of how this is meant to work would be greatly appreciated. I have been surprised by the lack of documentation I've found so I must be using the wrong terms.
For clarity the source of a single row is an instance of a class like this;
public class CustomDatum
{
public string ID {get; private set;}
public string Location {get; private set;}
public IEnumerable<Attributes> attributes {get; private set;}
public class Attribute
{
public string Name {get; private set;}
public string Value {get; private set;}
public override ToString()
{
....
}
}
}
I want to display the ID, Location and all attributes in a single Row, I have an IEnumerable<CustomDatum> to bind to. The actual class is a lot more complex than this example naturally.
Thanks!
I'm pretty sure you can use a DataGridView and set its AutoGenerateColumns to true.
example of ICustomTypeDescriptor
suppose the following classes:
public class Model
{
public ObservableCollection<A> Items { get; set; }
}
public class A
{
ObservableCollection<B> Data { get; set; }
//... some more properties
}
public class B
{
//..properties
}
The model is bound to a RibbonMenu and should also be used in a context menu. The context menu must be bound to all items of class B in the model. Changes in the model (new items added, items removed, items changed ...) should change both the context menu and the RibbonMenu.
The RibbonMenu works nicely but how is it possible to bind the contextmenu without creating a separate model?
You could create wrapper properties that flatten your A and B entities as needed for the view controls and expose them publicly from Model.
So, for instance, in Model, you have a private backer of ObservableCollection<A>. Then you have a public ObservableCollection<A> that simply returns the private backer for the ribbon to bind to.
Then also have a public ObservableCollection<B> that does whatever it needs to do in its getter to return what you want for the context menu. For example, if you want the distinct Bs across all As, have the getter do a query on all of A's Bs to return the correct list.
Finally, to tell the view that changes were made in Model, implement INotifyPropertyChanged and raise the PropertyChanged event in the setters of your public members.