【发布时间】:2014-02-06 14:30:07
【问题描述】:
我正在尝试使用 MVVM 模式来显示可以在 UI 中更新的对象列表。我还想要一些基本操作,例如添加/删除。现在我有一个简单的Customer 模型,它只是一个名字和姓氏。我将DataContext 分配给ViewModel。在ViewModel 类中,我有稍后将注入的“模型”(只是客户的List)。为了使底层List 模型保持最新,我在每次访问List 时都用ObservableCollection 包装它。在这样做时,它似乎并没有保持SelectedValue 处于活动状态,因为当您删除SelectedValue 时,它会将其设置为null 并清除ListView 中的选择。这意味着我需要一些手动跟踪(我希望避免这种情况)。
我试图将 ObservableCollection 作为成员变量保留到 ViewModel 类,但这只会复制底层数据列表,如果您从中添加/删除对象,它不会保持同步。
我还想避免使用 ObservableCollection 作为模型,因为这似乎更适合 ViewModel 数据绑定支持 (reference; see Using it in the model)。
有没有人这样做过,并找到了一个好方法来保持List 模型同步,同时使用ObservableCollection 将数据绑定到ListView?
MainWindow.xaml
<Window x:Class="TestCollectionAndSelectedItem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestCollectionAndSelectedItem"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:IndexConverter x:Key="IndexConverter" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="35*" />
<RowDefinition Height="276*" />
</Grid.RowDefinitions>
<Button Content="Add" Command="{Binding Path=AddCustomer}" Width="95" Margin="22,11,0,231" Grid.Row="1" HorizontalAlignment="Left" />
<Label Content="First" Height="26" Width="80" HorizontalAlignment="Left" Margin="263,95,0,155" Grid.Row="1" />
<TextBox Text="{Binding SelectedCustomer.FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="31" Width="133" IsEnabled="True" Margin="345,93,25,152" Name="textBox1" Grid.Row="1" />
<Label Content="Last" Height="26" Width="80" HorizontalAlignment="Left" Margin="263,139,0,111" Grid.Row="1" />
<TextBox Text="{Binding SelectedCustomer.LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="133" Height="31" IsEnabled="True" Margin="345,137,25,108" Name="textBox2" Grid.Row="1" />
<ListView SelectionMode="Single" ItemsSource="{Binding Customers}" SelectedValue="{Binding SelectedCustomer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Name="listviewNames" Margin="22,56,258,77" Grid.Row="1">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Number"
DisplayMemberBinding="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListViewItem}},
Converter={StaticResource IndexConverter}}" />
<GridViewColumn Header="Last" DisplayMemberBinding="{Binding Path=LastName}" Width="80"/>
<GridViewColumn Header="First" DisplayMemberBinding="{Binding Path=FirstName}" Width="80"/>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
<Button Command="{Binding Path=RemoveCustomer}" CommandParameter="{Binding ElementName=listviewNames, Path=SelectedIndex}" Content="Remove" HorizontalAlignment="Left" Margin="150,11,0,231" Width="95" Grid.Row="1" />
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
// wont go here, just example
List<Customer> customers = new List<Customer>() {
new Customer() { LastName = "Anderson", FirstName = "John" },
new Customer() { LastName = "NoName", FirstName = "" } };
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel(customers);
}
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<Customer> _customersModel;
private Customer _selectedCustomer;
public DelegateCommand<object> AddCustomer { get; private set; }
public DelegateCommand<int> RemoveCustomer { get; private set; }
public ViewModel(List<Customer> listOfCustomers)
{
_customersModel = listOfCustomers;
AddCustomer = new DelegateCommand<object>((a) => Add(), (a) => CanAdd());
RemoveCustomer =
new DelegateCommand<int>((a) => Remove(a), (a) => CanRemove());
}
public ObservableCollection<Customer> Customers
{
get
{
return new ObservableCollection<Customer>(_customersModel);
}
set
{
_customersModel = new List<Customer>(value);
OnPropertyChanged("Customers");
}
}
public Customer SelectedCustomer
{
get { return _selectedCustomer; }
set
{
if (_selectedCustomer != value)
{
_selectedCustomer = value;
UpdateCommands();
OnPropertyChanged("SelectedCustomer");
}
}
}
public void Add()
{
_customersModel.Add(new Customer() { FirstName = "", LastName = "" });
UpdateCommands();
OnPropertyChanged("Customers");
}
public Boolean CanAdd()
{
return _customersModel.Count < 8;
}
public void Remove(int selectedIndex)
{
_customersModel.Remove(_selectedCustomer);
UpdateCommands();
OnPropertyChanged("Customers");
}
public Boolean CanRemove()
{
return (_selectedCustomer != null) &&
(_customersModel != null) &&
(_customersModel.Count > 0);
}
public void UpdateCommands()
{
RemoveCustomer.RaiseCanExecuteChanged();
AddCustomer.RaiseCanExecuteChanged();
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
IndexConverter.cs
public class IndexConverter : IValueConverter
{
public object Convert(object value, Type TargetType, object parameter, CultureInfo culture)
{
ListViewItem item = (ListViewItem)value;
ListView listView =
ItemsControl.ItemsControlFromItemContainer(item) as ListView;
int index =
listView.ItemContainerGenerator.IndexFromContainer(item) + 1;
return index.ToString();
}
public object ConvertBack(object value,
Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Customer.cs
public class Customer
{
public String FirstName { get; set; }
public String LastName { get; set; }
}
【问题讨论】:
标签: c# wpf xaml data-binding mvvm