Styling DataGrid rows WPF exception - c#

I'm trying to style my grid a little bit. I want to color rows based on information in the each row. I've got this nasty error during this operation. Moreover this is happening during application startup. This my first steps in WPF I'm trying to make some useful logger, but now I hit the huge wall.
Error:
Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.
It is worth mentioning that I'm using static list as ItemSource to grid. Below some code of mine.
<DataGrid DockPanel.Dock="Bottom" Height="Auto" ItemsSource="{Binding Source={x:Static Log:Logger.LogCollection}, Mode=OneWay}" FontSize="12" FontFamily="Segoe UI">
<i:Interaction.Behaviors>
<fw:ScrollGridView/>
</i:Interaction.Behaviors>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding LogType}" Value="Info">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid>
</DockPanel>
And the class
public static class Logger
{
private static ObservableCollection<LogMessage> _logCollection = new ObservableCollection<LogMessage>();
public static ObservableCollection<LogMessage> LogCollection
{
get { return Logger._logCollection; }
set
{
if (_logCollection.Count > 500)
{
_logCollection.RemoveAt(_logCollection.Count - 1);
}
Logger._logCollection = value;
}
}
public static void Log(string message)
{
Log(LogType.Info, message, null);
}
public static void Log(LogType type, string message, string exception)
{
LogCollection.Add(new LogMessage { Type = type, Time = DateTime.Now, Message = message, Exception = exception });
}
}
LogMessage:
public enum LogType
{
Debug,
Warning,
Error,
Info
}
public class LogMessage
{
public LogType Type { get; set; }
public DateTime Time { get; set; }
public string Message { get; set; }
public string Exception { get; set; }
}
Thanks!

Make sure you define your Columns for DataGrid in DataGrid.Columns like below and not directly within DataGrid and define your Style in DataGrid.Resource
<DataGrid>
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding LogType}" Value="Info">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn/>
</DataGrid.Columns>
</DataGrid>

It helped!!! Now it looks like this:
<DataGrid DockPanel.Dock="Bottom" Height="Auto" ItemsSource="{Binding Source={x:Static Log:Logger.LogCollection}, Mode=OneWay}" FontSize="12" FontFamily="Segoe UI" AutoGenerateColumns="False">
<i:Interaction.Behaviors>
<fw:ScrollGridView/>
</i:Interaction.Behaviors>
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="Info">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn MinWidth="30" Header="Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Width="16"
Height="16"
Source="{Binding Path=Type,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource ImageTypeConverter}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Time"
Binding="{Binding Time}"/>
<DataGridTextColumn Header="Message"
Binding="{Binding Message}"
Width="1200">
<DataGridTextColumn.ElementStyle>
<Style>
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Exception"
Binding="{Binding Exception}"
Width="1200">
<DataGridTextColumn.ElementStyle>
<Style>
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
And I have another one question. Before I have added DataGrid.Resources my behavior worker perfectly and after that the event stopped firing. It is strange because I didn't change way of adding item to my grid source.
public class ScrollGridView : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectionChanged += new SelectionChangedEventHandler(AssociatedObject_SelectionChanged);
}
private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is DataGrid)
{
DataGrid grid = (sender as DataGrid);
if (grid.Items.Count > 0)
{
var border = VisualTreeHelper.GetChild(grid, 0) as Decorator;
if (border != null)
{
var scroll = border.Child as ScrollViewer;
if (scroll != null && !scroll.IsMouseOver) scroll.ScrollToEnd();
}
}
}
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.SelectionChanged -= new SelectionChangedEventHandler(AssociatedObject_SelectionChanged);
}
}

Related

WPF Dependency Property Not Binding / PropertyChangedCallback Not Getting Called

