WPF DataGridCell Style binding issue - c#

I'm facing a strange issue binding Collection to DataGrid on which when selecting a row the binding seems to disappear (I have an empty cell)
Here is my object list
public ReadOnlyCollection<ItemPoint> CurrentItemPoints { get; private set; }
ItemPoint object is defined as :
public sealed class ItemPoint : PropertyChangedNotifierBase
{
private bool _IsSubscribed = false;
private string _Name = string.Empty;
private string _Value = string.Empty;
private DateTime _ValueTime = DateTime.UtcNow;
public bool IsSubscribed
{
get { return _IsSubscribed; }
set
{
if (_IsSubscribed != value)
ChangeSubscription(value);
ChangeProperty("IsSubscribed", ref _IsSubscribed, value);
}
}
public string Name
{
get { return _Name; }
private set
{
ChangeProperty("Name", ref _Name, value);
}
}
public string Value
{
get { return _Value; }
set
{
_Value = value;
}
}
public DateTime ValueTime
{
get { return _ValueTime; }
private set
{
ChangeProperty("ValueTime", ref _ValueTime, value);
}
}
}
here is my xaml:
<DataGrid Name="PointsList" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="2,0,0,0"
ItemsSource="{Binding CurrentItemPoints, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding Path=IsSubscribed, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Width="10">
<DataGridCheckBoxColumn.CellStyle>
<Style>
<EventSetter Event="CheckBox.Checked" Handler="SubscriptionCheckBox_Checked"/>
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
<DataGridTextColumn Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" Header="Name" Width="*"/>
<DataGridTextColumn Binding="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, IsAsync=True, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}" Header="WorkingValueColumn" Width="*"/>
<DataGridTextColumn Binding="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, IsAsync=True, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}" Header="NonWorkingValueColumn" Width="*">
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Path=ValueTime, UpdateSourceTrigger=PropertyChanged, NotifyOnTargetUpdated=True, Mode=OneWay, StringFormat='{}{0:dd-MM-yyyy HH:mm:ss.fff}'}" Header="Time" Width="*">
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
If I now display the datagrid everything is ok... until I select any cell of the grid.
Doing that I will have the "WorkingValueColumn" working well... meaning that will keep displaying the selected item value while the "NonWorkingValueColumn" will display an empty cell...
Here is a screenshot of part of datagrid as sample:
I've been looking for a while now without finding any kind of explication... I though I could have missed some selection template or something like that but I really don't know.
Thanks for your help!

I finally could workaround that strange behavior changing the DataTextColumn by a DataTemplateColumn as that:
<DataGridTemplateColumn Header="Value">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=OneWay, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}" HorizontalAlignment="Stretch"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}" BorderThickness="0" HorizontalAlignment="Stretch"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

Related

new row in datagrid with custom data

