Avalonia library DataGrid not showing rows

4 weeks ago 16
ARTICLE AD BOX

I'm trying to create an Avalonia control to be used in a library. The control is meant to be directly bindable to a DataTable and display the contents in a grid with TextBoxes above each column to be used as filters for the values in that column. I've managed to make it so that the TextBox filters are displayed and I can see during debugging that the columns get created, as well, but no rows are being displayed and I can't figure out why. I'm not that adept at Avalonia and I've tried everything I can think of.

I'm using the following nuget packages in the library project:

<PackageReference Include="Avalonia" Version="11.3.10" /> <PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.10" />

first try:

using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Layout; using Avalonia.LogicalTree; using System; using System.Collections.Generic; using System.Data; using System.Linq; namespace AvaloniaLibrary; public class FilterableDataTableGrid : ContentControl { DataTable sourceDataTable; DataView filteredDataView; DataGrid dataGrid; StackPanel filterPanel; Dictionary<string, TextBox> columnFilters = new(); public static readonly StyledProperty<DataTable> DataSourceProperty = AvaloniaProperty.Register<FilterableDataTableGrid, DataTable>(nameof(DataSource)); public DataTable DataSource { get => GetValue(DataSourceProperty); set => SetValue(DataSourceProperty, value); } public FilterableDataTableGrid() { DataSourceProperty.Changed.AddClassHandler<FilterableDataTableGrid>(OnDataSourceChanged); } static void OnDataSourceChanged(FilterableDataTableGrid control, AvaloniaPropertyChangedEventArgs e) { if (e.NewValue is DataTable dataTable) { control.LoadDataTable(dataTable); } } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); BuildControl(); } void BuildControl() { var mainPanel = new StackPanel { Orientation = Orientation.Vertical }; filterPanel = new StackPanel { Orientation = Orientation.Horizontal, Height = 30 }; dataGrid = new DataGrid { IsReadOnly = true, CanUserSortColumns = true, CanUserResizeColumns = true, CanUserReorderColumns = true, GridLinesVisibility = DataGridGridLinesVisibility.All, HorizontalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Auto, VerticalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Auto }; mainPanel.Children.Add(filterPanel); mainPanel.Children.Add(dataGrid); Content = mainPanel; if (DataSource != null) { LoadDataTable(DataSource); } } void LoadDataTable(DataTable dataTable) { if (dataGrid == null || filterPanel == null) return; sourceDataTable = dataTable; filteredDataView = dataTable.DefaultView; dataGrid.Columns.Clear(); filterPanel.Children.Clear(); columnFilters.Clear(); foreach (DataColumn column in dataTable.Columns) { CreateColumnFilter(column); CreateDataGridColumn(column); } dataGrid.ItemsSource = filteredDataView; } void CreateColumnFilter(DataColumn column) { var filterBox = new TextBox { Watermark = $"Filter {column.ColumnName}", Width = 150, Margin = new Thickness(2) }; filterBox.TextChanged += (s, e) => ApplyFilters(); columnFilters[column.ColumnName] = filterBox; filterPanel.Children.Add(filterBox); } void CreateDataGridColumn(DataColumn column) { var gridColumn = new DataGridTextColumn { Header = column.ColumnName, Binding = new Binding($"[{column.ColumnName}]"), Width = new DataGridLength(150), CanUserSort = true, CanUserResize = true, CanUserReorder = true }; dataGrid.Columns.Add(gridColumn); } void ApplyFilters() { if (filteredDataView == null) return; var filterExpressions = new List<string>(); foreach (var kvp in columnFilters) { string columnName = kvp.Key; string filterText = kvp.Value.Text; if (!string.IsNullOrWhiteSpace(filterText)) { string escapedFilter = filterText.Replace("'", "''"); filterExpressions.Add($"Convert([{columnName}], 'System.String') LIKE '%{escapedFilter}%'"); } } filteredDataView.RowFilter = filterExpressions.Count > 0 ? string.Join(" AND ", filterExpressions) : string.Empty; } }

then I add an Avalonia MVVM project, reference the AvaloniaLibrary project and do this in the MainWindow.axaml:

<Window xmlns="https://github.com/avaloniaui" xmlns:controls="using:AvaloniaLibrary" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="using:AvaloniaApplication1.ViewModels" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="AvaloniaApplication1.Views.MainWindow" x:DataType="vm:MainWindowViewModel" Icon="/Assets/avalonia-logo.ico" Title="AvaloniaApplication1"> <Design.DataContext> <!-- This only sets the DataContext for the previewer in an IDE, to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) --> <vm:MainWindowViewModel/> </Design.DataContext> <controls:FilterableDataTable DataSource="{Binding Items}"/> </Window>

