 |
|
 |
This demonstrates an effective way to separate concerns, but it seems to me a bit overly complicated just to invoke a dialog.
In the framework we use (built in-house), if a view wants to show another view such as a dialog, it invokes an ICommand on its viewmodel, which can then do any preliminary work as needed (e.g., run stuff through domain objects), and raise an event for the view to handle (event data contains a new viewmodel needed by the new view), at which point the view creates and opens the new view. The view does indeed know its viewmodel (but not vice versa), which I find completely acceptable, and we have base classes for views and viewmodels that provide common behavior. There's some code-behind in the base view class to wire this up, but I also find that completely acceptable, so long as concrete views have very little or none.
Again, though, your solution is pretty elegant and does keep things decoupled.
|
|
|
|
 |
|
 |
Hi Phil.
I've seen view driven examples to open dialogs and message boxes, and have nothing against them. In your example the view runs a command to indirectly open the dialog, but I assume the view model could run that command as well based on its own decisions, i.e. the operation of opening a new dialog doesn't have to be started by the view, it could be started by the view model as well?
I am unsure whether you can talk about your solution since it in-house (I developed this code on a dare by a co-worker that some kind of registration between view and view model had to be made before opening dialogs could be accomplished, and we ended up using this code at work after I won the dare) but how does the view model know when the new dialog has been closed? A event is sent when the dialog should be opened, is another command executed by the view when the new dialog closes?
As a final remark, I implemented this solution with one requirement in mind: opening dialogs should be accomplished with minimal code in view and view model, i.e. the footprint of the dialog framework should be kept at a minimal. Setting one attached property in the view, and call one method on a already implemented interface is pretty simple according to be. No boiler plate code, nothing strange. Just a synchronous call to a service and then investigate the result, pretty simple don't you think?
|
|
|
|
 |
|
 |
Like what you have done. I have been following what you have done and adapting it so it fits my project. How can we go about making the popup dialog Edit data; in other words, when we select a Person from the list and click on "Show Information", ideally we can change data and click Save.
So sending the modified data to the PersonViewModel is what I am after...
Thanks in advance...
|
|
|
|
 |
|
 |
Hi Marcelo Areal.
Let me first discuss two of many different solutions we have when it comes to editing data.
Often enough the view model responsible for editing the data takes the model as input, as I've done in PersonDialogViewModel. I think this is acceptable if a majority of the properties being edited are represented by the model, i.e. the model represent the object being edited. A situation where I wouldn't think it acceptable is for instance if the dialog edited a phone number, and the view model required me to inject it with a person, which in turn had a property describing the phone number. The view model doesn't need to know what a person is, it only needs to know what a phone number is. I will write you a solution regarding this case further on.
Back to the example in question. The easiest way to support editing is to have the following code.
public class PersonDialogViewModel : ViewModelBase
{
private readonly Person person;
private string name;
private Gender gender;
public PersonDialogViewModel(Person person)
{
Contract.Requires(person != null);
this.person = person;
Name = person.Name;
Gender = person.Gender;
}
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
OnPropertyChanged("Name");
}
}
}
public Gender Gender
{
get { return gender; }
set
{
if (gender != value)
{
gender = value;
OnPropertyChanged("Gender");
}
}
}
internal Person Person
{
get
{
person.Name = Name;
person.Gender = Gender;
return person;
}
}
}
and then in MainWindowViewModel update the person when edited.
private void ShowInformation(object o)
{
PersonViewModel selectedPerson = persons.Single(p => p.IsSelected);
PersonDialogViewModel personDialogViewModel = new PersonDialogViewModel(selectedPerson.Person);
if (dialogService.ShowDialog<PersonDialog>(this, personDialogViewModel) == true)
{
selectedPerson.Person = personDialogViewModel.Person;
}
}
A second way to do it, and the reason to why I rambled about persons and phone numbers, is to not expose the model to the view model editing it. In this case PersonViewModel could have two properties, Name and Gender, with public getters and setters and not take a Person in the construct. Since Name and Gender are value types, they could be edited in PersonDialogViewModel, and canceled, without affecting the values known by MainWindowViewModel.
Hope this helped you.
Happy coding!
|
|
|
|
 |
|
 |
