Changing the ContentControl binding results in a null DataContext - c#

Perhaps one of you could enlighten me on the following problem.
I'm using a list box as a way to navigate between 2 viewmodels.
<Window x:Class="MyWPFApp.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="8*"/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" cal:Message.Attach="[Event SelectionChanged]
= [Action onViewSelectionChange($this.SelectedItem.Text)]">
<TextBlock Text="FirstView"/>
<TextBlock Text="SecondView"/>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding MyViewModel}"/>
</Grid>
namespace MyWPFApp
{
public class ShellViewModel : Caliburn.Micro.PropertyChangedBase, IShell
{
private object _vm;
public object MyViewModel
{
get { return _vm; }
set { _vm = value; NotifyOfPropertyChange(() => MyViewModel); }
}
public FirstViewModel vm1;
public SecondViewModel vm2;
public ShellViewModel()
{
vm1 = new FirstViewModel();
vm2 = new SecondViewModel();
MyViewModel = vm1;
}
public void onViewSelectionChange(string str)
{
switch (str)
{
case "FirstView":
MyViewModel = vm1;
break;
case "SecondView":
MyViewModel = vm2;
break;
default:
break;
}
}
}
}
I've create 2 datatemplates to associate the viewmodel with its view
<Application x:Class="MyWPFApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-MyWPFApp">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<local:AppBootstrapper x:Key="bootstrapper" />
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type local:FirstViewModel}">
<Grid>
<local:FirstView></local:FirstView>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SecondViewModel}">
<Grid>
<local:SecondView></local:SecondView>
</Grid>
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
FirstView actually has controls which have bindings to properties in FirstViewModel
<UserControl x:Class="MyWPFApp.FirstView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyWPFApp"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="Aquamarine">
<ListBox SelectedIndex="{Binding SelectedIndex}"
SelectedItem="{Binding SelectedItem}"
cal:Message.Attach="[Event SelectionChanged]=[Action OnLBSelectionChanged($source)]">
<ListBoxItem>apple</ListBoxItem>
<ListBoxItem>orange</ListBoxItem>
<ListBoxItem>pear</ListBoxItem>
</ListBox>
</Grid>
using Caliburn.Micro;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyWPFApp
{
public class FirstViewModel : PropertyChangedBase
{
private int _selectedindex;
public int SelectedIndex
{
get { return _selectedindex; }
set { _selectedindex = value; NotifyOfPropertyChange(() => SelectedIndex); }
}
private string _selecteditem;
public string SelectedItem
{
get { return _selecteditem; }
set { _selecteditem = value; NotifyOfPropertyChange(() => SelectedItem); }
}
public FirstViewModel()
{
}
public void OnLBSelectionChanged(System.Windows.Controls.ListBox lb)
{
if(lb.DataContext == null)
{
Debug.WriteLine("The DataContext is null");
}
}
}
}
The SecondView contains no controls.
When the ShellView list box selection changes from FirstView to SecondView, I've noticed that the FirstView ListBox DataContext becomes null.
public void OnLBSelectionChanged(System.Windows.Controls.ListBox lb)
{
if(lb.DataContext == null)
{
Debug.WriteLine("The DataContext is null");
}
}
I would prefer not to have to handle selection changes looking for a null DataContext. Instead, I'd like to know what's actually happening.
Thanks.

The reason why this is happening is that the DataContext for FirstView is implicitly wired up by WPF when the ContentControl's Content property is set to the FirstViewModel via data binding.
Once the binding changes to be SecondViewModel the FirstViewModel goes out of scope and thus WPF cleans up after itself, removing FirstViewModel, FirstView, and its implicit DataContext binding.
I hope this helps.

Related

calling a method from ViewModel when DataContext changes

