一种用XAML写Data Converter的方式

在WPF程序中,数据绑定是非常常用的手段。伴随着数据绑定,我们通常还需要编写一些Converter。而编写Converter是一件非常枯燥的事情,并且大量的converter不容易组织和维护。

今天在网上发现了一篇文章SwitchConverter – A "switch statement" for XAML,它可以通过XAML的方式编写一些类似switch-case方式的converter,十分简洁明了。例如,对如如下的数据绑定转换:

    

可以直接在XAML中通过如下方式写converter:

<Grid>
    <Grid.Resources>
        <e:SwitchConverter x:Key="WeatherIcons">
            <e:SwitchCase When="Sunny" Then="Sunny.png" />
            <e:SwitchCase When="Cloudy" Then="Cloudy.png" />
            <e:SwitchCase When="Rain" Then="Rain.png" />
            <e:SwitchCase When="Snow" Then="Snow.png" />
        </e:SwitchConverter>
    </Grid.Resources>
    <Image Source="{Binding Condition, Converter={StaticResource WeatherIcons}}" />
</Grid>

原文已经附上了代码的工程,但由于担心哪天方校长抖威风而导致该文章失效,这里将其转录了下来,一共三个文件:

SwitchCase.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Windows;
using System.Windows.Markup;

namespace SwitchConverterDemo
{

    /// <summary>
    /// An individual case in the switch statement.
    /// </summary>
    [ContentProperty( "Then" )]
    public sealed class SwitchCase : DependencyObject
    {

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="T:SwitchCase"/> class.
        /// </summary>
        public SwitchCase( )
        {

        }

        #endregion

        #region Properties

        /// <summary>
        /// Dependency property for the <see cref="P:When"/> property.
        /// </summary>
        public static readonly DependencyProperty WhenProperty = DependencyProperty.Register( "When", typeof( object ), typeof( SwitchCase ), new PropertyMetadata( default( object ) ) );

        /// <summary>
        /// The value to match against the input value.
        /// </summary>
        public object When
        {
            get
            {
                return (object)GetValue( WhenProperty );
            }
            set
            {
                SetValue( WhenProperty, value );
            }
        }

        /// <summary>
        /// Dependency property for the <see cref="P:Then"/> property.
        /// </summary>
        public static readonly DependencyProperty ThenProperty = DependencyProperty.Register( "Then", typeof( object ), typeof( SwitchCase ), new PropertyMetadata( default( object ) ) );

        /// <summary>
        /// The output value to use if the current case matches.
        /// </summary>
        public object Then
        {
            get
            {
                return (object)GetValue( ThenProperty );
            }
            set
            {
                SetValue( ThenProperty, value );
            }
        }

        #endregion

    }   // class

}   // namespace
View Code

SwitchCaseCollection.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Linq;

namespace SwitchConverterDemo
{

    /// <summary>
    /// A collection of switch cases.
    /// </summary>
    public sealed class SwitchCaseCollection : Collection<SwitchCase>
    {

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="T:SwitchCaseCollection"/> class.
        /// </summary>
        internal SwitchCaseCollection( )
        {

        }

        #endregion

        #region Methods

        /// <summary>
        /// Adds a new case to the collection.
        /// </summary>
        /// <param name="when">The value to compare against the input.</param>
        /// <param name="then">The output value to use if the case matches.</param>
        public void Add( object when, object then )
        {
            Add(
                new SwitchCase {
                    When = when,
                    Then = then
                }
            );
        }

        #endregion

    }   // class

}   // namespace
View Code

SwitchConverter.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;

namespace SwitchConverterDemo
{

    /// <summary>
    /// Produces an output value based upon a collection of case statements.
    /// </summary>
    [ContentProperty( "Cases" )]
    public class SwitchConverter : IValueConverter
    {

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="T:SwitchConverter"/> class.
        /// </summary>
        public SwitchConverter( )
            : this( new SwitchCaseCollection( ) )
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:SwitchConverter"/> class.
        /// </summary>
        /// <param name="cases">The case collection.</param>
        internal SwitchConverter( SwitchCaseCollection cases )
        {

            Contract.Requires( cases != null );

            Cases = cases;
            StringComparison = StringComparison.OrdinalIgnoreCase;

        }

        #endregion

        #region Properties

        /// <summary>
        /// Holds a collection of switch cases that determine which output
        /// value will be produced for a given input value.
        /// </summary>
        public SwitchCaseCollection Cases
        {
            get;
            private set;
        }

        /// <summary>
        /// Specifies the type of comparison performed when comparing the input
        /// value against a case.
        /// </summary>
        public StringComparison StringComparison
        {
            get;
            set;
        }

        /// <summary>
        /// An optional value that will be output if none of the cases match.
        /// </summary>
        public object Else
        {
            get;
            set;
        }

        #endregion

        #region Methods

        /// <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, CultureInfo culture )
        {

            if ( value == null ) {
                
                // Special case for null
                // Null input can only equal null, no convert necessary
                
                return Cases.FirstOrDefault( x => x.When == null ) ?? Else;

            }

            foreach ( var c in Cases.Where( x => x.When != null ) ) {

                // Special case for string to string comparison
                if ( value is string && c.When is string ) {
                    if ( String.Equals( (string)value, (string)c.When, StringComparison ) ) {
                        return c.Then;
                    }
                }

                object when = c.When;

                // Normalize the types using IConvertible if possible
                if ( TryConvert( culture, value, ref when ) ) {
                    if ( value.Equals( when ) ) {
                        return c.Then;
                    }
                }

            }

            return Else;

        }

        /// <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, CultureInfo culture )
        {
            throw new NotSupportedException( );
        }

        /// <summary>
        /// Attempts to use the IConvertible interface to convert <paramref name="value2"/> into a type
        /// compatible with <paramref name="value1"/>.
        /// </summary>
        /// <param name="culture">The culture.</param>
        /// <param name="value1">The input value.</param>
        /// <param name="value2">The case value.</param>
        /// <returns>True if conversion was performed, otherwise false.</returns>
        private static bool TryConvert( CultureInfo culture, object value1, ref object value2 )
        {

            Type type1 = value1.GetType( );
            Type type2 = value2.GetType( );

            if ( type1 == type2 ) {
                return true;
            }

            if ( type1.IsEnum ) {
                value2 = Enum.Parse( type1, value2.ToString( ), true );
                return true;
            }

            var convertible1 = value1 as IConvertible;
            var convertible2 = value2 as IConvertible;

            if ( convertible1 != null && convertible2 != null ) {
                value2 = System.Convert.ChangeType( value2, type1, culture );
                return true;
            }

            return false;

        }

        #endregion

    }   // class

}   // namespace
View Code

这种绑定的方式非常简洁有效,但也有限制,只能处理简单的switch-case形式的关联,并且不能有转换逻辑。不过已经可以替换很大一部分Converter了(非常典型的应用就是这种枚举到图片的转换)。

另外,网上也有一些开源库,实现了一些常见的通用Converter。例如:http://wpfconverters.codeplex.com/。在自己编写Converter之前,不妨先使用这些通用的Converter。

原文地址:https://www.cnblogs.com/TianFang/p/3238996.html