简体   繁体   English

在数据模板中更改组合框的值

[英]Changing Value of a ComboBox in a DataTemplate

I have a Listbox with a DataTemplate which includes a Combobox. 我有一个带有数据模板的列表框,其中包括一个组合框。 I need to change the selectedItem/Index of a particular ComboBox. 我需要更改特定组合框的selectedItem / Index。 How would I access it? 我将如何访问它?

Additional Detail 附加细节

All the combobox have the same options. 所有组合框具有相同的选项。 If a Combobox is set to the same value as another ComboBox then the Combobox that was set first should return to empty (which is the first item in my cbxOptions Dictionary that the ComboBoxes are Bound to). 如果将组合框设置为与另一个组合框相同的值,则首先设置的组合框应返回为空(这是我的cbxOptions词典中绑定组合框的第一项)。

<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# Populate UI C# 填充UI

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

Trying to wipe combobox 试图擦拭组合框

This is where I would expect I could foreach through the Listbox, checking the controls for a match at which point I'd change it to blank. 我希望在这里可以遍历列表框,检查控件是否匹配,然后将其更改为空白。 However my foreach just gives me back stupid Tuple...which is readonly but I don't think that'd update my ComboBox anyways. 但是我的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, "");
    }
}

I'm sure there must be a very simple way of accessing the control. 我确信必须有一种非常简单的访问控件的方法。 Any help would be much appreciated. 任何帮助将非常感激。 Please let me know if additional info is required. 如果需要其他信息,请告诉我。

Without a good Minimal, Complete, and Verifiable code example that clearly and concisely illustrates your question, it's not practical to try to address your current design. 如果没有一个清晰,简洁地说明您的问题的良好的, 最小的,完整的和可验证的代码示例 ,尝试解决当前的设计是不切实际的。 Based on the bit of code you did post, one can make some observations though: 根据您发布的一些代码,尽管可以观察到一些信息:

  • Since Tuple<...> is immutable, you can't modify the Item2 property. 由于Tuple<...>是不可变的,因此无法修改Item2属性。 You have to replace the entire Tuple<...> object with a new one. 您必须用一个新的替换整个Tuple<...>对象。
  • The code you posted shouldn't even compile, because you are trying to modify the l variable in the foreach loop. 您发布的代码甚至不应该编译,因为您正在尝试在foreach循环中修改l变量。
  • Even if you could, it wouldn't change the element in the list itself, just that particular variable. 即使您可以,它也不会更改列表本身中的元素,而不会更改该特定变量。
  • Not that you even want to change the element; 并不是说您甚至不想更改元素;而是 it's the selection of the combo box that should change, not its Item2 options. 应该更改的是组合框的选择,而不是其Item2选项。

The use of a dictionary object for the ComboBox items eludes me. 我对ComboBox项使用字典对象感到困惑。 Perhaps with a complete code example, it would be more clear. 也许有了一个完整的代码示例,它将更加清楚。

All that said… 所有这些……

How would I access it? 我将如何访问它?

This question comes up only because you are misusing WPF to start with. 仅因为您滥用WPF才出现此问题。 You should not be manipulating the UI directly; 您不应该直接操作UI。 instead, your UI state should be represented in view model data structures. 相反,您的UI状态应在视图模型数据结构中表示。 Then the ComboBox selection would be bound to a view model property, and the answer to your question would be simply to look at that property. 然后,将ComboBox选择绑定到视图模型属性,而对您问题的答案将只是看一下该属性。

It's hard to know for sure, given the lack of details, but it appears to me that you are trying to implement a scenario where you have a list of items, where each item has a selectable option, and you want those options to be mutually exclusive. 由于缺乏细节,因此很难确定,但是在我看来,您正在尝试实现一种场景,其中有一个项目列表,每个项目都有一个可选选项,并且您希望这些选项可以相互使用独家。 That is, only one item at a time can have any given option. 也就是说,一次只有一项可以具有任何给定的选项。

