Interact between Model and ViewModel of different UserControls without violating MVVM - c#

EDIT: Added concrete example to clarify what I trying to achieve.
Here is application scheme:
To make code simpler, I will use trivial Messenger class instead of event aggregator from Prism. Tuple contains Id and string payload.
public static class Messenger
{
public static event EventHandler<Tuple<int, string>> DoWork;
public static void RaiseDoWork(int id, string path)
{
DoWork?.Invoke(null, new Tuple<int, string>(id, path));
}
}
Model instance subscribe to messenger for knowing when to start work (if Id correct), and notify view-model when work finished.
public class Model
{
public int id;
public Model(int id)
{
this.id = id;
Messenger.DoWork += (sender, tuple) =>
{
if (tuple.Item1 != this.Id)
{
return;
}
var result = tuple.Item2 + " processed with id " + this.id;
this.OnWorkCompleted(result);
};
}
public event EventHandler<string> WorkCompleted;
private void OnWorkCompleted(string path)
{
this.WorkCompleted?.Invoke(null, path);
}
}
UserControlResult is responsible for payload processing and result output. To make code simpler, lets just trace output instead of putting it on UI. So XAML will be default.
Code-behind:
public partial class UserControlResult : UserControl
{
private ResultViewModel viewModel;
public UserControlResult()
{
this.InitializeComponent();
}
public void Init(int id)
{
this.viewModel = new ResultViewModel(id);
this.DataContext = this.viewModel;
}
}
View-model:
public class ResultViewModel
{
private Model model;
public ResultViewModel(int id)
{
this.model = new Model(id);
this.model.WorkCompleted += path =>
{
Trace.WriteLine(path);
};
}
}
UserControlButtons contains buttons, one of them should start processing of model in UserControlResult via messenger. To make code simpler, lets omit command implementation and just show its handler.
Code-behind:
public partial class UserControlButtons : UserControl
{
private ButtonsViewModel viewModel;
public UserControlButtons()
{
this.InitializeComponent();
}
public void Init(int id)
{
this.viewModel = new ButtonsViewModel(id);
this.DataContext = this.viewModel;
}
}
View-model:
public class ButtonsViewModel
{
private int id;
public ButtonsViewModel(int id)
{
this.id = id;
}
// DelegateCommand implementation...
private void StartWorkingCommandHandler()
{
Messenger.RaiseDoWork(this.id, "test path");
}
}
UserControlParent contains both UserControlResult and UserControlButtons. His only role is to pass Id to them, so he doesn't even need view-model.
Xaml:
<StackPanel>
<uc:UserControlResult x:Name="UserControlResult" />
<uc:UserControlButtons x:Name="UserControlButtons" />
</StackPanel>
Code-behind:
public partial class UserControlParent : UserControl
{
public UserControlParent()
{
this.InitializeComponent();
}
public void Init(int id)
{
this.UserControlResult.Init(id);
this.UserControlButtons.Init(id);
}
}
And finally MainWindow contains two instances of UserControlParent. Its role to assign them different Ids.
Xaml:
<StackPanel>
<uc:UserControlParent x:Name="UserControlParent1" />
<uc:UserControlParent x:Name="UserControlParent2" />
</StackPanel>
Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.UserControlParent1.Init(111);
this.UserControlParent2.Init(222);
}
}
This will work: pressing button in UserControlButtons will start working in UserControlResult model, and both UserControlParent will working correct and independend thanks to Id.
But I believe that this chain of invoking Init methods is violates MVVM because code-behind (which is View in MVVM) should not know anything about Id value (which is relative to Model in MVVM). Talking that, I'm sure that Id is not part of view-model, because it doesn't have any presentation in UI.
How can I pass Id value from top window to "deepest" view-models without violating MVVM?
Original Question
Here is WPF application consisting from 3 UserControls:
UserControl3 is a part of UserControl2 content. I keep MVVM during developing and using Prism.
I need to invoke method of custom class (which is model in terms of MVVM) in UserControl3 from view-model of UserControl1. The restriction that custom class can't be singleton. I suppose to do it one of the following way:
Using event aggregator from Prism. UserControl1 view-model is publisher and UserControl3 model is subscriber. For this I'll need to create unique Id in Window and pass it to UserControl1 and UserControl3.
Creating service instance in Window and pass it to UserControl1 and UserControl3. Then UserControl1 will just invoke method of this instance.
Window pass UserControl2 instance to UserControl1. View-model in UserControl1 will just invoke method of UserControl2, which will invoke method of UserControl3 and so on.
It seems like 2 and 3 approaches violates MVVM. How would you resolve this situation?

