【问题标题】:How to propagate silverlight usercontrol properties to parent?如何将 Silverlight 用户控件属性传播给父级?
【发布时间】:2011-03-29 02:10:03
【问题描述】:

我希望自定义用户控件中的一些属性可用于父页面。我创建了一个小样本来说明我在寻找什么。

我正在尝试使用 MVVM 模式和所有绑定机制来实现它。

用户控制 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>

上面的Usercontrol绑定到下面的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");
        }
    }
}

使用上述 USERCONTROL 的消费者页面

<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: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>

现在这是我的问题,如果您查看与用户控件关联的视图模型,它有一个名为 FullName 的属性,我希望通过 Usercontrol 公开它,以便我可以从消费页面访问它。就像消费页面想要访问用户控件的一些属性一样。我不太确定如何实现这一目标。我想坚持使用 MVVM 模式。

【问题讨论】:

    标签: silverlight user-controls properties propagation


    【解决方案1】:

    您已经标记了一个 StaticResource,因此您可以在两个视图中使用它。 这样做

    <UserControl.Resources>
        <local:UserNameViewModel x:Key="TheViewModel"/>
    </UserControl.Resources>
    

    在“ConsumeName 页面”中。如果您只是将 DataContext 添加到您的网格中

    <Grid x:Name="LayoutRoot" DataContext="{StaticResource TheViewModel}">
    

    这应该可以。 (您不再需要 ElementName=MyNameControl)。

    my:UserNameControl 应该继承 DataContext。如果没有,您必须在此处添加它。

    <my:UserNameControl DataContext="{StaticResource TheViewModel}"/>
    

    这应该可行。

    现在 local:UserNameViewModel 和键 TheViewModel 只能在您定义它的地方实现。如果您在 app.xaml 中定义它,您可以从应用程序的任何位置访问它。

    希望这一切顺利。

    BR,

    TJ

    【讨论】:

    • 您好 TJ,感谢您的帮助,但这并没有解决我的问题。让我重新表述我的问题。从父页面视图模型与用户控件视图模型通信的最佳方式是什么?
    • 您是否尝试过将 TheViewModel 直接设置为父视图中的 DataContext?如果这样做,您可以像这样访问它:{Binding DataContext, ElementName=MyNameControl}。这应该有效。如果可能,我会在 app.xaml 中定义一个静态资源。我想这将是 MVVM 的最佳方法。
    【解决方案2】:

    我正在回答我自己的问题。但在回答我的问题之前,只想澄清一些事情。我试图用自己的 ViewModel 创建一个完全封装的 UserControl。并且无论在哪里消费用户控件,消费者都应该对用户控件的内部视图模型一无所知。我希望消费者拥有的唯一通信选项是设置一些属性并使用绑定机制。 所以我解决问题的方法是,我在 UserControl 中创建了依赖属性,并在用户控件的视图模型发生变化时设置它。

    UserNameControl.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>
    

    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(带有 DependencyProperty 声明的用户控件代码)

    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(消费者导航页面,用户高于用户控件并将 SelectedFullName 拉入 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");
                }
            }
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多