WPF 4 DataGrid: Showing & Hiding Columns - wpf

I'm trying to implement column chooser functionality for a DataGrid and am running into problems if I try to define the content of the header for the column as something more than just a string. Below is a very simplified example with all styles, view models, binding, etc all stripped out.
There are 3 columns:
The first column uses a string for the header.
The second column tries to set the header content to a Label with a ToolTip.
The third column ties to set the header content to a TextBlock with a ToolTip.
Clicking the Toggle Visibility button for Column A works fine. The Toggle Visibility buttons for both Columns B and C cause an InvalidOperationException with the message "Specified element is already the logical child of another element. Disconnect it first."
<Window x:Class="DataGridColumnChoosing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,10">
<TextBlock Margin="15, 0">Toggle Visibility:</TextBlock>
<Button Click="ToggleA">Column A</Button>
<Button Click="ToggleB">Column B</Button>
<Button Click="ToggleC">Column C</Button>
</StackPanel>
<!-- Main Fuel Mileage Datagrid -->
<DataGrid x:Name="mySampleDataGrid" Grid.Row="1"
AutoGenerateColumns="False" CanUserSortColumns="False" CanUserResizeRows="False" CanUserAddRows="False"
GridLinesVisibility="All" RowHeaderWidth="0">
<DataGrid.Columns>
<DataGridTemplateColumn x:Name="colA" Width="40*" IsReadOnly="True" Header="Column A">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn x:Name="colB" Width="40*" IsReadOnly="True" >
<DataGridTemplateColumn.Header>
<Label Content="Column B" ToolTip="A short explanation of Column B"/>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn x:Name="colC" Width="40*" IsReadOnly="True" >
<DataGridTemplateColumn.Header>
<TextBlock Text="Column C" ToolTip="A short explanation of Column C " />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
The simple click event handlers for the buttons that are toggling the visibility in this example are simply modifying the visibility of the columns.
private void ToggleA(object sender, RoutedEventArgs e)
{
colA.Visibility = colA.Visibility == System.Windows.Visibility.Visible ? System.Windows.Visibility.Hidden : System.Windows.Visibility.Visible;
}
private void ToggleB(object sender, RoutedEventArgs e)
{
colB.Visibility = colB.Visibility == System.Windows.Visibility.Visible ? System.Windows.Visibility.Hidden : System.Windows.Visibility.Visible;
}
private void ToggleC(object sender, RoutedEventArgs e)
{
colC.Visibility = colC.Visibility == System.Windows.Visibility.Visible ? System.Windows.Visibility.Hidden : System.Windows.Visibility.Visible;
}
Thanks all.

I had this issue once when I had a control defined in my Resources, and was trying to use it within multiple control's Content areas. That does not work because the control can only belong to one parent.
Instead, I needed to define a Template of some kind which contained the control I wanted, and set the Template of my object instead of the content directly.
Your comment on #Gimno's answer makes me think this is the case.
Try changing it so instead of setting a Label/TextBox in DataGrid.Header's content directly, set DataGrid.HeaderTemplate to a DataTemplate which contains the Label or TextBox.
EDIT
Here's some example code
<DataGridTemplateColumn x:Name="colB" Width="40*" IsReadOnly="True" >
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<Label Content="Column B" ToolTip="A short explanation of Column B"/>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

I think it would be easiest if you just use DataGridTemplateColumn.HeaderStyle instead of DataGridTemplateColumn.Header
As example for column c:
<DataGridTemplateColumn x:Name="colC" Width="40*" IsReadOnly="True" >
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Column C" ToolTip="A short explanation of Column C "/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridTemplateColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGridTemplateColumn.HeaderStyle>

