5

Universal windows apps don't support data triggers.

Without data triggers, how can I change the background color of a button using xaml and data binding only when a boolean property in the view model changes?

For example given this XAML:

<StackPanel>
    <Button Name="ButtonA" Click="ButtonA_Click" Content="A" />
    <Button Name="ButtonB" Click="ButtonB_Click" Content="B" />
    <Button Name="ButtonC" Click="ButtonC_Click" Content="C" />
</StackPanel>

with this code behind

private void ButtonA_Click(object sender, RoutedEventArgs e)
{
    Model.IsOnA = !Model.IsOnA;
}

private void ButtonB_Click(object sender, RoutedEventArgs e)
{
    Model.IsOnB = !Model.IsOnB;
}

private void ButtonC_Click(object sender, RoutedEventArgs e)
{
    Model.IsOnC = !Model.IsOnC;
}

What is the best approach to change the background color of the buttons using data binding when the corresponding property in the view model is changed?

I was able to make it work for one button only using the VisualStateManager manager:

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup>
        <VisualState>
            <VisualState.StateTriggers>
                <StateTrigger IsActive="{x:Bind Model.IsOnA, Mode=OneWay}" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="ButtonA.Background" Value="Red"></Setter>
                <Setter Target="ButtonA.Foreground" Value="White"></Setter>
            </VisualState.Setters>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

But with multiple buttons that bind to different properties in the view model this approach is not working.

Patrick
  • 167
  • 3
  • 9
  • You can bind the background color to the boolean property and use an IValueConverter to covert the boolean to a solid color brush – Ken Tucker Dec 13 '15 at 13:37
  • Are there other ways? Because if I want to change different properties (e.g. background color, foreground color, visibility, template), each property needs a different converter. – Patrick Dec 13 '15 at 22:28
  • I added two more ways to do it as answers. If you are used to WPF all three approaches feel like poor workarounds for missing data triggers. – Patrick Dec 14 '15 at 18:30

3 Answers3

7

You can check my previous answer in the following link. Delete button on ListView items

You just need to create a converter which converts Boolean to SolidColorBrush. For example:

public class BooleanToColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        return (value is bool && (bool)value) ? new SolidColorBrush(Colors.Red) : new SolidColorBrush(Colors.Green);
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new Exception("Not Implemented");
    }
}

And to add it to your Xaml Binding.

<Page.Resources>
    <local:BooleanToColorConverter x:Key="ColorConverter"/>
</Page.Resources>

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <ListView ItemsSource="{x:Bind Activities}">
        <ListView.ItemTemplate>
            <DataTemplate x:DataType="local:Activity">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="3*"/>
                        <ColumnDefinition Width="1*"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock x:Name="txt" Text="{x:Bind Name}" Grid.Column="0"/>
                    <Button x:Name="delItem" Click="delItem_Click" Grid.Column="1" Foreground="{x:Bind Visible, Mode=OneWay, Converter={StaticResource ColorConverter}}" Background="Transparent" Margin="100, 0, 0, 0">
                        <SymbolIcon Symbol="Delete"/>
                    </Button>
                </Grid>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>
GEOCHET
  • 21,119
  • 15
  • 74
  • 98
  • This works. Is it correct though that for every property I would like to change e.g. foreground color, background color, visibility or template a different converter would be needed? – Patrick Dec 13 '15 at 22:23
  • @P4tr3ck for attributes like `Foreground` and `Background`(that require a `Brush` to be passed to them) you can create one Converter and pass a parameter to it via tha `ConverterParameter` attribute. Then in the converter based on the value and the parameter decide what color to return. For Visibility you can write a `BooleanToVisibilityConverter` and re-use it across your app. Keep in mind that Converters are highly reusable... Write them once and use them anywhere. – Corcus Dec 14 '15 at 11:21
  • @P4tr3ck Can you please mark this post as answered if you don't need additional information ? Thanks. – Jean-Sébastien Dupuy Dec 14 '15 at 16:15
  • Sure. Your solution does exactly what I asked. – Patrick Dec 14 '15 at 18:32
0

An alternative to converters are attached properties. Useful if more than one property should be changed or if one needs access to the view model (through the DataContext of the control) to decide how to change the user interface properties.

Here is a simple example:

public class IsFavoriteBehavior
{
    public static bool GetIsFavorite(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsFavoriteProperty);
    }

    public static void SetIsFavorite(DependencyObject obj, bool value)
    {
        obj.SetValue(IsFavoriteProperty, value);
    }

    public static readonly DependencyProperty IsFavoriteProperty =
        DependencyProperty.RegisterAttached("IsFavorite", typeof(bool), typeof(Button), new PropertyMetadata(false, (o, e) =>
        {
            var button = o as Button;

            if (button == null)
                return;

            if ((bool)e.NewValue)
            {
                button.Background = (SolidColorBrush)Application.Current.Resources["HighlightBackgroundColorBrush"];
                button.Foreground = (SolidColorBrush)Application.Current.Resources["HighlightTextColorBrush"];
            }
            else
            {
                button.Background = (SolidColorBrush)Application.Current.Resources["NormalBackgroundColorBrush"];
                button.Foreground = (SolidColorBrush)Application.Current.Resources["NormalTextColorBrush"];
            }

            o.SetValue(IsFavoriteProperty, e.NewValue);
        }));
}

It can be used like this in XAML:

<Button Name="FavoriteButton" Content="Favorite" local:IsFavoriteBehavior.IsFavorite="{x:Bind ViewModel.Favorite, Mode=OneWay}" >
Patrick
  • 167
  • 3
  • 9
0

One could put a property with the background color brush directly into the view model.

For example in the view model:

SolidColorBrush IsOnABackground
{
    get
    {
        if(IsOnA)
            return (SolidColorBrush)Application.Current.Resources["HighlightBackgroundColorBrush"];
        else
            return (SolidColorBrush)Application.Current.Resources["NormalBackgroundColorBrush"];
    }
}

bool isOnA = false;
bool IsOnA
{
    set
    {
        if (isOnA != value)
        {
            isOnA = value;

            OnPropertyChanged("IsOnA");
            OnPropertyChanged("IsOnABackground");
        }
    }
    get { return isOnA; }
}

and in XAML:

<Button Name="ButtonA" Content="A" Background="{x:Bind ViewModel.IsOnABackground, Mode=OneWay}" />
Patrick
  • 167
  • 3
  • 9