I would use option 1. I use MVVM Light to send a message and whoever receives that specific message will fire off the service method. Loosely coupled.

I think I achieved truly MVVM implementation shown in simplified example below. Special thanks to Ed Plunkett's comment and Nikita's answer.
First, I don't need to pass unique Ids anymore. For identification of different ParentViewModel instances, I just pass them different Messenger instances (which replaces Prism's EventAggregator for the sake of simplicity):
internal class Messenger
{
public event EventHandler<string> DoWork;
public void RaiseDoWork(string path)
{
this.DoWork?.Invoke(this, path);
}
}
Second, it seems like in my particular case Model should not worry about Messenger's DoWork event. As soon as this event raised in one view-model (ButtonsViewModel), it is more appropriate for this event to be consumed by another view-model (ResultViewModel) rather than by Model itself. So Model simplified too:
internal class Model
{
public string Process(string input)
{
return input + " processed!";
}
}
Below demonstrated all view-models "from top to bottom".
internal class MainViewModel
{
private readonly Messenger eventAggregator1 = new Messenger();
private readonly Messenger eventAggregator2 = new Messenger();
public MainViewModel()
{
this.ParentViewModel1 = new ParentViewModel(this.eventAggregator1);
this.ParentViewModel2 = new ParentViewModel(this.eventAggregator2);
}
public ParentViewModel ParentViewModel1 { get; }
public ParentViewModel ParentViewModel2 { get; }
}
internal class ParentViewModel
{
public ParentViewModel(Messenger eventAggregator)
{
this.ButtonsViewModel = new ButtonsViewModel(eventAggregator);
this.ResultViewModel = new ResultViewModel(eventAggregator);
}
public ButtonsViewModel ButtonsViewModel { get; }
public ResultViewModel ResultViewModel { get; }
}
internal class ButtonsViewModel
{
private readonly Messenger eventAggregator;
public ButtonsViewModel(Messenger eventAggregator)
{
this.eventAggregator = eventAggregator;
this.StartCommand = new DelegateCommand(this.StartProcessing);
}
public DelegateCommand StartCommand { get; }
private void StartProcessing()
{
this.eventAggregator.RaiseDoWork("test path");
}
}
internal class ResultViewModel : ViewModelBase
{
private readonly Model model = new Model();
private string textValue;
public ResultViewModel(Messenger eventAggregator)
{
eventAggregator.DoWork += (sender, s) => this.DoWorkHandler(s);
}
public string TextValue
{
get { return this.textValue; }
set { this.SetProperty(ref this.textValue, value); }
}
private void DoWorkHandler(string s)
{
var result = this.model.Process(s);
this.TextValue = result;
}
}
Note that in ResultViewModel I replaced Trace.WriteLine with actual screen output (because now strings are without Id, so trace output the same). ViewModelBase just implements INotifyPropertyChanged.
Below demonstrated content part of all views "from top to bottom".
<!-- MainWindow.xaml -->
<StackPanel Orientation="Horizontal">
<views:UserControlParent DataContext="{Binding ParentViewModel1}" />
<views:UserControlParent DataContext="{Binding ParentViewModel2}" />
</StackPanel>
<!-- UserControlParent.xaml -->
<StackPanel>
<local:UserControlResult DataContext="{Binding ResultViewModel}" />
<local:UserControlButtons DataContext="{Binding ButtonsViewModel}" />
</StackPanel>
<!-- UserControlButtons.xaml -->
<Grid>
<Button Content="Test" Command="{Binding StartCommand}" />
</Grid>
<!-- UserControlResult.xaml -->
<Grid>
<TextBlock Text="{Binding TextValue}" />
</Grid>
And finally this two worlds are connected in App.xaml.cs:
private void App_OnStartup(object sender, StartupEventArgs e)
{
new MainWindow { DataContext = new MainViewModel() }.Show();
}
Seems like MVVM, but any remarks are welcome.