I liked the column chooser solution from CodePlex:
DataGrid Behavior
I clean up the code and remove unnecessary code:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using Microsoft.Xaml.Behaviors;
namespace Behaviors
{
public class WpfDataGridConfigurationBehavior : Behavior<DependencyObject>
{
#region "public Properties"
public bool DontPersistVisibleColumns { get; set; }
public bool DontPersistColumnsOrder { get; set; }
public string ContextMenuChoices { get; set; } = "Alphabetical Menu,Show All Columns";
public string DataGridName { get; set; }
#endregion "public Properties"
#region "private Properties"
private DataGrid dataGrid;
private ContextMenu theContextMenu; // Context Menu for the field chooser.
private string AllColumnsHeaders { get; set; }
private string AllColumnDisplayIndexes { get; set; }
private int nBaseItems = 5;
private MenuItem mnuAlpha;
private MenuItem mnuShowAll;
#endregion "Private Properties"
protected override void OnAttached()
{
base.OnAttached();
dataGrid = this.AssociatedObject as DataGrid;
if (DataGridName == null) DataGridName = dataGrid.Name;
ContextMenu_BuildStaticMenu();
dataGrid.Loaded += (sender, e) => { DataGrid_Loaded(); };
dataGrid.AutoGeneratedColumns += DataGrid_AutoGeneratedColumns;
dataGrid.ColumnReordered += (sender, e) => { DataGrid_ColumnReordered(sender); };
dataGrid.ContextMenuOpening += (sender, e) => { DataGrid_ContextMenuOpening(e); };
dataGrid.LoadingRow += (sender, e) => { e.Row.Header = (e.Row.GetIndex() + 1).ToString(); };
dataGrid.RowHeaderWidth = 0;
var ns = new Style(typeof(DataGridRowHeader)) { BasedOn = dataGrid.RowHeaderStyle };
ns.Setters.Add(new Setter(Control.FontWeightProperty, FontWeights.Bold));
ns.Setters.Add(new Setter(Control.FontSizeProperty, 11.0));
ns.Setters.Add(new Setter(Control.PaddingProperty, new Thickness(4, 0, 4, 0)));
dataGrid.RowHeaderStyle = ns;
}
private void DataGrid_AutoGeneratedColumns(object sender, EventArgs e)
{
DataGrid_Loaded();
}
#region "DataGrid Events"
private void DataGrid_ContextMenuOpening(ContextMenuEventArgs e)
{
var dep = (DependencyObject)e.OriginalSource;
// iteratively traverse the visual tree
while ((dep != null) && !(dep is DataGridCell) && !(dep is DataGridColumnHeader))
dep = VisualTreeHelper.GetParent(dep);
if (dep == null)
return;
if (dep is DataGridColumnHeader)
{
// do something
}
if (dep is DataGridCell)
{
// navigate further up the tree
while ((dep != null) && !(dep is DataGridRow))
dep = VisualTreeHelper.GetParent(dep);
var row = dep as DataGridRow;
dataGrid.ItemContainerGenerator.IndexFromContainer(row);
}
}
private void DataGrid_ColumnReordered(object sender)
{
Settings_SaveDisplayIndexes(sender);
ContextMenu_BuildMenu();
}
private void DataGrid_Loaded()
{
ContextMenu_BuildMenu(false);
VisibleColumns_Initialize();
}
#endregion "DataGrid Events"
#region "ContextMenu Methods and Events"
private void ContextMenu_BuildStaticMenu()
{
theContextMenu = new ContextMenu { FontSize = 11, StaysOpen = true };
mnuAlpha = new MenuItem
{
Header = ContextMenuChoices.Split(',')[0],
FontWeight = FontWeights.Bold,
IsCheckable = true,
StaysOpenOnClick = true
};
mnuAlpha.Click += (sender, e) => { ContextMenu_BuildMenu(); };
mnuShowAll = new MenuItem
{
Header = ContextMenuChoices.Split(',')[1],
FontWeight = FontWeights.Bold,
IsCheckable = true,
StaysOpenOnClick = true
};
mnuShowAll.Checked += (sender, e) => { VisibleColumns = AllColumnsHeaders; };
mnuShowAll.Click += (sender, e) =>
{
if (mnuShowAll.IsChecked == false) VisibleColumns_Initialize();
};
theContextMenu.Items.Add(mnuShowAll);
theContextMenu.Items.Add(mnuAlpha);
}
private void ContextMenu_BuildMenu(bool pbRebuild = true)
{
for (int i = theContextMenu.Items.Count - 1; i > 0; i--)
if (((MenuItem)theContextMenu.Items[i]).FontWeight != FontWeights.Bold)
theContextMenu.Items.Remove(theContextMenu.Items[i]);
nBaseItems = theContextMenu.Items.Count;
// Attach the context menu to the DataGrid ColumnHeaders
var headersPresenter = WpfDataGridConfigurationBehaviorFinder.FindChild<DataGridColumnHeadersPresenter>(dataGrid);
ContextMenuService.SetContextMenu(headersPresenter, theContextMenu);
if (VisibleColumns == null)
throw (new SettingsPropertyNotFoundException("User's VisibleColumns setting not found."));
// Get the current column ordering from user.config
if (DisplayIndexes == null)
throw (new SettingsPropertyNotFoundException("User's DisplayIndexes setting not found."));
AllColumnDisplayIndexes = DisplayIndexes;
string[] colIndexes = AllColumnDisplayIndexes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var dataGridColumns = dataGrid.Columns.OrderBy(x => x.DisplayIndex);
// Sort the columns in display index order so menu header order matchs display column order
if (pbRebuild == false)
// Initially the datagrid column order is such that display indexes are the same as the col indexes
if (colIndexes.Length > 0)
dataGridColumns = dataGrid.Columns.OrderBy(x => Convert.ToInt16(colIndexes[x.DisplayIndex]));
if (mnuAlpha.IsChecked)
dataGridColumns = dataGrid.Columns.OrderBy(x => x.Header.ToString());
AllColumnsHeaders = "";
foreach (var col in dataGridColumns)
{
// All column name to a list of all column headers for later use.
AllColumnsHeaders = $"{col.Header.ToString().Replace("\n", " ").Replace("\r", " ")};{AllColumnsHeaders}";
// Add new menu item in display order.
ContextMenu_AddNewMenuItem(col);
}
string sTemp = VisibleColumns;
VisibleColumns = null;
VisibleColumns = sTemp;
}
private void ContextMenu_AddNewMenuItem(DataGridColumn col)
{
var menuItem = new MenuItem { Header = col.Header.ToString().Replace("\n", " ").Replace("\r", " "), StaysOpenOnClick = true };
var saVisibleColumns = new List<string> { string.Empty };
if (VisibleColumns != null)
{
saVisibleColumns = VisibleColumns.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList();
}
menuItem.IsChecked = (saVisibleColumns.Contains(menuItem.Header));
menuItem.Click += (sender, e) => { ContextMenu_ColumnName_Click(sender); };
theContextMenu.Items.Add(menuItem);
}
private void ContextMenu_ColumnName_Click(object sender)
{
var mi = sender as MenuItem;
// Get the column name that was clicked
string colName = mi.Header.ToString();
// Capture new visible columns list
Settings_SaveVisibleColumns(mi, colName);
}
#endregion "ContextMenu Methods and Events"
#region "Settings Methods"
private void Settings_SaveVisibleColumns(MenuItem mi, string colName)
{
if (theContextMenu.Items.Count - nBaseItems < dataGrid.Columns.Count)
return;
// Put the visible column names into an array
var saVisibleColumns = VisibleColumns.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList();
if (saVisibleColumns != null && saVisibleColumns.Count > 0 &&
saVisibleColumns[saVisibleColumns.Count - 1].StartsWith("-"))
saVisibleColumns.RemoveAt(saVisibleColumns.Count - 1);
// If the menu item is unchecked (column is not visible)
if (!mi.IsChecked)
// Make the column visible by adding its name to the Visible Columns list
saVisibleColumns.Add(colName);
else
// Hide the column by removing its name from the VisibleColumns list
if (saVisibleColumns.Contains(colName) && saVisibleColumns.Count > 1)
saVisibleColumns.Remove(colName);
VisibleColumns = string.Join(";", saVisibleColumns) + ";";
}
private void Settings_SaveDisplayIndexes(object sender)
{
// Capture the new column order
AllColumnDisplayIndexes = "";
foreach (DataGridColumn col in ((DataGrid)sender).Columns)
{
AllColumnDisplayIndexes +=
(AllColumnDisplayIndexes.Length > 0 ? ";" : "") + col.DisplayIndex;
}
DisplayIndexes = AllColumnDisplayIndexes;
}
#endregion "Settings Methods"
#region DisplayIndexes (DependencyProperty)
public string DisplayIndexes
{
get { return (string)GetValue(DisplayIndexesProperty); }
set { SetValue(DisplayIndexesProperty, value); }
}
public static readonly DependencyProperty DisplayIndexesProperty =
DependencyProperty.Register("DisplayIndexes", typeof(string), typeof(WpfDataGridConfigurationBehavior), new PropertyMetadata("", OnDisplayIndexesChanged));
private static void OnDisplayIndexesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((WpfDataGridConfigurationBehavior)d).DisplayIndexesChanged(e);
}
public void DisplayIndexesChanged(DependencyPropertyChangedEventArgs e)
{
// Persist the new column order
if (dataGrid != null && !DontPersistColumnsOrder)
Settings_Save(DataGridName + "DisplayIndexes", AllColumnDisplayIndexes);
}
#endregion "DisplayIndexes (DependencyProperty)"
#region VisibleColumns (DependencyProperty)
/// <summary>
///
/// Gets or sets a value indicating the names of columns
/// (as they appear in the column header) to be visible, seperated by a semicolon.
///
/// Columns whose names are not here will be hidden.
/// </summary>
public string VisibleColumns
{
get { return (string)GetValue(VisibleColumnsProperty); }
set { SetValue(VisibleColumnsProperty, value); }
}
private void VisibleColumns_Initialize()
{
// Get saved VisibleColumns from app.config
// Initialize VisibleColumns
VisibleColumns = string.IsNullOrEmpty(VisibleColumns.Replace("\n", " ").Replace("\r", " ")) ? AllColumnsHeaders : VisibleColumns;
}
public static readonly DependencyProperty VisibleColumnsProperty =
DependencyProperty.Register("VisibleColumns", typeof(string), typeof(WpfDataGridConfigurationBehavior), new PropertyMetadata("", OnVisibleColumnsChanged));
private static void OnVisibleColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((WpfDataGridConfigurationBehavior)d).VisibleColumnsChanged(e);
}
/// <summary>
///
/// Updates the display
///
/// </summary>
/// <param name="e"></param>
public void VisibleColumnsChanged(DependencyPropertyChangedEventArgs e)
{
if (theContextMenu == null)
return;
if (e.NewValue != null)
{
var showTheseColumns = e.NewValue.ToString().Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var saContextMenuVisibleItems = new List<string>();
int iCol = 0;
foreach (MenuItem menuItem in theContextMenu.Items)
{
// Show/Hide the Context Menu item's checkmark.
if (menuItem.FontWeight == FontWeights.Bold) continue;
menuItem.IsChecked = showTheseColumns.Contains(menuItem.Header.ToString());
if (menuItem.IsChecked) saContextMenuVisibleItems.Add(menuItem.Header.ToString());
mnuShowAll.IsChecked = mnuShowAll.IsChecked && menuItem.IsChecked;
// Assign menu item's column's DisplayIndex in display order, (i.e. in menu item order), looking up each column by header name.)
if (mnuAlpha.IsChecked == false)
dataGrid.Columns.First(x => x.Header.ToString().Replace("\n", " ").Replace("\r", " ") == menuItem.Header.ToString()).DisplayIndex = iCol++;
}
// Show the columns
foreach (var col in dataGrid.Columns)
col.Visibility =
showTheseColumns.Contains(col.Header.ToString().Replace("\n", " ").Replace("\r", " "))
&& (saContextMenuVisibleItems.Contains(col.Header.ToString().Replace("\n", " ")
.Replace("\r", " ")))
? Visibility.Visible
: Visibility.Collapsed;
// Persist the new visible columns list
if (dataGrid != null && !DontPersistVisibleColumns)
Settings_Save(DataGridName + "VisibleColumns", VisibleColumns);
}
}
#endregion "VisibleColumns"
public static void Settings_Save(string propertyName, string propertyValue)
{
foreach (SettingsPropertyValue property in Settings.Default.PropertyValues)
{
if (propertyName == property.Name)
{
property.PropertyValue = propertyValue;
Settings.Default.Save();
}
}
}
}
public static class WpfDataGridConfigurationBehaviorFinder
{
public static T FindChild<T>(DependencyObject depObj) where T : DependencyObject
{
// Confirm obj is valid.
if (depObj == null) return null;
// success case
if (depObj is T) return depObj as T;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
T obj = FindChild<T>(VisualTreeHelper.GetChild(depObj, i));
if (obj != null) return obj;
}
return null;
}
public interface IBreakVisualParenting
{
DependencyObject Parent { get; }
}
public static T LastVisualAncestorOfType<T>(this DependencyObject element) where T : DependencyObject
{
T item = null;
var parent = VisualTreeHelper.GetParent(element);
while (parent != null)
{
if (parent is T)
item = (T)parent;
if (parent is IBreakVisualParenting)
{
parent = ((IBreakVisualParenting)parent).Parent;
}
else
parent = VisualTreeHelper.GetParent(parent);
}
return item;
}
}
}

