Switch (Select) statement in Xaml Binding?
Asked Answered
C

6

8

Is there a way to create a conditional binding in XAML?

Example:

<Window x:Name="Me" DataContext="{Binding ElementName=Me}">
    <TextBlock>
        <TextBlock.Text>
            <SelectBinding Binding="{Binding MyValue}">
                <Case Value="Value1" Value="value is 1!">                
                <Case Value="Value2" Value="value is 2!">                
                <Case Value="Value3" Value="value is 3!">                
            </SelectBinding >
        </TextBlock.Text>
    </TextBlock>
</Window>

Bottom line, I want to set a TextBlock value according to another value of Binding, that can be of a list of cases where each case (or cases) is addressed to the appropriate output/setter.

Maybe I can use a DataTrigger in my case, I just don't know exactly how I am gonna do it, since I am not using any DataTemplate here.

Update
In my scenario, I am having a UserControl that has several controls. I want that according to a certain property in the UserControl.DataContext data-item, other controls in the user control should get affected accordingly. Basically same as my example above just that each case leads to a list of Setters.

Caricature answered 6/12, 2010 at 13:42 Comment(4)
Actually, strictly for your example, {Binding MyValue, StringFormat='Value is {0}!'}Vinitavinn
I know, but I wanted to emphasize the switch part.Caricature
@Shimmy, just pushed you over 10K :)Lehmann
@DanM YES MAN, YOU'RE THE ONE WHO DID THIS!!! THANKS ♥♥♥Caricature
M
5

Try to use the Switch Converter written by Josh:

SwitchConverter –

A "switch statement" for XAML - http://josheinstein.com/blog/index.php/2010/06/switchconverter-a-switch-statement-for-xaml/

Edit:

Here is code of SwitchConverter as Josh's site seems to be down -

/// <summary>
/// A converter that accepts <see cref="SwitchConverterCase"/>s and converts them to the 
/// Then property of the case.
/// </summary>
[ContentProperty("Cases")]
public class SwitchConverter : IValueConverter
{
    // Converter instances.
    List<SwitchConverterCase> _cases;

    #region Public Properties.
    /// <summary>
    /// Gets or sets an array of <see cref="SwitchConverterCase"/>s that this converter can use to produde values from.
    /// </summary>
    public List<SwitchConverterCase> Cases { get { return _cases; } set { _cases = value; } }
    #endregion
    #region Construction.
    /// <summary>
    /// Initializes a new instance of the <see cref="SwitchConverter"/> class.
    /// </summary>
    public SwitchConverter()
    {
        // Create the cases array.
        _cases = new List<SwitchConverterCase>();
    }
    #endregion

    /// <summary>
    /// Converts a value.
    /// </summary>
    /// <param name="value">The value produced by the binding source.</param>
    /// <param name="targetType">The type of the binding target property.</param>
    /// <param name="parameter">The converter parameter to use.</param>
    /// <param name="culture">The culture to use in the converter.</param>
    /// <returns>
    /// A converted value. If the method returns null, the valid null value is used.
    /// </returns>
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        // This will be the results of the operation.
        object results = null;

        // I'm only willing to convert SwitchConverterCases in this converter and no nulls!
        if (value == null) throw new ArgumentNullException("value");

        // I need to find out if the case that matches this value actually exists in this converters cases collection.
        if (_cases != null && _cases.Count > 0)
            for (int i = 0; i < _cases.Count; i++)
            {
                // Get a reference to this case.
                SwitchConverterCase targetCase = _cases[i];

                // Check to see if the value is the cases When parameter.
                if (value == targetCase || value.ToString().ToUpper() == targetCase.When.ToString().ToUpper())
                {
                    // We've got what we want, the results can now be set to the Then property
                    // of the case we're on.
                    results = targetCase.Then;

                    // All done, get out of the loop.
                    break;
                }
            }

        // return the results.
        return results;
    }

    /// <summary>
    /// Converts a value.
    /// </summary>
    /// <param name="value">The value that is produced by the binding target.</param>
    /// <param name="targetType">The type to convert to.</param>
    /// <param name="parameter">The converter parameter to use.</param>
    /// <param name="culture">The culture to use in the converter.</param>
    /// <returns>
    /// A converted value. If the method returns null, the valid null value is used.
    /// </returns>
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

