Skip to content

Instantly share code, notes, and snippets.

@jnm2
Last active May 20, 2019 15:45
Show Gist options
  • Select an option

  • Save jnm2/aa79720170f91ec3cd515c657da4347c to your computer and use it in GitHub Desktop.

Select an option

Save jnm2/aa79720170f91ec3cd515c657da4347c to your computer and use it in GitHub Desktop.
Adds an easy IsNullable property to the End User Designer property grid to toggle the type between non-nullable and nullable
/* Required assembly references:
- DevExpress.Data
- DevExpress.Printing.Core
- DevExpress.Utils.UI */
using System;
using System.ComponentModel;
using System.Globalization;
using DevExpress.XtraReports.Design;
using DevExpress.XtraReports.Native;
using DevExpress.XtraReports.Parameters;
public static class NullableParametersFeature
{
public static void Apply()
{
CustomTypeDescriptionProvider.Install(typeof(Parameter), (w, i) => new ParameterTypeDescriptor(w));
}
private sealed class ParameterTypeDescriptor : CustomTypeDescriptor
{
public ParameterTypeDescriptor(ICustomTypeDescriptor wrapped)
: base(wrapped)
{
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = base.GetProperties(attributes);
var r = new PropertyDescriptor[properties.Count + 1];
properties.CopyTo(r, 0);
r[r.Length - 1] = IsNullablePropertyDescriptor.Instance;
var typeProperty = properties[nameof(Parameter.Type)];
r[Array.IndexOf(r, typeProperty)] = new TypePropertyDescriptor(typeProperty);
return new PropertyDescriptorCollection(r, true);
}
private sealed class IsNullablePropertyDescriptor : PropertyDescriptor
{
public static readonly IsNullablePropertyDescriptor Instance = new IsNullablePropertyDescriptor();
private IsNullablePropertyDescriptor()
: base("IsNullable", new Attribute[] { BrowsableAttribute.Yes, new DisplayNameAttribute("Is Nullable"), new CategoryAttribute("Data") })
{
}
public override bool CanResetValue(object component)
{
var type = (component as Parameter)?.Type;
return type != null && (!type.IsValueType || Nullable.GetUnderlyingType(type) != null);
}
public override object GetValue(object component)
{
var type = (component as Parameter)?.Type;
if (type == null) return null;
return !type.IsValueType || Nullable.GetUnderlyingType(type) != null;
}
public override void ResetValue(object component) => SetValue(component, false);
public override void SetValue(object component, object value)
{
if (!(value is bool)) return;
var parameter = component as Parameter;
var type = parameter?.Type;
if (type == null || !type.IsValueType) return;
if ((bool)value)
{
if (Nullable.GetUnderlyingType(type) == null)
parameter.Type = typeof(Nullable<>).MakeGenericType(type);
}
else
{
if (Nullable.GetUnderlyingType(type) != null)
parameter.Type = Nullable.GetUnderlyingType(type);
}
}
public override bool ShouldSerializeValue(object component) => false;
public override Type ComponentType => typeof(Parameter);
public override bool IsReadOnly => false;
public override Type PropertyType => typeof(bool);
}
private sealed class TypePropertyDescriptor : PropertyDescriptorWrapper
{
public TypePropertyDescriptor(PropertyDescriptor wrapped) : base(wrapped, new Attribute[]
{
new TypeConverterAttribute(typeof(ParameterTypeConverterEx))
})
{
}
private sealed class ParameterTypeConverterEx : ParameterTypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var makeNullable = false;
var str = value as string;
if (str != null)
{
if (str.EndsWith(" (nullable)"))
{
value = str.Substring(0, str.Length - 11);
makeNullable = true;
}
else if (str.EndsWith(", nullable)"))
{
value = str.Substring(0, str.Length - 11) + ')';
makeNullable = true;
}
}
var r = (Type)base.ConvertFrom(context, culture, value);
return makeNullable && r?.IsValueType == true ? typeof(Nullable<>).MakeGenericType(r) : r;
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
var type = value as Type;
if (type != null)
{
type = Nullable.GetUnderlyingType(type);
if (type != null)
{
var rString = base.ConvertTo(context, culture, type, destinationType) as string;
if (rString != null)
{
rString = rString.Trim();
return rString.EndsWith(")")
? rString.Substring(0, rString.Length - 1) + ", nullable)"
: rString + " (nullable)";
}
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
var r = base.GetStandardValues(context);
if (r != null && context.Instance != null && IsNullablePropertyDescriptor.Instance.GetValue(context.Instance) as bool? == true)
{
var newValues = new object[r.Count];
r.CopyTo(newValues, 0);
for (var i = 0; i < newValues.Length; i++)
{
var type = newValues[i] as Type;
if (type?.IsValueType == true) newValues[i] = typeof(Nullable<>).MakeGenericType(type);
}
return new StandardValuesCollection(newValues);
}
return r;
}
}
}
}
}
public class CustomTypeDescriptionProvider : TypeDescriptionProvider
{
public delegate ICustomTypeDescriptor GetTypeDescriptorDelegate(ICustomTypeDescriptor wrapped, object instance);
private readonly GetTypeDescriptorDelegate getTypeDescriptor;
public CustomTypeDescriptionProvider(TypeDescriptionProvider wrapped, GetTypeDescriptorDelegate getTypeDescriptor)
: base(wrapped)
{
this.getTypeDescriptor = getTypeDescriptor;
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
return getTypeDescriptor(base.GetTypeDescriptor(objectType, instance), instance);
}
public static void Install(Type componentType, GetTypeDescriptorDelegate getTypeDescriptor)
{
TypeDescriptor.AddProvider(new CustomTypeDescriptionProvider(TypeDescriptor.GetProvider(componentType), getTypeDescriptor), componentType);
}
}
@HaidarZ
Copy link

HaidarZ commented Feb 10, 2018

How do we use this solution ? Moreover there's missing references.

@jnm2
Copy link
Author

jnm2 commented Apr 2, 2018

@HaidarZ Sorry, gist authors are not notified when people comment. You use it by calling NullableParametersFeature.Apply(); once in your application (technically, once per AppDomain). I updated the gist to include the missing using directive and list the assembly references needed in your csproj.

@jnm2
Copy link
Author

jnm2 commented May 20, 2019

@HaidarZ GitHub just enabled notifications for gist comments!

@HaidarZ
Copy link

HaidarZ commented May 20, 2019

Thanks @jnm2 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment