【问题标题】:Bind property between sibling user controls在同级用户控件之间绑定属性
【发布时间】:2018-09-20 11:53:23
【问题描述】:

我有以下问题:我在父用户控件中有两个不同的用户控件。它们是trainList,它包含一个火车对象列表和trainView,它是一个用户控件,在列表中显示所选火车的详细信息。

我希望与trainView 共享一个变量trainList。 我现在拥有的是:

父用户控制:

<UserControl>
    <UserControl>
        <customControls:trainList x:Name="trainList"></customControls:trainList>
    </UserControl>

    <UserControl>
        <customControls:trainView x:Name="trainView"></customControls:trainView>
    </UserControl>

    <TextBlock DataContext="{Binding ElementName=trainList, Path=SelectedTrain}" Text="{ Binding SelectedTrain.Id }">Test text</TextBlock>
</UserControl>

TrainList 类:

public partial class TrainList : UserControl
    {
        public TrainList()
        {
            this.InitializeComponent();
            this.DataContext = this;
        }

        public Train SelectedTrain { get; set; }

        public void SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            Debug.Print(this.SelectedTrain.Id);
        }
    }

注意:Train 类实现了INotifyPropertyChanged

如果我让它工作,我会将绑定应用到 trainView 用户控件(不确定这是否会工作)而不是文本块。

    <UserControl>
        <customControls:trainView x:Name="trainView" DataContext="{Binding ElementName=trainList, Path=SelectedTrain}"></customControls:trainView>
    </UserControl>

然后,我会以某种方式从 trainView 的代码隐藏中访问该变量。

(在此之后,我想与其父用户控件共享一个与 trainView 不同的变量,但也许这是另一个问题)。

我目前的问题是:可以这样做还是我需要遵循另一种策略?

【问题讨论】:

  • 首先,在任何情况下都不应在 UserControl 的构造函数中使用 DataContext = this;,因为它会破坏其依赖属性的任何典型的基于 DataContext 的绑定。其次,两个用户控件应该共享一个comman view model,它可以由外部的UserControl提供。 TrainList 和 TrainView(或其共同内容)的属性将绑定到公共视图模型,该模型将保存 SelectedTrain 属性和具有所有火车列表的另一个属性。
  • @Clemens 感谢您的回答。我是 WPF 新手,从未应用过 MVVM 模式。我不知道给我一个你提到的通用视图模型的示例实现是否太过分。

标签: c# wpf xaml binding


【解决方案1】:

采用这个简单的视图模型,它有一个实现INotifyPropertyChanged 接口的基类,以及一个Train、TrainViewModel 和MainViewModel 类。

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected void SetValue<T>(
        ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (!Equals(storage, value))
        {
            storage = value;
            OnPropertyChanged(propertyName);
        }
    }
}

public class Train : ViewModelBase
{
    private string name;
    public string Name
    {
        get { return name; }
        set { SetValue(ref name, value); }
    }

    private string details;
    public string Details
    {
        get { return details; }
        set { SetValue(ref details, value); }
    }

    // more properties
}

public class TrainViewModel : ViewModelBase
{
    public ObservableCollection<Train> Trains { get; }
        = new ObservableCollection<Train>();

    private Train selectedTrain;
    public Train SelectedTrain
    {
        get { return selectedTrain; }
        set { SetValue(ref selectedTrain, value); }
    }
}

public class MainViewModel
{
    public TrainViewModel TrainViewModel { get; } = new TrainViewModel();
}

可以像这样在 MainWindow 的构造函数中初始化:

public MainWindow()
{
    InitializeComponent();

    var vm = new MainViewModel();
    DataContext = vm;

    vm.TrainViewModel.Trains.Add(new Train
    {
        Name = "Train 1",
        Details = "Details of Train 1"
    });

    vm.TrainViewModel.Trains.Add(new Train
    {
        Name = "Train 2",
        Details = "Details of Train 2"
    });
}

TrainDetails 控件看起来像这样,当然还有更多元素用于 Train 类的更多属性:

<UserControl ...>
    <StackPanel>
        <TextBlock Text="{Binding Name}"/>
        <TextBlock Text="{Binding Details}"/>
    </StackPanel>
</UserControl>

和这样的父 UserControl,我直接使用 ListBox 而不是 TrainList 控件:

<UserControl ...>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ListBox ItemsSource="{Binding Trains}"
                 SelectedItem="{Binding SelectedTrain}"
                 DisplayMemberPath="Name"/>
        <local:TrainDetailsControl Grid.Column="1" DataContext="{Binding SelectedTrain}"/>
    </Grid>
</UserControl>

它将像这样在 MainWindow 中实例化:

<Grid>
    <local:TrainControl DataContext="{Binding TrainViewModel}"/>
</Grid>

请注意,在这个简单的示例中,UserControls 的 XAML 中的元素直接绑定到通过其 DataContext 传递的视图模型实例。这意味着 UserControl 知道视图模型(或至少知道它们的属性)。更通用的方法是在 UserControl 类中声明依赖属性,这些属性绑定到视图模型属性。然后,UserControl 将独立于任何特定的视图模型。

【讨论】:

  • 谢谢,这非常有用。但是,如果我想使用另一个 UserControl 而不是直接使用 ListBox,我应该放什么?我试过&lt;local:TrainListControl DataContext="{Binding RelativeSource={RelativeSource Self}}"/&gt;,但列表没有填满,是空白的(当我直接使用 ListBox 时,TrainsDetailsControl 效果很好)。
  • 哦,好的。我多虑了。只需&lt;local:TrainListControl /&gt; 效果很好。
猜你喜欢
  • 1970-01-01
  • 2012-12-31
  • 1970-01-01
  • 1970-01-01
  • 2011-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多