Currently I am working on WPF application (with MVVM) in which I am displaying data in the DataGridView.
<DataGrid RowHeaderWidth="0" ItemsSource="{Binding PartsList,UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Item Name" IsReadOnly="True" Width="*" Binding="{Binding ProductName}"></DataGridTextColumn>
<DataGridTextColumn Header="Model Name" IsReadOnly="True" Width="*" Binding="{Binding CarModelName}"></DataGridTextColumn>
<DataGridTextColumn Header="Company Name" IsReadOnly="True" Width="*" Binding="{Binding CompanName}"></DataGridTextColumn>
<DataGridTextColumn Header="Price" IsReadOnly="True" Width="*" Binding="{Binding Rate}">
</DataGrid.Columns>
</DataGrid>
Here PartsList is an ObservableCollection of entity Part.
Now I want to add custom column to the DataGrid which shows discount and another column which shows net amount. How can I do this?
Please give a good idea to do this as I need to work with thousands of records so performance is very important for me.
Thank you in advance.
Try to add columns on the Loaded event of DataGrid:
private void DataGrid_Loaded_1(object sender, RoutedEventArgs e)
{
dataGrid.Columns.Add((DataGridTextColumn)this.Resources["DiscountColumn"]);
dataGrid.Columns.Add((DataGridTextColumn)this.Resources["NetAmountColumn"]);
//Alternatively you can create columns in .cs like
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "Dicount", Binding = new Binding("Discount") });
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "Net Amount", Binding = new Binding("NetAmount") });
}
<Window.Resources>
<DataGridTextColumn x:Key="DiscountColumn" Header="Discount" IsReadOnly="True" Width="*" Binding="{Binding Discount}"/>
<DataGridTextColumn x:Key="NetAmountColumn" Header="Net Amount" IsReadOnly="True" Width="*" Binding="{Binding NetAmount}"/>
</Window.Resources>
<DataGrid RowHeaderWidth="0" x:Name="dataGrid" Loaded="DataGrid_Loaded_1" />
This is an old post but I have done something similar using MVVM and WPF so thought I would through my two pennies worth in.
I cannot give any real indication of how it will perform however we haven't seen any real issues with displaying around a thousand objects in the ItemSource.
Apologies it is going to be a lengthy display of code but I will try to break it down so it is easy to follow.
Ultimately what you need to do is create an Attached Behavior.
Here is mine:
Behavior class
This does the core work in creating the actual columns and add them to your DataGrid based on the ColumnsSource that you bind to it.
public class DataGridColumnCollectionBehavior
{
private object columnsSource;
private DataGrid dataGrid;
public DataGridColumnCollectionBehavior(DataGrid dataGrid)
{
this.dataGrid = dataGrid;
}
public object ColumnsSource
{
get { return this.columnsSource; }
set
{
object oldValue = this.columnsSource;
this.columnsSource = value;
this.ColumnsSourceChanged(oldValue, this.columnsSource);
}
}
public string DisplayMemberFormatMember { get; set; }
public string DisplayMemberMember { get; set; }
public string FontWeightBindingMember { get; set; }
public string FontWeightMember { get; set; }
public string HeaderTextMember { get; set; }
public string IsEditableMember { get; set; }
public string SortMember { get; set; }
public string TextAlignmentMember { get; set; }
public string TextColourMember { get; set; }
public string WidthMember { get; set; }
private void AddHandlers(ICollectionView collectionView)
{
collectionView.CollectionChanged += this.ColumnsSource_CollectionChanged;
}
private void ColumnsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ICollectionView view = sender as ICollectionView;
if (this.dataGrid == null)
{
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
for (int i = 0; i < e.NewItems.Count; i++)
{
DataGridColumn column = CreateColumn(e.NewItems[i]);
dataGrid.Columns.Insert(e.NewStartingIndex + i, column);
}
break;
case NotifyCollectionChangedAction.Move:
List<DataGridColumn> columns = new List<DataGridColumn>();
for (int i = 0; i < e.OldItems.Count; i++)
{
DataGridColumn column = dataGrid.Columns[e.OldStartingIndex + i];
columns.Add(column);
}
for (int i = 0; i < e.NewItems.Count; i++)
{
DataGridColumn column = columns[i];
dataGrid.Columns.Insert(e.NewStartingIndex + i, column);
}
break;
case NotifyCollectionChangedAction.Remove:
for (int i = 0; i < e.OldItems.Count; i++)
{
dataGrid.Columns.RemoveAt(e.OldStartingIndex);
}
break;
case NotifyCollectionChangedAction.Replace:
for (int i = 0; i < e.NewItems.Count; i++)
{
DataGridColumn column = CreateColumn(e.NewItems[i]);
dataGrid.Columns[e.NewStartingIndex + i] = column;
}
break;
case NotifyCollectionChangedAction.Reset:
dataGrid.Columns.Clear();
CreateColumns(sender as ICollectionView);
break;
default:
break;
}
}
private void ColumnsSourceChanged(object oldValue, object newValue)
{
if (this.dataGrid != null)
{
dataGrid.Columns.Clear();
if (oldValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
if (view != null)
{
this.RemoveHandlers(view);
}
}
if (newValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
if (view != null)
{
this.AddHandlers(view);
this.CreateColumns(view);
}
}
}
}
private DataGridColumn CreateColumn(object columnSource)
{
DataGridColumn column = new DataGridTemplateColumn();
var textBlockFactory = new FrameworkElementFactory(typeof(TextBlock));
((DataGridTemplateColumn)column).CellTemplate = new DataTemplate { VisualTree = textBlockFactory };
textBlockFactory.SetValue(TextBlock.MarginProperty, new Thickness(3));
if (!string.IsNullOrWhiteSpace(this.FontWeightBindingMember))
{
string propertyName = GetPropertyValue(columnSource, this.FontWeightBindingMember) as string;
textBlockFactory.SetBinding(TextBlock.FontWeightProperty, new Binding(propertyName));
}
else if (!string.IsNullOrWhiteSpace(this.FontWeightMember))
{
textBlockFactory.SetValue(TextBlock.FontWeightProperty, (FontWeight)GetPropertyValue(columnSource, this.FontWeightMember));
}
if (!string.IsNullOrWhiteSpace(this.SortMember))
{
column.SortMemberPath = GetPropertyValue(columnSource, this.SortMember) as string;
}
if (!string.IsNullOrEmpty(this.DisplayMemberMember))
{
string propertyName = GetPropertyValue(columnSource, this.DisplayMemberMember) as string;
string format = null;
if (!string.IsNullOrEmpty(this.DisplayMemberFormatMember))
{
format = GetPropertyValue(columnSource, this.DisplayMemberFormatMember) as string;
}
if (string.IsNullOrEmpty(format))
{
format = "{0}";
}
textBlockFactory.SetBinding(TextBlock.TextProperty, new Binding(propertyName) { StringFormat = format });
// If there is no sort member defined default to the display member.
if (string.IsNullOrWhiteSpace(column.SortMemberPath))
{
column.SortMemberPath = propertyName;
}
}
if (!string.IsNullOrWhiteSpace(this.TextAlignmentMember))
{
textBlockFactory.SetValue(TextBlock.TextAlignmentProperty, GetPropertyValue(columnSource, this.TextAlignmentMember));
}
if (!string.IsNullOrEmpty(this.HeaderTextMember))
{
column.Header = GetPropertyValue(columnSource, this.HeaderTextMember);
}
if (!string.IsNullOrWhiteSpace(this.TextColourMember))
{
string propertyName = GetPropertyValue(columnSource, this.TextColourMember) as string;
textBlockFactory.SetBinding(TextBlock.ForegroundProperty, new Binding(propertyName));
}
if (!string.IsNullOrEmpty(this.WidthMember))
{
double width = (double)GetPropertyValue(columnSource, this.WidthMember);
column.Width = width;
}
return column;
}
private void CreateColumns(ICollectionView collectionView)
{
foreach (object item in collectionView)
{
DataGridColumn column = this.CreateColumn(item);
this.dataGrid.Columns.Add(column);
}
}
private object GetPropertyValue(object obj, string propertyName)
{
object returnVal = null;
if (obj != null)
{
PropertyInfo prop = obj.GetType().GetProperty(propertyName);
if (prop != null)
{
returnVal = prop.GetValue(obj, null);
}
}
return returnVal;
}
private void RemoveHandlers(ICollectionView collectionView)
{
collectionView.CollectionChanged -= this.ColumnsSource_CollectionChanged;
}
}
Accessor class
This is the class that you use within your XAML files in order to create the Bindings.
public static class DataGridColumnCollection
{
public static readonly DependencyProperty ColumnCollectionBehaviourProperty =
DependencyProperty.RegisterAttached("ColumnCollectionBehaviour", typeof(DataGridColumnCollectionBehaviour), typeof(DataGridColumnCollection), new UIPropertyMetadata(null));
public static readonly DependencyProperty ColumnsSourceProperty =
DependencyProperty.RegisterAttached("ColumnsSource", typeof(object), typeof(DataGridColumnCollection), new UIPropertyMetadata(null, DataGridColumnCollection.ColumnsSourcePropertyChanged));
public static readonly DependencyProperty DisplayMemberFormatMemberProperty =
DependencyProperty.RegisterAttached("DisplayMemberFormatMember", typeof(string), typeof(DataGridColumnCollection), new UIPropertyMetadata(null, DataGridColumnCollection.DisplayMemberFormatMemberChanged));
public static readonly DependencyProperty DisplayMemberMemberProperty =
DependencyProperty.RegisterAttached("DisplayMemberMember", typeof(string), typeof(DataGridColumnCollection), new UIPropertyMetadata(null, DataGridColumnCollection.DisplayMemberMemberChanged));
public static readonly DependencyProperty FontWeightBindingMemberProperty =
DependencyProperty.RegisterAttached("FontWeightBindingMember", typeof(string), typeof(DataGridColumnCollection), new UIPropertyMetadata(null, DataGridColumnCollection.FontWeightBindingMemberChanged));
public static readonly DependencyProperty FontWeightMemberProperty =
DependencyProperty.RegisterAttached("FontWeightMember", typeof(string), typeof(DataGridColumnCollection), new UIPropertyMetadata(null, DataGridColumnCollection.FontWeightMemberChanged));
public static readonly DependencyProperty IsEditableMemberProperty =
DependencyProperty.RegisterAttached("IsEditableMember", typeof(string), typeof(DataGridColumnCollection), new UIPropertyMetadata(null, DataGridColumnCollection.IsEditableMemberChanged));
public static readonly DependencyProperty HeaderTextMemberProperty =
DependencyProperty.RegisterAttached("HeaderTextMember", typeof(string), typeof(DataGridColumnCollection), new UIPropertyMetadata(null, DataGridColumnCollection.HeaderTextMemberChanged));
public static readonly DependencyProperty SortMemberProperty =
DependencyProperty.RegisterAttached("SortMember", typeof(string), typeof(DataGridColumnCollection), new UIPropertyMetadata(null, DataGridColumnCollection.SortMemberChanged));
public static readonly DependencyProperty TextAlignmentMemberProperty =
DependencyProperty.RegisterAttached("TextAlignmentMember", typeof(string), typeof(DataGridColumnCollection), new UIPropertyMetadata(null, DataGridColumnCollection.TextAlignmentMemberChanged));
public static readonly DependencyProperty TextColourMemberProperty =
DependencyProperty.RegisterAttached("TextColourMember", typeof(string), typeof(DataGridColumnCollection), new UIPropertyMetadata(null, DataGridColumnCollection.TextColourMemberChanged));
public static readonly DependencyProperty WidthMemberProperty =
DependencyProperty.RegisterAttached("WidthMember", typeof(string), typeof(DataGridColumnCollection), new UIPropertyMetadata(null, DataGridColumnCollection.WidthMemberChanged));
public static DataGridColumnCollectionBehaviour GetColumnCollectionBehaviour(DependencyObject obj)
{
return (DataGridColumnCollectionBehaviour)obj.GetValue(ColumnCollectionBehaviourProperty);
}
public static void SetColumnCollectionBehaviour(DependencyObject obj, DataGridColumnCollectionBehaviour value)
{
obj.SetValue(ColumnCollectionBehaviourProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(DataGrid))]
public static object GetColumnsSource(DependencyObject obj)
{
return (object)obj.GetValue(ColumnsSourceProperty);
}
[AttachedPropertyBrowsableForType(typeof(DataGrid))]
public static void SetColumnsSource(DependencyObject obj, ObservableCollection<DataGridColumn> value)
{
obj.SetValue(ColumnsSourceProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static string GetDisplayMemberFormatMember(DependencyObject obj)
{
return (string)obj.GetValue(DisplayMemberFormatMemberProperty);
}
public static void SetDisplayMemberFormatMember(DependencyObject obj, string value)
{
obj.SetValue(DisplayMemberFormatMemberProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static string GetDisplayMemberMember(DependencyObject obj)
{
return (string)obj.GetValue(DisplayMemberMemberProperty);
}
public static void SetDisplayMemberMember(DependencyObject obj, string value)
{
obj.SetValue(DisplayMemberMemberProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static string GetFontWeightBindingMember(DependencyObject obj)
{
return (string)obj.GetValue(FontWeightBindingMemberProperty);
}
public static void SetFontWeightBindingMember(DependencyObject obj, string value)
{
obj.SetValue(FontWeightBindingMemberProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static string GetFontWeightMember(DependencyObject obj)
{
return (string)obj.GetValue(FontWeightMemberProperty);
}
public static void SetFontWeightMember(DependencyObject obj, string value)
{
obj.SetValue(FontWeightMemberProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static string GetTextAlignmentMember(DependencyObject obj)
{
return (string)obj.GetValue(TextAlignmentMemberProperty);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static void SetTextAlignmentMember(DependencyObject obj, string value)
{
obj.SetValue(TextAlignmentMemberProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static string GetTextColourMember(DependencyObject obj)
{
return (string)obj.GetValue(TextColourMemberProperty);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static void SetTextColourMember(DependencyObject obj, string value)
{
obj.SetValue(TextColourMemberProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(DataGrid))]
public static string GetHeaderTextMember(DependencyObject obj)
{
return (string)obj.GetValue(HeaderTextMemberProperty);
}
[AttachedPropertyBrowsableForType(typeof(DataGrid))]
public static void SetHeaderTextMember(DependencyObject obj, string value)
{
obj.SetValue(HeaderTextMemberProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static string GetWidthMember(DependencyObject obj)
{
return (string)obj.GetValue(WidthMemberProperty);
}
public static void SetWidthMember(DependencyObject obj, string value)
{
obj.SetValue(WidthMemberProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static string GetSortMember(DependencyObject obj)
{
return (string)obj.GetValue(SortMemberProperty);
}
public static void SetSortMember(DependencyObject obj, string value)
{
obj.SetValue(SortMemberProperty, value);
}
private static void ColumnsSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DataGridColumnCollection.GetOrCreateBehaviour(sender).ColumnsSource = e.NewValue;
}
private static void DisplayMemberFormatMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DataGridColumnCollection.GetOrCreateBehaviour(sender).DisplayMemberFormatMember = e.NewValue as string;
}
private static void DisplayMemberMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DataGridColumnCollection.GetOrCreateBehaviour(sender).DisplayMemberMember = e.NewValue as string;
}
private static void FontWeightBindingMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DataGridColumnCollection.GetOrCreateBehaviour(sender).FontWeightBindingMember = e.NewValue as string;
}
private static void FontWeightMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DataGridColumnCollection.GetOrCreateBehaviour(sender).FontWeightMember = e.NewValue as string;
}
private static void IsEditableMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DataGridColumnCollection.GetOrCreateBehaviour(sender).IsEditableMember = e.NewValue as string;
}
private static void HeaderTextMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DataGridColumnCollection.GetOrCreateBehaviour(sender).HeaderTextMember = e.NewValue as string;
}
private static void SortMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DataGridColumnCollection.GetOrCreateBehaviour(sender).SortMember = e.NewValue as string;
}
private static void TextAlignmentMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DataGridColumnCollection.GetOrCreateBehaviour(sender).TextAlignmentMember = e.NewValue as string;
}
private static void TextColourMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DataGridColumnCollection.GetOrCreateBehaviour(sender).TextColourMember = e.NewValue as string;
}
private static void WidthMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DataGridColumnCollection.GetOrCreateBehaviour(sender).WidthMember = e.NewValue as string;
}
private static DataGridColumnCollectionBehaviour GetOrCreateBehaviour(DependencyObject source)
{
DataGridColumnCollectionBehaviour behaviour = DataGridColumnCollection.GetColumnCollectionBehaviour(source);
if (behaviour == null)
{
behaviour = new DataGridColumnCollectionBehaviour(source as DataGrid);
DataGridColumnCollection.SetColumnCollectionBehaviour(source, behaviour);
}
return behaviour;
}
}
Example XAML usage
Now we actually get on to using it.
<DataGrid behaviors:DataGridColumnCollection.ColumnsSource="{Binding ColumnHeaders}"
behaviors:DataGridColumnCollection.DisplayMemberFormatMember="Format" behaviors:DataGridColumnCollection.DisplayMemberMember="DisplayMember"
behaviors:DataGridColumnCollection.FontWeightBindingMember="FontWeightMember"
behaviors:DataGridColumnCollection.HeaderTextMember="Header"
behaviors:DataGridColumnCollection.SortMember="SortMember"
behaviors:DataGridColumnCollection.TextAlignmentMember="TextAlignment"
behaviors:DataGridColumnCollection.TextColourMember="TextColourMember"
behaviors:DataGridColumnCollection.WidthMember="Width"
ItemsSource="{Binding Items}">
Column Header class
This is just my 'simple' class that describes a column.
public class ColumnHeaderDescriptor
{
public string DisplayMember { get; set; }
public string FontWeightMember { get; set; }
public string Format { get; set; }
public string Header { get; set; }
public string SortMember { get; set; }
public TextAlignment TextAlignment { get; set; }
public string TextColourMember { get; set; }
public double Width { get; set; }
}
Instantiating the columns
And this is how I create them.
this.ColumnHeaders.Add(new ColumnHeaderDescriptor { Header = Properties.Resources.Name, DisplayMember = "ItemName", Width = 250 });
this.ColumnHeaders.Add(new ColumnHeaderDescriptor { Header = Properties.Resources.ManufPartNumber, DisplayMember = "ManufPartNumber", Width = 150 });
this.ColumnHeaders.Add(new ColumnHeaderDescriptor { Header = Properties.Resources.LastUnitPrice, DisplayMember = "UnitPriceString", SortMember = "UnitPrice", Width = 90 });
this.ColumnHeaders.Add(new ColumnHeaderDescriptor { Header = Properties.Resources.AssetType, DisplayMember = "AssetType", Width = 100 });
this.ColumnHeaders.Add(new ColumnHeaderDescriptor { Header = Properties.Resources.Quantity, DisplayMember = "QuantityString", SortMember = "Quantity", Width = 80 });
Conclusion
Now I appreciate that this may not be fully MVVM but at the end of the day we have to make sacrifices in order to get the job done. This will allow you to dynamically create columns and show different pieces of information from within your View Model.
My solution is quite a complicated one and I can't take full credit. I am sure I got the starting point from an existing StackOverflow answer however I am at a loss to know where that is now. Given it's complexity by allowing the consumer to determine lots of different things like text colour, etc. this could well be overkill for others solutions and you could remove the unnecessary properties should you not need them.
Assuming you are using Entity Framework for your model; create a partial class of your model with property getters that calculate based on the base class. Ensure that you implement INotifyPropertyChange and then bind the new properties of NetAmount and Discount in new columns.
Related
I use this codes. In this codes, I want to change each row's background color when I click the button.
I tried to bind with DataGridRow's background. But I only get BindingExpression.
I know that if I use ObservableCollection as rowdata, it will slove very easy. But I cannot use the collection because I want to bind each column's visibility, too.
I cannot slove to this problem with this code? Please some help.
<StackPanel>
<CheckBox IsChecked="{Binding IsChecked}" Content="I change the header of Column A and I hide Column B." Margin="10"/>
<Button Content="Click!" Click="Button_OnClick" Margin="10" Width="50"/>
<DataGrid IsReadOnly="True"
ItemsSource="{Binding Table}"
AutoGeneratingColumn="DataGrid_OnAutoGeneratingColumn"
x:Name="DataGrid1"
Loaded="DataGrid1_Loaded">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="{Binding MyBackground, Mode=TwoWay}"/>
</Style>
</DataGrid.RowStyle>
</DataGrid>
</StackPanel>
public partial class MainWindow : Window
{
public ViewModel _viewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = _viewModel;
}
private void DataGrid_OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
BindingOperations.SetBinding(e.Column,
DataGridColumn.HeaderProperty,
new Binding($"{nameof(ViewModel.ColumnHeader)}[{e.PropertyName}]")
{
Source = _viewModel
});
BindingOperations.SetBinding(e.Column,
DataGridColumn.VisibilityProperty,
new Binding($"{nameof(ViewModel.ColumnVisibility)}[{e.PropertyName}].{nameof(BindableValue_ColumnVisible<Visibility>.Value)}")
{
Source = _viewModel
});
}
private void Button_OnClick(object sender, RoutedEventArgs e)
{
}
int mCount = 0;
string[] displayedColumnOrder;
private void DataGrid1_Loaded(object sender, RoutedEventArgs e)
{
displayedColumnOrder = new string[_viewModel.Table.Columns.Count];
DataGrid _datagrid = (DataGrid)sender;
_getColumnOrder(_datagrid.Columns);
}
void _getColumnOrder(IEnumerable<DataGridColumn> columnCollection)
{
DataGridColumn[] columnArray;
int columnIndexWorking;
displayedColumnOrder = new string[columnCollection.Count()];
columnArray = columnCollection.ToArray();
foreach (var item_Column in columnCollection)
{
columnIndexWorking = item_Column.DisplayIndex;
displayedColumnOrder[columnIndexWorking] = item_Column.Header.ToString();
}
}
}
public class ViewModel : BindableBase
{
private Brush _myBackground = Brushes.AliceBlue;
public Brush MyBackground
{
get
{
return _myBackground;
}
set
{
_myBackground = value;
NotifyPropertyChanged(nameof(MyBackground));
}
}
private bool _isChecked = false;
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
_isChecked = value;
if (value == true)
{
SetHeader();
SetVisible();
}
else
{
UnSetHeader();
UnSetVisible();
}
NotifyPropertyChanged(nameof(IsChecked));
}
}
public DataTable Table { get; } = new DataTable();
public Dictionary<string, string> ColumnHeader { get; } = new Dictionary<string, string>();
public Dictionary<string, BindableValue_ColumnVisible<Visibility>> ColumnVisibility { get; } = new Dictionary<string, BindableValue_ColumnVisible<Visibility>>();
public ViewModel()
{
Table.Columns.Add("A");
Table.Columns.Add("B");
Table.Columns.Add("C");
Table.Columns.Add("D");
Table.Columns.Add("E");
for (int i = 0; i < 10; i++)
{
Table.Rows.Add($"A-{i}", $"B-{i}", $"C-{i}", $"D-{i}", $"E-{i}");
}
foreach (DataColumn column in Table.Columns)
{
ColumnHeader.Add(column.ColumnName, $"Column {column.ColumnName}");
if (column.ColumnName == "B")
{
ColumnVisibility.Add(column.ColumnName, BindableValue_ColumnVisible.Create(Visibility.Collapsed));
}
else
{
ColumnVisibility.Add(column.ColumnName, BindableValue_ColumnVisible.Create(Visibility.Visible));
}
}
}
public void SetHeader()
{
ColumnHeader["A"] = "I changed Column A!!";
NotifyPropertyChanged(nameof(ColumnHeader));
}
public void SetVisible()
{
ColumnVisibility["B"].Value = Visibility.Collapsed;
}
public void UnSetHeader()
{
ColumnHeader["A"] = "Column A";
NotifyPropertyChanged(nameof(ColumnHeader));
}
public void UnSetVisible()
{
ColumnVisibility["B"].Value = Visibility.Visible;
}
}
public class BindableValue_ColumnVisible<T> : BindableBase
{
public T Value
{
get => _value;
set => SetColumnVisibleProperty(ref _value, value);
}
private T _value;
public BindableValue_ColumnVisible()
{
}
public BindableValue_ColumnVisible(T value)
{
Value = value;
}
}
public static class BindableValue_ColumnVisible
{
public static BindableValue_ColumnVisible<T> Create<T>(T value) => new BindableValue_ColumnVisible<T>(value);
}
public class BindableBase : INotifyPropertyChanged
{
protected virtual bool SetColumnVisibleProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
return SetColumnVisibleProperty(ref field, value, null, propertyName);
}
protected virtual bool SetColumnVisibleProperty<T>(ref T field, T value, Action onChanged, [CallerMemberName]string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
onChanged?.Invoke();
NotifyPropertyChanged(propertyName);
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
If you add a "MyBackground" column to your DataTable, your current RowStyle and binding should work:
public ViewModel()
{
Table.Columns.Add("A");
Table.Columns.Add("B");
Table.Columns.Add("C");
Table.Columns.Add("D");
Table.Columns.Add("E");
Table.Columns.Add("MyBackground");
for (int i = 0; i < 10; i++)
{
Table.Rows.Add($"A-{i}", $"B-{i}", $"C-{i}", $"D-{i}", $"E-{i}", "Yellow");
}
...
}
If you set the column to a known string representation of a brush, such as for example "Yellow" or "Red", you don't have to something else. Else, you could use a converter that converts the value in the DataTable to a Brush.
By the way, it's pointless to set the Mode of this Binding to TwoWay.
public class MyDataTable : System.Data.DataTable {
private Brush _myBackground = Brushes.AliceBlue;
public Brush MyBackground {
get {
return _myBackground;
}
set {
_myBackground = value;
NotifyPropertyChanged(nameof(MyBackground));
}
}
}
public MyDataTable Table { get; } = new MyDataTable();
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="{Binding MyBackground }" />
</Style>
I have a customlistbox whose itemssource is bound to an observablecollection in the viewmodel. I have created a SelectedItemsList DependencyProperty in the customListbox so that the user can select items and the viewmodel will updated. This works perfectly.
I would also like the bound list in the viewmodel, when changed, to update the selected items in the customListbox.
static FrameworkPropertyMetadata fpm = new FrameworkPropertyMetadata(
new ObservableCollection<MyItem>(),
(FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault),
new PropertyChangedCallback(OnSelectedItemsChanged)
);
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//the code
}
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList",
typeof(ObservableCollection<MyItem>),
typeof(CustomListBox), fpm);
SelectedItems is read only. Is there anyway to update the selected items from the viewModel? Is there an alternative to ListBox that would be more suitable?
I figured that I would post my solution just in case it helps anyone.
Here is my very simple Item class
class MyItem
{
public string MyString { get; set; }
public MyItem(string m)
{
MyString = m;
}
}
Here is my CustomListBox Code
class CustomListBox : ListBox
{
public CustomListBox()
{
this.SelectionChanged += CustomListBox_SelectionChanged;
}
void CustomListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ObservableCollection<MyItem> tempList = new ObservableCollection<MyItem>();
foreach (MyItem i in this.SelectedItems)
{
tempList.Add(i);
}
this.SelectedItemsList = tempList;
}
#region SelectedItemsList
public ObservableCollection<MyItem> SelectedItemsList
{
get { return (ObservableCollection<MyItem>)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList", typeof(ObservableCollection<MyItem>), typeof(CustomListBox),
new PropertyMetadata(new ObservableCollection<MyItem>(), new PropertyChangedCallback(OnSelectionChanged)));
public static void OnSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomListBox clb = d as CustomListBox;
var selectedItems = e.NewValue as ObservableCollection<MyItem>;
if (selectedItems != null)
{
clb.SetSelectedItems(selectedItems);
}
}
#endregion
}
The XAML Binding in my window
<local:CustomListBox Height="500" Width="200" x:Name="listview" Margin="0,40,0,0" ItemsSource="{Binding MyItemsList}"
Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" TabIndex="50"
SelectionMode="Multiple" SelectedItemsList="{Binding SelectedMyItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=MyString}" />
</DataTemplate>
</ListBox.ItemTemplate>
</local:CustomListBox>
And my ViewModel
class MyViewModel : ViewModelBase
{
public MyViewModel()
{
this.SelectChangeMyItemsCommand = new BaseCommand(new Action(SelectChangeMyItems));
}
private ObservableCollection<MyItem> selectedMyItems = null;
public ObservableCollection<MyItem> SelectedMyItems
{
get
{
if (selectedMyItems == null)
{
selectedMyItems = new ObservableCollection<MyItem>();
}
return selectedMyItems;
}
set
{
selectedMyItems = value;
OnPropertyChanged("SelectedMyItems");
}
}
private ObservableCollection<MyItem> myItemsList = null;
public ObservableCollection<MyItem> MyItemsList
{
get
{
if (myItemsList == null)
{
MyItemsRefresh();
}
return myItemsList;
}
set
{
myItemsList = value;
OnPropertyChanged("MyItemsList");
}
}
public void MyItemsRefresh()
{
ObservableCollection<MyItem> tempMyList = new ObservableCollection<MyItem>();
tempMyList.Add(new MyItem("Angry Apple"));
tempMyList.Add(new MyItem("Big Bird"));
tempMyList.Add(new MyItem("Candy Cane"));
tempMyList.Add(new MyItem("Daring Dart"));
MyItemsList = tempMyList;
}
private static bool iseven = true;
public ICommand SelectChangeMyItemsCommand { get; private set; }
public void SelectChangeMyItems()
{
ObservableCollection<MyItem> items = new ObservableCollection<MyItem>();
for(int i = 0; i < myItemsList.Count; i++)
{
if (iseven && IsEven(i))
{
items.Add(MyItemsList[i]);
}
else if (!iseven && !IsEven(i))
{
items.Add(MyItemsList[i]);
}
}
this.SelectedMyItems = items;
iseven = !iseven;
}
public static bool IsEven(int value)
{
return value % 2 == 0;
}
}
I created a reference to load big data into a datagrid with a dapper extension. I have a Behavior that detects when the scroll is then down load the following data using this RelayCommand:
XAML in Datagrid Properties :
Behavior:ScrollViewerMonitor.AtEndCommand="{Binding LoadCommand}"
My Behavior (Detect when scroll is down) :
public class ScrollViewerMonitor
{
public static DependencyProperty AtEndCommandProperty
= DependencyProperty.RegisterAttached(
"AtEndCommand", typeof(ICommand),
typeof(ScrollViewerMonitor),
new PropertyMetadata(OnAtEndCommandChanged));
public static ICommand GetAtEndCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(AtEndCommandProperty);
}
public static void SetAtEndCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(AtEndCommandProperty, value);
}
public static void OnAtEndCommandChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)d;
if (element != null)
{
element.Loaded -= element_Loaded;
element.Loaded += element_Loaded;
}
}
static void element_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
element.Loaded -= element_Loaded;
ScrollViewer scrollViewer = FindChildOfType<ScrollViewer>(element);
if (scrollViewer == null)
{
// throw new InvalidOperationException("ScrollViewer not found.");
return;
}
var dpd = DependencyPropertyDescriptor.FromProperty(ScrollViewer.VerticalOffsetProperty, typeof(ScrollViewer));
dpd.AddValueChanged(scrollViewer, delegate (object o, EventArgs args)
{
bool atBottom = scrollViewer.VerticalOffset
>= scrollViewer.ScrollableHeight;
if (atBottom)
{
var atEnd = GetAtEndCommand(element);
if (atEnd != null)
{
atEnd.Execute(null);
}
}
});
}
static T FindChildOfType<T>(DependencyObject root) where T : class
{
var queue = new Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
DependencyObject current = queue.Dequeue();
for (int i = VisualTreeHelper.GetChildrenCount(current) - 1; 0 <= i; i--)
{
var child = VisualTreeHelper.GetChild(current, i);
var typedChild = child as T;
if (typedChild != null)
{
return typedChild;
}
queue.Enqueue(child);
}
}
return null;
}
}
My ViewModel with LoadCommand :
//Init mmy ObservableCollection for DataGrid
var myObservableCollection = new ObservableCollection<Mouvement_Brouillard>();
//Init my Object
//Parameters(NumberPerPage,Conditions,OrderBy,Connection)
var myReference = new Paged2<Mouvement_Brouillard>(150, "", "Swmo_Id", new ConnectionProvider());
//Load First Datas
myReference.AddDatas(myObservableCollection);
//Call LoadCommand when Scroll is down
LoadCommand = new RelayCommand<object>(myReference.LoadCommand(myObservableCollection));
And my reference Paged2 (AddData in ObservableCollection and execute LoadCommand:
public class Paged2<T>
{
private T Value { get; set; }
public int NumberPage { get; set; }
public int RowsPerPage { get; set; }
public string Conditions { get; set; }
public string OrderBy { get; set; }
public ConnectionProvider Cnn { get; set; }
public static bool Busy;
public Paged2(int _RowsPerPage, string _Conditions, string _OrdeBy, ConnectionProvider _Cnn)
{
this.RowsPerPage = _RowsPerPage;
this.Conditions = _Conditions;
this.OrderBy = _OrdeBy;
this.NumberPage = 1;
this.Cnn = _Cnn;
}
public async void AddDatas(ObservableCollection<T> myList)
{
IEnumerable<T> myNewBlocList;
//DAL
using (var myCnn = this.Cnn.GetOpenConnection())
{
myNewBlocList = await myCnn.GetListPagedAsync<T>(this.NumberPage, this.RowsPerPage, this.Conditions, this.OrderBy);
}
NumberPage++;
foreach (var Item in myNewBlocList)
myList.Add(Item);
}
public Action<object> LoadCommand(Ref<ObservableCollection<T>> myList)
{
return new Action<object>(
obj =>
{
if (Busy)
return;
Busy = true;
System.Threading.ThreadPool.QueueUserWorkItem(
delegate
{
Application.Current.Dispatcher.BeginInvoke(new Action(
delegate
{
AddDatas(myList);
Busy = false;
}));
});
});
}
public class Ref<T>
{
public Ref() { }
public Ref(T value) { Value = value; }
public T Value { get; set; }
public override string ToString()
{
T value = Value;
return value == null ? "" : value.ToString();
}
public static implicit operator T(Ref<T> r) { return r.Value; }
public static implicit operator Ref<T>(T value) { return new Ref<T>(value); }
}
}
Everything works but since I outsource (place in another file ) method LoadCommand the next part of the code no longer works :
public Action<object> LoadCommand(Ref<ObservableCollection<T>> myList)
{
return new Action<object>(
obj =>
{
if (Busy)
return;
Busy = true;
System.Threading.ThreadPool.QueueUserWorkItem(
delegate
{
Application.Current.Dispatcher.BeginInvoke(new Action(
delegate
{
AddDatas(myList);
Busy = false;
}));
});
});
}
I have a small DataGrid to do a simple operation. Fields are 3: Number 1, 2 and Result Numer.
DataGrid code is as follows:
<DataGrid x:Name="dgNumbers" ItemsSource="{Binding lstOperations, Mode=TwoWay}" CanUserAddRows="True" AutoGenerateColumns="False" CellEditEnding="dgNumbers_CellEditEnding">
<DataGrid.Columns>
<DataGridTextColumn Header="Number 1" Width="*" Binding="{Binding N1, Mode=TwoWay}"/>
<DataGridTextColumn Header="Number 2" Width="*" Binding="{Binding N2, Mode=TwoWay}"/>
<DataGridTextColumn Header="Result" Width="*" Binding="{Binding Result, Mode=TwoWay}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
I created an object where I keep the number 1, the number and outcome. This is the class code:
public class Numbers
{
public decimal N1 { get; set; }
public decimal N2 { get; set; }
public decimal Result { get; set; }
}
I made this small example to try to understand the Binding that make the ObservableCollection.
For this example, I have the following code in the event:
public MainWindow()
{
InitializeComponent();
lstOperations = new ObservableCollection<Numbers>();
}
ObservableCollection<Numbers> lstOperations;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Numbers n = new Numbers();
n.N1 = 10;
n.N2 = 5;
n.Result = 15;
lstOperations.Add(n);
dgNumbers.ItemsSource = lstOperations;
}
private void dgNumbers_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
foreach(var item in lstOperations)
{
item.Result = item.N1 + item.N2;
}
}
What I am trying to know if changing a data collection, this fact is reflected in the DataGrid, if possible, how to do it right ?, if not possible, how to achieve something similar?
You can also install by NuGet Prism.Core and use the BindableBase class:
using Prism.Mvvm;
using System.Collections.ObjectModel;
using System.Windows;
namespace YourApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
private void dgNumbers_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
foreach (var item in (DataContext as MainWindowViewModel).LstOperations)
{
item.Result = item.N1 + item.N2;
}
}
}
public class MainWindowViewModel : BindableBase
{
private ObservableCollection<Numbers> _lstOperations;
public ObservableCollection<Numbers> LstOperations
{
get { return _lstOperations; }
set
{
_lstOperations = value;
OnPropertyChanged();
}
}
public MainWindowViewModel()
{
_lstOperations = new ObservableCollection<Numbers>();
Numbers n = new Numbers
{
N1 = 10,
N2 = 5,
Result = 15
};
LstOperations.Add(n);
}
}
public class Numbers : BindableBase
{
private decimal _n1;
public decimal N1
{
get { return _n1; }
set { SetProperty(ref _n1, value); }
}
private decimal _n2;
public decimal N2
{
get { return _n2; }
set { SetProperty(ref _n2, value); }
}
private decimal _result;
public decimal Result
{
get { return _result; }
set { SetProperty(ref _result, value); }
}
}
}
In the end you also have to change the Binding in the View:
<DataGrid x:Name="dgNumbers" ItemsSource="{Binding LstOperations, Mode=TwoWay}" CanUserAddRows="True" AutoGenerateColumns="False" CellEditEnding="dgNumbers_CellEditEnding">
Using BindableBase is quite easy because it takes the CallerMemberName attribute in the implementation, so you don't have to specify which property is being called (you write OnPropertyChanged() in the setter instead of OnPropertyChanged("propertyName")). And it's even better, because there is also the SetProperty method, which sets the new value AND calls OnPropertyChanged event only when needed (when the new value is really new, really changed).
Do you have a class which implements INotifyPropertyChanged?
You can subscribe to ObservableCollection events. CollectionChange would probably do the trick but doesn't take advantage of data binding.
public MainWindow()
{
InitializeComponent();
lstOperations = new ObservableCollection<Numbers>();
lstOperations.CollectionChanged += MyCollectionChanged;
}
private MyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
dgNumbers.ItemsSource = lstOperations;
}
So the basic data binding would go like this.
public class ModelView : INotifyPropertyChanged
{
public ModelView()
{
lstOperations = new ObservableCollection<Numbers>();
lstOperations.CollectionChanged += new NotifyCollectionChangedEventHandler((obj, e) => { OnPropertyChanged("lstOperations "); });
}
//----------------- Implementing the interface here
public event PropertyChangedEventHandler PropertyChanged;
// Call this method when you want the GUI updated.
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
//-------------------Your Properties-----------------------------
private ObservableCollection<Numbers> _lstOperations ;
public ObservableCollection<Numbers> lstOperations
{
get{return _lstOperations ;}
set
{
_lstOperations = value;
OnPropertyChanged("lstOperations");
}
}
Ok the class above now contains your variable you want to bind. Now you need to set your datacontext for the Datagrid.
// Need your model instance.
private ModelView Model;
public MainWindow()
{
InitializeComponent();
Model = new ModelView();
dgNumbers.DataContext = Model;
}
Now anywhere you are manipulating lstOperations you manipulate Model.lstOperations.
forgive my stupid tip but for me it works when i bind such a list
public class ModelNumber
{
public decimal N1 { get; set; }
public decimal N2 { get; set; }
public decimal Result { get; set; }
}
public class ViewModelNumber : NotifyPropertyChanged, IDataErrorInfo
{
protected ModelNumber __dataModel = null;
#region ---constructor---
public ViewModelNumber()
{
// model init
this.__dataModel = new ModelNumber();
}
#endregion
#region ---accessoren model basis---
public decimal N1
{
get
{
return this.__dataModel.N1;
}
set
{
if (this.__dataModel.N1 != value)
{
this.__dataModel.N1 = value;
this.OnPropertyChanged("N1");
}
}
}
public decimal N2
{
get
{
return this.__dataModel.N2;
}
set
{
if (this.__dataModel.N2 != value)
{
this.__dataModel.N2 = value;
this.OnPropertyChanged("N1");
}
}
}
public decimal Result
{
get
{
return this.__dataModel.Result;
}
set
{
if (this.__dataModel.Result != value)
{
this.__dataModel.Result = value;
this.OnPropertyChanged("N1");
}
}
}
#endregion
#region ---validation---
/// <summary>Gets an error message indicating what is wrong with this object.</summary>
public string Error
{
get { throw new NotImplementedException(); }
}
/// <summary>Gets the error message for the property with the given name.</summary>
public string this[string _columnName]
{
get
{
try
{
if (_columnName != null)
{
switch (_columnName)
{
default:
break;
}
}
return (null);
}
catch (Exception _except)
{
// mlog
Log.Exception(this.GetType().FullName, MethodBase.GetCurrentMethod().Name, _except);
return (null);
}
}
}
#endregion
}
ObservableCollection<ViewModelNumber> lstOperations;
In my View I got a ListView bound to a CollectionView in my ViewModel, for example like this:
<ListView ItemsSource="{Binding MyCollection}" IsSynchronizedWithCurrentItem="true">
<ListView.View>
<GridView>
<GridViewColumn Header="Title" DisplayMemberBinding="{Binding Path=Title}"/>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}"/>
<GridViewColumn Header="Phone" DisplayMemberBinding="{Binding Path=Phone}"/>
<GridViewColumn Header="E-mail" DisplayMemberBinding="{Binding Path=EMail}"/>
</GridView>
</ListView.View>
</ListView>
Right now these GridViewColumns are fixed, but I'd like to be able to change them from the ViewModel. I'd guess I'll have to bind the GridViewColumn-collection to something in the ViewModel, but what, and how?
The ViewModel does know nothing of WPF, so I got no clue how to achieve this in MVVM.
any help here?
The Columns property is not a dependency property, so you can't bind it. However, it might be possible to create an attached property that you could bind to a collection in your ViewModel. This attached property would then create the columns for you.
UPDATE
OK, here's a basic implementation...
Attached properties
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace TestPadWPF
{
public static class GridViewColumns
{
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static object GetColumnsSource(DependencyObject obj)
{
return (object)obj.GetValue(ColumnsSourceProperty);
}
public static void SetColumnsSource(DependencyObject obj, object value)
{
obj.SetValue(ColumnsSourceProperty, value);
}
// Using a DependencyProperty as the backing store for ColumnsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColumnsSourceProperty =
DependencyProperty.RegisterAttached(
"ColumnsSource",
typeof(object),
typeof(GridViewColumns),
new UIPropertyMetadata(
null,
ColumnsSourceChanged));
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static string GetHeaderTextMember(DependencyObject obj)
{
return (string)obj.GetValue(HeaderTextMemberProperty);
}
public static void SetHeaderTextMember(DependencyObject obj, string value)
{
obj.SetValue(HeaderTextMemberProperty, value);
}
// Using a DependencyProperty as the backing store for HeaderTextMember. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HeaderTextMemberProperty =
DependencyProperty.RegisterAttached("HeaderTextMember", typeof(string), typeof(GridViewColumns), new UIPropertyMetadata(null));
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static string GetDisplayMemberMember(DependencyObject obj)
{
return (string)obj.GetValue(DisplayMemberMemberProperty);
}
public static void SetDisplayMemberMember(DependencyObject obj, string value)
{
obj.SetValue(DisplayMemberMemberProperty, value);
}
// Using a DependencyProperty as the backing store for DisplayMember. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayMemberMemberProperty =
DependencyProperty.RegisterAttached("DisplayMemberMember", typeof(string), typeof(GridViewColumns), new UIPropertyMetadata(null));
private static void ColumnsSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
GridView gridView = obj as GridView;
if (gridView != null)
{
gridView.Columns.Clear();
if (e.OldValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(e.OldValue);
if (view != null)
RemoveHandlers(gridView, view);
}
if (e.NewValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(e.NewValue);
if (view != null)
{
AddHandlers(gridView, view);
CreateColumns(gridView, view);
}
}
}
}
private static IDictionary<ICollectionView, List<GridView>> _gridViewsByColumnsSource =
new Dictionary<ICollectionView, List<GridView>>();
private static List<GridView> GetGridViewsForColumnSource(ICollectionView columnSource)
{
List<GridView> gridViews;
if (!_gridViewsByColumnsSource.TryGetValue(columnSource, out gridViews))
{
gridViews = new List<GridView>();
_gridViewsByColumnsSource.Add(columnSource, gridViews);
}
return gridViews;
}
private static void AddHandlers(GridView gridView, ICollectionView view)
{
GetGridViewsForColumnSource(view).Add(gridView);
view.CollectionChanged += ColumnsSource_CollectionChanged;
}
private static void CreateColumns(GridView gridView, ICollectionView view)
{
foreach (var item in view)
{
GridViewColumn column = CreateColumn(gridView, item);
gridView.Columns.Add(column);
}
}
private static void RemoveHandlers(GridView gridView, ICollectionView view)
{
view.CollectionChanged -= ColumnsSource_CollectionChanged;
GetGridViewsForColumnSource(view).Remove(gridView);
}
private static void ColumnsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ICollectionView view = sender as ICollectionView;
var gridViews = GetGridViewsForColumnSource(view);
if (gridViews == null || gridViews.Count == 0)
return;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var gridView in gridViews)
{
for (int i = 0; i < e.NewItems.Count; i++)
{
GridViewColumn column = CreateColumn(gridView, e.NewItems[i]);
gridView.Columns.Insert(e.NewStartingIndex + i, column);
}
}
break;
case NotifyCollectionChangedAction.Move:
foreach (var gridView in gridViews)
{
List<GridViewColumn> columns = new List<GridViewColumn>();
for (int i = 0; i < e.OldItems.Count; i++)
{
GridViewColumn column = gridView.Columns[e.OldStartingIndex + i];
columns.Add(column);
}
for (int i = 0; i < e.NewItems.Count; i++)
{
GridViewColumn column = columns[i];
gridView.Columns.Insert(e.NewStartingIndex + i, column);
}
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (var gridView in gridViews)
{
for (int i = 0; i < e.OldItems.Count; i++)
{
gridView.Columns.RemoveAt(e.OldStartingIndex);
}
}
break;
case NotifyCollectionChangedAction.Replace:
foreach (var gridView in gridViews)
{
for (int i = 0; i < e.NewItems.Count; i++)
{
GridViewColumn column = CreateColumn(gridView, e.NewItems[i]);
gridView.Columns[e.NewStartingIndex + i] = column;
}
}
break;
case NotifyCollectionChangedAction.Reset:
foreach (var gridView in gridViews)
{
gridView.Columns.Clear();
CreateColumns(gridView, sender as ICollectionView);
}
break;
default:
break;
}
}
private static GridViewColumn CreateColumn(GridView gridView, object columnSource)
{
GridViewColumn column = new GridViewColumn();
string headerTextMember = GetHeaderTextMember(gridView);
string displayMemberMember = GetDisplayMemberMember(gridView);
if (!string.IsNullOrEmpty(headerTextMember))
{
column.Header = GetPropertyValue(columnSource, headerTextMember);
}
if (!string.IsNullOrEmpty(displayMemberMember))
{
string propertyName = GetPropertyValue(columnSource, displayMemberMember) as string;
column.DisplayMemberBinding = new Binding(propertyName);
}
return column;
}
private static object GetPropertyValue(object obj, string propertyName)
{
if (obj != null)
{
PropertyInfo prop = obj.GetType().GetProperty(propertyName);
if (prop != null)
return prop.GetValue(obj, null);
}
return null;
}
}
}
ViewModel
class PersonsViewModel
{
public PersonsViewModel()
{
this.Persons = new ObservableCollection<Person>
{
new Person
{
Name = "Doe",
FirstName = "John",
DateOfBirth = new DateTime(1981, 9, 12)
},
new Person
{
Name = "Black",
FirstName = "Jack",
DateOfBirth = new DateTime(1950, 1, 15)
},
new Person
{
Name = "Smith",
FirstName = "Jane",
DateOfBirth = new DateTime(1987, 7, 23)
}
};
this.Columns = new ObservableCollection<ColumnDescriptor>
{
new ColumnDescriptor { HeaderText = "Last name", DisplayMember = "Name" },
new ColumnDescriptor { HeaderText = "First name", DisplayMember = "FirstName" },
new ColumnDescriptor { HeaderText = "Date of birth", DisplayMember = "DateOfBirth" }
};
}
public ObservableCollection<Person> Persons { get; private set; }
public ObservableCollection<ColumnDescriptor> Columns { get; private set; }
private ICommand _addColumnCommand;
public ICommand AddColumnCommand
{
get
{
if (_addColumnCommand == null)
{
_addColumnCommand = new DelegateCommand<string>(
s =>
{
this.Columns.Add(new ColumnDescriptor { HeaderText = s, DisplayMember = s });
});
}
return _addColumnCommand;
}
}
private ICommand _removeColumnCommand;
public ICommand RemoveColumnCommand
{
get
{
if (_removeColumnCommand == null)
{
_removeColumnCommand = new DelegateCommand<string>(
s =>
{
this.Columns.Remove(this.Columns.FirstOrDefault(d => d.DisplayMember == s));
});
}
return _removeColumnCommand;
}
}
}
XAML :
<ListView ItemsSource="{Binding Persons}" Grid.Row="0">
<ListView.View>
<GridView local:GridViewColumns.HeaderTextMember="HeaderText"
local:GridViewColumns.DisplayMemberMember="DisplayMember"
local:GridViewColumns.ColumnsSource="{Binding Columns}"/>
</ListView.View>
</ListView>
Note that the ColumnDescriptor class is not actually needed, I only added it for clarity, but any type will do (including an anonymous type). You just need to specify which properties hold the header text and display member name.
Also, keep in mind that it's not fully tested yet, so there might be a few problems to fix...
I took Thomas Levesque's excellent solution and modified it to remove the static collection of GridViews and also add the ability to set a column width and string format, so thought I'd share my code.
Modified attached property class:
public static class GridViewColumnCollection
{
public static readonly DependencyProperty ColumnCollectionBehaviourProperty =
DependencyProperty.RegisterAttached("ColumnCollectionBehaviour", typeof(GridViewColumnCollectionBehaviour), typeof(GridViewColumnCollection), new UIPropertyMetadata(null));
public static readonly DependencyProperty ColumnsSourceProperty =
DependencyProperty.RegisterAttached("ColumnsSource", typeof(object), typeof(GridViewColumnCollection), new UIPropertyMetadata(null, GridViewColumnCollection.ColumnsSourceChanged));
public static readonly DependencyProperty DisplayMemberFormatMemberProperty =
DependencyProperty.RegisterAttached("DisplayMemberFormatMember", typeof(string), typeof(GridViewColumnCollection), new UIPropertyMetadata(null, GridViewColumnCollection.DisplayMemberFormatMemberChanged));
public static readonly DependencyProperty DisplayMemberMemberProperty =
DependencyProperty.RegisterAttached("DisplayMemberMember", typeof(string), typeof(GridViewColumnCollection), new UIPropertyMetadata(null, GridViewColumnCollection.DisplayMemberMemberChanged));
public static readonly DependencyProperty HeaderTextMemberProperty =
DependencyProperty.RegisterAttached("HeaderTextMember", typeof(string), typeof(GridViewColumnCollection), new UIPropertyMetadata(null, GridViewColumnCollection.HeaderTextMemberChanged));
public static readonly DependencyProperty WidthMemberProperty =
DependencyProperty.RegisterAttached("WidthMember", typeof(string), typeof(GridViewColumnCollection), new UIPropertyMetadata(null, GridViewColumnCollection.WidthMemberChanged));
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static GridViewColumnCollectionBehaviour GetColumnCollectionBehaviour(DependencyObject obj)
{
return (GridViewColumnCollectionBehaviour)obj.GetValue(ColumnCollectionBehaviourProperty);
}
public static void SetColumnCollectionBehaviour(DependencyObject obj, GridViewColumnCollectionBehaviour value)
{
obj.SetValue(ColumnCollectionBehaviourProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static object GetColumnsSource(DependencyObject obj)
{
return (object)obj.GetValue(ColumnsSourceProperty);
}
public static void SetColumnsSource(DependencyObject obj, object value)
{
obj.SetValue(ColumnsSourceProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static string GetDisplayMemberFormatMember(DependencyObject obj)
{
return (string)obj.GetValue(DisplayMemberFormatMemberProperty);
}
public static void SetDisplayMemberFormatMember(DependencyObject obj, string value)
{
obj.SetValue(DisplayMemberFormatMemberProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static string GetDisplayMemberMember(DependencyObject obj)
{
return (string)obj.GetValue(DisplayMemberMemberProperty);
}
public static void SetDisplayMemberMember(DependencyObject obj, string value)
{
obj.SetValue(DisplayMemberMemberProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static string GetHeaderTextMember(DependencyObject obj)
{
return (string)obj.GetValue(HeaderTextMemberProperty);
}
public static void SetHeaderTextMember(DependencyObject obj, string value)
{
obj.SetValue(HeaderTextMemberProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(GridView))]
public static string GetWidthMember(DependencyObject obj)
{
return (string)obj.GetValue(WidthMemberProperty);
}
public static void SetWidthMember(DependencyObject obj, string value)
{
obj.SetValue(WidthMemberProperty, value);
}
private static void ColumnsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
GridViewColumnCollection.GetOrCreateColumnCollectionBehaviour(sender).ColumnsSource = e.NewValue;
}
private static void DisplayMemberFormatMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
GridViewColumnCollection.GetOrCreateColumnCollectionBehaviour(sender).DisplayMemberFormatMember = e.NewValue as string;
}
private static void DisplayMemberMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
GridViewColumnCollection.GetOrCreateColumnCollectionBehaviour(sender).DisplayMemberMember = e.NewValue as string;
}
private static void HeaderTextMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
GridViewColumnCollection.GetOrCreateColumnCollectionBehaviour(sender).HeaderTextMember = e.NewValue as string;
}
private static void WidthMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
GridViewColumnCollection.GetOrCreateColumnCollectionBehaviour(sender).WidthMember = e.NewValue as string;
}
private static GridViewColumnCollectionBehaviour GetOrCreateColumnCollectionBehaviour(DependencyObject source)
{
GridViewColumnCollectionBehaviour behaviour = GetColumnCollectionBehaviour(source);
if (behaviour == null)
{
GridView typedSource = source as GridView;
if (typedSource == null)
{
throw new Exception("This property can only be set on controls deriving GridView");
}
behaviour = new GridViewColumnCollectionBehaviour(typedSource);
SetColumnCollectionBehaviour(typedSource, behaviour);
}
return behaviour;
}
}
Behaviour (this is stored against each GridView and obviates the need to store the collection-GridView mappings centrally):
public class GridViewColumnCollectionBehaviour
{
private object columnsSource;
private GridView gridView;
public GridViewColumnCollectionBehaviour(GridView gridView)
{
this.gridView = gridView;
}
public object ColumnsSource
{
get { return this.columnsSource; }
set
{
object oldValue = this.columnsSource;
this.columnsSource = value;
this.ColumnsSourceChanged(oldValue, this.columnsSource);
}
}
public string DisplayMemberFormatMember { get; set; }
public string DisplayMemberMember { get; set; }
public string HeaderTextMember { get; set; }
public string WidthMember { get; set; }
private void AddHandlers(ICollectionView collectionView)
{
collectionView.CollectionChanged += this.ColumnsSource_CollectionChanged;
}
private void ColumnsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ICollectionView view = sender as ICollectionView;
if (this.gridView == null)
{
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
for (int i = 0; i < e.NewItems.Count; i++)
{
GridViewColumn column = CreateColumn(e.NewItems[i]);
gridView.Columns.Insert(e.NewStartingIndex + i, column);
}
break;
case NotifyCollectionChangedAction.Move:
List<GridViewColumn> columns = new List<GridViewColumn>();
for (int i = 0; i < e.OldItems.Count; i++)
{
GridViewColumn column = gridView.Columns[e.OldStartingIndex + i];
columns.Add(column);
}
for (int i = 0; i < e.NewItems.Count; i++)
{
GridViewColumn column = columns[i];
gridView.Columns.Insert(e.NewStartingIndex + i, column);
}
break;
case NotifyCollectionChangedAction.Remove:
for (int i = 0; i < e.OldItems.Count; i++)
{
gridView.Columns.RemoveAt(e.OldStartingIndex);
}
break;
case NotifyCollectionChangedAction.Replace:
for (int i = 0; i < e.NewItems.Count; i++)
{
GridViewColumn column = CreateColumn(e.NewItems[i]);
gridView.Columns[e.NewStartingIndex + i] = column;
}
break;
case NotifyCollectionChangedAction.Reset:
gridView.Columns.Clear();
CreateColumns(sender as ICollectionView);
break;
default:
break;
}
}
private void ColumnsSourceChanged(object oldValue, object newValue)
{
if (this.gridView != null)
{
gridView.Columns.Clear();
if (oldValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
if (view != null)
{
this.RemoveHandlers(view);
}
}
if (newValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
if (view != null)
{
this.AddHandlers(view);
this.CreateColumns(view);
}
}
}
}
private GridViewColumn CreateColumn(object columnSource)
{
GridViewColumn column = new GridViewColumn();
if (!string.IsNullOrEmpty(this.HeaderTextMember))
{
column.Header = GetPropertyValue(columnSource, this.HeaderTextMember);
}
if (!string.IsNullOrEmpty(this.DisplayMemberMember))
{
string propertyName = GetPropertyValue(columnSource, this.DisplayMemberMember) as string;
string format = null;
if (!string.IsNullOrEmpty(this.DisplayMemberFormatMember))
{
format = GetPropertyValue(columnSource, this.DisplayMemberFormatMember) as string;
}
if (string.IsNullOrEmpty(format))
{
format = "{0}";
}
column.DisplayMemberBinding = new Binding(propertyName) { StringFormat = format };
}
if (!string.IsNullOrEmpty(this.WidthMember))
{
double width = (double)GetPropertyValue(columnSource, this.WidthMember);
column.Width = width;
}
return column;
}
private void CreateColumns(ICollectionView collectionView)
{
foreach (object item in collectionView)
{
GridViewColumn column = this.CreateColumn(item);
this.gridView.Columns.Add(column);
}
}
private object GetPropertyValue(object obj, string propertyName)
{
object returnVal = null;
if (obj != null)
{
PropertyInfo prop = obj.GetType().GetProperty(propertyName);
if (prop != null)
{
returnVal = prop.GetValue(obj, null);
}
}
return returnVal;
}
private void RemoveHandlers(ICollectionView collectionView)
{
collectionView.CollectionChanged -= this.ColumnsSource_CollectionChanged;
}
}
I think this code would cause some memory leak issues; As your class GridViewColumns described, you've defined a static dictionary "_gridViewsByColumnsSource" in which contains the gridviews and their columns source references; so this is a strong reference to the added gridviews and columns source; Because this dictionary is static, it seems that there's a static reference "point" to the gridviews and columns source data all the time, if the screen in which the gridview defined closed, the gridview cannot be collected by GC if GC is started; As more and more similar screens opened, more and more gridviews and its columns source data cannot be collected, there'll be memory leak at the end.