gpt4 book ai didi

c# - ItemsControl中使用的DataTemplate中WPF组合框的默认SelectedItem无法正常工作

转载 作者:太空宇宙 更新时间:2023-11-03 13:57:15 32 4
gpt4 key购买 nike

在严格遵守MVVM模式的同时,如何在ComboBox元素的DataTemplate内的WPF ItemsControl中拥有并且始终具有默认SelectedItem

我的目标是定义一个“表单字段”列表,然后通过模板将其转换为实际的表单字段(即-TextBoxComboBoxDatePicker等)。字段列表是100%动态的,可以随时(由用户)添加和删除字段。

伪实现为:

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

**实际字段的“类型”及其中包含的实现未包含在此问题中,因为它与显示问题无关。

根据上述伪实现,以下是生成表单所需的主要类:

FormViewModel.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" }
}
}
};
}
}

形式
<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
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
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
<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
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
public class Operator {
public string Id { get; set; }
public string Label { get; set; }
}

该表格已正确生成; Fields列表中的所有“表单字段”都创建为 TextBox元素,其名称显示为标签,并且每个都有一个由运算符组成的 ComboBox。但是,默认情况下 ComboBox没有选择任何项目。

解决此问题的第一步是在 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>

我添加的触发器将检查当前 SelectedItem是否为 null,如果是,请将 SelectedIndex设置为0。这可行!当我运行该应用程序时,每个 ComboBox都有一个默认选中的项目!但是,等等,还有更多:
如果随后从 Fields列表中删除了一个项目并在任何时候将其添加回去,则 ComboBox不会再次选择任何项目。基本上,发生的是,当首次创建该字段时,数据触发器会选择运算符列表中的第一项并将其设置为该字段的 SelectedItem。删除该字段然后再添加回该字段时, SelectedItem不再是 null,因此原始DataTrigger不起作用。奇怪的是,即使为SelectedItem属性显然存在绑定(bind),也不会选择当前选择的项目。

总结:在DataTemplate中使用 ComboBox时,该 SelectedItemComboBox未使用其bound属性作为默认值。

我尝试过的

SelectedItem为null时,使用
  • DataTrigger选择列表中的第一项。
    结果:创建字段时正确选择项目;将字段从显示中删除然后重新添加时,该项目会丢失。
  • 与1相同,外加一个DataTrigger,用于当SelectedItem不为null时重新选择列表中的第一项。
    结果:与#1相同。结果+当将该字段从显示中删除并重新添加时,请正确选择列表中的第一项;如果使用已经创建的Fields项目重新创建了整个FormField列表本身,则所选项目再次为空。同样,预选先前选择的运算符也很不错(尽管不是必需的)。
  • 使用带-和不带-DataTriggers的SelectedIndex代替SelectedItem(如#1和#2)。
    结果:在两种情况下都没有成功选择默认项,几乎就像在SelectedIndex之前读取了ItemsSource一样。
  • 使用DataTrigger来检查Items.Count属性;如果大于零,则将SelectedItem设置为列表中的第一个元素。
    结果:未成功选择一个项目。
  • 与4相同,但使用SelectedIndex而不是SelectedItem
    结果:与#1结果
  • 相同
  • IsSynchronizedWithCurrentItemTrueFalse值一起使用。
    结果:未选择任何内容。
  • 对XAML属性进行了重新排序,以将SelectedItem(和SelectedIndex,如果使用的话)放置在ItemsSource之前。我在网上阅读过的每项测试都可以完成这项工作。
    结果:无济于事。
  • 尝试了Operators属性的不同类型的集合。我使用过ListIEnumerableICollectionView,目前正在使用ObservableCollection
    结果:除IEnumerable之外,所有都提供了相同的输出-删除/重新添加字段后,该值丢失。

  • 任何帮助将不胜感激。

    最佳答案

    尽管我重组了应用程序,但以上问题不再存在,但我也想出了解决方案!

    步骤:

  • 从Will的评论中得到一点提示,我更新了Form的代码背后,在PropertyMetadata中添加了 FieldsProperty 回调。
  • #1的回调遍历整个字段列表,并使用 Dispatcher.BeginInvoke() Input -priority level调用Delegate-Action,它将当前字段的SelectedOperator设置为该字段的Operators列表中的第一个运算符。
  • 如果不使用.BeginInvoke()或任何其他较低优先级,则更新将在GUI生成之前尝试访问该字段,并且将失败。
  • DataTriggers中的Operators ComboBox中删除了DataTemplate(现在,它与我的问题中DataTemplates.xaml的第一个代码示例相同)。

  • 新的有效代码(仅更新):

    Form.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];
    }));
    }
    }
    }

    上面的一点警告是 SelectedOperator将始终设置为列表中的第一个。对我来说,这不是问题-但我可以看到需要重新选择“最后选择的运算符(operator)”的情况。

    调试后,将 Field重新添加到 Fields列表中时,它仍然保留以前的 SelectedItem值-然后 ComboBoxSelectedIndex立即设置为 -1。在 FormField.SelectedOperator的 setter 中防止这种情况(并尝试通过 SelectedItem/ SelectedIndex)没有帮助。

    而是在 FormField中创建另一个名为 LastOperator的“占位符”属性,并在将setter传递给 SelectedOperator时将其设置为 null,然后更新 field.Operator =中的 Form.cs行似乎可行:

    FormField.cs
    ...
    public Operator SelectedOperator {
    get { return _selectedOperator; }
    set {
    if (value == null) LastOperator = _selectedOperator;
    _selectedOperator = value; _onPropertyChanged("SelectedOperator");
    }
    }

    public Operator LastOperator { get; set; }

    Form.cs
    ...
    field.SelectedOperator = ((field.LastOperator != null) ? field.LastOperator : field.Operators[0]);
    ...

    关于c# - ItemsControl中使用的DataTemplate中WPF组合框的默认SelectedItem无法正常工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11763724/

    32 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com