How to propagate silverlight usercontrol properties to parent?
I would like to have some of the properties in the custom user control to be available to the parent page. I created a small sample to illustrate what I am looking for.
I am trying to use MVVM pattern and all the binding mechanisms to achieve it.
USERCONTROL XAML
<UserControl x:Class="TestCustomUserControl.MyControls.UserNameControl"
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:TestCustomUserControl.ViewModels"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<local:UserNameViewModel x:Key="TheViewModel"/>
</UserControl.Resources>
<Grid x:Name="NameCtrlRoot" Background="White" DataContext="{StaticResource TheViewModel}">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="First Name:" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Last Name: "/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="txtFirstName" Text="{Binding FirstName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<!-- This behavior updates the binding after a specified delay
instead of the user having to lose focus on the TextBox. -->
<local:TextChangedDelayedBindingBehavior RefreshTimeMilliseconds="750" />
</i:Interaction.Behaviors>
</TextBox>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="txtLastName" Text="{Binding LastName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<!-- This behavior updates the binding after a specified delay
instead of the user having to lose focus on the TextBox. -->
<local:TextChangedDelayedBindingBehavior RefreshTimeMilliseconds="750" />
</i:Interaction.Behaviors>
</TextBox>
<TextBlock Grid.Row="3" Grid.Column="0" Text="fullname inside control:" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding FullName}" />
<Border Height="1" Background="Black" Grid.Column="2" Grid.Row="4" />
</Grid>
</StackPanel>
</Grid>
the above Usercontrol is binded to the following VIEWMODEL
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class UserNameViewModel : BaseViewModel
{
private String _firstName;
public String FirstName
{
get { return _firstName; }
set
{
_firstName = value;
NotifyPropertyChanged("FirstName");
OnNameChange();
}
}
private String _lastName;
public String LastName
{
get { return _lastName; }
set
{
_lastName = value;
NotifyPropertyChanged("LastName");
OnNameChange();
}
}
private void OnNameChange()
{
FullName = String.Format("{0} {1}", FirstName, LastName);
}
public String _fullName;
public String FullName
{
get { return _fullName; }
set {
_fullName = value;
NotifyPropertyChanged("FullName");
}
}
}
Consumer Page that uses the above USERCONTROL
<navigation:Page xmlns:my="clr-namespace:TestCustomUser开发者_运维问答Control.MyControls" x:Class="TestCustomUserControl.Views.ConsumeName"
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"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="640" d:DesignHeight="480"
Title="ConsumeName Page">
<Grid x:Name="LayoutRoot">
<StackPanel>
<my:UserNameControl x:Name="MyNameControl"/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Full Name in Parent: " />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding FullName, ElementName=MyNameControl}"/>
</Grid>
</StackPanel>
</Grid>
Here is my question now, If you look at the view model associated with user control, it has a property called FullName and I would like that to be exposed via Usercontrol, so that I can access it from the consuming page. Its like consuming page want to access some of the properties of usercontrol. I am not quite sure as to how that can be acheived. I would like to stick with MVVM pattern.
you have already decalred a StaticResource, so you can use it in both views. Do this
<UserControl.Resources>
<local:UserNameViewModel x:Key="TheViewModel"/>
</UserControl.Resources>
in the "ConsumeName Page". If yous simply add the DataContext to your Grid
<Grid x:Name="LayoutRoot" DataContext="{StaticResource TheViewModel}">
this should work. (You don't need ElementName=MyNameControl any longer).
The my:UserNameControl should inherit the DataContext. If not, you have to add it here.
<my:UserNameControl DataContext="{StaticResource TheViewModel}"/>
This should work.
Right now the local:UserNameViewModel with the key TheViewModel is only achievable where you defined it. If you define it in your app.xaml you can access it from everywhere in the app.
Hope this hels.
BR,
TJ
I am answering my own question. But before answering my question, just want to clarify somethings. I was trying to create a fully encapsulated UserControl with its own ViewModel. And where ever the usercontrol is consumed, the consumer should know nothing about the usercontrol's internal viewmodel. Only communication option I want the consumer to have is by setting some properties and using binding mechanism. So the way I resolved my problem is, I created dependency property inside the UserControl and setting it whenever something changes in the usercontrol's viewmodel.
UserNameControl.xaml (usercontrol)
<UserControl x:Class="TestCustomUserControl.MyControls.UserNameControl"
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:TestCustomUserControl.ViewModels"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<local:UserNameViewModel x:Key="TheViewModel"/>
</UserControl.Resources>
<Grid x:Name="NameCtrlRoot" Background="White" DataContext="{StaticResource TheViewModel}">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="First Name:" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Last Name: "/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="txtFirstName" Text="{Binding FirstName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<!-- This behavior updates the binding after a specified delay
instead of the user having to lose focus on the TextBox. -->
<local:TextChangedDelayedBindingBehavior RefreshTimeMilliseconds="750" />
</i:Interaction.Behaviors>
</TextBox>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="txtLastName" Text="{Binding LastName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<!-- This behavior updates the binding after a specified delay
instead of the user having to lose focus on the TextBox. -->
<local:TextChangedDelayedBindingBehavior RefreshTimeMilliseconds="750" />
</i:Interaction.Behaviors>
</TextBox>
<TextBlock Grid.Row="3" Grid.Column="0" Text="fullname inside control:" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding FullName}" />
<Border Height="1" Background="Black" Grid.Column="2" Grid.Row="4" />
</Grid>
</StackPanel>
</Grid>
BaseViewModel.cs
public class BaseViewModel : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
UserNameViewModel.cs
public class UserNameViewModel : BaseViewModel
{
private String _firstName;
public String FirstName
{
get { return _firstName; }
set
{
_firstName = value;
NotifyPropertyChanged("FirstName");
OnNameChange();
}
}
private String _lastName;
public String LastName
{
get { return _lastName; }
set
{
_lastName = value;
NotifyPropertyChanged("LastName");
OnNameChange();
}
}
public Action NameChanged { get; set; }
private void OnNameChange()
{
FullName = String.Format("{0} {1}", FirstName, LastName);
if(NameChanged != null) NameChanged.Invoke();
}
private String _fullName;
public String FullName
{
get { return _fullName; }
set {
_fullName = value;
NotifyPropertyChanged("FullName");
}
}
}
UserNameControl.xaml.cs (Usercontrol code behind with DependencyProperty declaration)
public partial class UserNameControl : UserControl
{
private UserNameViewModel _TheViewModel;
public UserNameControl()
{
InitializeComponent();
_TheViewModel = Resources["TheViewModel"] as UserNameViewModel;
_TheViewModel.NameChanged = OnNameChanged;
}
public String SelectedFullName
{
get { return (String) GetValue(SelectedFullNameProperty); }
set { SetValue(SelectedFullNameProperty, value); }
}
public static readonly DependencyProperty SelectedFullNameProperty =
DependencyProperty.Register("SelectedFullName", typeof (String), typeof (UserNameControl), null);
private void OnNameChanged()
{
SelectedFullName = _TheViewModel.FullName;
}
}
ConsumeName.xaml (Consumer navigation Page, user above usercontrol and pulls SelectedFullName into UserFullName)
<navigation:Page xmlns:my="clr-namespace:TestCustomUserControl.MyControls" x:Class="TestCustomUserControl.Views.ConsumeName"
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"
mc:Ignorable="d"
xmlns:local="clr-namespace:TestCustomUserControl.ViewModels"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="640" d:DesignHeight="480"
Title="ConsumeName Page">
<navigation:Page.Resources>
<local:ConsumeNameViewModel x:Key="TheConsumeNameViewModel"/>
</navigation:Page.Resources>
<Grid x:Name="LayoutRoot" DataContext="{StaticResource TheConsumeNameViewModel}">
<StackPanel>
<my:UserNameControl x:Name="MyNameControl" SelectedFullName="{Binding UserFullName, Mode=TwoWay}" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Full Name in Parent: " />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding UserFullName}"/>
</Grid>
</StackPanel>
</Grid>
ConsumeNameViewModel.cs
public class ConsumeNameViewModel : BaseViewModel
{
private string _UserFullName;
public string UserFullName
{
get { return _UserFullName; }
set
{
if (value != _UserFullName)
{
_UserFullName = value;
NotifyPropertyChanged("UserFullName");
}
}
}
}
精彩评论