My project consists of a Treeview with a Datagrid as a child. The parent in the Treeview has a checkbox, and every row in the datagrid also has a check box. My goal is to bind the checkboxes together, so that when the parent checkbox is checked, all of the datagrid rows are checked as well. When the datagrid rows are checked I would like the parent checkbox to be updated based of the datagrid row checked values.
Parent checkbox IsChecked = true when all of the datagrid row checkboxes are true, null if only some are, and false if no rows are checked.
The problem I'm facing is that my dependency properties aren't updating and the PropertyChangedCallback is not getting called.
This is my Checkbox_Helper class in which the IsChecked properties of both the parent and the datagrid row checkboxes are bound to.
public class CheckBox_Helper : DependencyObject
{
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), typeof(CheckBox_Helper),
new PropertyMetadata(false, new PropertyChangedCallback(OnIsCheckedPropertyChanged)));
private static void OnIsCheckedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Parent && ((bool?)e.NewValue).HasValue)
{
foreach (Child s in (d as Parent).SheetList[0].SheetChildList)
{
if (s.IsSizeCompatible)
{
SetIsChecked(s, (bool?)e.NewValue);
}
}
}
if (d is Child)
{
AddToCheckedElements(d as Child);
int _checked = (d.GetValue(ParentProperty) as Parent).SheetList[0].SheetChildList.Where(x => GetIsChecked(x) == true).Count();
int _unchecked = (d.GetValue(ParentProperty) as Parent).SheetList[0].SheetChildList.Where(x => GetIsChecked(x) == false).Count();
int _total = (d.GetValue(ParentProperty) as Parent).SheetList[0].SheetChildList.Count();
if ((d as Child).MegaParent.SelectedRowList != null && (d as Child).MegaParent.SelectedRowList.Count > 0)
{
(d as Child).MegaParent.SelectedRowList.Where(i => GetIsChecked(i) != GetIsChecked(d as Child)).ToList().ForEach(i => CheckBox_Helper.SetIsChecked(i, GetIsChecked(d as Child)));
}
if (_checked == 0)
{
(d.GetValue(ParentProperty) as Parent).HasCheckedItems = false;
}
else
{
(d.GetValue(ParentProperty) as Parent).HasCheckedItems = true;
}
(d.GetValue(ParentProperty) as Parent).CheckedRatio = $"{_checked}/{_total}";
if (_unchecked > 0 && _checked > 0)
{
CheckBox_Helper.SetIsChecked((d as Child).GetValue(CheckBox_Helper.ParentProperty) as DependencyObject, null);
return;
}
if (_checked > 0)
{
CheckBox_Helper.SetIsChecked((d as Child).GetValue(CheckBox_Helper.ParentProperty) as DependencyObject, true);
return;
}
CheckBox_Helper.SetIsChecked((d as Child).GetValue(CheckBox_Helper.ParentProperty) as DependencyObject, false);
}
}
public static readonly DependencyProperty ParentProperty = DependencyProperty.RegisterAttached("Parent", typeof(Parent), typeof(CheckBox_Helper));
public static void SetIsChecked(DependencyObject element, bool? IsChecked)
{
element.SetValue(CheckBox_Helper.IsCheckedProperty, IsChecked);
}
public static bool? GetIsChecked(DependencyObject element)
{
return (bool?)element.GetValue(CheckBox_Helper.IsCheckedProperty);
}
// Set and Get Parent are unused since they are hardcoded categories
public static void SetParent(DependencyObject element, object Parent)
{
element.SetValue(CheckBox_Helper.ParentProperty, Parent);
}
public static object GetParent(DependencyObject element)
{
return (object)element.GetValue(CheckBox_Helper.ParentProperty);
}
private static void AddToCheckedElements(Child child)
{
ObservableCollection<Child> tempList = child.MegaParent.CheckedElements;
if (!child.MegaParent.CheckedElements.Contains(child) && GetIsChecked(child) == true)
{
tempList.Add(child);
child.MegaParent.CheckedElements = tempList;
}
else if (child.MegaParent.CheckedElements.Contains(child) && GetIsChecked(child) == false)
{
tempList.Remove(child);
child.MegaParent.CheckedElements = tempList;
}
}
}
This is the code for the Parent class:
public class Parent : DependencyObject
{
public string Name { get; set; }
public ObservableCollection<ChildList> SheetList { get; set; }
public static readonly DependencyProperty HasCheckedItemsProperty =
DependencyProperty.Register("HasCheckedItems", typeof(bool), typeof(Parent), new UIPropertyMetadata(false));
public bool HasCheckedItems
{
get { return (bool)GetValue(HasCheckedItemsProperty); }
set { SetValue(HasCheckedItemsProperty, value); }
}
public static readonly DependencyProperty CheckedRatioProperty =
DependencyProperty.Register("CheckedRatio", typeof(string), typeof(Parent), new UIPropertyMetadata($"0/0"));
public string CheckedRatio
{
get { return (string)GetValue(CheckedRatioProperty); }
set { SetValue(CheckedRatioProperty, value); }
}
public Parent(string name)
{
Name = name;
SheetList = new ObservableCollection<ChildList>();
}
}
This is the code for the Child class:
(Note: The MegaParent property is the viewmodel)
public class Child : DependencyObject
{
public ViewSheet Sheet { get; set; }
public string Name { get; set; }
public string Number { get; set; }
private string _paperSize = "";
public string PaperSize
{
get { return _paperSize; }
set { _paperSize = value; }
}
public static readonly DependencyProperty IsSizeCompatibleProperty =
DependencyProperty.Register("IsSizeCompatible", typeof(bool), typeof(Child), new UIPropertyMetadata(true));
public bool IsSizeCompatible
{
get { return (bool)GetValue(IsSizeCompatibleProperty); }
set { SetValue(IsSizeCompatibleProperty, value); }
}
public PrintSheets_ViewModel MegaParent { get; set; }
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected", typeof(bool), typeof(Child), new UIPropertyMetadata(false));
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); AddToSelectedList(); }
}
private void AddToSelectedList()
{
ObservableCollection<Child> tempList = MegaParent.SelectedRowList;
if (!MegaParent.SelectedRowList.Contains(this) && (bool)GetValue(IsSelectedProperty) == true)
{
tempList.Add(this);
MegaParent.SelectedRowList = tempList;
}
else if (MegaParent.SelectedRowList.Contains(this) && (bool)GetValue(IsSelectedProperty) == false)
{
tempList.Remove(this);
MegaParent.SelectedRowList = tempList;
}
}
public Child(string name, string number, ViewSheet sheet, string paperSize, bool isSizeCompatible, PrintSheets_ViewModel megaParent)
{
Name = name;
Number = number;
Sheet = sheet;
PaperSize = paperSize;
IsSizeCompatible = isSizeCompatible;
MegaParent = megaParent;
}
}
This is the XAML code for the Treeview:
<Border Grid.Row="1" Grid.RowSpan="4" Grid.Column="3" Grid.ColumnSpan="3" Margin="10,10,10,10"
BorderBrush="Black" BorderThickness="1">
<ScrollViewer x:Name="treeScroller" PreviewMouseWheel="treeScroller_PreviewMouseWheel" VerticalScrollBarVisibility="Auto">
<TreeView Background="LightGray" ItemsSource="{Binding tv_model.ParentItems}" x:Name="sheetTree"
ScrollViewer.CanContentScroll="False">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type m:Parent}" ItemsSource="{Binding SheetList}">
<Border BorderBrush="DarkGray" CornerRadius="5" Padding="5,0,0,0" Height="40"
Width="{Binding Path=ActualWidth, ElementName=sheetTree, Converter={StaticResource minusFortyFiveConverter}}">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding HasCheckedItems}" Value="True">
<Setter Property="Background" Value="#b5ffc0"/>
<Setter Property="BorderThickness" Value="3"/>
</DataTrigger>
<DataTrigger Binding="{Binding HasCheckedItems}" Value="False">
<Setter Property="Background" Value="LightCyan"/>
<Setter Property="BorderThickness" Value="2"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center"
IsChecked="{Binding Path=(h:CheckBox_Helper.IsChecked), Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Cursor="Hand"/>
<Label Grid.Column="1" Content="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Left" FontWeight="SemiBold"/>
<Label Grid.Column="3" VerticalAlignment="Center" HorizontalContentAlignment="Right"
FontWeight="Normal" FontSize="12"
Content="Sheets Checked:"/>
<Label Grid.Column="4" VerticalAlignment="Center" HorizontalAlignment="Right"
Content="{Binding CheckedRatio}">
<Label.Style>
<Style TargetType="Label">
<Style.Triggers>
<DataTrigger Binding="{Binding HasCheckedItems}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="14"/>
</DataTrigger>
<DataTrigger Binding="{Binding HasCheckedItems}" Value="False">
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="FontSize" Value="12"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</Grid>
</Border>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type m:ChildList}">
<DataGrid x:Name="ChildDataGrid" ItemsSource="{Binding SheetChildList}"
Width="{Binding Path=ActualWidth, ElementName=sheetTree, Converter={StaticResource minusEightyConverter}}"
IsReadOnly="True" AutoGenerateColumns="False" SelectionMode="Extended" CanUserResizeColumns="True"
EnableRowVirtualization="True" CanUserAddRows="False" CanUserDeleteRows="False"
CanUserResizeRows="True" CanUserReorderColumns="False" CanUserSortColumns="True" >
<DataGrid.Resources>
<Style TargetType="DataGrid">
<EventSetter Event="LostFocus" Handler="OnLostFocus_DataGrid"/>
</Style>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSizeCompatible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSizeCompatible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Width="20">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Cursor="Hand" IsChecked="{Binding Path=(h:CheckBox_Helper.IsChecked), Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Sheet Number" Binding="{Binding Number}" IsReadOnly="True" Width="Auto"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="True" Width="*"/>
<DataGridTextColumn Header="Paper Size" Binding="{Binding PaperSize}" IsReadOnly="True" Width="Auto"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Margin" Value="0,3,0,3"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</ScrollViewer>
</Border>
I had the checkboxes working before I added some of the extra methods to the CheckBox_Helper, but I need those methods in order for other controls on my UI to function properly.
I feel like it could be a DataContext issue. I've tried adding RelativeSource to the IsChecked binding with the AncestorType as Datagrid, and I've also tried it as Window.
Still no luck, any help would be greatly appreciated.

