Disable specific DataGridCheckBoxColumn cells via ViewModel - wpf

My MVVM project has this ViewModel:
class ListViewModel : ViewModelBase {
public ObservableCollection<ListItemviewModel> Items { ... }
}
class ListItemViewModel : ViewModelBase {
public String Name { ... }
public Boolean IsChecked { ... }
public Boolean IsEnabled { ... }
}
Writing the XAML seemed straightforward:
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridCheckBoxColumn Header="Is checked" Binding="{Binding IsChecked}" />
</DataGrid>
However, how can I get it so when a ListItemViewModel's IsEnabled property is false the DataGridCheckBoxColumn's cell in that row is disabled?
I tried setting IsReadOnly={Binding IsDisabled} (and adding an IsDisabled property to ListItemViewModel, however to no avail) - and I recognise that would disable/enable the whole column, not individual cells.
I also tried these instructions ( How to disable a cell in a DataGrid while binding to an ObservableCollection ):
<DataGridCheckBoxColumn Header="Is checked" Binding="{Binding IsChecked}" />
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridCheckBoxColumn.CellStyle>
However this has no effect and no binding errors are displayed in the Output window.

It turns out the question I linked to ( How to disable a cell in a DataGrid while binding to an ObservableCollection ) was almost-right, but the XAML was somewhat cargo-cultish. The correct XAML is merely:
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
</Style>
</DataGridCheckBoxColumn.CellStyle>
Sorted!

Related

WPF multiple-condition binding filtering

I am new to WPF and all this magical binding and datatriggers stuff, so i ask you for a little help.
I have a simple wpf app shown on picture below.
I want my datagrid contents to reflect conditions and date filter. I already figured out how to bind datagrid rows visibility depending on event codes and checkboxes (start, stop, error). But i cant't figure out how to implement date filtering. All i want is: when "Filter by date" checkbox is checked, in my datagrid only those rows remain visible, which have date in "server time" field (i guess i need to parse it somehow from datetime) equal to selected date combobox.
Can i achieve that using xaml only? Can enyone help me to do that?
Here is xaml for my datagrid:
<DataGrid
Grid.Row="1"
Margin="5"
AutoGenerateColumns="False"
IsReadOnly="True"
ItemsSource="{Binding LogEntries}"
Style="{DynamicResource Helvetica}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding event_code}" Value="1">
<Setter Property="Background" Value="LightGreen" />
<Setter Property="Visibility" Value="{Binding IsChecked, ElementName=StartShowChecked, Converter={StaticResource BooleanToVisibilityConverter}}" />
</DataTrigger>
<DataTrigger Binding="{Binding event_code}" Value="2">
<Setter Property="Background" Value="LightGray" />
<Setter Property="Visibility" Value="{Binding IsChecked, ElementName=StopShowChecked, Converter={StaticResource BooleanToVisibilityConverter}}" />
</DataTrigger>
<DataTrigger Binding="{Binding event_code}" Value="3">
<Setter Property="Background" Value="#FFEA816F" />
<Setter Property="Visibility" Value="{Binding IsChecked, ElementName=ErrorShowChecked, Converter={StaticResource BooleanToVisibilityConverter}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding server_datetime, StringFormat=\{0:dd.MM.yy HH:mm:ss\}}" Header="Server time" />
<DataGridTextColumn Binding="{Binding user_datetime, StringFormat=\{0:dd.MM.yy HH:mm:ss\}}" Header="Client time" />
<DataGridTextColumn
Width="*"
Binding="{Binding log_entry}"
Header="Entry" />
</DataGrid.Columns>
Can i achieve that using xaml only?
No you can't because XAML is a markup language and nothing else.
What you shold do is to bind the SelectedItem of the date ComboBox to a DateTime property of your view model and bind the IsChecked property of the "filter by" CheckBox to a bool property of your view model and filter the LogEntries source collection when the IsChecked source property is set, e.g.:
public class ViewModel : INotifyPropertyChanged
{
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged();
//filter collection:
LogEntries = allLogEntries.Where(x => x.ServerTime == SelectedDate).ToList();
}
}
private List<LogEntry> _logEntries;
public List<LogEntry LogEntries
{
get { return _logEntries; }
set
{
_logEntries = value;
OnPropertyChanged();
}
}
//...
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I doubt there is a way to do it in XAML only, since filtering requires you to specify how to filter (for example by creating a predicate).
What I recommend is getting an ICollectionView from your ItemsSource (which I assume is an ObservableCollection) and set it's Filter property.
Check out this answer for more details : Filter a DataGrid in WPF

