'Multiple values single data trigger WPF

I have the following piece of code.

                <Button.Style>
                    <Style BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
                           TargetType="{x:Type Button}">
                        <Setter Property="Visibility" Value="Collapsed" />
                        <Style.Triggers>

                            <DataTrigger Binding="{Binding Status, Mode=OneWay}"
                                         Value="{x:Static comm:DeviceStatus.Standby}">
                                <Setter Property="Visibility" Value="Visible" />
                            </DataTrigger>

                            <DataTrigger Binding="{Binding Status, Mode=OneWay}"
                                         Value="{x:Static comm:DeviceStatus.Busy}">
                                <Setter Property="Visibility" Value="Visible" />
                            </DataTrigger>

                            <DataTrigger Binding="{Binding Status, Mode=OneWay}"
                                         Value="{x:Static comm:DeviceStatus.Offline}">
                                <Setter Property="Visibility" Value="Visible" />
                            </DataTrigger>

                            <DataTrigger Binding="{Binding Status, Mode=OneWay}"
                                         Value="{x:Static comm:DeviceStatus.StartingStream}">
                                <Setter Property="Visibility" Value="Visible" />
                            </DataTrigger>

                            <DataTrigger Binding="{Binding Status, Mode=OneWay}"
                                         Value="{x:Static comm:DeviceStatus.Connecting}">
                                <Setter Property="Visibility" Value="Visible" />
                            </DataTrigger>

                            <DataTrigger Binding="{Binding Status, Mode=OneWay}"
                                         Value="{x:Static comm:DeviceStatus.Disconnecting}">
                                <Setter Property="Visibility" Value="Visible" />
                            </DataTrigger>

                            <DataTrigger Binding="{Binding Status, Mode=OneWay}"
                                         Value="{x:Static comm:DeviceStatus.DownloadingFiles}">
                                <Setter Property="Visibility" Value="Visible" />
                            </DataTrigger>

                        </Style.Triggers>
                    </Style>
                </Button.Style>

The button is hidden by default and is visible when a property in the viewmodel has one out of a few values. The property is of an enum type called DeviceStatus.

Basically it conducts an OR operation on the provided triggers. So, the visibility of the button is determined by: Status == StandBy || Status == Busy || ...

How can I implement this without having to have 8 triggers?

I would like to have something like the following:

<DataTrigger Binding="{Binding Status, Mode=OneWay}">
    <DataTrigger.AnyValue>
        <AnyValueItem Value="{x:Static comm:DeviceStatus.Standby}" />
        <AnyValueItem Value="{x:Static comm:DeviceStatus.Busy}" />
        <AnyValueItem Value="{x:Static comm:DeviceStatus.Offline}" />
        ...
    </DataTrigger.AnyValue>
    <Setter Property="Visibility" Value="Visible" />
</DataTrigger>

Where the Visibility of the Button is set to Visible if the binding gets ANY of the supplied values. Thanks!



Solution 1:[1]

After reading the answers I got some ideas and found a satisfying extendable solution.

First I created the following converter.

public sealed class EnumOrConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {

        DeviceStatus status = (DeviceStatus)value;

        DeviceStatus[] statuses = parameter as DeviceStatus[];

        if (statuses.Any(s => s == status))
        {
            return Visibility.Visible;
        }
        else
        {
            return Visibility.Collapsed;
        }
    }

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

Second I simply added the following code to the XAML.

<Button Command="{Binding SomeCommand}">
    <Button.Style>
        <Style BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
                           TargetType="{x:Type Button}">
            <Setter Property="Visibility">
                <Setter.Value>
                    <Binding Path="Status" Converter="{StaticResource EnumOrConverter}">
                        <Binding.ConverterParameter>
                            <x:Array Type="{x:Type comm:DeviceStatus}">
                                <x:Static Member="comm:DeviceStatus.Standby" />
                                <x:Static Member="comm:DeviceStatus.Busy" />
                                <x:Static Member="comm:DeviceStatus.Offline" />
                                <x:Static Member="comm:DeviceStatus.StartingStream" />
                                <x:Static Member="comm:DeviceStatus.Connecting" />
                                <x:Static Member="comm:DeviceStatus.Disconnecting" />
                                <x:Static Member="comm:DeviceStatus.DownloadingFiles" />
                            </x:Array>
                        </Binding.ConverterParameter>
                    </Binding>
                </Setter.Value>
            </Setter>
        </Style>
    </Button.Style>
</Button>

This allows me to reuse the converter logic for many other buttons and no adding of variables or properties to the ViewModel. What also happened is that the converter encapsulates the logic of "OR:ing" the parameters and "defaulting to Visibility.Collapsed". And adding a new parameter simply requires one line of code in XAML which is where it belongs.

Solution 2:[2]

The DataTrigger has no support for some kind of list of possible values. It only has a single Value property.

The easiest way to work around this would be to add a property to the view model that returns a value that indicates whether to display the Button:

public bool IsVisible => Status == Standby || Status == Busy || ...;

XAML:

<DataTrigger Binding="{Binding IsVisible, Mode=OneWay}" Value="True">
    <Setter Property="Visibility" Value="Visible" />
</DataTrigger>

Another option may be to use a converter as suggested by @l33t. You would then simply move the logic out of the view model, e.g.:

public class Converter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        ViewModel vm = value as ViewModel;
        return (vm != null && (vm.Status == Standby || vm.Status == Busy || ...)) ? Vsibility.Visible : Visibility.Collapsed;
    }

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

XAML:

<Style ...>
    <Style.Resources>
        <local:Converter x:Key="conv" />
    </Style>
    <Setter Property="Visibility" Value="{Binding Path=., Converter={StaticResource converter}}" />

Solution 3:[3]

You could just make a property:

private DeviceStatus _Status;
    public DeviceStatus Status
    {
        get { return _Status; }
        set
        {
            this.Set(ref _Status, value);
            RaisePropertyChanged(nameof(this.StatusVisibility));
        }
    }

    public Visibility StatusVisibility
    {
        get
        {
            switch (_Status)
            {
                case DeviceStatus.Busy: //add other statuses here
                    return Visibility.Visible;
            }

            return Visibility.Collapsed;
        }
    }

and then in your button:

<Button Content="MyButton" Visibility="{Binding StatusVisibility}"></Button>

Solution 4:[4]

For others. An answer that's similar to Lugvig W's answer but keeps with DataTriggers and is more generalised

ValueConverter

public sealed class AnyMatchConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (parameter is Array objects && value is not null)
        {
            return Array.BinarySearch(objects, value) != -1;
        }
        return DependencyProperty.UnsetValue;
    }
   ...

XAML

 <DataTrigger Value="True">
     <DataTrigger.Binding>
          <Binding Path="Status" Converter="{StaticResource AnyMatchConverter}">
               <Binding.ConverterParameter>
                   <x:Array Type="{x:Type comm:DeviceStatus}">
                            <x:Static Member="comm:DeviceStatus.Standby" />
                            <x:Static Member="comm:DeviceStatus.Busy" />
                            ...
                        </x:Array>
               </Binding.ConverterParameter>
          </Binding>
     </DataTrigger.Binding>
   ...

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 mm8
Solution 3 Kevin Cook
Solution 4 dtwk2