更改DataTemplate中ComboBox的值

时间:2017-07-19 17:09:45

标签: c# wpf

我有一个带有DataTemplate的Listbox,其中包含一个Combobox。我需要更改特定ComboBox的selectedItem / Index。我该如何访问它?

其他详细信息

所有组合框都有相同的选项。如果将Combobox设置为与另一个ComboBox相同的值,则首先设置的Combobox应该返回空(这是我的cbxOptions字典中ComboBoxes绑定的第一个项目。)

<DataTemplate x:Key="lbxHeaderDataTemplate">
    <Grid>
        <Label Content="{Binding Item1}"></Label>
        <ComboBox Grid.Column="1" ItemsSource="{Binding Item2}" 
DisplayMemberPath="Key" SelectionChanged="ComboBox_SelectionChanged"></ComboBox>
    </Grid>
</DataTemplate>

C# 填充用户界面

foreach (DataColumn dc in _loadedData.Columns)
{
    ListBox.Items.Add(new Tuple<string, Dictionary<string, string>>
              (dc.ColumnName, cbxOptions));
}

尝试擦除组合框

这是我期望我可以通过列表框进行预测的地方,检查控件是否匹配,此时我将其更改为空白。然而,我的foreach只是让我回到了愚蠢的元组......这是只读的,但我认为不管怎么说都要更新我的ComboBox。

private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    ComboBox cbxSelected = (ComboBox)sender;
    DependencyObject parent = VisualTreeHelper.GetParent(cbxSelected);
    Label currentLbl = null;
    foreach (object o in ((Grid)parent).Children)
    {
        if (o is Label)
        {
            currentLbl = (Label)o;
        }
    }

    string LblText = currentLbl.Content.ToString();
    string cbxValue = cbxSelected.SelectedValue.ToString();

    //HERE I want to iterate through the listbox controls, not the datasource
    foreach (Tuple<string, Dictionary<string, string>> l in lbxDatFields.Items)
    {
        //l.Item2 = "";
        if (l.Item1.EndsWith(cbxOptions[cbxValue]))
            l = new Tuple<string, Dictionary<string, string>>(l.Item1, "");
    }
}

我确定必须有一种非常简单的方法来访问控件。任何帮助将非常感激。如果需要其他信息,请告诉我。

2 个答案:

答案 0 :(得分:1)

如果没有一个好的Minimal, Complete, and Verifiable code example能够清晰简洁地说明您的问题,那么尝试解决您当前的设计是不切实际的。根据您发布的一些代码,可以做一些观察:

  • 由于Tuple<...>是不可变的,因此您无法修改Item2属性。您必须用新的Tuple<...>对象替换整个l对象。
  • 您发布的代码甚至不应该编译,因为您试图修改foreach循环中的Item2变量。
  • 即使你可以,它也不会改变列表本身的元素,只是改变那个特定的变量。
  • 并不是说你甚至想要改变元素;它是应该更改的组合框的选择,而不是其ComboBox选项。

ComboBox项的字典对象的使用使我无法使用。也许有一个完整的代码示例,它会更清楚。

所有这一切......

  

我如何访问它?

这个问题的出现只是因为你误用了WPF。你不应该直接操纵UI;相反,您的UI状态应该在视图模型数据结构中表示。然后class PropertyChangedExEventArgs<T> : PropertyChangedEventArgs { public T OldValue { get; } public PropertyChangedExEventArgs(string propertyName, T oldValue) : base(propertyName) { OldValue = oldValue; } } class ItemViewModel : INotifyPropertyChanged { private string _name; public string Name { get { return _name; } set { _UpdateField(ref _name, value); } } private string _value; public string Value { get { return _value; } set { _UpdateField(ref _value, value); } } public event PropertyChangedEventHandler PropertyChanged; protected void _UpdateField<T>(ref T field, T newValue, Action<T> onChangedCallback = null, [CallerMemberName] string propertyName = null) { if (EqualityComparer<T>.Default.Equals(field, newValue)) { return; } T oldValue = field; field = newValue; onChangedCallback?.Invoke(oldValue); PropertyChanged?.Invoke(this, new PropertyChangedExEventArgs<T>(propertyName, oldValue)); } } 选择将绑定到视图模型属性,您问题的答案就是查看该属性。

由于缺乏细节,很难确定,但在我看来,您正在尝试实施一个项目列表,其中每个项目都有一个可选项,并且你希望这些选项是互斥的。也就是说,一次只能有一个项目可以有任何给定的选项。

假设情况如此,我将展示一个实现,在我看来,它比你试图实现的方法要好得多。也就是说,它使用我上面提出的基本思想,从数据模型开始,然后从那里回到UI。这样做,数据模型非常简单易懂,所有行为的实现也是如此。

看起来像这样......

首先,从基本的每项视图模型数据结构开始:

INotifyPropertyChanged

