Search code examples
vb.netxamluwpuwp-xamlvisualstatemanager

UWP/VB.Net - Change Bound Button Color Dependent on Variable


I'm a little new to UWP. I've spent two days looking for this answer and haven't come across the answer yet. I know I could be approaching this completely wrong. All help is appreciated!

I have a SolidColorBrush StaticResource I've created in my App.xaml file:

    <Application.Resources>
         <SolidColorBrush x:Key="colorClockStatus" Color="#cae0d5" />
    </Application.Resources>`

I've bound this brush to the Background property of a few buttons on a few pages. For example, I bound the buttons it like this:

    <Button Background="{StaticResource colorClockStatus}"/>

That works great, the button backgrounds are the desired red. The problem comes in when I change the color of the StaticResource. The button color doesn't change. If I open another page after the color change, the buttons on the new page will be green as desired, but I can't get a button on an already opened page to change color.

So here's the scenario. When the user is 'ClockedIn', the bound buttons need to be green, and when the user clocks out, the bound buttons need to turn red and vise versa. Here's the backend code in the App.xaml.vb file:

    Public Shared ClockedIn As Boolean = False

    Public Shared Sub SetClockedStatus(ByVal bClockedIn As Boolean)
        ClockedIn = bClockedIn
        With Application.Current
            If bClockedIn Then
                .Resources("colorClockStatus") = Color.FromArgb(255, 202, 224, 213) 'Green
            Else
                .Resources("colorClockStatus") = Color.FromArgb(255, 227, 184, 179) 'Red
            End If
        End With
    End Sub

When the user taps the 'ClockIn' button, the above code is called. I think I need a INotifyPropertyChanged event to let the UI know the colorClockStatus property changed and therefore needs to redraw the buttons, but I don't know how to do this with a StaticResource. I've also have tried implementing a VisualStateManager, and create two SaticResources the button switches between (which seems like a better way), but I couldn't figure out how to set the trigger dependent on the Boolean 'ClockIn' variable, and then to implement the manager globally.

So how can I get the UI to change colors dependent on a Boolean variable?

Thank you!

UPDATE

I've been working on this some more and found an example using a StateTriggerBase class here. I adapted this code to work for a Boolean property. Below is my adapted class:

    Public Class ClockedInTrigger
        Inherits StateTriggerBase
        Private _ClockedIn As Boolean = False

        Public Property ClockedIn As Boolean
            Get
                Return _ClockedIn
            End Get
            Set
                If Not Value.Equals(_ClockedIn) Then
                    _ClockedIn = _ClockedIn
                    SetActive(ClockedIn = _ClockedIn)
                End If
            End Set
        End Property
    End Class

Then, instead of having just one StaticResource, I made two:

    <Application.Resources>
         <SolidColorBrush x:Key="colorClockInLight" Color="#cae0d5" />
         <SolidColorBrush x:Key="colorClockOutLight" Color="#e3b8b3" />
    </Application.Resources>

Then I added a VisualStateManager to my MainPage to test it:

    <VisualStateManager.VisualStateGroups>
       <VisualStateGroup x:Name="ClockInStates">                
            <VisualState x:Name="vsClockedOut">
                <VisualState.StateTriggers>
                    <local:ClockedInTrigger ClockedIn="False" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="HamburgerButton.Background" Value="{StaticResource colorClockOutLight}" />
                </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="vsClockedIn">
                <VisualState.StateTriggers>
                    <local:ClockedInTrigger ClockedIn="True" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="HamburgerButton.Background" Value="{StaticResource colorClockInLight}" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

I was really excited when I opened the program and the button was green! (By default, the button is red) But to my disappointment, the button did not turn back red when I clocked out. I used the debugger and the SetActive(ClockedIn = _ClockedIn) method is being called, but the UI doesn't change. Do I have something wrong with my binding? Is there some method I need to call to refresh the UI?


Solution

  • To achieve what you want, I think you can just take advantage of StateTrigger with a InvertBoolConverter like following:

    XAML:

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="ClockInStates">
            <VisualState x:Name="vsClockedOut">
                <VisualState.StateTriggers>
                    <StateTrigger IsActive="{x:Bind ClockedIn, Converter={StaticResource InvertBoolConverter}, Mode=OneWay}" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="HamburgerButton.Background" Value="{StaticResource colorClockOutLight}" />
                </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="vsClockedIn">
                <VisualState.StateTriggers>
                    <StateTrigger IsActive="{x:Bind ClockedIn, Mode=OneWay}" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="HamburgerButton.Background" Value="{StaticResource colorClockInLight}" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    

    InvertBoolConverter:

    Public Class InvertBoolConverter
        Implements IValueConverter
    
        Public Function Convert(value As Object, targetType As Type, parameter As Object, language As String) As Object Implements IValueConverter.Convert
            Dim boolValue = CBool(value)
            Return Not boolValue
        End Function
    
        Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, language As String) As Object Implements IValueConverter.ConvertBack
            Dim boolValue = CBool(value)
            Return Not boolValue
        End Function
    End Class
    

    Also you can try with WindowsStateTriggers which is a collection of custom visual state triggers. You can use StateTrigger and IsFalseStateTrigger instead of InvertBoolConverter. Besides, please note you need to implement INotifyPropertyChanged for ClockedIn property to make it work.