I have two datagrids. The first one is an overview about a time tracking. The second one shows some details.
For example:
The first datagrid contains each day, where a employee works. The second datagrid contains the time stamping for a day.
However, when I add a new stamping row, the controls in the datagrid are empty. That is not really a problem but the datepicker shows the date 01/01/0001.
what i want is when i add a new row, the date field should be filled with the date from the first datagrid.
VIEW:
<DataGrid Grid.Column="0"
IsReadOnly="True"
AutoGenerateColumns="False"
Grid.Row="0"
x:Name="DgStundenView"
Margin="0 0 10 0"
SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding TimeOverviewList}">
<DataGrid.Columns>
<DataGridTextColumn Header="Wochentag" Binding="{Binding Wochentag}" />
<DataGridTextColumn Header="Datum" Binding="{Binding Tag}" />
<DataGridTextColumn Header="Arbeitszeit Soll" Binding="{Binding ArbeitszeitInStunden}" />
<DataGridTextColumn Header="Arbeitszeit gesamt" Binding="{Binding GesamtDauerInStunden}" />
<DataGridTextColumn Header="Pausenzeit" Binding="{Binding Pausenzeit}" />
<DataGridTextColumn Header="Pausendifferenz" Binding="{Binding PausenDifferenzInStunden}" />
<DataGridTextColumn Header="Arbeitszeit inkl. Pause" Binding="{Binding TatsaechlicheDauerInStunden}" />
<DataGridCheckBoxColumn Header="Status" Binding="{Binding StempelungVorhanden,Mode=OneWay}" />
<DataGridTextColumn Header="Info" Binding="{Binding Info}" />
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Wochentag}" Value="Sa">
<Setter Property="Background" Value="LightGray" />
</DataTrigger>
<DataTrigger Binding="{Binding Wochentag}" Value="So">
<Setter Property="Background" Value="LightGray" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
<DataGrid Grid.Column="0"
Grid.Row="1"
x:Name="DgStempelungen"
Margin="0 10 10 0"
AutoGenerateColumns="False"
CanUserAddRows="True"
SelectedItem="{Binding SelectedValue}"
ItemsSource="{Binding TimeDetailList}">
<DataGrid.Resources>
<x:Array x:Key="Reasons" Type="system:String">
<system:String>NICHT ANWENDBAR</system:String>
<system:String>KRANK</system:String>
<system:String>GANZER TAG URLAUB</system:String>
</x:Array>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="ID" IsReadOnly="True" Binding="{Binding Id}"/>
<DataGridTemplateColumn Header="KOMMEN DATUM">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding ComeBooking}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="KOMMEN UHRZEIT">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<materialDesign:TimePicker Text="{Binding ComeBookingTime, StringFormat=t}"
Is24Hours="True">
</materialDesign:TimePicker>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="GEHEN DATUM">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding GoBooking}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="GEHEN UHRZEIT">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<materialDesign:TimePicker Text="{Binding GoBookingTime, StringFormat=t}"
Is24Hours="True">
</materialDesign:TimePicker>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridComboBoxColumn Header="GRUND"
ItemsSource="{StaticResource Reasons}"
TextBinding="{Binding Info}"/>
</DataGrid.Columns>
</DataGrid>
VIEW MODEL
[CanBeNull] private ClassTimeTrackingDayStamp _selectedItem;
[CanBeNull]
public ClassTimeTrackingDayStamp SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged();
if (_selectedItem != null)
TimeDetailList = new ObservableCollection<ClassTimeTrackingTimeStamp>(_selectedItem.Stempelungen);
}
}
private ObservableCollection<ClassTimeTrackingDayStamp> _timeOverviewList;
public ObservableCollection<ClassTimeTrackingDayStamp> TimeOverviewList
{
get { return _timeOverviewList; }
set
{
_timeOverviewList = value;
OnPropertyChanged();
}
}
[CanBeNull] private ObservableCollection<ClassTimeTrackingTimeStamp> _timeDetailList;
[CanBeNull]
public ObservableCollection<ClassTimeTrackingTimeStamp> TimeDetailList
{
get { return _timeDetailList; }
set
{
_timeDetailList = value;
OnPropertyChanged();
}
}
MODEL
public class ClassTimeTrackingTimeStamp : ViewModelBase
{
public DateTime ComeBooking { get; set; }
public DateTime GoBooking { get; set; }
public TimeSpan ComeBookingTime { get; set; }
public TimeSpan GoBookingTime { get; set; }
public int Id { get; set; }
public string Info { get; set; }
}
Update
EVENT OVER MVVM VIEW
<DataGrid Grid.Column="0"
Grid.Row="1"
x:Name="DgStempelungen"
Margin="0 10 10 0"
AutoGenerateColumns="False"
CanUserAddRows="True"
SelectedItem="{Binding SelectedValue}"
ItemsSource="{Binding TimeDetailList}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="AddingNewItem">
<i:InvokeCommandAction Command="{Binding Path=AddNewItemCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
...
VIEW MODEL
public ICommand AddNewItemCommand { get; set; }
#endregion Properties
#region Events
public event EventHandler AddNewItemEventHandler;
#endregion
#region Constructor
public WTimeClockOverviewVM()
{
AddNewItemCommand = new RelayCommand(o =>
{
this.AddNewItemEventHandler?.Invoke(this, EventArgs.Empty);
});
}
CODE BEHIND
public WTimeClockOverview()
{
InitializeComponent();
var vm = (WTimeClockOverviewVM)DataContext;
if(vm == null) return;
vm.AddNewItemEventHandler += (sender, args) =>
{
var selectedItemInFirstDg = vm.SelectedItem;
if (selectedItemInFirstDg != null)
{
var newItem = new ClassTimeTrackingTimeStamp();
newItem.ComeBooking = selectedItemInFirstDg.Tag;
}
};
}
Thank you for your help :)
Best regards
Levin
In second DataGrid, you can subscribe to AddingNewItem event
<DataGrid x:Name="DgStempelungen"
AddingNewItem="DgStempelungen_OnAddingNewItem"
...
In code behind
private void DgStempelungen_OnAddingNewItem(object sender, AddingNewItemEventArgs e)
{
// I'll assume that the Model's Name in first DataGrid collection is StudentItem!
var selectedItemInFirstDg = DgStundenView.SelectedItem as StudentItem;
// or you can get the selected item from the DataContext:
// (DgStundenView.DataContext as MyViewModel)?.SelectedItem;
if (selectedItemInFirstDg != null){
// create the new row with data from selectedItemInFirstDg, ex. date, etc...
e.NewItem = new ClassTimeTrackingDayStamp
{
ComeBooking = selectedItemInFirstDg.Date; // for example
};
}
}