Related

Change ViewModel from Service WPF

I am learning to create a WPF application following the MVVM patern. I'm try change data in viewmodel from service class but it can work, here is example code:
In MainWindow.xaml:
<Grid Grid.Row="6">
<TextBox materialDesign:HintAssist.Hint="Status"
Text="{Binding Status, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
VerticalAlignment="Center" />
</Grid>
In MainViewModel.cs:
public class MainViewModel : BaseViewModel
{
private static MainViewModel _instance = new MainViewModel();
public static MainViewModel Instance { get { return _instance; } }
//...
// Status
private string _Status = "Status";
public string Status { get => _Status; set { _Status = value; OnPropertyChanged(); } }
public MainViewModel()
{
////
// => This command can change status
// Start
StartCommand = new RelayCommand<object>((p) => { return true; }, (p) =>
{
OutStatus("Task success!");
});
}
public void OutStatus(string status)
{
Status = status;
}
}
In UtilitiesService.cs
public static class UtilitiesService{
public static void SetStatus(){
// => Here i can't change Status and can't binding to MainWindow.xaml
MainViewModel.Instance.OutStatus("Change Status in service");
}
}
So how can I change a property in manviewmodel in service file.Sorry Im so noob :))
You are using different instances of MainViewModel for data binding and for updating.
Also don't use public static instances or members across the application. Instead directly pass around the instance (in your case MainViewModel and UtilitiesService).
Generally static class members like properties or fields introduce a potential memory leak, because the garbage collector can't collect them to free memory. It also makes unit testing difficult and defies the concept of object oriented language key features like encapsulation. It will make code hard to modify.
In the simplest scenario, you can create the MainViewModel instance in your MainWindow. You can also create a shared instance of UtilitiesService at this point as well.
It's unclear what purpose UtilitiesService has. If it is meant to update MainViewModel by other View Model classes you can do it your way. If it is meant to be used in the Model, then you shouldn't do it your way. In this case your MainVoewModel would listen to the UtilitiesService events to update itself. Because in MVVM the Model does never talk to the View Model.
The recommended C# naming convention suggests to name fields using the camelCase pattern (starting with a lower case letter). Microsoft Docs: Naming Guidelines
A TextBox.Text binding that is configured to bind OneWay is pretty useless. In this case the TextBox only serves as display. You should then use TextBlock instead.
MainViewModel.cs
public class MainViewModel : BaseViewModel
{
//...
// Status
private string _status = "Status";
public string Status { get => _status; set { _status = value; OnPropertyChanged(); } }
public MainViewModel()
{
////
// => This command can change status
// Start
StartCommand = new RelayCommand<object>((p) => { return true; }, (p) =>
{
SetStatus("Task success!");
});
}
public void SetStatus(string status)
{
Status = status;
}
}
UtilitiesService.cs
public class UtilitiesService
{
private MainViewModel MainViewModel { get; }
public void UtilitiesService(MainViewModel mainViewModel)
{
this.MainViewModel = mainViewModel;
}
public void SetStatus()
{
// Change MainViewModel.Status and update bindings in MainWindow.xaml
this.MainViewModel.SetStatus("Change Status in service");
}
}
MainWindow.xaml.cs
partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var mainViewModel = new MainViewModel();
this.DataContext = mainViewModel;
var sharedUtilitiesService = new UtilitiesService(mainViewModel);
// Pass the shared UtilitiesService instance to other view model classes
// to allow them to update the MainViewModel anonymously.
var otherViewModel = new OtherViewModel(sharedUtilitiesService);
}
}
MainWindow.xaml
<Window>
<TextBlock Text="{Binding Status}" />
</Window>