The situation:
I have a little app that works with fantasy classes. In the example below I boiled it down to the bare bones. In a ComboBox, situated in the Main Window, the user selects a fantasy class (warrior, rogue, mage etc.) from a list loaded from a DB. This information is passed to a UserControl sitting in Main Window which exposes details about the class using MVVM and data binding. All of this works so far.
The DB has a value (in this case Gear) saved as an int which at the moment displays as an int on screen. It's the app's responsibility to parse that to a string.
So the question is: How do I wire up a method in the UserControl's ViewModel to trigger whenever it's associated View has a DataContext (the selected CharacterClass) change?
Main Window:
<Window x:Class="ExampleApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ExampleApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ComboBox Height="22" MinWidth="70"
ItemsSource="{Binding Classes}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedClass}"/>
<local:DetailsView Grid.Column="1" DataContext="{Binding SelectedClass}"/>
</Grid>
</Window>
Main Window ViewModel:
namespace ExampleApp
{
class MainWindowViewModel : Observable
{
private ObservableCollection<CharacterClass> _Classes;
private CharacterClass _SelectedClass;
public ObservableCollection<CharacterClass> Classes
{
get { return _Classes; }
set { SetProperty(ref _Classes, value); }
}
public CharacterClass SelectedClass
{
get { return _SelectedClass; }
set { SetProperty(ref _SelectedClass, value); }
}
public MainWindowViewModel()
{
LoadCharacterClasses();
}
private void LoadCharacterClasses()
{
//simulated data retrieval from a DB.
//hardcoded for demo purposes
Classes = new ObservableCollection<CharacterClass>
{
//behold: Gear is saved as an int.
new CharacterClass { Name = "Mage", Gear = 0, Stats = "3,2,1" },
new CharacterClass { Name = "Rogue", Gear = 1, Stats = "2,2,2" },
new CharacterClass { Name = "Warrior", Gear = 2, Stats = "1,2,3" }
};
}
}
}
My CharacterClass definition. Inheriting from Observable which encapsulates INotifyPropertyChanged
namespace ExampleApp
{
public class CharacterClass : Observable
{
private string _Name;
private int _Gear;
private string _Stats;
public string Name
{
get { return _Name; }
set { SetProperty(ref _Name, value); }
}
public int Gear
{
get { return _Gear; }
set { SetProperty(ref _Gear, value); }
}
public string Stats
{
get { return _Stats; }
set { SetProperty(ref _Stats, value); }
}
}
}
Details about the Observable baseclass:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ExampleApp
{
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
{
if (object.Equals(member, val)) return;
member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The DetailsView UserControl:
<UserControl x:Class="ExampleApp.DetailsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ExampleApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:DetailsViewModel}">
<local:DetailsView/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel>
<Label Content="Name:"/>
<Label Content="Base Stats"/>
<Label Content="Starting Gear"/>
</StackPanel>
<StackPanel Grid.Column="1">
<Label Content="{Binding Name}"/>
<Label Content="{Binding Stats}"/>
<Label Content="{Binding gearToString}"/>
</StackPanel>
</Grid>
</UserControl>
and finally: the DetailsViewModel:
public class DetailsViewModel : Observable
{
public string GearToString;
//The method I would like to have called whenever the selected
//CharacterClass (DetailsView.DataContext, so to speak) changes.
private void OnCharacterClassChanged(int gearNumber)
{
switch (gearNumber)
{
case 0:
GearToString = "Cloth";
break;
case 1:
GearToString = "Leather";
break;
case 2:
GearToString = "Plate";
break;
default:
GearToString = "*Error*";
break;
}
}
}
I've fiddled around with attempting to have a command fire when the DetailsView Label updates.
Made a failed attempt to convert DetailsViewModel.GearToString to a dependencyproperty.
I've attempted to override Observable's SetProperty inside of DetailsViewModel.
I don't know which, if any of, those attempts would be viable, if I managed to implement them properly (I've only been coding for several months now :))
I could get it to work using DetailsView code-behind, however that's not MVVM'y.
Because you change your DetailViews DataContext via the combobox, you can access the "current" DetailDataContext before the combobox changes SelectedItem.
You can do this right here:
public CharacterClass SelectedClass
{
get { return _SelectedClass; }
set {
_SelectedClass.DoWhatever();
SetProperty(ref _SelectedClass, value);
}
}
Or you can handle the ComboBoxes SelectionChanged event via a command. Your old value is in e.RemovedItem.
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count > 0)
(e.RemovedItems[0] as CharacterClass).DoSomething();
}
I tend to prefer that approach since it can get confusing quickly if you put too much logic in the setters. It leads to chain reactions that are pretty hard to follow and debug.
In general viewmodels communicate with each other via events. In more complex / disconnected situations with the help of an EventAggregator, MessageBus or something similiar.

WPF TwoWay Binding not work with CustomControl in Template

