DataGrid Multiplicating Two Columns - c#

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
}
}

Related

Debugging a Major Data Binding Memory Leak in C#

I have the following data bindings set up which cause a huge memory leak:
<DataGrid x:Name="dataGridReflections" Width="auto" MaxWidth="1570" HorizontalContentAlignment="Center" AutoGenerateColumns="False" SelectionMode="Single" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" Margin="0,0,240,0">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Foreground" Value="{Binding ColourSet}"/>
<Setter Property="FontSize" Value="{Binding FontSize}"/>
<Setter Property="FontWeight" Value="{Binding FontWeight}"/>
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGrid.RowStyle>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="16"/>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Bearing (PLL)" Width="*" MaxWidth="221" Binding="{Binding BearingPLL, Mode=OneWay}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Bearing (°)" Width="*" MaxWidth="221" Binding="{Binding BearingDegrees, Mode=OneWay}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Subtended (PLL)" Width="*" MaxWidth="221" Binding="{Binding SubtendPLL, Mode=OneWay}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Subtended (°)" Width="*" MaxWidth="221" Binding="{Binding SubtendDegrees}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Range" Width="*" MaxWidth="221" Binding="{Binding Range, Mode=OneWay}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Intensity" Width="*" MaxWidth="221" Binding="{Binding Intensity, Mode=OneWay}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Time" Width="*" MaxWidth="221" Binding="{Binding Time, Mode=OneWay}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
Which is linked in the code behind as such:
private void UpdateUserInterface()
{
if (ReflectionsReported != null)
ReflectionsReported.Clear();
ReflectionList reflectionDatas;
for (ReflectionCount = 0; ReflectionCount < SmallestArray(); ReflectionCount++)
{
reflectionDatas = new(ReflectionCount);
ReflectionsReported.Add(reflectionDatas);
}
labelMarkers.Content = Reflections.Revolution[(int)Reflections.Data.Marker][0];
labelReflections.Content = dataGridReflections.Items.Count;
}
And the ReflectionsList is defined as:
public ReflectionList(int i) : base()
{
ColourSet = ReflectionsUC.normal;
FontWeight = FontWeights.Normal;
ReflectionData reflr = new()
{
BearingPLL = Reflections.Revolution[(int)Reflections.Data.Bearing][i],
BearingDegrees = Math.Round((decimal)Convert.ToInt32(Reflections.Revolution[(int)Reflections.Data.Bearing][i]) / 65536 * 360, 3),
SubtendPLL = Reflections.Revolution[(int)Reflections.Data.Subtend][i],
SubtendDegrees = Math.Round((decimal)Convert.ToInt32(Reflections.Revolution[(int)Reflections.Data.Subtend][i]) / 65536 * 360, 3),
Intensity = Reflections.Revolution[(int)Reflections.Data.Intensity][i],
Range = Reflections.Revolution[(int)Reflections.Data.Range][i],
Time = DateTime.Parse(Reflections.Revolution[(int)Reflections.Data.Time][0]).ToString("HH:mm:ss")
};
With each value defined as:
public class ReflectionData : INotifyPropertyChanged
{
private string _bearingPLL;
private decimal _bearingDeg;
private string _subtendPLL;
private decimal _subtendDeg;
private string _intensity;
private string _range;
private string _time;
public string BearingPLL { get { return _bearingPLL; } set { if (_bearingPLL != value) { _bearingPLL = value; OnPropertyChanged(); } } }
public decimal BearingDegrees { get { return _bearingDeg; } set { if (_bearingDeg != value) { _bearingDeg = value; OnPropertyChanged(); } } }
public string SubtendPLL { get { return _subtendPLL; } set { if (_subtendPLL != value) { _subtendPLL = value; OnPropertyChanged(); } } }
public decimal SubtendDegrees { get { return _subtendDeg; } set { if (_subtendDeg != value) { _subtendDeg = value; OnPropertyChanged(); } } }
public string Intensity { get { return _intensity; } set { if (_intensity != value) { _intensity = value; OnPropertyChanged(); } } }
public string Range { get { return _range; } set { if (_range != value) { _range = value; OnPropertyChanged(); } } }
public string Time { get { return _time; } set { if (_time != value) { _time = value; OnPropertyChanged(); } } }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
The update user interface method gets triggered every time the Reflections.Revolution gets filled with new data. If I remove all user interface elements and just have the data coming in there is no memory leak, but as soon as I allow the update user interface method to be ran I get up to 12 gigabytes over an hour!
Is there a better / easier way for me to do this with minimal processing / memory usage, understanding I do have a lot of data here which is updating once every 0.125 seconds.
Stupid question, but not a stupid idea: have you ran the code with a memory profiler so you can see what is causing the leak? Which classes are staying around when they should be gone?
Leaks occur when the garbage collector cannot clean up an object because it is still being referenced. Without a memory profiler I would look at any code which creates new objects or attaches event handlers as those will be the ones which are most likely to get stuck in memory. You have a class that implements the INotifyPropertyChanged interface, it is possible that the event handler is not being removed when the binding is.
This would be a lot easier to diagnose if you had a working sample, of course this is not always easy to do, so you can do some tricks if you don't have access to a memory profiler such as implementing IDisposable or Finalizers to see if your classes are ever being disposed of. You can put a timer in your classes that prints their hash or construction time to the console every minute that they exist. You can also force a garbage collection to be done using GC.Collect()
https://learn.microsoft.com/en-us/dotnet/api/system.gc.collect?view=net-5.0
My advice is to download a memory profiler and use a trial version. JetBrains has one with a 5 day full feature trial.

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.

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 with Empty DataTable to allow user input C# WPF

I have an application that has multiple tabs each containing a Datagrid that contains data from my database. I have a save button that writes all the datagrids into a .csv file. I want to create an additional tab that contains an empty datagrid so the user can type information so it can be output the same way.
Is there a way to bind a datagrid to an empty data table? Or is there a better solution that will allow the user to dynamically enter variable amounts of information (somtimes one row, somtimes 10)
C#
DataTable dt_Call_Drivers = new DataTable();
Call_Drivers_DataGrid.ItemsSource = dt_Call_Drivers.DefaultView;
XAML
<DataGrid x:Name="Call_Drivers_DataGrid" ItemsSource="{Binding}" GridLinesVisibility="Horizontal" CanUserAddRows="true" AutoGenerateColumns="False" Margin="0,0,0,0">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}" BasedOn="{StaticResource MetroDataGridColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Case" Width ="90" Binding="{Binding Case}">
<DataGridTextColumn.ElementStyle>
<Style>
<Setter Property="TextBlock.TextAlignment" Value="Center" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
Just bind datagrid to ObservableCollection, define your type. And Set CanUserAddRows = true. That's all.
<DataGrid CanUserAddRows="True" AutoGenerateColumns="False" ItemsSource="{Binding SimpleCollection}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding A}"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding B}"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding C}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
public class SimpleClass
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
private ObservableCollection<SimpleClass> _simpleCollection;
public ObservableCollection<SimpleClass> SimpleCollection
{
get { return _simpleCollection ?? (_simpleCollection = new ObservableCollection<SimpleClass>()); }
set { _simpleCollection = value; }
}

Styling DataGrid rows WPF exception

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);
}
}

Categories

Resources