In my AvaloniaUI application I have a user control that's basically a PathIcon with a TextBlock caption, the contents of which are bound to dependency properties. One DP is IsActive, which when true should have the icon and text be the theme's accent color, and normal theme foreground when false. That all works, except when the theme switches to light or dark, the colors don't update until I change the state bound to IsActive.
I need the icon's and text's color to automatically update to what they should be when the theme toggles. I would rather not use a converter that inputs the state and theme. I would also rather not have to track all bound state flags and double-toggle them, which seems inefficient and can create side effects if other operations are executed on the togglings.
User control XAML:
<UserControl xmlns="https://github.com/avaloniaui"
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"
mc:Ignorable="d" d:DesignWidth="48" d:DesignHeight="48"
xmlns:views="clr-namespace:MyApp.Views"
x:Class="MyApp.Views.CaptionedToggleIcon">
<UserControl.Styles>
<Style Selector="views|CaptionedToggleIcon">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="views:CaptionedToggleIcon">
<Panel VerticalAlignment="Stretch">
<Rectangle Fill="Transparent" />
<StackPanel>
<PathIcon Width="{Binding IconWidth,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type views:CaptionedToggleIcon}}}"
Height="{Binding IconHeight,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type views:CaptionedToggleIcon}}}"
Data="{Binding IconGeo,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type views:CaptionedToggleIcon}}}">
<Interaction.Behaviors>
<DataTriggerBehavior Binding="{TemplateBinding IsActive}"
ComparisonCondition="Equal"
Value="True">
<!--AccentButtonBackground is darker than SystemAccentColor
during light theme and lighter during dark theme-->
<ChangePropertyAction PropertyName="Foreground"
Value="{DynamicResource AccentButtonBackground}" />
</DataTriggerBehavior>
<DataTriggerBehavior Binding="{TemplateBinding IsActive}"
ComparisonCondition="Equal"
Value="False">
<ChangePropertyAction PropertyName="Foreground"
Value="{DynamicResource TextFillColorPrimary}" />
</DataTriggerBehavior>
</Interaction.Behaviors>
</PathIcon>
<TextBlock Text="{Binding Caption,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type views:CaptionedToggleIcon}}}">
<Interaction.Behaviors>
<DataTriggerBehavior Binding="{TemplateBinding IsActive}"
ComparisonCondition="Equal"
Value="True">
<ChangePropertyAction PropertyName="Foreground"
Value="{DynamicResource AccentButtonBackground}" />
</DataTriggerBehavior>
<DataTriggerBehavior Binding="{TemplateBinding IsActive}"
ComparisonCondition="Equal"
Value="False">
<ChangePropertyAction PropertyName="Foreground"
Value="{DynamicResource TextFillColorPrimary}" />
</DataTriggerBehavior>
</Interaction.Behaviors>
</TextBlock>
</StackPanel>
</Panel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Styles>
</UserControl>
Code-behind:
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Media;
namespace MyApp.Views;
public partial class CaptionedToggleIcon : UserControl
{
public static readonly StyledProperty<string> CaptionProperty =
AvaloniaProperty.Register<CaptionedToggleIcon, string>(nameof(IsActive), defaultValue: string.Empty);
public static readonly StyledProperty<Geometry> IconProperty =
AvaloniaProperty.Register<CaptionedToggleIcon, Geometry>(nameof(IconGeo));
public static readonly StyledProperty<bool> IsActiveProperty =
AvaloniaProperty.Register<CaptionedToggleIcon, bool>(nameof(IsActive), defaultValue: false);
public static readonly StyledProperty<double> IconWidthProperty =
AvaloniaProperty.Register<CaptionedToggleIcon, double>(nameof(IconWidth), defaultValue: 24d);
public static readonly StyledProperty<double> IconHeightProperty =
AvaloniaProperty.Register<CaptionedToggleIcon, double>(nameof(IconHeight), defaultValue: 24d);
public string Caption {
get => GetValue(CaptionProperty);
set => SetValue(CaptionProperty, value);
}
public Geometry IconGeo {
get => GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
public bool IsActive {
get => GetValue(IsActiveProperty);
set => SetValue(IsActiveProperty, value);
}
public double IconWidth {
get => GetValue(IconWidthProperty);
set => SetValue(IconWidthProperty, value);
}
public double IconHeight {
get => GetValue(IconHeightProperty);
set => SetValue(IconHeightProperty, value);
}
public CaptionedToggleIcon() {
InitializeComponent();
}
}
Example usage in XAML, where a button toggles a viewmodel flag's state and lights up in the accent color while the state is true:
<Button Command="{Binding ToggleFlag}">
<Button.Content>
<views:CaptionedToggleIcon IconWidth="20" IconHeight="20" Caption="Text below icon"
IsActive="{Binding IsFlagSet}"
IconGeo="{StaticResource IconStreamGeometry}" />
</Button.Content>
</Button>
Relevant code of viewmodel that inherits from ObservableRecipient:
[RelayCommand]
public void ToggleFlag() {
IsFlagSet = !IsFlagSet;
}