Proper MVVM for a WPF ListCollectionView?

I'm implementing a ListCollectionView so I can get groupings of objects within my datagrid. It seems to be working pretty well, but it doesn't feel like proper MVVM. I'm a little confused because a property can't directly be defined as a ListCollectionView. Instead, a separate collection has to be provided that implements IList.
The original ObservableCollection<EntityConcept> has too much information to display in the datagrid at once. The user selects one of the EntityConcept using a combobox, then a ListCollectionView<PropertyItem> is constructed to display all of the PropertyItem that belong to that EntityConcept.
I feel like it would make sense for the ObservableCollection<PropertyItem> to be defined as a ListCollectionView from the start. However, I don't think I can do that.
public class EntityConcept
{
private string _Name;
private ObservableCollection<PropertyItem> _propertyList;
public ObservableCollection<PropertyItem> propertyList
{
get
{
return _propertyList;
}
set
{
_propertyList = value;
}
}
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
}
}
}
Instead, a ListCollectionView is constructed based on what the user selects in the combobox:
<ComboBox x:Name="ifcCombo"
Grid.Column="2"
Grid.Row="1"
Margin="0,3,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="150"
DisplayMemberPath="ifcEntity"
SelectedItem="{Binding chosenConcept}"
ItemsSource="{Binding ConceptList}"/>
<!--Grid.Row 3-->
<DataGrid x:Name="propertiesTable"
ItemsSource="{Binding chosenConceptProperties}"
Grid.Column="1"
Grid.ColumnSpan="3"
Grid.Row="3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
AutoGenerateColumns="False"
CanUserAddRows="False"
Margin="0,25,0,0" CellEditEnding="propertiesTable_CellEditEnding">
<DataGrid.Columns>
<DataGridCheckBoxColumn Width="75" MinWidth="50" Header="" IsReadOnly="False" Binding="{Binding On}"/>
<DataGridTextColumn Width="10*" MinWidth="250" Header="Property Name" Binding="{Binding PropertyName}" IsReadOnly="True" FontSize="10" />
<DataGridTextColumn Width="10*" MinWidth="250" Header="Mapping" Binding="{Binding Mapping}" IsReadOnly="False" FontSize="10" />
<!--<DataGridTemplateColumn Width="*" Header="" CellTemplateSelector="{StaticResource myDynamicTemplateSelector}"/>-->
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="{Binding StringType, Converter={StaticResource stringToBrush}}"/>
</Style>
</DataGrid.RowStyle>
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander x:Name="exp" IsExpanded="True" Background="White" Foreground="Black" BorderThickness="1,1,1,5">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="HeaderCheckBox" Grid.Column="2" Tag="{Binding Name}" Margin="5,0,0,0" Click="CheckAll"/>
<TextBlock Grid.Column="0" Text="{Binding Path=Name}" Foreground="#2a5fa4" FontWeight="Bold" Margin="10,0,0,0"/>
</StackPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter/>
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
Instead, the ListCollectionView is manually constructed every time the chosenConcept is set. This doesn't feel correct to me, and I think there must be a cleaner way.
private ObservableCollection<EntityConcept> _ConceptList = new ObservableCollection<EntityConcept>();
private EntityConcept _chosenConcept = new EntityConcept();
private ListCollectionView _chosenConceptProperties;
public EntityConcept chosenConcept
{
get
{
return _chosenConcept;
}
set
{
_chosenConcept = value;
OnPropertyChanged(nameof(chosenConcept));
ConstructChosenConcept();
}
}
public ListCollectionView chosenConceptProperties
{
get
{
return _chosenConceptProperties;
}
set
{
_chosenConceptProperties = value;
OnPropertyChanged(nameof(chosenConceptProperties));
}
}
public void ConstructChosenConcept()
{
if(chosenConcept != null)
{
chosenConceptProperties = new ListCollectionView(chosenConcept.propertyList);
chosenConceptProperties.GroupDescriptions.Add(new PropertyGroupDescription("Pset"));
name = chosenConcept.Name;
description = chosenConcept.Description;
objectType = chosenConcept.ObjectType;
tag = chosenConcept.Tag;
}
}