Assuming that's the case, I will show an implementation that in my opinion is much better than the approach you are attempting to implement. 假设情况如此,我将展示一种实现,我认为它比您尝试实现的方法要好得多。 That is, it uses the basic idea I've proposed above, where you start with the data models, and then work back to the UI from there. 也就是说,它使用了我上面提出的基本思想,即从数据模型开始,然后从那里返回到UI。 Doing it this way, the data models are very simple and easy to understand, and so is all of the implementation for the behavior you want. 这样,数据模型非常简单易懂,所需行为的所有实现也是如此。

It looks like this… 看起来像这样…

First, start with the basic per-item view model data structure: 首先,从基本的逐项视图模型数据结构开始:

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));
    }
}

Notes: 笔记:

  • The above class implements INotifyPropertyChanged directly. 上面的类直接实现INotifyPropertyChanged In a real-world program, this implementation would typically be in a base class, which each view model class inherits. 在实际程序中,此实现通常在每个视图模型类都继承的基类中。 If you do a significant amount of WPF programming, you'll have this base class as a reusable component you just include in each project, either in a separate project you reference, or as a code snippet. 如果您进行大量WPF编程,则此基类将作为可重用的组件,您只需将其包含在每个项目中即可,无论是在您引用的单独项目中,还是作为代码段。 There are many WPF user frameworks you can use as well, which provide this functionality. 您也可以使用许多WPF用户框架来提供此功能。
  • In this particular example, there's not already a convenient mechanism for event subscribers to know the old value of the property after it's changed, but the logic involved requires that, so that the key for the mapping from value to model object can be removed from the dictionary when it's no longer valid. 在此特定示例中,还没有一种方便的机制供事件订阅者在更改属性后知道属性的旧值,但是所涉及的逻辑要求这样做,以便可以从属性中删除从值到模型对象的映射键。不再有效的字典。 There are a variety of ways to address that need — arguably, the more straightforward is to just do a linear search of the Values collection of the relatively small dictionary. 有多种方法可以满足这种需求-可以说,更直接的方法是对相对较小的词典的Values集合进行线性搜索。 But I decided to extend the PropertyChangedEventArgs class instead, as that's a more scalable solution to that particular need (and so is more useful as a general solution to the problem). 但是我决定扩展PropertyChangedEventArgs类,因为这是一种针对特定需求的更具可伸缩性的解决方案(因此,作为对该问题的常规解决方案更加有用)。

Here, I only need one class to implement that interface, and it's simpler for the sake of illustration to keep everything together there. 在这里,我只需要一个类即可实现该接口,为便于说明起见,将所有内容都保存在那儿比较简单。

Okay, so with the per-item data structure in place, we also want a parent data structure to encapsulate these items as a collection and to handle the broader manipulation of these items: 好的,因此,有了每个项目的数据结构,我们还希望一个父数据结构将这些项目封装为一个集合,并处理这些项目的更广泛的操作:

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;
            }
        }
    }
}

This object maintains the collection of items, as well as the collection of options for the ComboBox elements. 该对象维护项目的集合以及ComboBox元素的选项的集合。 This is also where the logic to handle the mutual-exclusion of options is handled, because this is the class that has access to all of the per-item data objects. 这也是处理选项互斥的逻辑的地方,因为这是可以访问所有每个项目数据对象的类。

On that last point: you could, of course, provide a way for the per-item objects to interact with the parent data structure to be able to enumerate the other per-item objects. 关于最后一点:您当然可以为每个项目对象提供一种与父数据结构进行交互的方式,以便能够枚举其他每个项目对象。 This would allow each per-item object to handle its own property changes, so that the parent object doesn't need to subscribe to each per-item object's PropertyChanged event. 这将允许每个每个项目对象处理其自己的属性更改,从而使父对象不需要订阅每个每个项目对象的PropertyChanged事件。 But doing so would also increase coupling between the classes and make the basic logic harder to follow. 但是这样做也会增加类之间的耦合,并使基本逻辑更难遵循。 IMHO, it is preferable to keep this top-down approach, where owned objects know as little as possible about their owners (and in this case, nothing at all). 恕我直言,最好保留这种自上而下的方法,在这种方法中,所拥有的对象尽可能少地了解其所有者(在这种情况下,一无所知)。

Note that with the above, all of the logic necessary to track the state of the items and ensure mutual exclusion of the options setting is present, without anything that is actually specific to the view objects. 请注意,使用上述方法,将存在跟踪项目状态并确保相互排斥选项设置所需的所有逻辑,而实际上没有特定于视图对象的任何逻辑。 The above code would work in any program, with or without a user interface. 上面的代码可以在任何带有或不带有用户界面的程序中工作。 It's completely decoupled from the view itself. 它与视图本身完全分离。

And so, how does the view use it? 因此,视图如何使用它? Like this: 像这样:

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

Similar to how the ItemViewModel object knows nothing about the MainViewModel , but rather the latter subscribes to the former's PropertyChanged event and accesses the item object's properties to do the work, the view binds to the relevant properties of both model objects, without those objects having any need to know about those bindings, or the view itself. 类似于ItemViewModel对象对MainViewModel一无所知,而是后者订阅前者的PropertyChanged事件并访问item对象的属性来执行工作,该视图绑定到两个模型对象的相关属性,而这些对象没有任何对象需要了解这些绑定或视图本身。

The view has no code-behind at all. 该视图根本没有代码隐藏。 It's just a simple, declarative description of what the user sees, and does nothing more than present to the user the current state of the underlying data. 这只是对用户所见内容的简单声明式描述,仅向用户提供基础数据的当前状态。

Doing it this way keeps everything very simple and disconnected, so that each object has a very narrow set of responsibilities, and the interaction between objects is kept to a minimum. 这样做可以使所有事情变得非常简单和分离,从而使每个对象的职责范围非常狭窄,并且将对象之间的交互保持在最低限度。 This makes it easier to assure that the code is correct, and reduces the mental workload when implementing features, because you're only dealing with a small section of the code at a time, instead of having to keep straight how everything relates to each other. 这使您更容易确保代码正确,并减少了实现功能时的工作量,因为您一次只处理一小部分代码,而不必保持所有内容之间的直接联系。

For what it's worth, it took way longer to explain the code above here in this post, than it did to write the code itself. 对于它的价值,它采取的方式更长的时间才能在这个岗位上面解释这里的代码,的确要比编写代码本身。 Following the standard WPF idioms, the actual authoring the code can go very quickly, especially if you already have the basic base classes in place for things like INotifyPropertyChanged . 按照标准的WPF习惯用法,编写代码的实际过程可以非常快速地进行,尤其是如果您已经拥有INotifyPropertyChanged类的基本基类的话。 Much of that time savings comes from not having to puzzle over how to get at the data you need. 节省的时间大部分来自不必为获取所需数据而困惑的问题。 By following better practices, the data is always already right there where you want it. 通过遵循更好的做法,数据始终始终位于所需位置。

I have a Listbox with a DataTemplate which includes a Combobox. 我有一个带有数据模板的列表框,其中包括一个组合框。 I need to change the selectedItem/Index of a particular ComboBox. 我需要更改特定组合框的selectedItem / Index。 How would I access it? 我将如何访问它?

By accessing the corresponding data item in the Items collection of the ListBox . 通过访问ListBoxItems集合中的相应数据项。

Replace your Tuple<string, Dictionary<string, string>> with a class that also includes a SelectedIndex property. 用还包含SelectedIndex属性的类替换您的Tuple<string, Dictionary<string, string>> Make sure that you implement the INotifyPropertyChanged interface correctly: 确保正确实现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 });
}

Then you bind the SelectedIndex property of the ComboBox in your DataTemplate to your SelectedIndex property: 然后,将DataTemplate ComboBoxSelectedIndex属性绑定到SelectedIndex属性:

<ComboBox Grid.Column="1" ItemsSource="{Binding Item2}" DisplayMemberPath="Key" 
          SelectedIndex="{Binding SelectedIndex}"></ComboBox>

And change the selected index of a ComboBox by setting the source property of the corresponding object in the Items collection: 并通过在Items集合中设置相应对象的source属性来更改ComboBox的选定索引:

(ListBox.Items[2] as DataItem).SelectedIndex = 1;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM