I'm trying to understand MVVM for WPF applications
In the example below, we use a delegate that inherits from ICommand, then in our ViewModel, we instantiate the delegate and provide the appropriate implementation
My Question is why can't we just make the ViewModel implement ICommand?
ViewModel :
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
InitializeViewModel();
}
protected void InitializeViewModel()
{
DelegateCommand MyCommand = new DelegateCommand<SomeClass>(
SomeCommand_Execute, SomeCommand_CanExecute);
}
void SomeCommand_Execute(SomeClass arg)
{
// Implementation
}
bool SomeCommand_CanExecute(SomeClass arg)
{
// Implementation
}
}
DelegateCommand :
public class DelegateCommand<T> : ICommand
{
public DelegateCommand(Action<T> execute) : this(execute, null) { }
public DelegateCommand(Action<T> execute, Predicate<T> canExecute) : this(execute, canExecute, "") { }
public DelegateCommand(Action<T> execute, Predicate<T> canExecute, string label)
{
_Execute = execute;
_CanExecute = canExecute;
}
.
.
.
}
The reason would be having a one to many relationship between your view and your number of commands.
You typically will have one ViewModel for every View. But you may want to have many Commands for a single view. If you were to use your ViewModel as a Command, you would have to have multiple instances of your ViewModel.
The typical implementation would be that your ViewModel would contain instances of all of the Commands your View needs.
Short answer: because your ViewModel isn't a command.
Moreover, your ViewModel can hold multiple commands.
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
InitializeViewModel();
OpenCommand = new DelegateCommand<SomeClass>(
param => { ... },
param => { return true; });
SaveCommand = new DelegateCommand<SomeClass>(
param => { ... },
param => { return true; });
SaveAsCommand = new DelegateCommand<SomeClass>(
param => { ... },
param => { return true; });
}
public ICommand OpenCommand { get; private set; }
public ICommand SaveCommand { get; private set; }
public ICommand SaveAsCommand { get; private set; }
}
Now you can binding those commands to your view, because they are a property.
You can implement ICommand this way - and this is a very common way of implementing ICommand. That being said, you still need to make MyCommand a property on the ViewModel in order to bind to it.
Related
In our app we use ReactiveUI to follow the MVVM pattern. In one view we want to show a UITableView. Data are usually passed to a UITableView.Source but with ReactiveUI we use ReactiveTableViewSource<TSource>.
What I don't understand is how I can bind my data that I got in my view model in a ReactiveList to the ReactiveTableViewSource.
What we now have is that we create a table inside a UIView like this:
Table = new UITableView(UIScreen.MainScreen.Bounds);
Table.Source = new TableSource(Table, MyReactiveList, (NSString) CellIdentifier, _cellHeight, cell => Debug.WriteLine(cell));
Btw: What is the cell action used for?
Furthermore we have a Table source class that looks like this:
internal sealed class TableSource : ReactiveTableViewSource<MyListObject>
{
public TableSource(UITableView tableView, IReactiveNotifyCollectionChanged<MyListObject> collection, NSString cellKey, float sizeHint, Action<UITableViewCell> initializeCellAction = null) : base(tableView, collection, cellKey, sizeHint, initializeCellAction)
In my view model I have a service that is updating my ReactiveList. It looks like this:
public sealed class MyService : ReactiveObject, IMyService
{
public IReactiveList<MyListObject> MyReactiveList { get; }
public async Task UpdateMyReactiveListAsync() {//...}
Where do I bind the table source to the ReactiveList? Where do I subscribe for events? Is there any documentation or example code I might have missed?
Working with ReactiveTableViewSource is quite easy:
Just connect the List with your UITableView
var tableView = new UITableView ();
// Bind the List agains the table view
// SampleObject is our model and SampleCell the cell
ViewModel.WhenAnyValue (vm => vm.Items).BindTo<SampleObject, SampleCell> (tableView, 46, cell => cell.Initialize());
Then create a custom cell where you bind the model data against the cell.
public class SampleCell : ReactiveTableViewCell, IViewFor<SampleObject>
{
public SampleCell () : base() { }
public SampleCell (IntPtr handle) : base(handle) { }
private SampleObject _viewModel;
public SampleObject ViewModel
{
get { return _viewModel; }
set { this.RaiseAndSetIfChanged (ref _viewModel, value); }
}
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = value as SampleObject; }
}
public void Initialize()
{
this.WhenAnyValue (v => v.ViewModel.Name).BindTo (
this,
v => v.TextLabel.Text);
}
}
A compelling example you can find here: https://github.com/reicheltp/ReactiveTableViewSource-Sample
Update 2016/03/09: Better do binding in a separated Initialize method to prevent multiple calls.
If you have more questions you can ask me on twitter: #reicheltp
May be you miss this https://github.com/reactiveui/ReactiveUI/blob/275eca3dc2e5fc93bddb137e60be32885f788688/docs/basics/rx-cocoa-delegates.md
To subscribe the event you can assign a UITableViewDelegateRx
var tvd = new UITableViewDelegateRx ();
Table.Delegate = tvd;
tvd.RowSelectedObs.Subscribe (c => { });
Just to make a small update using ReactiveUI version 9.16.6:
Example that lists some cities with their according postal codes.
=> My ViewController inherites from ReactiveViewController
=> My CityViewModel inherites from ReactiveObject
=> My CityItemViewModel inherites from ReactiveObject
=> My CityTableViewCell inherites from ReactiveTableViewCell
public class CityViewModel : ReactiveObject
{
private ICityService CityService { get; }
private SourceCache<CityItemViewModel, int> _citiesCache;
private IObservable<IChangeSet<CityItemViewModel, int>> _citiesOperations => _citiesCache.Connect();
public readonly ReadOnlyObservableCollection<CityItemViewModel> Cities;
public CityViewModel(ICityService cityService)
{
CityService = cityService;
}
#region CityViewModelCommand
public ReactiveCommand<CityItemViewModel, Unit> CityClickCommand { get; }
#endregion
private async Task<IEnumerable<CityItemViewModel>> SearchCitiesAsync(string searchText, CancellationToken token)
{
IEnumerable<CityItemViewModel> items;
if (string.IsNullOrEmpty(searchText))
items = await CityService.ToListWithCountryCodeAsync(cancellationToken: token);
else
items = await CityService.ToListBySearchWithCountryCodeAsync(searchText, cancellationToken: token);
_citiesCache.Edit(innerList =>
{
innerList.Clear();
innerList.AddOrUpdate(items);
});
return items;
}
}
public class CityItemViewModel : ReactiveObject
{
public int Id { get; set; }
public string Name { get; set; }
public string PostalCode { get; set; }
public override string ToString()
=> $"[CityItemViewModel: Id={Id}, Name={Name}, PostalCode={PostalCode}]";
public CityItemViewModel() : base()
{
}
}
In my ViewController's ViewDidLoad method:
this.WhenActivated((cleanup) =>
{
this.Bind(ViewModel,
x => x.SearchText,
x => x.searchBar.Text)
.DisposeWith(cleanup);
this.WhenAnyValue(v => v.ViewModel.Cities)
.BindTo<CityItemViewModel, CityTableViewCell>(tableView, CityTableViewCell.Key, 50, cell => cell.Initialize(),
source => source.ElementSelected.InvokeCommand(ViewModel.CityClickCommand))
.DisposeWith(cleanup);
});
In my Initialize method of my UITableViewCell:
public void Initialize()
{
this.WhenAnyValue(v => v.ViewModel.Name)
.Subscribe(name => cityLabel.Text = name);
this.WhenAnyValue(v => v.ViewModel.PostalCode)
.Subscribe(x => postalCodeLabel.Text = x);
}
Hope that can help someone ;)
I use Automapper to map my domain model to my viewModel and this works great. Atm I'm just prototyping and changing the Model a lot, so at this point my viewModel is almost an exact copy of my Model and my viewModel references classes from the domain for its complex types (so I only have to keep the root class of my viewModel in sync with my domain model).
Although mapping from the domain model to the viewModel works great, mapping the viewModel back to the domain model doesn't work very well. The values directly in the viewModel do map, but the lists of a complex type don't. How do i fix this?
This is a simple representation of my models:
public class model
{
public int someValue { get; set; }
public virtual ICollection<ComplexType> aList { get; set; }
}
public class viewModel
{
public int someValue { get; set; }
public virtual ICollection<ComplexType> aList { get; set; }
}
public class ComplexType
{
public int someOtherValue { get; set; }
}
In this case they both model and viewModel reference the same file for ComplexType so these can't differ.
Did you use ReverseMap when mapping from ViewModel to Domain Model?
public class CustomProfile : Profile
{
protected override void Configure()
{
// Mapper.CreateMap<Model, ViewModel>();
// Mapper.CreateMap<ViewModel, Model>();
Mapper.CreateMap<Model, ViewModel>().ReverseMap();
}
}
This works perfectly fine and maps everything with no issue.
[TestInitialize]
public void Initialize()
{
Mapper.Initialize(conf => conf.AddProfile(new CustomProfile()));
}
[TestMethod]
public void AssertConfiguration()
{
Mapper.AssertConfigurationIsValid();
}
[TestMethod]
public void Test()
{
var model = new Model()
{
ComplexTypes = new Collection<ComplexType>() { new ComplexType() { SomeOtherValue = 1 }, new ComplexType() { SomeOtherValue = 4 } },
SomeValue = 3
};
var viewModel = Mapper.Map<ViewModel>(model);
Assert.AreEqual(model.SomeValue, viewModel.SomeValue);
Assert.AreEqual(model.ComplexTypes.Count, viewModel.ComplexTypes.Count);
Assert.AreEqual(model.ComplexTypes.ElementAt(0), viewModel.ComplexTypes.ElementAt(0));
Assert.AreEqual(model.ComplexTypes.ElementAt(1), viewModel.ComplexTypes.ElementAt(1));
model = Mapper.Map<Model>(viewModel);
Assert.AreEqual(viewModel.SomeValue, model.SomeValue);
Assert.AreEqual(viewModel.ComplexTypes.Count, model.ComplexTypes.Count);
Assert.AreEqual(viewModel.ComplexTypes.ElementAt(0), model.ComplexTypes.ElementAt(0));
Assert.AreEqual(viewModel.ComplexTypes.ElementAt(1), model.ComplexTypes.ElementAt(1));
}
I've got a ValidationAttribute that looks like this:
public class RegistrationUniqueNameAttribute : ValidationAttribute
{
public IRepository<User> UserRepository { get; set; }
public override bool IsValid(object value)
{
//use UserRepository here....
}
}
In my container setup (in app start) I have this:
builder.Register(c => new RegistrationUniqueEmailAttribute
{
UserRepository = c.Resolve<IRepository<User>>()
});
However, when debugging, the value of UserRepository is always null, so the property isn't getting injected.
Have I set up my container wrong?
I'd really rather not have to use DependencyResolver.Current.GetService<IRepository<User>>() as this isn't as testable...
No, Autofac v3 doesn't do anything special with ValidationAttribute and friends [Autofac.Mvc does lots of powerful things e.g., with filter attributes].
I solved the problem indirectly in this answer, enabling one to write:
class MyModel
{
...
[Required, StringLength(42)]
[ValidatorService(typeof(MyDiDependentValidator), ErrorMessage = "It's simply unacceptable")]
public string MyProperty { get; set; }
....
}
public class MyDiDependentValidator : Validator<MyModel>
{
readonly IUnitOfWork _iLoveWrappingStuff;
public MyDiDependentValidator(IUnitOfWork iLoveWrappingStuff)
{
_iLoveWrappingStuff = iLoveWrappingStuff;
}
protected override bool IsValid(MyModel instance, object value)
{
var attempted = (string)value;
return _iLoveWrappingStuff.SaysCanHazCheez(instance, attempted);
}
}
(And some helper classes inc wiring to ASP.NET MVC...)
In MVVM application with clean model (not implementing interfaces like INotifyPropertyChabged), the View Model Contains properties bound to the View and these properties get its values from the model object contained in the view model and should set the value of its properties when view changes one of the controls that are bound to these properties.
the propblem is when the view change; the changes are captured by the bound view model properties but the properties can't set the model object fields, the model doesn't change. I need the model fields to accept setting by the view model properties, then i can persist the updated model into the database taking into account that it is a clean model.
Here part of the view model code
public class SubsystemDetailsViewModel: INotifyPropertyChanged, ISubsystemDetailsViewModel
{
#region Fields
//Properties to which View is bound
private int? _serial;
public int? Serial
{
get { return Subsystem.Serial; }
set
{
//Subsystem.Serial=value;
_serial = value;
OnPropertyChanged("Serial");
}
}
private string _type;
public string Type
{
get { return Subsystem.Type; }
set
{
//Subsystem.Type = value;
_type = value;
OnPropertyChanged("Type");
}
}
//remaining properties ....
#endregion
//Service
private readonly ISubsystemService _subsystemService;
//Reference to the View
public ISubsystemDetailsView View { get; set; }
//Event Aggregator Event
private readonly IEventAggregator eventAggregator;
//Commands
public ICommand ShowTPGCommand { get; set; }
public DelegateCommand UpdateCommand { get; set; }
//
private bool _isDirty;
//Constructor ************************************************************************************************
public SubsystemDetailsViewModel(ISubsystemDetailsView View, ISubsystemService subsystemService, IEventAggregator eventAggregator)
{
_subsystemService = subsystemService;
this.View = View;
View.VM = this;
//EA-3
if (eventAggregator == null) throw new ArgumentNullException("eventAggregator");
this.eventAggregator = eventAggregator;
//Commands
this.ShowTPGCommand = new DelegateCommand<PreCommissioning.Model.Subsystem>(this.ShowTestPacks);
this.UpdateCommand = new DelegateCommand(this.UpdateSubsystem, CanUpdateSubsystem);
}
//****************************************************************************************************************
//ICommand-3 Event Handler
//this handler publish the Payload "SelectedSubsystem" for whoever subscribe to this event
private void ShowTestPacks(PreCommissioning.Model.Subsystem subsystem)
{
eventAggregator.GetEvent<ShowTestPacksEvent>().Publish(SelSubsystem);
}
//===============================================================================================
private void UpdateSubsystem()
{
_subsystemService.SaveChanges(Subsystem);
}
private bool CanUpdateSubsystem()
{
return _isDirty;
}
//*******************************************************************************************
public void SetSelectedSubsystem(PreCommissioning.Model.Subsystem subsystem)
{
this.SelSubsystem = subsystem;
}
//************************************************************************************************************
/// <summary>
/// Active subsystem >> the ItemSource for the View
/// </summary>
private PreCommissioning.Model.Subsystem _subsystem;
public PreCommissioning.Model.Subsystem Subsystem
{
get
{
//return this._subsystem;
GetSubsystem(SelSubsystem.SubsystemNo);
return this._subsystem;
}
set
{
if (_subsystem != value)
{
_subsystem = value;
OnPropertyChanged("Subsystem");
}
}
}
//Call the Service to get the Data form the Database
private void GetSubsystem(string SSNo)
{
this._subsystem = _subsystemService.GetSubsystem(SSNo);
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
_isDirty = true;
UpdateCommand.RaiseCanExecuteChanged();
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
Subsystem is the model object which is populated using GetSubsystem() method. the view model properties like Serial get its value from the model as shown. i tried to set the model properties as shown in the commented out line in set part of the property but no change happen to the Subsystem object, always keep its original values
If GetSubsystem returns a new subsystem every time, that's your problem. In the 'set' for the properties you're binding to the view, you're calling the public property "Subsystem", not the private field you've created. So, every single time you set a property from the view, you are calling Subsystem.get which calls GetSubsystem(SelSubsystem.SubsystemNo);.
I think, in your ViewModel properties', you want to change it to:
//Properties to which View is bound
public int? Serial
{
get { return _subsystem.Serial; }
set
{
_subsystem.Serial=value; // NOTE THE USE OF THE PRIVATE FIELD RATHER THAN THE PROPERTY
OnPropertyChanged("Serial");
}
}
public string Type
{
get { return _subsystem.Type; }
set
{
_subsystem.Type = value; // NOTE THE USE OF THE PRIVATE FIELD RATHER THAN THE PROPERTY
OnPropertyChanged("Type");
}
You need to have a reference in your view-model to the model and the view-model will pass the values to the model. Your view-model will implement INotifyPropertyChanged and will be the datacontext of your view. In your view-model, write your bound properties like this:
private string yourProperty;
public string YourProperty
{
get { return yourProperty; }
set
{
if (value == yourProperty)
return;
yourProperty= value;
YOUR_MODEL_REFERENCE.YourProperty= yourProperty;
this.RaisePropertyChanged(() => this.YourProperty);
}
}
I have two questions regarding communication between ViewModels.
I am developing a customer management program. I'm using Laurent Bugnion's MVVM Light framework.
In the main page, there's a list of customers. when each customer is clicked, a child windows shows up with information about that customer. the user should be able to open up multiple child windows at the same time and compare information between customers. how do you pass customer object from the main page's ViewModel to the child window's ViewModel in an MVVM-friendly fashion?
In the child window that shows customer information, there are a number of tabs, each showing different areas of information. I've created separate ViewModels for each of the tabs. how can you share the current customer information between each tab's viewmodels?
Thanks a lot!
In my project I'm passing ViewModels to child windows too. I create a dependency property for the ViewModel in my child window's code behind and in the setter of this property I pass the ViewModel along to my child window's ViewModel. This means you're creating a separate ViewModel class just for your child window.
To answer your second question, you could have your child window's ViewModel contain properties that each tab cares about, but have their data context still be the same as the child window's data context so they have access to shared properties. This is actually very easy since they automatically get the child window's data context.
Here's an example illustrating the two concepts above.
The child window view DetailsWindow.xaml (note that I've gotten in the habit of naming my child window views *Window.xaml instead of *View.xaml)
<controls:ChildWindow x:Class="DetailsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:Views="clr-namespace:Views"
Title="Details"
DataContext="{Binding DetailsWindowViewModel, Source={StaticResource Locator}}"
>
<Grid>
<sdk:TabControl>
<sdk:TabItem Header="First Tab" Content="{Binding FirstTabContent}" />
<sdk:TabItem Header="Second Tab" Content="{Binding SecondTabContent}" />
</sdk:TabControl>
</Grid>
</controls:ChildWindow>
The child window view's code behind DetailsWindow.xaml.cs and its interface IDetailsWindow.cs
public partial class DetailsWindow : ChildWindow, IDetailsWindow
{
private IDetailsWindowViewModel ViewModel
{
get { return this.DataContext as IDetailsWindowViewModel; }
}
public DetailsWindow()
{
InitializeComponent();
}
#region Customer dependency property
public const string CustomerViewModelPropertyName = "Customer";
public ICustomerViewModel Customer
{
get
{
return (ICustomerViewModel)GetValue(CustomerViewModelProperty);
}
set
{
SetValue(CustomerViewModelProperty, value);
if (ViewModel != null)
{
ViewModel.Customer = value;
}
}
}
public static readonly DependencyProperty CustomerViewModelProperty = DependencyProperty.Register(
CustomerViewModelPropertyName,
typeof(ICustomerViewModel),
typeof(CustomerDetailsWindow),
null);
#endregion
}
public interface IDetailsWindow
{
ICustomerViewModel Customer { get; set; }
void Show();
}
The child window view model DetailsWindowViewModel.cs and its interface IDetailsWindowViewModel
public class DetailsWindowViewModel : ViewModelBase, IDetailsWindowViewModel
{
public DetailsWindowViewModel(IMessenger messenger)
: base(messenger)
{
}
#region Properties
#region Customer Property
public const string CustomerPropertyName = "Customer";
private ICustomerViewModel _customer;
public ICustomerViewModel Customer
{
get { return _customer; }
set
{
if (_customer == value)
return;
var oldValue = _customer;
_customer = value;
RaisePropertyChanged(CustomerPropertyName, oldValue, value, true);
}
}
#endregion
#region FirstTabContent Property
public const string FirstTabContentPropertyName = "FirstTabContent";
private FrameworkElement _firstTabContent;
public FrameworkElement FirstTabContent
{
get { return _firstTabContent; }
set
{
if (_firstTabContent == value)
return;
_firstTabContent = value;
RaisePropertyChanged(FirstTabContentPropertyName);
}
}
#endregion
#region SecondTabContent Property
public const string SecondTabContentPropertyName = "SecondTabContent";
private FrameworkElement _secondTabContent;
public FrameworkElement SecondTabContent
{
get { return _secondTabContent; }
set
{
if (_secondTabContent == value)
return;
_secondTabContent = value;
RaisePropertyChanged(SecondTabContentPropertyName);
}
}
#endregion
#endregion
}
public interface IDetailsWindowViewModel
{
ICustomerViewModel Customer { get; set; }
FrameworkElement FirstTabContent { get; set; }
FrameworkElement SecondTabContent { get; set; }
void Cleanup();
}
And you can show the child window from your MainPageViewModel.cs like this.
public class MainViewModel : ViewModelBase, IMainViewModel
{
private readonly IDetailsWindow _detailsWindow;
public MainViewModel(IMessenger messenger, IDetailsWindow DetailsWindow)
: base(messenger)
{
_detailsWindow = DetailsWindow;
}
private void DisplayCustomerDetails(ICustomerViewModel customerToDisplay)
{
_detailsWindow.Customer = customerToDisplay;
_detailsWindow.Show();
}
}
Note that I create interfaces for all of my view models and child windows and I use an DI/IoC container in my ViewModelLocator so that all of my ViewModels' dependencies are injected for me. You don't have to do this, but I like how it works.