How to set selected items in MVVM using SelectionMode="Extended"?

Goal
I currently have an app that select the items only by selecting the check box. Like so:
My goal is to multi select items (Which highlights the selected rows), right click and have a data context menu item to select, selected items.
Desired Output
As an example, this would be my desired output (Obviously not selecting the checkboxes manually).
Question
What would a good starting point to achieve this output? I couldn't find any examples.
Code
The Model is pretty simple.
public class OrdersModel : BaseVM
{
public int OrderId { get; set; } // Hidden
public string OrderNumber { get; set; }
public DateTime OrderDate { get; set; }
private bool _selectedRecord;
public bool SelectedRecord
{
get { return _selectedRecord; }
set
{
_selectedRecord = value;
OnPropertyChanged();
}
}
}
And this is my current XAML.
<DataGrid ItemsSource="{Binding Orders}"
AutoGenerateColumns="False"
SelectionMode="Extended"
CanUserAddRows="False">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedRecord}" Value="True">
<Setter Property="Background" Value="LightGray" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<!--Columns Used-->
<DataGrid.Columns>
<!--Select All Column-->
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox Content="Select All"
IsChecked="{Binding DataContext.SelectAll, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"
Command="{Binding DataContext.ToggleAllOrdersCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=SelectedRecord, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Order Date"
Binding="{Binding OrderDate, StringFormat='{}{0:MM-dd-yyyy}'}" Width="*"
IsReadOnly="True"
FontSize="14" />
<DataGridTextColumn Header="Order Number"
Binding="{Binding OrderNumber}" Width="*"
IsReadOnly="True"
FontSize="14" />
</DataGrid.Columns>
<!--Menu Options-->
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Select Highlighted Rows" />
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
This solution worked for me. I took the liberty of changing some of your property names to avoid confusion as to what is "selected" and what is "checked".
OrdersModel:
public class OrdersModel : BaseVM
{
public int OrderId { get; set; } // Hidden
public string OrderNumber { get; set; }
public DateTime OrderDate { get; set; }
private bool _selected;
private bool _checked;
public bool Selected
{
get { return _selected; }
set { _selected = value; OnPropertyChanged(); }
}
public bool Checked
{
get { return _checked; }
set { _checked = value; OnPropertyChanged(); }
}
}
MainWindow.xaml.cs
<DataGrid ItemsSource="{Binding Orders}"
AutoGenerateColumns="False"
SelectionMode="Extended"
CanUserAddRows="False"
Tag="{Binding ElementName=myWindow,Path=DataContext}"
>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected" Value="{Binding Selected}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Selected}" Value="True">
<Setter Property="Background" Value="LightGray" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<!--Columns Used-->
<DataGrid.Columns>
<!--Select All Column-->
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox Content="Select All"
IsChecked="{Binding DataContext.SelectAll, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"
Command="{Binding DataContext.ToggleAllOrdersCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=Checked, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Order Date"
Binding="{Binding OrderDate, StringFormat='{}{0:MM-dd-yyyy}'}" Width="*"
IsReadOnly="True"
FontSize="14" />
<DataGridTextColumn Header="Order Number"
Binding="{Binding OrderNumber}" Width="*"
IsReadOnly="True"
FontSize="14" />
</DataGrid.Columns>
<!--Menu Options-->
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Select Highlighted Rows" Command="{Binding CheckSelectedCommand}"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
MainWindow.xaml.cs:
public class MainWindowViewModel : BaseVM
{
private ObservableCollection<OrdersModel> _orders;
private RelayCommand _checkSelectedCommand;
public MainWindowViewModel()
{
Initialize();
}
public ObservableCollection<OrdersModel> Orders
{
get
{
if (_orders == null)
_orders = new ObservableCollection<OrdersModel>();
return _orders;
}
}
public RelayCommand CheckSelectedCommand
{
get
{
if (_checkSelectedCommand == null)
_checkSelectedCommand = new RelayCommand(o => DoCheckSelectedRows());
return _checkSelectedCommand;
}
}
public void Initialize()
{
var dateTime = DateTime.Now.AddDays(-5);
for (int i = 0; i < 10; i++)
{
this.Orders.Add(new OrdersModel() { OrderId = i, OrderDate = dateTime.AddHours(4), OrderNumber = $"CA{i + 1593:0000}" });
}
}
private void DoCheckSelectedRows()
{
var selectedOrders = this.Orders.Where(o => o.Selected);
foreach (var order in selectedOrders)
{
order.Checked = true;
}
}
}
Solution will look like this:

