Update Parent View From Child MVVM - c#

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();
}

Related

Make binding work on property in a different class

So I have a class MainViewModel in which I have a button. The button navigates to a view which has its own view model, let's call in ListViewModel. It resides inside MainViewModel. It has an ObservableCollection called WorkOrders.
In my main view model, I have a property, which returns the number of items, in the list in my ListViewModel. However, if I bind my button text to this property (NumberOfWorkOrders), then nothing happens, when WorkOrders.Count() changes. Even if I call OnPropertyChanged("NumberOfWorkOrders").
However, it does work, if I bind to the identical property inside the ListViewModel. How come it does not work, with the property in the MainViewModel? Is it because the notification from INotifyPropertyChanged does not work in a different view model?
Button binding which DOES NOT work (uses property from MainViewModel)
<Button
Content="{Binding NumberOfWorkOrders}"
ContentStringFormat="WorkOrders ({0})" />
Button binding which DOES work (uses property from ListViewModel)
<Button
DataContext="{Binding LVM}"
Content="{Binding NumberOfWorkOrders}"
ContentStringFormat="WorkOrders ({0})" />
MainViewModel.cs
public class MainViewModel : BindableBase
{
// INotifyPropertiesChanged is implemented in BindableBase
private ListViewModel listViewModel = new ListViewModel();
// This is where I would like to bind my button text to
public int NumberOfWorkOrders
{
get { return listViewModel.WorkOrders.Count(); }
}
// I would prefer not to have this
public ListViewModel LVM
{
get { return listViewModel; }
}
}
ListViewModel.cs
public class ListViewModel : BindableBase
{
// INotifyPropertiesChanged is implemented in BindableBase
public ObservableCollection<WorkOrder> WorkOrders
{
get; set;
}
// I would prefer to use the version of this in the MainViewModel
public int NumberOfWorkOrders
{
get { return listViewModel.WorkOrders.Count(); }
}
public void RefreshWorkOrders()
{
(...) // Load work orders and add them to collection
OnPropertyChanged("NumberOfWorkOrders");
}
}
You are running into this problem
, where you have aggregated property which required additional job: you will have to subscribe to ListViewModel.PropertyChanged and rise notification for MainViewModel.NumberOfWorkOrders property:
public class MainViewModel : BindableBase
{
readonly ListViewModel listViewModel = new ListViewModel();
public int NumberOfWorkOrders => listViewModel.WorkOrders.Count();
public MainViewModel()
{
// since you already rise notification in ListViewModel
// listen to it and "propagate"
listViewModel.PropertyChanged += (s, e) =>
{
if(e.PropertyName == nameof(ListViewModel.NumberOfWorkOrders))
OnPropertyChanged(nameof(NumberOfWorkOrders));
};
}
}
First button (which does NOT work) has MainViewModel as data context, binding will only listen for notification in this class.
As a simple fix you can include LVM in Path of binding. Binding is smart and will start listening to both events: of main view model and of that instance given by LVM property:
<Button Content="{Binding LVM.NumberOfWorkOrders}" ... />
you can delete NumberOfWorkOrders from MainViewModel then.
The reason your MainViewModel binding doesn't work, because you call OnPropertyChanged("NumberOfWorkOrders") from ListViewModel context.
I would suggest in MainViewModel to sign to changes in listViewModel.WorkOrders, and fire OnPropertyChanged("NumberOfWorkOrders") from MainViewModel, when WorkOrders changed. You need to look into documentation of ObservableCollection to find how to sign for collection changed notifications.

Parent ViewModel communicate child

I have a main View with nested child Views. I have a main VM that holds instances of the child VMs.
At some point, the main VM needs to interact with child.
In the main VM. I defined the child ViewModel as
public ChildViewModel VmChild
{
get
{
if (this.vmChild == null)
this.vmChild = new ChildViewModel();
return this.vmChild;
}
set
{
if (this.vmChild != value)
{
this.vmChild = value;
this.OnPropertyChanged("VmChild");
}
}
}
In the main View. I have
<StackPanel Orientation="Vertical" HorizontalAlignment="Left" >
<localViews:ChildView DataContext="{Binding VmChild}"> </localViews:ChildView>
</StackPanel>
In the child view code behind.
public ChildViewModel ViewModel
{
get
{
if (this.vmChild == null)
this.vmChild = new ChildViewModel();
return this.vmChild;
}
set
{
if (this.vmChild != value)
{
this.vmChild = value;
}
}
}
And
private void InitializeViewModel()
{
if (this.DataContext is ChildViewModel)
{
this.ViewModel = this.DataContext as ChildViewModel;
}
else
{
this.DataContext = this.ViewModel;
}
}
My question is my code works out. However it has an exception when I open the main view.
The exception is NullReferenceException was thrown on "ChildView": Cannot create an instance of "ChildView". The error line is at this.vmChild = new ChildViewModel();
I think that I defined the view model instance twice to cause it. In the main View Model I already define the child vm instance. In code behind of the child view, I defined it again. But I don't know how to fix it.
EDIT:
You are trying to 'fix' a null value scenario that shouldn't occur or should be left alone.
Remove both the pieces of the child's code-behind and if you really need a ViewModel property (is this for UWP?), use this:
// not normally needed in WPF/MVVM
public ChildViewModel ViewModel { get { return DataContext as ChildViewModel; } }
The Child View is getting a ViewModel, it shouldn't create one.
In main view replace
DataContext="{Binding VmEChild}
with
DataContext="{Binding VmChild}

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.

Close a view from a viewmodel in MVVM

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.

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.

Categories