重新加载 ItemsSource 时 WPF UserControl 组合框绑定选择丢失

WPF UserControl Combobox Bound Selection Lost when ItemsSource reloaded

我有一个显示 ComboBox 以及添加和编辑按钮的 UserControl。

XAML 代码块

<Style TargetType="local:MaintenanceComboBox">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:MaintenanceComboBox">
                <Border x:Name="_boxBorder">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="Auto"/>
                        </Grid.ColumnDefinitions>
                        <ComboBox x:Name="cboMaintenance" Grid.Column="0" 
                            Background="{StaticResource BH_TitleDefaultBackground}" Foreground="Black"
                            VerticalContentAlignment="Center"
                            ItemsSource="{TemplateBinding DataSource}"
                            DisplayMemberPath="{TemplateBinding DisplayItem}"                                                                
                            SelectedValue="{TemplateBinding ModelPropertyField}"
                            SelectedValuePath="{TemplateBinding DisplayRecordKey}"/>                            
                        <Button Grid.Column="1" Name="btnAddRecord" ToolTip="Add Record" Style="{StaticResource IconButton}" Margin="1.5,0" Background="Transparent" 
                                                Command="{TemplateBinding AddRecordCommand}">
                            <Image Source="{Binding Converter={StaticResource ImgConverter}, ConverterParameter={x:Static utilui:eImages_16x16.Add} }"/>
                        </Button>
                        <Button Grid.Column="2" Name="btnEditRecord" ToolTip="Edit Record" Margin="1.5,0" Background="Transparent" 
                                                Command="{TemplateBinding EditRecordCommand}">
                            <Image Source="{Binding Converter={StaticResource ImgConverter}, ConverterParameter={x:Static utilui:eImages_16x16.PageEdit} }"/>
                            <Button.Style>
                                <Style TargetType="{x:Type Button}" BasedOn="{StaticResource IconButton}" >
                                    <Setter Property="Visibility" Value="Visible"/>
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding ElementName=cboMaintenance, Path=SelectedIndex}" Value="0">
                                            <Setter Property="Visibility" Value="Collapsed"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Button.Style>
                        </Button>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

内容控制代码

public class MaintenanceComboBox : ContentControl
{
    private static object _previousSelection = null;

    public ICommand AddRecordCommand
    {
        get { return (ICommand)GetValue(AddRecordCommandProperty); }
        set { SetValue(AddRecordCommandProperty, value); }
    }
    public static readonly DependencyProperty AddRecordCommandProperty =
        DependencyProperty.Register("AddRecordCommand", typeof(ICommand), typeof(MaintenanceComboBox),
            new PropertyMetadata(default(ICommand), new PropertyChangedCallback(AddRecordCommandChanged)));

