Binding to DataGridColumnHeader.Command - wpf

I need to use Command on DataGridColumnHeader to sort my data. Standard DataGrid sorting feature is not enough for me because there are not all data displayed in DataGrid. I don't even have all data present in my VM. (It's not possible... it's too much) I just request concrete page of data from server. And now I would also like to get concrete page from sorted data.
So I did this:
<DataGrid ItemsSource="{Binding Path=Entities, Mode=OneWay}" CanUserSortColumns="False" SelectionMode="Single" SelectedItem="{Binding Path=SelectedEntity}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Command" Value="{Binding Path=MyCommand}"/>
<Setter Property="CommandParameter" Value="{Binding Path=Content, RelativeSource={RelativeSource Self}}"/>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTemplateColumn >
<DataGridTemplateColumn.Header>
<DataGridColumnHeader Content="Column1" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Property1}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
It works great. And now I just want to have AutoGenerateColumns="True" unfortunately it works no more. Is there anybody who can explain why it doesn't work for autogenerated columns and provide me with some solution? Thank you in advance!
EDIT
It has probably something to do with the fact that following doesn't work either.
<DataGrid ItemsSource="{Binding Path=Entities, Mode=OneWay}" CanUserSortColumns="False" SelectionMode="Single" SelectedItem="{Binding Path=SelectedEntity}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Command" Value="{Binding Path=MyCommand}"/>
<Setter Property="CommandParameter" Value="{Binding Path=Content, RelativeSource={RelativeSource Self}}"/>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTemplateColumn>
<!-- HERE IS THE CHANGE -->
<DataGridTemplateColumn.Header>Column1</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Property1}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
But style is applied. I know this because I tried:
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="Tomato"/> <!-- beautiful tomato background -->
<Setter Property="Command" Value="{Binding Path=MyCommand}"/>
<Setter Property="CommandParameter" Value="{Binding Path=Content, RelativeSource={RelativeSource Self}}"/>
</Style>
Column header has Tomato background, but command doesn't work.
EDIT2
Following is the solution. Apparently DataGridColumnHeaders doesn't inherit DataContext, because when I change the command binding everything works again.
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=DataContext.MyCommand}"/>
But still I don't quite understand why. So answer with detailed explanation will get the bounty.

DataGridColumnHeader is container item for DataGridColumn.Header same as, for example, ListBoxItem is container item for each item in ListBox's ItemsSource.
Since ListBoxItem's DataContext will be set to an item from ListBox's ItemsSource, it is natural that DataGridColumnHeader's DataContext will be set to DataGridColumn.Header object.

As your findings suggest, autogenerated columns will have a DataGridColumn.Header with its DataContext set to the string header object.
If you do not use autogenerated columns, and instead specify a DataGridTemplateColumn and the DataGridTemplateColumn.Header, standard inheritance for the DataContext still applies, so that's why it worked for you the first time.
Edit: A quick search didn't provide immediate results where this behavior is described. However, verifying the fact is not too hard:
Xaml:
<DataGrid ItemsSource="{Binding MyClasses}" AutoGenerateColumns="True" MouseRightButtonUp="DataGrid_MouseRightButtonUp_1" >
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<DataGridColumnHeader Content="Name (Manual Column)" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<Class1> _myClasses;
public ObservableCollection<Class1> MyClasses { get { return _myClasses; } set { _myClasses = value; OnPropertyChanged("MyClasses"); } }
public ViewModel()
{
MyClasses = new ObservableCollection<Class1>()
{
new Class1() { Name = "Andy" },
new Class1() { Name = "Mark" },
new Class1() { Name = "Peter" },
new Class1() { Name = "Gregor" },
new Class1() { Name = "Jenny" }
};
}
}
Code behind (for illustration purpose only):
private void DataGrid_MouseRightButtonUp_1(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is Microsoft.Windows.Themes.DataGridHeaderBorder)
{
object dataContext = ((Microsoft.Windows.Themes.DataGridHeaderBorder)e.OriginalSource).DataContext;
}
}
Right-click on the manual column header (outside the textblock) produces the following dataContext:
Right-click on the autogenerated column header:

Related

wpf combobox not binding to collection when inside ContentTemplate

I'm trying show a certain control based on a property (combobox or textbox). So I have this contentcontrol implemented:
<!--<ComboBox MaxWidth="200" Background="#333333" ItemsSource="{Binding ModelObjectWrapper.Values}" Grid.Row="1" Grid.Column="1"/>-->
<ContentControl Grid.Row="1" Grid.Column="1">
<ContentControl.Resources>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ModelObjectWrapper.ObjType}" Value="typeA">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ComboBox HorizontalAlignment="Left" MaxWidth="200" Background="#333333" ItemsSource="{Binding ModelObjectWrapper.Values, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ModelObjectWrapper.ObjType}" Value="typeB">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Resources>
</ContentControl>
The problem is that the combobox is not showing any items when its part of a controltemplate, and I know the bound list does have them, so I'm assuming the combobox is not being bound to the ItemsSource correctly.
The first line (having only a combobox without the templates), commented out, works fine. Am I not doing the binding right? Could it be that because its part of a datatrigger, it's not getting the right DataContext? I must note that the DataTrigger itself works great (IE showing a combobox if *.ObjType == "typeA".
The VM is a wrapper class around an object:
public class ModelObjectWrapper : ViewModelBase
{
private theModelObject model_obj;
public ModelObjectWrapper(theModelObject obj)
{
model_obj = obj;
}
public ObservableCollection<string> Values
{
get { return model_obj.Values; }
set
{
if (value == model_obj.Values)
return;
model_obj.Values = value;
OnPropertyChanged();
}
}
}
The DataContext of the root element in a ContentControl is the Content of the same ContentControl. Try to use a RelativeSource to bind to a property of the ContentControl's DataContext:
<ComboBox HorizontalAlignment="Left" MaxWidth="200" Background="#333333"
ItemsSource="{Binding DataContext.ModelObjectWrapper.Values, RelativeSource={RelativeSource AncestorType=ContentControl}}"/>
By the way, there is no point setting the UpdateSourceTrigger of an ItemsSource binding to PropertyChanged because the ComboBox never sets the source property.

WPF Custom Control library : trying to bind to templatedparent property from a keyed resource, ancestor way fails

How can I make this custom control library binding work? The code is heavily simplified for the sake of brevity, the Listview chooses its current View among several Views thank to a Datatrigger in its style, I'm only showing one for the sake of brevity. The failing binding is the GridViewColumn.Width one
Themes/Generic.Xaml :
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1">
<DataTemplate x:Key ="myDataTemplate">
<!--This visibility binding works-->
<CheckBox Width="16" Height="16" x:Name="ItemCheckbox"
Visibility="{Binding CheckBoxVisibility,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:CustomControl1}}}" />
</DataTemplate>
<GridView x:Name="myView" x:Key="myView" x:Shared="False">
<!--this width binding fails : Cannot find source for binding-->
<GridViewColumn x:Name="NameColumn" Header="Name"
CellTemplate="{StaticResource myDataTemplate}"
Width="{Binding NameColumnWidth,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:CustomControl1}}}" />
</GridView>
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<ListView ItemsSource="{Binding
RelativeSource={RelativeSource TemplatedParent},
Path=ItemsSource}" >
<ListView.Style>
<Style TargetType="{x:Type ListView}">
<Style.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource TemplatedParent},
Path=ViewDetails}" Value="True">
<Setter Property="View"
Value="{StaticResource myView}" />
</DataTrigger>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource TemplatedParent},
Path=ViewDetails}" Value="False ">
<Setter Property="View"
Value="{StaticResource myView2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
</ListView>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
CustomControl1.cs
public class CustomControl1 : Control
{
//these properties should be DependencyProperties, simplified here for the sake of brevity
ObservableCollection<Item> items;
public object ItemsSource { get => items; }
//this is the property I fail to bind to
public double NameColumnWidth { get => this.Width; }
//this one works
public Visibility CheckBoxVisibility { get => Visibility.Visible; }
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1),
new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
}
Edit:
I actually ended up changing my approach and instead of binding the column width to a Property in the templated parent I made it auto resizing to its content by subclassing GridView. This is an overall better solution. If anyone finds a solution to this binding puzzle I'll still be happy to test and accept it.
public class AutoAdjustingGridView: GridView
{
protected override void PrepareItem(ListViewItem item)
{
base.PrepareItem(item);
foreach(var column in Columns)
{
if (double.IsNaN(column.Width))
column.Width = column.ActualWidth;
column.Width = double.NaN;
}
}
}

WPF MVVM-Light adding an image to a listview item when selecting

I have a ListView that I want to be able to add a 'Delete' button to, with a command attached to it, when I select an item. I can't find anything about this on stack which is quite surprising.
One way you could do this is by applying a style to the ListViewItem in the ListView.Resources. In the Style, you set the ContentTemplate to a DataTemplate with only a TextBlock. Then you give the Style a Trigger that is tied to the IsSelected property. In the Trigger you'll set the ContentTemplate to a new DataTemplate with a TextBlock and a Button. The Command property of the Button is bound to the ViewModel using the RelativeSource binding and the CommandParameter is bound to the item so that it gets passed along as a parameter to your DeleteCommand.
XAML
<ListView Margin="3"
MinWidth="200"
ItemsSource="{Binding Items}">
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding SomeProperty}"
Margin="3" />
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected"
Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SomeProperty}"
Margin="3" />
<Button Content="Delete"
Margin="3"
Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"
CommandParameter="{Binding }" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListView.Resources>
</ListView>
ViewModel
private ICommand _DeleteCommand;
public ICommand DeleteCommand
{
get
{
if (_DeleteCommand == null)
{
_DeleteCommand = new RelayCommand<ExampleModel>(param => DeleteItem(param));
}
return _DeleteCommand;
}
}
private void DeleteItem(ExampleModel item)
{
MessageBox.Show(item.SomeProperty);
}
I just have the Delete method showing the value in a MessageBox for demo purposes. You should be able to modify the DataTemplate and Delete method to meet your needs.

How to bind DataGridTemplateColumn.Visibility to a property outside of DataGrid.ItemsSource?

I need to bind the Visibility of a DataGridTemplateColumn to a property outside of the DataGrid.ItemsSource,because i need to bind this column in the all the rows to one property inside the ViewModel,but as far as i know you just can bind that to something inside the ItemsSource or you should use ElementStyle and EditingElementStyle
I've Already tried this code:
<DataGridTemplateColumn Header="post"
Visibility="{Binding DataContext.ProjectPostVisibility
, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MvvmCommonControl:DataGrid}}"/>
And i'm Sure my binding is correct because it works fine when i bind the DataGridCell.Visibility like below:
<DataGridTemplateColumn Header="post">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Visibility" Value="{Binding DataContext.ProjectPostVisibility,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MvvmCommonControl:DataGrid}}"/>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn >
Your binding is correct, but it won't work with DataGridTemplateColumn directly because it's not in the visual tree. So it's not inherting DataContext.
You need to bind the DataGridTemplateColumn from code behind. Here is a demo that shows a way of doing it.
As mentionned in other answers, the column isn't part of the visual/logical tree and doesn't inherit from FrameworkElement meaning it has no DataContext. That's why your binding doesn't work.
However you can add a dummy (collapsed) FrameworkElement at a level where the DataContext is what you're looking for (so taking your example, it'd be at the DataGrid's level), collapse it and use it as the Source of your Binding with the x:Reference markup extension.
Here's an example :
<FrameworkElement x:Name="Proxy" Visibility="Collapsed"/>
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn Header="post"
Visibility="{Binding DataContext.ProjectPostVisibility, Source={x:Reference Name=Proxy}}"/>
</DataGrid.Columns>
</DataGrid>
Add this setter in the DataGridTemplateColumn.CellStyle and done:
<Setter Property="Visibility" Value="{Binding DataContext.isVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"/>
If you need more help look at my example below.
I want the Remove button to not be visible at the project level. First you have to make sure you have a isVisible property in your view model:
private System.Windows.Visibility _isVisible;
public System.Windows.Visibility isVisible
{
get { return _isVisible; }
set
{
if (_isVisible != value)
{
_isVisible = value;
OnPropertyChanged("isVisible");
}
}
}
Then:
if (isProj == false)
this.model.isVisible = Visibility.Visible;
else
this.model.isVisible = Visibility.Collapsed;
XAML:
<DataGridTemplateColumn >
<DataGridTemplateColumn.CellTemplate >
<DataTemplate >
<Button x:Name="btnRemove" Content="X">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="FontWeight" Value="ExtraBold" />
<Setter Property="FontSize" Value="50" />
</Style>
</Button.Style>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="Red"/>
<Setter Property="Visibility" Value="{Binding DataContext.isVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"/>
</Style>
</DataGridTemplateColumn.CellStyle>

IsSelected Binding in WPF DataGrid

I have in my Model (Class X) Boolean property: IsSelected, is linked to a WPF DataGrid as follows:
<DataGrid SelectedIndex="{Binding SelectedXIndex,Mode=TwoWay}"
DataContext="{Binding MyViewModel}"
ItemsSource="{Binding ListX}" AutoGenerateColumns="False">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</Style>
</DataGrid.RowStyle>
</DataGrid>
ListX- ObservableCollection
IsSelecte- Call to NotifyPropertyChange
It works great.
But when I have a lot of rows, that I need to scroll to see them, and I press the button "Select All" that runs the following function, he chooses me only some of the rows and not all: (Even though all the IsSelected on the list is true)
public void SelectAll()
{
ListX.All(c => c.IsSelected = true);
}
I can not understand why this is happening?
that's what helped me finally:
I put in the DataGrid:
VirtualizingStackPanel.VirtualizationMode="Standard"

Resources