Closing a window using ViewModel first MVVM and Stylet - c#

I'm using a MVVM ViewModel first approach with Stylet and I'm struggling to close a window from it's ViewModel.
In the Stylet Wiki it states that I can use:
Screen.RequestClose
I have the following code:
public class MdExportViewModel : Screen
{
public MdExportViewModel()
{
if(canExport == true)
{
this.RequestClose();
}
}
}
When I try to call the 'RequestClose' I get the following error:
System.InvalidOperationException: 'Unable to close ViewModel
Drain.ViewModels.Windows.MdExportViewModel as it must have a conductor
as a parent (note that windows and dialogs automatically have such a
parent)'
I've tried adding the Conductor<T> as follows:
public class MdExportViewModel : Conductor<IScreen>
But I get the same error. I didn't really understand how a conductor should be used in this instance. I assumed my origional attempt would work since note that windows and dialogs automatically have such a parent.
What am I doing wrong here? Other answers to similar questions use complicated workarounds, but I'd like to use a Stylet method to keep things consistent and simple.
EDIT:
The window is opened in another viewmodel as follows:
public void ExportMD()
{
MdExportViewModel MdExportViewModel = new(networkMain, DesignCriteriaViewModel)
{
Parent = this
};
this.windowManager.ShowWindow(MdExportViewModel);
}

Related

Calling parent function from frame