I have some problems with my Custom Control - Two way binding don't work when I use it in template.
So I have created template xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
xmlns:controls="clr-namespace:GUIControls;assembly=GUIControls"
>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
xmlns:controls="clr-namespace:GUIControls;assembly=GUIControls"
>
<ControlTemplate x:Key="YParamCombo" TargetType="ContentControl">
<controls:ParamCombo Header="MY CONTROL TEMPLATE"
Items="{Binding Items}"
PCValue="{Binding Codes[MY_CONTROL_TEMPLATE], Mode=TwoWay}"
Required="True"
MultiSelect="False"/>
</ControlTemplate>
<ControlTemplate x:Key="YComboBox" TargetType="ContentControl">
<ComboBox DisplayMemberPath="Name"
StaysOpenOnEdit="True"
ItemsSource="{Binding Items}"
SelectedValue="{Binding Codes[STANDARD_TEMPLATE], Mode=TwoWay}"
SelectedValuePath="Code"/>
</ControlTemplate>
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
xmlns:controls="clr-namespace:GUIControls;assembly=GUIControls"
Title="MainWindow" Height="250" Width="525">
<Grid Margin="0,0,0,-1">
<Button Margin="62,162,299,4" Content="Show Codes-1" Click="Button_Click2"></Button>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<HeaderedContentControl Header="STANDARD CONTROL XAML" >
<ComboBox DisplayMemberPath="Name"
StaysOpenOnEdit="True"
ItemsSource="{Binding Items}"
SelectedValue="{Binding Codes[STANDARD_XAML]}"
SelectedValuePath="Code"/>
</HeaderedContentControl>
<HeaderedContentControl Header="STANDARD CONTROL TEMPLATE" >
<ContentControl Height="23" Template="{StaticResource YComboBox}"/>
</HeaderedContentControl>
<ContentControl Height="44" Template="{StaticResource YParamCombo}">
</ContentControl>
<controls:ParamCombo Header="MY CONTROL XAML"
Items="{Binding Items}"
PCValue="{Binding Codes[MYCONTROL_XAML], Mode=TwoWay}"
Required="True"
MultiSelect="False"/>
</StackPanel>
</Grid>
cs
using System.Linq;
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new WModel();
InitializeComponent();
}
private WModel vm { get { return (DataContext as WModel); } }
private void Button_Click2(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Join(";", vm.Codes._codes.Select(x => x.Key + "=" + x.Value).ToArray()));
}
}
}
using GUIControls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace WpfApp1
{
public class WModel
{
public WModel()
{
Codes = new CodesClass();
}
public string Caption { get; set; }
public ObservableCollection<Dict> Items
{
get
{
return new ObservableCollection<Dict>()
{
new Dict(){ Name = "Name1", Code = "Code1" } ,
new Dict(){ Name = "Name2", Code = "Code2" }
};
}
}
public CodesClass Codes { get; set; }
}
public class Dict : IDict
{
public string Name { get; set; }
public string Code { get; set; }
}
public class CodesClass
{
public Dictionary<string, object> _codes;
public CodesClass()
{
_codes = new Dictionary<string, object>();
}
public object this[string param]
{
get
{
if (_codes.ContainsKey(param))
return _codes[param];
else
return null;// "I have no " + param;
}
set
{
_codes[param] = value;
}
}
}
}
When I run app and select all 4 comboboxes and Press button, I can see that twoway binding in one combobox(Custom Control declared in template ) do not work
---------------------------
---------------------------
STANDARD_XAML=Code2;STANDARD_TEMPLATE=Code2;MYCONTROL_XAML=Code2
---------------------------
ОК
---------------------------
Some code from control
public static readonly DependencyProperty PCValueProperty =
DependencyProperty.Register("PCValue", typeof(string), typeof(ParamCombo),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnPCValuePropertyChanged)));
//new PropertyMetadata(new PropertyChangedCallback(OnValuePropertyChanged)));, new PropertyChangedCallback(OnPCValuePropertyChanged))
#endregion
private static void OnPCValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ParamCombo paramCombo = (ParamCombo)sender;
paramCombo.UpdateSelected((e.NewValue == null) ? "" : e.NewValue.ToString());
}
Thanks for your help!
I have had problems with twoway binding in a combo in a customcontrol template and the solution was to override OnApplyTemplate in the custom control, using Template.FindName to get the combo, get the DataContex object of the combo and raise PropertyChanged in the DataContext object for the bound property. My problem was to update the combo when the window was loaded.

WPF/C# Assigning a ViewModel to a custom control from parent view

