Reputation: 2012
I am binding a WPF Datagrid's ItemsSource property to an ObservableCollection. The data gets pulled in fine but I notice strange behavior when I click on a cell (any cell) and start using the keyboard to navigate.
I believe I've isolated the incident to the bound ViewModel class (as opposed to the XAML). In preliminary debugging I've stripped the XAML down to the bare minimum (no styles, 2 columns, only binding is the ItemsSource) and still get the weird behavior.
<UserControl x:Name="FuelMileageView" x:Class="TRD.RaceStrategy.Views.FuelMileage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TRD.RaceStrategy"
xmlns:views="clr-namespace:TRD.RaceStrategy.Views"
xmlns:vm="clr-namespace:TRD.RaceStrategy.ViewModels;assembly=RaceStrategy.Support"
xmlns:converters="clr-namespace:TRD.RaceStrategy.Converters;assembly=RaceStrategy.Support"
xmlns:Behaviours="clr-namespace:TRD.RaceStrategy.Behaviours;assembly=RaceStrategy.Support"
mc:Ignorable="d" DataContext="{Binding Path=Properties[PrimaryVM].CarEventVM.FuelMileage.FuelMileageLaps, Source={x:Static local:App.Current}}"
d:DesignHeight="410" d:DesignWidth="485">
<DataGrid x:Name="dgFMLaps" ItemsSource="{Binding}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Column 1" >
</DataGridTextColumn>
<DataGridTextColumn Header="Column 2" >
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</UserControl>
Aside from the InitializeComponent() call there is no codebehind to speak of which seems to leave the bound FuelMileageLaps collection as the only culprit.
public class FuelMileageLapViewModel : LapViewModel
{
public FuelMileageLapViewModel() { }
}
NOTE: ObservableCollectionEx is an extension of the ObservableCollection class which apparently accounts for threading problems (?) I've used this class with other collections that I've in turn plugged into datagrids that didn't have this keyboard navigation problem.
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
// Override the event so this class can access it
public override event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// Be nice - use BlockReentrancy like MSDN said
using (BlockReentrancy())
{
System.Collections.Specialized.NotifyCollectionChangedEventHandler eventHandler = CollectionChanged;
if (eventHandler == null)
return;
Delegate[] delegates = eventHandler.GetInvocationList();
// Walk thru invocation list
foreach (System.Collections.Specialized.NotifyCollectionChangedEventHandler handler in delegates)
{
DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
// If the subscriber is a DispatcherObject and different thread
if (dispatcherObject != null && dispatcherObject.CheckAccess() == false)
{
// Invoke handler in the target dispatcher's thread
dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, this, e);
}
else // Execute handler as is
handler(this, e);
}
}
}
}
At this point I'm at my wit's end as to the next step for debugging. There's no obvious place to throw in a breakpoint or try/catch block. I've Googled my problem to death and haven't found anything worthwhile. Please help!
Here's the FuelMileageViewModel class where FuelMileageLaps is initialized:
public class FuelMileageViewModel : WorkspaceViewModel
{
/// <summary>
/// View model for the fuel mileage calculator
/// </summary>
/// <param name="car">Car on which to base all calculations</param>
public FuelMileageViewModel()
{}
/// <summary>
/// A separate collection of laps that store the extra fuel mileage data
/// </summary>
public ObservableCollectionEx<FuelMileageLapViewModel> FuelMileageLaps
{
get
{
if (_fuelMileageLaps == null)
{
_fuelMileageLaps = new ObservableCollectionEx<FuelMileageLapViewModel>();
}
return _fuelMileageLaps;
}
set
{
_fuelMileageLaps = value;
OnPropertyChanged("FuelMileageLaps");
}
}
private ObservableCollectionEx<FuelMileageLapViewModel> _fuelMileageLaps;
/// <summary>
/// Number of laps in the race
/// Affects: Laps_G, Laps_Y
/// </summary>
public int NumberOfLaps
{
get
{
return FuelMileageLaps.Count;
}
set
{
int count = FuelMileageLaps.Count;
if (value < 0)
{
throw new ArgumentException("Number of laps must be a positive integer");
}
if (count != value)
{
if( count < value )
{
int diff = value - count;
for (int i = 0; i < diff; i++)
{
FuelMileageLapViewModel lapToAdd = new FuelMileageLapViewModel();
FuelMileageLaps.Add(lapToAdd);
}
}
OnPropertyChanged("NumberOfLaps");
}
}
}
}
Upvotes: 2
Views: 2077
Reputation: 1065
I know this is an older question, but I was having VERY similar problems with a DataGrid recently. The arrow key problems were just as you described. However, I used DataGridTemplateColumns in which a TextBox allowed for editing (otherwise, as you noted, I couldn't edit the data). If I edited a cell and then clicked out of it (particularly if I clicked the row above it) the DataGrid would often produce a duplicate row (though the count in the collection remained the same).
In my case, I found a bug in my Equals(object obj) method for the class whose members were in the ObservableCollection being bound to the DataGrid. This prevented calls to Equals from determining the true equality between two items, and somehow this caused big issues. For example, if after editing a row the data grid can't find that same row already in its list (via calls to a buggy Equals method), perhaps it generates another row (assuming it must not have a row yet for that entry).
In any case, I'd suggest checking out the Equals method in your LapViewModel.
Hope that helps.
Upvotes: 0