Related

WPF: DataGridRow select

How to clean the previously selected row ?
I have this method that search for item with a given id.
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
foreach (MessageFieldViewModel rowItem in Datagrid.ItemsSource)
{
if (_mainWindowModel != null)
_mainWindowModel.SelectedMessageElement = (MessageElementViewModel)e.NewValue;
var row = Datagrid.ItemContainerGenerator.ContainerFromItem(rowItem) as DataGridRow;
if (_mainWindowModel != null && _mainWindowModel.SelectedMessageElement != null)
{
if (rowItem.Id == _mainWindowModel.SelectedMessageElement.Id)
{
if (row != null)
row.Background = Brushes.DarkSalmon;
}
row.Background.ClearValue();
}
// if (item != null) row.Background.ClearValue(rowItem.Id);
// break;
}
This selects the row of a given id. but if I want to select another Id The previous id is still selected.
how can I remove previously selected Ids and show only the newly selected id?
ok so here we go
XAML
<TreeView Name="Tree" Width="50" Height="100"
ItemsSource="{Binding YourTree}"
SelectedItemChanged="Tree_SelectedItemChanged"
SelectedValuePath="yourIdProperty"/>
<DataGrid Name="myDatagrid" Width="100" Height="100"
ItemsSource="{Binding YourList}"
SelectedItem="{Binding YourSelectedItem,UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Id"/>
Codebehind
private void Tree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var tree = sender as TreeView;
myDatagrid.SelectedValue = tree.SelectedValue;
}
ViewModel
public List<yourClass> YourList { get; set; }
private yourClass yourSelectedItem;
public yourClass YourSelectedItem
{
get { return yourSelectedItem; }
set
{
yourSelectedItem = value;
OnPropertyChanged("YourSelectedItem");
}
}
you need to set the SelectedValuePath based your Property which represent your Id but not as Binding just as string as example "Id"

How to set selected item of a DataGrid programmatically in WPF with MVVM application?

