Problem
I have a WPF Toolkit DataGrid
, and I'd like to be able to switch among several preset column orders. This is an MVVM project, so the column orders are stored in a ViewModel
. The problem is, I can't get bindings to work for the DisplayIndex
property. No matter what I try, including the sweet method in this Josh Smith tutorial, I get:
The DisplayIndex for the DataGridColumn with Header 'ID' is out of range. DisplayIndex must be greater than or equal to 0 and less than Columns.Count. Parameter name: displayIndex. Actual value was -1.
Is there any workaround for this?
I'm including my test code below. Please let me know if you see any problems with it.
ViewModel code
public class MainViewModel
{
public List<Plan> Plans { get; set; }
public int IdDisplayIndex { get; set; }
public int NameDisplayIndex { get; set; }
public int DescriptionDisplayIndex { get; set; }
public MainViewModel()
{
Initialize();
}
private void Initialize()
{
IdDisplayIndex = 1;
NameDisplayIndex = 2;
DescriptionDisplayIndex = 0;
Plans = new List<Plan>
{
new Plan { Id = 1, Name = "Primary", Description = "Likely to work." },
new Plan { Id = 2, Name = "Plan B", Description = "Backup plan." },
new Plan { Id = 3, Name = "Plan C", Description = "Last resort." }
};
}
}
Plan Class
public class Plan
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Window code - this uses Josh Smith's DataContextSpy
<Window
x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:mwc="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="Main Window" Height="300" Width="300">
<Grid>
<mwc:DataGrid ItemsSource="{Binding Plans}" AutoGenerateColumns="False">
<mwc:DataGrid.Resources>
<local:DataContextSpy x:Key="spy" />
</mwc:DataGrid.Resources>
<mwc:DataGrid.Columns>
<mwc:DataGridTextColumn
Header="ID"
Binding="{Binding Id}"
DisplayIndex="{Binding Source={StaticResource spy}, Path=DataContext.IdDisplayIndex}" />
<mwc:DataGridTextColumn
Header="Name"
Binding="{Binding Name}"
DisplayIndex="{Binding Source={StaticResource spy}, Path=DataContext.NameDisplayIndex}" />
<mwc:DataGridTextColumn
Header="Description"
Binding="{Binding Description}"
DisplayIndex="{Binding Source={StaticResource spy}, Path=DataContext.DescriptionDisplayIndex}" />
</mwc:DataGrid.Columns>
</mwc:DataGrid>
</Grid>
</Window>
Note: If I just use plain numbers for DisplayIndex
, everything works fine, so the problem is definitely with the bindings.
Update 5/1/2010
I was just doing a little maintenance on my project, and I noticed that when I ran it, the problem I discuss in this post had returned. I knew that it worked last time I ran it, so I eventually narrowed the problem down to the fact that I had installed a newer version of the WPF Toolkit (Feb '10). When I reverted to the June '09 version, everything worked fine again. So, I'm now doing something I should have done in the first place: I'm including the WPFToolkit.dll that works in my solution folder and checking it into version control. It's unfortunate, though, that the newer toolkit has a breaking change.
Set FallbackValue=<idx>
in your DisplayIndex binding, where <idx>
is your column's index. For example:
DisplayIndex="{Binding Source={StaticResource spy}, Path=DataContext.IdDisplayIndex, FallbackValue=0}" />
(My previous answer was way off track - so I deleted it - I'll try to answer again after reproducing the error on my machine)
The problem you encounter stems from the fact that when the bindings are evaluated for the first time, the DataContext
property is still set to null
. This, for some strange reason, yet unknown to me, causes the binding to evaluate at -1
. However if you make sure to set the DataContext
before the bindings are evaluated you'll be ok. Unfortunately this might only be possible in code-behind - hence during run-time, and not in design time. So in design time I still don't know how to circumvent the error.
To do that, set the DataContext
property of the window before the call to InitializeComponent
thus:
public MainWindow()
{
DataContext = new MainViewModel();
InitializeComponent();
}
Hope this helps.
I'm adding this as an answer since I can't comment yet. The reason why the value of the DisplayIndex is set to -1 is :
The DisplayIndex property has a default value of -1 before it is added to the DataGrid.Columns collection. This value is updated when the column is added to the DataGrid.
The DataGrid requires that the DisplayIndex property of each column must be a unique integer from 0 to the Count of Columns -1. Therefore, when the DisplayIndex of one column changes, the change typically causes the DisplayIndex of other columns to also change.
The restrictions on the DisplayIndex value are enforced by a ValidateValueCallback mechanism. If you attempt to set a value that is not valid, a run-time exception is thrown.
When the value of the DisplayIndex property is changed, the DataGrid.ColumnDisplayIndexChanged event is raised.
Source: http://msdn.microsoft.com/en-us/library/vstudio/system.windows.controls.datagridcolumn.displayindex(v=vs.100).aspx
So I suppose that it is possible to check this value when it is changed using the aforementioned event.
The solution mentioned by Aviad didn't work for me. I have my Datagrid in a UserControl, same as the OP, and changing the order of the ViewModel
constructor, the DataContext
declaration, and the InitializeComponent()
method didn't help.
Pakman's FallbackValue
method worked very well, on the other hand.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With