/// <summary>
/// Represents a case for a switch converter.
/// </summary>
[ContentProperty("Then")]
public class SwitchConverterCase
{
    // case instances.
    string _when;
    object _then;

    #region Public Properties.
    /// <summary>
    /// Gets or sets the condition of the case.
    /// </summary>
    public string When { get { return _when; } set { _when = value; } }
    /// <summary>
    /// Gets or sets the results of this case when run through a <see cref="SwitchConverter"/>
    /// </summary>
    public object Then { get { return _then; } set { _then = value; } }
    #endregion
    #region Construction.
    /// <summary>
    /// Switches the converter.
    /// </summary>
    public SwitchConverterCase()
    {
    }
    /// <summary>
    /// Initializes a new instance of the <see cref="SwitchConverterCase"/> class.
    /// </summary>
    /// <param name="when">The condition of the case.</param>
    /// <param name="then">The results of this case when run through a <see cref="SwitchConverter"/>.</param>
    public SwitchConverterCase(string when, object then)
    {
        // Hook up the instances.
        this._then = then;
        this._when = when;
    }
    #endregion

    /// <summary>
    /// Returns a <see cref="System.String"/> that represents this instance.
    /// </summary>
    /// <returns>
    /// A <see cref="System.String"/> that represents this instance.
    /// </returns>
    public override string ToString()
    {
        return string.Format("When={0}; Then={1}", When.ToString(), Then.ToString());
    }
}
Monsour answered 6/12, 2010 at 17:56 Comment(2)
Link is dead but you can access the article hereMonsour
@Monsour could be nice to provide a xaml example?Injustice
W
13

use a DataTrigger

(EDITED - original had slight mistake)

<TextBlock>
  <TextBlock.Style>
    <Style>
      <Style.Triggers>
        <DataTrigger Binding="{Binding MyValue}" Value="Value1">
          <Setter Property="TextBlock.Text" Value="value is 1!"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding MyValue}" Value="Value2">
          <Setter Property="TextBlock.Text" Value="value is 2!"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding MyValue}" Value="Value3">
          <Setter Property="TextBlock.Text" Value="value is 3!"/>
        </DataTrigger>
      </Style.Triggers>
    </Style>
  </TextBlock.Style>

Weingarten answered 6/12, 2010 at 13:59 Comment(0)
G
7

You have a number of options...

  • You could use a 'DataTrigger' by applying a 'Style' to your text block (use 'Style.Triggers').
  • You could create a converter that would convert your 'MyValue' into the appropriate text.
  • You could create another property on whatever your data source is (ideally this would be a ViewModel-style class) that reflects the text that should be displayed. Update the property from code and bind directly to it, instead of putting the logic in XAML.

Really I'd see this as a stylistic/design choice - none of the above are inherently better or worse, they're just suited to different scenarios.

Garcia answered 6/12, 2010 at 13:47 Comment(4)
Thanks. I've added some content to myc question.Caricature
Dan's answer is pretty much all you can do. I'd put it as correct answer but I can only upvote him :(Ulises
Please see my updated answer, perhaps you got something you can add.Caricature
You can use multiple setters in a DataTrigger, and there's a property on a setter called TargetName that might help you out (though I'm not sure if you can use it in this context).Garcia
M
5

Try to use the Switch Converter written by Josh:

SwitchConverter –

A "switch statement" for XAML - http://josheinstein.com/blog/index.php/2010/06/switchconverter-a-switch-statement-for-xaml/

Edit:

Here is code of SwitchConverter as Josh's site seems to be down -

/// <summary>
/// A converter that accepts <see cref="SwitchConverterCase"/>s and converts them to the 
/// Then property of the case.
/// </summary>
[ContentProperty("Cases")]
public class SwitchConverter : IValueConverter
{
    // Converter instances.
    List<SwitchConverterCase> _cases;

    #region Public Properties.
    /// <summary>
    /// Gets or sets an array of <see cref="SwitchConverterCase"/>s that this converter can use to produde values from.
    /// </summary>
    public List<SwitchConverterCase> Cases { get { return _cases; } set { _cases = value; } }
    #endregion
    #region Construction.
    /// <summary>
    /// Initializes a new instance of the <see cref="SwitchConverter"/> class.
    /// </summary>
    public SwitchConverter()
    {
        // Create the cases array.
        _cases = new List<SwitchConverterCase>();
    }
    #endregion

