Skip to content

Example application on Avalonia using Xaml.Benavior to control data visualization

Notifications You must be signed in to change notification settings

Kerminator1973/Avalonia.DataGridExample

Repository files navigation

Project description

The application was generated by using an Avalonia Template (0.10.18.6). The platform is .NET 6.

On the first step I have attached:

  • a model (Model/Banknote.cs) with some fake data
  • a DataGrid (see: "MainWindow")

Also, some styles were added to the "App.xaml", because without them we can't visualize any data in the DataGrid:

<Application.Styles>
    <StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
    <StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
    <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
</Application.Styles>

Alternatively, you can use the Fluent Theme with light or dark mode:

<Application.Styles>
    <FluentTheme Mode="Dark"/>
    <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
</Application.Styles>

XAML Behaviors

I also added, I included the Avalonia.XAML.Behaviors package to the .csproj and restored the dependencies.

Next, I added references to the namespace of Avalonia.XAML.Behaviors to the window header:

<Window xmlns="https://github.com/avaloniaui" ...
		xmlns:int="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
        xmlns:ia="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions"

The last part of the code change is adding the trigger to the XAML:

<Window.Styles>
    <Style Selector="DataGridCell.statusColumn">
        <Setter Property="FontSize" Value="24"/>
        <Setter Property="(int:Interaction.Behaviors)">
            <int:BehaviorCollectionTemplate>
                <int:BehaviorCollection>
                    <ia:DataTriggerBehavior Binding="{Binding Status}" ComparisonCondition="Equal" Value="Rejected">
                        <ia:ChangePropertyAction TargetObject="DataGridCell" PropertyName="Background" Value="Red" />
                    </ia:DataTriggerBehavior>
                </int:BehaviorCollection>
            </int:BehaviorCollectionTemplate>
        </Setter>
    </Style>
</Window.Styles>

The problem - the DataTriggerBehavior uses wrong data context

When I run the application under debugger, it duplicates massages to the output window:

[Binding] Error in binding to 'Avalonia.Xaml.Interactions.Core.DataTriggerBehavior'.'Binding': 'Could not find a matching property accessor for 'Status' on 'Avalonia.DataGridExample.ViewModels.MainWindowViewModel'' (DataTriggerBehavior #20745743)
[Binding] Error in binding to 'Avalonia.Xaml.Interactions.Core.DataTriggerBehavior'.'Binding': 'Could not find a matching property accessor for 'Status' on 'Avalonia.DataGridExample.ViewModels.MainWindowViewModel'' (DataTriggerBehavior #2683661)

The selector is good, because it changes the font size of the cells in the 'Status' column. The ChangePropertyAction is also good: when I use some property in my MainWindowViewModel, Xaml.Behaviors changes the Background color to red.

Under the debugger, the Developer Console (F12) definitely says that DataGridRow and its children (DataGridCells) refer to a Banknote object (the element of the ObservableCollection).

All the sings claim that the DataContext of the ChangePropertyAction refers to the data context of the ViewModel.

Using a converter as an alternative solution

The converter is a class that implements the IValueConverter interface. The main purpose is to convert a value of one attribute into a value of another attribute. The most important part of the conversion is the ability to replace not only a value, but also a type.

There should be two methods implemented in your converter: Convert() and Convertback(). The second method is often remains not implemented - it is only useful when two-sided binding is used.

Please, have a look at the implementation of the class:

public class StatusConverter : IValueConverter
{
    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        var status = value as string;

        return status switch
        {
            "Rejected" => Brushes.Red,
            _ => null
        };
    }

    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

If we want to use the converter in the UI element, we should create a static instance of this class:

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        ...
		xmlns:converter="using:BvsDesktopLinux.Converters">

    <Window.Resources>
        <converter:StatusConverter x:Key="StatusConverter" />
    </Window.Resources>

In addition, we can use the instance of converter for binding properties:

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        ...
		xmlns:models="using:BvsDesktopLinux.Models">

	<Window.Styles>
        <Style Selector="DataGridCell.statusColumn" x:DataType="models:Banknote">
            <Setter Property="Background" Value="{Binding Status, Converter={StaticResource StatusConverter}}" />
        </Style>
	</Window.Styles>

Potential drawback of using converters

Let’s say that you want to highlight in color a row, not a cell. In this case the selector must to be modified as follows (Selector="DataGridCell"):

<Style Selector="DataGridCell" x:DataType="models:Banknote">
    <Setter Property="Background" Value="{Binding Status, Converter={StaticResource StatusConverter}}" />
</Style>

If you use the style selector like as in the example above, the Convert() method will be called as many times as the table has columns, for each row.

Frankly speaking, I don’t have any performance reports, but it can be a bit frustrating.

If performance is an issue for your project, you should consider other options. For example, have a look at an example of using the LoadingRow attribute.

Using CompiledBindings in DataGrid

To improve the performance of the application, the bindings types have been replaced with CompiledBindings. The idea is to avoid using reflection at runtime to extract cell values.

To add compiled bindings you need to set the x:CompileBindings option in the description of the UI element:

<Window xmlns="https://github.com/avaloniaui" ...
		x:CompileBindings="True">

You must also to specify the main data type of the UI element, and append the reference the namespace of a model class:

<Window xmlns="https://github.com/avaloniaui" ...
		xmlns:local="using:BvsDesktopLinux.Models"
		x:DataType="vm:MainWindowViewModel"
		x:CompileBindings="True">

Next, you should replace the keyword Binding with CompiledBinding. You should also to specify a data type that you will use to access the cell values (x:DataType="local:Banknote"):

<DataGrid AutoGenerateColumns="False" Items="{CompiledBinding Banknotes}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="{x:Static p:Resources.NoteId}" 
                            Binding="{CompiledBinding Id}" x:DataType="local:Banknote" />
        <DataGridTextColumn Header="{x:Static p:Resources.NoteCurrency}" 
                            Binding="{CompiledBinding Currency}" x:DataType="local:Banknote" />
        <DataGridTextColumn Header="{x:Static p:Resources.NoteDenomination}" 
                            Binding="{CompiledBinding Denomination}" x:DataType="local:Banknote" />
        <DataGridTextColumn Header="{x:Static p:Resources.Status}" 
                            Binding="{CompiledBinding Status}" x:DataType="local:Banknote" 
                            CellStyleClasses="statusColumn" />
    </DataGrid.Columns>
</DataGrid>

About

Example application on Avalonia using Xaml.Benavior to control data visualization

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages