c# - ¿Por qué el enlace DataGridRow IsSelected no funciona en DataGrid?
wpf mvvm (5)
En la demostración MVVM de Josh Smith, usa un ListView con un estilo ListViewItem como este:
<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>
Vincula la propiedad IsSelected muy bien. El estilo se aplica a ListView de esta manera:
<ListView
ItemContainerStyle="{StaticResource CustomerItemStyle}"
ItemsSource="{Binding}"
>
Mi version
He intentado enlazar IsSelected de forma similar con un DataGrid a través de DataGridRow. Sin embargo, está causando problemas cuando los elementos se seleccionan mediante la colección de elementos ViewModels, donde se define la propiedad IsSelected.
Como está utilizando un enlace bidireccional, habría pensado que los elementos podrían seleccionarse a través de la interfaz de usuario y la colección de elementos ViewModels.
Digamos que selecciono elementos a través de la interfaz de usuario, esto funciona bien. Puedo seleccionar un elemento singular y luego usar [shift] para seleccionar un rango, luego usar [ctrl] seleccionar algunos elementos más. La anulación de la selección de elementos también funciona correctamente.
Sin embargo, selecciono un grupo a través de la colección. Digamos al hacer clic en un botón (como hago en el código a continuación), se seleccionan muchos elementos. Cuando me desplazo hacia abajo DataGrid, algunos se seleccionan como deberían ser y otros no. Si selecciono un elemento a través de la interfaz de usuario, solo algunos de los elementos se deseleccionan y algunos permanecen seleccionados, todo es un poco funky. Incluso el botón Seleccionar todo en la esquina superior izquierda no funciona del todo bien.
Código Todo el código está debajo, en la parte inferior está la vista, la pieza clave es el estilo DataGridRow con el enlace IsSelected.
Aquí está mi clase de usuario:
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));
}
}
}
Aquí está el UserViewModel, donde reside IsSelected:
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));
}
}
}
Aquí está mi 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);
}
}
}
Aquí está el repositorio de usuarios donde creo los usuarios:
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);
}
}
}
Y finalmente aquí está la vista para todos los usuarios:
<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>
Recuerdo haber tenido este problema con mi DataGrid cuando me desplazo. La razón por la que causaba problemas extraños era debido a la virtualización. Intente configurar la virtualización en False en su DataGrid.
<DataGrid EnableRowVirtualization="False">
Deberá capturar el evento Scroll y luego adjuntarlo a su generador de contenedor de elementos para obtener el elemento generado mientras se desplaza. Cuando se carga Item, puede actualizar el Enlace (utilizando BingingExpression) al elemento para que refleje el estado de su objeto, es decir, IsSelected
Este es el problema con el Recycling
VirtualizingStackpanel
Recycling
. Establecer VirtualizingStackPanel.VirtualizationMode="Standard"
en Datagrid
resolverá esto.
Hank, su problema es que la Cuadrícula solo enlazará las Rows
visibles de la Cuadrícula de DataGrid
y no actualizará la Vinculación tan pronto como otra Fila sea visible.
Este problema es causado por la Data Virtualization
.
Intente configurar la virtualización como Standard
VirtualizingStackPanel.VirtualizationMode="Standard"
Creo que esto resolverá tu problema.
Madeja
Desactivar la virtualización definitivamente solucionará su problema a un costo de rendimiento.
podría desactivar el reciclaje de VirtualizingStackPanel como lo sugirió @nit, pero eso no resolverá el problema por completo.
Un enfoque diferente es usar comportamientos vinculados.
hay una solución de trabajo probada por @Samuel Jack (la usé antes), es para listbox y MVVM y funciona muy bien. los elementos seleccionados son vinculables en modo bidireccional.
http://blog.functionalfun.net/2009/02/how-to-databind-to-selecteditems.html
Como es para ListBox, cargué una solución modificada para DataGrid, es una DataGrid básica con la funcionalidad deseada.
https://www.sugarsync.com/pf/D6837746_80955217_331798
Disfrutar