I have bound the DataTable to the DataGrid control. How can I set the selected item programmatically ?
Example
In my view model I have a property of type DataTable to bind the DataGrid
private DataTable sizeQuantityTable;
public DataTable SizeQuantityTable
{
get
{
return sizeQuantityTable;
}
set
{
sizeQuantityTable = value;
NotifyPropertyChanged("SizeQuantityTable");
}
}
My XAML
<DataGrid
ItemsSource="{Binding SizeQuantityTable}"
AutoGenerateColumns="True"
Margin="0,0,0,120" />
The constructor of the view model (assigning dummy values)
this.SizeQuantityTable = new DataTable();
DataColumn sizeQuantityColumn = new DataColumn();
sizeQuantityColumn.ColumnName = "Size Quantity";
this.SizeQuantityTable.Columns.Add(sizeQuantityColumn);
DataColumn sColumn = new DataColumn();
sColumn.ColumnName = "S";
this.SizeQuantityTable.Columns.Add(sColumn);
DataColumn mColumn = new DataColumn();
mColumn.ColumnName = "M";
this.SizeQuantityTable.Columns.Add(mColumn);
DataRow row1 = this.SizeQuantityTable.NewRow();
row1[sizeQuantityColumn] = "Blue";
row1[sColumn] = "12";
row1[mColumn] = "15";
this.SizeQuantityTable.Rows.Add(row1);
DataRow row2 = this.SizeQuantityTable.NewRow();
row2[sizeQuantityColumn] = "Red";
row2[sColumn] = "18";
row2[mColumn] = "21";
this.SizeQuantityTable.Rows.Add(row2);
DataRow row3 = this.SizeQuantityTable.NewRow();
row3[sizeQuantityColumn] = "Green";
row3[sColumn] = "24";
row3[mColumn] = "27";
this.SizeQuantityTable.Rows.Add(row3);
OK. I have created three columns namely sizeQuantityColumn, sColumn and mColumn and added three rows namely row1, row2 and row2.
So, Let's say I wanna set the selected item as row2 (So in the view, the second row should be highlighted).
How can I do this?
EDIT
I hardcoded the SelectedIndex of the DataGrid to 1. (So the second row should be selected). In design time it shows as selected. But not in the run time. You can see it in the below snapshot.
So ultimaltely the problem is Not highlighting the row.
There are a few way to select items in the DataGrid. It just depends which one works best for the situation
First and most basic is SelectedIndex this will just select the Row at that index in the DataGrid
<DataGrid SelectedIndex="{Binding SelectedIndex}" />
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set { _selectedIndex = value; NotifyPropertyChanged("SelectedIndex"); }
}
SelectedIndex = 2;
SelectedItem will select the row that matches the row you set
<DataGrid SelectedItem="{Binding SelectedRow}" />
private DataRow _selectedRow;
public DataRow SelectedRow
{
get { return _selectedRow; }
set { _selectedRow = value; NotifyPropertyChanged("SelectedRow");}
}
SelectedRow = items.First(x => x.whatever == something);
The most common one is SelectedValue with SelectedValuePath set, in this case you set the column you want to select with and then to can select the row by setting the corresponding value
<DataGrid SelectedValuePath="Size Quantity" SelectedValue="{Binding SelectionValue}"
private string _selectedValue
public string SelectionValue
{
get { return _selectedValue; }
set { _selectedValue = value; NotifyPropertyChanged("SelectionValue"); }
}
SelectionValue = "Blue";
Edit:
Here is my test and it is highlighting just fine
Code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.SizeQuantityTable = new DataTable();
DataColumn sizeQuantityColumn = new DataColumn();
sizeQuantityColumn.ColumnName = "Size Quantity";
...................
........
}
private string _selectedValue;
public string SelectionValue
{
get { return _selectedValue; }
set { _selectedValue = value; NotifyPropertyChanged("SelectionValue"); }
}
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set { _selectedIndex = value; NotifyPropertyChanged("SelectedIndex"); }
}
private DataTable sizeQuantityTable;
public DataTable SizeQuantityTable
{
get { return sizeQuantityTable; }
set { sizeQuantityTable = value; NotifyPropertyChanged("SizeQuantityTable"); }
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
SelectedIndex = 2;
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
SelectionValue = "Blue";
}
private void NotifyPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
}
Xaml:
<Window x:Class="WpfApplication21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="202" Width="232" Name="UI">
<Grid DataContext="{Binding ElementName=UI}">
<DataGrid SelectedValuePath="Size Quantity"
SelectedValue="{Binding SelectionValue}"
SelectedIndex="{Binding SelectedIndex}"
ItemsSource="{Binding SizeQuantityTable}"
AutoGenerateColumns="True"
Margin="0,0,0,41" />
<StackPanel Orientation="Horizontal" Height="37" VerticalAlignment="Bottom" >
<Button Content="SelectedIndex" Height="26" Width="107" Click="Button_Click_1"/>
<Button Content="SelectedValue" Height="26" Width="107" Click="Button_Click_2"/>
</StackPanel>
</Grid>
</Window>
Result:
You can always use SelectedItem property and bind it against row, as such:
SelectedItem="{Binding ActiveRow}"
and in ViewModel do:
ActiveRow = secondRow;
Add SelectedItem, SelectedValue in your DataGrid.
<DataGrid
ItemsSource="{Binding SizeQuantityTable}"
AutoGenerateColumns="True"
SelectedValue ="{Binding SelectedValue}"
Margin="0,0,0,120" />
And in your view model
private string _selectedValue;
public string SelectedValue
{
get
{
return _selectedValue;
}
set
{
_selectedValue = value;
NotifyPropertyChanged("SelectedValue");
}
}
private DataTable sizeQuantityTable;
public DataTable SizeQuantityTable
{
get
{
return sizeQuantityTable;
}
set
{
sizeQuantityTable = value;
NotifyPropertyChanged("SizeQuantityTable");
}
}
You can use SelectedItem as well, that is preferred.
I just had the same problem.
I saw that the item of a datagrid was selected correctly at design time, but not at runtime. (By the way, I create the instance of the view model in the xaml).
I could solve this problem by moving the code to programmatically set the selected item from the view models constructor to a different method in the view model and then calling this method in the loaded event of the window (or usercontrol).
Obviously the view is not completely done initializing itself when the view models constructor is called.
This can be avoided by not coding in the view models constructor.
View (xaml):
<Window x:Class="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels" Loaded="Window_Loaded">
<Window.DataContext>
<viewModel:ExampleViewModel/>
</Window.DataContext>
View (code behind):
private void Window_Loaded(object sender, RoutedEventArgs e)
{
((ExampleViewModel)this.DataContext).LoadData();
}
If you do not like setting up the Loaded event in the code behind, you can also do it in xaml (references to "Microsoft.Expression.Interactions" and "System.Windows.Interactivity" are needed):
<Window x:Class="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels">
<Window.DataContext>
<viewModel:ExampleViewModel/>
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<ei:CallMethodAction TargetObject="{Binding}" MethodName="LoadData"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In each case, you call the LoadData method in the ViewModel:
public class ExampleViewModel
{
/// <summary>
/// Constructor.
/// </summary>
public ExampleViewModel()
{
// Do NOT set selected item here
}
public void LoadData()
{
// Set selected item here
}
For anyone using Observable Collections you may find this solution useful.
The SelectedModelIndex property simply returns the index of the SelectedModel from the ItemSource collection. I've found setting the SelectedIndex along with the SelectedItem highlights the row in the DataGrid.
private ObservableCollection<Model> _itemSource
public ObservableCollection<Model> ItemSource
{
get { return _itemSource; }
set
{
_itemSource = value;
OnPropertyChanged("ItemSource");
}
}
// Binding must be set to One-Way for read-only properties
public int SelectedModelIndex
{
get
{
if (ItemSource != null && ItemSource.Count > 0)
return ItemSource.IndexOf(SelectedModel);
else
return -1;
}
}
private Model _selectedModel;
public Model SelectedModel
{
get { return _selectedModel; }
set
{
_selectedModel = value;
OnPropertyChanged("SelectedModel");
OnPropertyChanged("SelectedModelIndex");
}
}
xaml:
<DataGrid SelectionUnit="FullRow" >
</DataGrid>
code
SelectRowByIndex(yourDataGridViewName, 0);
public static void SelectRowByIndex(DataGrid dataGrid, int rowIndex)
{
if (!dataGrid.SelectionUnit.Equals(DataGridSelectionUnit.FullRow))
throw new ArgumentException("The SelectionUnit of the DataGrid must be set to FullRow.");
if (rowIndex < 0 || rowIndex > (dataGrid.Items.Count - 1))
throw new ArgumentException(string.Format("{0} is an invalid row index.", rowIndex));
dataGrid.SelectedItems.Clear();
/* set the SelectedItem property */
object item = dataGrid.Items[rowIndex]; // = Product X
dataGrid.SelectedItem = item;
DataGridRow row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
if (row == null)
{
/* bring the data item (Product object) into view
* in case it has been virtualized away */
dataGrid.ScrollIntoView(item);
row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
}
//TODO: Retrieve and focus a DataGridCell object
if (row != null)
{
DataGridCell cell = GetCell(dataGrid, row, 0);
if (cell != null)
cell.Focus();
}
}
public static DataGridCell GetCell(DataGrid dataGrid, DataGridRow rowContainer, int column)
{
if (rowContainer != null)
{
DataGridCellsPresenter presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
if (presenter == null)
{
/* if the row has been virtualized away, call its ApplyTemplate() method
* to build its visual tree in order for the DataGridCellsPresenter
* and the DataGridCells to be created */
rowContainer.ApplyTemplate();
presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
}
if (presenter != null)
{
DataGridCell cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
if (cell == null)
{
/* bring the column into view
* in case it has been virtualized away */
dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns[column]);
cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
}
return cell;
}
}
return null;
}
public static childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
foreach (childItem child in FindVisualChildren<childItem>(obj))
{
return child;
}
return null;
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj)
where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}

Treeview with checkbox bind by different lists

I have a class like this :
public class Project
{
List<Object> list1;
List<Object> list2;
}
I want to show this in a treeview control like as follows :
Checkbox + "Listing1"
--> Checkbox + Object 1 of list1
--> Checkbox + Object 2 of list1
Checkbox + "Listing2"
--> Checkbox + Object 1 of list2
-->Checkbox + Object 2 of list2
My biggest problem is making the difference between the 2 lists + some extra : if list2 does not contain any objects, the "Listing2" header may not be shown.
Does anybody have any good idea how I can do this ?
You can create one class TreeViewItemWithCheckbox by extending TreeViewItem class like below :
public class TreeViewItemWithCheckbox : TreeViewItem
{
#region Variable Declaration
CheckBox chkBox = new CheckBox();
StackPanel stpContent = new StackPanel();
#endregion
#region Properties
public string HeaderText
{
get { return chkBox.Content.ToString(); }
set { chkBox.Content = value; }
}
public bool IsChecked
{
get { return chkBox.IsChecked.Value; }
set { chkBox.IsChecked = value; }
}
#endregion
#region Constructor
public TreeViewItemWithCheckbox()
{
stpContent.Orientation = Orientation.Horizontal;
chkBox = new CheckBox();
chkBox.VerticalAlignment = VerticalAlignment.Center;
chkBox.Click += new RoutedEventHandler(SetCheckboxes);
chkBox.Margin = new Thickness(0, 0, 0, 0);
stpContent.Children.Add(chkBox);
Header = stpContent;
}
#endregion
#region Event Handlers
private void SetCheckboxes(object sender, RoutedEventArgs e)
{
TreeViewItemWithCheckbox selectedItem = ((TreeViewItemWithCheckbox)((StackPanel)((CheckBox)sender).Parent).Parent);
if (selectedItem != null)
{
/* Set checkboxes for all child items */
if (selectedItem.Items.Count > 0)
{
SetChildItemCheckboxes(selectedItem, selectedItem.IsChecked);
}
/* Check if all childs checked then check/uncheck parent accoringly */
if (selectedItem.Parent.GetType() == typeof(TreeViewItemWithCheckbox))
{
TreeViewItemWithCheckbox parentItem = (TreeViewItemWithCheckbox)selectedItem.Parent;
bool bIsAllChecked = true;
foreach (TreeViewItemWithCheckbox item in parentItem.Items)
{
if (!item.IsChecked)
{
bIsAllChecked = false;
break;
}
}
parentItem.IsChecked = bIsAllChecked;
}
}
}
private void SetChildItemCheckboxes(TreeViewItemWithCheckbox item, bool IsChecked)
{
if (item.Items.Count > 0)
{
foreach (TreeViewItemWithCheckbox childItem in item.Items)
{
SetChildItemCheckboxes(childItem, IsChecked);
item.IsChecked = IsChecked;
}
}
else
item.IsChecked = IsChecked;
}
#endregion
}
Then you need to add treeview nodes from 2 list like below :
trvTest.Items.Clear();
//Add default root element
TreeViewItemWithCheckbox rootNode = new TreeViewItemWithCheckbox();
rootNode.HeaderText = "Root";
rootNode.IsExpanded = true;
trvTest.Items.Add(rootNode);
if (_project.list1.Count > 0)
{
TreeViewItemWithCheckbox nodeHead1 = new TreeViewItemWithCheckbox();
nodeHead1.HeaderText = "Listing 1";
rootNode.Items.Add(nodeHead1);
TreeViewItemWithCheckbox node1;
for (int i = 0; i < _project.list1.Count; i++)
{
node1 = new TreeViewItemWithCheckbox();
node1.HeaderText = _project.list1[i].Name;
nodeHead1.Items.Add(node1);
}
}
if (_project.list2.Count > 0)
{
TreeViewItemWithCheckbox nodeHead2 = new TreeViewItemWithCheckbox();
nodeHead2.HeaderText = "Listing 2";
rootNode.Items.Add(nodeHead2);
TreeViewItemWithCheckbox node2 = new TreeViewItemWithCheckbox();
for (int i = 0; i < _project.list2.Count; i++)
{
node2 = new TreeViewItemWithCheckbox();
node2.HeaderText = _project.list2[i].Name;
nodeHead2.Items.Add(node2);
}
}
This can be solved without TreeView - Just 2 CheckBoxes and 2 ItemsControl with specific ItemTemplate.
I prefer this way of solving this problem:
<StackPanel>
<StackPanel.Resources>
<DataTemplate x:Key="MyTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding SomeProperty}" IsChecked="{Binding SomeBooleanProperty}" />
</StackPanel>
</DataTemplate>
</StackPanel.Resources>
<CheckBox Content="List number 1" />
<ItemsControl ItemsSource="{Binding list1}" ItemTemplate="{StaticResource MyTemplate}" />
<CheckBox Content="List number 2" />
<ItemsControl ItemsSource="{Binding list2}" ItemTemplate="{StaticResource MyTemplate}" />
</StackPanel>
As for your extra: you can bind Visibility to the property in your Project class, which will be looks like:
public class Project
{
public List<Object> list1;
public List<Object> list2;
public Visibility Visuality
{
get
{
return this.list2.Any() ? Visibility.Visible : Visibility.Colapsed;
}
}
}
and than in the code:
<ItemsControl Visibility="{Binding Visuality}" ... />
<CheckBox Visibility="{Binding Visuality}" ... />
...if I understand you right...
If your first "Listing" items are static, you can do something like this
<TreeView x:Name="tv">
<TreeViewItem Header="Listing 1" ItemsSource="{Binding list1}"/>
<TreeViewItem Header="Listing 2" ItemsSource="{Binding list2}"/>
</TreeView>
To hide elements without children you need a bit of code.
var view = CollectionViewSource.GetDefaultView(_project.list1);
view.Filter = myFilter;
view.Refresh();
tv.DataContext = _project;
For both lists of course.

WP7 - Animating add/remove item in a ListBox

I know you can achieve this in Silverlight 4 by playing with the ListBoxItem style's LayoutStates, i.e. BeforeUnloaded, BeforeLoaded and AfterLoaded.
It doesn't seem to be working at all in WP7 although these states exist in the default style.
I am currently using version 7.1.
Is there any way I can get this working?
Thanks,
Xin
for this I used Artefact Animator, it's for Silverlight but works perfectly for WP7 also. The code shows only the addition. Whole code from the project's sample page.
MainPage.xaml
<UserControl.Resources>
<!-- ADDS SMOOTH SCROLL -->
<ItemsPanelTemplate x:Key="ItemsPanelTemplate">
<StackPanel/>
</ItemsPanelTemplate>
</UserControl.Resources>
<Grid>
<ListBox x:Name="lb" Height="247" Width="100" ItemsPanel="{StaticResource ItemsPanelTemplate}" />
<Button x:Name="addBtn" Content="Add" Height="72" HorizontalAlignment="Left" Margin="159,145,0,0" VerticalAlignment="Top" Width="160" />
</Grid>
MainPage.xaml.cs
public partial class MainPage : PhoneApplicationPage
{
private static ScrollViewer _scrollViewer;
// Constructor
public MainPage()
{
InitializeComponent();
Loaded += MainPage_Loaded;
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
// INIT
lb.Items.Clear();
lb.UpdateLayout();
// SCROLL INTERACTION
_scrollViewer = FindVisualChild<ScrollViewer>(lb);
var bar = FindVisualChild<ScrollBar>(_scrollViewer);
if (bar != null)
bar.ValueChanged += (s, args) => SetValue(ListBoxScrollOffsetProperty, args.NewValue);
// INPUT
addBtn.Click += (s, args) => AddItem();
}
private void AddItem()
{
// Create New ListBoxItem
var lbi = new ListBoxItem
{
Content = "Item " + lb.Items.Count,
RenderTransform = new CompositeTransform
{
TranslateX = -lb.Width
},
};
// Add ListBoxItem
lb.Items.Add(lbi);
lb.UpdateLayout();
// Animate In Item
ArtefactAnimator.AddEase(lbi.RenderTransform, CompositeTransform.TranslateXProperty, 0, 1, AnimationTransitions.CubicEaseOut, 0);
ArtefactAnimator.AddEase(this, ListBoxScrollOffsetProperty, _scrollViewer.ScrollableHeight, .8, AnimationTransitions.CubicEaseOut, 0);
}
// LISTBOX SCROLL OFFSET
public static readonly DependencyProperty ListBoxScrollOffsetProperty =
DependencyProperty.Register("ListBoxScrollOffset", typeof(double), typeof(MainPage), new PropertyMetadata(0.0, OnListBoxScrollOffsetChanged));
private static void OnListBoxScrollOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
_scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}
public double ListBoxScrollOffset
{
get
{
return (double)GetValue(ListBoxScrollOffsetProperty);
}
set
{
SetValue(ListBoxScrollOffsetProperty, value);
}
}
// VISUAL HELPER
public static childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject
{
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
{
return (childItem)child;
}
else
{
var childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return null;
}
}

WPF DataGrid: Binding DataGridColumn visibility to ContextMenu MenuItems IsChecked (MVVM)

