4
votes

I've got a list of items bound to a ComboBox. When a user selects an item, I'd like to cancel the selection and select a different item instead. This must happen from within the setter of the property that the SelectedItem is bound to. I'm using Silverlight 3.

My data model for each item in the ComboBox:

public class DataItem
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Object that is set to the DataContext:

public class DataContainer : INotifyPropertyChanged
{

    public DataContainer()
    {
        itemList = new List<DataItem>();
        itemList.Add(new DataItem() { Id = 1, Name = "First" });
        itemList.Add(new DataItem() { Id = 2, Name = "Second" });
        itemList.Add(new DataItem() { Id = 3, Name = "Third" });
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private DataItem selectedItem;
    public DataItem SelectedItem
    {
        get { return selectedItem; }
        set
        {
            if (value != null && value.Id == 2)
                value = itemList[0];
            selectedItem = value;
            NotifyPropertyChanged("SelectedItem");
        }
    }

    private List<DataItem> itemList;
    public List<DataItem> ItemList
    {
        get { return itemList; }
        set { itemList = value; NotifyPropertyChanged("DataList"); }
    }

    protected void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

}

Relevant bits of xaml:

<StackPanel>
    <StackPanel Orientation="Horizontal">
        <ComboBox x:Name="comboBox" DisplayMemberPath="Name" Width="100" ItemsSource="{Binding ItemList}" SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>
        <Button Content="Set to First" Width="100" Click="Button_Click"/>
    </StackPanel>
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="Selected item: "/>
        <TextBlock Text="{Binding SelectedItem.Id}"/>
        <TextBlock Text=" - "/>
        <TextBlock Text="{Binding SelectedItem.Name}"/>
    </StackPanel>
</StackPanel>

It looks like my code to select the first item when the user selects the second item is working. The selected item is in fact set to "First" while the ComboBox is still displaying "Second" as if it was selected.

Is there any way to force the ComboBox to redraw or to reconsider what it should visually mark as selected?

I do this from the above mentioned Button_Click method and it works:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        var c = DataContext as DataContainer;
        if (c != null)
        {
            c.SelectedItem = null;
            c.SelectedItem = c.ItemList[0];
        }
    }

But setting to null and then the value I want doesn't work if I do it from within the setter like I need to.

2

2 Answers

0
votes

I may have found a solution for you. I was able to get what I think you're asking for working by doing the following:

public DataItem SelectedItem
{
    get { return _selectedItem; }
    set
    {
        if (value != null && value.Id == 2)
        {
            value = itemList[0];
            UpdateUI();  // Call this to force the UI to update.
        }
        _selectedItem = value;
        NotifyPropertyChanged("SelectedItem");

    }
}

private void UpdateUI()
{
    ThreadPool.QueueUserWorkItem(
        o =>
        {
            Thread.Sleep(1);

            Deployment.Current.Dispatcher.BeginInvoke(()=>
            {
                _selectedItem = null;
                NotifyPropertyChanged("SelectedItem");
                _selectedItem = itemList[0];
                NotifyPropertyChanged("SelectedItem");
            });
        });
}

I wish I could explain to you why this is working, but I can only guess. Basically, its exiting the UI thread, and then re-entering a moment later via the Dispatcher.BeginInvoke() call. This appears to give the ComboBox control time to update itself from the user interaction, and then respond to the Dispatcher execution.

One problem I've found is that Silverlight seems to go a little wonky after multiple executions of the threading code. Increasing the Thread.Sleep time seems to help. I think this solution will work for the majority of situations and won't be an issue.

0
votes

You don't have to queue a Thread with 1 second waiting as grimus suggested. this should also work for you:

public DataItem SelectedItem
{
    get { return _selectedItem; }
    set
    {
        _selectedItem = value;
        NotifyPropertyChanged("SelectedItem");

        if (value != null && value.Id == 2)
        {
            Diployment.Current.Dispatcher.BeginInvoke(() => { 
                          _selectedItem = itemList[0];               
                          NotifyPropertyChanged("SelectedItem"); });
        }


    }
}