.NET MAUI: Scrollable list of log data - c#

I'm making an application for gathering serial communication data and presenting it in a log window. This is my first .NET MAUI application, and I'm having problems with finding a way that performs well.
Preferably I would like to have columns with timestamp, raw hex bytes and ascii strings. But using a ListView with a Grid for each line with perhaps a couple hundred lines is not performing very well even on my Macbook M1 Max. It's really sluggish and completely unusable.
<ListView
ItemsSource="{Binding ReceivedDataBuffer}"
ios:ListView.RowAnimationsEnabled="False"
HasUnevenRows="False"
CachingStrategy="RetainElement">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:ReceivedData">
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Text="{Binding Timestamp}"
FontAttributes="Bold" />
<Label Grid.Column="1"
Text="{Binding HexString}"
FontAttributes="Italic"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Is there a way to get the ListView to perform better? Or is there a control that's better suited to show a lot of logged data (approx. 10,000 lines) with multiple properties?

You can use the DataGrid instead of ListView if you want to perform well. As the DataGrid can show different data types in different types of columns and it will only load what you scroll to.
I use the code below to show 10,000 lines of pseudo data in DataGrid and it performs well.
for(int i = 0; i<10000; i++)
{
orderInfo.Add(new OrderInfo("testdata column"+i, "testdata column" + i, "testdata column" + i, "testdata column" + i, "testdata column" + i));
}
And you can refer to DataGrid in .NET MAUI on how to use it.

Related

Binding to DataGridTemplateColumn in WPF DataGrid

