Home > Software design >  Can <x:Double x:Key="fontsize">50</x:Double> be converted into pure attribute
Can <x:Double x:Key="fontsize">50</x:Double> be converted into pure attribute

Time:11-07

Motivation:

Hybrid syntax

<Label FontSize="50">Hello</Label>

can be converted into pure attribute syntax,

<Label Text="Hello" FontSize="50"/>

or pure element syntax

<Label>
    <Label.Text>Hello</Label.Text>
    <Label.FontSize>50</Label.FontSize>
</Label>

Question:

Can we also convert <x:Double x:Key="fontsize">50</x:Double> into pure attribute syntax and pure element syntax?

In my attempt, I cannot find the name of attribute associated with 50.

CodePudding user response:

To set values via attributes or tags, a type must have properties with public setters. And you can only assign values to these properties.
But to create a type as a value in the content, the type must have a converter from String.

Example:

    [TypeConverter(typeof(SomeStructConverter))]
    public struct SomeStruct
    {
        private int X;
        private int Y;
        public override string ToString() => $"({X}, {Y})";

        public static SomeStruct Parse(string text)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            var split = text.Split(" \t\r\n,()".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            if (split.Length != 1 && split.Length != 2)
                throw new ArgumentException(nameof(text));
            int y, x = int.Parse(split[0]);
            if (split.Length == 2)
                y = int.Parse(split[1]);
            else
                y = x;
            return new SomeStruct() { X = y, Y = x };
        }
        public static bool TryParse(string text, out SomeStruct some)
        {
            try
            {
                some = Parse(text);
                return true;
            }
            catch (Exception)
            {
                some = new SomeStruct();
                return false;
            }
        }

        public static implicit operator SomeStruct(int number)
        {
            return new SomeStruct() { X = number, Y = number };
        }
        public static implicit operator int(SomeStruct some)
        {
            if (some.X != some.Y)
                throw new InvalidCastException("X and Y must be the equals.");
            return some.X;
        }
    }
    public class SomeStructConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
        {
            if (sourceType == typeof(string) || sourceType == typeof(int) || sourceType == typeof(SomeStruct))
                return true;
            return base.CanConvertFrom(context, sourceType);
        }

        public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
        {
            if (destinationType == typeof(string) || destinationType == typeof(int) || destinationType == typeof(SomeStruct))
                return true;
            return base.CanConvertTo(context, destinationType);
        }

        public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
        {
            Type sourceType = value.GetType();
            if (sourceType == typeof(SomeStruct))
                return value;
            if (sourceType == typeof(int))
                return (SomeStruct)(int)value;
            if (sourceType == typeof(string))
                return SomeStruct.Parse((string)value);

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

        public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
        {
            if (value is not null)
            {
                if (destinationType == typeof(SomeStruct))
                    return value;
                if (destinationType == typeof(int))
                {
                    return (int)(SomeStruct)value;
                }
                if (destinationType == typeof(string))
                    return value.ToString();
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }

        public override bool IsValid(ITypeDescriptorContext? context, object? value)
        {
            if (value != null)
            {
                Type sourceType = value.GetType();
                if (sourceType == typeof(SomeStruct) || sourceType == typeof(int))
                    return true;
                if (sourceType == typeof(string))
                    return SomeStruct.TryParse((string)value, out _);
            }
            return base.IsValid(context, value);
        }
    }
<local:SomeStruct x:Key="some">123 -456</local:SomeStruct>

The Double type (like other numeric types) has no properties. Therefore, it is impossible to set a value through attributes or tags for a number.

CodePudding user response:

You already have accepted an answer but if you want something a little less wordy, you could try a MarkupExtension that provides the double - or other strongly typed value

public class ValExtension<T> : MarkupExtension where T : struct
{
    protected ValExtension(T val) => Value = val;
    private object Value { get; }
    public override object ProvideValue(IServiceProvider sp) { return Value; }
}
public class DoubleValExtension : ValExtension<double>
{
    public DoubleValExtension(double v) : base(v) {}
}

I find this useful in places like anywhere I don't want to rely on some implicit type conversion, like in the ConverterParameter for a value converter in binding. Since the ConverterParameter is simply an object, merely putting, say "0.3" in it will not result in the actual converter getting a double.

For example, suppose I had a converter that took a Category enum and an optional double parameter to adjust the computed value. I could use the markup extension to force XAML to pass a boxed double.

Opacity="{Binding SelectedItem.Category,
                  Converter={StaticResource ConvertCategoryToOpacity},
                  ConverterParameter={local:DoubleVal 0.3}

If I tried to do it like this, the converter would just get an unconverted string.

Opacity="{Binding SelectedItem.Category,
                  Converter={StaticResource ConvertCategoryToOpacity},
                  ConverterParameter='0.3'}"

That might be fine in some cases, but not if my converter is counting on checking the type of the parameter.

You can just derive from the generic for other POD types too.

public class IntValExtension  : ValExtension<int>  { public IntValExtension(int v)   : base(v) {} }
public class BoolValExtension : ValExtension<bool> { public BoolValExtension(bool v) : base(v) {} }
  • Related