    private static void AddRecordCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MaintenanceComboBox)d).AddRecordCommand = (ICommand)e.NewValue;
    }

    public ICommand EditRecordCommand
    {
        get { return (ICommand)GetValue(EditRecordCommandProperty); }
        set { SetValue(EditRecordCommandProperty, value); }
    }
    public static readonly DependencyProperty EditRecordCommandProperty =
        DependencyProperty.Register("EditRecordCommand", typeof(ICommand), typeof(MaintenanceComboBox),
            new PropertyMetadata(default(ICommand), new PropertyChangedCallback(EditRecordCommandChanged)));

    private static void EditRecordCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MaintenanceComboBox)d).EditRecordCommand = (ICommand)e.NewValue;
    }
    //------------------------------------------------------------------------------------------
    public Color BackgroundColor
    {
        get { return (Color)GetValue(BackgroundColorProperty); }
        set { SetValue(BackgroundColorProperty, value); }
    }
    public static readonly DependencyProperty BackgroundColorProperty =
        DependencyProperty.Register("BackgroundColor", typeof(Color), typeof(MaintenanceComboBox),
            new PropertyMetadata(default(Color), new PropertyChangedCallback(BackgroundColorChanged)));

    private static void BackgroundColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MaintenanceComboBox)d).BackgroundColor = (Color)e.NewValue;
    }
    //------------------------------------------------------------------------------------------
    public object DataSource
    {
        get { return GetValue(DataSourceProperty); }
        set { SetValue(DataSourceProperty, value); }
    }
    public static readonly DependencyProperty DataSourceProperty =
        DependencyProperty.Register("DataSource", typeof(object), typeof(MaintenanceComboBox),
            new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, DataSourceChanged));

    private static void DataSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MaintenanceComboBox)d).DataSource = e.NewValue;
    }
    //------------------------------------------------------------------------------------------
    public string DisplayItem
    {
        get { return GetValue(DisplayItemProperty).ToString(); }
        set { SetValue(DisplayItemProperty, value); }
    }
    public static readonly DependencyProperty DisplayItemProperty =
        DependencyProperty.Register("DisplayItem", typeof(string), typeof(MaintenanceComboBox),
            new PropertyMetadata(default(string), new PropertyChangedCallback(DisplayItemChanged)));

    private static void DisplayItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MaintenanceComboBox)d).DisplayItem = e.NewValue.ToString();
    }
    //------------------------------------------------------------------------------------------
    public string DisplayRecordKey
    {
        get { return GetValue(DisplayRecordKeyProperty).ToString(); }
        set { SetValue(DisplayRecordKeyProperty, value); }
    }
    public static readonly DependencyProperty DisplayRecordKeyProperty =
        DependencyProperty.Register("DisplayRecordKey", typeof(string), typeof(MaintenanceComboBox),
            new PropertyMetadata(default(string), new PropertyChangedCallback(DisplayRecordKeyChanged)));

    private static void DisplayRecordKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MaintenanceComboBox)d).DisplayRecordKey = e.NewValue.ToString();
    }
    //------------------------------------------------------------------------------------------
    public object ModelPropertyField
    {
        get { return GetValue(ModelPropertyFieldProperty); }
        set { SetValue(ModelPropertyFieldProperty, value); }
    }
    public static readonly DependencyProperty ModelPropertyFieldProperty =
        DependencyProperty.Register("ModelPropertyField", typeof(object), typeof(MaintenanceComboBox),
            new PropertyMetadata(default(object), new PropertyChangedCallback(ModelPropertyFieldChanged)));

    private static void ModelPropertyFieldChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MaintenanceComboBox)d).ModelPropertyField = e.NewValue;
    }
    //------------------------------------------------------------------------------------------

    public int SelectedIndex
    {
        get { return Convert.ToInt32(GetValue(SelectedIndexProperty)); }
        set { SetValue(SelectedIndexProperty, value); }
    }
    public static readonly DependencyProperty SelectedIndexProperty =
        DependencyProperty.Register("SelectedIndex", typeof(int), typeof(MaintenanceComboBox),
            new PropertyMetadata(default(int), new PropertyChangedCallback(SelectedIndexChanged)));

    private static void SelectedIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MaintenanceComboBox)d).SelectedIndex = Convert.ToInt32(e.NewValue);
    }
    //------------------------------------------------------------------------------------------
    public object ModelSelectionItem
    {
        get { return GetValue(ModelSelectionItemProperty); }
        set { SetValue(ModelSelectionItemProperty, value); }
    }
    public static readonly DependencyProperty ModelSelectionItemProperty =
        DependencyProperty.Register("ModelSelectionItem", typeof(object), typeof(MaintenanceComboBox),
            new PropertyMetadata(default(object), new PropertyChangedCallback(ModelSelectionItemChanged)));

    private static void ModelSelectionItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MaintenanceComboBox)d).ModelSelectionItem = e.NewValue;
    }


}

在一个表单上多次使用维护框,以从共享的业务联系人列表中为不同的业务角色进行选择。所有使用用户控件的用户都可以添加或编辑一个条目。发生这种情况时,将刷新数据以允许选择。

维护组合框控件的使用

<utilctl:MaintenanceComboBox x:Name="cboFieldService" DisplayItem="FullName" DisplayRecordKey="ActorID" 
 ModelPropertyField="{Binding FieldServiceRep, Mode=TwoWay}"     
 DataSource="{Binding ContactList, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />

在记录的初始显示时,一切都按预期运行:

添加或编辑记录过程完成并刷新数据后,当前选择的项目丢失,而下拉列表显示更新的联系人列表。

视图模型是提供维护控件并实现 INotify属性Changed 的​​表单的 DataContext。表单相当大,并且是非常重复的功能。

属性 cboFieldService 的定义

private string _fieldServiceRep = string.Empty;
public string FieldServiceRep { get { return _fieldServiceRep; } set { _fieldServiceRep = value; OnPropertyChanged(); } }

在过去几天对此进行研究时,有很多人提到:

这几天的调试发现,个别联系人选择的属性值还是完整的,只是刷新后不显示了。添加或编辑后刷新联系人列表后,如何才能正确显示每个列表选择?

更新#1------

加载记录时,添加和编辑命令在视图模型中定义。