Frontend does not change after calling function that selects all checkboxes in a WPF

I have a problem in a WPF .NET Core 3.1 that I am writing.
There is a 'Main Page', where the user can input some filters that will be used to search for some files by an external webAPI; so far so good. The response from the webAPI is an XML with the list of the files available and the user must choose which of these files download. To do so, I have a 'popup box' where the user can read all the available files and selected the desired ones by checkboxes. I have to add some buttons to select / deselect all the files and here lies the problem: the files are selected but the fronted does not notice and keep showing them as unchecked.
In the main page, parsing the XML I generate a List of these objects:
public class righeNoteSpese {
public Boolean selezionato { get; set; }
public Boolean isOK { get; set; }
public String errore { get; set; }
// Other String fields...
public righeNoteSpese() {
selezionato = false;
isOK = true;
errore = String.Empty;
}
}
and I call the popup with
ListaXML l = new ListaXML(lr);
await this.ShowChildWindowAsync(l.listaXML);
where lr is the list of rows I found.
The code behind of the popup is
public partial class ListaXML : ChildWindow
{
public List<righeNoteSpese> Elenco = new List<righeNoteSpese>();
public ListaXML()
{
InitializeComponent();
}
public ListaXML(List<righeNoteSpese> listF) {
InitializeComponent();
this.DataContext = this;
Elenco = listF;
selFiles.ItemsSource = listF;
/* If not commented the foreach works and all the rows are checked!
foreach (righeNoteSpese r in Elenco)
{
if (r.isOK)
{
r.selezionato = true;
}
}*/
}
private void All_Click(object sender, RoutedEventArgs e)
{
foreach (righeNoteSpese r in Elenco) {
if (r.isOK)
{
r.selezionato = true;
}
}
}
}
The XAML of the popup is
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Name="Btn1" Width="100" Content="Select All" Grid.Row="0" Grid.Column="0" Margin="10 15 10 15" Click="All_Click" />
<DataGrid Name="selFiles" AutoGenerateColumns="False" CanUserAddRows="false" HorizontalAlignment="Stretch" ScrollViewer.VerticalScrollBarVisibility="Auto" AlternatingRowBackground="LightGray" Grid.Row="1" Grid.ColumnSpan="4">
<DataGrid.Columns><DataGridTextColumn Header="Errore" Width="200" Binding="{Binding errore, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding isOK}" Value="False">
<Setter Property="Background" Value="Red"/>
<Setter Property="FontStyle" Value="Italic" />
</DataTrigger>
<DataTrigger Binding="{Binding selezionato}" Value="True">
<Setter Property="Background" Value="SpringGreen"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTemplateColumn Width="SizeToHeader" IsReadOnly="True" Header="Select">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding isOK}" Value="False">
<Setter Property="Background" Value="Red"/>
<Setter Property="FontStyle" Value="Italic" />
</DataTrigger>
<DataTrigger Binding="{Binding selezionato}" Value="True">
<Setter Property="Background" Value="SpringGreen"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTemplateColumn.CellStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding selezionato, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="2,0,2,0" IsEnabled="{Binding isOK, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- Other columns... -->
</DataGrid.Columns>
</DataGrid>
</Grid>
If I check manually a checkbox everything works, the background changes and the change is passed back to the main page. If I use the button the values are changed and passed back to the main page but there is no change in the frontend but if I execute these same instructions when I call the page everything is OK.
What am I missing?
Thank you for your help.
I didn't implement correctly the INotifyPropertyChanged, I changed the class from
public Boolean selezionato { get; set; }
to
private Boolean _selezionato;
public Boolean selezionato {
get {
return _selezionato;
}
set {
_selezionato = value;
OnPropertyChanged("selezionato");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
and now it works fine.

DataTrigger binded to property in ViewModel is not fired in the Button of DataGrid

I've created ControlTemplates:
<Window.Resources>
<ControlTemplate x:Key="imgNo" TargetType="{x:Type Control}">
<Image Source="pack://application:,,,/Images/up.png"/>
</ControlTemplate>
<ControlTemplate x:Key="imgUp" TargetType="{x:Type Control}">
<!--<TextBlock Text="Up"/>-->
<Image Source="pack://application:,,,/Images/up.png"/>
</ControlTemplate>
<ControlTemplate x:Key="imgDown" TargetType="{x:Type Control}">
<Image Source="pack://application:,,,/Images/downArrow.png"/>
</ControlTemplate>
<DataTemplate x:Key="ButtonOneDataTemplate">
<Control x:Name="theControl" Template="{DynamicResource imgNo}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsImageChanged}" Value="true">
<Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgUp}" />
</DataTrigger>
<DataTrigger Binding="{Binding IsImageChanged}" Value="false">
<Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgDown}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</WindowResources>
and Button in DataGrid which uses above ControlTemplates:
<DataGrid ItemsSource="{Binding Persons}" Grid.Row="1" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding IdPerson}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<Border Background="Violet">
<StackPanel>
<Button ContentTemplate="{StaticResource ButtonOneDataTemplate}"
Command="{Binding DataContext.HelloCommand, RelativeSource=
{RelativeSource AncestorType=Window}}"
CommandParameter="{Binding DataContext.Hello,
RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
</StackPanel>
</Border>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
My ViewModel:
public class MainWindowViewModel:ViewModelBase
{
public RelayCommand HelloCommand { get; set; }
public MainWindowViewModel()
{
LoadPersons();
HelloCommand = new RelayCommand(SayHello);
}
int helloCounter = 0;
private void SayHello(object obj)
{
if (helloCounter % 2 == 0)
IsImageChanged = true;
else
IsImageChanged = false;
helloCounter++;
}
private bool isImageChanged=true;
public bool IsImageChanged
{
get { return isImageChanged; }
set { isImageChanged = value;
OnPropertyChanged("IsImageChanged");
}
}
}
What I want is when I click on the button <Button ContentTemplate="{StaticResource ButtonOneDataTemplate}"/>, then Template should be replaced to {DynamicResource imgDown} or {DynamicResource imgUp}. DataTrigger depends on IsImageChanged value.
However, if I click on the Button, then DataTrigger is not fired(Controltemplates such as imgUp, imgDown are not changed). How can I achieve this from my ViewModel?
Problem is that DataGrid column is not a part of a visual tree, and because of that it does not inherit DataContext. To be able to use DataTriggers in your ButtonOneDataTemplate you need that button, you applying this template to, has correct DataContext. There is a trick, how to provide DataContext to elements that are not in VisualTree, described here
Applying that solution to your code we'll have the following:
Proxy
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));
}
Window.Resources
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
<DataTemplate x:Key="ButtonOneDataTemplate">
<Control x:Name="theControl" Template="{DynamicResource imgNo}" Foreground="Orange"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.IsImageChanged}" Value="True">
<Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgUp}" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.IsImageChanged}" Value="False">
<Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgDown}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
HeaderTemplate
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<Border Background="Violet">
<StackPanel>
<Button ContentTemplate="{StaticResource ButtonOneDataTemplate}"
DataContext="{Binding Path=Data, Source={StaticResource proxy}}"
Command="{Binding DataContext.ButtonClick, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
</StackPanel>
</Border>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>

