在 Josh Smith 的 MVVM 演示中,他使用了带有 ListViewItem 样式的 ListView,如下所示:
<Style x:Key="CustomerItemStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="ItemsControl.AlternationIndex" Value="1" />
<Condition Property="IsSelected" Value="False" />
<Condition Property="IsMouseOver" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="#EEEEEEEE" />
</MultiTrigger>
</Style.Triggers>
</Style>
它很好地绑定(bind)了 IsSelected 属性。样式应用于 ListView,如下所示:
<ListView
ItemContainerStyle="{StaticResource CustomerItemStyle}"
ItemsSource="{Binding}"
>
我的版本
我已尝试通过 DataGridRow 以类似的方式将 IsSelected 与 DataGrid 绑定(bind)。但是,当通过 IsSelected 属性定义的项目 ViewModel 集合选择项目时,它会导致问题。
因为它使用双向绑定(bind),所以我认为可以通过 UI 和项目 ViewModel 的集合来选择项目。
假设我通过 UI 选择项目,这很好用。我可以选择单个项目,然后使用 [shift] 选择一个范围,然后使用 [ctrl] 选择更多项目。取消选择项目也能正常工作。
但是我通过收藏选择了一堆。假设单击一个按钮(就像我在下面的代码中所做的那样),一堆项目被选中。当我向下滚动 DataGrid 然后选择了一些,因为它们应该是一些不是。如果我通过 UI 选择一个项目,那么只有一些项目被取消选择,一些项目保持选中状态,这有点古怪。即使是左上角的“全选”按钮也不太正确。
代码 所有代码都在下面,底部是 View ,关键部分是具有 IsSelected 绑定(bind)的 DataGridRow 样式。
这是我的用户类:
using System.ComponentModel;
namespace WpfAppDataGrid.Model
{
public class User : INotifyPropertyChanged
{
public static User CreateNewUser()
{
return new User();
}
public User() { }
public int User_ID { get; set; }
public string Username { get; set; }
public string Name { get; set; }
public string Job_Title { get; set; }
public string Department { get; set; }
public string Company { get; set; }
public string Phone_Office { get; set; }
public string Phone_Mobile { get; set; }
public string Email { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
这是 IsSelected 所在的 UserViewModel:
using System;
using System.ComponentModel;
using WpfAppDataGrid.DataAccess;
using WpfAppDataGrid.Model;
namespace WpfAppDataGrid.ViewModel
{
class UserViewModel : INotifyPropertyChanged
{
readonly User _user;
readonly UserRepository _userRepository;
bool _isSelected;
public UserViewModel(User user, UserRepository userRepository)
{
if (user == null)
throw new ArgumentNullException("user");
if (userRepository == null)
throw new ArgumentNullException("userRepository");
_user = user;
_userRepository = userRepository;
}
public UserViewModel()
{
}
public int User_ID
{
get { return _user.User_ID; }
set
{
if (value == _user.User_ID)
return;
_user.User_ID = value;
RaisePropertyChanged("User_ID");
}
}
public string Username
{
get { return _user.Username; }
set
{
if (value == _user.Username)
return;
_user.Username = value;
RaisePropertyChanged("Username");
}
}
public string Name
{
get { return _user.Name; }
set
{
if (value == _user.Name)
return;
_user.Name = value;
RaisePropertyChanged("Name");
}
}
public string Job_Title
{
get { return _user.Job_Title; }
set
{
if (value == _user.Job_Title)
return;
_user.Job_Title = value;
RaisePropertyChanged("Job_Title");
}
}
public string Department
{
get { return _user.Department; }
set
{
if (value == _user.Department)
return;
_user.Department = value;
RaisePropertyChanged("Department");
}
}
public string Company
{
get { return _user.Company; }
set
{
if (value == _user.Company)
return;
_user.Company = value;
RaisePropertyChanged("Company");
}
}
public string Phone_Office
{
get { return _user.Phone_Office; }
set
{
if (value == _user.Phone_Office)
return;
_user.Phone_Office = value;
RaisePropertyChanged("Phone_Office");
}
}
public string Phone_Mobile
{
get { return _user.Phone_Mobile; }
set
{
if (value == _user.Phone_Mobile)
return;
_user.Phone_Mobile = value;
RaisePropertyChanged("Phone_Mobile");
}
}
public string Email
{
get { return _user.Email; }
set
{
if (value == _user.Email)
return;
_user.Email = value;
RaisePropertyChanged("Email");
}
}
/// <summary>
/// Gets/sets whether this customer is selected in the UI.
/// </summary>
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value == _isSelected)
return;
_isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
这是我的 AllUsersViewModel:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Input;
using WpfAppDataGrid.DataAccess;
namespace WpfAppDataGrid.ViewModel
{
class AllUsersViewModel : INotifyPropertyChanged
{
readonly UserRepository _userRepository;
public AllUsersViewModel()
{
_userRepository = new UserRepository();
_userRepository.LoadUsers();
CreateAllUsers();
}
void CreateAllUsers()
{
List<UserViewModel> all =
(from usr in _userRepository.GetUsers()
select new UserViewModel(usr, _userRepository)).ToList();
foreach (UserViewModel uvm in all)
{
uvm.PropertyChanged += this.OnUserViewModelPropertyChanged;
}
this.UserCollection = new ObservableCollection<UserViewModel>(all);
this.UserCollection.CollectionChanged += this.OnCollectionChanged;
}
private ObservableCollection<UserViewModel> userCollection;
public ObservableCollection<UserViewModel> UserCollection
{
get
{
return userCollection;
}
set
{
userCollection = value;
RaisePropertyChanged("UserCollection");
}
}
RelayCommand selectItemsCommand;
public ICommand SelectItemsCommand
{
get
{
if (selectItemsCommand == null)
selectItemsCommand = new RelayCommand(SelectItemsCommandExecute, CanSelectItemsCommand);
return selectItemsCommand;
}
}
private void SelectItemsCommandExecute(object parameter)
{
for (int i = 4; i <= 49; i++)
{
UserCollection[i].IsSelected = true;
}
}
private bool CanSelectItemsCommand(object parameter)
{
return true;
}
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count != 0)
foreach (UserViewModel userVM in e.NewItems)
userVM.PropertyChanged += this.OnUserViewModelPropertyChanged;
if (e.OldItems != null && e.OldItems.Count != 0)
foreach (UserViewModel userVM in e.OldItems)
userVM.PropertyChanged -= this.OnUserViewModelPropertyChanged;
}
void OnUserViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
string IsSelected = "IsSelected";
if (e.PropertyName == IsSelected)
this.RaisePropertyChanged("TotalSelectedUsers");
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
}
这是我创建用户的用户存储库:
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WpfAppDataGrid.Model;
namespace WpfAppDataGrid.DataAccess
{
public class UserRepository
{
ObservableCollection<User> _users = new ObservableCollection<User>();
public UserRepository()
{
}
public ObservableCollection<User> GetUsers()
{
return _users;
}
public void LoadUsers()
{
int i = 0;
while (i < 1000)
{
i++;
var user = new User();
user.User_ID = i;
user.Username = RandomString(8, true);
user.Name = user.Username + " " + RandomString(8, true);
user.Job_Title = RandomString(8, true);
user.Department = RandomString(8, true);
user.Company = RandomString(10, true);
user.Phone_Office = "07 " + RandomNumber(5200, 6700) + " " + RandomNumber(1000, 9999);
user.Phone_Mobile = "04 " + RandomNumber(2800, 4500) + " " + RandomNumber(1000, 9999);
user.Email = user.Username + "@gmail.com";
_users.Add(user);
}
}
private static Random randomSeed = new Random();
public static string RandomString(int size, bool lowerCase)
{
StringBuilder RandStr = new StringBuilder(size);
int Start = (lowerCase) ? 97 : 65;
for (int i = 0; i < size; i++)
RandStr.Append((char)(26 * randomSeed.NextDouble() + Start));
return RandStr.ToString();
}
private int RandomNumber(int min, int max)
{
return randomSeed.Next(min, max);
}
}
}
最后是所有用户的 View :
<Window x:Class="WpfAppDataGrid.View.AllUsersView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewmodel="clr-namespace:WpfAppDataGrid.ViewModel"
Title="AllUsersView" Height="450" Width="820">
<Window.DataContext>
<viewmodel:AllUsersViewModel />
</Window.DataContext>
<Window.Resources>
<Style x:Key="UserRowStyle" TargetType="{x:Type DataGridRow}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Setter Property="BorderBrush" Value="DarkGray" />
<Setter Property="BorderThickness" Value="0,0,1,0"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridRow}">
<Grid>
<Border x:Name="DGR_BackingBorder" BorderBrush="Orange" BorderThickness="1,2,1,2" Background="Transparent">
</Border>
<Border x:Name="DGR_Border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1,2,1,2"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="True">
<SelectiveScrollingGrid>
<SelectiveScrollingGrid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</SelectiveScrollingGrid.ColumnDefinitions>
<SelectiveScrollingGrid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</SelectiveScrollingGrid.RowDefinitions>
<DataGridCellsPresenter x:Name="DGR_CellsPresenter" Grid.Column="1" ItemsPanel="{TemplateBinding ItemsPanel}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<DataGridDetailsPresenter x:Name="DGR_DetailsPresenter" Grid.Column="1" Grid.Row="1"
SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding AreRowDetailsFrozen, ConverterParameter={x:Static SelectiveScrollingOrientation.Vertical}, Converter={x:Static DataGrid.RowDetailsScrollingConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
Visibility="{TemplateBinding DetailsVisibility}" />
<DataGridRowHeader Foreground="White" Grid.RowSpan="2" SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical"
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Row}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
</SelectiveScrollingGrid>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="DGR_Border" Property="BorderBrush" Value="Transparent" />
<Setter TargetName="DGR_Border" Property="BorderThickness" Value="1,2,1,2" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="DGR_Border" Property="BorderBrush" Value="DarkOrange"/>
<Setter TargetName="DGR_Border" Property="Background" Value="Orange"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type DataGridCell}" >
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Foreground" Value="Black" />
</Style>
</Window.Resources>
<Grid Name="gridUsers" Background="Transparent">
<DockPanel Background="Transparent" Margin="2,10,2,2" >
<Grid DockPanel.Dock="Bottom" Margin="0,2,4,2">
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Center">
<Button Content="Select rows 5 to 50" Command="{Binding SelectItemsCommand}"/>
<TextBlock Text=" Total: " />
<ContentPresenter Content="{Binding ElementName=GenericDataGrid, Path=ItemsSource.Count}" ContentStringFormat="0" />
</StackPanel>
</Grid>
<DataGrid Name="GenericDataGrid" Background="Transparent"
RowStyle="{StaticResource UserRowStyle}"
BorderThickness="0"
CanUserReorderColumns="True"
AutoGenerateColumns="False"
ItemsSource="{Binding UserCollection}"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" CanUserReorder="True" IsReadOnly="True" Binding="{Binding Path=User_ID,NotifyOnTargetUpdated=True}" />
<DataGridTextColumn Header="Name" CanUserReorder="True" CanUserSort="True" Binding="{Binding Path=Name}"/>
<DataGridTextColumn Header="Username" CanUserReorder="True" CanUserSort="True" Binding="{Binding Path=Username}"/>
<DataGridTextColumn Header="Job Title" CanUserReorder="True" CanUserSort="True" Binding="{Binding Path=Job_Title}"/>
<DataGridTextColumn Header="Department" CanUserReorder="True" CanUserSort="True" Binding="{Binding Path=Department}"/>
<DataGridTextColumn Header="Company" CanUserReorder="True" CanUserSort="True" Binding="{Binding Path=Company}"/>
<DataGridTextColumn Header="Phone" CanUserReorder="True" CanUserSort="True" Binding="{Binding Path=Phone_Office}"/>
<DataGridTextColumn Header="Mobile" CanUserReorder="True" CanUserSort="True" Binding="{Binding Path=Phone_Mobile}"/>
<DataGridTextColumn Header="eMail" CanUserReorder="True" CanUserSort="True" Binding="{Binding Path=Email}"/>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Grid>
</Window>
最佳答案
请不要禁用虚拟化!
如果这是一个错误,Microsoft 早就修复了它,因为数据表通常是最常见的数据 View 之一。
WPF 框架不是针对业余爱好者和业余程序员的玩笑。它旨在供专业开发人员用来定制商业丰富的桌面应用程序。如此严重的错误,在那段时间未修复(即未得到服务),将立即取消 WPF 作为一项严肃和专业技术的资格。
一切正常。
您必须设置 Binding.UpdateSourceTrigger
Binding
的属性(property)在 DataGridRow.IsSelected
上属性明确为 UpdateSourcetrigger.PropertyChanged
.
DataGridRow
有多种视觉选择状态,包括未聚焦的选择。 Unfocused selected 是在没有焦点的情况下选择行时的状态,例如通过数据模型选择时。
默认情况下,DataGridRow
实际上已被选中,但样式没有为未聚焦状态定义(令人满意的)视觉反馈。
这个“问题”是通过使用简单触发器自定义视觉状态而人为引入的。这是另一个很好的例子,为什么要使用 VisualStateManager
总是优于简单的属性 Trigger
(当可视化 Control
的视觉状态时)。
后来的框架(如 UWP)强制使用 VisualStateManager
(和 VisualState
)。
关键是,一个控件可以有多个状态,这些状态通常不会通过属性来表达。状态是相互排斥的,这在通过属性表达这种约束时需要一些额外的努力。 VisualState
是表达视觉状态的便捷方式。
以下完整示例 Style
目标 DataGridRow
展示如何绑定(bind) DataGridRow.IsSelected
属性(property)给IsEnabled
行项目(数据模型)的属性以及如何更改视觉 Unfocused_Selected
状态。
虽然不完整,但它正在充分发挥作用,以证明 DataGrid
正在按预期工作 - 启用了 UI 虚拟化。只需调整 DataGrid.ItemsSource
绑定(bind)并确保您的数据模型具有 IsEnabled
引发 INotifyPropertyChanged.PropertyChanged
的属性(property)事件:
<DataGrid EnableRowVirtualization="True"
ItemsSource="{Binding DataItems}"
Height="200">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected"
Value="{Binding IsEnabled, UpdateSourceTrigger=PropertyChanged}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridRow}">
<Border x:Name="DGR_Border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True"
Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Normal_Selected">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="DGR_Border"
Storyboard.TargetProperty="(Panel.Background).
(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0"
Value="DarkRed" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!-- Unfocused related states -->
<VisualState x:Name="Unfocused_Selected">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="DGR_Border"
Storyboard.TargetProperty="(Panel.Background).
(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0"
Value="Orange" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseOver_Unfocused_Selected">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="DGR_Border"
Storyboard.TargetProperty="(Panel.Background).
(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0"
Value="GreenYellow" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseOver_Unfocused_Editing">
<Storyboard>
<!-- TODO::Define ColorAnimation -->
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused_Editing">
<Storyboard>
<!-- TODO::Define ColorAnimation -->
</Storyboard>
</VisualState>
<!-- Focused states -->
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="DGR_Border"
Storyboard.TargetProperty="(Panel.Background).
(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0"
Value="CornflowerBlue" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseOver_Selected">
<Storyboard>
<!-- TODO::Define ColorAnimation -->
</Storyboard>
</VisualState>
<VisualState x:Name="Normal_Editing">
<Storyboard>
<!-- TODO::Define ColorAnimation -->
</Storyboard>
</VisualState>
<VisualState x:Name="MouseOver_Editing">
<Storyboard>
<!-- TODO::Define ColorAnimation -->
</Storyboard>
</VisualState>
<VisualState x:Name="Normal_AlternatingRow">
<Storyboard>
<!-- TODO::Define ColorAnimation -->
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<SelectiveScrollingGrid>
<SelectiveScrollingGrid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</SelectiveScrollingGrid.ColumnDefinitions>
<SelectiveScrollingGrid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</SelectiveScrollingGrid.RowDefinitions>
<DataGridCellsPresenter Grid.Column="1"
ItemsPanel="{TemplateBinding ItemsPanel}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<DataGridDetailsPresenter Grid.Column="1"
Grid.Row="1"
Visibility="{TemplateBinding DetailsVisibility}"
SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding AreRowDetailsFrozen,
ConverterParameter={x:Static SelectiveScrollingOrientation.Vertical},
Converter={x:Static DataGrid.RowDetailsScrollingConverter},
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
<DataGridRowHeader Grid.RowSpan="2"
SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical"
Visibility="{Binding HeadersVisibility,
ConverterParameter={x:Static DataGridHeadersVisibility.Row},
Converter={x:Static DataGrid.HeadersVisibilityConverter},
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
</SelectiveScrollingGrid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
</DataGrid>
关于c# - 为什么 DataGridRow IsSelected 绑定(bind)不适用于 DataGrid,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23441113/