Cześć,
natknąłem się w swoim projekcie na następujący problem: Dropdown list w ComboBoxie powiązanym z ObservableCollection nie zmienia się, mimo że zmieniły się właściwości wyrzucane przez ToString() obiektu tej kolekcji. Chciałbym mieć taką sytuację, że jeśli np ComboBox jest powiązany z ObservableCollection<Thing> gdzie Thing.ToString() wyrzuca Thing.Name, to w momencie edytowania wartości Name Dropdown jak i obecna wartość tego ComboBoxa aktualizują się na bieżąco z edytowaną wartością. Obecnie rozwiązałem to siłowo stosując przy setterze właściwości Name powiązanej z TextBoxem metodę RefreshList() która na na chwilę ustala wartość powiązanej ObservableCollection na null po czym ustawia ją z powrotem na poprzednią wartość - to wymusza update ComboBoxa, jednak wydaje mi się beznadziejnym rozwiązaniem jeśli chodzi o wydajność programu przy dłuższej liście. Czy jest sposób, żeby rozwiązać to ładniej?
W celu przedstawienia problemu stworzyłem prosty przykład:
https://github.com/piotr-napadlek/MVVMEditComboBoxItemsSample
A oto kod przykładu:
MainWindow.xaml
<Window x:Class="MVVMEditComboBoxItemsSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MVVMEditComboBoxItemsSample"
mc:Ignorable="d"
Title="MVVM ComboBox edit sample" Height="372.52" Width="415.214">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50*"></RowDefinition>
<RowDefinition Height="81*"/>
<RowDefinition Height="26*"/>
<RowDefinition Height="45*"/>
<RowDefinition Height="26*"/>
<RowDefinition Height="44*"/>
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" Margin="10" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" ItemsSource="{Binding Path=Things}" SelectedItem="{Binding Path=SelectedThing, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></ComboBox>
<StackPanel Grid.Row="1" Margin="10">
<Button Command="{Binding Path=AddCommand}">Add Item</Button>
<Button Command="{Binding Path=CloneCommand}">Clone Item</Button>
<Button Command="{Binding Path=DeleteCommand}">Delete Item</Button>
</StackPanel>
<Label Grid.Row="2" Margin="0" >Name</Label>
<TextBox Grid.Row="3" HorizontalAlignment="Stretch" Margin="10" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}"/>
<Label Grid.Row="4" Margin="0" >Price</Label>
<TextBox Grid.Row="5" HorizontalAlignment="Stretch" Margin="10" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Text="{Binding Path=Price}"/>
</Grid>
</Window>
MainViewModel.cs
using MVVMEditComboBoxItemsSample.MockModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MVVMEditComboBoxItemsSample
{
class MainViewModel : ViewModelBase
{
private ObservableCollection<Thing> things;
private Thing selectedThing;
private ICommand addCommand;
private ICommand cloneCommand;
private ICommand deleteCommand;
public MainViewModel()
{
Things = new ObservableCollection<Thing>(ThingDataManager.Instance.GetThings());
SelectedThing = Things.FirstOrDefault();
}
public ObservableCollection<Thing> Things
{
get
{
return things;
}
set
{
things = value;
OnPropertyChanged(nameof(Things));
}
}
public Thing SelectedThing
{
get
{
return selectedThing;
}
set
{
selectedThing = value;
OnPropertyChanged(nameof(SelectedThing));
OnPropertyChanged(nameof(Name));
OnPropertyChanged(nameof(Price));
}
}
public string Name
{
get
{
if (SelectedThing != null)
{
return SelectedThing.Name;
}
return null;
}
set
{
SelectedThing.Name = value;
OnPropertyChanged(nameof(Name));
RefreshList();
}
}
public string Price
{
get
{
if (SelectedThing != null)
{
return SelectedThing.Price;
}
return null;
}
set
{
SelectedThing.Price = value;
OnPropertyChanged(nameof(Price));
}
}
public ICommand AddCommand
{
get
{
if(addCommand==null)
{
addCommand = new CommandBase(i => AddItem(), null);
}
return addCommand;
}
}
public ICommand CloneCommand
{
get
{
if(cloneCommand==null)
{
cloneCommand = new CommandBase(i => CloneItem(), i => SelectedThing!=null);
}
return cloneCommand;
}
}
public ICommand DeleteCommand
{
get
{
if(deleteCommand==null)
{
deleteCommand = new CommandBase(i => DeleteItem(), i => SelectedThing!=null);
}
return deleteCommand;
}
}
public void AddItem()
{
Thing newThing = new Thing();
Things.Add(newThing);
SelectedThing = newThing;
}
public void CloneItem()
{
Thing clonedThing = new Thing();
clonedThing.Name = SelectedThing.Name + " - copy";
clonedThing.Price = SelectedThing.Price;
Things.Add(clonedThing);
SelectedThing = clonedThing;
}
public void DeleteItem()
{
Thing tempThing = new Thing();
tempThing = SelectedThing;
if (Things.IndexOf(SelectedThing) != 0)
{
SelectedThing = Things.FirstOrDefault();
}
else if (Things.Count==1)
{
SelectedThing = null;
}
else
{
SelectedThing = Things[1];
}
Things.Remove(tempThing);
}
private void RefreshList()
{
List<Thing> tempThings = Things.ToList();
Thing tempThing = SelectedThing;
Things = null; //for instant combobox update, comment out if unnecesary
Things = new ObservableCollection<Thing>(tempThings);
SelectedThing = tempThing;
}
}
}
Thing.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMEditComboBoxItemsSample.MockModel
{
class Thing
{
public string Name { get; set; }
public string Price { get; set; }
public override string ToString()
{
return Name;
}
}
}
Reszta mam nadzieje jest samoopisująca się ;).
EDIT: Tzn. Zdaje sobie sprawę z tego, że update właściwości jakiegoś obiektu nie powoduje de facto zmiany kolekcji zawierającej te obiekty, bo ona nadal zawiera te same referencje, ale myślałem że wymuszenie OnPropertyChanged dla tej listy mimo wszystko wymusi przejechanie jeszcze raz po ToString() obiektów tej kolekcji.