1

Problem

I have an Button whose command is ToggleMode, Inside this button is a FontIcon and a TextBlock.

In Styles.xaml I set the foreground color of a Button to white when the Button is enabled and to a light gray if the button is disabled. This leads to the Textblocks foreground color correctly changing when the Button is disabled because the ToggleMode.CanExecute() returns false. However the FontIcons normal color is a light blue, which I set using the Foreground property. When the Button is disabled the color should be changed to a pale blue improving the impression of the button being disabled.

Code example

Boolean converter

    <local:BooleanConverter x:Key="CanExecuteToIconColorConverter">
        <local:BooleanConverter.True>
            <SolidColorBrush Color="{ThemeResource VividSkyBlueColor}" />
        </local:BooleanConverter.True>
        <local:BooleanConverter.False>
            <SolidColorBrush Color="{ThemeResource PaleVividSkyBlueColor}" />
        </local:BooleanConverter.False>
    </local:BooleanConverter>

Button

    <Button Grid.Row="0" Width="150" Padding="10"
            Command="{x:Bind ToggleMode, Mode=OneWay}">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="3*"/>
            </Grid.ColumnDefinitions>
            <FontIcon FontSize="20" Grid.Column="0"
                      Glyph="{StaticResource mdi_toggle_off}"
                      FontFamily="{StaticResource MaterialDesignIconsOutlined}"
                      Foreground="{ThemeResource VividSkyBlueColor}"/>
            <TextBlock Grid.Column="1" Margin="10,0,0,0" Text="Toggle"/>
        </Grid>
    </Button>

What I tried

I tried using the generic BooleanConverter given here: How do I invert BooleanToVisibilityConverter? Setting a SolidColorBrush with the light blue color as True Value and the pale blue as the False Value and Binding the Foreground property of my FontIcon to the command but this does not work.

After that I tried to subscribe to the CanExecuteChanged Event of the Command, saving the new value in a private field and binding to that but this does not work as well because at the time of initializing my view this command is still null.

My next guess would be to subscribe to the property changed event of the command when this is fired the command should'nt be null anymore and I should be able to subscribe to the Event but this seems like a lot boiler code especially if I have various commands I need to do this to.

How can I achieve this easier?

Edit1 How I tried to bind the command to theFontIcon

<FontIcon FontSize="20" Grid.Column="0"
          Glyph="{StaticResource mdi_toggle_off}"
          FontFamily="{StaticResource MaterialDesignIconsOutlined}"
          Foreground="{x:Bind ToggleCommand, Mode=OneWay, Converter={StaticResource CanExecuteToIconColorConverter}}"/>

What worked for me

Following the anser by @AndrewKeepCoding I found out that just binding to the IsEnabled property of the button instead of binding to the command itself:

<Button Grid.Row="0" Width="150" Padding="10"
        Command="{x:Bind ToggleMode, Mode=OneWay}"
        x:Name="ToggleModeButton">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="3*"/>
        </Grid.ColumnDefinitions>
        <FontIcon FontSize="20" Grid.Column="0"
                  Glyph="{StaticResource mdi_toggle_off}"
                  FontFamily="{StaticResource MaterialDesignIconsOutlined}"
                  Foreground="{x:Bind ToggleModeButton.IsEnabled, Mode=OneWay, Converter={StaticResource CanExecuteToIconColorConverter}}"/>
        <TextBlock Grid.Column="1" Margin="10,0,0,0" Text="Toggle"/>
    </Grid>
</Button>
tiko
  • 17
  • 8
  • "[...] and Binding the Foreground property of my FontIcon to the command but this does not work." -- please show exactly how you tried this and explain exactly how it did not work. – Mike Nakis Apr 13 '23 at 10:14
  • @MikeNakis added the FontIcon the way it looked when I tried to bind the Command to the color using the given converter. – tiko Apr 13 '23 at 11:48

2 Answers2

1

Let me show you another way to achieve this using a ValueConverter.

BooleanToBrushConverter.cs

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Media;
using System;

namespace FontIconExample;

public class BooleanToBrushConverter : DependencyObject, IValueConverter
{
    public static readonly DependencyProperty TrueBrushProperty =
        DependencyProperty.Register(
            nameof(TrueBrush),
            typeof(Brush),
            typeof(BooleanToBrushConverter),
            new PropertyMetadata(default));

    public static readonly DependencyProperty FalseBrushProperty =
        DependencyProperty.Register(
            nameof(FalseBrush),
            typeof(Brush),
            typeof(BooleanToBrushConverter),
            new PropertyMetadata(default));

    public Brush TrueBrush
    {
        get => (Brush)GetValue(TrueBrushProperty);
        set => SetValue(TrueBrushProperty, value);
    }