wpf datagrid expand all not working

Hi have a Datagrid with groups, i added a button to Expand All groups but it's not working, all groups stay collapsed.
I'm using PropertyChanged event Handler and a button with a Command
Here is the xaml:
<StackPanel Grid.Row="0">
<Button x:Name="ExpandAll" Content="Tout deplier" VerticalAlignment="Bottom" Command="{Binding ExpandAll}"/>
<!-- This textblock text is updated by the Expanded property changed -->
<TextBlock Text="{Binding Expanded}" />
</StackPanel>
<DataGrid x:Name="GrdLignes" HorizontalAlignment="Stretch" VerticalContentAlignment="Stretch" Margin="0,0,0,0"
Grid.Row="1" VerticalAlignment="Top" AutoGenerateColumns="False" CanUserAddRows="False"
CanUserDeleteRows="False" ItemsSource="{Binding Lignes}" IsReadOnly="True"
RowDetailsVisibilityMode="VisibleWhenSelected" RowHeaderWidth="25">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander Background="Lavender" IsExpanded="{Binding Expanded}">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Padding="0,0,5,0" FontWeight="Bold" />
<TextBlock Text="{Binding Path=ItemCount}" Padding="0,0,5,0"/>
<TextBlock Text="Commandes"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Pièce Achat" Binding="{Binding Path=Piece}" FontWeight="Bold"/>
<DataGridTextColumn Header="Type" Binding="{Binding Path=TypeLabel}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="{Binding Path=Type, Converter={StaticResource TypeToBrushConverter}}" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Statut" Binding="{Binding Path=StatutLabel}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="{Binding Path=Statut, Converter={StaticResource StatutToBrushConverter}}" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid RowHeaderWidth="25" ItemsSource="{Binding Path=Lignes}" AutoGenerateColumns="False" Margin="0" CanUserAddRows="False" CanUserDeleteRows="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Acheteur" Binding="{Binding Path=Acheteur}"/>
<DataGridTextColumn Header="Pièce" Binding="{Binding Path=Piece}"/>
<DataGridTextColumn Header="Client" Binding="{Binding Path=Client}"/>
<DataGridTextColumn Header="Ref" Binding="{Binding Path=ArRef}"/>
<DataGridTextColumn Header="Ref Fourn" Binding="{Binding Path=RefFourn}"/>
<DataGridTextColumn Header="Designation" Binding="{Binding Path=Designation}"/>
<DataGridTextColumn Header="Qte" Binding="{Binding Path=CmQte}"/>
<DataGridTextColumn Header="Vendeur" Binding="{Binding Path=Vendeur}"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Here is the viewModel:
public class MainViewModel : INotifyPropertyChanged
{
private bool _expanded = false;
public bool Expanded
{
get { return _expanded; }
set
{
_expanded = value;
OnPropertyChanged("Expanded");
}
}
public ICommand ExpandAll { get; set; }
public MainViewModel()
{
ExpandAll = new Command(ExpandAllAction);
}
private void ExpandAllAction(object parameters)
{
Expanded = true;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I think you need to set the UpdateSource Trigger to "PropertyChanged" when binding to the Expanded Property.
<Expander Background="Lavender" IsExpanded="{Binding Expanded, UpdateSourceTrigger=PropertyChanged}">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Padding="0,0,5,0" FontWeight="Bold" />
<TextBlock Text="{Binding Path=ItemCount}" Padding="0,0,5,0"/>
<TextBlock Text="Commandes"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
I found a solution from this thread:
https://stackoverflow.com/a/12611779/3182178
I added in MainWindow class this code:
public static class VisualTreeHelper
{
public static Collection<T> GetVisualChildren<T>(DependencyObject current) where T : DependencyObject
{
if (current == null)
return null;
var children = new Collection<T>();
GetVisualChildren(current, children);
return children;
}
private static void GetVisualChildren<T>(DependencyObject current, Collection<T> children) where T : DependencyObject
{
if (current != null)
{
if (current.GetType() == typeof(T))
children.Add((T)current);
for (int i = 0; i < System.Windows.Media.VisualTreeHelper.GetChildrenCount(current); i++)
{
GetVisualChildren(System.Windows.Media.VisualTreeHelper.GetChild(current, i), children);
}
}
}
}
private void ExpandAll_OnClick(object sender, RoutedEventArgs e)
{
Collection<Expander> collection = VisualTreeHelper.GetVisualChildren<Expander>(GrdLignes);
foreach (Expander expander in collection)
expander.IsExpanded = true;
}
private void CollapseAll_OnClick(object sender, RoutedEventArgs e)
{
Collection<Expander> collection = VisualTreeHelper.GetVisualChildren<Expander>(GrdLignes);
foreach (Expander expander in collection)
expander.IsExpanded = false;
}
Then inside the xaml i added two button with this code:
<Button Name="ExpandAll" Content="++" VerticalAlignment="Bottom" Width="30" Click="ExpandAll_OnClick"/>
<Button Name="CollapseAll" Content="--" VerticalAlignment="Bottom" Width="30" Margin="0" Click="CollapseAll_OnClick"/>
It's not the best but it's working...
Using one-way binding to set all groups open or close after a button click command.
View
<UserControl.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</UserControl.Resources>
<!-- grid code -->
<Expander IsExpanded="{Binding Source={StaticResource proxy}, Path=Data.Expanded, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
ViewModel
public bool Expanded
{
get { return _expanded; }
set { _expanded = value; OnPropertyChanged(); }
}
Proxy
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return 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));
}
It's because the binding is done on the group, not on the main model. Replace your XAML code by:
<Expander IsExpanded="{Binding Path=DataContext.IsExpanded, Mode=OneWay,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
Note the one way mode: when the user expands or collapses the groups, you don't want to push back the change to your model.

