Reputation: 129
I'm binding DataGridCoulmun's DisplayIndex to my ViewModel. Since DataGridColumns doesn't belong to DataGrid's visual or lgical tree, i had to do some some tricks to achieve that binding, but it works.
My problem is: When DataContext is changed (if we have more ViewModels), DataGridColumns shoud get new DisplayIndexes. Unfortunately the behaviour is strange and after the change, the column order is more or less random. Do you have any idea how to handle this problem or at least what is the cause?
Here is a example:
Before initializing the datagrid I set the DataContext to the new instance of the ViewModel and it works as it should. After that i reorder the columns and it still works and the changes are propagated to the ViewModel correctly. Finally I click the button, which set the DataContext to the new instance of the ViewModel, so the columns after click should be ordered as at the beginning.
Here is a XAML code:
<Window x:Class="TestDataGridBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestDataGridBinding"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn
Header="A"
Binding="{Binding A}"
DisplayIndex="{Binding Path=Data.ADisplayIndex, FallbackValue=0, Mode=TwoWay, Source={StaticResource proxy}}"/>
<DataGridTextColumn
Header="B"
Binding="{Binding B}"
DisplayIndex="{Binding Path=Data.BDisplayIndex, FallbackValue=1, Mode=TwoWay, Source={StaticResource proxy}}"/>
<DataGridTextColumn
Header="C"
Binding="{Binding C}"
DisplayIndex="{Binding Path=Data.CDisplayIndex, FallbackValue=2, Mode=TwoWay, Source={StaticResource proxy}}"/>
<DataGridTextColumn
Header="D"
Binding="{Binding D}"
DisplayIndex="{Binding Path=Data.DDisplayIndex, FallbackValue=3, Mode=TwoWay, Source={StaticResource proxy}}"/>
</DataGrid.Columns>
</DataGrid>
<Button Click="Button_Click" Width="70" Height="30" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="10" Content="Click me"/>
</Grid>
</Window>
Here is a Code-behind
public partial class MainWindow : Window
{
ViewModel _model;
public MainWindow()
{
_model = new ViewModel();
_model.Items.Add(new Item("x","y","z","zz"));
DataContext = _model;
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_model = new ViewModel();
_model.Items.Add(new Item("xx", "y", "zz", "zzz"));
DataContext = _model;
}
}
[Serializable]
public class ViewModel : INotifyPropertyChanged
{
#region notifications
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
#endregion
#region private and default values
private int _a = 3;
private int _b = 2;
private int _c = 1;
private int _d = 0;
private ObservableCollection<Item> _items = new ObservableCollection<Item>();
#endregion
#region public
public int ADisplayIndex { get { return _a; } set { _a = value; NotifyPropertyChanged("ADisplayIndex"); } }
public int BDisplayIndex { get { return _b; } set { _b = value; NotifyPropertyChanged("BDisplayIndex"); } }
public int CDisplayIndex { get { return _c; } set { _c = value; NotifyPropertyChanged("CDisplayIndex"); } }
public int DDisplayIndex { get { return _d; } set { _d = value; NotifyPropertyChanged("DDisplayIndex"); } }
public ObservableCollection<Item> Items
{ get { return _items; } set { _items = value; NotifyPropertyChanged("Items"); } }
#endregion
}
public class Item
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
public string D { get; set; }
public Item(string a, string b, string c, string d)
{
A = a; B = b; C = c; D = d;
}
}
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
Trick with proxy was found here: http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
Upvotes: 0
Views: 3162
Reputation: 9
As Alan Singfield mentioned, the internal UpdateDisplayIndexForChangedColumn method updates the DisplayIndex of the affected columns. Instead of directly updating the columns, you can sort the columns by their original DisplayIndex, and then update the DisplayIndex of the corresponding columns in that order. The code is as follows:
var sortedColumns = Columns.OrderBy(i => i.DisplayIndex).ToList();
// Rather than foreach (var column in this.Columns)
foreach (var column in sortedColumns)
{
var setting = result.FirstOrDefault(i => i.ShowedName == column.Header.ToString());
if (setting == null)
continue;
column.CanUserReorder = setting.CanUserReorder;
column.Visibility = setting.Visibility;
column.DisplayIndex = setting.DisplayIndex;
}
This approach ensures that the DisplayIndex values are updated in the correct order, avoiding potential issues with column reordering. Hope this helps those encountering a similar problem
Upvotes: 0
Reputation: 176
The DataGrid automatically renumbers the DisplayIndex of all the other columns when you adjust the first.
E.g. if you set your second column to DisplayIndex = 0, it will give the first column DisplayIndex = 1.
See UpdateDisplayIndexForChangedColumn() in https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/DataGridColumnCollection.cs
This is going to play havoc with your bindings unless you change them in ascending order. You'd need some kind of custom NotifyPropertyChanged logic which sets all of the new desired index values on your model first, without raising NotifyPropertyChanged, then raises NotifyPropertyChanged events in ascending numerical order.
eg.
_aDisplayIndex = 2;
_bDisplayIndex = 1;
_cDisplayIndex = 0;
_dDisplayIndex = 3;
NotifyPropertyChanged("CDisplayIndex");
NotifyPropertyChanged("BDisplayIndex");
NotifyPropertyChanged("ADisplayIndex");
NotifyPropertyChanged("DDisplayIndex");
Also watch out for two-way binding, in this case you might want to re-write the values of _*DisplayIndex between each NotifyPropertyChanged call.
Upvotes: 2