Hi,
I liked your article and it's given me ideas as to how to do and use dialogs in my applications.
I downloaded the code and ran the tests and one of them is showing the following error.
MVVM_DialogsTest.ViewModel.MainWindowViewModelTest.ShowInformationTest:
Moq.MockException :
Invocation was not performed on the mock: m => m.ShowDialog(value(MVVM_DialogsTest.ViewModel.MainWindowViewModelTest).viewModel, It.IsAny<PersonDialogViewModel>())
Stack trace is
at Moq.Mock.ThrowVerifyException(IProxyCall expected, Expression expression, Times times)
at Moq.Mock.VerifyCalls(Interceptor targetInterceptor, MethodCall expected, Expression expression, Times times)
at Moq.Mock.Verify[T,TResult](Mock mock, Expression`1 expression, Times times, String failMessage)
at Moq.Mock`1.Verify[TResult](Expression`1 expression)
at MVVM_DialogsTest.ViewModel.MainWindowViewModelTest.ShowInformationTest() in C:\Users\michael\Desktop\MVVM_Dialogs\MVVM_DialogsTest\ViewModel\MainWindowViewModelTest.cs:line 119
As I've not used Moq before I'm not sure what the problem is here.
Regards
Michael
|
|
|
|
 |
|
 |
Hi Michael.
Really sorry about the failing test. It seems that I've forgotten to modify a test before submitting the code. The failing test looks like this:
[Test]
public void ShowInformationTest()
{
LoadPersons();
viewModel.Persons[0].IsSelected = true;
dialogServiceMock
.Setup(m => m.ShowDialog(viewModel, It.IsAny<object>()))
.Returns(false);
viewModel.ShowInformationCommand.Execute(null);
dialogServiceMock
.Verify(m => m.ShowDialog(viewModel, It.IsAny<PersonDialogViewModel>()));
}
It should look like this:
[Test]
public void ShowInformationTest()
{
LoadPersons();
viewModel.Persons[0].IsSelected = true;
dialogServiceMock
.Setup(m => m.ShowDialog<PersonDialog>(viewModel, It.IsAny<object>()))
.Returns(false);
viewModel.ShowInformationCommand.Execute(null);
dialogServiceMock
.Verify(m => m.ShowDialog<PersonDialog>(viewModel, It.IsAny<PersonDialogViewModel>()));
}
The only difference is that the type of the dialog; PersonDialog; should be declared when showing dialogs using alternative 1.
Sorry for the inconvenience...
|
|
|
|
 |
|
 |
Just to let you know that all the tests are passing now.
|
|
|
|
 |
|
 |
Great! Thanks for noticing it.
|
|
|
|
 |
|
 |
A nice and easy to implement solution. However I am concerned about the coupling of the DialogService with the rest of the project. The attached property in DialogService uses ServiceLocator to obtain a reference to IDialogService implementation. The ServiceLocator is also used in viewmodels which means that the service cannot be reused easily. For example, if I wanted to use the service in a project which uses some DI container instead of this service locator I would need to either change the implementation of DialogService class to use DI container instead of ServiceLocator or to configure both of them.
The possible solution would be moving the attached property code out of DialogService class and - poof! - the coupling is gone. Then you can, for example, move everything dialog service related except the attached property code to a class library for ease of reuse. It will compile happily without any knowledge of who and when will use it and will not enforce service locator pattern to be used in the project.
The only drawback with this approach I see is that the code using the library would need to implement the attached property every time. But this embraces separation of concerns in a way. The responsibility of dialog service should be to display dialogs when needed and it should be unaware how the views get registered to it.
|
|
|
|
 |
|
 |
Hi mackius.
mackius wrote: The responsibility of dialog service should be to display dialogs when needed and it should be unaware how the views get registered to it.
You are absolutely right. If I would implement this functionality today I would dependency inject a interface in the DialogService, and that interface would be responsible for giving me the registered views. How those views are registered (either by a attached property or whatnot), is of non concern of the DialogService.
mackius wrote: The only drawback with this approach I see is that the code using the library would need to implement the attached property every time.
I don't think you have to. I can still have the attached property code (now used by a implementation of the interface dependency injected in the DialogService) in the same assembly as the DialogService, because I can always instruct the DI framework to resolve another implementation of the interface if I would like to.
|
|
|
|
 |
|
 |
Thank you for a great reply!
I was struggling to make your code reusable without ServiceLocator and your response gave me the missing piece how to do it. I ended up splitting IDialogService to 2 interfaces, one that shows dialogs and one (i called it IDialogParentResolver) that handles registering/unregistering and resolving views. The IDialogParentResolver interface gets injected into DialogService.
Then I made a default implementation of IDialogParentResolver a singleton which also has the attached property in it. I am aware that singletons are evil but there is no way i know of to inject dependencies to attached property PropertyChanged handlers (which are static) without a service locator pattern and this did not affect testability of the solution.
The result is a reusable class library. Hope this helps for the future readers.
|
|
|
|
 |
|
 |
You are correct, one of the problems with attached properties are that they are static. That's one of the many reasons as to why I'm nowadays using Behaviors[^] more often than I use attached properties. I am not saying Behaviors will fix this particular situation, but I think that the implementation doesn't have to be a Singleton, as long as the field holding the registered views is static (and of course private).
I want to thank you for your appreciated feedback, and will refer all future questions regarding this issue to your solution.
|
|
|
|
 |
|
 |
I've been doing Forms stuff for quite some time and I'm now making the shift to WPF. I want to make sure I'm going about it properly so I've decided to use MVVM, making sure I understand the principles and ideas properly. I ran into the problem of throwing up a dialog from the ViewModel pretty quickly, started looking for solutions, and found your example.
Unlike most of the other folks poking through it, I'm not raving. I count many, many new classes and interfaces and outrageous amounts of complexity. I also see that if you want to create any new type of dialog, you have to create at least two additional classes for each . . . all to avoid a single, very simple call in a ViewModel class that throws up a View. Sure, that violates an important principle, namely, separation of concerns. However . . . can anyone say, "over-engineered?"
That is/was my initial response. I am, however, here to learn so I'm gonna clear myself of a few prejudices and ask a few questions, if you don't mind:
1. Why do you concern yourself with one ViewModel not being tightly coupled to other ViewModels? I'm having a tough time seeing why you wouldn't create ViewModels as Singletons and then freely and easily call one ViewModel from another ViewModel. Sure, tight connections might require that you upon occasion modify two ViewModels instead of one, but is that worse than bending yourself into a pretzel to ensure that you don't?
2. Instead of doing all this, I suppose you could create an instance of every possible dialog and have it siting in memory. You then control their visibility with properties. You reduce complexity, but you could kill memory, depending upon how many Views/Dialogs you have in your app. Thoughts?
3. Strictly speaking, MVVM is violated right off the bat when you start the app. App.xaml.cs has to create at least the first ViewModel and the first View. All in one spot. The horror! Why not take a peek at the C part of MVC and say, "OK, I'll create one spot, one class, a Controller class, through which I funnel calls from ViewModel to View?" I'm not saying that you send everything through this controller; jeez, use the coolness that is two-way binding and command binding. But stuff like this, yeah. Sure, concerns might not be as perfectly, pristinely separated as they would be with both this example or any other pure implementation of MVVM, but I'm failing to see how that would be any worse than the outrageous complexity I see in this example.
I realize this post might sound a bit snotty and for that, I apologize. I did, however, want to sort of convey my initial response as purely as I could and then ask some questions. I really am interested and willing to learn so please explain why your approach is superior.
|
|
|
|
 |
|
 |
Nice you've come around to WPF, WinForms is so yesterday
Lets start answering your questions...
BFullwood wrote: I also see that if you want to create any new type of dialog, you have to create at least two additional classes for each . . . all to avoid a single, very simple call in a ViewModel class that throws up a View. Sure, that violates an important principle, namely, separation of concerns. However . . . can anyone say, "over-engineered?"
I am not quite following you here when you are referring to "two additional classes for each". I guess you don't mean the view (e.g. PersonDialog.xaml) or the code-behind of the view (e.g. PersonDialog.xaml.cs). Other than those there is only the DialogService and the actual viewmodel (e.g. PersonDialogViewModel.cs) left. Of those two I can subtract the viewmodel since it is a part of the MVVM pattern, and that leaves the dialog service. As a result I would say: "Apart from the classes defined by MVVM, namely the view and the viewmodel, this framework requires you to use one extra class, namely the DialogService.
I know that taking the shortcut of opening a view from the viewmodel seems tempting, but I would say don't. Not primarily based on the separation of concerns, but based on testability. According to me testability is the primary reason why you should use MVVM. Does testability come with a cost? Well that depends on your notion of cost. If it only includes the time a developer is spending on a project it is cheaper to skip testability, but if your cost also include the possible bug reports/fixes the developer has to attend I would say it is cheaper to develop testable code.
Opening a modal dialog from a viewmodel is not testable, but if you don't open the dialog yourself but lets a third-party do it, say the dialog service, you can mock that third party in a test and the viewmodel code becomes testable. Hurray!
BFullwood wrote: 1. Why do you concern yourself with one ViewModel not being tightly coupled to other ViewModels? I'm having a tough time seeing why you wouldn't create ViewModels as Singletons and then freely and easily call one ViewModel from another ViewModel. Sure, tight connections might require that you upon occasion modify two ViewModels instead of one, but is that worse than bending yourself into a pretzel to ensure that you don't?
It seems like a mantra but the answer is again testability. Lets say viewmodel A is tightly coupled with viewmodel B, and viewmodel B is tightly coupled with viewmodel C and D. In testing viewmodel A, you would be forced to set up viewmodel B, C and D. That's a LOT of work for testing one viewmodel.
Having the viewmodels as Singletons is not good either, are you sure that two instances of the same viewmodel shouldn't be allowed to coexist? Lets say you have an application that can show stock indexes. By clicking on a stock you can see extended information about the stock. With singletons you could only be watching one stock at the time, but what if the requirements of the application would force you to show two indexes in separate windows at the same time? That wouldn't be impossible using singletons.
Have a look at the event aggregator in Prism, it is an implementation of the Mediator pattern, where the sender and receiver has no notion of each other when it comes to sending events.
BFullwood wrote: 2. Instead of doing all this, I suppose you could create an instance of every possible dialog and have it siting in memory. You then control their visibility with properties. You reduce complexity, but you could kill memory, depending upon how many Views/Dialogs you have in your app. Thoughts?
I am not sure you reduce complexity, because based on my previous answers I think having everything tightly coupled is a bad thing. If things weren't coupled you would need somebody managing the visibility of the dialogs, and we are back to the same complexity again.
BFullwood wrote: 3. Strictly speaking, MVVM is violated right off the bat when you start the app. App.xaml.cs has to create at least the first ViewModel and the first View. All in one spot. The horror!
I can see that for someone just starting with WPF and MVVM that there seems to be no way round this, but there is. A very simple and MVVM friendly way. Lets start with App.xaml.cs. Remove the view and viewmodel:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ServiceLocator.RegisterSingleton<IDialogService, DialogService>();
ServiceLocator.RegisterSingleton<IPersonService, PersonService>();
ServiceLocator.RegisterSingleton<IWindowViewModelMappings, WindowViewModelMappings>();
ServiceLocator.Register<IOpenFileDialog, OpenFileDialogViewModel>();
}
}
The we're moving on to App.xaml:
<Application
x:Class="MVVM_Dialogs.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="View\MainWindow.xaml"/>
Here we are specifying that MainWindow.xaml should be the startup URI. Finally we move on to MainWindow.xaml.
<Window
x:Class="MVVM_Dialogs.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:MVVM_Dialogs.ViewModel"
xmlns:Service="clr-namespace:MVVM_Dialogs.Service"
Title="MVVM Dialog Example"
WindowStartupLocation="CenterScreen"
Service:DialogService.IsRegisteredView="True"
Height="300"
Width="400">
<Window.DataContext>
<ViewModel:MainWindowViewModel />
</Window.DataContext>
...
We define a ViewModel prefix and instantiate the viewmodel here. All according to any guidance defined by WPF and MVVM.
What can you learn from this? Well, don't jump to conclusions before you understand the bigger picture.
As a finale, you ask why my approach is superior. I would ask, superior to what? The .NET framework itself does not help you when it comes to opening dialogs from a viewmodel. There exists other frameworks handling a hole lot more than dialogs, like Cinch, Caliburn and Onyx. If you think my approach is complex, take a look at those I think my approach is simple, easy to understand and very straight forward. But that's just me...
Happy coding.
|
|
|
|
 |
|
 |
That's a very help answer. I'll spend a bit of time poking through it. Thank you for taking the time to compose it.
|
|
|
|
 |
|
 |
Excuse the title, but thats actually the best description.
Your DialogService is great, but it doesn't work correctly when you use a generic DataTemplate filled dialog like this:
<Window ...>
<DockPanel Margin="3">
<StackPanel ...buttons... />
<ContentPresenter Name="WindowContent" Content="{Binding}"/>
</DockPanel>
</Window>
With views like this:
<UserControl
ns:DialogService.IsRegisteredView="True">
...
</UserControl>
And Type Targeted Data Templates like this:
<DataTemplate Type="{x:Type SomeViewModel}">
<ns:SomeView/>
</DataTemplate>
The reason is the IsRegisterdView property is set before the View has been loaded completely. The leads to an exception where you try to attach to the owner window's Close event, since the View doesn't have an owner window yet.
I fixed it by temporarily subscribing to the View's Loaded event. Here is the code for that:
public void Register(FrameworkElement view)
{
if (views.Contains(view))
throw new ArgumentException("View has already been registered.");
Window owner = view as Window;
if (owner == null)
owner = Window.GetWindow(view);
if (owner == null)
view.Loaded += view_Loaded;
else
owner.Closed += OwnerClosed;
views.Add(view);
}
void view_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement view = (FrameworkElement)sender;
Window owner;
owner = Window.GetWindow(view);
if (owner != null)
owner.Closed += OwnerClosed;
view.Loaded -= view_Loaded;
}
Hopefully somebody finds this useful!
|
|
|
|
 |
|
 |
Thank you dear sir, and about the title, it sure made me worried
I have also encountered this problem, and solved it almost exactly as you did:
public void Register(FrameworkElement view)
{
Contract.Requires(view != null);
Contract.Requires(!views.Contains(view));
Window owner = view as Window;
if (owner == null)
{
owner = Window.GetWindow(view);
if (owner == null)
{
view.Loaded += LateRegister;
return;
}
}
owner.Closed += OwnerClosed;
views.Add(view);
}
private void LateRegister(object sender, RoutedEventArgs e)
{
FrameworkElement view = sender as FrameworkElement;
if (view != null)
{
view.Loaded -= LateRegister;
Register(view);
}
}
I will try to update the attached downloadable source as soon as possible... Thanks a bunch for the heads up about the bug, I really appreciate it!
Why is it drug addicts and computer afficionados are both called users?
--Clifford Stoll
|
|
|
|
 |
|
 |
Hi,
You have a Close button on the ShowInformation dialog. It closes the dialog by virtue of its IsCancel property. But let's say the dialog also allows the user to edit, and so you would have a Save button. When he clicks it, you would validate the state and decide whether to save or not. If you save, you would show a success message after saving, and finally close the edit dialog. Obviously IsCancel would not be appropriate here. Can you share your thoughts on this? Thanks.
|
|
|
|
 |
|
 |
I have written another article called Zip My Code[^] which have the additions you request.
DialogViewModelBase.cs has the ability to validate data in a dialog and inject additional code (by overriding the virtual method OnOk), in your case the display of save success.
Don't forget to vote on it as well
Why is it drug addicts and computer afficionados are both called users?
--Clifford Stoll
|
|
|
|
 |
|
 |
I downloaded the solution but could not open it. I get the error mwssage that says it was created by a newer version of VS (I'm assuming VS2010). Do you have a 2008 version? Thanks.
|
|
|
|
 |
|
 |
You are correct, I have converted the code to .NET4 and VS2010. However, without much effort you will be able to open it in VS2008. Please perform the following steps:
1. Start VS2008
2. Create a new WPF Application
3. Remove the default project contained in the solution
4. Add exiting project, and select MVVM_Dialogs.csproj from the downloaded source
5. Compile and fix the build errors
Why is it drug addicts and computer afficionados are both called users?
--Clifford Stoll
|
|
|
|
 |
|
 |
I followed your instructions. The compile errors were intially regarding System.Diagnostics.Contracts (is this a VS2010 specific namespace?) which I removed. But now I'm getting the error "The name 'InitializeComponent' does not exist in the current context" in both PersonDialog.xaml.cs and MainWindow.xaml.cs. I've not seen this kind of compile error before. Any idea?
|
|
|
|
 |
|
 |
You are correct, System.Diagnostics.Contracts is a new namespace in .NET4.
I converted the code back to VS2008, and it can be downloaded from here.
Why is it drug addicts and computer afficionados are both called users?
--Clifford Stoll
|
|
|
|
 |
|
 |
Ok, I downloaded it and was able to build. Thanks. I will post another message when I've studied it.
|
|
|
|
 |
|
 |
Any thoughts yet? Don't be afraid to elaborate on your criticism.
Why is it drug addicts and computer afficionados are both called users?
--Clifford Stoll
|
|
|
|
 |