public void LoadRecord(IncidentModel record)
{
    DataContext = _activeIncident;
    cboFieldService.AddRecordCommand = new DelegateCommand(_activeIncident.AddFieldServiceRep);
    cboFieldService.EditRecordCommand = new DelegateCommand(_activeIncident.EditFieldServiceRep);
}
    public void AddFieldServiceRep() { AddContact(nameof(FieldServiceRep)); }
    public void AddContact(string propertyName)
    {
        ProcessContactMaintenance(new ContactMaintenanceWindow(MaintenanceType.Add), propertyName);
    }

    public void EditFieldServiceRep() { EditContact(ContactTypeCode.Rep, nameof(FieldServiceRep)); }
    public void EditContact(ContactTypeCode type, string propertyName)
    {
        ProcessContactMaintenance(new ContactMaintenanceWindow(MaintenanceType.Edit, GetIncidentContact(type)?.ActorInfo), propertyName);
    }
    private void ProcessContactMaintenance(ContactMaintenanceWindow win, string propertyName)
    {
        win.ShowDialog();
        if (win.DialogResult.Equals(true))
        {
            RefreshContactList(); OnPropertyChanged(nameof(ContactList)); UpdateContactProperties();
        }
    }

    public void RefreshContactList()
    {
        ObservableCollection<ActorModel> contacts = new ObservableCollection<ActorModel>(FieldServiceManagementDataService.GetActors());
        List<ActorModel> actors = contacts?.OrderBy(c => c.FullName).ToList();
        ActorModel none = new ActorModel();
        none.ActorID = "-1";
        none.FirstName = "-- Select --";
        actors?.Insert(0, none);
        ContactList = new ObservableCollection<ActorModel>(actors);
    }

    public void UpdateContactProperties()
    {
        OnPropertyChanged(nameof(FieldServiceRep));
    }

更新#2 ------

根据建议,重新定义了数据源并删除了对 属性ChangedCallback 的引用。结果是行为没有改变。

该行为仅在 ContactList 绑定模式=TwoWay 时发生。设置为 OneWay 时,ComboBox 选择不显示任何更新信息,记录选择以可视方式显示。

public ObservableCollection<dynamic> DataSource
{
    get { return (ObservableCollection<dynamic>)GetValue(DataSourceProperty); }
    set { SetValue(DataSourceProperty, value); }
}
public static readonly DependencyProperty DataSourceProperty =
    DependencyProperty.Register("DataSource", typeof(ObservableCollection<dynamic>), typeof(MaintenanceComboBox),
        new PropertyMetadata(default(ObservableCollection<dynamic>)));

前像

联系人信息更新后的图片(Mode=TwoWay)

您可以不使用 object 作为数据源 属性,而是使用 ObservableCollection 吗?然后,不是重新加载 DataSourceChanged 方法中的项目,而是将其上的任何 add/remove 操作自动传递给具有数据绑定的 XAML。

ContactList = new ObservableCollection<ActorModel>(actors); 这行可能是问题所在,因为您要在此处创建新列表,而您可以向现有 OvservableCollection 添加或添加范围。

创建一个新实例将refresh your whole collection(这就是你在那里所做的,因为你失去了选择)。

您的目标是添加或删除项目。没有刷新整个集合。

检查 this。希望这样你不会因为刷新而丢失你的选择。

原来是使用TemplateBinding的时候出了问题。对用户控件和 DependecyProperty 定义进行了以下更新。给原来的 post 记下无法花时间解释原因的讨厌者....继续走。

归功于 Microsoft forum post.

上的 TSoftware-Old
<ComboBox x:Name="cboMaintenance" Grid.Column="0" Grid.Row="1"
    Background="{StaticResource BH_TitleDefaultBackground}" Foreground="Black" 
    VerticalContentAlignment="Center"
    ItemsSource="{Binding DataSource, RelativeSource={RelativeSource TemplatedParent}}"
    DisplayMemberPath="{Binding DisplayItem, RelativeSource={RelativeSource TemplatedParent}}"                                
    SelectedValue="{Binding ModelPropertyField, RelativeSource={RelativeSource TemplatedParent}}"
    SelectedValuePath="{Binding DisplayRecordKey, RelativeSource={RelativeSource TemplatedParent}}" 
    SelectedItem="{Binding ModelSelectionItem, RelativeSource={RelativeSource TemplatedParent}}"/>

-------------------------

   public static readonly DependencyProperty ModelSelectionItemProperty =
        DependencyProperty.Register("ModelSelectionItem", typeof(object), typeof(MaintenanceComboBox),
        new FrameworkPropertyMetadata() { BindsTwoWayByDefault = true, DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });