0

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

我正在尝试使用 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 模式。

4

2 回答 2

0

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

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

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

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

这应该有效。(您不再需要 ElementName=MyNameControl 了)。

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

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

这应该有效。

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

希望这有帮助。

BR,

TJ

于 2011-03-29T06:17:50.870 回答
0

我正在回答我自己的问题。但在回答我的问题之前,只想澄清一些事情。我试图用自己的 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));
    }
}

用户名ViewModel.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>

消费名称ViewModel.cs

public class ConsumeNameViewModel : BaseViewModel
{

    private string _UserFullName;

    public string UserFullName
    {
        get { return _UserFullName; }
        set
        {
            if (value != _UserFullName)
            {
                _UserFullName = value;
                NotifyPropertyChanged("UserFullName");
            }
        }
    }

}
于 2011-04-21T18:57:58.567 回答