Add New Usercontrol On button Click Command In WPF MVVM - c#

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

Related

Changing the ContentControl binding results in a null DataContext

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.

WPF Navigation using MVVM

I'm trying to follow the answer provided in this post, but I must be missing something trivial. I've defined my DataTemplates as App.xaml as follows:
<Application.Resources>
<DataTemplate DataType="{x:Type vm:BlowerViewModel}">
<v:BlowerView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<v:HomeView />
</DataTemplate>
</Application.Resources>
Then, in my MainWindow.xaml I've defined the following code:
<Window x:Class="App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:App.UI.ViewModel"
Title="MainWindow" SizeToContent="WidthAndHeight">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<ContentControl Content="{Binding CurrentView}" />
</Window>
The code for MainViewModel contains a property CurrentView and an ICommand so I can switch views. Defined as follows:
public class MainViewModel : BaseViewModel
{
private BaseViewModel _currentView;
public MainViewModel()
{
CurrentView = new HomeViewModel();
}
public BaseViewModel CurrentView
{
get { return _currentView; }
set
{
if (_currentView != value)
{
_currentView = value;
RaiseChangedEvent("CurrentView");
}
}
}
public ICommand SwitchView {
get {
return new CommandHandler(() => SwitchBlower());
}
}
protected void SwitchBlower()
{
CurrentView = new BlowerViewModel();
}
}
In my HomeView.xaml, I have defined a button that links to the MainViewModel to execute the SwitchView ICommand. This is shown below.
<UserControl x:Class="App.UI.View.HomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:App.UI.ViewModel"
Height="300" Width="300">
<Grid>
<TextBlock>This is the homeview</TextBlock>
<Button Command="{Binding DataContext.SwitchView, RelativeSource={RelativeSource AncestorType={x:Type vm:MainViewModel}}, Mode=OneWay}" Content="Test" />
</Grid>
</UserControl>
When I start the application it doesn't register the event, and clicking on the button does not fire the event to change the view. I've tried putting breakpoints in both the ICommand get and the function call itself. At first, I thought maybe I needed to define MainViewModel in my data templates, but doing so results in the following error (even though the project builds fine)
Can't put a Window in a style
Can anyone provide the missing piece I need to get this working?
The AncestorType should be MainWindow not MainViewModel. MainViewModel is not a class that is part of the visual tree.

Data Template not shown

A listbox data template doesn't show and I cannot figure out why.
If I don't use a DataTemplate and copy the contents into the control section itself, it's fine.
I don't do very much binding in XAML, I usually do it all in code. What did I do wrong?
XAML
<UserControl x:Class="Cis.CustomControls.CisArrivalsPanel"
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"
mc:Ignorable="d" Height="296" Width="876">
<UserControl.Resources>
<DataTemplate x:Key="DataTemplate">
<ListBoxItem>
<StackPanel>
<TextBlock Background="Blue" Text="{Binding Path=StationName}" />
<TextBlock Background="Brown" Text="{Binding Path=ArrivalPlatform}" />
</StackPanel>
</ListBoxItem>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<ListBox Width="487" Margin="0,66,0,33" ItemTemplate="{StaticResource DataTemplate}">
</ListBox>
</StackPanel>
</Grid>
</UserControl>
CS
public partial class CisArrivalsPanel : UserControl
{
public CisArrivalsPanel()
{
InitializeComponent();
this.DataContext = new ArrivalRowItem();
}
}
Model
public class ArrivalRowItem : INotifyPropertyChanged
{
public ArrivalRowItem()
{
this.StationName = "Lincoln";
this.ArrivalPlatform = "1";
}
private string _stationName;
public string StationName
{
get
{
return _stationName;
}
set
{
_stationName = value;
NotifyPropertyChanged("StationName");
}
}
private string _arrivalPlatform;
public string ArrivalPlatform
{
get
{
return _arrivalPlatform;
}
set
{
_arrivalPlatform = value;
NotifyPropertyChanged("ArrivalPlatform");
}
}
private DateTime _arrivalDateTime;
public DateTime ArrivalDateTime
{
get
{
return _arrivalDateTime;
}
set
{
_arrivalDateTime = value;
NotifyPropertyChanged("ArrivalDateTime");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
You have everything set up, but you don't actually have any data.
ListBox, like other ItemsControls acts against a collection of data, and generates an instance of the template for each item it finds.
Given that you haven't set ItemsSource or populated any collection I can see, you need to create a collection (probably an ObservableCollection) and set the ItemsSource to it via binding. Then add some items to it, and the ListBox will display them!

Bind DependencyProperty of Usercontrol in ListBox

I need ListBox with my UserControl listed in it. My UserControl has TextBox. So I want to display property of List's subitem in UserControl's textBox. I have tried a lot of options with DataContext and ElementName - it just doesn`t work. I just stucked on it. The only way to make it work is to remove DataContext binding of UserControl to itself and change Item Property name so it matches to DependencyProperty name - but I need to reuse my control in different viewmodels with different entities so it is almost not possible to use the approach.
Interesting thing is that if I change my UserControl to Textbox and bind Text property of it - everything works. What the difference between Textbox and my UserControl?
So let me just show my code.
I have simplified the code to show only essential:
Control XAML:
<UserControl x:Class="TestControl.MyControl"
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"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="200"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<TextBlock Text="{Binding Text}"/>
</Grid>
</UserControl>
Control CS:
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
}
public string Text
{
get {
return (string)this.GetValue(TextProperty); }
set {
this.SetValue(TextProperty, value); }
}
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(MyControl), new propertyMetadata(""));
}
Window XAML:
<Window x:Class="TestControl.MainWindow"
Name="_windows"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestControl"
Title="MainWindow" Height="350" Width="525" >
<Grid Name="RootGrid">
<ListBox ItemsSource="{Binding ElementName=_windows, Path=MyList}">
<ItemsControl.ItemTemplate >
<DataTemplate >
<local:MyControl Text="{Binding Path=Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
</Grid>
</Window>
Window CS:
public partial class MainWindow : Window
{
public MainWindow()
{
_list = new ObservableCollection<Item>();
_list.Add(new Item("Sam"));
_list.Add(new Item("App"));
_list.Add(new Item("H**"));
InitializeComponent();
}
private ObservableCollection<Item> _list;
public ObservableCollection<Item> MyList
{
get { return _list;}
set {}
}
}
public class Item
{
public Item(string name)
{
_name = name;
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
This is a pretty big gotcha in XAML. The problem is that when you do this in the user control:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
You change its data context, so that in this line:
<local:MyControl Text="{Binding Path=Name}"/>
The runtime will now attempt to resolve "Name" on the instance of "MyControl", instead of on the inherited data context (ie, the view model). (Confirm this by checking the Output window -- you should see a binding error to that effect.)
You can get around this by, instead of setting the user control's data context that way, using a RelativeSource binding:
<UserControl x:Class="TestControl.MyControl"
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"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="200"
<Grid>
<TextBlock
Text="{Binding Text,RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
</Grid>
</UserControl>

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