Introduction
In MVVM, there are situations when we need to have the access of the particular controls in the ViewModel
. For example, if I want to bind IEnumerable
to the DataGrid
because I am not able to determine the fields till runtime. Now if I bind IEnumerable<dynamic>
and set AutogeneratedColumns = true
then also DataGrid
will not show the records because each row is returned as an "ExpandoObject
" with dynamic properties representing the fields. In this case, I have to add the columns dynamically. To do that, I need the DataGrid
control. Now to get the dataGrid
control in the ViewModel
we can use attached property. Let's try.
Background
For better understanding of this article, I would recommend reading of the attached properties.
Using the Code
First of all, define the attached property for the control in the ViewModel
.
Public static readonly DependecyProperty DataGridProperty = _
DependencyProperty.RegisterAttached("DataGrid", typeof(DataGrid),
typeof(MainWindowViewModel),
New FrameworkPropertyMetadata(OnDataGridChanged));
public static void SetDataGrid(DependencyObject element, DataGrid value)
{
element.SetValue(DataGridProperty, value);
}
public static DataGrid GetDataGrid(DependencyObject element)
{
return (DataGrid)element.GetValue(DataGridProperty);
}
Assign the name
property on the control. Now we need to bind the control with this attached property.
<Window x:Class="AccessControlsInViewModel.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AccessControlsInViewModel"
Title="Access Controls In ViewModel" Height="350" Width="525">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="0,0,0,25">
<Button Content="Add Columns" Command="{Binding AddCommand}" />
</StackPanel>
<DataGrid Name="accessGrid" ItemsSource="{Binding ListOfPerson}"
local:MainWindowViewModel.DataGrid="{Binding ElementName=accessGrid}" />
</DockPanel>
</Window>
Here I have binded the control with the attached property using the ElementName
binding.
The reason behind using attached property is here we need to bind the whole control with the property and there is no in-built property available for doing this.
Then define a static
variable for the control and assign its value on the attached property changed event.
private static DataGrid dataGrid = null;
public static void OnDataGridChanged
(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
dataGrid = obj as DataGrid;
}
Now we have the control available in the ViewModel
. So we can achieve everything that we can do in the code behind file.
For the DataGrid
example, on the add button
's command we can add the columns like this:
private static void AddDynamicColumnsToDataGrid()
{
IEnumerable<IDictionary<string, object>>
rows = dataGrid.ItemsSource.OfType<IDictionary<string, object>>();
IEnumerable<string> columns = rows.SelectMany
(d => d.Keys).Distinct(StringComparer.OrdinalIgnoreCase);
foreach (string text in columns)
{
var column = new DataGridTextColumn
{
Header = text,
Binding = new Binding(text)
};
dataGrid.Columns.Add(column);
}
}
Points of Interest
To solve this problem, I have added click and command both on the Button
and the trick has really worked for me. But as I was using MVVM, I needed to do it in the ViewModel
and I found out this solution.