tags:

views:

771

answers:

2

Consider the following XAML:

<ComboBox Name="CompanyComboBox" 
    HorizontalAlignment="Stretch"
    ItemsSource="{Binding Path=GlobalData.Companies}" 
    SelectedValuePath="Id"
    SelectedValue="{Binding Customer.CompanyId, ValidatesOnDataErrors=True}"
    DisplayMemberPath="Name" />

GlobalData.Companies is a collection (IEnumerable<Company>) of companies; this collection can be reloaded on background (it is downloaded from a webservice). When this happens, ComboBox correctly reloads items via binding. However as a side-effect, it also resets the selected item!

I have used Reflector to inspect combo-box sources and apparently this is intended behavior.

Is there any "nice" way how to get around this? What I want to achieve, is that if the user selects "Company A" and reloads list of companies afterwards, then "Company A" stays selected (assuming it is in the new list).

A: 

hmm, I don't know if it is a "nice" way, but if you can access the selected item before the reload occurs, you can save it (or its key or something), and select it programatically again after the reload is done.

quick mockup:

var selectedItem = myCombo.SelectedItem;
DoReload();
myCombo.SelectedItem = selectedItem;

But I assume you mean another way than this manual work around?
Hope this helps anyway...

UPDATE
Ok I see, from a background thread.
Are you using an ICollectionView to bind your combobox too? If so, you can use the CurrentItem property to keep a reference. I made a quick mockup, and this is working on my setup. this assumes you have a reference to your UI:

XAML

<Grid VerticalAlignment="Top">  
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <ComboBox ItemsSource="{Binding Items}" IsSynchronizedWithCurrentItem="True" Grid.Column="0" Grid.Row="0" DisplayMemberPath="Name"/>
    <Button Command="{Binding UpdateCommand}" Grid.Column="1" Grid.Row="0">Update</Button>
</Grid>

View/ViewModel

public partial class Window1 : Window {
   public Window1() {
        InitializeComponent();
        this.DataContext = new ViewModel(this);
   }
}

public class ViewModel
{
    private readonly Window1 window;
    private ObservableCollection<Item> items;
    private ICollectionView view;

    public ViewModel(Window1 window) {
        this.window = window;
        items = new ObservableCollection<Item>
            {
                new Item("qwerty"),
                new Item("hello"),
                new Item("world"),
            };

        view = CollectionViewSource.GetDefaultView(items);
    }

    public ObservableCollection<Item> Items { get { return items; } }

    public ICommand UpdateCommand {
        get { return new RelayCommand(DoUpdate); }
    }

    public Item SelectedItem { get; set; }

    private void DoUpdate(object obj) {
        var act = new Func<List<Item>>(DoUpdateAsync);
        act.BeginInvoke(CallBack, act);
    }

    private List<Item> DoUpdateAsync() {
        return new List<Item> {
                new Item("hello"),
                new Item("world"),
                new Item("qwerty"),
            };
    }

    private void CallBack(IAsyncResult result) {
        try {
            var act = (Func<List<Item>>)result.AsyncState;
            var list = act.EndInvoke(result);

            window.Dispatcher.Invoke(new Action<List<Item>>(delegate(List<Item> lst) {
                                                                    var current = lst.Single(i => i.Name == ((Item)view.CurrentItem).Name);
                                                                    Items.Clear();
                                                                    lst.ForEach(Items.Add);
                                                                    view.MoveCurrentTo(current);
                                                                }), list);

        } catch(Exception exc){ Debug.WriteLine(exc); }
    }
}

public class Item {
    public Item(string name) {
        Name = name; 
    }
    public string Name { get; set; }
}

You will need to do some handling in case the selected item is no longer in the list.
The IsSynchronizedWithCurrentItem property is important here, else it won't work!
Also, the way the reference to the main window is made should be by a DI-framework.

Roel
Miroslav Bajtoš
A: 

Maybe you can use ObservableCollection<Company> instead of your IEnumerable<Company>? Then, on background change you would only Add / Remove items that are new / absent in the new list, selected item should stay, unless it was removed by the change.

You can update your observable collection in a separate thread with a small hack-around.

Yacoder
This could be the solution, although it isn't as nice/easy as I would like to :-|
Miroslav Bajtoš
It's probably not necessary to update the collection from the background thread. Get the current collection from the web service in the background, and then (assuming you're using a BackgroundWorker) update the `ObservableCollection<Company>` in the RunWorkerCompleted event.
Robert Rossney