How to provide ChildViewModel with data from MainViewModel

Often when I’m designing an MVVM application, the following scenario comes up. A Window, having multiple children, sharing the same data source.
I’m trying to decide on the best way to implement a single datasource for all children. There are 3 options I can think of, all with their own advantages and disadvantages.
Example
The Window has two child UserControls, each in their own tab.
UI is linked up like this.
In order to keep modularity and provide them with data, the same design is reflected in ViewModels.
The MainViewModel is set up like this.
public class MainViewModel : ViewModelBase
{
private readonly ChildViewModelA _childViewModelA = new ChildViewModelA();
private readonly ChildViewModelB _childViewModelB = new ChildViewModelB();
public ChildViewModelA ChildViewModelA { get { return this._childViewModelA; } }
public ChildViewModelB ChildViewModelB { get { return this._childViewModelB; } }
}
The MainWindow instantiates the MainViewModel and sets DataContext of the children. Child controls are bound to data properties.
public partial class MainWindow : Window
{
private readonly MainViewModel _viewModel = new MainViewModel();
public MainWindow()
{
InitializeComponent();
}
public MainViewModel ViewModel { get { return this._viewModel; } }
}
<Window x:Class="WpfApplication3.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WpfApplication3.View.Controls"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<TabControl>
<TabItem Header="Tab 1">
<controls:ChildViewA DataContext="{Binding ViewModel.ChildViewModelA}"/>
</TabItem>
<TabItem Header="Tab 2">
<controls:ChildViewB DataContext="{Binding ViewModel.ChildViewModelB}"/>
</TabItem>
</TabControl>
</Grid>
</Window>
To prevent every ViewModel from retrieving the same data from the database, I want to load data in the MainViewModel and provide the children. However, there are multiple ways of doing this.
Example 1: Using a setter method on the children
public class MainViewModel : ViewModelBase
{
...
private readonly FakeDataManager _fakeDataManager = new FakeDataManager();
public MainViewModel()
{
this.CurrentPerson = _fakeDataManager.GetNextPerson();
}
private Person _currentPerson;
public Person CurrentPerson
{
get { return this._currentPerson; }
set
{
this._currentPerson = value;
this.RaisePropertyChanged("CurrentPerson");
this.ChildViewModelA.SetPerson(this.CurrentPerson);
this.ChildViewModelB.SetPerson(this.CurrentPerson);
}
}
public class ChildViewModelA : ViewModelBase
{
private Person _currentPerson;
public Person CurrentPerson
{
get { return this._currentPerson; }
set
{
this._currentPerson = value;
this.RaisePropertyChanged("CurrentPerson");
}
}
}
Easy to implement, however quickly get's hard to remain. Not a lot of code reuse. No loose coupling. Should not use this.
Example 2: Putting data in a container
public class MainViewDataContainer : INotifyPropertyChanged
{
private Person _currentPerson;
public Person CurrentPerson
{
get { return this._currentPerson; }
set
{
this._currentPerson = value;
this.RaisePropertyChanged("CurrentPerson");
}
}
}
public class MainViewModel : ViewModelBase
{
...
private readonly FakeDataManager _fakeDataManager = new FakeDataManager();
private readonly MainViewDataContainer _dataContainer = new MainViewDataContainer();
public MainViewModel()
{
this._childViewModelA = new ChildViewModelA(_dataContainer);
this._childViewModelB = new ChildViewModelB(_dataContainer);
this._dataContainer.CurrentPerson = _fakeDataManager.GetNextPerson();
}
public class ChildViewModelA : ViewModelBase
{
private readonly MainViewDataContainer _dataContainer;
public ChildViewModelA(MainViewDataContainer dataContainer)
{
this._dataContainer = dataContainer;
}
public MainViewDataContainer DataContainer { get { return this._dataContainer; } }
}
Easier to maintain, more code reuse. Looser coupling.
Example 3: Storing in MainViewModel Properties and provide to children through an interface
public interface IMainDataProvider
{
Person CurrentPerson { get; }
}
public class MainViewModel : ViewModelBase, IMainDataProvider
{
private readonly ChildViewModelA _childViewModelA;
private readonly ChildViewModelB _childViewModelB;
private readonly FakeDataManager _fakeDataManager = new FakeDataManager();
public MainViewModel()
{
this._childViewModelA = new ChildViewModelA(this);
this._childViewModelB = new ChildViewModelB(this);
this.CurrentPerson = _fakeDataManager.GetNextPerson();
}
private Person _currentPerson;
public Person CurrentPerson
{
get { return this._currentPerson; }
set
{
this._currentPerson = value;
this.RaisePropertyChanged("CurrentPerson");
}
}
}
public class ChildViewModelA : ViewModelBase
{
private readonly IMainDataProvider _dataProvider;
public ChildViewModelA(IMainDataProvider dataProvider)
{
this._dataProvider = dataProvider;
}
public IMainDataProvider DataProvider { get { return this._dataProvider; } }
}
Yet again easier to maintain, more code reuse. Loose coupling.
Example 3 seems to be the best solution, however is this true? How do you think about this? Are there better ways to solve this issue?
If CurrentPerson is used by both ChildViewModels and MainView is the one who can change the CurrentPerson. How I will proceed to solve this problem is:
Define one base VM ChildViewModelBase : ViewModelBase with CurrentPerson property (with INotifyPropertyChanged and all). All of my ChildViewModels will extend this VM.
Use Event Aggregator http://msdn.microsoft.com/en-us/library/ff921122.aspx. Using EventAggregator you can subscribe to CurrentPersonChanged event in your ChildViewModelBase.
From you MainViewModel once the CurrentPerson is loaded from db, raise CurrentPersonChanged event with new Person as event arguement.
In event handler in the ChildViewModelBase, set CurrentPerson that you get as event argument.
In this way it would not matter to your ChildViewModels who is loading the Person from DB. Today it is MainView, tommorow it can be some other View also. You will just need to raise the above event with the person object as argument and Child Views will get it.

How to drive the Form events in a other class?

I desire the following:
When clickee a button on the form, I want to handle the events are another class. Thus, the form contains only controls.
It's almost like a MVC pattern: Controller I have a class, and a class RegistrarTrabajador (Model). When controller detects an event of the form, passes the task to the model.
Here the Controller class and the form:
Controller:
namespace RegistroDeUsuarios
{
public class Controller
{
private MainWindow vista;
private RegistrarTrabajador modelo;
public Controller()
{
}
public Controller(MainWindow vista, RegistrarTrabajador modelo)
{
this.vista = vista;
this.modelo = modelo;
}
public void btnRegistrar_Click(Object sender, RoutedEventArgs e)
{
Trabajador trabajador = new Trabajador();
trabajador.setPrimerNombre(vista.txtPrimerNombre.Text);
trabajador.setSegundoNombre(vista.txtSegundoNombre.Text);
trabajador.setPrimerApellido(vista.txtPrimerApellido.Text);
trabajador.setSegundoApellido(vista.txtSegundoApellido.Text);
trabajador.setRangoTrabajador(vista.cboRangoTrabajador.SelectedItem.ToString());
trabajador.setFechaNacimiento(vista.txtFechaNacimiento.Text);
modelo.registrarTrabajador(trabajador);
}
public void btnNuevo_Click(Object sender, RoutedEventArgs e)
{
vista.clean();
}
public void btnSalir_Click(Object sender, RoutedEventArgs e)
{
//Application.Current.Shutdown();
}
}
}
GUI:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
cboRangoTrabajador.Items.Add("Administrador");
cboRangoTrabajador.Items.Add("Vendedor");
cboRangoTrabajador.Items.Add("Contador");
cboRangoTrabajador.Items.Add("Tecnico Mantenimiento");
cboRangoTrabajador.Items.Add("Programador");
cboRangoTrabajador.Items.Add("Analista");
cboRangoTrabajador.SelectedIndex = 0;
}
public void setControlador(Controller controlador)
{
controlador.btnRegistrar_Click(controlador,new RoutedEventArgs());
controlador.btnNuevo_Click(controlador,new RoutedEventArgs());
controlador.btnSalir_Click(controlador,new RoutedEventArgs());
}
public void clean()
{
txtPrimerNombre.Clear();
txtSegundoNombre.Clear();
txtPrimerApellido.Clear();
txtSegundoApellido.Clear();
txtFechaNacimiento.Clear();
cboRangoTrabajador.SelectedItem = "Administrador";
txtPrimerNombre.Focus();
}
}
You don't use MVC in WPF. You use Model-View-ViewModel (MVVM)
And
you don't create or manipulate UI elements in procedural code in WPF. That's what XAML is for.
Please read about DataBinding,
things like this:
trabajador.setPrimerNombre(vista.txtPrimerNombre.Text);
trabajador.setSegundoNombre(vista.txtSegundoNombre.Text);
are horrible and should NEVER be done in WPF.
Also, your code smells like crappy java. Instead of methods like setPrimerNombre() you should really use Properties. WPF has support for two way databinding to properties, so you don't need to do all this piping manually.
To make this clear, here is a small example:
XAML:
<StackPanel>
<TextBox Text="{Binding Model.LastName}"/>
<TextBox Text="{Binding Model.FirstName}"/>
<Button Content="Registrar" Click="Registrar_Click"/>
<Button Content="Clear" Click="Clear_Click"/>
</StackPanel>
Code Behind:
public class MainWindow: Window
{
public MainViewModel ViewModel { get { return DataContext as MainViewModel; } }
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
public void Registrar_Click(object sender, RoutedEventArgs e)
{
ViewModel.Registrar();
}
public void Clear_Click(object sender, RoutedEventArgs e)
{
ViewModel.Clear();
}
}
ViewModel:
public class MainViewModel: ViewModelBase //You should have some ViewModelBase implementing INotifyPropertyChanged, etc
{
private Trabajador _model;
public Trabajador Model
{
get { return _model; }
set
{
_model = value;
NotifyPropertyChange("Model");
}
}
public void Registrar()
{
DataAccessLayer.Registrar(Model);
}
public void Clear()
{
Model = new Trabajador();
}
}
Model:
public class Trabajador: ModelBase //ModelBase Should also implement INotifyPropertyChanged
{
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
NotifyPropertyChanged("LastName");
}
}
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
NotifyPropertyChanged("FirstName");
}
}
//... And so on.
}
Not sure to have fully understand what you mean but i think you want to know what it is the best way of building a WPF application in layers.
If that's right then MVVM pattern is definitly what you are looking for. Here is a great link to understand how it works and to begin to play with it!

