Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using bindings to control column order in a DataGrid

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.

like image 728
devuxer Avatar asked Jan 24 '10 07:01

devuxer


3 Answers

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}" />
like image 69
Pakman Avatar answered Nov 10 '22 08:11

Pakman


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

like image 42
Aviad P. Avatar answered Nov 10 '22 06:11

Aviad P.


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.

like image 44
Noxxys Avatar answered Nov 10 '22 07:11

Noxxys