ItemsControl 中使用的 DataTemplate 中 WPF ComboBox 的默认 SelectedItem 不起作用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11763724/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Default SelectedItem for a WPF ComboBox inside a DataTemplate used in an ItemsControl not working
提问by newfurniturey
How can I get a WPF ComboBoxthat is within a DataTemplatewithin an ItemsControlelement to have, and always have, a default SelectedItem, all while sticking strictly to the MVVM pattern?
我怎样才能得到一个WPFComboBox是内部的DataTemplate一个内部ItemsControl因素有,并一直是,默认的SelectedItem,同时还能严格执行MVVM模式?
My goal is to define a list of "form fields" that then are translated, via templates, into actual form fields (i.e. - TextBox, ComboBox, DatePicker, etc.). The list of fields is 100% dynamic and fields can be added and removed (by the user) at any time.
我的目标是定义,然后被转换“表单字段”列表,通过模板,为实际的表单字段(即- ,,等)。字段列表是 100% 动态的,并且可以随时添加和删除字段(由用户)。TextBoxComboBoxDatePicker
The pseudo-implementation is:
伪实现是:
MainWindow
-> Sets FormViewModel as DataContext
FormViewModel (View Model)
-> Populated the `Fields` Property
Form (View)
-> Has an `ItemsControl` element with the `ItemsSource` bound to FormViewModel's `Fields` Property
-> `ItemsControl` element uses an `ItemTemplateSelector` to select correct template based on current field's type**
FormField
-> Class that has a `DisplayName`, `Value`, and list of `Operators` (=, <, >, >=, <=, etc.)
Operator
-> Class that has an `Id` and `Label` (i.e.: Id = "<=", Label = "Less Than or Equal To")
DataTemplate
-> A `DataTemplate` element for each field type* that creates a form field with a label, and a `ComboBox` with the field's available Operators
*** The `Operators` ComboBox is where the issue occurs ***
** The actual field's "type" and the implementation contained therein is not included in this question as it's not relevant to the display issue.
** 实际字段的“类型”和其中包含的实现不包含在此问题中,因为它与显示问题无关。
Here are the primary classes required to generate the form, based on the pseudo-implementation above:
以下是基于上述伪实现生成表单所需的主要类:
FormViewModel.cs
窗体视图模型.cs
public class FormViewModel : INotifyPropertyChanged {
protected ObservableCollection<FormField> _fields;
public ObservableCollection<FormField> Fields {
get { return _fields; }
set { _fields = value; _onPropertyChanged("Fields"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void _onPropertyChanged(string propertyName) {
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public FormViewModel() {
// create a sample field that has a list of operators
Fields = new ObservableCollection<FormField>() {
new FormField() {
DisplayName = "Field1",
Value = "Default Value",
Operators = new ObservableCollection<Operator>() {
new Operator() { Id = "=", Label = "Equals" },
new Operator() { Id = "<", Label = "Less Than" },
new Operator() { Id = ">", Label = "Greater Than" }
}
}
};
}
}
Form.xaml
表格.xaml
<UserControl.Resources>
<ResourceDictionary Source="DataTemplates.xaml" />
</UserControl.Resources>
<ItemsControl
ItemsSource="{Binding Fields}"
ItemTemplateSelector="{StaticResource fieldTemplateSelector}">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<ItemsPresenter />
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
Form.xaml.cs
表格.xaml.cs
public partial class Form : UserControl {
public static readonly DependencyProperty FieldsProperty = DependencyProperty.RegisterAttached("Fields", typeof(ObservableCollection<FormField>), typeof(Form));
public ObservableCollection<FormField> Fields {
get { return ((ObservableCollection<FormField>)GetValue(FieldsProperty)); }
set { SetValue(FieldsProperty, ((ObservableCollection<FormField>)value)); }
}
public Form() {
InitializeComponent();
}
}
FieldTemplateSelector.cs
FieldTemplateSelector.cs
public class FieldTemplateSelector : DataTemplateSelector {
public DataTemplate DefaultTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
FrameworkElement element = (container as FrameworkElement);
if ((element != null) && (item != null) && (item is FormField)) {
return (element.FindResource("defaultFieldTemplate") as DataTemplate);
}
return DefaultTemplate;
}
}
DataTemplates.xaml
数据模板.xaml
<local:FieldTemplateSelector x:Key="fieldTemplateSelector" />
<DataTemplate x:Key="defaultFieldTemplate">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=DisplayName}" />
<TextBox Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox
ItemsSource="{Binding Path=Operators}"
DisplayMemberPath="Label" SelectedValuePath="Id"
SelectedItem="{Binding SelectedOperator, Mode=TwoWay}"
HorizontalAlignment="Right"
/>
</StackPanel>
</DataTemplate>
FormField.cs
表单域.cs
public class FormField : INotifyPropertyChanged {
public string DisplayName { get; set; }
public string Value { get; set; }
protected ObservableCollection<Operator> _operators;
public ObservableCollection<Operator> Operators {
get { return _operators; }
set {
_operators = value;
_onPropertyChanged("Operators");
}
}
protected Operator _selectedOperator;
public Operator SelectedOperator {
get { return _selectedOperator; }
set { _selectedOperator = value; _onPropertyChanged("SelectedOperator"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void _onPropertyChanged(string propertyName) {
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Operator.cs
操作员.cs
public class Operator {
public string Id { get; set; }
public string Label { get; set; }
}
The form is properly generated; All "form fields" in the Fieldslist are created as TextBoxelements with their name's displayed as labels, and they each have a ComboBoxfull of operators. However, the ComboBoxdoesn't have an item selected by default.
表单正确生成;Fields列表中的所有“表单域”都是作为TextBox元素创建的,它们的名称显示为标签,并且它们每个都有一个ComboBox完整的运算符。但是,ComboBox默认情况下没有选择项目。
My initial step to fix the issue was to set SelectedIndex=0on the ComboBox; this didn't work. After trial and error, I opted to use a DataTriggersuch as the following:
我解决这个问题的第一步是SelectedIndex=0在ComboBox; 这没有用。经过反复试验,我选择使用DataTrigger如下:
<ComboBox
ItemsSource="{Binding Path=Operators}"
DisplayMemberPath="Label" SelectedValuePath="Id"
SelectedItem="{Binding SelectedOperator, Mode=TwoWay}"
HorizontalAlignment="Right">
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<!-- select the first item by default (if no other is selected) -->
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem}" Value="{x:Null}">
<Setter Property="SelectedIndex" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
The trigger I added will check if the current SelectedItemis nulland, if so, set the SelectedIndexto 0. This works! When I run the application, each ComboBoxhas an item selected by default! But wait, there's more:
If an item is then removed from the Fieldslist and at any time added back, the ComboBoxhas no item selected again. Basicaly, what's happening is, when the field is created for the first time, the data-trigger selects the first item in the operators list and sets that as the field's SelectedItem. When the field is removed and then added back, SelectedItemis no longer nullso the original DataTrigger doesn't work. Oddly enough, even though there is clearly a binding for the SelectedItem property, the currently-selected item is not being selected.
我添加的触发器将检查电流SelectedItem是否是null,如果是,则将其设置SelectedIndex为 0。这有效!当我运行应用程序时,每个应用程序ComboBox默认选择一个项目!但是等等,还有更多:如果某个项目随后从Fields列表中删除并随时添加回来,则该ComboBox项目不会再次被选中。基本上,发生的事情是,当第一次创建字段时,数据触发器选择运算符列表中的第一项并将其设置为字段的SelectedItem. 当该字段被删除然后重新添加时,SelectedItem不再null是原来的 DataTrigger 不起作用。奇怪的是,即使 SelectedItem 属性有明显的绑定,但当前选择的项目并没有被选中。
Summarized: When a ComboBoxis used within a DataTemplate, the SelectedItemfor the ComboBoxis not using its bound property as a default value.
总结:当 aComboBox在 DataTemplate 中使用时,SelectedItemforComboBox不使用其绑定属性作为默认值。
What I've tried:
我试过的:
DataTrigger when
SelectedItemis null to select the first item in the list.
Result: Correctly selects the item when the field is created; Loses the item when the field is removed from the display and then added back.Same as 1, plus a DataTrigger for when
SelectedItemis not null to re-select the first item in the list.
Result: Same as #1 Result + Correctly selects the first item in the list when the field is removed from the display and then added back; If the entireFieldslist itself is recreated using already-createdFormFielditems, the selected item is empty again. Also, it would be nice to pre-select the previously selected operator (not a requirement though).Used
SelectedIndexinstead ofSelectedItem, with - and without - DataTriggers (as in #1 and #2).
Result: Did not successfully select default items in either case, almost as if theSelectedIndexwas being read before theItemsSource.Used a DataTrigger to check the
Items.Countproperty; if it was greater-than-zero, set theSelectedItemto the first element in the list.
Result: Did not successfully select an item.Same as 4, but using
SelectedIndexinstead ofSelectedItem.
Result: Same as #1 ResultUsed
IsSynchronizedWithCurrentItemwith bothTrueandFalsevalues.
Result: Nothing selected.Re-ordered the XAML properties to place
SelectedItem(andSelectedIndex, when used) to be beforeItemsSource. This was done for every test as I've read online that it helps.
Result: Doesn't help.Tried different types of collections for the
Operatorsproperty. I've usedList,IEnumerable,ICollectionView, and am currently usingObservableCollection.
Result: All provided the same output, exceptIEnumerable- it lost the value after the field was removed/re-added.
DataTrigger 当
SelectedItem为空时选择列表中的第一项。
结果:创建字段时正确选择了项目;当该字段从显示中移除然后重新添加时,将丢失该项目。同 1,加上一个 DataTrigger for when
SelectedItemis not null 重新选择列表中的第一项。
结果:与#1 结果相同 + 当该字段从显示中移除然后重新添加时,正确选择列表中的第一项;如果Fields使用已创建的FormField项目重新创建整个列表本身,则所选项目再次为空。此外,预先选择先前选择的运算符会很好(虽然不是必需的)。用于
SelectedIndex代替SelectedItem,带 - 和不带 - DataTriggers(如#1 和#2)。
结果:在任何一种情况下都没有成功选择默认项目,几乎就像SelectedIndex在ItemsSource.使用 DataTrigger 检查
Items.Count属性;如果它大于零,则将 设置SelectedItem为列表中的第一个元素。
结果:未成功选择项目。与 4 相同,但使用
SelectedIndex代替SelectedItem。
结果:与#1 结果相同使用
IsSynchronizedWithCurrentItem与两个True和False值。
结果:未选择任何内容。将 XAML 属性重新排序以放置
SelectedItem(并且SelectedIndex在使用时)位于之前ItemsSource。这是为每个测试完成的,因为我在网上阅读它有帮助。
结果:没有帮助。为该
Operators物业尝试了不同类型的集合。我使用过List,IEnumerable,ICollectionView, 并且目前正在使用ObservableCollection.
结果:所有都提供了相同的输出,除了IEnumerable- 在删除/重新添加字段后它丢失了值。
Any help would be greatly appreciated.
任何帮助将不胜感激。
采纳答案by newfurniturey
Though I restructured my application and the above issue is no longer present, I have also figured out a solution to solving it!
虽然我重组了我的应用程序并且不再存在上述问题,但我也找到了解决它的方法!
The steps:
步骤:
Taking a hint from a comment by Will, I updated the
Form's codebehind to add aPropertyMetadatacallback to theFieldsProperty.The callback from #1 iterates through the entire list of fields and uses
Dispatcher.BeginInvoke()to invoke a Delegate-Action on anInput-priority levelthat will set the current field'sSelectedOperatorto the first operator in the field'sOperatorslist.- Without using
.BeginInvoke()or any other lower priority, the update would attempt to access the field before it was GUI-generated and would fail.
- Without using
Removed the
DataTriggersfrom theOperatorsComboBoxin theDataTemplate(now, it is the same as the first code-example forDataTemplates.xamlin my question).
从 Will 的评论中得到提示,我更新了
Form的代码隐藏以PropertyMetadata向FieldsProperty.来自 #1 的回调遍历整个字段列表,并用于
Dispatcher.BeginInvoke()在Input-priority 级别调用 Delegate-Action,该级别会将当前字段设置SelectedOperator为字段Operators列表中的第一个运算符。- 在不使用
.BeginInvoke()或任何其他较低优先级的情况下,更新将尝试在 GUI 生成之前访问该字段,并且会失败。
- 在不使用
删除了
DataTriggers从OperatorsComboBox在DataTemplate(现在,它是一样的第一个代码,例如,对于DataTemplates.xaml我的问题)。
New, working code (updates only):
新的工作代码(仅限更新):
Form.cs
表单.cs
...
public static readonly DependencyProperty FieldsProperty =
DependencyProperty.RegisterAttached("Fields", typeof(ObservableCollection<FormField>), typeof(Form), new PropertyMetadata(_fieldsListUpdated));
...
// PropertyMetaData-callback for when the FieldsProperty is updated
protected static void _fieldsListUpdated(DependencyObject sender, DependencyPropertyChangedEventArgs args) {
foreach (FormField field in ((Form)sender).Fields) {
// check to see if the current field has valid operators
if ((field.Operators != null) && (field.Operators.Count > 0)) {
Dispatcher.CurrentDispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input, (Action)(() => {
// set the current field's SelectedOperator to the first in the list
field.SelectedOperator = field.Operators[0];
}));
}
}
}
The slight caveat to the above is that the SelectedOperatorwill always be set to the first in the list. For me, this isn't an issue - but I could see a case where the "last-selected-operator" would want to be re-selected.
对上述稍加注意的是,SelectedOperator将始终设置为列表中的第一个。对我来说,这不是问题 - 但我可以看到“最后选择的操作员”希望被重新选择的情况。
After debugging, when the Fieldis added back to the list of Fields, it still retains the previous SelectedItemvalue - and then the ComboBox's SelectedIndexis immediately set to -1. Preventing this in the setter for FormField.SelectedOperator(and by trying SelectedItem/SelectedIndex) doesn't help.
调试,当后Field添加回列表中Fields,它仍然保留以前的SelectedItem值-然后ComboBox的SelectedIndex立即设置为-1。在 setter for FormField.SelectedOperator(并尝试SelectedItem/ SelectedIndex)中防止这种情况无济于事。
Instead, creating a second "placeholder" Property in FormFieldnamed LastOperatorand setting it to the SelectedOperatorwhen the setter is passed null, and then updating the field.Operator =line in Form.csseems to work:
相反,在FormFieldnamed 中创建第二个“占位符”属性LastOperator并将其设置为SelectedOperator当 setter 被传递时null,然后更新该field.Operator =行Form.cs似乎有效:
FormField.cs
表单域.cs
...
public Operator SelectedOperator {
get { return _selectedOperator; }
set {
if (value == null) LastOperator = _selectedOperator;
_selectedOperator = value; _onPropertyChanged("SelectedOperator");
}
}
public Operator LastOperator { get; set; }
Form.cs
表单.cs
...
field.SelectedOperator = ((field.LastOperator != null) ? field.LastOperator : field.Operators[0]);
...
回答by Tono
Using ComboBoxes with databound SelectedItem, inside DataTemplate is tricky.. I resolved this by: instead of using SelectedItem, (TwoWay) bind only SelectedValue(to your custom type property - SelectedOperator) and set DisplayMemberPath(but NOT SelectedValuePath - to have the whole custom type instance as a value)
使用数据绑定与组合框的SelectedItem,里面的DataTemplate是棘手。我解决了这个由:而不是使用的SelectedItem,(双向)绑定唯一的SelectedValue(您的自定义类型属性- SelectedOperator)和集的DisplayMemberPath(但不是SelectedValuePath -拥有全定制键入实例作为值)
回答by Mduduzi Shelembe
Try the following:
请尝试以下操作:
FormField.cs
表单域.cs
protected ObservableCollection<Operator> _operators;
public ObservableCollection<Operator> Operators {
get { return _operators; }
set {
_operators = value;
_onPropertyChanged("Operators");
}
}
private QuestionOption _selectedItem;
public QuestionOption SelectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem != value)
{
if (SelectedIndex == -1)
SelectedIndex = Operators.IndexOf(value);
_selectedItem = value;
_onPropertyChanged("SelectedItem");
}
}
}
private int _selectedIndex = -1;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
if (_selectedIndex != value)
{
_selectedIndex = value;
_onPropertyChanged("SelectedIndex");
}
}
}
DataTemplate.xaml
数据模板.xaml
<ComboBox Width="Auto"
ItemsSource="{Binding Operators}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
DisplayMemberPath="Label" SelectedValuePath="Id">
As for ensuring that changes to the Fields fire the PropertyChanged event try the following to force the event to fire:
至于确保对字段的更改触发 PropertyChanged 事件,请尝试以下操作以强制触发事件:
// Set the changes to the modifiedFormField placeholder
ObservableCollection<FormField> modifiedFormField;
this.Fields = new ObservableCollection<FormField>(modifiedFormField);
I experienced a similar issue whilst working on an MVVM Silverlight 5 Application and did something similar to this to get the binding working. The concepts should be interchangeable with WPF. Hope this helps.
我在处理 MVVM Silverlight 5 应用程序时遇到了类似的问题,并做了与此类似的操作以使绑定正常工作。这些概念应该可以与 WPF 互换。希望这可以帮助。