Implementing "close window" command with MVVM

So my first attempt did everything out of the code behind, and now I'm trying to refactor my code to use the MVVM pattern, following the guidance of the MVVM in the box information.
I've created a viewmodel class to match my view class, and I'm moving the code out of the code behind into the viewmodel starting with the commands.
My first snag is trying to implement a 'Close' button that closes the window if the data has not been modified. I've rigged up a CloseCommand to replace the 'onClick' method and all is good except for where the code tries to run this.Close(). Obviously, since the code has been moved from a window to a normal class, 'this' isn't a window and therefore isn't closeable. However, according to MVVM, the viewmodel doesn't know about the view, so i can't call view.Close().
Can someone suggest how I can close the window from the viewmodel command?
I personally use a very simple approach: for every ViewModel that is related to a closeable View, I created a base ViewModel like this following example:
public abstract class CloseableViewModel
{
public event EventHandler ClosingRequest;
protected void OnClosingRequest()
{
if (this.ClosingRequest != null)
{
this.ClosingRequest(this, EventArgs.Empty);
}
}
}
Then in your ViewModel that inherits from CloseableViewModel, simply call this.OnClosingRequest(); for the Close command.
In the view:
public class YourView
{
...
var vm = new ClosableViewModel();
this.Datacontext = vm;
vm.ClosingRequest += (sender, e) => this.Close();
}
You don't need to pass the View instance to your ViewModel layer. You can access the main window like this -
Application.Current.MainWindow.Close()
I see no issue in accessing your main window in ViewModel class as stated above. As per MVVM principle there should not be tight coupling between your View and ViewModel i.e. they should work be oblivious of others operation. Here, we are not passing anything to ViewModel from View. If you want to look for other options this might help you - Close window using MVVM
My solution to close a window from view model while clicking a button is as follows:
In view model
public RelayCommand CloseWindow;
Constructor()
{
CloseWindow = new RelayCommand(CloseWin);
}
public void CloseWin(object obj)
{
Window win = obj as Window;
win.Close();
}
In View, set as follows
<Button Command="{Binding CloseWindowCommand}" CommandParameter="{Binding ElementName=WindowNameTobeClose}" Content="Cancel" />
I do it by creating a attached property called DialogResult:
public static class DialogCloser
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached(
"DialogResult",
typeof(bool?),
typeof(DialogCloser),
new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null && (bool?)e.NewValue == true)
window.Close();
}
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
then write this to you XAML, in the window tag
WindowActions:DialogCloser.DialogResult="{Binding Close}"
finally in the ViewModel
private bool _close;
public bool Close
{
get { return _close; }
set
{
if (_close == value)
return;
_close = value;
NotifyPropertyChanged("Close");
}
}
if you change the Close to true, the window will be closed
Close = True;
Here is the simplest and pure MVVM solution
ViewModel Code
public class ViewModel
{
public Action CloseAction { get; set; }
private void CloseCommandFunction()
{
CloseAction();
}
}
Here is XAML View Code
public partial class DialogWindow : Window
{
public DialogWindow()
{
ViewModel vm = new ViewModel();
this.DataContext = vm;
vm.CloseAction = Close;
}
}
This solution is quick and easy. Downside is that there is some coupling between the layers.
In your viewmodel:
public class MyWindowViewModel: ViewModelBase
{
public Command.StandardCommand CloseCommand
{
get
{
return new Command.StandardCommand(Close);
}
}
public void Close()
{
foreach (System.Windows.Window window in System.Windows.Application.Current.Windows)
{
if (window.DataContext == this)
{
window.Close();
}
}
}
}
MVVM-light with a custom message notification to avoid the window to process every notificationmessage
In the viewmodel:
public class CloseDialogMessage : NotificationMessage
{
public CloseDialogMessage(object sender) : base(sender, "") { }
}
private void OnClose()
{
Messenger.Default.Send(new CloseDialogMessage(this));
}
Register the message in the window constructor:
Messenger.Default.Register<CloseDialogMessage>(this, nm =>
{
Close();
});
This is very similar to eoldre's answer. It's functionally the same in that it looks through the same Windows collection for a window that has the view model as its datacontext; but I've used a RelayCommand and some LINQ to achieve the same result.
public RelayCommand CloseCommand
{
get
{
return new RelayCommand(() => Application.Current.Windows
.Cast<Window>()
.Single(w => w.DataContext == this)
.Close());
}
}
using MVVM-light toolkit:
In the ViewModel:
public void notifyWindowToClose()
{
Messenger.Default.Send<NotificationMessage>(
new NotificationMessage(this, "CloseWindowsBoundToMe")
);
}
And in the View:
Messenger.Default.Register<NotificationMessage>(this, (nm) =>
{
if (nm.Notification == "CloseWindowsBoundToMe")
{
if (nm.Sender == this.DataContext)
this.Close();
}
});
This is taken from ken2k answer (thanks!), just adding the CloseCommand also to the base CloseableViewModel.
public class CloseableViewModel
{
public CloseableViewModel()
{
CloseCommand = new RelayCommand(this.OnClosingRequest);
}
public event EventHandler ClosingRequest;
protected void OnClosingRequest()
{
if (this.ClosingRequest != null)
{
this.ClosingRequest(this, EventArgs.Empty);
}
}
public RelayCommand CloseCommand
{
get;
private set;
}
}
Your view model, inherits it
public class MyViewModel : CloseableViewModel
Then on you view
public MyView()
{
var viewModel = new StudyDataStructureViewModel(studyId);
this.DataContext = viewModel;
//InitializeComponent(); ...
viewModel.ClosingRequest += (sender, e) => this.Close();
}
Given a way, Please check
https://stackoverflow.com/a/30546407/3659387
Short Description
Derive your ViewModel from INotifyPropertyChanged
Create a observable property CloseDialog in ViewModel, Change CloseDialog property whenever you want to close the dialog.
Attach a Handler in View for this property change
Now you are almost done. In the event handler make DialogResult = true
first of all give your window a name like
x:Name="AboutViewWindow"
on my close button I've defined Command and Command Parameter like
CommandParameter="{Binding ElementName=AboutViewWindow}"
Command="{Binding CancelCommand}"
then in my view model
private ICommand _cancelCommand;
public ICommand CancelCommand
{
get
{
if (_cancelCommand == null)
{
_cancelCommand = new DelegateCommand<Window>(
x =>
{
x?.Close();
});
}
return _cancelCommand;
}
}
Most MVVM-compliant solution using HanumanInstitute.MvvmDialogs
Implement ICloseable interface in your ViewModel and that's it!
No code in your view whatsoever.

