WPF UserControl Combobox Bound Selection теряется при перезагрузке ItemsSource - PullRequest
0 голосов
/ 25 сентября 2019

У меня есть UserControl, который отображает ComboBox вместе с кнопками добавления и редактирования.

Блок кода 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;
    }


}

Блок обслуживания используется несколько раз приФорма для выбора из общего списка деловых контактов для отдельных бизнес-ролей.Все виды использования пользовательского элемента управления могут добавлять или редактировать запись.Когда это произойдет, данные будут обновлены для обеспечения возможности выбора.

Использование элемента управления ComboBox для обслуживания

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

При первоначальном отображении записивсе работает как положено:

  • список контактов загружен во все элементы управления окна обслуживания
  • текущие выбранные бизнес-роли отображаются как выбранный элемент.

После завершения процесса добавления или редактирования записи и обновления данных выбранный элемент теряется, а в раскрывающемся списке отображается обновленный список контактов.

Модель представления - это DataContext для формы, которая передает элементы управления обслуживанием и реализует INotifyPropertyChanged.Форма довольно большая и имеет довольно повторяющиеся функциональные возможности.

Определение свойства для cboFieldService

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

При исследовании этого в последние несколько дней было много упоминанийto:

  • INotifyPropertyChanged - реализуется
  • IsSyncronizeWithCurrentItem - не может быть установлено в значение true, поскольку использование пользовательского элемента управлениявсе независимые выборки, которые совместно используют один и тот же пул выбора контактов

Отладка за последние несколько дней указывает на то, что значения свойств отдельных выборок контактов остаются неизменными, однако они не отображаются после обновления.Что можно сделать, чтобы каждый выбранный список правильно отображался после обновления списка контактов после добавления или редактирования?

ОБНОВЛЕНИЕ № 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));
    }

UPDATE # 2 ------

В соответствии с рекомендацией источник данных был переопределен, а ссылка на PropertyChangedCallback удалена.Результатом было неизменное поведение.

Поведение возникает только тогда, когда режим привязки 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>)));

Перед изображением

enter image description here

Изображение после обновления контактной информации (Режим = Двухсторонний)

enter image description here

Ответы [ 3 ]

1 голос
/ 26 сентября 2019

ContactList = new ObservableCollection<ActorModel>(actors); Эта строка может быть проблемой, так как вы создаете новый список, вместо этого вы можете добавить или добавить в существующую коллекцию OvservableCollection.

создание нового экземпляра refresh your whole collection (что вы и делаете, потому что теряете выбор).

Ваша цель - добавить или удалить элементы.не обновляет всю коллекцию.

отметьте это .надеюсь, что таким образом вы не потеряете свой выбор, обновив.

0 голосов
/ 27 сентября 2019

Оказывается, есть проблема при использовании TemplateBinding.Следующее обновление было сделано для пользовательского элемента управления и определения DependecyProperty.К первоначальному сообщению отметьте ненавистника, который не мог ни на минуту объяснить, почему .... продолжайте идти.

Благодарим TSoftware-Old на сообщении на форуме Microsoft .

<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 });
0 голосов
/ 26 сентября 2019

Вместо использования object для вашего свойства DataSource, вы могли бы вместо этого использовать ObservableCollection?Затем вместо перезагрузки элементов в методе DataSourceChanged все операции добавления / удаления с ним передаются в XAML с привязкой данных автоматически.

...