-1

I have a UserControl that displays a ComboBox along with add and edit buttons.

XAML Code Block

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

Content Control Code

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;
    }


}

The maintenance box is used multiple times on a form to choose from a shared list of business contacts for separate business roles. All uses of the user control can add or edit an entry. When this occurs, the data will be refreshed in order to allow a selection.

Usage of the Maintenance ComboBox Control

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

Upon the initial display of a record, everything functions as expected:

  • contact list is loaded in all maintenance box controls
  • current business role selections are displayed as the selected item.

After the add or edit record process is completed and the data refreshed, the current item selected lost while the the dropdown list displays the updated contacts list.

A view model is the DataContext for the form that feeds the maintenance controls and implements INotifyPropertyChanged. The form is rather large and is pretty repetitive functionality.

Property definition for the cboFieldService

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

While researching this the past few days, there have been numerous mentions to:

  • INotifyPropertyChanged - is being implemented
  • IsSyncronizeWithCurrentItem - can not be set to true since the uses of the user control are all independent selections that happen to share the same contacts selection pool

Debugging the past few days indicates that the property values of the individual contact selections are still intact, however they aren't displayed after refresh. What can be done so that the each list selection properly displays after the contacts list has been refreshed following an add or edit?

UPDATE # 1------

When the record is loaded, the add and edit commands are defined in the view model.

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

Per recommendation, the DataSource was redefined and the reference to the PropertyChangedCallback was removed. The result was unchanged behavior.

The behavior occurs only when the ContactList binding mode=TwoWay. When it is set to OneWay, the ComboBox selections do not show any updated information and the record selections are visually shown.

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>)));

Before Image

enter image description here

Image after contact information is updated (Mode=TwoWay)

enter image description here

Galactic
  • 400
  • 4
  • 14
  • Where are you actually doing the add ?? Post the code of that you doing it(Probably ViewModel). – Abin Sep 25 '19 at 21:27
  • Thanks for pointing out the oversight. An update has been made that provides the view model code used for the add and edit processing. – Galactic Sep 26 '19 at 11:52

3 Answers3

1

ContactList = new ObservableCollection<ActorModel>(actors); This line may be the issue as you are creating a new list here instead you can Add or Addrange to the existing OvservableCollection.

creating a new instance will refresh your whole collection(which is what you are doing there for you lose the selection).

Your goal is to add or remove items. not refreshing entire collection.

check this out. hope this way you wont lose your selection by refreshing.

Abin
  • 2,868
  • 1
  • 23
  • 59
0

Instead of using an object for your DataSource property, could you instead use an ObservableCollection? Then instead of reloading the items in the DataSourceChanged method, any add/remove operations on it are passed to the XAML with databinding automatically.

pete the pagan-gerbil
  • 3,136
  • 2
  • 28
  • 49
  • I defined the DataSource as ObservableCollection, removed the reference to DataSoureChanged, and tried one and two binding with and without the OnPropertyChanged activation. The behavior is unchanged: on two-way binding, the data updates, but the pre-existing selection becomes unlinked to the combobox. – Galactic Sep 26 '19 at 12:48
  • After locating the solution as listed, the DataSource is also defined as an object as originally planned. – Galactic Sep 27 '19 at 19:01
0

It turns out that there is an issue when using TemplateBinding. The following update was made to the user control and the DependecyProperty definition. To the original post mark down hater that could not take a moment to explain why....keep on walking.

Credit to TSoftware-Old on a Microsoft forum post.

<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 });
Galactic
  • 400
  • 4
  • 14