Unable to disable rows in WPF DataGrid using Style trigger

I want to disable rows based on a property "IsEditable".
This is my RowStyle
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEditable}" Value="False">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
I have implemented INotifyPropertyChanged.
I'm able to disable the entire grid using the same property but I'm unable to disable rows.
"IsEditable" property is defined in my ViewModel. My datacontext is also the ViewModel.
This is my grid code
<DataGrid RowHeaderWidth="0" MouseDoubleClick="listViewItem_MouseDoubleClick"
AutoGenerateColumns="False" IsReadOnly="True" SelectionMode="Extended" SelectionUnit="FullRow"
BorderBrush="#FF898C95" ItemsSource="{Binding EqpGrpList}" CanUserSortColumns="False" HorizontalGridLinesBrush="#FFDEDBDB"
VerticalGridLinesBrush="#FFDEDBDB" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Margin="0,-5,0,0" Background="White" CellStyle="{StaticResource DataGridCellStyle}">
<DataGrid.Columns...>
</DataGrid>
I have included the row style in UserControl.Resources.
Bind your property into DataGrid ItemCollection Class.
xaml....
<Style x:Key="DataGridCellStyle" TargetType="DataGridRow">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnable}" Value="False">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
<DataGrid RowHeaderWidth="0"
AutoGenerateColumns="False" IsReadOnly="True" SelectionMode="Extended" SelectionUnit="FullRow"
BorderBrush="#FF898C95" ItemsSource="{Binding GridData}" CanUserSortColumns="False" HorizontalGridLinesBrush="#FFDEDBDB"
VerticalGridLinesBrush="#FFDEDBDB" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Margin="0,-5,0,0" Background="White" RowStyle="{StaticResource DataGridCellStyle}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="City" Binding="{Binding City}"/>
</DataGrid.Columns>
</DataGrid>
Code...
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
for(int i=0;i<10;i++)
{
GridData.Add(new UserData { Name = "NAME " + i, City = "CITY " + i });
}
}
private ObservableCollection<UserData> _gridData=new ObservableCollection<UserData>();
public ObservableCollection<UserData> GridData
{
get { return _gridData; }
set { _gridData = value; }
}
class....
public class UserData
{
public string Name { get; set; }
public string City { get; set; }
private bool _isEnable = false;
public bool IsEnable
{
get { return _isEnable; }
set { _isEnable = value; }
}
}

How to change IsReadOnly by Trigger

