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.
Related
I have the Expense screen that contains a textbox where I can put the price of an expense. As I have built lot of logic (validation etc.) for this textbox I would like to extract it to a separate control and reuse it on other screens. I'm trying to build it in the mvvm style. Here's what I have for now:
ExpenseView
<page>
<Label Text={Binding Date} />
<wpfControls:CurrencyTextBoxView ViewModel="{Binding PriceViewModel}" />
</page>
ExpenseViewModel
public class ExpenseViewModel : INotifyPropertyChanged
{
private ExpenseModel Model { get; }
public string Date
{
get { return Model.Date.ToString(); }
set
{
Model.Date = DateTime.Parse(value);
RaisePropertyChanged();
}
}
private CurrencyTextBoxViewModel _priceViewModel;
public CurrencyTextBoxViewModel PriceViewModel
{
get { return _priceViewModel; }
set { _priceViewModel = value; }
}
}
ExpenseModel
public class ExpenseModel
{
public DateTime Date { get; set; }
public decimal Price { get; set; } // This is the bit I don't know how to implement correctly
}
CurrencyTextBoxView
<control>
<TextBox Content={Binding Price} />
</control>
CurrencyTextBoxViewModel
public class CurrencyTextBoxViewModel : INotifyPropertyChanged
{
private CurrencyModel Model { get; }
public string Price
{
get { return Model.Price.ToString(); }
set
{
Model.Price = decimal.Parse(value);
RaisePropertyChanged();
}
}
}
CurrencyModel
public class CurrencyModel
{
public decimal Price { get; set; }
}
And now the problem is: I need to have the Price property in my ExpenseModel as well (as it's being saved in the db). I don't want to have the Date property in my CurrencyModel (as not always it makes sense).
Should I keep the CurrencyModel inside of my ExpenseModel? How would I update it efficiently when the text in the currency textbox would change?
Also the ExpenseModel is located in different project than the rest of my classes and I wouldn't like to make this project dependend on the project with the CurrencyModel.
Should I listen to PropertyChanged events from CurrencyTextBoxViewModel and update the ExpenseModel.Price whenever the CurrencyTextBoxViewModel.Price string changes? I like the way my view models act as parsers of models for views (the Date property example). Is there any way to implement the PriceViewModel so that its getter returns data straight from the ExpenseModel (so it acts as a parser)?
I know there are lot of ways to implement it but I'm looking for the most mvvm-consistent one. Also, I'm not sure if I have implemented the whole pattern correctly?
As for Date property, place it only on the view model if it needs no serialization.
How I would do:
If you want to reuse your CurrencyTextBox, I would not tie the control with the model. No CurrencyModel is needed. Prepare a DependencyProperty (say CurrencyText) on CurrencyTextBox. The view goes as:
<xxx:CurrencyTextBox CurrencyText="{Binding Currency}" />
You can omit CurrencyTextBoxViewModel because the CurrencyText can be bound to the internal TextBox.Text (or, just update relevant property on the view model). Anyway, with two-way binding, when CurrencyText is updated, ExpenseViewModel.Currency is also update, and here you can update your model.
I think it is ok to listen event of the model because the view model live shorter than the model. However you should block propagate event firing if the new value is the same as the old one.
The model serves as the origin of data and does business logic. The view model is just a shadow of the model, thus it is usually thin. Consider using some MVVM frameworks to get a sense of that.
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.
I am working on my first WPF/MVVM application, and I have come across a limitation in my knowledge of commands!
Here is my scenario.
I have a window - Customer.xaml.
It houses 2 usercontrols
viewCustomerSearch.xaml
viewCustomerDetails.xaml
Each of THOSE has it's own view model.
So, the hierarchy looks like this:
... Customer.xaml
... ... viewCustomerSearch.xaml
... ... ... viewmodelCustomerSearch.xaml
... ... viewCustomerDetails.xaml
... ... ... viewmodelCustomerDetails.xaml
I understand this to be a 'not uncommon' scenario.
For what it is worth, the user selects a customer by double clicking on a listview line in the viewCustomerSearch.xaml control.
The problem is: I now need to tell the viewmodelCustomerDetails.xaml class which customer the user has just selected. I cannot work this out at all.
Does anyone have any help on where I declare the command I need, how it gets hooked up, where the implementation code fires, etc?
Any help gratefully appreciated,
DS
Typically, to do inter-viewmodel communication, you can either:
Use standard .NET events, and use the parent view model as the mediator - in your case the Customer view model would have references to the 2 child view models, and can subscribe to events, and call appropriate methods on the child view models when the events are published
Use an event aggregator pattern
Frameworks such as Caliburn.Micro and Prism provide an implementation of the event aggregator pattern.
Alternatively, if you don't need completely decoupled view-models, then your Customers.xaml could set its DataContext to an instance of CustomersViewModel. The search view would inherit this data context, would bind it's list view to the list of customers, and would set the SelectedItem property in response to a double click. The detail view DataContext would be bound to the SelectedItem property.
public class CustomersViewModel : ViewModelBase
{
public Customer SelectedItem
{
get { return _selectedItem; }
set { Set(() => SelectedItem, ref _selectedItem, value); }
}
private Customer _selectedItem;
public IEnumerable<Customer> Customers { get; private set; }
}
public class Customer : ViewModelBase
{
private string _name;
public string Name
{
get { return _name; }
set { Set(() => Name, ref _name, value); }
}
...
}
I have a ViewModel class that looks like this.
class MyViewModel : Screen
{
public BindableCollection<MyObject> MyObjects { get; set; }
private MyObject selectedObject;
public MyObject SelectedMyObject
{
get { return selectedObject; }
set
{
selectedObject = value:
//some additional unrelated logic
}
}
public void SaveObject()
{
//some logic
}
public bool CanSaveObject{
get{
//logic to determine if the selectedObject is valid
}
}
That is the relevant code. Now the problem.
MyObject is a class with three properties. In the View I have a ListView that is bound to the MyObjects collection, and three TextBoxes that are bound to the SelectedItem in the ListView.
When I fill in the textboxes, the related object gets changed in the Model, but I want to make sure that the object is in a valid state before you can save it. CanSaveObject has the necessary logic, but the problem is that is never gets called since I don't have any oppurtunity to call NotifyOfPropertyChanged when the textboxes are filled since only the properties of selectedObject are called, and no properties on MyViewModel.
So the question is: Are there any good way to do this without making properties on the ViewModel that encapsulate the properties inside MyObject.
I have got it working if I make properties like these, and then bind to these instead of the SelectedItem directly in the view, but the viewmodel gets cluttered up in the hurry if hacks like this is the only way to do it. I hope it's not :)
public string SelectedObjectPropertyOne{
get{ return selectedObject.PropertyOne; }
set{
selectedObject.PropertyOne = value;
NotifyOfPropertyChange(() => SelectedObjectPropertyOne);
NotifyOfPropertyChange(() => CanSaveObject);
}
}
ActionMessage.EnforceGuardsDuringInvocation is a static boolean field that can be set to enforce a guard check when an action is about to be invoked. This will guard the actual Save action from being invoked, however it will not help with the issue of the UI appearance based on the guard state immediately after an update to the selected model.
Without doing that, the only other modification I could suggest would be to create a VM type for MyObject model and move the validation and save logic there. This would also allow you to simplify your Views...
MVVM pattern is implemented in my Silverlight4 application.
Originally, I worked with ObservableCollection of objects in my ViewModel:
public class SquadViewModel : ViewModelBase<ISquadModel>
{
public SquadViewModel(...) : base(...)
{
SquadPlayers = new ObservableCollection<SquadPlayerViewModel>();
...
_model.DataReceivedEvent += _model_DataReceivedEvent;
_model.RequestData(...);
}
private void _model_DataReceivedEvent(ObservableCollection<TeamPlayerData> allReadyPlayers, ...)
{
foreach (TeamPlayerData tpd in allReadyPlayers)
{
SquadPlayerViewModel sp = new SquadPlayerViewModel(...);
SquadPlayers.Add(sp);
}
}
...
}
Here is a peacie of XAML code for grid displaying:
xmlns:DataControls="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Data"
...
<DataControls:DataGrid ItemsSource="{Binding SquadPlayers}">
...</DataControls:DataGrid>
and my ViewModel is bound to DataContext property of the view.
This collection (SquadPlayers) is not changed after its creation so I would like to change its type to
List<SquadPlayerViewModel>
. When I did that, I also added
RaisePropertyChanged("SquadPlayers")
in the end of '_model_DataReceivedEvent' method (to notify the grid that list data are changed.
The problem is that on initial displaying grid doesn't show any record... Only when I click on any column header it will do 'sorting' and display all items from the list...
Question1: Why datagrid doesn't contain items initially?
Q2: How to make them displayed automatically?
Thanks.
P.S. Here is a declaration of the new List object in my view-model:
public List<SquadPlayerViewModel> SquadPlayers { get; set; }
You can't use List as a binding source, because List not implement INotifyCollectionChanged it is require for WPF/Silverlight to have knowledge for whether the content of collection is change or not. WPF/Sivlerlight than can take further action.
I don't know why you need List<> on your view model, but If for abstraction reason you can use IList<> instead. but make sure you put instance of ObservableCollection<> on it, not the List<>. No matter what Type you used in your ViewModel Binding Only care about runtime type.
so your code should like this:
//Your declaration
public IList<SquadPlayerViewModel> SquadPlayers { get; set; }
//in your implementation for WPF/Silverlight you should do
SquadPlayers = new ObservableCollection<SquadPlayerViewModel>();
//but for other reason (for non WPF binding) you can do
SquadPlayers = new List<SquadPlayerViewModel>();
I usually used this approach to abstract my "Proxied" Domain Model that returned by NHibernate.
You'll need to have your SquadPlayers List defined something like this:
private ObservableCollection<SquadPlayerViewModel> _SquadPlayers;
public ObservableCollection<SquadPlayerViewModel> SquadPlayers
{
get
{
return _SquadPlayers;
}
set
{
if (_SquadPlayers== value)
{
return;
}
_SquadPlayers= value;
// Update bindings, no broadcast
RaisePropertyChanged("SquadPlayers");
}
}
The problem is that whilst the PropertyChanged event informs the binding of a "change" the value hasn't actually changed, the collection object is still the same object. Some controls save themselves some percieved unnecessary work if they believe the value hasn't really changed.
Try creating a new instance of the ObservableCollection and assigning to the property. In that case the currently assigned object will differ from the new one you create when data is available.