Joe
Joe

Reputation: 81

WPF combobox not displaying correct value after updating binding source

I'm using a WPF combobox and I've noticed a problem where the combobox is not showing the correct bound value after updating the binding source while the source itself is in the process of being updated from the binding.

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

The intended functionality is that if the user selects "A","B" or "C" from the combobox, then the logic in the datacontext will attempt to reset the selection on the combobox to "D". However, instead what happens is that if the user selects "A" from the combobox, the selection remains on "A".

Here's 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-behind 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've found that if I queue up the ComboSelectedItem set so that it happens on the UI thread, then this will work e.g.

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 completely sure why this works and anyway I would prefer not to have to do this for all the comboboxes in my application where a scenario like this may occur.

Instead, is there a setting/property on the Combobox or some other method that would solve this issue for me? Thanks.

Upvotes: 3

Views: 1577

Answers (1)

biju
biju

Reputation: 18000

This could help

 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's 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"));

        }
    }

Check here for more

Upvotes: 1

Related Questions