I am customizing WPF DataGrid in a generic way. consider that I have defined an enum of Creatures like this:
public enum CreatureType
{
Human,
Animal,
Insect,
Virus
}
then I have an attached property of the above type so it can be attached to any control I want, that called CreatureTypeProeprty. then I want to attach this property to DataGridTemplateColumn and I want to access this inside the Template. for example consider the following reproducible code of my problem:
<DataGridTemplateColumn local:CreatureTypeProeprty.Value = "Human" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=(local:CreatureTypeProeprty.Value), RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGridTemplateColumn }}"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
This doesn't work and I know why because DataGrid column is not part of the visual tree. but I can't find any way to solve this issue. I also tried This but this solution can not be used in a generic way and ends up in a very dirty solution! I'm looking for a more sophisticated and clean way. I also tried using the name for the column and specifying ElementName it works but also this can not be used when there is multiple columns and I have to repeat codes every time.
The datagrid column is more a conceptual thing. It's not so much not in the visual tree as not there at all.
There is no spoon.
One way to do this sort of thing would be to use a list and index it. In your window viewmode
public list<CreatureType> Creatures {get;set;}
Obviously, load that up with CreatureTypes in the same order as your columns.
You can then reference using something like
Text="{Binding DataContext.Creatures[4]}
, RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Datagrid}}
Unfortunately, you need a fixed number for indexing.
You could perhaps dynamically build your columns and add them to the datagrid.
I would do that by defining a string template and substituting values, then using xamlreader.parse() to build a datagrid column which you add to your datagrid.
In my sample I have a col.txt file. This is a sort of template for my datagrid columns.
It is pretty much a piece of datagrid column markup.
<?xml version="1.0" encoding="utf-8" ?>
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="xxMMMxx" TextAlignment="Center" Grid.ColumnSpan="2"/>
<TextBlock Text="Units" Margin="2,0,2,0" Grid.Row="1"/>
<TextBlock Text="Value" Margin="2,0,2,0" Grid.Row="1" Grid.Column="2" />
</Grid>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding MonthTotals[xxNumxx].Products}" Margin="2,0,2,0" TextAlignment="Right"/>
<TextBlock Text="{Binding MonthTotals[xxNumxx].Total}" Margin="2,0,2,0" TextAlignment="Right" Grid.Column="1"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
It's showing totals for months so obviously 12 months and my array here is monthtotals[12].
Obviously, this is a different thing and illustrating the approach.
I also have a dg.txt file which is of course something rather similar for my datagrid. But that'd be repetitive.
And I have some code substitutes strings, parses the result and appends a built column:
private void Button_Click(object sender, RoutedEventArgs e)
{
// Get the datagrid shell
XElement xdg = GetXElement(#"pack://application:,,,/dg.txt");
XElement cols = xdg.Descendants().First(); // Column list
// Get the column template
XElement col = GetXElement(#"pack://application:,,,/col.txt");
DateTime mnth = DateTime.Now.AddMonths(-6);
for (int i = 0; i < 6; i++)
{
DateTime dat = mnth.AddMonths(i);
XElement el = new XElement(col);
// Month in mmm format in header
var mnthEl = el.Descendants("TextBlock")
.Single(x => x.Attribute("Text").Value.ToString() == "xxMMMxx");
mnthEl.SetAttributeValue("Text", dat.ToString("MMM"));
string monthNo = dat.AddMonths(-1).Month.ToString();
// Month as index for the product
var prodEl = el.Descendants("TextBlock")
.Single(x => x.Attribute("Text").Value == "{Binding MonthTotals[xxNumxx].Products}");
prodEl.SetAttributeValue("Text",
"{Binding MonthTotals[" + monthNo + "].Products}");
// Month as index for the total
var prodTot = el.Descendants("TextBlock")
.Single(x => x.Attribute("Text").Value == "{Binding MonthTotals[xxNumxx].Total}");
prodTot.SetAttributeValue("Text",
"{Binding MonthTotals[" + monthNo + "].Total}");
cols.Add(el);
}
string dgString = xdg.ToString();
ParserContext context = new ParserContext();
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
DataGrid dg = (DataGrid)XamlReader.Parse(dgString, context);
Root.Children.Add(dg);
}
private XElement GetXElement(string uri)
{
XDocument xmlDoc = new XDocument();
var xmltxt = Application.GetContentStream(new Uri(uri));
string elfull = new StreamReader(xmltxt.Stream).ReadToEnd();
xmlDoc = XDocument.Parse(elfull);
return xmlDoc.Root;
}
Might be more complicated than you need but maybe it'll help someone else down the line.
BTW
I suggest an "unknown" or "none" as the first in any enum. This is standard practice in my experience so you can differentiate between a set and unset value.
Q1 How common is this?
Not very. It solves problems I would describe as "awkward" bindings and can simplify UI logic in templating.
Or a sort of super auto generator for columns.
A similar approach can be very useful for user configurable UI or dynamic UI.
Imagine a dynamic UI where the user is working with xml and xml defines the UI. Variable stats or variable points calculation for a tabletop wargaming points calculator.
Or where the user picks the columns they want and how to display them. That can be built as strings and saved to their roaming profile. Loaded from there using xamlreader.load which gives you your entire datagrid definition.
q2
That's what the sample does.
dg.txt looks like
<?xml version="1.0" encoding="utf-8" ?>
<DataGrid
ItemsSource="{Binding SalesMen}"
AutoGenerateColumns="False"
Grid.Column="1"
CanUserAddRows="False"
IsReadOnly="False"
Name="dg">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding FullName}" Header="Name"/>
</DataGrid.Columns>
</DataGrid>
This line gets Columns
XElement cols = xdg.Descendants().First(); // Column list
And this line append each column
cols.Add(el);
It is so there are Xelement nodes you can find that the file is manipulated as xml rather than just as a string.

How To Add Data Points Fast To A WPF Chart

Hello I have a big database from where I take like 1500 values in order to show in a chart.
My issues is that it takes a long time to display all the points, I think is because of animation settings.
How can I change the speed of the animation or is there a way to display the points faster?
<UserControl x:Class="Ipte.UI.Pages.StatisticsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:gcl="clr-namespace:GuiControlLibrary;assembly=GuiControlLibrary"
xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:time="clr-namespace:Ipte.UI"
xmlns:chartToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
mc:Ignorable="d"
Height="800" Width="1200">
<UserControl.Resources>
<Style x:Key="Scater" TargetType="chartToolkit:ScatterDataPoint">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="chartToolkit:ScatterDataPoint">
<Viewbox x:Name="viewbox">
<!--<Ellipse Width="1" Height="1" Fill="Black"/>-->
</Viewbox>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Width" Value="4"/>
<Setter Property="Height" Value="4"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid x:Name="filterGrid" Grid.Column="0" Margin="4">
<StackPanel>
<TextBlock Text="" Margin="2"/>
<toolkit:DatePicker x:Name="dpStartDate" Margin="2" />
<time:TimePicker x:Name="tpStartTime" Margin="2"/>
<TextBlock Text="End date & time:" Margin="2"/>
<toolkit:DatePicker x:Name="dpEndDate" Margin="2"/>
<time:TimePicker x:Name="tpEndTime" Margin="2"/>
<gcl:GuiGroupBox Header="Select router" BorderBrush="LightGray">
<UniformGrid Rows="2" Columns="2">
<CheckBox x:Name="cbEr11" Content="ER 1.1" Margin="2"/>
<CheckBox x:Name="cbEr12" Content="ER 1.2" Margin="2"/>
<CheckBox x:Name="cbEr21" Content="ER 2.1" Margin="2"/>
<CheckBox x:Name="cbEr22" Content="ER 2.1" Margin="2"/>
</UniformGrid>
</gcl:GuiGroupBox>
<TextBlock Text="" Margin="2"/>
<ComboBox x:Name="cmbGoodBad" Margin="2"/>
<TextBlock Text="" Margin="2"/>
<TextBox x:Name="" Margin="2"/>
<TextBlock Text="" Margin="2"/>
<TextBox x:Name="" Margin="2"/>
<gcl:GuiGroupBox Header="Select value" BorderBrush="LightGray">
<StackPanel>
<RadioButton x:Name="combValueA" Content="Value A" Margin="2"/>
<RadioButton x:Name="combValueB" Content="Value B" Margin="2"/>
<RadioButton x:Name="combValueC" Content="Value C" Margin="2"/>
</StackPanel>
</gcl:GuiGroupBox>
<Button x:Name="btnResetFilters" Content="Reset filters" Margin="2 10 2 2" Click="ResetFilters_Click"/>
<Button x:Name="btnUpdateChart" Content="Update Chart" Margin="2 2 2 2" Click="UpdateChartAndFilters_Click"/>
<Button x:Name="btnLoadFile" Content="Load file..." Grid.Column="0" VerticalAlignment="Top" Margin="2" Visibility="Visible" Click="OpenFile_Click"/>
</StackPanel>
<Button x:Name="deleteDatabase" Content="Delete database" Grid.Column="0" VerticalAlignment="Bottom" Margin="2" Click="deleteDatabase_Click"/>
</Grid>
<chartToolkit:Chart Grid.Column="1" x:Name="dataChart">
<chartToolkit:Chart.Series>
<chartToolkit:ScatterSeries x:Name="scatterSeries"
ItemsSource="{Binding}"
DependentValueBinding="{Binding Path=Value}"
IndependentValueBinding="{Binding Path=Key}"
IsSelectionEnabled="False"
AnimationSequence="Simultaneous">
<chartToolkit:ScatterSeries.IndependentAxis>
<chartToolkit:DateTimeAxis Orientation="X" Title="Time"/>
</chartToolkit:ScatterSeries.IndependentAxis>
<chartToolkit:ScatterSeries.DependentRangeAxis>
<chartToolkit:LinearAxis Orientation="Y" Title="Points" x:Name="yAxis"/>
</chartToolkit:ScatterSeries.DependentRangeAxis>
</chartToolkit:ScatterSeries>
<chartToolkit:LineSeries x:Name="lineSeriesMax"
Title="Maximum"
ItemsSource="{Binding}"
DependentValueBinding="{Binding Path=Value}"
IndependentValueBinding="{Binding Path=Key}">
</chartToolkit:LineSeries>
<chartToolkit:LineSeries x:Name="lineSeriesMin"
Title="Minimum"
ItemsSource="{Binding}"
DependentValueBinding="{Binding Path=Value}"
IndependentValueBinding="{Binding Path=Key}">
</chartToolkit:LineSeries>
<chartToolkit:LineSeries x:Name="lineSeriesAvg"
Title="Average"
ItemsSource="{Binding}"
DependentValueBinding="{Binding Path=Value}"
IndependentValueBinding="{Binding Path=Key}">
</chartToolkit:LineSeries>
</chartToolkit:Chart.Series>
</chartToolkit:Chart>
</Grid>
</UserControl>
This is the way my points are displayed:
I know this is an older question but I wanted to share my thoughts on charting in WPF that goes beyond just plotting a few bars or scatter points or lines.
Probably everyone agrees that the stock WPF library was not built and intended to handle many thousands or even millions of datapoints when it comes to charting. Whatever workarounds I tried such as data sampling, I was never really satisfied with stock WPF charting capabilities. If you are working to chart data other than just as a one-time prototyping pursuit I highly recommend you to look at a professional WPF charting library.
I have in particular used Scichart, a third party vendor WPF library, for many years and cannot speak highly enough of its capabilities. I often chart hundreds of thousands, sometimes millions of scatter data points and most recently also rendered 3D charts with tons of data and find Scichart highly performant. It costs more than "free" but I find the investment more than worth it because (and I try to incorporate your questions here):
The 2D and 3D libraries are rock solid, what I mean with that is that rendering performance is stellar, bindings just work, virtually anything can be customized, full MVVM support
The documentation and support forum are probably the best part of what Scichart has to offer. Most questions, even trickier ones are addressed already and if something can't be found then other users or the support team respond in a timely fashion.
In particular to your question, with Scichart you simply add the dataset all at once as array via binding or directly and it renders within milliseconds. Its real-time capabilities are also amazing if you need to add data points one by one.
I strongly suggest you give them a try, they offer a trial license. I tried charting with DevExpress, of which I also own a license, and their charting capabilities top out beyond a few datapoints and their charts are imho better suited for dashboards with few data points. I also tried and used Arction's
lightningchart library for a while and while raw performance was on par with Scichart, their styling is absolutely horrific, their MVVM capabilities are all but non existent, in fact when you take a look at their documentation you see that a lot of solution suggestions to problems are presented in code behind. Their support forum and Q&A repository is also very sparsely populated, something that usually really turns me off because at some point every developer will encounter issues he/she needs to look up for a solution.
To be honest, if you only look to chart 1500 data points and have a DevExpress license then by all means, use them, because I think they can still handle 1500 points, though more might become tricky. But if you ever see the need for charting capabilities of larger data sets then I can't speak of Scichart highly enough. Why? Because I worked with that library since early version 1.3.x.xxxx. I truly believe they are the best charting library out there in WPF space.
What contributes the most to slow down your chart is all the events generated to draw your series point-by-point as they are added to the view model collection. Adding them all at once solves that problem:
Extend ObservableCollection to support AddRange, as shown HERE:
public class ObservableCollectionRange<T> : ObservableCollection<T>
{
public void AddRange(IEnumerable<T> items)
{
this.CheckReentrancy();
foreach (var item in items)
this.Items.Add(item);
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
And then use it in your view model:
public class MyViewModel
{
public ObservableCollectionRange<KeyValuePair<double, double>> Power { get; set; }
public ObservableCollectionRange<KeyValuePair<double, double>> PowerAvg { get; set; }
public MyViewModel()
{
Power = new ObservableCollectionRange<KeyValuePair<double, double>>();
PowerAvg = new ObservableCollectionRange<KeyValuePair<double, double>>();
}
public void Add(double x, double y)
{
Power.Add(new KeyValuePair<double, double>(x, y));
double xmin = Power.Min(kvp => kvp.Key);
double xmax = Power.Max(kvp => kvp.Key);
double ymin = Power.Min(kvp => kvp.Value);
double ymax = Power.Max(kvp => kvp.Value);
double yavg = Power.Average(kvp => kvp.Value);
PowerAvg.Clear();
PowerAvg.Add(new KeyValuePair<double, double>(xmin, yavg));
PowerAvg.Add(new KeyValuePair<double, double>(xmax, yavg));
}
public void AddRange(IEnumerable<KeyValuePair<double, double>> items)
{
Power.AddRange(items);
double xmin = Power.Min(kvp => kvp.Key);
double xmax = Power.Max(kvp => kvp.Key);
double ymin = Power.Min(kvp => kvp.Value);
double ymax = Power.Max(kvp => kvp.Value);
double yavg = Power.Average(kvp => kvp.Value);
PowerAvg.Clear();
PowerAvg.Add(new KeyValuePair<double, double>(xmin, yavg));
PowerAvg.Add(new KeyValuePair<double, double>(xmax, yavg));
}
}
And at button click event:
private void Button_Click(object sender, RoutedEventArgs e)
{
ShowPoints();
}
private void ShowPoints()
{
Random random = new Random();
ObservableCollection<KeyValuePair<double, double>> oc = new ObservableCollection<KeyValuePair<double, double>>();
for (int i = 1; i <= 1500; i++)
oc.Add(new KeyValuePair<double, double>(i, random.NextDouble()));
vm.AddRange(oc);
}
If your main priority is Line charts and you consider switching to a third-party component vendor, you may consider DevExpress ChartControl as a suitable option.
For instance, the latest version of DevExpress components ships with the "Large Datasource" demo module, where up to 500K points can be shown without major performance degradation (this includes interactive operations such as scrolling and zooming).

GridView out of memory with large number of ObservableCollection

I'm developing a Universal Win App and the issue is present on both platforms. I've a ObservableCollection with products, object properties are bound from xaml DataTemplate and ObservableCollection is set as ItemsSource to GridView.
private ObservableCollection<Product> productList = new ObservableCollection<Product>();
Bound products have images too.
<Image
CacheMode="BitmapCache"
Source="{Binding ImageUrl}"
Stretch="Uniform" />
GridView
<ScrollViewer
x:Name="ProductList_GridView_ParentScrollViewer"
VerticalScrollBarVisibility="Hidden"
Grid.Row="1">
<StackPanel
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<Viewbox
AllowDrop="False"
ScrollViewer.HorizontalScrollMode="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollMode="Disabled"
Stretch="UniformToFill">
<GridView
x:Name="ProductList_GridView"
Margin="15,9.5,15,0" />
</Viewbox>
</StackPanel>
</ScrollViewer>
Also I've a kind of pagination setting which make request each time scrollable height is equal to vertical offset. And I'm adding new items to ObservableCollection by doing
foreach (var item in requestResult.products)
{
productList.Add(item);
}
this seems to work fine until i reach certain number of items. After that the app just crashes. My app's memory consumption reaches as high as 800Mb's before crashing on 2GB ram device. On 512Mb ram it crashes a lot earlier.
How can I fix the issue? Do I need to change the way I did this pagination. Or can it be fixed with small changes. I guess the problem is with Image but not sure. Isn't CacheMode="BitmapCache" supposed to fix the memory problem?
Your GridView virtualization is broken because you are wrapping it in a ScrollViewer and all items are rendered at once (and using much more memory).
If you only leave the following XAML you will have no more memory problems:
<GridView
x:Name="ProductList_GridView"
Margin="15,9.5,15,0" />

C1 DataGrid Not Showing Bound Data

I'm looking for basic ideas on what might be happening. I have bound data to a DataGrid that works fine on my local. Sometimes though, on the dev test server, the data does not display in the grids I made. Others work fine. On some machines these grids work fine. There are all of the rows that belong and the data is there underneath for use in everything, but it just isn't visible. As in the grid bound to 20 objects will have 20 completely blank rows that I can click on and use the SelectedItem property to get values from.
I've tried clearing out the cache many times. Resetting the ItemsSource property to null then the underlying data, and calling Refresh() on the grid. So, any ideas on why data that is bound might create a row but not have anything visible.
Below is an example of one of the grids. This grid has the first column set in the constructor and isn't bound. The top is on the server and the bottom is from my local host.
Below is the XAML code for the grid in the picture. The object bound to is a list of class called AccountTypeGridClass. All properties are public strings with both gets and sets implemented as well as a property changed event being thrown. Though that isn't used.
<af:GroupHeader x:Name="AccountTypeHeader" Header="Account Type" Grid.Row="0" Margin="5" >
<dataGrid:DataGridBase HorizontalAlignment="Left" x:Name="AccountTypeGrid" GridLinesVisibility="None"
RowBackground="White"
HorizontalScrollBarVisibility="Hidden" HeadersVisibility="Column"
CanUserAddRows="False" CanUserResizeRows="False"
SelectionMode="SingleRow" >
<c1:C1DataGrid.Columns>
<c1:DataGridTemplateColumn Header="Type" Name="TypeColumn" Width="100">
<c1:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock VerticalAlignment="Center" Margin="3, 0, 0, 0" Text="Account Type" af:AutoToolTip.Active="True" />
</DataTemplate>
</c1:DataGridTemplateColumn.CellTemplate>
</c1:DataGridTemplateColumn>
<c1:DataGridTemplateColumn Header="Code" Name="AccountTypeCodeColumn" Width="100">
<c1:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock VerticalAlignment="Center" Margin="3, 0, 0, 0" Text="{Binding AccountTypeCode}" af:AutoToolTip.Active="True" />
</DataTemplate>
</c1:DataGridTemplateColumn.CellTemplate>
</c1:DataGridTemplateColumn>
<c1:DataGridTemplateColumn Header="Code Description" Name="AccountTypeCodeDescriptionColumn" Width="200">
<c1:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock VerticalAlignment="Center" Margin="3, 0, 0, 0" Text="{Binding AccountTypeCodeDescription}" af:AutoToolTip.Active="True" />
</DataTemplate>
</c1:DataGridTemplateColumn.CellTemplate>
</c1:DataGridTemplateColumn>
</c1:C1DataGrid.Columns>
</dataGrid:DataGridBase>
</af:GroupHeader>
Edit: I will update an answer once I can figure it out but the comment by curiousBoy put me on the right track. It seems to be the object the grid is bound to. Another way to describe the original problem is that it is like the columns in the grid were bound to the wrong column name on some of the machines. I've found a way that works that is very similar so I am trying to figure out what the difference actually is that caused this issue.

WPF ListBoxItem(s) losing programatically set styles after scroll

So, I have a ListBox which is bound to a list of business objects, using a DataTemplate:
<DataTemplate x:Key="msgListTemplate">
<Grid Height="17">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding MaxWidth}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Foreground="Silver" Text="{Binding SequenceNo}" />
<TextBlock Grid.Column="1" Text="{Binding MessageName}" />
</Grid>
</DataTemplate>
<ListBox Name="msgList"
Grid.Column="0"
ItemTemplate="{StaticResource msgListTemplate}"
SelectionChanged="msgList_SelectionChanged"
VirtualizingStackPanel.IsVirtualizing="True"
ScrollViewer.HorizontalScrollBarVisibility="Hidden">
</ListBox>
Sometime after binding, I want to colour certain items in the list to distinguish them from the others. I do this on a background thread:
if(someCondition)
{
msgList.Dispatcher.BeginInvoke(new Fader(FadeListItem), DispatcherPriority.Render, request);
}
delegate void Fader(GMIRequest request);
void FadeListItem(GMIRequest request)
{
ListBoxItem item =
msgList.ItemContainerGenerator.ContainerFromItem(request) as ListBoxItem;
if(item!=null)
item.Foreground = new SolidColorBrush(Colors.Silver);
}
This all works fine, and some list items are greyed out as expected. However, if I scroll such that the greyed items are no longer shown, then scroll back again to where they were, they are no longer silver, and have returned to the default black foreground.
Any idea why this is, or how to fix it? Is it because I have set IsVirtualizing to true? The listbox typically contains many items (20,000 is not uncommon).
Is it because I have set IsVirtualizing to true? The listbox typically contains many items (20,000 is not uncommon).
You nailed it - the item you set the foreground color on is getting trashed once the user scrolls away.
While you've got the right general idea, the way you're going about this is a very un-WPFy way to do this - one better way to do this is to have a bool DP in your business object class (or have the BO implement INotifyPropertyChanged), then bind the bool to the Foreground color via a custom IValueConverter that returns (isTrue ? whiteBrush : greyBrush).
Since you may not want to / may not be able to modify your business object to support INotifyPropChanged, this is the reason for the M-V-VM pattern - create a class that wraps the object that is a DependencyObject and exposes just the properties you're interested in displaying.

Categories

Resources