Close a view from a viewmodel in MVVM - c#

I have seen many examples over the net but I am not able to figure out what i am doing wrong here.
I need to close a view from a view model. Below is the code i have tried:
ViewModel:
public class ViewModel
{
public event EventHandler RequestClose;
public ViewModel()
{
}
}
//Calling the event from view model to close the view from a method in View Model
This event is called on a button click
private void Download()
{
//Download Logic
if(RequestClose != null)
RequestClose(this,EventArgs.Empty);
}
View:
ViewModel vm = new ViewModel();
vm.RequestClose += delegate(object sender, EventArgs args) {this.Close();}
this.DataContext = vm;

You fire the RequestClose event in the ViewModel constructor which is too early to be catched by the event registration.

The best MVVM solution is to use an attached behavior, as outlined in the top rated answer to this question How should the ViewModel close the form?

I faced a similar problem earlier, and did the following: In the viewmodel, create a command that you can bind to (I personally use MvvmLight and its RelayCommand)
public class ViewModel
{
public RelayCommand<object> CloseWindowCommand {get; private set;}
public ViewModel()
{
CloseWindowCommand = new RelayCommand<object>(CloseTheWindow);
}
private void CloseWindow(object obj)
{
var window = obj as Window;
if(window != null)
window.Close();
}
}
In my view, I have button that triggers this command, e.g.
Button Content="Close" Command="{Binding CloseWindowCommand}" CommandParameter="{Binding ElementName=NameOvViewModel}"
I realize now, that this may require the use of MvvmLight, but I hope it offers some guidance on a possible solution to your question.

Related

Using Two Views and switch between Views

I see example on this link, how to switch between two views. Easiest sollution and perfect for my application - I will also have only two views.
So we have one parent View (MainWindow) and two children Views. Parent View have dedicadet two buttons to swtich between this two Views ("First View" and "Second View") which are located in "DockPanel".
My question is how to use any button in "First View" to switch to the second View and in "Second View" button to come back to the "First View". What I want is get rid of DockPanel and use buttons from View.
Please for advices, how to do that. If any question please ask. THANKS!
You can use an event from each child viewmodel to signal the parent to change views. So in the code below ButtonOnViewModel1Command is pressed on View1 (which is bound to ViewModel1) which raises the SwitchViewModel2Request event. The MainViewModel subscribes to this event and switches the CurrentViewModel collection to ViewModel2. You can do this same thing on ViewModel2 to switch back to ViewModel1.
public class MainViewModel
{
private ViewModel1 _viewModel1 = new ViewModel1();
private ViewModel2 _viewModel2 = new ViewModel2();
public MainViewModel()
{
//event from ViewModel1
_viewModel1.SwitchViewModel2Request += NavigateToView2;
}
//switch View to ViewModel2
private void NavigateToView2(object sender, EventArgs e)
{
CurrentViewModel = _viewModel2;
}
}
public class ViewModel1
{
public ViewModel1()
{
ButtonOnViewModel1Command = new RelayCommand(Button1Method);
}
//some button on child view 1
public RelayCommand ButtonOnViewModel1Command { get; set; }
private void Button1Method(object obj)
{
OnSwitchViewModel2Request();
}
//event that MainViewModel will subscribe to
public event EventHandler SwitchViewModel2Request = delegate { };
private void OnSwitchViewModel2Request()
{
SwitchViewModel2Request(this, EventArgs.Empty);
}
}
Since you're using MVVM light you should look to using the messenger system ( Good tutorial here ). A simple way would be on first view to send a NotificationMessage as follows:
Messenger.Default.Send<NotificationMessage>(new NotificationMessage("GotoSecondView"));
Then in your main window you would register to receive it as follows:
Messenger.Default.Register<NotificationMessage>(this, NotificationReceived);
Followed by a function to handle them:
private void NotificationReceived(NotificationMessage message)
{
string notice = message.Notification;
switch (notice)
{
case "GotoSecondView":
ExecuteSecondViewCommand
break;
}
}
Repeat the same idea for the other view and add it to your switch. Then you can trigger from anywhere and mainview will handle the change without directly having to link your viewmodels.

Call view model method from view (xaml.cs)

I have a question on calling a method of view model from view (xaml.cs). Any suggestions would be appreciated.
The method works in view model since if I call the method in view model constructor, I will be able to see the data in DataGrid. But when I call the method in view (xaml.cs), the DataGrid is empty.
In the view part, I have the code snippet below: (xaml.cs part)
private ShellViewModel viewModel = new ShellViewModel();
public ShellView()
{
this.DataContext = viewModel;
InitializeComponent();
}
......
private void Button_Click(object sender, RoutedEventArgs e)
{
viewModel.Init();
}// ##cannot call this method successfully
In View you can have event handlers but what ever those handlers do, they should only affect the view. So, I'd suggest you initialize your ViewModel e.g. in its own constructor.
But if you insist viewModel.Init() to be called after button is clicked, then bind your Button to a ICommand exposed from your ViewModel as property.
For example:
<Window.DataContext>
<viewModel:ShellViewModel />
</Window.DataContext>
<Button Command="{Binding InitCommand}">Init</Button>
public ShellViewModel()
{
InitCommand = new RelayCommand(Init);
}
public ICommand InitCommand { get; private set; }
private void Init()
{
throw NotImplementedException();
}

Update Parent View From Child MVVM

I'm building a WPF MVVM application but I'm having problems updating a parent view from a child view model.
I call the doAction() method on my parent view model which updates a property and raises a PropertyChangeEvent. When this method is called from the MainViewModel everything works great however when I call the same method from my child view model the PropertyChangedEvent get's raised but the view doesn't update.
Example:
ChildViewModel()
{
private ParentViewModel parent;
parent.doAction(); // Raised event but MainView doesn't update
}
ParentViewModel()
{
public void doAction()
{
this.Property = true;
OnPropertyChange("Property");
}
}
My Views are created using XAML:
<MainView>
<TabItem>
<view:ChildView/>
</TabItem>
</MainView>
Propery Change event is raised like so:
protected void OnPropertyChanged(string name)
{
LOGGER.Info("Property Changed: " + name);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
My question is how do I get the parent view to listen and update to a property change event raised by a child view.
Edit:
Base Class:
public abstract class AbstractBaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ICommand CloseCommand { get; set; }
public AbstractBaseViewModel()
{
this.CloseCommand = new CloseCommand(this);
}
public void CloseWindow()
{
Application.Current.MainWindow.Close();
}
protected void OnPropertyChanged(string name)
{
LOGGER.Info("Property Changed: " + name);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Parent ViewModel:
public class ParentViewModel : AbstractBaseViewModel
{
private Dispatcher dispatcher;
private bool visible;
public bool Visible
{
get { return visible; }
set { visible= value; OnPropertyChanged("Visible"); }
}
public MainWindowViewModel()
{
this.dispatcher = Dispatcher.CurrentDispatcher;
this.manager = manager;
}
public void ShowTab(){
this.Visible = true;
}
}
Child View Model:
public class ChildViewModel : AbstractBaseViewModel
{
private ParentViewModel parentVm;
public GeneralViewModel(ParentViewModel vm)
{
this.parentVm= vm;
}
public void Command(){
vm.ShowTab();
}
}
ParentView Xaml:
<TabItem Header="ViewWeWantToHide/Show"
Visibility="{Binding Visible,Converter={converter:BooleanToVisibilityConverter}}">
<views:SomeOtherView/>
</TabItem>
<TabItem Header="ChildView Tab">
<views:ChildView/>
</TabItem>
Without seeing all of your code, it'd be hard to guess what is causing your problem. However, I'd have to say that it is much more common in WPF to display view model instances in the UI and have WPF automatically display the relevant views, rather than displaying the views directly as you have. If you use view models rather than views then you'll have access to the child view model(s) from the parent view models.
If you did, or do have access to the child view model from the parent view model then I would advise that you use a simple delegate to 'pass your message' from the child view model to the parent view model instead. You can even define a parameter-less delegate and use it to just send a signal, rather than any property value.
You can declare a delegate, add a property of that type to your child UserControl and attach a handler in the main view model. Then you can call the delegate from your child UserControl and in the handler in the parent view model, you can call your method. For more information on this technique, please see my answer to the Passing parameters between viewmodels question (which answers a similar, but not exactly the same problem).
Thank you everyone for your assistance.
I found the issue which was unrelated to WPF and was actually a product of how I was setting the datacontext on my child views. In my parent window I was creating and registering a singleton instance of my ParentViewModel with the Unity container. This instance would then be injected in to all child views, the problem was the InitializeComponent method was being called before my parent view model was created and registered with the unity container meaning all child views were receiving completely a different instance.
Not working:
public MainWindow()
{
InitializeComponent();
if (DesignerProperties.GetIsInDesignMode(this))
{
this.DataContext = new ParentViewModel();
}
else
{
IUnityContainer container = UnityFactory.Retrieve();
ParentViewModel parentVM = container.Resolve<ParentViewModel>();
container.RegisterInstance<ParentViewModel>(parentVM);
this.DataContext = parentVM;
}
}
Working:
public MainWindow()
{
if (DesignerProperties.GetIsInDesignMode(this))
{
this.DataContext = new ParentViewModel();
}
else
{
IUnityContainer container = UnityFactory.Retrieve();
ParentViewModel parentVM = container.Resolve<ParentViewModel>();
container.RegisterInstance<ParentViewModel>(parentVM );
this.DataContext = parentVM;
}
/**
* Initialize after registering parent VM and setting the datacontext
*/
InitializeComponent();
}

Command binding not working

I'm developing my first WPF app for university using MVVM. I cannot get this specific binding to work, although i've followed the steps used previously that have been successful.
I have the following xaml snippet:
<Button Command="{Binding GetTicketsCommand}" Canvas.Left="50" Canvas.Top="202" Content="Refresh List" Height="25" Width="137" />
The view initialises the VM as such:
public JobListView()
{
InitializeComponent();
viewModel = new JobListViewModel(this);
this.DataContext = viewModel;
}
The viewmodel has the command called GetTicketsCommand which the button binds to, but when I click the button the command Execute or CanExecute methods do not get called. The command that I created is getting instantiated in the VM constructor.
Any ideas?
Edit:
The command class is like this:
public class GetTicketsCommand : ICommand
{
private readonly JobListViewModel viewModel;
public GetTicketsCommand(JobListViewModel viewModel)
{
this.viewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
viewModel.GetTickets();
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
And in the viewmodel I simply create an instance of it in the constructor:
public JobListViewModel()
{
GetTicketsCommand = new GetTicketsCommand(this);
dataAccess = new DataLayerClient();
}
Bindings only work with properties. Make sure that your GetTicketsCommand command within your view model is a property:
public ICommand GetTicketsCommand { get; set; }
rather than a field:
public ICommand GetTicketsCommand;
For MVVM scenarios a RelayCommand or DelegateCommand is a better fit than the RoutedCommand which is provided with WPF.
The DelegateCommand is provided in the Prism framework, and see http://msdn.microsoft.com/en-us/magazine/dd419663.aspx for more details for an implementation of a RelayCommand (based on the DelegateCommand).
Commanding has limitations though. For example, a button would be disabled if the command states that it can't execute. What if you wanted to hide the button instead? Commands limit your scope in reimagining the UI.
You might want to look at Caliburn.Micro which implements Actions. These let you invoke verbs on your view model from default events of the control type, all based on conventions.
As an example, if you had a button on your view with a name of Save, then the Save method on your view model will be invoked when the button is clicked. No explicit plumbing is required. You then have further flexibility in the behaviour of the button if the CanSave property on your view model returns false.

How do I let the View know its have to close the Window?

I am using MVVM, from one I know the commnication for Data is View <=> ViewModel <=> Model.
The ViewModel can interact with View by using Two-Way binding.
But now I have a LoginView (which is in a Window), if the login successful will be check in ViewModel.
When it fail, it should tell the Window to close. But...how? The ViewModel is not suppose to know about the View....
Your ViewModel is a representation of your UI state. You could simply have a IsLoginWindowVisible boolean property exposed, your view can then have code (yes, I said it, view can have code!) that shows / hides a windows based on the state of this property.
I think people stress too much about MVVM. As long as your ViewModel can execute without a view present, in order to facilitate testing, you are doing MVVM correctly. There is nothing wrong with having code to support your view.
I would go simple here and use an event to notify the view it should close.
ViewModel:
public event EventHandler LoginFailed;
public void Login()
{
if (fail)
{
if (this.LoginFailed != null)
{
this.LoginFailed(this, EventArgs.Empty);
}
}
}
View:
((MyViewModel)this.DataContext).LoginFailed += (sender, e) =>
{
// Code to close the window, such as window.Close();
};
You should add a specific event to your ViewModel (something like LoginFailed). Then you should link this event to a command that closes the window. See this blog post on how to link the two.
Here's another option. Instead of using an event you could use a delegate:
public class View {
...
myViewModel.OnFail = () => {this.Close();};
...
}
public class MyViewModel {
public Action OnFail {get; set;}
private void Login() {
....
if (failed && OnFail != null)
OnFail();
}
}

Categories