Switch (Select) statement in Xaml Binding?

11,657

Solution 1

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());
    }
}

Solution 2

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>

Solution 3

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.

Solution 4

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>
Share:
11,657
Shimmy Weitzhandler
Author by

Shimmy Weitzhandler

Updated on June 04, 2022

Comments

  • Shimmy Weitzhandler
    Shimmy Weitzhandler almost 2 years

    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.

  • Shimmy Weitzhandler
    Shimmy Weitzhandler over 13 years
    Thanks. I've added some content to myc question.
  • David Brunelle
    David Brunelle over 13 years
    Dan's answer is pretty much all you can do. I'd put it as correct answer but I can only upvote him :(
  • Shimmy Weitzhandler
    Shimmy Weitzhandler over 13 years
    Please see my updated answer, perhaps you got something you can add.
  • Dan Puzey
    Dan Puzey over 13 years
    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).
  • akjoshi
    akjoshi over 10 years
    Link is dead but you can access the article here
  • J4N
    J4N about 5 years
    @akjoshi could be nice to provide a xaml example?