    /// <summary>
    /// Converts a value.
    /// </summary>
    /// <param name="value">The value produced by the binding source.</param>
    /// <param name="targetType">The type of the binding target property.</param>
    /// <param name="parameter">The converter parameter to use.</param>
    /// <param name="culture">The culture to use in the converter.</param>
    /// <returns>
    /// A converted value. If the method returns null, the valid null value is used.
    /// </returns>
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        // This will be the results of the operation.
        object results = null;

        // I'm only willing to convert SwitchConverterCases in this converter and no nulls!
        if (value == null) throw new ArgumentNullException("value");

        // I need to find out if the case that matches this value actually exists in this converters cases collection.
        if (_cases != null && _cases.Count > 0)
            for (int i = 0; i < _cases.Count; i++)
            {
                // Get a reference to this case.
                SwitchConverterCase targetCase = _cases[i];

                // Check to see if the value is the cases When parameter.
                if (value == targetCase || value.ToString().ToUpper() == targetCase.When.ToString().ToUpper())
                {
                    // We've got what we want, the results can now be set to the Then property
                    // of the case we're on.
                    results = targetCase.Then;

                    // All done, get out of the loop.
                    break;
                }
            }

        // return the results.
        return results;
    }

    /// <summary>
    /// Converts a value.
    /// </summary>
    /// <param name="value">The value that is produced by the binding target.</param>
    /// <param name="targetType">The type to convert to.</param>
    /// <param name="parameter">The converter parameter to use.</param>
    /// <param name="culture">The culture to use in the converter.</param>
    /// <returns>
    /// A converted value. If the method returns null, the valid null value is used.
    /// </returns>
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

/// <summary>
/// Represents a case for a switch converter.
/// </summary>
[ContentProperty("Then")]
public class SwitchConverterCase
{
    // case instances.
    string _when;
    object _then;

    #region Public Properties.
    /// <summary>
    /// Gets or sets the condition of the case.
    /// </summary>
    public string When { get { return _when; } set { _when = value; } }
    /// <summary>
    /// Gets or sets the results of this case when run through a <see cref="SwitchConverter"/>
    /// </summary>
    public object Then { get { return _then; } set { _then = value; } }
    #endregion
    #region Construction.
    /// <summary>
    /// Switches the converter.
    /// </summary>
    public SwitchConverterCase()
    {
    }
    /// <summary>
    /// Initializes a new instance of the <see cref="SwitchConverterCase"/> class.
    /// </summary>
    /// <param name="when">The condition of the case.</param>
    /// <param name="then">The results of this case when run through a <see cref="SwitchConverter"/>.</param>
    public SwitchConverterCase(string when, object then)
    {
        // Hook up the instances.
        this._then = then;
        this._when = when;
    }
    #endregion

    /// <summary>
    /// Returns a <see cref="System.String"/> that represents this instance.
    /// </summary>
    /// <returns>
    /// A <see cref="System.String"/> that represents this instance.
    /// </returns>
    public override string ToString()
    {
        return string.Format("When={0}; Then={1}", When.ToString(), Then.ToString());
    }
}
Monsour answered 6/12, 2010 at 17:56 Comment(2)
Link is dead but you can access the article hereMonsour
@Monsour could be nice to provide a xaml example?Injustice
N
1

I made an simplified, updated converter based on the accepted answer. It also allows a string comparison and a default case to be set:

[ContentProperty("Cases")]
public class SwitchConverter : IValueConverter
{
    public SwitchConverter()
    {
        Cases = new List<SwitchConverterCase>();
    }

    public List<SwitchConverterCase> Cases { get; set; }

    public StringComparison StringComparisonType { get; set; } = StringComparison.InvariantCulture;

    public object Default { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null || Cases == null)
        {
            return DependencyProperty.UnsetValue;
        }

        SwitchConverterCase result = Cases.FirstOrDefault(c => string.Equals(value.ToString(), c.When, StringComparisonType));
        return result != null ? result.Then : Default;
    }

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

The SwitchConverterCase class:

[ContentProperty("Then")]
public class SwitchConverterCase
{
    public SwitchConverterCase()
    {
    }

    public SwitchConverterCase(string when, object then)
    {
        When = when;
        Then = then;
    }

    public string When { get; set; }

    public object Then { get; set; }

