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.
Related
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.
Hy, I am new in WPF programming and I have a little problem that I can't solve it by my self.
I have created a list of object:
List<TestInfo> Info = new List<TestInfo>();
where TestInfo:
public class TestInfo
{
public string Serial { get; set; }
public string Test { get; set; }
public string Result { get; set; }
}
After that I have grouped it:
ListCollectionView groupedInfo = new ListCollectionView(Info);
groupedInfo.GroupDescriptions.Add(new PropertyGroupDescription("Serial"));
Then binded it to datagrid:
dataGrid.ItemsSource = groupedInfo;
Now, my question is, in this case, how can i change datagrid's row forecolor based on a condition? Something like: if groupedInfo.Result == F change row forecolor.
I can't manage to do it myself. Please help!
Here is an example of how to do it based on a condition:
<DataGrid>
<DataGrid.CellStyle>
<Style x:Key="DGCell" TargetType="{x:Type DGCell}" >
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Gray"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
</DataGrid>
And to use it on a column you do:
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DGCell">
Thanks all for your help. I have finally solved it and learned what triggers are [:D]. The solution for me was using a DataTrigger. The code is below:
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Result}" Value="P">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
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
}
}
How could I bind a brush from my ViewModel to a setter value?
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{Binding SelectionBrush, Mode=OneWay}" />
<Setter Property="BorderBrush" Value="{Binding SelectionBrush, Mode=OneWay}" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
class Leerformular
{
public Leerformular()
{
SelectionBrush = (SolidColorBrush)(new BrushConverter()).ConvertFromString(settings.SelectionBrush);
}
public SolidColorBrush SelectionBrush
{
get { return selectionBrush; }
set
{
if (selectionBrush != value && value != null)
{
selectionBrush = value;
OnPropertyChanged("SelectionBrush");
}
}
}
}
When I execute the program nothing is happening when I select the cells.
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);
}
}