C# allows the following functionality to display a user-friendly version of enums. The type converter takes the description attribute and uses it to generate a string. Can this be done in C++/CLI? From what I'm seeing, a 'public enum class' cannot have attributes on the enum members. This means I can't define the description attribute content for each enum. How can the friendly names be defined?
[TypeConverter(typeof(EnumDescriptionConverter))]
public enum MyEnum
{
[Description("Item1")]
Item1,
[Description("Item2")]
Item2,
}
class EnumDescriptionConverter : EnumConverter
{
private Type _enumType;
public EnumDescriptionConverter(Type type)
: base(type)
{
_enumType = type;
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destType)
{
return destType == typeof(string);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destType)
{
String ReturnString = "";
if (_enumType.GetCustomAttributes<FlagsAttribute>().Any())
{
foreach (var val in EnumExtensions.GetIndividualFlags((Enum)value))
{
FieldInfo fi = _enumType.GetField(Enum.GetName(_enumType, val));
DescriptionAttribute dna = (DescriptionAttribute)Attribute.GetCustomAttribute(fi, typeof(DescriptionAttribute));
if (ReturnString != "")
ReturnString += " | ";
if (dna != null)
ReturnString += dna.Description;
else
ReturnString += val.ToString();
}
}
else
{
FieldInfo fi = _enumType.GetField(Enum.GetName(_enumType, value));
DescriptionAttribute dna = (DescriptionAttribute)Attribute.GetCustomAttribute(fi, typeof(DescriptionAttribute));
if (ReturnString != "")
ReturnString += " | ";
if (dna != null)
ReturnString += dna.Description;
else
ReturnString += value.ToString();
}
return ReturnString;
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type srcType)
{
return srcType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
foreach (FieldInfo fi in _enumType.GetFields())
{
DescriptionAttribute dna =
(DescriptionAttribute)Attribute.GetCustomAttribute(
fi, typeof(DescriptionAttribute));
if ((dna != null) && ((string)value == dna.Description))
return Enum.Parse(_enumType, fi.Name);
}
return Enum.Parse(_enumType, (string)value);
}
}
You've broken the cardinal rule of Stack Overflow questions: You didn't actually include real source code or the exact error message.
This code compiles just fine for me in Visual Studio 2013 in a brand new C++/CLI Console Application project:
using System::ComponentModel::DescriptionAttribute;
public enum class MyEnum
{
[Description("Item1")]
Item1,
[Description("Item2")]
Item2,
};
After researching this more, I resorted to the following type converter. It simply replaces an underscore in the enum member with a space. It can also handle flags.
ref class EnumDescriptionConverter : public System::ComponentModel::EnumConverter
{
private:
System::Type^ _enumType;
public:
EnumDescriptionConverter(System::Type^ type) : System::ComponentModel::EnumConverter(type)
{
_enumType = type;
}
bool CanConvertTo(System::ComponentModel::ITypeDescriptorContext^ context, System::Type^ destType)override
{
return destType == System::String::typeid;
}
System::Object^ ConvertTo(System::ComponentModel::ITypeDescriptorContext^ context, System::Globalization::CultureInfo^ culture, System::Object^ value, System::Type^ destType)override
{
return value->ToString()->Replace("_", " ");
}
bool CanConvertFrom(System::ComponentModel::ITypeDescriptorContext^ context, System::Type^ srcType)override
{
return srcType == System::String::typeid;
}
System::Object^ ConvertFrom(System::ComponentModel::ITypeDescriptorContext^ context, System::Globalization::CultureInfo^ culture, System::Object^ value)override
{
return System::Enum::Parse(_enumType, ((System::String^)value)->Replace(" ", "_")->Replace(",_", ", "));
}
};
Enums are defined like this:
[System::ComponentModel::TypeConverter(typeof(EnumDescriptionConverter))]
public enum class MyEnum
{
Item_1, // Appears as 'Item 1' when data bound or in a property grid
Item_2 // Appears as 'Item 2' when data bound or in a property grid
}
Related
Currently, it looks like:
[EmailAddress]
public string Email { get; set; }
Currently, we are using [EmailAddress] validator data annotation for a field, but also need to allow "" (empty string) as well. If we do not allow for "" (empty string), then we will break old versions of our front-end apps.
How can I use data annotations to allow for an email address, or an empty string, for a given data field?
I understand that the front-end apps should be sending null instead of "" (empty string), and then I could just keep the field as optional, but it is too late, as old versions of the apps cannot be eradicated from the wild, and need to continue to work.
Thank you!
You could refer to the EmailAddress source code and try to custom EmailAddress DataTypeAttribute like below:
public class CustomEmailAddress: DataTypeAttribute
{
const string pattern = #"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))#((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$";
const RegexOptions options = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture;
private static Regex _regex = new Regex(pattern, options);
public CustomEmailAddress()
: base(DataType.EmailAddress)
{
}
public override bool IsValid(object value)
{
if (String.IsNullOrEmpty(value.ToString()))
{
return true;
}
string valueAsString = value as string;
// Use RegEx implementation if it has been created, otherwise use a non RegEx version.
if (_regex != null)
{
return valueAsString != null && _regex.Match(valueAsString).Length > 0;
}
else
{
int atCount = 0;
foreach (char c in valueAsString)
{
if (c == '#')
{
atCount++;
}
}
return (valueAsString != null
&& atCount == 1
&& valueAsString[0] != '#'
&& valueAsString[valueAsString.Length - 1] != '#');
}
}
}
Result:
What I ended up using, which is surprisingly compact and easy:
public class FooBar : IValidatableObject
{
public string Email { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var checker = new EmailAddressAttribute();
if (!checker.IsValid(Email) && Email != "")
{
yield return new ValidationResult("Some error message");
}
}
}
It is also very easy to test:
[Fact]
public void ShouldAcceptEmailOrEmptyString()
{
var request = new FooBar()
{
Email = "",
}
Assert.Empty(request.Validate(new ValidationContext(request)));
}
I'm struggling a bit with UI Automation for an WindowsStore app. I get an UITestControlNotAvailableException as soon as I implement IValueProvider. If I remove the implementation it works.
Microsoft.VisualStudio.TestTools.UITest.Extension.UITestControlNotAvailableException (The control is not available or not valid.)
InnerException: Points to a call to testControl.Name (Microsoft.VisualStudio.TestTools.UITesting.UITestControl.get_Name())
InnerException: An event was unable to invoke any of the subscribers (Exception from HRESULT: 0x80040201)
The StackTrace looks like this:
at Microsoft.VisualStudio.TestTools.UITest.Extension.Uia.UiaUtility.MapAndThrowException(Exception e, IUITechnologyElement element, Boolean useRethrowException)
at Microsoft.VisualStudio.TestTools.UITest.Extension.Uia.UiaUtility.GetAutomationPropertyValue[T](AutomationElement element, AutomationProperty property)
at Microsoft.VisualStudio.TestTools.UITest.Extension.Uia.UiaElement.GetAutomationPropertyValue[T](AutomationProperty automationProperty)
at Microsoft.VisualStudio.TestTools.UITest.Extension.Uia.UiaElement.get_NativeWindowHandle()
at Microsoft.VisualStudio.TestTools.UITest.Extension.Uia.UiaElement.get_WindowHandle()
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.get_IsRefetchRequired()
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.EnsureValid(Boolean waitForReady, Boolean refetch)
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.GetPropertyValuePrivate(String propertyName)
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.GetPropertyPrivate(String propertyName)
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.GetPropertyOfType[T](String propertyName)
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.get_Name()
at UITest.Framework.Windows.TestGround.GetElementString(UITestControl element, Int32 level) in ...
at UITest.Framework.Windows.TestGround.WalkTree(UITestControl element, Int32 level) in ...
at UITest.Framework.Windows.TestGround.WalkTree(UITestControl element, Int32 level) ...
at UITest.Framework.Windows.TestGround.WalkTree(UITestControl element, Int32 level) ...
at UITest.Framework.Windows.TestGround.WalkTree(UITestControl element, Int32 level) ...
at UITest.Framework.Windows.TestGround.PrintTree(Modes mode, XamlWindow window) in ...
On the app side I use this code to get information about the control:
public class CanvasAP : FrameworkElementAutomationPeer, IValueProvider
{
public CanvasAP(Windows.UI.Xaml.Controls.Canvas owner) : base(owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Custom;
}
protected override string GetClassNameCore()
{
return "Canvas";
}
protected override object GetPatternCore(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.Value)
{
return this;
}
return base.GetPattern(patternInterface);
}
#region Implementation of IValueProvider
bool IValueProvider.IsReadOnly => true;
string IValueProvider.Value
{
get
{
var str = "Test";
var owner = (Windows.UI.Xaml.Controls.Canvas)Owner;
foreach (var child in owner.Children)
{
str += $"{child.GetType()}";
}
return str;
}
}
void IValueProvider.SetValue(string value)
{
}
#endregion
}
On the UI Automation client side I use this code to get information about the control:
private static string GetElementString(UITestControl element, Int32 level = 0)
{
var xaml = element as XamlControl;
var str = "";
for (var i = 0; i < level; i++)
str += " ";
str += $"{element.ControlType} {element.ClassName} {element.Name} {xaml?.AutomationId ?? ""}\n";
return str;
}
Found the bug that I had made - it was in calling base.GetPattern instead of base.GetPatternCore.
protected override object GetPatternCore(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.Value)
return this;
return base.GetPatternCore(patternInterface);
}
Trying to save selections from a CheckBoxList as a comma-separated list (string) in DB (one or more choices selected). I am using a proxy in order to save as a string because otherwise I'd have to create separate tables in the DB for a relation - the work is not worth it for this simple scenario and I was hoping that I could just convert it to a string and avoid that.
The CheckBoxList uses an enum for it's choices:
public enum Selection
{
Selection1,
Selection2,
Selection3
}
Not to be convoluted, but I use [Display(Name="Choice 1")] and an extension class to display something friendly on the UI. Not sure if I can save that string instead of just the enum, although I think if I save as enum it's not a big deal for me to "display" the friendly string on UI on some confirmation page.
This is the "Record" class that saves a string in the DB:
public virtual string MyCheckBox { get; set; }
This is the "Proxy", which is some sample I found but not directly dealing with enum, and which uses IEnumerable<string> (or should it be IEnumerable<Selection>?):
public IEnumerable<string> MyCheckBox
{
get
{
if (String.IsNullOrWhiteSpace(Record.MyCheckBox)) return new string[] { };
return Record
.MyCheckBox
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(r => r.Trim())
.Where(r => !String.IsNullOrEmpty(r));
}
set
{
Record.MyCheckBox = value == null ? null : String.Join(",", value);
}
}
To save in the DB, I am trying to do this in a create class:
proxy.MyCheckBox = record.MyCheckBox; //getting error here
but am getting the error:
Cannot implicitly convert 'string' to System.Collections.Generic.IEnumerable'
I don't know, if it's possible or better, to use Parse or ToString from the API for enum values.
I know that doing something like this will store whatever I put in the ("") into the DB, so it's just a matter of figuring out how to overcome the error (or, if there is an alternative):
proxy.MyCheckBox = new[] {"foo", "bar"};
I am not good with this stuff and have just been digging and digging to come up with a solution. Any help is much appreciated.
You can accomplish this using a custom user type. The example below uses an ISet<string> on the class and stores the values as a delimited string.
[Serializable]
public class CommaDelimitedSet : IUserType
{
const string delimiter = ",";
#region IUserType Members
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
var xSet = x as ISet<string>;
var ySet = y as ISet<string>;
if (xSet == null || ySet == null)
{
return false;
}
// compare set contents
return xSet.Except(ySet).Count() == 0 && ySet.Except(xSet).Count() == 0;
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var outValue = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
if (string.IsNullOrEmpty(outValue))
{
return new HashSet<string>();
}
else
{
var splitArray = outValue.Split(new[] {Delimiter}, StringSplitOptions.RemoveEmptyEntries);
return new HashSet<string>(splitArray);
}
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var inValue = value as ISet<string>;
object setValue = inValue == null ? null : string.Join(Delimiter, inValue);
NHibernateUtil.String.NullSafeSet(cmd, setValue, index);
}
public object DeepCopy(object value)
{
// return new ISet so that Equals can work
// see http://www.mail-archive.com/nhusers#googlegroups.com/msg11054.html
var set = value as ISet<string>;
if (set == null)
{
return null;
}
return new HashSet<string>(set);
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get { return new[] {new SqlType(DbType.String)}; }
}
public Type ReturnedType
{
get { return typeof(ISet<string>); }
}
public bool IsMutable
{
get { return false; }
}
#endregion
}
Usage in mapping file:
Map(x => x.CheckboxValues.CustomType<CommaDelimitedSet>();
I have a type which has a default sort order as it implements IComparable<T> and IComparable. I'm not getting the results I expect from LINQ , basically it looks as if the IComparable<T> which the type implements is not being applied.
I thought I would get the result I want with an expression in the form:
var result = MyEnumerable<T>.OrderBy(r => r);
where T itself implements IComparable<T>. It's not happening.
I can see related questions where specific IComparable<T> classes are specified for the sort, but I can't find one which uses the default IComparable<T> implemented by T itself.
My syntax is clearly incorrect. What is the correct syntax please?
Thanks in advance.
OrderBy uses the default comparer Comparer<T>.Default which in turn will default to use the IComparable<T> implementation for T, or the non-generic IComparable if the former does not exist.
This code works:
public class Program
{
static void Main(string[] args)
{
var list = new List<Stuff>
{
new Stuff("one"),
new Stuff("two"),
new Stuff("three"),
new Stuff("four")
};
var sorted = list.OrderBy(x => x);
foreach (var stuff in sorted)
{
Console.Out.WriteLine(stuff.Name);
}
}
}
public class Stuff : IComparable<Stuff>
{
public string Name { get; set; }
public Stuff(string name)
{
Name = name;
}
public int CompareTo(Stuff other)
{
return String.CompareOrdinal(Name, other.Name);
}
}
public static class GenericSorter
{
public static IOrderedEnumerable<T> Sort<T>(IEnumerable<T> toSort, Dictionary<string, SortingOrder> sortOptions)
{
IOrderedEnumerable<T> orderedList = null;
foreach (KeyValuePair<string, SortingOrder> entry in sortOptions)
{
if (orderedList != null)
{
if (entry.Value == SortingOrder.Ascending)
{
orderedList = orderedList.ApplyOrder<T>(entry.Key, "ThenBy");
}
else
{
orderedList = orderedList.ApplyOrder<T>(entry.Key, "ThenByDescending");
}
}
else
{
if (entry.Value == SortingOrder.Ascending)
{
orderedList = toSort.ApplyOrder<T>(entry.Key, "OrderBy");
}
else
{
orderedList = toSort.ApplyOrder<T>(entry.Key, "OrderByDescending");
}
}
}
return orderedList;
}
private static IOrderedEnumerable<T> ApplyOrder<T>(this IEnumerable<T> source, string property, string methodName)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
Expression expr = param;
foreach (string prop in property.Split('.'))
{
expr = Expression.PropertyOrField(expr, prop);
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), expr.Type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, param);
MethodInfo mi = typeof(Enumerable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), expr.Type);
return (IOrderedEnumerable<T>)mi.Invoke(null, new object[] { source, lambda.Compile() });
}
}
I'm creating MaskedTextBox in WP7.1 In that i want to set the Prompt char as '#'.
For ex: ##-##-#### ##:##. While running this i got an error message as
Failed to create a 'System.Char' from the text '#'
Please anyone can help me....
Please, post more details regarding your exception.
Also, consider using standard PasswordBox with PasswordChar property:
<PasswordBox PasswordChar="#"/>
UPDATE:
Use this char converter on your PromptChar property:
public class CharTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
return true;
return base.CanConvertTo(context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
return '_';
if (value is string)
{
string s = (string)value;
if (s.Length == 0 || s.Length > 1)
return '_';
return s[0];
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value != null && !(value is char))
throw new ArgumentException("Invalid prompt character", "value");
if (destinationType == typeof(string))
{
if (value == null)
return String.Empty;
char promptChar = (char)value;
return promptChar.ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
Usage:
[TypeConverter(typeof(CharTypeConverter))]
public char PromptChar
{
get { ... }
set { ... }
}