WPF layout does not display the correct value after updating the binding source

I am using combobox WPF and I noticed a problem where combobox does not show the correct binding value after updating the binding source while the source itself is in the process of updating from the binding.

I put together a simple example to demonstrate this. In this example, I have a combobox containing 4 elements (strings "A", "B", "C" and "D"). There is a two way binding between the SelectedItem in the combobox and the property in the datacontext called ComboSelectedItem .

The assumed functionality is that if the user selects “A”, “B” or “C” from the drop-down list, then the logic in the datacontext will try to reset the “D” in the combo box. However, instead, what happens is that if the user selects “A” from the drop-down list, the selection remains on “A”.

Here is the sample code below:

MainWindow.xaml:

<Window x:Class="Testing.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>

    <Label Grid.Row="0" Grid.Column="0" Margin="10,10,10,10">Combobox test:</Label>
    <ComboBox Grid.Row="0" Grid.Column="1" Margin="10,10,10,10" x:Name="comboBox"
              ItemsSource="{Binding Path=ComboBoxItems}" Width="80"
              SelectedItem="{Binding Path=ComboSelectedItem, Mode=TwoWay}"/>

        </Grid>
</Window>

and the code for it:

using System;
using System.Windows;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Windows.Threading;

namespace Testing
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private ObservableCollection<String> items;
        public ObservableCollection<String> ComboBoxItems
        {
            get
            {
                if (items == null)
                {
                    items = new ObservableCollection<string>();
                    items.Add("A");
                    items.Add("B");
                    items.Add("C");
                    items.Add("D");
                }

                return items;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private string comboSelectedItem;
        public string ComboSelectedItem
        {
            get { return comboSelectedItem; }
            set
            {
                comboSelectedItem = value;

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("ComboSelectedItem"));

                //if value != D, set to D
                if (ComboSelectedItem != "D")
                {
                     ComboSelectedItem = "D";
                }
            }
        }
    }
}

I found that if I queue ComboSelectedItem so that this happens in the user interface thread, then this will work, for example.

public string ComboSelectedItem
    {
        get { return comboSelectedItem; }
        set
        {
            comboSelectedItem = value;

            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("ComboSelectedItem"));

            //if value != D, set to D
            if (ComboSelectedItem != "D")
            {
                ThreadPool.QueueUserWorkItem(delegate(Object theElement)
                {
                    UIElement elem = (UIElement)theElement;
                    elem.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)delegate()
                        {
                            ComboSelectedItem = "D";
                        });
                }, comboBox);
            }
        }
    }

However, I'm not quite sure why this works, and in any case, I would prefer not to do this for all comboboxes in my application where such a scenario may occur.

/ Combobox - , ? .

+3
1

 private string comboSelectedItem;
    public string ComboSelectedItem
    {
        get { return comboSelectedItem; }
        set
        {
            var origValue = "D";

            if (value == comboSelectedItem)
                return;

            comboSelectedItem = value;
            //if value != D, set to D
            if (ComboSelectedItem != "D")
            {
                // change the value back, but do so after the 
                // UI has finished it current context operation.
                Application.Current.Dispatcher.BeginInvoke(
                        new Action(() =>
                        {
                            comboSelectedItem = origValue;
                            if (PropertyChanged != null)
                                PropertyChanged(this, new PropertyChangedEventArgs("ComboSelectedItem"));
                        }), DispatcherPriority.ContextIdle, null);
                // Exit early. 
                return;
            }
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("ComboSelectedItem"));

        }
    }

+1

Source: https://habr.com/ru/post/1784643/


All Articles