[英]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:
根据您发布的一些代码,尽管可以观察到一些信息:
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<...>
对象。 l
variable in the foreach
loop. foreach
循环中修改l
变量。 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: 笔记:
INotifyPropertyChanged
directly. INotifyPropertyChanged
。 In a real-world program, this implementation would typically be in a base class, which each view model class inherits. 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
. 通过访问
ListBox
的Items
集合中的相应数据项。
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
ComboBox
的SelectedIndex
属性绑定到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.