can anybody enlight me way the following doesn't work
i'm using the mvvm pattern
after executing my code i'm still able to check and uncheck the Checkboxes in my DataGridCheckBoxColumn
Property (part of my ViewModel)
public bool noCheckPermission
{
get { return false; } // just as example
}
Xaml (my View no code behind)
<DataGrid Height="300" AutoGenerateColumns="False" SelectionMode="Single"
ItemsSource="{Binding Itemlist, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCheckBoxColumn}">
<Style.Triggers>
<DataTrigger Binding="{Binding noCheckPermission}" Value="False">
<Setter Property="IsReadOnly" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding noCheckPermission}" Value="True">
<Setter Property="IsReadOnly" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridCheckBoxColumn Width="60"
Header="Freigabe" Binding="{Binding FreigegebenL}" CanUserReorder="False">
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
Edit:
after doing what blindmeis and Will suggested. I got this as my current XAML
<DataGrid Height="300" Margin="12" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False"
CanUserResizeColumns="False" CanUserResizeRows="False" CanUserReorderColumns="False"
x:Name="grd" ItemsSource="{Binding Itemlist, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding selectedItem, UpdateSourceTrigger=PropertyChanged}" SelectionMode="Single" SelectionChanged="DataGrid_SelectionChanged" TabIndex="2">
<DataGrid.Columns>
<DataGridCheckBoxColumn Width="60" IsReadOnly="{Binding DataContext, Path=noCheckPermission, ElementName=grd}" Binding="{Binding FreigegebenL}"
Header="Freigabe" CanUserReorder="False">
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
and this as Error in my Output Window:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=noCheckPermission; DataItem=null; target element is 'DataGridCheckBoxColumn' (HashCode=35155182); target property is 'IsReadOnly' (type 'Boolean')
first
i noticed that my property was in the wrong VM (it was in the Child VM which are my rows) fixing this doesn't solved my problem
finally
after reading this article i was able to solve it
in condensed form
class
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));
}
XAML
Resources
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
Column
<DataGridCheckBoxColumn Width="60" Header="Freigabe" CanUserReorder="False"
IsReadOnly="{Binding Data.noCheckPermission, Source={StaticResource proxy}}"
Binding="{Binding FreigegebenL}"/>
if you just want the DataGridCheckBoxColumn to be readonly, you should add your style to your DataGridCheckBoxColumn.
<DataGrid Height="300" AutoGenerateColumns="False" SelectionMode="Single"
x:Name="grd"
ItemsSource="{Binding Itemlist, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Width="60"
Header="Freigabe" Binding="{Binding FreigegebenL}" CanUserReorder="False">
<DataGridCheckBoxColumn.Style>
<Style TargetType="{x:Type DataGridCheckBoxColumn}">
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.noCheckPermission, ElementName=grd}" Value="False">
<Setter Property="IsReadOnly" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding DataContext.noCheckPermission, ElementName=grd}" Value="True">
<Setter Property="IsReadOnly" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridCheckBoxColumn.Style>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>

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>

WPF DataGrid bind data between columns

Lets say I have 2 columns in my data Grid: Column A: Selected, and Column B: Name. The Selected column is a checkbox. And Name column is text field. I want to set the color of the text in 'Name' column as Blue if Column A's check box is checked, and Red otherwise.
Essentially I don't know how to bind data between columns of the datagrid. And sample code/link providing example would be useful.
I haven't used the WPF Toolkit's DataGrid much, but from what I can gather, one method is to use DataGridTemplateColumn's and then set up your DataTriggers based on the binding.
Here is an example that uses DataTriggers to set the style of the Foreground color as well the entire row's background color. Of note, you'll need a boolean Property in your ItemsSource's binding to make this work with this method.
XAML
<Window.Resources>
<Style TargetType="{x:Type tk:DataGridRow}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=IsSelected}" Value="True">
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type TextBlock}" x:Key="MyTextBlockStyle">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
<DataTrigger
Binding="{Binding Path=IsSelected}" Value="False">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<tk:DataGrid x:Name="MyGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding}">
<tk:DataGrid.Columns>
<tk:DataGridTemplateColumn Header="Selected"
Width="75">
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsSelected}"/>
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
<tk:DataGridTemplateColumn Header="Name" Width="100" >
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"
Style="{StaticResource MyTextBlockStyle}" />
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
</tk:DataGrid.Columns>
</tk:DataGrid>
</Grid>
Code Behind
public partial class DataGridDataTrigger : Window
{
public List<Person> People { get; set; }
public DataGridDataTrigger()
{
InitializeComponent();
var names = new List<string> { "Joe", "Bob", "Frank", "Scott", "Mike" };
People = new List<Person>();
names.ForEach( x => People.Add( new Person { Name = x } ) );
People.ForEach( x =>
{
if( x.Name.Contains( "o" ) )
x.IsSelected = true;
} );
MyGrid.DataContext = People;
}
}
public class Person
{
public string Name { get; set; }
public bool IsSelected { get; set; }
}

Resources