WPF DataGrid different controls in the same column - incorrect binding

I am developing the application which performs checks on the list of items. Each item has the list of the checks that need to be performed on it. Each check can be one of 3 types: CheckBox, ComboBox, TextBox.
I would like to have Datagrid with 2 columns (one for item name, second for list of checks). Second column contains another DataGrid with 2 columns (one for check name, second for check control). The purpose is to have different types of controls in the same column bound with the Check models.
The problem is that binding with CheckValue doesn't work, however bindings with all the other properties work fine.
The last column contains CheckBoxes, TextBox and ComboBox, however they are not filled with any values.
Does anyone know what is wrong with below code?
Here are examples of model classes
public class Item
{
public string ItemName { get; set; }
public ObservableCollection<Check> Checks { get; set; }
public Item()
{
Checks = new ObservableCollection<Check>();
}
}
public enum CheckType
{
CheckBox,
ComboBox,
TextBox
}
public abstract class Check
{
public string CheckName { get; set; }
public CheckType CheckType { get; protected set; }
public abstract object CheckValue { get; set; }
}
public class CheckBox : Check
{
private bool checkValue;
public CheckBox()
{
CheckType = CheckType.CheckBox;
}
public override object CheckValue
{
get
{
return checkValue;
}
set
{
checkValue = (bool)value;
}
}
}
public class ComboBox : Check
{
private List<string> checkValue;
public ComboBox()
{
CheckType = CheckType.ComboBox;
}
public override object CheckValue
{
get
{
return checkValue;
}
set
{
checkValue = value as List<string>;
}
}
}
public class TextBox : Check
{
private string checkValue;
public TextBox()
{
CheckType = CheckType.TextBox;
}
public override object CheckValue
{
get
{
return checkValue;
}
set
{
checkValue = value as string;
}
}
}
public class MainViewModel
{
public ObservableCollection<Item> Items { get; set; }
public MainViewModel()
{
Items = new ObservableCollection<Item>();
Item item = new Item();
item.ItemName = "First item";
Check check1 = new CheckBox() { CheckName = "Check 1", CheckValue = true };
Check check2 = new CheckBox() { CheckName = "Check 2", CheckValue = false };
Check text1 = new TextBox() { CheckName = "Check 3", CheckValue = "Please enter check" };
Check combo1 = new ComboBox() { CheckName = "Check 4", CheckValue = new List<string> { "Value1", "Value2" } };
item.Checks.Add(check1);
item.Checks.Add(check2);
item.Checks.Add(text1);
item.Checks.Add(combo1);
Items.Add(item);
}
}
And finally here is XAML code of the main window.
<Window x:Class="ItemTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm ="clr-namespace:ItemTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<vm:MainViewModel x:Key="mainViewModel"/>
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource mainViewModel}}">
<DataGrid ItemsSource="{Binding Path=Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Item" Binding="{Binding ItemName}" />
<DataGridTemplateColumn Header="Checks">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding Checks}" AutoGenerateColumns="False" HeadersVisibility="None">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding CheckName}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding CheckType}" Value="CheckBox">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox IsChecked="{Binding CheckValue}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding CheckType}" Value="ComboBox">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ComboBox ItemsSource="{Binding CheckValue}" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding CheckType}" Value="TextBox">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding CheckValue}" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
Just set the ItemControl's Content property:
<ContentControl Content="{Binding}">
WPF will automatically set DataTemplate's DataContext to its parent ContentControl's Content. But in your XAML you don't set the Content property (you only specify ContentControl's Style, but forget to set its Content).
And don't forget to set UpdateSourceTrigger=PropertyChanged on your control bindings, otherwise you may see no updates in your viewmodel.
XAML example working, with binding for BindingList :
<DataGrid x:Name="dataGridParametros"
Grid.Row="1"
Margin="5"
AutoGenerateColumns="False"
HeadersVisibility="All"
ItemsSource="{Binding}"
RowHeaderWidth="20"
SelectionUnit="FullRow"
ScrollViewer.CanContentScroll="True"
CanUserAddRows="false"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
FontFamily="Arial"
CellEditEnding="dataGridParametros_CellEditEnding" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding IdParametro}" Header="Id" FontFamily="Arial" IsReadOnly="True" Visibility="Hidden"/>
<DataGridTextColumn Binding="{Binding Codigo}" Header="Código" FontFamily="Arial" IsReadOnly="True"/>
<DataGridTextColumn Width="200" Binding="{Binding Mnemonico}" Header="Mnemonico" FontFamily="Arial" IsReadOnly="True" />
<DataGridTextColumn Width="250*" Binding="{Binding Descricao}" Header="Descrição" FontFamily="Arial" IsReadOnly="True" />
<DataGridTemplateColumn Header="Valor" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding TipoCampo}" Value="CheckBox">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox IsChecked="{Binding Valor , Mode=TwoWay , UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding TipoCampo}" Value="ComboBox">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ComboBox ItemsSource="{Binding Valor , Mode=TwoWay , UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding TipoCampo}" Value="TextBox">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Valor , Mode=TwoWay , UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

Categories