注意:

  • 上述类直接实现Values。在实际程序中,此实现通常位于基类中,每个视图模型类都继承该基类。如果您进行了大量的WPF编程,那么您将此基类作为可重用组件包含在每个项目中,可以在您引用的单独项目中,也可以作为代码片段。您可以使用许多WPF用户框架,它们提供此功能。
  • 在这个特定的例子中,事件订阅者在更改后知道属性的旧值时,还不是一个方便的机制,但所涉及的逻辑要求,以便关键字为当值不再有效时,可以从字典中删除从值到模型对象的映射。有多种方法可以满足这种需求 - 可以说,更简单的方法就是对相对较小的字典的PropertyChangedEventArgs集合进行线性搜索。但我决定扩展class MainViewModel { public ObservableCollection<ItemViewModel> Items { get; } = new ObservableCollection<ItemViewModel> { new ItemViewModel { Name = "Item #1" }, new ItemViewModel { Name = "Item #2" }, new ItemViewModel { Name = "Item #3" }, }; public IReadOnlyList<string> Options { get; } = new [] { "Option One", "Option Two", "Option Three" }; private readonly Dictionary<string, ItemViewModel> _valueToModel = new Dictionary<string, ItemViewModel>(); public MainViewModel() { foreach (ItemViewModel itemModel in Items) { itemModel.PropertyChanged += _ItemPropertyChanged; } } private void _ItemPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(ItemViewModel.Value)) { ItemViewModel itemModel = (ItemViewModel)sender; PropertyChangedExEventArgs<string> exArgs = (PropertyChangedExEventArgs<string>)e; if (exArgs.OldValue != null) { _valueToModel.Remove(exArgs.OldValue); } if (itemModel.Value != null) { if (_valueToModel.TryGetValue( itemModel.Value, out ItemViewModel otherModel)) { otherModel.Value = null; } _valueToModel[itemModel.Value] = itemModel; } } } } 类,因为它是针对该特定需求的更具可扩展性的解决方案(因此作为该问题的通用解决方案更有用)。 / LI>

在这里,我只需要一个类来实现该接口,为了便于说明,将其保持在一起更简单。

好的,所以使用per-item数据结构,我们还希望父数据结构将这些项封装为集合,并处理对这些项的更广泛操作:

ComboBox

此对象维护项目集合以及PropertyChanged元素的选项集合。这也是处理处理选项互斥的逻辑的地方,因为这是可以访问所有每个项目数据对象的类。

关于最后一点:当然,您可以为每个项目对象提供一种与父数据结构交互的方法,以便能够枚举其他每个项目对象。这将允许每个每个项目对象处理其自己的属性更改,以便父对象不需要订阅每个项目对象的<Window x:Class="TestSO45196940ComboBoxExclusive.MainWindow" 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:l="clr-namespace:TestSO45196940ComboBoxExclusive" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <l:MainViewModel/> </Window.DataContext> <StackPanel> <ListBox ItemsSource="{Binding Items}"> <ListBox.Resources> <DataTemplate DataType="{x:Type l:ItemViewModel}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Label Content="{Binding Name}"/> <ComboBox ItemsSource="{Binding DataContext.Options, RelativeSource={RelativeSource AncestorType=ListBox}}" SelectedItem="{Binding Value}" Grid.Column="1"/> </Grid> </DataTemplate> </ListBox.Resources> </ListBox> </StackPanel> </Window> 事件。但这样做也会增加类之间的耦合,使基本逻辑更难遵循。恕我直言,最好保持这种自上而下的方法,即拥有的对象尽可能少地了解它们的所有者(在这种情况下,根本没有任何东西)。

注意,通过上述内容,可以使用跟踪项目状态并确保互斥选项设置所需的所有逻辑,而不存在实际上特定于视图对象的任何。上述代码可以在任何程序中使用,无论是否有用户界面。它完全脱离了视角本身。

那么,视图如何使用它?像这样:

ItemViewModel

类似于MainViewModel对象对PropertyChanged一无所知,而后者订阅前INotifyPropertyChanged事件并访问项目对象&#39}要完成工作的属性,视图会绑定到两个模型对象的相关属性,而这些对象无需了解这些绑定或视图本身。

该视图根本没有代码隐藏。它只是对用户所看到内容的简单,声明性描述,并且只是向用户呈现基础数据的当前状态。

这样做可以使一切变得非常简单和断开,因此每个对象都有一组非常狭窄的责任,并且对象之间的交互保持最小。这样可以更容易地确保代码是正确的,并在实现功能时减少心理工作量,因为您一次只处理一小部分代码,而不是必须保持一切与彼此。

对于它的价值,在这篇文章中解释上面的代码需要更长的时间方式,而不是编写代码本身。按照标准的WPF惯用法,实际创作代码的速度非常快,特别是如果你已经为{{1}}之类的东西准备了基本的基类。大部分时间的节省来自于不必考虑如何获取所需的数据。通过遵循更好的实践,数据总是在您想要的地方。

答案 1 :(得分:1)

  

我有一个带有DataTemplate的Listbox,其中包含一个Combobox。我需要更改特定ComboBox的selectedItem / Index。我该如何访问它?

访问Items。{/ p>的ListBox集合中的相应数据项

将您的Tuple<string, Dictionary<string, string>>替换为包含SelectedIndex属性的类。确保正确实现INotifyPropertyChanged接口:

class DataItem : INotifyPropertyChanged
{
    public string Item1 { get; set; }
    public Dictionary<string, string> Item2 { get; set; }

    private int _selectedIndex;
    public int SelectedIndex
    {
        get { return _selectedIndex; }
        set { _selectedIndex = value; OnPropertyChanged(); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}
...
foreach (DataColumn dc in _loadedData.Columns)
{
    ListBox.Items.Add(new DataItem() { Item1 = dc.ColumnName, Item2 = cbxOptions });
}

然后,将SelectedIndexComboBox的{​​{1}}属性绑定到DataTemplate属性:

SelectedIndex

通过在<ComboBox Grid.Column="1" ItemsSource="{Binding Item2}" DisplayMemberPath="Key" SelectedIndex="{Binding SelectedIndex}"></ComboBox> 集合中设置相应对象的source属性来更改ComboBox的所选索引:

Items