I have have a WPF application Im building with the designer. In it I have a Frame that I am loading a Page in.
To be totally honest Im lost. With a lack of instruction and ability to wrap my head around MVVM I am hoping someone might be able to help me understand how to do what I'm trying to do.
From within the page I need to call a public method 'public void UpdateTxt()'. I know this had been done a million time but I just don't understand. Most of my searches pull web/javascript results too.
I did something similar once before with two windows in a winform environment.
public partial class setupApp : Form
{
private Form1 m_parent;
public setupApp(Form1 frml)
{
InitializeComponent();
m_parent = frml;
}
While the above code works in winform enabling me to locate all public functions from the parent window, I can't seem to translate it to WPF.
I have tried
public partial class Childpage: Page
{
private MainWindow m_parent;
public Childpage(MainWindow mw1)
{
InitializeComponent();
m_parent = mw1;
}
This throws no errors on build, but fails to break mode as soon as the debugger launches. I have no idea what the error means as well.
No Matching Constructor Found on type
Why wont the C# back end code translate? Is there a better way?
I think you set the Source property of the Frame in xaml code like this:
<Frame Source="SamplePage.xaml"/>
in this case you need add a parameter-less constructor to your page.
public partial class SamplePage
{
private MainWindow _parentWindow;
public SamplePage()
{
InitializeComponent();
}
public SamplePage(MainWindow parentWindow) : this()
{
_parentWindow = parentWindow;
}
}
But if you want pass the parent window to child page you can set the Frame content in code-behind. like this:
SampleFrame.NavigationService.Navigate(new SamplePage(this));
in this case you don't need to parameter-less constructor.

Opening new window from ViewModel

How should I be opening new windows? I'm currently doing the following.
EventArgs:
public class GenericViewRequestedEventArgs : EventArgs
{
public GenericViewModel ViewModel { get; private set; }
public GenericViewRequestedEventArgs(GenericViewModel viewModel)
{
ViewModel = viewModel;
}
}
ViewModel:
public class MainWindowViewModel : ViewModelBase
{
private RelayCommand _viewSpecificCommand;
public ICommand ViewSpecificCommand
{
get
{
if (_viewSpecificCommand == null)
_viewSpecificCommand = new RelayCommand(x => viewSpecific());
return _viewSpecificCommand;
}
}
public EventHandler<GenericViewRequestedEventArgs> GenericViewRequested;
private void RaiseGenericViewRequested(GenericViewModel viewModel)
{
var handler = GenericViewRequested;
if (handler != null)
handler(this, new GenericViewRequestedEventArgs(viewModel));
}
private void viewSpecific()
{
RaiseGenericViewRequested(_specificViewModel);
}
}
View:
public partial class MainWindow : Window
{
private void OnGenericViewRequested(object sender, GenericViewRequestedEventArgs e)
{
GenericWindow window = new GenericWindow(e.ViewModel);
window.ShowDialog();
}
}
This does work, but it seems like a lot of code and I end up with code behind in my view any ways.
What's the logic behind sending the command to the viewmodel at all?
Is it just to optionally use the predicate(if so why not bind to Enabled) and possibly avoid exposing additional viewmodels as properties?
Should I be attaching simple event handlers in the XAML(e.g. Click="btnViewSpecific_Click")?
It depends on how "strict" you want to follow the MVVM pattern. This is just one of the basic pitfalls of MVVM and you can solve it depending on your preferences. One way is to simply use the code-behind, but then how will you handle application-wide commands, keyboard shortcuts, etc? It is a bit too short-sighted IMHO.
I think you should at least consider using existing frameworks that have solved these issues for you years ago and will provide you with a solid base for your application.
For example, Catel has a IUIVisualizerService that can show windows based on a view model. The major advantage is that you can push data into the view model and respond to the result as a service. Another nice advantage is that you can mock the IUIVisualizerService so you can test the reacting code on different results provided by the dialog.
** Disclaimer **
I am the developer of Catel, so I have explained the Catel way here. If anyone else wants to comment on other frameworks, feel free to comment or create a new answer.
Yes, there are a lot of additional codes for MVVM. Building a command that independent of Views is usually for unit testing, such that the command and ViewModel can be unit tested without involving UI components.
However, if the "command" is just opening a window, it is not worth to create a command, and unit test the command to see if the GenericViewRequested is really fired(you can even check if the correct _specificViewModel is returned). The codes are far more complicated and just little value is added. Just open the window in View's button click event handler and it is fine.
If you want to see good example, see how this works in the ViewModel (EmailClient) sample application of the WPF Application Framework (WAF).

Navigation & reinstanciate pages/viewmodel constructor

I am using Galasoft Mvvm Light toolkit, to build my application in the MVVM pattern for windows phone. I have to pages that each have their own viewmodel.
When a user starts the app he can choose new game og spin up a questions page. These to pages have each a viewmodel, and everything works using the viewmodellocator. When the user then navigates back to choose between new game and questions again. The viewmodel/page is not removed. which means when the user a second time goes into questions or new game, the constructor for the viewmodel is not called, such that the initialisation in the constructor is not run, and the view is not set correctly.
Solutions I have tried
I tried removing the backstack in the navigations, such as a new navigation to new game or questions, should spin up a new page, and thereby caling the constructor. Not working.
USing the loaded event in the view, and calling the constructor. Not working.
Tried to follow
How to reset all instances in IOC Container
But could not get it to work, might just be me.
Have anyone solve this issue, if so how should it be solved?
Code
Here you can find an example. Press questions, and press the button in there once, use backkey. and press questions again. you see that the number is now 1, this could easily be changed. But the error comes when you press the button again. Suddenly two popups are shown.
So what is the correct way to set up the viewmodel. since the view of newgame will be used when reloading an old game, just with other values, and when one wants to start a new game. Hope you understand :)
This example is only to show my problem with popups count going up for each return to the viewmodel page.
https://www.dropbox.com/s/gjbz0l8rmsxqzrd/PhoneApp8.rar
ViewModel Locator
I am in my current project using three viewmodels seen in the below code:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
namespace MVVMTestApp.ViewModel
{
public class ViewModelLocator
{
public ViewModelLocator()
{
//Holder styr på ViewModels
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
//Tilføj linje her for hver ViewModel
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<MainViewModelTest>();
SimpleIoc.Default.Register<MenuViewModel>();
}
//Tilføj metode som denne for hver ViewModel
public MainViewModel Map
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public MainViewModelTest Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModelTest>();
}
}
public MenuViewModel Menu
{
get
{
return ServiceLocator.Current.GetInstance<MenuViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
I have looked into the link I reference above resetting all instances in IOC Container. But are unsure how the key works, and how to make sure the cleanup function is called when navigating away from the views. Since I would not want to clean all the viewmodels at the same time.
Navigation And viewmodelbinding
I bind my viewmodel to the view as
DataContext="{Binding Source={StaticResource Locator},Path=Map}"
I navigate back and forth using NavigationService and backbutton. From Menu to Game:
NavigationService.Navigate(new Uri("/View/MainPage.xaml", UriKind.Relative));
and in the page
protected override void OnNavigatedTo(NavigationEventArgs e)
{
//e.Content = NavigationMode.New;
//e.NavigationMode = NavigationMode(
ViewModel.MainViewModel test = new ViewModel.MainViewModel();
GC.Collect();
base.OnNavigatedTo(e);
}
and from Game to Menu:
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
//e.NavigationMode = NavigationMode.
this.DataContext = null;
GC.Collect();
base.OnNavigatedFrom(e);
//test = null;
}
And in the menu I invoke the garbage collector. As can be seen I break the MVVM structure to accommodate the problem.
Properties of your ViewModelLocator return Singletons. To make the property return a new instance each time you could simply write:
private int questCount;
public Question Quest
{
get
{
return ServiceLocator.Current.GetInstance<Question>((++questCount).ToString());
}
}
However, it will result in Question ViewModel caching. You need to release unused ViewModels by closely following the answer you linked. This results in my opinion in too much code for a simple result. There are other IOC Containers that you could use in place of SimpleIoc on Windows Phone (like ninject or unity), which may be better suited for your needs.
In a simple project (a couple-of-pages app), especially in the case of not having to much experience with IOC container, I would advise you to abandon all that SimpleIoc wiring and just call the constructor:
public Question Quest
{
get { return new Question(); }
}

Open/Close View from ViewModel

I have an AddClientViewModel which is referenced by 2 views (AddClientView and SuggestedAddressesView). The AddClientView is a form which has a field for an address. The form has a validate button which validates the entered address using Geocoding. If more than one address is returned, the SuggestedAddressesView opens.
Here is how I am currently doing it:
AddClientViewModel:
private void ValidateExecute(object obj)
{
SuggestedAddresses = new ObservableCollection<DBHelper.GeocodeService.GeocodeResult>(GeoCodeTest.SuggestedAddress(FormattedAddress));
....
if (SuggestedAddresses.Count > 0)
{
var window = new SuggestedAddressesView(this);
window.DataContext = this;
window.Show();
}
}
Here is the SuggestedAddressesView constructor where AddClientViewModel inherits from ViewModelBase
public SuggestedAddressesView(ViewModelBase viewModel)
{
InitializeComponent();
viewModel.ClosingRequest += (sender, e) => this.Close();
}
The other problem I am having is when I call OnClosingRequest() from the AddClientViewModel...both the AddClientView and SuggestedAddressesView closes. I know this happens because both views reference the same ViewModel. This is not the behaviour I want. I would like to be able to independently close either Window.
Is opening a View from the ViewModel proper MVVM structure and how would I go about being able to close windows independently?
As soon as you refer to UI elements(In this case the View) from the VM, you're going against suggested MVVM Guidelines. With just that we can know creating the Window object in the VM is wrong.
So now onto rectifying this:
Firstly try to keep a 1 View <-> 1 VM in your application. It's cleaner and allows you to switch out View implementations with the same logic very easily. Adding multiple View's to the same VM even if not "ground-breaking" just makes it clumsy.
So now you got AddClientView and SuggestedAddressesView with their own VM. Great!
Implementing a View Open/Close from the VM:
Since we cannot access the View directly from our VM(to comply with standards), we can use approaches such as using a Messenger(MVVM Light), EventAggregator(PRISM) and so on to send a "message" from the VM to the View when you need to open/close a View and do the actual operation in the View.
This way the VM just initiates the message and can be unit-tested fine for the same operation and does not reference any UI elements.
Using a "Messenger" approach to handle View open:
As per your Logic, it is the AddClientViewModel which would have to ask for the SuggestedAddressesView to be opened.
Thus when you detect SuggestedAddresses.Count > 0, you would send a message to the AddClientView asking it to open up the SuggestedAddressesView.
In AddClientView.xaml.cs upon receiving this message, you would do what you're currently doing in the VM. Create an object of SuggestedAddressesView and call .Show() on it.
One extra step you would add in the above step's process is to assign the DataContext of SuggestedAddressesView as SuggestedAddressesViewModel.
That's it. Now what you have is, when AddClientViewModel wants SuggestedAddressesView shown, it sends a message to it's own View and the View in-turn creates and shows the SuggestedAddressesView. This way the VM does not reference any View's and we keep to holding MVVM standards.
Using a "Messenger" approach to handle View close:
Closing a View is pretty simple. Again when you need to close the View from the VM, you send a message to it's own View asking for it to be closed.
On receiving this message, the View pretty much closes itself via .Hide() / .Close() or however else you want to get rid of it.
In this each VM handles it's own View's closing and you don't have any inter-connected dependencies.
You can use this as a start point to guide you in handling "messages" for this approach. it has an attached download you can get and see how the Messenger works. This is with MVVM Light, if you do not use it or use something else/ your own MVVM implementation, use it as a guide to help get to what you need.
you can use RelayCommand so that you send the parameter as follows:
Command="{Binding CloseWindowCommand, Mode=OneWay}"
CommandParameter="{Binding ElementName=TestWindow}"
By using this you can close the individual views.
Example:
public ICommand CloseCommand
{
get
{
return new RelayCommand(OnClose, IsEnable);
}
}
public void OnClose(object param)
{
AddClientView/SuggestedAddressesView Obj = param as AddClientView/SuggestedAddressesView;
obj.Close();
}
To open window from ViewModel:
Create NavigationService.cs class for opening window:
Let NavigationService.cs
Now put following code in that class file.
public void ShowWindow1Screen(Window1ViewModel window1ViewModel)
{
Window1= new Window1();
Window1.DataContext = window1ViewModel;
Window1.Owner = Window1View;
Window1.ShowDialog();
}
then.
Create instance of NavigationService.cs class MainWindowViewModel file.
Then
Window1ViewModel window1ViewModel = new Vindow1ViewModel();
window1ViewModel.Name = MainWindowTextValue;
NavigationService navigationService = new NavigationService();
navigationService.ShowWindow1Screen(window1ViewModel);

Caliburn ShowDialog and MessageBox

I'm making a small demo application for MVVM with caliburn.
Now I want to show a MessageBox, but the MVVM way.
For dialogs I created an event, that is handled in the ShellView (the root view)
and just calls WindowManager.ShowDialog with a Dialogs ViewModel type.
Seems to stick to MVVM for me.
But what is the way to show a messagebox and get its result (Okay or cancel)?
I already saw this question, but it contains no answer either.
Mr Eisenberg hisself answers with
"Caliburn has services built-in for calling custom message boxes."
Can anyone tell what he means with that? I don't see it in the samples.
As you mentioned, you just prepare the view model (e.g. ConfirmationBoxViewModel) and an appropriate view. You'll have to create two actions (after inheriting the view model from Screen, which is necessary to use TryClose. You can always implement IScreen instead, but that would be more work):
public void OK()
{
TryClose(true);
}
public void Cancel()
{
TryClose(false);
}
and then in your other view model:
var box = new ConfirmationBoxViewModel()
var result = WindowManager.ShowDialog(box);
if(result == true)
{
// OK was clicked
}
Notice that after the dialog closes, you can access the view model properties if you need to pull additional data from the dialog (e.g. Selected item, display name etc).
In the article A Billy Hollis Hybrid Shell (written by the framework coordinator) the author showed a nice way to handle both dialog and message boxes, but he used dependency injection (you can go without DI of course but it makes things simpler). The main idea is that you can let your main window, the one used as the application shell implement an interface that looks something like this:
public interface IDialogManager
{
void ShowDialog(IScreen dialogModel);
void ShowMessageBox(string message, string title = null, MessageBoxOptions options = MessageBoxOptions.Ok, Action<IMessageBox> callback = null);
}
and then he registers this interface with the IoC container, I guess you can use your imagination from there on and if you don't have time then you can look at the source code that accompanies the article.
When the root/main/shell view-model implements a kind of DialogService interface, every other view-model needing to show dialogs will end up with a dependency on the root view-model. Sometimes this might not be desiderable, e.g. if it could cause a dependency loop:
DialogService (aka RootViewModel) -> SomeViewModel -> RootViewModel.
A more involved approach to break this dependency chain (and actually invert it) is the following:
Implement a behavior that detects Window.OnSourceInitialized event and attach it to main view Window component. That is the event fired when the window handle is available. Upon event, behavior will notify some handler passed in via attached property:
<my:WindowSourceBehavior InitListener="{Binding WindowListener}" />
public class WindowSourceBehavior : Behavior<Window>
{
// ...
// boilerplate code for IWindowListener InitListener dependency property
// ...
attachedWindow.SourceInitialized += (sender, evt) =>
{
// ...
InitListener.SourceInitialized(sender as Window);
}
}
DialogService exposes a handler - or interface - as requested by behavior:
public class DialogService : IWindowListener
{
// ...
public void SourceInitialized(Window rootWindow) { /* ... */ }
}
In root view-model, (indirectly) get the DialogService injected as a dependency. During construction, sets view-model bound property, WindowListener, to the DialogService handler/interface:
public MainViewModel(IWindowListener dialogServiceInDisguise)
{
WindowListener = dialogServiceInDisguise;
}
public IWindowListener WindowListener { get; private set; }
Doing so, the DialogService is able to get a hold of root Window, and whichever view-model needs to show a dialog does not create a(n indirect) dependency on main view-model.

Categories

Resources