Why DataGridRow IsSelected binding does not work in DataGrid

In a Josh Smith MVVM demo, he uses a ListView with a ListViewItem style as follows:

<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> 

It perfectly binds the IsSelected property. The style is applied to the ListView like this:

 <ListView ItemContainerStyle="{StaticResource CustomerItemStyle}" ItemsSource="{Binding}" > 

My version

I tried to bind IsSelected in the same way with a DataGrid via a DataGridRow. However, this causes problems when items are selected through the ViewModels collection of elements where the IsSelected property is defined.

Since it uses two-way binding, I would think that elements can be selected through the user interface and a collection of ViewModels elements.

Say I select items through the user interface, this works great. I can select a particular item, and then use [shift] to select a range, and then use [ctrl] to select a few more items. Deselecting items also works correctly.

However, I choose a bunch through the collection. Let me say by clicking the button (as I do in the code below), a set of elements is selected. When I scroll through the DataGrid, some of them are selected the way they should be, some of them are not. If I select one item through the user interface, then only some of the elements will be canceled, and some will remain selected, and all this is a little scared. Even the "Select All" button in the upper left corner does not work correctly.

code All the codes below are at the bottom, and at the bottom is the DataGridRow style with IsSelected binding.

Here is my user class:

 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)); } } } 

Here is the UserViewModel where IsSelected is located:

 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)); } } } 

Here is my 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); } } } 

Here is the user repository in which I create users:

 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); } } } 

And finally, here is a view for all users:

 <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> 
+7
c # wpf mvvm wpfdatagrid
source share
5 answers

Hank

Turning off virtualization will certainly fix your problem at the expense of performance.

you can disable the repeated VirtualizingStackPanel utility as suggested by @nit, but this will not solve the problem completely.

Another approach is to use Attached behavior.

there is a proven working solution from @Samuel Jack (used before), this is for listbox and MVVM, and it works great. selected items are snapped in two-way mode.

http://blog.functionalfun.net/2009/02/how-to-databind-to-selecteditems.html

As for the ListBox, I downloaded a modified solution for the DataGrid, this is the base DataGrid with the required functionality.

https://www.sugarsync.com/pf/D6837746_80955217_331798

Enjoy

+2
source share

I remember this problem with my DataGrid when scrolling. The reason this caused a strange problem was caused by virtualization. Try setting virtualization to False in your DataGrid.

 <DataGrid EnableRowVirtualization="False"> 
+1
source share

You will need to catch the Scroll event and then attach it to the element container generator to get the element generated by the scroll. When an element is loaded, you can update the binding (using BingingExpression) to the element so that it reflects the state of your object, i.e. IsSelected

0
source share

This is a problem with Recycling VirtualizingStackpanel . Setting VirtualizingStackPanel.VirtualizationMode="Standard" on the Datagrid will solve this.

0
source share

Hank, your problem is that the Grid will only bind the visible Rows to the DataGrid and will not update the binding as soon as another row appears.

This problem is caused by Data Virtualization .

Try setting virtualization to Standard

 VirtualizingStackPanel.VirtualizationMode="Standard" 

I think this will solve your problem.

0
source share

All Articles