and this in the MainWindowViewModel.cs:

using CommunityToolkit.Mvvm.ComponentModel; using System.Data; namespace AvaloniaApplication1.ViewModels; public partial class MainWindowViewModel : ObservableObject { [ObservableProperty] DataTable items; public MainWindowViewModel() { DataTable table = new DataTable(); table.Columns.Add("ID", typeof(int)); table.Columns.Add("Name", typeof(string)); table.Columns.Add("Age", typeof(int)); table.Rows.Add(1, "Alice", 30); table.Rows.Add(2, "Bob", 25); table.Rows.Add(3, "Charlie", 35); Items = table; } }

and I get this on run:

enter image description here

What I tried (to no avail):

Change ContentControl to UserControl Add debugging logging of the amount of columns and rows in the DataGrid (logs showed 3 columns and 3 rows were bound) Remove OnApplyTemplate method and move BuildControl(); to constructor Added && control.dataGrid != null to if (e.NewValue is DataTable dataTable) Change filteredDataView = dataTable.DefaultView; to filteredDataView = new DataView(dataTable); Change Binding = new Binding($"[{column.ColumnName}]"), to Binding = new Binding($"Row.ItemArray[{column.Ordinal}]"), then to Binding = new Binding(column.ColumnName), Try to use Items instead of ItemsSource in the DataGrid but despite numerous places online claiming it's the correct property, it does not exist in mine Change from DataView to object wrappers, like so:

DataView filteredDataView;

to

ObservableCollection<ExpandoObject> allRows;
ObservableCollection<ExpandoObject> filteredRows;

and the following methods:

void LoadDataTable(DataTable dataTable) { sourceDataTable = dataTable; dataGrid.Columns.Clear(); filterPanel.Children.Clear(); columnFilters.Clear(); allRows = new ObservableCollection<ExpandoObject>(); filteredRows = new ObservableCollection<ExpandoObject>(); foreach (DataRow row in dataTable.Rows) { var expando = new ExpandoObject(); var expandoDict = (IDictionary<string, object>)expando; foreach (DataColumn column in dataTable.Columns) { expandoDict[column.ColumnName] = row[column]; } allRows.Add(expando); filteredRows.Add(expando); } foreach (DataColumn column in dataTable.Columns) { CreateColumnFilter(column); CreateDataGridColumn(column); } dataGrid.ItemsSource = filteredRows; System.Diagnostics.Debug.WriteLine($"Loaded DataTable with {dataTable.Rows.Count} rows and {dataTable.Columns.Count} columns"); System.Diagnostics.Debug.WriteLine($"DataGrid has {dataGrid.Columns.Count} columns"); System.Diagnostics.Debug.WriteLine($"Filtered collection has {filteredRows.Count} items"); } void ApplyFilters() { if (allRows == null) return; filteredRows.Clear(); foreach (var row in allRows) { var rowDict = (IDictionary<string, object>)row; bool matches = true; foreach (var kvp in columnFilters) { string columnName = kvp.Key; string filterText = kvp.Value.Text; if (!string.IsNullOrWhiteSpace(filterText)) { if (!rowDict.ContainsKey(columnName)) { matches = false; break; } string cellValue = rowDict[columnName]?.ToString() ?? string.Empty; if (!cellValue.Contains(filterText, StringComparison.OrdinalIgnoreCase)) { matches = false; break; } } } if (matches) { filteredRows.Add(row); } } } Change the parent of the DataGrid from StackPanel to Grid like so:

(Markdown filler)

void BuildControl() { var grid = new Grid { RowDefinitions = { new RowDefinition(GridLength.Auto), new RowDefinition(GridLength.Star) } }; filterPanel = new StackPanel { Orientation = Orientation.Horizontal, Height = 30 }; dataGrid = new DataGrid { IsReadOnly = true, CanUserSortColumns = true, CanUserResizeColumns = true, CanUserReorderColumns = true, GridLinesVisibility = DataGridGridLinesVisibility.All, HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, VerticalScrollBarVisibility = ScrollBarVisibility.Auto }; Grid.SetRow(filterPanel, 0); Grid.SetRow(dataGrid, 1); grid.Children.Add(filterPanel); grid.Children.Add(dataGrid); Content = grid; if (DataSource != null) LoadDataTable(DataSource); }

also tried changing new RowDefinition(GridLength.Star) to new RowDefinition(60, GridUnitType.Pixel), still nothing

Nothing works and I have no more ideas. Neither to ChatGPT or Claude AI.

How can I make the DataGrid items to show?

Read Entire Article