Setting property of class type with TypeConverter from Property Panel produces expanded XAML, not string

StackOverflow https://stackoverflow.com/questions/17064370

Question

I'm trying to create a User Control with a property whose type is a class I've defined. I'm using a TypeConverter to allow the property to be processed as a string. The application correctly handles reading XAML where the property is a string, but if the property is set to a string through the property panel, then the XAML contains an expanded syntax breaking out the user-defined class.

Concretely, since that was a bit hard to follow, I'm following this Microsoft tutorial. I have the following code as a result:

Complex.cs

namespace WpfApplication1
{
    [TypeConverter(typeof(ComplexTypeConverter))]
    public class Complex
    {
        private double m_real;
        private double m_imag;

        public Complex() { }
        public Complex(double r, double i)
        {
            m_real = r;
            m_imag = i;
        }

        public double Real
        {
            get { return m_real; }
            set { m_real = value; }
        }

        public double Imaginary
        {
            get { return m_imag; }
            set { m_imag = value; }
        }

        public override string ToString()
        {
            return String.Format("{0},{1}", this.m_real, this.m_imag);
        }

        public static Complex Parse(string complexNumber)
        {
            if (String.IsNullOrEmpty(complexNumber))
            {
                return new Complex();
            }

            // The parts array holds the real and 
            // imaginary parts of the object.
            string[] parts = complexNumber.Split(',');
            return new Complex(double.Parse(parts[0].Trim()), double.Parse(parts[1].Trim()));
        }
    }

    public class ComplexTypeConverter : TypeConverter
    {
        private static List<Complex> defaultValues = new List<Complex>();

        static ComplexTypeConverter()
        {
            defaultValues.Add(new Complex(0, 0));
            defaultValues.Add(new Complex(1, 1));
            defaultValues.Add(new Complex(-1, 1));
            defaultValues.Add(new Complex(-1, -1));
            defaultValues.Add(new Complex(1, -1));
        }

        // Override CanConvertFrom to return true for String-to-Complex conversions.
        public override bool CanConvertFrom(
            ITypeDescriptorContext context,
            Type sourceType)
        {
            if (sourceType == typeof(string))
            {
                return true;
            }

            return base.CanConvertFrom(context, sourceType);
        }

        // Override CanConvertTo to return true for Complex-to-String conversions.
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(string))
            {
                return true;
            }

            return base.CanConvertTo(context, destinationType);
        }

        // Override ConvertFrom to convert from a string to an instance of Complex.
        public override object ConvertFrom(
            ITypeDescriptorContext context,
            System.Globalization.CultureInfo culture,
            object value)
        {
            string text = value as string;

            if (text != null)
                            return Complex.Parse(text);                 

            return base.ConvertFrom(context, culture, value);
        }

        // Override ConvertTo to convert from an instance of Complex to string.
        public override object ConvertTo(
            ITypeDescriptorContext context,
            System.Globalization.CultureInfo culture,
            object value,
            Type destinationType)
        {
            if (destinationType == null)
            {
                throw new ArgumentNullException("destinationType");
            }

            //Convert Complex to a string in a standard format.
            Complex c = value as Complex;

            if (c != null && this.CanConvertTo(context, destinationType))
            {
                return c.ToString();
            }

            return base.ConvertTo(context, culture, value, destinationType);
        }

        public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
        {
            return true;
        }

        public override TypeConverter.StandardValuesCollection GetStandardValues(
            ITypeDescriptorContext context)
        {
            StandardValuesCollection svc = new StandardValuesCollection(defaultValues);
            return svc;
        }
    }
}

ComplexNumberControl.xaml.cs

namespace WpfApplication1
{
    public partial class ComplexNumberControl : UserControl
    {
        public ComplexNumberControl()
        {
            InitializeComponent();
        }

        public Complex ComplexNumber
        {
            get
            {
                return (Complex)this.GetValue(ComplexNumberProperty);
            }

            set
            {
                this.SetValue(ComplexNumberProperty, value);
            }
        }

        public static readonly DependencyProperty ComplexNumberProperty = DependencyProperty.Register(
          "ComplexNumber",
          typeof(Complex),
          typeof(ComplexNumberControl),
          new PropertyMetadata(new Complex()));
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:WpfApplication1">
    <Grid>
        <my:ComplexNumberControl HorizontalAlignment="Left" Margin="88,78,0,0" x:Name="complexNumberControl1" VerticalAlignment="Top" />
    </Grid>
</Window>

I can add ComplexNumber="0,0" to the ComplexNumberControl with no error (and I know, from more complicated assemblies, that the property is correctly handled as the Complex number 0 + 0i). However, if I edit ComplexNumber in the property panel, the XAML changes to:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:WpfApplication1">
    <Grid>
        <my:ComplexNumberControl HorizontalAlignment="Left" Margin="88,78,0,0" x:Name="complexNumberControl1" VerticalAlignment="Top">
            <my:ComplexNumberControl.ComplexNumber>
                <my:Complex Imaginary="-1" Real="1" />
            </my:ComplexNumberControl.ComplexNumber>
        </my:ComplexNumberControl>
    </Grid>
</Window>

How can I ensure the generated XAML simply reads ComplexNumber="1,-1", instead of the verbose ComplexNumberControl.ComplexNumber construct?

Was it helpful?

Solution

You almost there and needs only two minor adjustments on Complex class to make it work as you expect:

1) Remove default public constructor:

public Complex() { } // <- delete this line

2) Add magical DesignerSerializationVisibility attribute to Real and Imaginary properties (or in general - to all properties with public setter):

[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public double Real
{
    get { return m_real; }
    set { m_real = value; }
}

[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public double Imaginary
{
    get { return m_imag; }
    set { m_imag = value; }
}

Hope this helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top