I'm puzzled how to simply right-align some columns in a GridView without writing tons of markup for every column.
I can't use a static CellTemplate, because cell templates are ignored when you set DisplayMemberBinding at the same time (see remarks in MSDN). Without DisplayMemberBinding, I would be back at one custom cell template per column because the binding is different, and that's what I want to avoid.
So a style would be great like we can use for the header:
<GridViewColumn DisplayMemberBinding="{Binding Bla}" HeaderContainerStyle="{StaticResource RightAlignStyle}" />
However, I can't find a property to set a style for cell items.
Probably I'm missing the forest through the trees...
Markus, here's what I would do. Bite the bullet and for the price of writing 10 lines of code get yourself a first class support for alignments and any other unsupported properties. you can traverse the visual tree and look up for PART_* thing for the heavy fine tunung.
The solution is:
1. Alignable Column Class:
namespace AlignableCellsProject
{
public class AlignableTextColumn: DataGridTextColumn
{
protected override System.Windows.FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
FrameworkElement element = base.GenerateElement(cell, dataItem);
element.SetValue(FrameworkElement.HorizontalAlignmentProperty, this.HorizontalAlignment);
return element;
}
protected override System.Windows.FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
FrameworkElement element = base.GenerateEditingElement(cell, dataItem);
element.SetValue(FrameworkElement.HorizontalAlignmentProperty, this.HorizontalAlignment);
return element;
}
public HorizontalAlignment HorizontalAlignment
{
get { return (HorizontalAlignment)this.GetValue(FrameworkElement.HorizontalAlignmentProperty); }
set { this.SetValue(FrameworkElement.HorizontalAlignmentProperty, value); }
}
}
}
2. Consumer's XAML:
<Window x:Class="AlignableCellsProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AlignableCellsProject"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="g" AutoGenerateColumns="False">
<DataGrid.Columns>
<local:AlignableTextColumn HorizontalAlignment="Left"
Width="200" Binding="{Binding}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
3 - Consumer's Code Behind:
namespace AlignableCellsProject
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded +=
(o, e) =>
{
this.g.ItemsSource = Enumerable.Range(1, 3);
};
}
}
}
I am not using .Net 4.0 but this serves the purpose...
<tk:DataGrid ItemsSource="{Binding}" IsReadOnly="True" AutoGenerateColumns="True">
<tk:DataGrid.Resources>
<Style x:Key="MyAlignedColumn" TargetType="{x:Type tk:DataGridCell}">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
</Style>
</tk:DataGrid.Resources>
<tk:DataGridTextColumn Header="Name"
CellStyle="{StaticResource MyAlignedColumn}"
Binding="{Binding Name, Mode=TwoWay}"/>
</tk:DataGrid>
Related
I have a MVVM WPF application.
I have a DataGridTextColumn in a WPF datagrid. I want to bind its width property to a converter and pass to it its cell value. For this column, there are cases where all cells for this column are empty so I also want to set the column width to a fixed value, 20 (the same as its MinWidth) in case all cells are empty, otherwise 50. The problem is that converter is not being called.
To simplify and focus on the interesting parts I only post here the relevant code:
<DataGrid Grid.Row="1"
AutoGenerateColumns="False"
ItemsSource="{Binding Path=MyListOfItems}"
VerticalAlignment="Stretch" IsReadOnly="True"
SelectionMode="Single" ColumnWidth="*"
>
<DataGridTextColumn
CellStyle="{StaticResource MyDataGridCellStyle}"
Binding="{Binding Path=EntryDate, StringFormat=\{0:dd/MM/yyyy\}}"
Header="Entry Date"
Width="{Binding Path=EntryDate, Converter={StaticResource ColumnWidthConverter}}"
HeaderStyle="{DynamicResource CenterGridHeaderStyle}">
</DataGridTextColumn>
</DataGrid>
Converter:
public class ColumnWidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string cellContent= (string)value;
return (string.IsNullOrEmpty(cellContent.Trim()) ? 20 : 50);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
My final goal is to set column width to 20 when all its cells are empty, otherwise set its width to 50. I thought that using a converter it will be a good idea but converter is never called. Why?
UPDATE:
Finllay I have done what #Andy suggests: bind a property from view model to datagridtextcolumn width property on view. This property on view model iterates over all column cells, and then set the width accordingly. See below. My problem is that this property 'EntryDateColumnWidth' on view model only fires first time when application is launched, then when calling OnPropertyChanged("EntryDateColumnWidth"), it is not raised.
View model:
public class MyMainViewModel : ViewModelBase
{
public DataGridLength EntryDateColumnWidth
{
get
{
bool isEmpty = this.MyListOfItems.TrueForAll(i => string.IsNullOrEmpty(i.EntryDate.ToString().Trim()));
return (isEmpty ? 20 : new DataGridLength(0, DataGridLengthUnitType.Auto));
}
}
}
Also, from view model, when I have set the list of items for the datagrid, I perform:
OnPropertyChanged("EntryDateColumnWidth");
This property returns a DataGridLength object because I need to set width to auto when any of the column cells is not empty.
Note: ViewModelBase is an abstract class that implements INotifyPropertyChanged.
View:
<DataGrid Grid.Row="1"
AutoGenerateColumns="False"
ItemsSource="{Binding Path=MyListOfItems}"
VerticalAlignment="Stretch" IsReadOnly="True"
SelectionMode="Single" ColumnWidth="*">
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
<DataGridTextColumn
CellStyle="{StaticResource MyDataGridCellStyle}"
Binding="{Binding Path=EntryDate, StringFormat=\{0:dd/MM/yyyy\}}"
Header="Entry Date"
Width="{Binding Data.EntryDateColumnWidth, Source={StaticResource proxy}}"
HeaderStyle="{DynamicResource CenterGridHeaderStyle}">
</DataGridTextColumn>
</DataGrid>
Class BindingProxy:
namespace MyApp.Classes
{
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
}
UPDATE 2:
Dependency object class:
namespace My.WPF.App.Classes
{
public class BridgeDO: DependencyObject
{
public DataGridLength DataComandaColWidth
{
get { return (DataGridLength)GetValue(DataComandaColWidthProperty); }
set { SetValue(DataComandaColWidthProperty, value); }
}
public static readonly DependencyProperty EntryDateColWidthProperty =
DependencyProperty.Register("EntryDateColWidth",
typeof(DataGridLength),
typeof(BridgeDO),
new PropertyMetadata(new DataGridLength(1, DataGridLengthUnitType.Auto)));
}
}
Instance in the resource dictionary (DictionaryDO.xaml):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:My.WPF.App.Classes">
<local:BridgeDO x:Key="DO"/>
</ResourceDictionary>
Merging it into resource dictionary (app.xaml) :
<Application x:Class="My.WPF.Apps.MyApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:local="clr-namespace:My.WPF.Apps.MyApp"
StartupUri="Main.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionaries/DictionaryDO.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!-- Styles -->
</ResourceDictionary>
</Application.Resources>
</Application>
Window :
<Window x:Name="MainWindow" x:Class="My.WPF.Apps.MyApp.wMain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<!-- Resources -->
</Window.Resources>
<DataGrid Grid.Row="1"
AutoGenerateColumns="False"
ItemsSource="{Binding Path=MyListOfItems}"
VerticalAlignment="Stretch" IsReadOnly="True"
SelectionMode="Single" ColumnWidth="*">
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
<DataGridTextColumn
CellStyle="{StaticResource MyDataGridCellStyle}"
Binding="{Binding Path=EntryDate, StringFormat=\{0:dd/MM/yyyy\}}"
Header="Entry Date"
Width="{Binding EntryDateColWidth, Source={StaticResource DO}}"
HeaderStyle="{DynamicResource CenterGridHeaderStyle}">
</DataGridTextColumn>
</DataGrid>
</Window>
View model:
public class myMainViewModel : ViewModelBase
{
private BridgeDO _do;
public myMainViewModel(IView view)
{
_view = view;
_do = Application.Current.Resources["DO"] as BridgeDO;
}
private void BackgroundWorker_DoWork()
{
// Do some stuff
SetColumnWidth();
}
private void SetColumnWidth()
{
_view.GetWindow().Dispatcher.Invoke(new Action(delegate
{
bool isEmpty = this.MyListOfItems.TrueForAll(e => !e.EntryDate.HasValue);
_do.SetValue(BridgeDO.EntryDateColWidthProperty, isEmpty ? new DataGridLength(22.0) : new DataGridLength(1, DataGridLengthUnitType.Auto));
}), DispatcherPriority.Render);
}
}
But column width is not being updated...
OK, this demonstrates the principle of what I'm describing and it's a bit quick n dirty.
Define a dependency object as a new class.
using System.Windows;
using System.Windows.Controls;
namespace wpf_12
{
public class BridgeDO : DependencyObject
{
public DataGridLength ColWidth
{
get { return (DataGridLength)GetValue(ColWidthProperty); }
set { SetValue(ColWidthProperty, value); }
}
public static readonly DependencyProperty ColWidthProperty =
DependencyProperty.Register("ColWidth", typeof(DataGridLength), typeof(BridgeDO), new PropertyMetadata(new DataGridLength(20.0)));
}
}
Create an instance in a resource dictionary.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:wpf_12">
<local:BridgeDO x:Key="DO"/>
</ResourceDictionary>
Merge that resource dictionary in app.xaml:
<Application x:Class="wpf_12.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:wpf_12"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Quick and dirty viewmodel, this will change the column width to auto 6 seconds after it's instantiated.
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace wpf_12
{
public class MainWIndowViewModel
{
public ObservableCollection<object> Items { get; set; } = new ObservableCollection<object>
{ new { Name="Billy Bob", ID=1},
new { Name="Peter Parker", ID=2},
new { Name="Sherlock Holmes", ID=2}
};
public MainWIndowViewModel()
{
ChangeWidth();
}
private async void ChangeWidth()
{
await Task.Delay(6000);
var _do = Application.Current.Resources["DO"] as BridgeDO;
_do.SetCurrentValue(BridgeDO.ColWidthProperty, new DataGridLength(1, DataGridLengthUnitType.Auto));
}
}
}
Use that in my window:
Name="Window"
>
<Window.DataContext>
<local:MainWIndowViewModel/>
</Window.DataContext>
<Window.Resources>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Items}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Width="{Binding ColWidth, Source={StaticResource DO}}"/>
<DataGridTextColumn Binding="{Binding ID}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
When I run this I start off with a narrow-ish column. Sit there watching it for a while and it changes to auto width and widens.
I thought that using a converter it will be a good idea but converter is never called. Why?
Because the DataGridTextColumn inherits no DataContext and can't bind to the EntryDate property like that.
My final goal is to set column width to 20 when all its cells are empty, otherwise set its width to 50.
Then you could iterate through all items in the DataGrid's ItemsSource and check the value of their EntryDate property, e.g.:
dgg.Loaded += (s, e) =>
{
bool isEmpty = true;
foreach(var item in dgg.Items.OfType<Item>())
{
if (!string.IsNullOrEmpty(item.EntryDate))
{
isEmpty = false;
break;
}
}
//set the Width of the column (at index 0 in this sample)
dgg.Columns[0].Width = isEmpty? 20 : 500;
};
Note: In this particular example I have assumed that EntryDate is indeed a string. If it's a DateTime or a Nullable<DateTime> you could check whether it equals default(DateTime) or default(DateTime?) respectively.
I'm using a DataGrid to display user permissions in my WPF-application.
The first row of the DataGrid will always contain the owner of the project for which the permissions are displayed.
This owner is set when the project is created and can not be changed directly from the DataGrid.
My question then is.
How can I make the first row ReadOnly, and maybe give it a specific style so the background can be changed?
You will need a trigger for this, the only problem is that getting a row index on the wpf datagrid is pretty awful, so i tend to do something like this:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsOwner}" Value="true">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
where there's a property value on the object i know will be unique to that particular row. So if there's a unique ID or something that the owner always has, you can bind to that.
The setter property is 'IsEnabled' as datagridrow doesn't contain a property for readonly, but this will stop a user modifying the row.
Here's my take on it. The previous sample will do you just fine, my sample is for demonstration of the approach for acquiring low level control over the cells look. In WPF DataGridRow is just a logical container, you can only use 'attached' properties with it, such as Enabled, FontSize, FontWeight etc., as they'll get propagated down to the cell level), but the actual control's look is defined at a cell level.
TextBlock's for readonly stuff generally look cleaner than disabled texboxes, also you might want to apply completely different style for readonly and editable modes of your cells, for which you'll have to do somewhat similar to what the code below does.
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ReadOnlyRows
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += (o, e) =>
{
this.g.ItemsSource = new List<Person>(2)
{
new Person(){ Name="Dmitry", Role="Owner" },
new Person(){ Name="Jody", Role="BA" }
};
};
}
}
public class Person
{
public string Role
{
get;
set;
}
public string Name
{
get;
set;
}
}
public class PersonServices
{
// that shouldn't be in template selector, whould it?
public static bool CanEdit(Person person)
{
return person.Role != "Owner";
}
}
public class TemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Person person = item as Person;
if (person == null) return null;
string templateName = PersonServices.CanEdit(person) ? "EditableDataTemplate" : "ReadOnlyDataTemplate";
return (DataTemplate)((FrameworkElement)container).FindResource(templateName);
}
}
public class EditingTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Person person = item as Person;
if (person == null) return null;
string templateName = PersonServices.CanEdit(person) ? "EditableEditingDataTemplate" : "ReadOnlyEditingDataTemplate";
return (DataTemplate)((FrameworkElement)container).FindResource(templateName);
}
}
}
XAML:
<Window x:Class="ReadOnlyRows.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ReadOnlyRows"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="g" AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Resources>
<DataTemplate x:Key="EditableEditingDataTemplate">
<TextBox Text="{Binding Name}" />
</DataTemplate>
<DataTemplate x:Key="ReadOnlyEditingDataTemplate">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</DataTemplate>
<DataTemplate x:Key="EditableDataTemplate">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
<DataTemplate x:Key="ReadOnlyDataTemplate">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</DataTemplate>
<local:TemplateSelector x:Key="TemplateSelector" />
<local:EditingTemplateSelector x:Key="EditingTemplateSelector" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name"
CellTemplateSelector="{StaticResource TemplateSelector}"
CellEditingTemplateSelector="{StaticResource EditingTemplateSelector}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
The column header of the DataGrid is not a FrameWork element for some reason, and so you cannot use bindings to set things like the header text. Please correct me if that is wrong of if that has changed with .NET 4.0 (I am using the latest WPFToolkit from CodePlex now).
I am trying to use the DataGrid for a time sheet presentation where the day's date should be part of the header text (ie, "Sun, Nov 01"), and I have the following in my XAML:
<dg:DataGrid.Columns>
<dg:DataGridTextColumn Header="Description" Width="Auto" Binding="{Binding Description}" IsReadOnly="True"/>
<dg:DataGridTextColumn Header="Mon" Width="50" Binding="{Binding Allocations[0].Amount}" />
... every other day of the week ....
<dg:DataGridTextColumn Header="Sun" Width="50" Binding="{Binding Allocations[6].Amount}" />
<dg:DataGridTextColumn Header="Total" MinWidth="50" Binding="{Binding TotalAllocatedAmount}" IsReadOnly="True" />
</dg:DataGrid.Columns>
I'd like to use the same AllocationViewModel I am using for data (ie, "{Binding Allocations[0].Amount}" and bind it's DisplayName property to the header text. Can someone show me how to do that? If I have to use a static resource, how can I get the DataContext in there?
EDIT ---------------- PREFERRED WORK-AROUND
Josh Smith had posted about a DataContextSpy awhile back, and it is the cleanest workaround I have come across to this problem. Here is the class that makes it work:
/// <summary>
/// Workaround to enable <see cref="DataContext"/> bindings in situations where the DataContext is not redily available.
/// </summary>
/// <remarks>http://blogs.infragistics.com/blogs/josh_smith/archive/2008/06/26/data-binding-the-isvisible-property-of-contextualtabgroup.aspx</remarks>
public class DataContextSpy : Freezable
{
public DataContextSpy()
{
// This binding allows the spy to inherit a DataContext.
BindingOperations.SetBinding(this, DataContextProperty, new Binding());
}
public object DataContext
{
get { return GetValue(DataContextProperty); }
set { SetValue(DataContextProperty, value); }
}
// Borrow the DataContext dependency property from FrameworkElement.
public static readonly DependencyProperty DataContextProperty = FrameworkElement
.DataContextProperty.AddOwner(typeof (DataContextSpy));
protected override Freezable CreateInstanceCore()
{
// We are required to override this abstract method.
throw new NotImplementedException();
}
}
With this in place, I can hijack the DC I need in xaml:
<dg:DataGrid.Resources>
<behavior:DataContextSpy x:Key="spy" DataContext="{Binding Allocations}" />
</dg:DataGrid.Resources>
And then apply as needed via binding:
<dg:DataGridTextColumn Header="{Binding Source={StaticResource spy}, Path=DataContext[0].DisplayName}"
Width="50" Binding="{Binding Allocations[0].Amount}" />
Suh-weet!
This is the easy way to bind the DataGridTextColumn header to the data context:
<DataGrid x:Name="summaryGrid" Grid.Row="3" AutoGenerateColumns="False" IsReadOnly="True" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Hard Coded Title" Width="*"/>
<DataGridTextColumn Width="100">
<DataGridTextColumn.Header>
<TextBlock Text="{Binding DataContext.SecondColumnTitle,
RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Width="150">
<DataGridTextColumn.Header>
<TextBlock Text="{Binding DataContext.ThirdColumnTitle,
RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
You will obviously need to have properties: SecondColumnTitle and ThirdColumnTitle implemented on your data context class.
I have this solution working in .net 4.5 and did not have a chance nor reason to try it in earlier versions of the framework.
I know this post is old, but when I looked up how to do this, this is the first entry that came up. I did not like this answer because it seemed like overkill. After more searching, I ran across this link the showed how to do this in the markup, using a template column.
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
**<TextBlock Text="{Binding DataContext.HeaderTitle, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />**
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Width="200" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I solved this by using the HeaderTemplate and binding to the DataContext of the DataGrid, using RelativeSource.
<DataGrid ItemsSource="{Binding Items}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Value1}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.ColumnTitel1, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
The same binding within the Header property did not work out.
My solution allows writing a single line in DataGridColumn with the name of the property that need to bind. He has the following features:
There is support for DataGridTextColumn
There is support for DataGridTemplateColumn
Set StringFormat for each column
Specify a static value for the StringFormat
Fully complies with MVVM pattern
Example, which is below, includes StringFormat (he should stand before the PropertyPath):
<DataGridTextColumn Behaviors:DataGridHeader.StringFormat="StringFormat: {0:C}"
Behaviors:DataGridHeader.PropertyPath="HeaderValueOne" ... />
Equivalent to a this line:
<DataGridTextColumn HeaderStringFormat="{0:C}"
Header="{Binding Path=HeaderValueOne}" ... />
Who need more examples of solutions and features, please read below.
Link for the sample project.
Notes about the solution
From all the solutions that I have seen earlier, the easiest for me turned out to be this example:
<DataGridTextColumn Binding="{Binding Name}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DataContext.YourPropertyName,
RelativeSource={RelativeSource AncestorType={x:Type SomeControl}}" />
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
Please pay attention to DataGridTextColumn.HeaderTemplate, if was used DataGridTextColumn.Header, then for .NET framework below version 4.5 and for Silverlight would produce an exception:
Header property does not support UIElements
It would seem that what is necessary? I wanted to find a solution that would allow to write a single line in DataGridColumn with the name of the property that need to bind.
And here's what happened:
<DataGridTextColumn Behaviors:DataGridHeader.PropertyPath="HeaderValueOne" // Attached dependency property
This construction similar to this:
<DataGridTextColumn Header="{Binding Path=HeaderValueOne}" ... />
Also is possible to use StringFormat for each column like this:
<DataGridTextColumn Behaviors:DataGridHeader.StringFormat="StringFormat: {0:C}"
Behaviors:DataGridHeader.PropertyPath="TestStringFormatValue" ... />
And there is the ability to specify a static value for the StringFormat:
<DataGridTextColumn Behaviors:DataGridHeader.StringFormat="{x:Static Member=this:TestData.TestStaticStringFormatValue}" // public static string TestStaticStringFormatValue = "Static StringFormat: {0}$";
Behaviors:DataGridHeader.PropertyPath="TestStringFormatValue"
Here is the original DataTemplate, which is dynamically set to the column:
<DataTemplate>
<TextBlock Text="{Binding Path=DataContext.YourPropertyName,
StringFormat="YourStringFormat",
RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}" />
</DataTemplate>
In order to RelativeSource did not depend on the type of DataContext, I took great solution from Mr.Bruno.
In this case, DataGridCellsPanel contains the correct DataContext, which is set for a parent DataGrid.
Below is the basic code that is performed all the magic:
IsSetHeader PropertyChanged handler
private static void IsSetHeader(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var textColumn = sender as DataGridTextColumn;
var templateColumn = sender as DataGridTemplateColumn;
string path = e.NewValue as string;
if ((textColumn == null) & (templateColumn == null))
{
return;
}
if (String.IsNullOrEmpty(path) == false)
{
currentStringFormat = ReturnStringFormat(textColumn, templateColumn);
dataTemplate = CreateDynamicDataTemplate(path, currentStringFormat);
if (dataTemplate != null)
{
if (textColumn != null)
textColumn.HeaderTemplate = dataTemplate;
if (templateColumn != null)
templateColumn.HeaderTemplate = dataTemplate;
}
}
}
CreateDynamicDataTemplate
private static DataTemplate CreateDynamicDataTemplate(string propertyPath, string stringFormat)
{
var pc = new ParserContext();
MemoryStream sr = null;
string xaml = GetXamlString(propertyPath, stringFormat);
sr = new MemoryStream(Encoding.ASCII.GetBytes(xaml));
pc.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
pc.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
return XamlReader.Load(sr, pc) as DataTemplate;
}
GetXamlString
private static string GetXamlString(string propertyPath, string stringFormat)
{
#region Original PropertyPath for TextBlock
// {Binding Path=DataContext.YourProperty, RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}"
// Thanks to Bruno (https://stackoverflow.com/users/248118/bruno) for this trick
#endregion
var sb = new StringBuilder();
sb.Append("<DataTemplate><TextBlock Text=\"{Binding Path=DataContext.");
sb.Append(propertyPath);
sb.Append(", StringFormat=");
sb.Append(stringFormat);
sb.Append(", RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}\" /></DataTemplate>");
return sb.ToString();
}
StringFormat must appear before the PropertyPath, because it is optional. In order to for columns, who did not have it is not an exception occurs, I registered try-catch in GetStringFormat:
public static string GetStringFormat(DependencyObject DepObject)
{
try
{
return (string)DepObject.GetValue(StringFormatProperty);
}
catch
{
return String.Empty;
}
}
Plus: do not write in methods try-catch block, that are trying to get the value.
Minus: The minus for every missed StringFormat exception will be generated once when the program starts. If it is critical for you, you can always specify the StringFormat="null" for the column.
Just in case, show the full code of project:
public static class DataGridHeader
{
#region Private Section
private static string textColumnStringFormat = null;
private static string templateColumnStringFormat = null;
private static string currentStringFormat = null;
private static DataTemplate dataTemplate = null;
#endregion
#region PropertyPath DependencyProperty
public static readonly DependencyProperty PropertyPathProperty;
public static void SetPropertyPath(DependencyObject DepObject, string value)
{
DepObject.SetValue(PropertyPathProperty, value);
}
public static string GetPropertyPath(DependencyObject DepObject)
{
return (string)DepObject.GetValue(PropertyPathProperty);
}
#endregion
#region StringFormat DependencyProperty
public static readonly DependencyProperty StringFormatProperty;
public static void SetStringFormat(DependencyObject DepObject, string value)
{
DepObject.SetValue(StringFormatProperty, value);
}
public static string GetStringFormat(DependencyObject DepObject)
{
try
{
return (string)DepObject.GetValue(StringFormatProperty);
}
catch
{
return String.Empty;
}
}
#endregion
#region Constructor
static DataGridHeader()
{
PropertyPathProperty = DependencyProperty.RegisterAttached("PropertyPath",
typeof(string),
typeof(DataGridHeader),
new UIPropertyMetadata(String.Empty, IsSetHeader));
StringFormatProperty = DependencyProperty.RegisterAttached("StringFormat",
typeof(string),
typeof(DataGridHeader),
new UIPropertyMetadata(String.Empty));
}
#endregion
#region IsSetHeader PropertyChanged Handler
private static void IsSetHeader(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var textColumn = sender as DataGridTextColumn;
var templateColumn = sender as DataGridTemplateColumn;
string path = e.NewValue as string;
if ((textColumn == null) & (templateColumn == null))
{
return;
}
if (String.IsNullOrEmpty(path) == false)
{
currentStringFormat = ReturnStringFormat(textColumn, templateColumn);
dataTemplate = CreateDynamicDataTemplate(path, currentStringFormat);
if (dataTemplate != null)
{
if (textColumn != null)
textColumn.HeaderTemplate = dataTemplate;
if (templateColumn != null)
templateColumn.HeaderTemplate = dataTemplate;
}
}
}
#endregion
#region ReturnStringFormat Helper
private static string ReturnStringFormat(DependencyObject depObject1, DependencyObject depObject2)
{
textColumnStringFormat = GetStringFormat(depObject1) as string;
templateColumnStringFormat = GetStringFormat(depObject2) as string;
if (String.IsNullOrEmpty(textColumnStringFormat) == false)
{
return textColumnStringFormat;
}
if (String.IsNullOrEmpty(templateColumnStringFormat) == false)
{
return templateColumnStringFormat;
}
return "null";
}
#endregion
#region CreateDynamicDataTemplate Helper
private static DataTemplate CreateDynamicDataTemplate(string propertyPath, string stringFormat)
{
var pc = new ParserContext();
MemoryStream sr = null;
string xaml = GetXamlString(propertyPath, stringFormat);
sr = new MemoryStream(Encoding.ASCII.GetBytes(xaml));
pc.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
pc.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
return XamlReader.Load(sr, pc) as DataTemplate;
}
#endregion
#region GetXamlString Helper
private static string GetXamlString(string propertyPath, string stringFormat)
{
#region Original PropertyPath for TextBlock
// {Binding Path=DataContext.YourProperty, RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}"
// Thanks to Bruno (https://stackoverflow.com/users/248118/bruno) for this trick
#endregion
var sb = new StringBuilder();
sb.Append("<DataTemplate><TextBlock Text=\"{Binding Path=DataContext.");
sb.Append(propertyPath);
sb.Append(", StringFormat=");
sb.Append(stringFormat);
sb.Append(", RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}\" /></DataTemplate>");
return sb.ToString();
}
#endregion
}
XAML
<Window x:Class="BindingHeaderInDataGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:BindingHeaderInDataGrid"
xmlns:Behaviors="clr-namespace:BindingHeaderInDataGrid.AttachedBehaviors"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="220" Width="600">
<Window.DataContext>
<this:TestData />
</Window.DataContext>
<Grid Name="TestGrid">
<DataGrid Name="TestDataGrid"
Width="550"
Height="100"
Margin="10"
VerticalAlignment="Top"
Background="AliceBlue">
<DataGrid.Columns>
<DataGridTextColumn Behaviors:DataGridHeader.StringFormat="StringFormat: {0:C}"
Behaviors:DataGridHeader.PropertyPath="TestStringFormatValue"
Width="100"
IsReadOnly="False">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Height" Value="20" />
<Setter Property="Background" Value="Pink" />
<Setter Property="Margin" Value="2,0,0,0" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn Behaviors:DataGridHeader.StringFormat="{x:Static Member=this:TestData.TestStaticStringFormatValue}"
Behaviors:DataGridHeader.PropertyPath="TestStringFormatValue"
Width="2*"
IsReadOnly="False">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Height" Value="20" />
<Setter Property="Background" Value="CadetBlue" />
<Setter Property="Margin" Value="2,0,0,0" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn Behaviors:DataGridHeader.PropertyPath="TestUsualHeaderValue"
Width="1.5*"
IsReadOnly="False">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Height" Value="20" />
<Setter Property="Background" Value="Gainsboro" />
<Setter Property="Margin" Value="2,0,0,0" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTemplateColumn Behaviors:DataGridHeader.PropertyPath="TestTemplateColumnValue"
Width="150"
IsReadOnly="False">
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Height" Value="20" />
<Setter Property="Background" Value="Beige" />
<Setter Property="Margin" Value="2,0,0,0" />
</Style>
</DataGridTemplateColumn.HeaderStyle>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Name="ChangeHeader"
Width="100"
Height="30"
VerticalAlignment="Bottom"
Content="ChangeHeader"
Click="ChangeHeader_Click" />
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ChangeHeader_Click(object sender, RoutedEventArgs e)
{
TestData data = this.DataContext as TestData;
data.TestStringFormatValue = "777";
data.TestUsualHeaderValue = "DynamicUsualHeader";
data.TestTemplateColumnValue = "DynamicTemplateColumn";
}
}
public class TestData : NotificationObject
{
#region TestStringFormatValue
private string _testStringFormatValue = "1";
public string TestStringFormatValue
{
get
{
return _testStringFormatValue;
}
set
{
_testStringFormatValue = value;
NotifyPropertyChanged("TestStringFormatValue");
}
}
#endregion
#region TestStaticStringFormatValue
public static string TestStaticStringFormatValue = "Static StringFormat: {0}$";
#endregion
#region TestUsualHeaderValue
private string _testUsualHeaderValue = "UsualHeader";
public string TestUsualHeaderValue
{
get
{
return _testUsualHeaderValue;
}
set
{
_testUsualHeaderValue = value;
NotifyPropertyChanged("TestUsualHeaderValue");
}
}
#endregion
#region TestTemplateColumnValue
private string _testTemplateColumnValue = "TemplateColumn";
public string TestTemplateColumnValue
{
get
{
return _testTemplateColumnValue;
}
set
{
_testTemplateColumnValue = value;
NotifyPropertyChanged("TestTemplateColumnValue");
}
}
#endregion
}
BTW, in Silverlight (tested with SL 3.0) you can simply use the Header property as the DataContext for the ControlTemplate set via HeaderStyle (see my related question on SO).
I just tried this solution in WPF 3.5 using the WPF Toolkit DataGrid and it works!
**EDIT :-
You can style the DataGridColumnHeader and do some funky bindings. try here and download the ColumnHeaderBindings.zip, it has a little test project, that is a bit of a hack, but it works
**End Edit
The Binding on the column happens on a per row basis, the column is not part of the visual tree, the binding gets applied to each item in the grid, from the grids source code you can see that the property Binding has these comments
/// <summary>
/// The binding that will be applied to the generated element.
/// </summary>
/// <remarks>
/// This isn't a DP because if it were getting the value would evaluate the binding.
/// </remarks>
So binding to the columns does not make much sense, because as you have found out, when you are not part of the visual tree you have no data context.
The same problem exists with the ComboBoxColumn when you want to bind to the items source. You can bind to a StaticResource, but StaticResources dont have a data context either. You could use an object data provider or just instantiate directly in xaml.
but i would just create the columns in code, and set the header. this problem would just go away then.
there is a good article here on the visual layout.
an even better solution would be to set the binding in the header's style and pass the column as the header's dataContext... (or even better: to set up an object representing the header's dataContext and pass it)
see there for a way to do this :
How to set the DataContext on a DataGrid Column Header
#mmichtch's answer works well for me, you just have to create a local namespace(xmlns), which contains reference to your project as follows:
xmlns:local="clr-namespace:your_project_name"
and along with it don't forget to mention the property you want to bind:
<DataGridTextColumn Width="Auto">
<DataGridTextColumn.Header>
<TextBlock Text="{Binding DataContext.PropertyNameYouWantToBind,
RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
it works well with VS 2010 and .net version 4.
I used it to populate the DataGrid Column Header.
Trick is in the binding mode. Its' "Mode" needs to be set to "OneWay". Otherwise it is no good.
Example:
<DataGridTextColumn Binding="{Binding RowData}" Header="{Binding Mode=OneWay, Source={StaticResource spy},Path=DataContext.HeaderText, FallbackValue= header text}"/>
I used a lowercase fallback value, and the value from DataContext was capitalized to assure me that the resource was not null. Also, the value from DataContext was only showing up for me at run time, during design time it displayed the fallback value. Hope this helps.
I have a DataTemplate that will be a templated ListBoxItem, this DataTemplate has a
ComboBox in it which when it has focus I want the ListBoxItem that this template
represents to become selected, this looks right to me. but sadly enough it doesn't work =(
So the real question here is within a DataTemplate is it possible to get or set the value
of the ListBoxItem.IsSelected property via a DataTemplate.Trigger?
<DataTemplate x:Key="myDataTemplate"
DataType="{x:Type local:myTemplateItem}">
<Grid x:Name="_LayoutRoot">
<ComboBox x:Name="testComboBox" />
</Grid>
<DataTemplate.Triggers>
<Trigger Property="IsFocused" value="true" SourceName="testComboBox">
<Setter Property="ListBoxItem.IsSelected" Value="true" />
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
<ListBox ItemTemplate="{StaticResource myDataTemplate}" />
I found a solution for your problem.
The problem is that when you have a control on your listboxitem, and the control is clicked (like for inputting text or changing the value of a combobox), the ListBoxItem does not get selected.
this should do the job:
public class FocusableListBox : ListBox
{
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is FocusableListBoxItem);
}
protected override System.Windows.DependencyObject GetContainerForItemOverride()
{
return new FocusableListBoxItem();
}
}
--> Use this FocusableListBox in stead of the default ListBox of WPF.
And use this ListBoxItem:
public class FocusableListBoxItem : ListBoxItem
{
public FocusableListBoxItem()
{
GotFocus += new RoutedEventHandler(FocusableListBoxItem_GotFocus);
}
void FocusableListBoxItem_GotFocus(object sender, RoutedEventArgs e)
{
object obj = ParentListBox.ItemContainerGenerator.ItemFromContainer(this);
ParentListBox.SelectedItem = obj;
}
private ListBox ParentListBox
{
get
{
return (ItemsControl.ItemsControlFromItemContainer(this) as ListBox);
}
}
}
A Treeview does also have this problem, but this solution does not work for a Treeview, 'cause SelectedItem of Treeview is readonly.
So if you can help me out with the Treeview please ;-)
I found that I preferred to use this:
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"></Setter>
</Trigger>
</Style.Triggers>
</Style>
Simple and works for all the listboxitems, regardless of what's inside.
No idea why your trigger don't work. To catch the get focus event of the combo box (or any control inside a listbox item) you can use attached routed events. You could put the code also in a derived listbox if you need this behavior in other parts of your application.
XAML:
<Window x:Class="RoutedEventDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Specialized="clr-namespace:System.Collections.Specialized;assembly=System"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="myDataTemplate">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" Margin="5,0"/>
<ComboBox Width="50">
<ComboBoxItem>AAA</ComboBoxItem>
<ComboBoxItem>BBB</ComboBoxItem>
</ComboBox>
</StackPanel>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemTemplate="{StaticResource myDataTemplate}">
<ListBox.ItemsSource>
<Specialized:StringCollection>
<System:String>Item 1</System:String>
<System:String>Item 2</System:String>
<System:String>Item 3</System:String>
</Specialized:StringCollection>
</ListBox.ItemsSource>
</ListBox>
</Grid>
</Window>
Code behind hooking up to all got focus events.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace RoutedEventDemo
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
EventManager.RegisterClassHandler(typeof(UIElement),
GotFocusEvent,
new RoutedEventHandler(OnGotFocus));
}
private static void OnGotFocus(object sender, RoutedEventArgs e)
{
// Check if element that got focus is contained by a listboxitem and
// in that case selected the listboxitem.
DependencyObject parent = e.OriginalSource as DependencyObject;
while (parent != null)
{
ListBoxItem clickedOnItem = parent as ListBoxItem;
if (clickedOnItem != null)
{
clickedOnItem.IsSelected = true;
return;
}
parent = VisualTreeHelper.GetParent(parent);
}
}
}
}
I have a DataGrid in a WPF form with a DataGridCheckBoxColumn, but I did not find any click event, Checked and unchecked for it...
Are these events available for the DataGridCheckBoxColumn? If not please suggest some workaround I could use.
Quoted from William Han's answer here: http://social.msdn.microsoft.com/Forums/ar/wpf/thread/9e3cb8bc-a860-44e7-b4da-5c8b8d40126d
It simply adds an event to the column. It is a good simple solution.
Perhaps you can use EventSetter as example below:
Markup:
<Window x:Class="DataGridCheckBoxColumnTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataGridCheckBoxColumnTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:People x:Key="People"/>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{StaticResource People}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Name}" Header="Name"/>
<DataGridCheckBoxColumn Binding="{Binding Path=LikeCar}" Header="LikeCar">
<DataGridCheckBoxColumn.CellStyle>
<Style>
<EventSetter Event="CheckBox.Checked" Handler="OnChecked"/>
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Code:
using System;
using System.Windows;
namespace DataGridCheckBoxColumnTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
void OnChecked(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
}
}
namespace DataGridCheckBoxColumnTest
{
public class Person
{
public Person(string name, bool likeCar)
{
Name = name;
LikeCar = likeCar;
}
public string Name { set; get; }
public bool LikeCar { set; get; }
}
}
using System.Collections.Generic;
namespace DataGridCheckBoxColumnTest
{
public class People : List<Person>
{
public People()
{
Add(new Person("Tom", false));
Add(new Person("Jen", false));
}
}
}
Expanding on the DataGridCell concept noted above, this is what we used to get it working.
...XAML...
<DataGrid Grid.ColumnSpan="2" Name="dgMissingNames" ItemsSource="{Binding Path=TheMissingChildren}" Style="{StaticResource NameListGrid}" SelectionChanged="DataGrid_SelectionChanged">
<DataGrid.Columns>
<DataGridTemplateColumn CellStyle="{StaticResource NameListCol}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=Checked, UpdateSourceTrigger=PropertyChanged}" Name="theCheckbox" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding Path=SKU}" Header="Album" />
<DataGridTextColumn Binding="{Binding Path=Name}" Header="Name" "/>
<DataGridTextColumn Binding="{Binding Path=Pronunciation}" Header="Pronunciation" />
</DataGrid.Columns>
</DataGrid>
TheMissingChildren is an ObservableCollection object that contains the list of data elements including a boolean field "Checked" that we use to populate the datagrid.
The SelectionChanged code here will set the checked boolean in the underlying TheMissingChildren object and fire off a refresh of the items list. That ensures that the box will get checked off & display the new state no matter where you click on the row. Clicking the checkbox or somewhere in the row will toggle the check on/off.
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataGrid ThisGrid = (DataGrid)sender;
CheckedMusicFile ThisMusicfile = (CheckedMusicFile)ThisGrid.SelectedItem;
ThisMusicfile.Checked = !ThisMusicfile.Checked;
ThisGrid.Items.Refresh();
}
<wpf:DataGridCheckBoxColumn Header="Cool?" Width="40" Binding="{Binding IsCool}"/>
How about something like this.
partial class SomeAwesomeCollectionItems : INotifyPropertyChanged
{
public event PropertyChanged;
protected void OnPropertyChanged(string property)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(property);
}
private bool _IsSelected;
public bool IsSelected { get { return _IsSelected; } set { _IsSelected = Value; OnPropertyChanged("IsSelected"); } }
}
Then in XAML
<DataGrid ItemsSource="{Binding Path=SomeAwesomeCollection"} SelectionMode="Single">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}"
BasedOn="{StaticResource {x:Type DataGridRow}}">
<!--Note that you will probably need to base on other style if you have stylized your DataGridRow-->
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
</Style>
</DataGrid.Resources
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
<!--More Columns-->
</DataGrid.Columns>
</DataGrid>
One note with this approach, however, is you may run into issues with virtualization and checked items not clearing (not sure, haven't tested with SelectionMode="Single"). If that is the case, the simplest workaround I have found to work is to turn virtualization off, but perhaps there is a better way to get around that particular issue.
If you do not want to add the event to your style you can also do it this way.
<DataGridCheckBoxColumn x:Name="name" Header="name?" Binding="{Path=Name}"
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
<EventSetter Event="CheckBox.Checked" Handler="Checked"/>
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>eckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
Try this in xml
<DataGridCheckBoxColumn Header="" IsThreeState="False" Binding="{Binding isCheck, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}" />
And in c#
public partial class UploadWindow : Window
{
ObservableCollection<Items> pl = new ObservableCollection<Items>();
class Items
{
public bool isCheck { get; set; }
}
public UploadWindow(Dictionary<string, object> ipDictionary)
{
InitializeComponent();
GridView1.ItemsSource = pl;
}
}
In my case, I need to find all checked checkboxes
var allChecked = pl.Where(x => x.isCheck == true);