Execute UserControl readonly ICommand from ViewModel

I have a specialized UserControl to play media content called PlayerView.
The control has its own commands (readonly, not provided by client).
public partial class PlayerView
{
public PlayerView()
{
InitializeComponent();
PlayCommand = new RelayCommand(() =>
{
// Play some media: audio/video.
});
}
...
#region PlayCommand property
private static readonly DependencyPropertyKey PlayCommandPropertyKey = DependencyProperty.RegisterReadOnly(
"PlayCommand",
typeof(ICommand),
typeof(PlayerView),
new PropertyMetadata());
public static readonly DependencyProperty PlayCommandProperty = PlayCommandPropertyKey.DependencyProperty;
public ICommand PlayCommand
{
get { return (ICommand)GetValue(PlayCommandProperty); }
private set { SetValue(PlayCommandPropertyKey, value); }
}
#endregion
...
}
The play command of the control works fine from XAML:
<Controls:PlayerView x:Name="PlayerView" />
<Button Command="{Binding ElementName=PlayerView, Path=PlayCommand, Mode=OneWay}" Content="Play" />
But currently, I am implemeting slideshow feature and I would like to execute the play command of the control from the ViewModel.
public class SlideshowViewModel : ViewModelBase
{
// Stores collection of audio/video clips to be played by the PlayerView.
// Assume that this ViewModel should invoke PlayerView PlayCommand.
}
public class MainViewModel : ViewModelBase
{
// Stores a lot of stuff.
public SlideshowViewModel Slideshow { get; }
}
The question is: how the SlideshowViewModel can execute the PlayCommand of this control? Is there a best practice?
If I am understanding your issue correctly, the ViewModel should contain the implementation of the Command, not the View. This would be a truer MVVM implementation, and then the VM can call that command from within itself, if necessary.
edit:
to answer your question,
public partial class PlayerView : IHaveAPlayCommand
{
public PlayerView()
{
this.DataContext = new ViewModel(this);
}
}
public class ViewModel
{
IHaveAPlayCommand view;
public ViewModel(IHaveAPlayCommand view)
{
this.view = view
}
}

Categories

Resources