CanUserAddRows New Row not saving in DataGrid

Created DataGrid and set CanUserAddRows="True"
Have a button which saves updates in the cs file:
private void Save_Click(object sender, RoutedEventArgs e)
{
UnitService unitService = new UnitService();
unitService.SaveUpdates(valuationCase);
MainWindow mainWin = new MainWindow();
mainWin.Show();
this.Close();
}
There is also a textbox not in the datagrid on the window which is editable and this is correctly saving edits with the save click button. Just the new rows aren't.
Any ideas??
datagrid definition:
<DataGrid Name="dgCommentsList" AutoGenerateColumns="False" Margin="10,196,9.953,38.204" CanUserAddRows="True" FontSize="18">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontSize" Value="20" />
<Setter Property="FontWeight" Value="bold" />
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Type" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="Type" Text="{Binding Type}" >
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding IsReadOnly}" Value="False">
<Setter Property="TextBox.IsReadOnly" Value="False"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsReadOnly}" Value="True">
<Setter Property="TextBox.IsReadOnly" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid>
I think you need to set the mode of the binding for it to write back to the underlying object.Plus I noticed your DataGrid does not have an ItemsSource. I'm guessing as this was just a snippet that you left it out.
<TextBox x:Name="Type" Text="{Binding Type, Mode=TwoWay}">
You should commit the edit on the row using dataGrid.CommitEdit()
Edit: After diagnosing the issue here goes
You either need to implement INotifyPropertyChanged on your DataContext class (i.e: Viewmodel) like so:
public class ViewModel: INotifyPropertyChanged
{
private string _type;
public string Type
{
get { return _type; }
set
{
_type = value;
OnPropertyChanged("Type");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Or you extend from DependencyObject and use Dependency Properties, like so:
public class ViewModel: DependencyObject
{
public static readonly DependencyProperty TypeProperty = DependencyProperty.Register(
"Type", typeof (string), typeof (ViewModel), new PropertyMetadata(default(string)));
public int Type
{
get { return (int) GetValue(TypeProperty ); }
set { SetValue(TypeProperty , value); }
}
}
Hope it helps ;)

DataGrid Multiplicating Two Columns

I'm in need of the community's help. I am trying to multiply the values of two columns in a DataGrid (WPF and C#), the first column get its data from a MySql database and the second column is an input value where a user will type a number which should be multiplied with the first column and the result should be displayed in a third column called "Total". I have searched all over and tried different approaches from other people who tried the nearly same thing but I just can't get the value to multiply and the result to appear in the third column. Here's the last bit of code I've tried, I have to mention that I am still very new to C# and WPF with not so much experience:
<DataGrid AutoGenerateColumns="False" x:Name="tblData" Margin="30,197,7,0" Grid.Row="1" VerticalAlignment="Top" Height="510" Grid.ColumnSpan="4"
BorderThickness="2" BorderBrush="#FF445BBF" ItemsSource="{Binding Path=LoadDataBinding}" CanUserResizeRows="False" ClipToBounds="True"
CanUserSortColumns="False" HorizontalGridLinesBrush="#FFC7C7C7" VerticalGridLinesBrush="#FFC7C7C7" IsManipulationEnabled="True" EnableRowVirtualization="False"
IsTextSearchEnabled="True" xmlns:local="clr-namespace:PoS_Pimentel">
<DataGrid.Resources>
<local:AmmountConverter x:Key="AmmountConverter" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=nomprod}" Header="Producto" Width="500" IsReadOnly="True">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="14" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Path=preciogram, Mode=TwoWay}" Header="Precio por Gramo" Width="190" IsReadOnly="True">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="14" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Path=gramos, Mode=TwoWay}" Header="Gramos" Width="190" IsReadOnly="False">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="14" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Path=total, Mode=TwoWay}" Header="Total" Width="*" IsReadOnly="True">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="14" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
and on the C# end I created two separate .cs file for an EntitiyClass and an AmmountConverter class:
EntityClass code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows.Forms;
namespace PoS
{
#region
public class Entity_Class : INotifyPropertyChanged
{
private int _preciogram;
public int PrecioGram
{
get { return _preciogram; }
set { _preciogram = value; NotifyPropertyChanged("gramos"); }
}
private int _gramos;
public int Gramos
{
get { return _gramos; }
set { _gramos = value; NotifyPropertyChanged("gramos"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
And the AmmountConverter class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace PoS_Pimentel
{
public class AmmountConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double prcgrms = values[1] == null ? 0 : System.Convert.ToDouble(values[1]);
double grms = values[2] == null ? 0 : System.Convert.ToDouble(values[2]);
return prcgrms * grms;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I'm not very good at this but I am trying and any pointers will be greatly appreciated. Thank you all.
You dont need to use a converter for doing this calculation. Hope you can handle it in Model Class. Also You need to bind to property. DataGrid binding property Names are not correct c# is case sensitive. Refer my below code.
<DataGrid x:Name="dgr" AutoGenerateColumns="False" ItemsSource="{Binding LoadDataBinding}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=PrecioGram, Mode=TwoWay}" Header="Precio por Gramo" Width="190" IsReadOnly="True">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="14" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Path=Gramos, Mode=TwoWay}" Header="Gramos" Width="190" IsReadOnly="False">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="14" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Total" Width="100" Binding="{Binding Total}">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="14" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
public class MainViewModel
{
private ObservableCollection<Entity_Class> myVar = new ObservableCollection<Entity_Class>();
public ObservableCollection<Entity_Class> LoadDataBinding
{
get { return myVar; }
set { myVar = value; }
}
public MainViewModel()
{
for (int i = 1; i < 10; i++)
{
LoadDataBinding.Add(new Entity_Class() { PrecioGram=i});
}
}
}
public class Entity_Class : INotifyPropertyChanged
{
private int _preciogram;
public int PrecioGram
{
get { return _preciogram; }
set { _preciogram = value; NotifyPropertyChanged("PrecioGram"); }
}
private int _gramos;
public int Gramos
{
get { return _gramos; }
set
{
_gramos = value; NotifyPropertyChanged("gramos");
Total = _preciogram * _gramos;
}
}
private int _total;
public int Total
{
get { return _total; }
set { _total = value; NotifyPropertyChanged("Total"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
There are two ways to accomplish this, and it looks like you're trying to use a mix of both.
One method is to bind all 3 columns to properties on your data object, and use a PropertyChange event handler on the user-entered property that re-calculates the total column. This is usually my preference.
<DataGridTextColumn Binding="{Binding Value1}" />
<DataGridTextColumn Binding="{Binding Value2}" />
<DataGridTextColumn Binding="{Binding Total}" />
private void MyDataItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value2")
Total = Value1 * Value2;
}
With this approach, no converter is needed.
The other approach is to not bind the 3rd column, and to use a converter to display it
<!-- Note: syntax may be incorrect here -->
<DataGridTextColumn Binding="{Binding Value1}" />
<DataGridTextColumn Binding="{Binding Value2}" />
<DataGridTextColumn>
<DataGridTextColumn.Binding>
<Multibinding Converter="{StaticResource MyMultiplicationConverter}">
<Binding Path="Value1" />
<Binding Path="Value2" />
</Multibinding>
<DataGridTextColumn.Binding>
</DataGridTextColumn>
In this case, we are binding two columns to values on the data model, and the 3rd column is using a converter to convert the two values to a different 3rd value.
It should be noted that regardless of which approach you use, the default binding mode for a TextBox is to only update the source on LostFocus, so we don't raise excessive change notifications. If you want it to update in real-time as the user enters data, you should change the binding mode to be PropertyChanged, or write your own binding update behavior to update the bound source after a short delay.
Also as a side note, you're raising the PropertyChange notification for the wrong property in your PrecioGram property :
public int PrecioGram
{
get { return _preciogram; }
set
{
_preciogram = value;
NotifyPropertyChanged("gramos"); // Incorrect property here
}
}

Categories

Resources