    public override string ToString() => $"When={When}; Then={Then}";
}

Example usage:

<con:SwitchConverter x:Key="StyleConverter"
                     Default="{x:Static FontWeights.Normal}">
    <con:SwitchConverterCase When="pageHeader"
                             Then="{x:Static FontWeights.Bold}" />
    <con:SwitchConverterCase When="header"
                             Then="{x:Static FontWeights.SemiBold}" />
    <con:SwitchConverterCase When="smallText"
                             Then="{x:Static FontWeights.Light}" />
    <con:SwitchConverterCase When="tinyText"
                             Then="{x:Static FontWeights.Thin}" />
</con:SwitchConverter>

<TextBlock FontWeight="{Binding Style, Converter={StaticResource StyleConverter}}" />

Or inline:

<TextBlock>
    <TextBlock.FontWeight>
        <Binding Path="Style">
            <Binding.Converter>
                <con:SwitchConverter Default="{x:Static FontWeights.Normal}">
                    <con:SwitchConverterCase When="pageHeader"
                                             Then="{x:Static FontWeights.Bold}" />
                    <!-- etc -->
                </con:SwitchConverter>
            </Binding.Converter>
        </Binding>
    </TextBlock.FontWeight>
</TextBlock>
November answered 26/11, 2019 at 7:20 Comment(0)
G
0

You could just use a converter as Dan suggested...

public class MyValueConverter : IValueConverter
{
    public object Convert(
        object value, 
        Type targetType, 
        object parameter, 
        System.Globalization.CultureInfo culture)
    {
        string myValue = value.ToString();
        string output;

        switch(myValue)
        {
            case "Value1":
                output = "Value is 1";
                break;
            case "Value2":
                output = "Value is 2";
                break;
            case "Value3":
                output = "Value is 3";
                break;
            default:
                output = "Invalid Value";
                break;
        }

        return output;
    } 

    public object ConvertBack(
        object value, 
        Type targetType, 
        object parameter,
        System.Globalization.CultureInfo culture)
    {
        //Put reverse logic here
        throw new NotImplementedException();
    }
}

You would then use this from within your xaml...

<TextBlock 
    Text="{Binding MyValue, Converter={StaticResource MyValueConverter}}"/>
Gesticulatory answered 6/12, 2010 at 15:10 Comment(0)
R
0

Another solution to this problem is with a MultiBinding and a MultiValueConverter as follows:

<ContentPage>
    <ContentPage.Resources>
        <ResourceDictionary>
            <x:String x:Key="Value1">Value1</x:String>
            <x:String x:Key="Value2">Value2</x:String>
            <x:String x:Key="Value3">Value3</x:String>
            <x:String x:Key="Result1">value is 1!</x:String>
            <x:String x:Key="Result2">value is 2!</x:String>
            <x:String x:Key="Result3">value is 3!</x:String>
            <local:SelectMultiValueConverter x:Key="SelectMultiValueConverter" />
        </ResourceDictionary>
    </ContentPage.Resources>
    <ScrollView>
        </VerticalStackLayout>
            <Label>
                <Label.Text>
                    <MultiBinding Converter="{StaticResource SelectMultiValueConverter}">
                        <Binding Path={Binding MyValue}" />
                        <Binding Source="{StaticResource Value1}" />
                        <Binding Source="{StaticResource Result1}" />
                        <Binding Source="{StaticResource Value2}" />
                        <Binding Source="{StaticResource Result2}" />
                        <Binding Source="{StaticResource Value3}" />
                        <Binding Source="{StaticResource Result3}" />
                    </MultiBinding>
                </Label.Text>
            </Label>
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

With the following implementation which treats the input values as strings:

public class SelectMultiValueConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values.Length == 0) return null;
        int idx = 1;
        for (; (idx + 1 < values.Length; idx +=2)
            if (Compare(values[0], values[idx])) return values[idx + 1];
        return idx < values.Length ? values[idx] : null;
    }

    private bool Compare(object v1, object v2)
    {
        if (v1 == null && v2 == null) return true;
        if (v1 == null || v2 == null) return false;
        return v1.ToString() == v2.ToString();
    }

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

Using MultiBinding increases the flexibility of your input and return values. However, further work is required on the Compare() function to support other types, such as numbers and dates.

Reexamine answered 11/2 at 5:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.