I want to control DataGrid column visibility through a ContextMenu available to the user by right-clicking the column header. The ContextMenu displays the names of all available columns. I am using MVVM design pattern.
My question is: How do I bind the DataGridColumn's Visibility property to the IsChecked property of a MenuItem located in the ContextMenu.
Some mockup code:
<UserControl.Resources>
<ContextMenu x:Key="ColumnHeaderContextMenu">
<MenuItem Header="Menu Item..1" IsCheckable="True" />
</ContextMenu>
<Style x:Key="ColumnHeaderStyle"
TargetType="{x:Type toolkit:DataGridColumnHeader}">
<Setter Property="ContextMenu"
Value="{StaticResource ColumnHeaderContextMenu}" />
</Style>
<BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" />
</UserControl.Resources>
...flaf flaf flaf
<toolkit:DataGrid x:Name="MyGrid" AutoGenerateColumns="False"
ItemsSource="{Binding MyCollection, Mode=Default}"
EnableColumnVirtualization="True" IsReadOnly="True"
ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTextColumn Binding="{Binding Path=MyEntry}"
Header="MyEntry" Visibility="{Binding IsChecked, Converter=
{StaticResource booleanToVisibilityConverter}.... />
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
If I am being unclear please let me know and I will attempt to elaborate.
Cheers,
Updated 2021-09-29: Pasted the code from my old blog. I haven't worked with WPF in many years I know nothing about it anymore. This site has limits on post length so I had to share some of the example code using CodePile. See the links at bottom for usage. I believe the idea was the sample I made works in many scenarios versus just auto generated columns. I was working on a project where the columns were not known until runtime and could change dynamically. Also note there was a png file for the "eye" graphic, you'll need your own probably.
DataGridAPs.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
using System.Windows.Media;
using Microsoft.Windows.Controls;
using Microsoft.Windows.Controls.Primitives;
namespace CanUserHideColumnDemo
{
public static class DataGridAPs
{
#region HideColumns
#region HideColumnsHeader
public static readonly DependencyProperty HideColumnsHeaderProperty =
DependencyProperty.RegisterAttached("HideColumnsHeader",
typeof(object), typeof(DataGridAPs));
public static object GetHideColumnsHeader(DataGrid obj)
{
return obj.GetValue(HideColumnsHeaderProperty);
}
public static void SetHideColumnsHeader(DataGrid obj, object value)
{
obj.SetValue(HideColumnsHeaderProperty, value);
}
#endregion HideColumnsHeader
#region HideColumnsHeaderTemplate
public static readonly DependencyProperty HideColumnsHeaderTemplateProperty =
DependencyProperty.RegisterAttached("HideColumnsHeaderTemplate",
typeof(DataTemplate), typeof(DataGridAPs));
public static DataTemplate GetHideColumnsHeaderTemplate(DataGrid obj)
{
return (DataTemplate)obj.GetValue(HideColumnsHeaderTemplateProperty);
}
public static void SetHideColumnsHeaderTemplate(DataGrid obj, DataTemplate value)
{
obj.SetValue(HideColumnsHeaderTemplateProperty, value);
}
#endregion HideColumnsHeaderTemplate
#region HideColumnsIcon
public static readonly DependencyProperty HideColumnsIconProperty =
DependencyProperty.RegisterAttached("HideColumnsIcon",
typeof(object), typeof(DataGridAPs));
public static object GetHideColumnsIcon(DataGrid obj)
{
return obj.GetValue(HideColumnsIconProperty);
}
public static void SetHideColumnsIcon(DataGrid obj, object value)
{
obj.SetValue(HideColumnsIconProperty, value);
}
#endregion HideColumnsIcon
#region CanUserHideColumns
public static readonly DependencyProperty CanUserHideColumnsProperty =
DependencyProperty.RegisterAttached("CanUserHideColumns",
typeof(bool), typeof(DataGridAPs),
new UIPropertyMetadata(false, OnCanUserHideColumnsChanged));
public static bool GetCanUserHideColumns(DataGrid obj)
{
return (bool)obj.GetValue(CanUserHideColumnsProperty);
}
public static void SetCanUserHideColumns(DataGrid obj, bool value)
{
obj.SetValue(CanUserHideColumnsProperty, value);
}
private static void OnCanUserHideColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataGrid dataGrid = d as DataGrid;
if (dataGrid == null)
return;
if ((bool)e.NewValue == false)
{
dataGrid.Loaded -= new RoutedEventHandler(dataGrid_Loaded);
RemoveAllItems(dataGrid);
return;
}
if (!dataGrid.IsLoaded)
{
dataGrid.Loaded -= new RoutedEventHandler(dataGrid_Loaded);
dataGrid.Loaded += new RoutedEventHandler(dataGrid_Loaded);
}
else
SetupColumnHeaders(dataGrid);
}
private static void dataGrid_Loaded(object sender, RoutedEventArgs e)
{
DataGrid dataGrid = sender as DataGrid;
if (dataGrid == null)
return;
if (BindingOperations.IsDataBound(dataGrid, DataGrid.ItemsSourceProperty))
{
Binding b = BindingOperations.GetBinding(dataGrid, DataGrid.ItemsSourceProperty);
dataGrid.TargetUpdated += new EventHandler<DataTransferEventArgs>(dataGrid_TargetUpdated);
string xaml = XamlWriter.Save(b);
Binding b2 = XamlReader.Parse(xaml) as Binding;
if (b2 != null)
{
b2.NotifyOnTargetUpdated = true;
BindingOperations.ClearBinding(dataGrid, DataGrid.ItemsSourceProperty);
BindingOperations.SetBinding(dataGrid, DataGrid.ItemsSourceProperty, b2);
}
}
else
SetupColumnHeaders(dataGrid);
}
private static void dataGrid_TargetUpdated(object sender, DataTransferEventArgs e)
{
if (e.Property != DataGrid.ItemsSourceProperty)
return;
DataGrid dataGrid = sender as DataGrid;
if (dataGrid == null)
return;
EventHandler handler = null;
handler = delegate
{
RemoveAllItems(dataGrid);
if (SetupColumnHeaders(dataGrid))
dataGrid.LayoutUpdated -= handler;
};
dataGrid.LayoutUpdated += handler;
}
private static DataGridColumnHeader[] GetColumnHeaders(DataGrid dataGrid)
{
if (dataGrid == null)
return null;
dataGrid.UpdateLayout();
DataGridColumnHeader[] columnHeaders = CustomVisualTreeHelper<DataGridColumnHeader>.FindChildrenRecursive(dataGrid);
return (from DataGridColumnHeader columnHeader in columnHeaders
where columnHeader != null && columnHeader.Column != null
select columnHeader).ToArray();
}
private static string GetColumnName(DataGridColumn column)
{
if (column == null)
return string.Empty;
if (column.Header != null)
return column.Header.ToString();
else
return string.Format("Column {0}", column.DisplayIndex);
}
private static MenuItem GenerateItem(DataGrid dataGrid, DataGridColumn column)
{
if (column == null)
return null;
MenuItem item = new MenuItem();
item.Tag = column;
item.Header = GetColumnName(column);
if (string.IsNullOrEmpty(item.Header as string))
return null;
item.ToolTip = string.Format("Toggle column '{0}' visibility.", item.Header);
item.IsCheckable = true;
item.IsChecked = column.Visibility == Visibility.Visible;
item.Checked += delegate
{
SetItemIsChecked(dataGrid, column, true);
};
item.Unchecked += delegate
{
SetItemIsChecked(dataGrid, column, false);
};
return item;
}
public static MenuItem[] GetAttachedItems(DataGridColumnHeader columnHeader)
{
if (columnHeader == null || columnHeader.ContextMenu == null)
return null;
ItemsControl itemsContainer = (from object i in columnHeader.ContextMenu.Items
where i is MenuItem && ((MenuItem)i).Tag != null && ((MenuItem)i).Tag.ToString() == "ItemsContainer"
select i).FirstOrDefault() as MenuItem;
if (itemsContainer == null)
itemsContainer = columnHeader.ContextMenu;
return (from object i in itemsContainer.Items
where i is MenuItem && ((MenuItem)i).Tag is DataGridColumn
select i).Cast<MenuItem>().ToArray();
}
private static DataGridColumn GetColumnFromName(DataGrid dataGrid, string columnName)
{
if (string.IsNullOrEmpty(columnName))
return null;
foreach (DataGridColumn column in dataGrid.Columns)
{
if (GetColumnName(column) == columnName)
return column;
}
return null;
}
private static DataGridColumnHeader GetColumnHeaderFromColumn(DataGrid dataGrid, DataGridColumn column)
{
if (dataGrid == null || column == null)
return null;
DataGridColumnHeader[] columnHeaders = GetColumnHeaders(dataGrid);
return (from DataGridColumnHeader columnHeader in columnHeaders
where columnHeader.Column == column
select columnHeader).FirstOrDefault();
}
public static void RemoveAllItems(DataGrid dataGrid)
{
if (dataGrid == null)
return;
foreach (DataGridColumn column in dataGrid.Columns)
{
RemoveAllItems(dataGrid, column);
}
}
public static void RemoveAllItems(DataGrid dataGrid, DataGridColumn column)
{
if (dataGrid == null || column == null)
return;
DataGridColumnHeader columnHeader = GetColumnHeaderFromColumn(dataGrid, column);
List<MenuItem> itemsToRemove = new List<MenuItem>();
if (columnHeader == null)
return;
// Mark items and/or items container for removal.
if (columnHeader.ContextMenu != null)
{
foreach (object item in columnHeader.ContextMenu.Items)
{
if (item is MenuItem && ((MenuItem)item).Tag != null
&& (((MenuItem)item).Tag.ToString() == "ItemsContainer" || ((MenuItem)item).Tag is DataGridColumn))
itemsToRemove.Add((MenuItem)item);
}
}
// Remove items and/or items container.
foreach (MenuItem item in itemsToRemove)
{
columnHeader.ContextMenu.Items.Remove(item);
}
}
public static void ResetupColumnHeaders(DataGrid dataGrid)
{
RemoveAllItems(dataGrid);
SetupColumnHeaders(dataGrid);
}
private static void SetItemIsChecked(DataGrid dataGrid, DataGridColumn column, bool isChecked)
{
if (dataGrid == null || column == null)
return;
// Deny request if there are no other columns visible. Otherwise,
// they'd have no way of changing the visibility of any columns
// again.
//if (!isChecked && (from DataGridColumn c in dataGrid.Columns
// where c.Visibility == Visibility.Visible
// select c).Count() < 2)
// return;
if (isChecked && column.Visibility != Visibility.Visible)
{
ShowColumn(dataGrid, column);
}
else if (!isChecked)
column.Visibility = Visibility.Hidden;
DataGridColumnHeader[] columnHeaders = GetColumnHeaders(dataGrid);
ItemsControl itemsContainer = null;
object containerHeader = GetHideColumnsHeader(dataGrid);
foreach (DataGridColumnHeader columnHeader in columnHeaders)
{
itemsContainer = null;
if (columnHeader != null)
{
if (columnHeader.ContextMenu == null)
continue;
itemsContainer = (from object i in columnHeader.ContextMenu.Items
where i is MenuItem && ((MenuItem)i).Header == containerHeader
select i).FirstOrDefault() as MenuItem;
}
if (itemsContainer == null)
itemsContainer = columnHeader.ContextMenu;
foreach (object item in itemsContainer.Items)
{
if (item is MenuItem && ((MenuItem)item).Tag != null && ((MenuItem)item).Tag is DataGridColumn
&& ((MenuItem)item).Header.ToString() == GetColumnName(column))
{
((MenuItem)item).IsChecked = isChecked;
}
}
}
}
private static void SetupColumnHeader(DataGridColumnHeader columnHeader)
{
if (columnHeader == null)
return;
DataGrid dataGrid = CustomVisualTreeHelper<DataGrid>.FindAncestor(columnHeader);
if (dataGrid == null)
return;
DataGridColumnHeader[] columnHeaders = GetColumnHeaders(dataGrid);
if (columnHeaders == null)
return;
SetupColumnHeader(dataGrid, columnHeaders, columnHeader);
}
private static void SetupColumnHeader(DataGrid dataGrid, DataGridColumnHeader[] columnHeaders, DataGridColumnHeader columnHeader)
{
if (columnHeader.ContextMenu == null)
columnHeader.ContextMenu = new ContextMenu();
ItemsControl itemsContainer = null;
itemsContainer = columnHeader.ContextMenu;
object containerHeader = GetHideColumnsHeader(dataGrid);
if (containerHeader != null)
{
MenuItem ic = (from object i in columnHeader.ContextMenu.Items
where i is MenuItem && ((MenuItem)i).Tag != null && ((MenuItem)i).Tag.ToString() == "ItemsContainer"
select i).FirstOrDefault() as MenuItem;
if (ic == null)
{
itemsContainer = new MenuItem()
{
Header = containerHeader,
HeaderTemplate = GetHideColumnsHeaderTemplate(dataGrid) as DataTemplate,
Icon = GetHideColumnsIcon(dataGrid),
Tag = "ItemsContainer"
};
columnHeader.ContextMenu.Items.Add(itemsContainer);
}
else
return;
}
foreach (DataGridColumnHeader columnHeader2 in columnHeaders)
{
if (columnHeader2 != columnHeader
&& itemsContainer is ContextMenu
&& columnHeader2.ContextMenu == itemsContainer)
{
continue;
}
itemsContainer.Items.Add(GenerateItem(dataGrid, columnHeader2.Column));
}
}
public static bool SetupColumnHeaders(DataGrid dataGrid)
{
DataGridColumnHeader[] columnHeaders = GetColumnHeaders(dataGrid);
if (columnHeaders == null || columnHeaders.Count() == 0)
return false;
RemoveAllItems(dataGrid);
columnHeaders = GetColumnHeaders(dataGrid);
foreach (DataGridColumnHeader columnHeader in columnHeaders)
{
SetupColumnHeader(dataGrid, columnHeaders, columnHeader);
}
return true;
}
/// <summary>
/// Shows a column within the datagrid, which is not straightforward
/// because the datagrid not only hides a column when you tell it to
/// do so, but it also completely destroys its associated column
/// header. Meaning we need to set it up again. Before we can do
/// so we have to turn all columns back on again so we can get a
/// complete list of their column headers, then turn them back off
/// again.
/// </summary>
/// <param name="dataGrid"></param>
/// <param name="column"></param>
private static void ShowColumn(DataGrid dataGrid, DataGridColumn column)
{
if (dataGrid == null || column == null)
return;
column.Visibility = Visibility.Visible;
// Turn all columns on, but store their original visibility so we
// can restore it after we're done.
Dictionary<DataGridColumn, Visibility> vis = new Dictionary<DataGridColumn, Visibility>();
foreach (DataGridColumn c in dataGrid.Columns)
{
vis.Add(c, c.Visibility);
c.Visibility = Visibility.Visible;
}
dataGrid.UpdateLayout();
DataGridColumnHeader columnHeader = GetColumnHeaderFromColumn(dataGrid, column);
SetupColumnHeader(columnHeader);
foreach (DataGridColumn c in vis.Keys)
{
if ((Visibility)vis[c] != Visibility.Visible)
{
c.Visibility = (Visibility)vis[c];
}
}
dataGrid.UpdateLayout();
// Now we need to uncheck items that are associated with hidden
// columns.
SyncItemsOnColumnHeader(columnHeader);
}
private static void SyncItemsOnColumnHeader(DataGridColumnHeader columnHeader)
{
bool isVisible;
foreach (MenuItem item in GetAttachedItems(columnHeader))
{
if (item.Tag is DataGridColumn)
{
isVisible = ((DataGridColumn)item.Tag).Visibility == Visibility.Visible ? true : false;
if (item.IsChecked != isVisible)
{
item.IsChecked = isVisible;
}
}
}
}
#endregion CanUserHideColumns
#region CustomVisualTreeHelper
private static class CustomVisualTreeHelper<TReturn> where TReturn : DependencyObject
{
public static TReturn FindAncestor(DependencyObject descendant)
{
DependencyObject parent = descendant;
while (parent != null && !(parent is TReturn))
{
parent = VisualTreeHelper.GetParent(parent);
}
if (parent != null)
{
return (TReturn)parent;
}
return default(TReturn);
}
public static TReturn FindChild(DependencyObject parent)
{
int childCount = VisualTreeHelper.GetChildrenCount(parent);
DependencyObject child = null;
for (int childIndex = 0; childIndex < childCount; childIndex++)
{
child = VisualTreeHelper.GetChild(parent, childIndex);
if (child is TReturn)
{
return (TReturn)(object)child;
}
}
return default(TReturn);
}
public static TReturn FindChildRecursive(DependencyObject parent)
{
int childCount = VisualTreeHelper.GetChildrenCount(parent);
DependencyObject child = null;
for (int childIndex = 0; childIndex < childCount; childIndex++)
{
child = VisualTreeHelper.GetChild(parent, childIndex);
if (child is TReturn)
{
return (TReturn)(object)child;
}
else
{
child = CustomVisualTreeHelper<TReturn>.FindChildRecursive(child);
if (child is TReturn)
{
return (TReturn)(object)child;
}
}
}
return default(TReturn);
}
public static TReturn[] FindChildren(DependencyObject parent)
{
int childCount = VisualTreeHelper.GetChildrenCount(parent);
DependencyObject child = null;
List<TReturn> children = new List<TReturn>(childCount);
for (int childIndex = 0; childIndex < childCount; childIndex++)
{
child = VisualTreeHelper.GetChild(parent, childIndex);
if (child is TReturn)
{
children[childIndex] = (TReturn)(object)child;
}
}
return children.ToArray();
}
public static TReturn[] FindChildrenRecursive(DependencyObject parent)
{
int childCount = VisualTreeHelper.GetChildrenCount(parent);
DependencyObject child = null;
List<TReturn> children = new List<TReturn>();
for (int childIndex = 0; childIndex < childCount; childIndex++)
{
child = VisualTreeHelper.GetChild(parent, childIndex);
if (child is TReturn)
{
children.Add((TReturn)(object)child);
}
children.AddRange(CustomVisualTreeHelper<TReturn>.FindChildrenRecursive(child));
}
return children.ToArray();
}
}
#endregion CustomVisualTreeHelper
#endregion HideColumns
}
}
Window1.xaml
Window1.xaml.cs
I've been looking for a generic, XAML (i.e., no code-behind), automatic and simple example of a column chooser context menu that binds to a WPF DataGrid column header. I've read literally hundreds of articles, but none of them seem to do exactly the right thing, or they're no generic enough. So here's what I think is the best combined solution:
First, put these in the resource dictionary. I'll leave it as an exercise to the reader to write the Visibility/Boolean converter to ensure the checkboxes check when the column is visible and vice-versa. Note that by defining x:Shared="False" for the context menu resource, it'll get instance-specific state which means that you can use this single template/resource for all your datagrids and they'll all maintain their own state.
<Converters:VisiblityToInverseBooleanConverter x:Key="VisiblityToInverseBooleanConverter"/>
<ContextMenu x:Key="ColumnChooserMenu" x:Shared="False"
DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}"
ItemsSource="{Binding Columns, RelativeSource={RelativeSource AncestorType={x:Type sdk:DataGrid}}}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="AutomationProperties.Name" Value="{Binding Header}"/>
<Setter Property="IsCheckable" Value="True" />
<Setter Property="IsChecked" Value="{Binding Visibility, Mode=TwoWay, Converter={StaticResource VisiblityToInverseBooleanConverter}}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
<Style x:Key="ColumnHeaderStyle" TargetType="{x:Type Primitives:DataGridColumnHeader}">
<Setter Property="ContextMenu" Value="{StaticResource ColumnChooserMenu}" />
</Style>
<ContextMenu x:Key="GridItemsContextMenu" >
<MenuItem Header="Launch Do Some other action"/>
</ContextMenu>
Then define the DataGrid as follows (where OrdersQuery is some data source exposed by the View-model):
<sdk:DataGrid ItemsSource="{Binding OrdersQuery}"
AutoGenerateColumns="True"
ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}"
ContextMenu="{StaticResource GridItemsContextMenu}">
<!-- rest of datagrid stuff goes here -->
</sdk:DataGrid>
This will give you the following:
A context menu bound to the column headings which acts as a column chooser.
A context menu bound to the items in the grid (to perform actions on the items themselves - again, the binding of the actions is an exercise for the reader).
Hope this helps people who've been looking for an example like this.
I know this is a bit old. But I was looking at doing this and this post is much simpler:
http://iimaginec.wordpress.com/2011/07/25/binding-wpf-toolkit%E2%80%99s-datagridcolumn-to-a-viewmodel-datacontext-propogation-for-datagrid-columns-the-mvvm-way-to-interact-with-datagridcolumn/
All you need to do is set DataContext on the Columns and then bind Visibility to your ViewModel as per normal!
:) Simple and effective
Ok, this has been quite the exercise for a WPF n00b.
IanR thanks for the suggestion I used a similar aproach but it dosent take you all the way.
Here is what I have come up with if anyone can find a more consistent way of doing it I will appreciate any comments:
Impediments:
DataGridColumnHeader does not support a context menu. Therefore the context menu needs to be applied as a Style.
The contextmenu has its own datacontext so we have to use findancestor to link it to the ViewModels datacontext.
ATM the DataGrid control does not parse its datacontext to its Columns. This could be solved in codebehind however we are using the MVVM pattern so I decided to follow jamiers approach
Solution:
Place the following two blocks of code in Window.Resources
<Style x:Key="ColumnHeaderStyle"
TargetType="{x:Type toolkit:DataGridColumnHeader}">
<Setter Property="ContextMenu"
Value="{StaticResource ColumnHeaderContextMenu}" />
</Style>
<ContextMenu x:Key="ColumnHeaderContextMenu">
<MenuItem x:Name="MyMenuItem"
IsCheckable="True"
IsChecked="{Binding DataContext.IsHidden, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:DataGrid}}}"/>
</ContextMenu>
The datagrid then looks something like this in XAML
<toolkit:DataGrid x:Name="MyGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding SampleCollection, Mode=Default}"
EnableColumnVirtualization="True"
IsReadOnly="True"
ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTextColumn Binding="{Binding Path=SamplingUser}"
Header="{Binding (FrameworkElement.DataContext).IsHidden, RelativeSource={x:Static RelativeSource.Self}}"
Visibility="{Binding (FrameworkElement.DataContext).IsHidden,
RelativeSource={x:Static RelativeSource.Self},
Converter={StaticResource booleanToVisibilityConverter}}"/>
So the visibility property on the DataGridColumn and the ischeked property are both databound to the IsHidden property on the viewModel.
In the ViewModel:
public bool IsHidden
{
get { return isHidden; }
set
{ if (value != isHidden)
{
isHidden = value;
OnPropertyChanged("IsHidden");
OnPropertyChanged("IsVisible");
}
}
}
The Helper class defined by Jaimer:
class DataGridSupport
{
static DataGridSupport()
{
DependencyProperty dp = FrameworkElement.DataContextProperty.AddOwner(typeof(DataGridColumn));
FrameworkElement.DataContextProperty.OverrideMetadata ( typeof(DataGrid), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnDataContextChanged)));
}
public static void OnDataContextChanged ( DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataGrid grid = d as DataGrid ;
if ( grid != null )
{
foreach ( DataGridColumn col in grid.Columns )
{
col.SetValue ( FrameworkElement.DataContextProperty, e.NewValue );
}
}
}
}
Instanciated in the viewmodel (just to show done through Unity in real project)
private static DataGridSupport dc = new DataGridSupport();
Cheers,
Instead of booleanToVisibilityConverter you can use x:static
<Setter TargetName="UIElement" Property="UIElement.Visibility" Value="x:Static Visibility.Hidden" />
Statics in XAML:
http://msdn.microsoft.com/en-us/library/ms742135.aspx
I did try to ge this to bind to the ContextMenu using 'ElementName', but in the end, got it work using Properties in the VM, e.g.
bool _isHidden;
public bool IsHidden
{
get { return _isHidden; }
set
{
if (value != _isHidden)
{
_isHidden = value;
RaisePropertyChanged("IsHidden");
RaisePropertyChanged("IsVisible");
}
}
}
public Visibility IsVisible
{
get { return IsHidden ? Visibility.Hidden : Visibility.Visible; }
}
and in the XAML:
<Window.ContextMenu>
<ContextMenu>
<MenuItem Header="Hidden" IsCheckable="True" IsChecked="{Binding IsHidden}" />
</ContextMenu>
</Window.ContextMenu>
<toolkit:DataGrid x:Name="MyGrid" AutoGenerateColumns="False" ItemsSource="{Binding MyCollection, Mode=Default}" EnableColumnVirtualization="True" IsReadOnly="True" ColumnHeaderStyle="{StaticResource ColumnHeaderStyle}">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTextColumn Binding="{Binding Path=MyEntry}" Header="MyEntry" Visibility="{Binding Path=IsVisible, Mode=OneWay}" />
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>

Resources