    public Brush FalseBrush
    {
        get => (Brush)GetValue(FalseBrushProperty);
        set => SetValue(FalseBrushProperty, value);
    }

    public object Convert(object value, Type targetType, object parameter, string language)
    {
        return (bool)value is true
            ? TrueBrush
            : FalseBrush;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

MainPageViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace FontIconExample;

public partial class ViewModel : ObservableObject
{
    [RelayCommand(CanExecute = nameof(CanToggleMode))]
    private void ToggleMode()
    {
        CanToggleMode = false;
    }

    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(ToggleModeCommand))]
    private bool canToggleMode = true;
}

MainPage.xaml

<Page
    x:Class="FontIconExample.MainPage"
    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:local="using:FontIconExample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">
    <Page.DataContext>
        <local:ViewModel x:Name="ViewModel" />
    </Page.DataContext>
    <Page.Resources>
        <local:BooleanToBrushConverter
            x:Key="BooleanToBrushConverter"
            FalseBrush="LightGreen"
            TrueBrush="SkyBlue" />
    </Page.Resources>

    <Grid>
        <Button
            x:Name="ToggleModeButton"
            Grid.Row="0"
            Width="150"
            Padding="10"
            Command="{x:Bind ViewModel.ToggleModeCommand, Mode=OneWay}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="3*" />
                </Grid.ColumnDefinitions>
                <FontIcon
                    Grid.Column="0"
                    FontFamily="Segoe MDL2 Assets"
                    FontSize="20"
                    Foreground="{x:Bind ToggleModeButton.IsEnabled, Mode=OneWay, Converter={StaticResource BooleanToBrushConverter}}"
                    Glyph="&#xE790;" />
                <TextBlock
                    Grid.Column="1"
                    Margin="10,0,0,0"
                    Text="Toggle" />
            </Grid>
        </Button>
    </Grid>
</Page>
Andrew KeepCoding
  • 7,040
  • 2
  • 14
  • 21
  • In what way do you use the changes done to MainPageViewModel.cs? I do not see the method being called or the bool being used. I do not have a ViewModel for the view displaying the button whose icon should be changed as the command being called by the buton was passed to the view as a dependency property. – tiko Apr 14 '23 at 07:31
  • My answer is based on MVVM using the [CommunityToolkit.Mvvm](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/relaycommand) NuGet package. If you don't need MVVM, then you can just use the ``Click`` event instead of ``Command``. – Andrew KeepCoding Apr 14 '23 at 07:47
  • Giving the button a name and binding to its IsEnabled property did the trick perfectly. By doing so I did not even need to do any changes to the code behind or adding a converter. I could just use the BooleanConverter I already had. As I am new I do not know what the "etiquette" here is. As your answer is more than I need and I basically just need the binding to the isEnabled to get it work it feels a bit overkill to accept the whole answer thus I would like to accept a short answer just stating this to make it more clear. On the other hand you should deserve the accepted answer. Mind if you – tiko Apr 14 '23 at 07:49
  • just put some short info at the top of your answer stating that just binding to the isEnabled worked but leave the rest as a different solution so I can accept it? Or how should we work this out? – tiko Apr 14 '23 at 07:50
  • 1
    Just add what eventually worked for you at the bottom of your question. Might help others. – Andrew KeepCoding Apr 14 '23 at 07:55
0

In my experience FontIcon is a bit flaky because in some ways it does not behave as expected. Modifying the color of the FontIcon could perhaps suit your particular situation, (if you could get it to work,) but in more complicated situations where you might have a complex control that must look disabled, it won't work.

What I prefer to do in these cases is to refrain from modifying the colors of individual elements and instead do one of the following:

  • modify the transparency of the entire control
  • add another control on top of the control, which is almost transparent but not entirely transparent, to wash out the colors of the underlying control.

When a certain deep blue is made slightly transparent, (or when it is overlaid by a white but almost transparent shade,) it will appear as light blue, and also a deep orange will appear as light orange, etc.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • Okay overlaying another control or modifying the transparancy is one way to go that I did not think about prior to your comment thank you! I think I will go that way to make it at least work somehow. Yet thit is not a way that feels good. – tiko Apr 13 '23 at 11:50
  • What I do agree with is that it does not feel good to abandon a certain quest, in this case the quest to get FontIcon to work. But if FontIcon is weird, then getting it to work might be an exercise in futility, and the gain will be small, because you will only learn one thing: how to get FontIcon to work. On the other hand, using the approach I suggested should actually feel very good, because it is more general-purpose: it solves a broader range of problems than getting the silly FontIcon to work. – Mike Nakis Apr 13 '23 at 11:53
  • 1
    You're right that broader thinking seems to be something I need to learn. Didn't look at it that way. – tiko Apr 13 '23 at 12:00