I'm very new to C# and WPF, and I'm struggling a bit to get data where I need it.
I have one master set of data, which needs to be shared with various user controls, each of which have their own ViewModel. The problem is that I don't seem to be able to assign a ViewModel to a control from the parent XAML and then access that ViewModel from within the custom control's XAML.
I bind the control to a Viewmodel, but then the datacontext within the control doesn't allow me to access that model within the xaml, or I can set the datacontext in the user control so I can access its viewmodel, but then I can't bind to the viewmodel in xaml (because the binding is looking in the local datacontext, not the parent).
I may be going about this all wrong, most examples I've seen seem to instantiate a ViewModel in the custom control xaml, but then I don't see how you get that ViewModel to reference the correct DataModel (or specific part of the datamodel).
The following hopefully explains what I am trying to do.
Firstly I have my data model, in DataModel.cs
using System;
using System.Collections.Generic;
namespace BindingTest1
{
public class DataModel
{
private List<string>[] _dataLists;
public List<string>[] DataLists
{
get { return _dataLists; }
}
public DataModel()
{
List<string> list0 = new List<string> { "One", "Two", "Three" };
List<string> list1 = new List<string> { "Alpha", "Beta", "Gamma" };
_dataLists = new List<String>[] { list0, list1 };
}
}
}
In MainViewModel.cs
namespace BindingTest1
{
class MainViewModel
{
private MyViewModel _myFirstViewModel;
public MyViewModel MyFirstViewModel
{
get { return _myFirstViewModel; }
}
private MyViewModel _mySecondViewModel;
public MyViewModel MySecondModel
{
get { return _mySecondViewModel; }
}
private DataModel _dataModel;
public DataModel DataModel
{
get { return _dataModel; }
}
public MainViewModel()
{
_dataModel = new DataModel();
_myFirstViewModel = new MyViewModel(_dataModel.DataLists[0]);
_mySecondViewModel = new MyViewModel(_dataModel.DataLists[0]);
}
}
}
MainWindow.xaml
<Window x:Class="BindingTest1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BindingTest1"
mc:Ignorable="d"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<StackPanel HorizontalAlignment="Stretch" Height="100" VerticalAlignment="Top" Orientation="Horizontal">
<!-- These were just to check the data was being set up properly -->
<ListBox x:Name="listBox1" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" ItemsSource="{Binding DataModel.DataLists[0]}"/>
<ListBox x:Name="listBox2" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" ItemsSource="{Binding DataModel.DataLists[1]}"/>
<!-- this is what I want to be able to do -->
<local:MyView ViewModel="{Binding MyFirstViewModel}"/>
<local:MyView ViewModel="{Binding MySecondViewModel}"/>
</StackPanel>
</Grid>
</Window>
(Codebehind is default)
In MyViewModel.cs
using System;
using System.Collections.Generic;
namespace BindingTest1
{
public class MyViewModel
{
private List<string> _dataList;
public List<string> DataList
{
get { return _dataList; }
}
public MyViewModel(List<string> list)
{
_dataList = new List<String>(list);
_dataList.Add("Some Local Processing");
}
}
}
MyView.xaml
<UserControl x:Class="BindingTest1.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BindingTest1"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="100">
<Grid>
<ListBox x:Name="listBox" HorizontalAlignment="Left" Height="100" VerticalAlignment="Top" Width="100" ItemsSource="{Binding ViewModel.DataList}"/>
</Grid>
</UserControl>
Codebehind
using System.Windows;
using System.Windows.Controls;
namespace BindingTest1
{
/// <summary>
/// Interaction logic for MyView.xaml
/// </summary>
public partial class MyView : UserControl
{
public MyViewModel ViewModel
{
get { return (MyViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MyViewModel), typeof(MyView),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnViewModelChanged)));
public MyView()
{
InitializeComponent();
}
private static void OnViewModelChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
// Just making sure the right thing is being received
List<string> dataList = (e.NewValue as MyViewModel).DataList;
foreach(string line in dataList)
{
System.Console.WriteLine(line);
}
}
}
}
I don't think you need a dependency property here.
Try this.
<local:MyView DataContext="{Binding MyFirstViewModel}"/>
<local:MyView DataContext="{Binding MySecondViewModel}"/>
and bind the DataList to ItemsSource in the MyView XAML.
As you assigned MyFirstViewModel to the DataContext of MyView, bindings inside will look in MyFirstViewModel for the ItemsSource.
Here's how you ought to do this. Your view doesn't need a ViewModel property. It should bind to properties of its DataContext, which will be the viewmodel.
view:
ItemsSource="{Binding DataList}"
Window:
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyViewModel}">
<local:MyView
/>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel HorizontalAlignment="Stretch" Height="100" VerticalAlignment="Top" Orientation="Horizontal">
<!-- ... -->
<ContentControl Content="{Binding MyFirstViewModel}"/>
<ContentControl Content="{Binding MySecondViewModel}"/>
</StackPanel>

Add New Usercontrol On button Click Command In WPF MVVM

Hi i am trying to display usercontrol Dynamically But it is not working ...please help me to improve code .
In cunstructor of MainWindowViewModel i tried to set initial property of contentcontrol.
Thank you in advance
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:VM="clr-namespace:WpfApplication1.ViewModel"
xmlns:View="clr-namespace:WpfApplication1.View"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type VM:FirstControlViewModel}">
<View:FirstControl></View:FirstControl>
</DataTemplate>
<DataTemplate DataType="{x:Type VM:SecondControlViewModel}">
<View:SecondControl></View:SecondControl>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding LoadedControl}" />
</Grid>
</Window>
View Model Code :-
public class MainViewModel: INotifyPropertyChanged
{
public MainViewModel()
{
LoadedControl = "FirstControlViewModel";// here i am setting property
//But not working
}
private string _LoadedControl;
public string LoadedControl
{
get { return _LoadedControl; }
set { _LoadedControl = value;
NotifyPropertyChanged("LoadedControl");
}
}
You need to set LoadedControl to an instance of your ViewModel type, not a string!
public MainViewModel()
{
LoadedControl = new FirstControlViewModel();
}
private ViewModelBase _LoadedControl;
public ViewModelBase LoadedControl
{
get { return _LoadedControl; }
set { _LoadedControl = value;
NotifyPropertyChanged("LoadedControl");
}
}

User controls are not retaining the old values when loading through ListBox selecteditem in wpf mvvm

I have recently started using WPF and MVVM approach. I ma getting a problem in the following case. Can someone help me on this?
I have a list box in my MainWindow.xaml. I am trying to load the different user controls for each list box item selection. My MainWindow.xaml looks like below.
MainWindow.xaml
<Window x:Class="MoreOnBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MoreOnBinding"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:UserControl1VM}">
<local:UserControl1/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:UserControl2VM}">
<local:UserControl2/>
</DataTemplate>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding ViewModelsCollection}" DisplayMemberPath="ViewModelName" SelectedValue="{Binding SelectedViewModel}" IsSynchronizedWithCurrentItem="True"/>
<ContentControl Grid.Column="1" Content="{Binding SelectedViewModel.ViewModel}"/>
</Grid>
</Window>
My MainWidnow View model is as below.
MainWindowVM
public class MainWindowVM : ViewModelBase
{
public MainWindowVM()
{
this.ViewModelsCollection = new ObservableCollection<ViewModelInfo>(new List<ViewModelInfo>()
{
new ViewModelInfo("Control1", new UserControl1VM()),
new ViewModelInfo("Control2", new UserControl2VM()),
});
}
private ObservableCollection<ViewModelInfo> viewModelsCollection;
public ObservableCollection<ViewModelInfo> ViewModelsCollection
{
get { return viewModelsCollection; }
set
{
if (viewModelsCollection != value)
{
viewModelsCollection = value;
RaisePropertyChanged(() => ViewModelsCollection);
this.SelectedViewModel = this.ViewModelsCollection[0];
}
}
}
private ViewModelInfo selectedViewModel;
public ViewModelInfo SelectedViewModel
{
get { return selectedViewModel; }
set
{
if (selectedViewModel != value)
{
selectedViewModel = value;
RaisePropertyChanged(() => SelectedViewModel);
}
}
}
}
public class ViewModelInfo : ViewModelBase
{
public ViewModelInfo(string viewModelName, ViewModelBase viewModel)
{
this.ViewModelName = viewModelName;
this.ViewModel = viewModel;
}
private string viewModelName;
public string ViewModelName
{
get { return viewModelName; }
set
{
if (viewModelName != value)
{
viewModelName = value;
RaisePropertyChanged(() => ViewModelName);
}
}
}
private ViewModelBase viewModel;
public ViewModelBase ViewModel
{
get { return viewModel; }
set
{
if (viewModel != value)
{
viewModel = value;
RaisePropertyChanged(() => ViewModel);
}
}
}
}
Each user control has a text box in it. The user control xaml and viewmodels are as below.
UserControl1
<UserControl x:Class="MoreOnBinding.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MoreOnBinding"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="User Control 1"/>
<TextBox Grid.Row="2" Text="{Binding UC1Text}" Width="100" Height="30"/>
</Grid>
</UserControl>
UserControl1VM
public class UserControl1VM : ViewModelBase
{
private string uC1Text;
public string UC2Text
{
get { return uC1Text; }
set
{
if (uC1Text != value)
{
uC1Text = value;
RaisePropertyChanged(() => UC2Text);
}
}
}
}
The user control1 code file is as below
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
}
The UserControl 2 is same as the user control1 only the name change.
Now coming to my problem, after running the application I clicked the first item of the list box and it loaded the UserControl1 on ContentControl. I have enntered some text say "UC1" in the text box displayed. However if click on the second item of the listbox and agian click on the first item, the text I have entered is lost. I want to retain old text. Can someone look into this and help me out?
You have a typo: data source property is UserControl1VM.UC2Text, and the binding is to UC1Text. If you'll look at output window, you'll see a binding error.

Categories

Resources