I've created a custom converter that performs converting of values based on configured mapping. It looks like below
public class UniversalConverter : List<ConverterItem>, IValueConverter
{
private bool useDefaultValue;
private object defaultValue;
public object DefaultValue
{
get { return defaultValue; }
set
{
defaultValue = value;
useDefaultValue = true;
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
foreach (var item in this)
if (Equals(item.From, value))
return item.To;
if (useDefaultValue)
return DefaultValue;
throw new ConversionException(string.Format("Value {0} can't be converted and default value is not allowed", value));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
foreach (var item in this)
if (Equals(item.To, value))
return item.From;
throw new ConversionException(string.Format("Value {0} can't be converted back", value));
}
}
public class ConverterItem
{
public object From { get; set; }
public object To { get; set; }
}
public class ConversionException : Exception
{
public ConversionException() { }
public ConversionException(string message) : base(message) { }
}
Sample XAML is below
<core:UniversalConverter x:Key="ItemCountToVisiblityConverter" DefaultValue="{x:Static Visibility.Collapsed}">
<core:ConverterItem To="{x:Static Visibility.Visible}">
<core:ConverterItem.From>
<system:Int32>0</system:Int32>
</core:ConverterItem.From>
</core:ConverterItem>
</core:UniversalConverter>
Now everything builds and works fine, but if I use it XAML Visual Studio underscores the whole file with curvy blue lines and shows two kind of mistakes:
1) If converter is put into ResourceDictionary AND is assigned an x:Key attribute it shows Missing key value on 'UniversalConverter' object
2) If I assign DefaultValue property any value (e.g {x:Null}) the message is XAML Node Stream: Missing EndMember for 'StuffLib.UniversalConverter.{http://schemas.microsoft.com/winfx/2006/xaml}_Items' before StartMember 'StuffLib.UniversalConverter.DefaultValue'
What is the reason for those messages? I can live with them but they hide all other compiler and ReSharper markings
Don't inherit from list, just create Items property in your converter:
[ContentProperty("Items")]
public class UniversalConverter : IValueConverter
{
public ConverterItem[] Items { get; set; }
public object DefaultValue { get; set; }
//all other stuff goes here
}
and xaml:
<l:UniversalConverter x:Key="MyConverter">
<x:Array Type="l:ConverterItem">
<l:ConverterItem From="..." To="..." />
Based on answer given by #Leiro
[ContentProperty("Items")]
public class UniversalConverter : IValueConverter
{
public UniversalConverter()
{
Items = new List<ConverterItem>();
}
public List<ConverterItem> Items { get; private set; }
//All other logic is the same
}
Note that this way you won't need to wrap items in collection in XAML
Resulting XAML
<core:UniversalConverter x:Key="ItemCountToVisiblityConverter" DefaultValue="{x:Static Visibility.Collapsed}">
<core:ConverterItem To="{x:Static Visibility.Visible}">
<core:ConverterItem.From>
<system:Int32>0</system:Int32>
</core:ConverterItem.From>
</core:ConverterItem>
</core:UniversalConverter>
It's because it is being used at design time but there is no data so I suspect a NullReferenceException is being thrown. Try checking for design time mode as follows at the top of the IValueConverter.Convert() method body:
// Check for design mode.
if ((bool)(DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue))
{
return false;
}
Related
I am attaching enum to a picker and onSelect i am binding to the actual value of the enum, not its title.
My enum is as follows:
public enum Reason
{
AnnualLeave = 12,
Emergency = 23,
MaternityLeave = 34
}
My class uses the following to bind the enum title to the picker
public Reason ReasonSelectedOption { get; set; }
public ObservableCollection<Reason> ReasonDisplay
{
get => new ObservableCollection<Reason>(Enum.GetValues(typeof(Reason)).OfType<Reason>().ToList());
}
The actual picker
<Picker
ItemsSource="{Binding ReasonDisplay}"
SelectedItem="{Binding ReasonSelectedOption}"
Title="Please Select"
HorizontalOptions="FillAndExpand" />
Everything works fine except in the actual picker, the options appear as AnnualLeave and MaternityLeave which is what's expected from my code but i want them to appear as Annual Leave and Maternity Leave (with the space inbetween) preserving the selecteditem value
Current case: When user selects AnnualLeave, selectedItem value is 12, if i convert to string the selected value becomes 0.
I am simply asking how to put spaces inbetween the enum options and also preserve the SelectedItem integer value
Here you have to keep in mind the internationalisation.
Even if you don't have localised texts now, you may have to support it in the future. So, keeping that in mind, you won't need simply to "split" the string, but to take a specific text from somewhere (i.e. translate it according to the culture).
You can achieve it with the help of some attributes, extension methods and some clever binding.
Let's say that you want to have a picker with 2 options - what is the property type. The PropertyType is an enum, that looks like this:
public enum PropertyType
{
House,
Apartment
}
Since the built-in Description attribute can't translate texts for us, we can use a custom attribute to assign a specific text to an enum type, like this:
public enum PropertyType
{
[LocalizedDescription(nameof(R.SingleFamilyHouse))]
House,
[LocalizedDescription(nameof(R.ApartmentBuilding))]
Apartment
}
The attribute code looks like this:
public class LocalizedDescriptionAttribute : DescriptionAttribute
{
private readonly ResourceManager resourceManager;
private readonly string resourceKey;
public LocalizedDescriptionAttribute(string resourceKey, Type resourceType = null)
{
this.resourceKey = resourceKey;
if (resourceType == null)
{
resourceType = typeof(R);
}
resourceManager = new ResourceManager(resourceType);
}
public override string Description
{
get
{
string description = resourceManager.GetString(resourceKey);
return string.IsNullOrWhiteSpace(description) ? $"[[{resourceKey}]]" : description;
}
}
}
R is my resx file. I have created a Resources folder and inside it I have 2 resx files - R.resx (for English strings) & R.de.resx (for German translation). If you don't want to have internationalisation now, you can change the implementation to get your strings from another place. But it is considered a good practice to always use a resx file, even if you only have 1 language. You never now what tomorrow may bring.
Here is my structure:
The idea behind LocalizedDescriptionAttribute class is that the built-in Description attribute isn't very useful for our case. So we'll have to take the resource key that we have provided it, translates and to override the Description attribute, which later we will reference.
Now we need to obtain the localised description text with this helper method:
public static class EnumExtensions
{
public static string GetLocalizedDescriptionFromEnumValue(this Enum value)
{
return !(value.GetType()
.GetField(value.ToString())
.GetCustomAttributes(typeof(LocalizedDescriptionAttribute), false)
.SingleOrDefault() is LocalizedDescriptionAttribute attribute) ? value.ToString() : attribute.Description;
}
}
Now, when we create the bindings for the Picker, we won't just use a simple Enum, but a specific PropertyTypeViewModel, which will have 2 properties - the Enum itself and a Name that will be displayed.
public class PropertyTypeViewModel : BaseViewModel
{
private string name;
public string Name
{
get => name;
set => SetValue(ref name, value);
}
private PropertyType type;
public PropertyType Type
{
get => type;
set => SetValue(ref type, value);
}
public PropertyTypeViewModel()
{
}
public PropertyTypeViewModel(PropertyType type)
: this()
{
Type = type;
Name = type.GetLocalizedDescriptionFromEnumValue();
}
}
The important line is the last one - Name = type.GetLocalizedDescriptionFromEnumValue();
The final thing that is left is to set your Picker's ItemsSource to your collection of PropertyTypeViewModels and ItemDisplayBinding to be pointing to the Name property - ItemDisplayBinding="{Binding Name}"
That's it - now you have a Picker with dynamic localised strings.
You could use a converter
public class EnumToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return null;
var valueAsString = value.ToString();
valueAsString = valueAsString.SplitCamelCase();
return valueAsString;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And for the SplitCamelCase I wrote this but I'm sure there are cleaner options:
public static string SplitCamelCase(this string str)
{
string result = "";
for (int i = 0; i < str.Count(); i++)
{
var letter = str[i];
var previousLetter = i != 0 ? str[i - 1] : 'A';
if (i != 0 && char.IsUpper(previousLetter) == false && char.IsUpper(letter)) result = result + " " + letter;
else result = result + letter;
}
return result;
}
Then just used it like so:
<TextBlock Text="{Binding Converter={StaticResource EnumToStringConverter}}"/>
How I can make something like this in VS properties Window (collapsible multi properties):
I tried such code:
Test z = new Test();
[ Browsable(true)]
public Test _TEST_ {
get { return z; }
set { z = value; }
}
Where "Test" class is:
[Browsable(true)]
public class Test {
[Browsable(true)]
public string A { get;set; }
[Browsable(true)]
public string B { get;set; }
}
But this gives me only grayed-out name of class
Alright, I got something to work that may satisfy your case.
To get a class to expand in the PropertyGrid, you have to add a TypeConverterAttribute to it, referencing the type of an ExpandableObjectConverter (or something else that derives from it).
[TypeConverter(typeof(ExpandableObjectConverter))]
public class Test
{
[Browsable(true)]
public string A { get; set; }
[Browsable(true)]
public string B { get; set; }
}
The only problem is that it now displays the type name (the return value of its ToString() method as the value of your class). You can either live with it (which you probably won't want to), change the ToString() return value to something more fitting or use a custom TypeConverter for that case.
I'll show you a quick implementation on how the latter could be done:
internal class TestConverter : ExpandableObjectConverter
{
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
return "";
return base.ConvertTo(context, culture, value, destinationType);
}
}
And then you would write this, instead of what I wrote above:
[TypeConverter(typeof(TestConverter))]
public class Test
{
[Browsable(true)]
public string A { get; set; }
[Browsable(true)]
public string B { get; set; }
}
This just empties the information and prevents the user to enter some other value. You probably want to show something more descriptive which is completely up to you.
It is also possible to get information and parse it into useful values. A good example would be the location, which is an object of type Point visualized with [10,5] when X is 10 and Y is 5. When you enter new values they are parsed and set to the integers that are referenced by the original string.
Because I couldn't find much about the topic, I looked up some references in ReferenceSource, because it had to be done before. In my case, I peeked into ButtonBase and FlatButtonAppearance of WindowsForms to see how Microsoft did it, back in the day.
Hope I could help.
Here is the TypeConverter Class. This allows VS properties to access your object as strings, and convert back to it from strings.
for more about TypeConversion.
class MultiPropConverter : ExpandableObjectConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context,
Type sourceType)
{
if (sourceType == typeof(string)) { return true; }
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture,
object value)
{
if (value is string)
{
string[] v = ((string)value).Split(new char[] { ',' });
if(v.Length == 3) // Check that there are no ',' in your string(s) A.
{
return new DropDownProperties(v[0], float.Parse(v[1]), int.Parse(v[2]));
}
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture,
object value, Type destinationType)
{
if (destinationType == typeof(string)) // What VS properties ask for to display
{
DropDownProperties dDP = (DropDownProperties)value;
return dDP.A + "," + dDP.B.ToString() + "," + dDP.C.ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
the Multi-Property Class:
[TypeConverter(typeof(MultiPropConverter))]
public class DropDownProperties
{
[Description("Description of A")]
public string A { get; set; } = "Default";
[Description("Description of B")]
public float B { get; set; } = 0f;
[Description("Description of C")]
public int C { get; set; } = 1;
}
And then class instantiation:
[Description("Category Description"), Category("ACategory")]
public DropDownProperties dropProp { get; set; } = new DropDownProperties()
{ A = "Hello World", B = "0.1", C = 0};
You do not need the Browsable attribute if you include a category or description for the item.
Cheers!
In addition to the already good answers by others.
Browseable(true/false) means it can be browsed in the property window.
Please note, the Visual Studio properties editor will only show public properties. Private properties are hidden and can't be browsed for various reasons.
Public properties are browsable by default.
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.browsableattribute?view=netcore-3.1
The designer will always only show one value per default for strings, multi-property dropdowns only work for integers afaik.
Also, there is EditorBrowsable which defines whether or not Intellisense shows your property.
You can do something like this:
public class Column
{
[EditorBrowsable(EditorBrowsableState.Always)]
public string name { get; set; }
[EditorBrowsable(EditorBrowsableState.Never)]
}
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.editorbrowsablestate?view=netcore-3.1
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.editorbrowsableattribute?view=netcore-3.1
My current code is following, it is a WCF service method exposed as a proxy to the client:
public UnifiedDTO GetAllCardTitle(string trainSymbolOrCarLocation,
DateTime startDate,
DateTime endDate,
string procedureName = CardTitle.procedureNameTrainRuns)
This method takes a procedure name (as the last parameter) and rest of the parameters are the binding the input parameters, however the issue is that we do not have the flexibility in the case of parameters numbers and types changing. The project is in its initial stage, so changes will surely be made.
The options that I have is convert the method as follows:
public UnifiedDTO GetAllCardTitle(params object [] parameters)
Where we have the freedom to pass the parameters and procedure details and can accordingly bind. However, there might be a performance issue due to boxing and unboxing. It would require client application to pass the parameters with correct order and value to bind as it would be expected by underlying layers
public UnifiedDTO GetAllCardTitle(List<Filter> parameter, string procedureName)
Where Parameter class would be defined as:
public class Filter
{
public string name { set; get; }
public string value { set; get; }
public string dataTypeID { set; get; }
public Filter(string _name, string _value, string _dataTypeID)
{
name = _name;
value = _value;
dataTypeID = _dataTypeID;
}
}
In this method for a given procedure, we bind each parameter with its name value and DataType, and it would need value to typecasted to the correct data type, it has more flexibility then last method and can be passed in any order, as binding is by name. However, it would need much more due diligence from the application.
Is there still a better way to take care of this situation using something new introduced in C#.Net?
None. Use Dynamic object instead
To create a C# class that works with the DLR, the easiest thing to do is derive from DynamicObject. One limitation arises when trying to use a dynamic type in a WCF service. Trying to use a DynamicObject-derived type will result in a runtime exception when trying to serialize with WCF’s DataContractSerializer.
[DataContract]
public class SerializableDynamicObject : IDynamicMetaObjectProvider
{
[DataMember]
private IDictionary<string,object> dynamicProperties = new Dictionary<string,object>();
#region IDynamicMetaObjectProvider implementation
public DynamicMetaObject GetMetaObject (Expression expression)
{
return new SerializableDynamicMetaObject(expression,
BindingRestrictions.GetInstanceRestriction(expression, this), this);
}
#endregion
#region Helper methods for dynamic meta object support
internal object setValue(string name, object value)
{
dynamicProperties.Add(name, value);
return value;
}
internal object getValue(string name)
{
object value;
if(!dynamicProperties.TryGetValue(name, out value)) {
value = null;
}
return value;
}
internal IEnumerable<string> getDynamicMemberNames()
{
return dynamicProperties.Keys;
}
#endregion
}
public class SerializableDynamicMetaObject : DynamicMetaObject
{
Type objType;
public SerializableDynamicMetaObject(Expression expression, BindingRestrictions restrictions, object value)
: base(expression, restrictions, value)
{
objType = value.GetType();
}
public override DynamicMetaObject BindGetMember (GetMemberBinder binder)
{
var self = this.Expression;
var dynObj = (SerializableDynamicObject)this.Value;
var keyExpr = Expression.Constant(binder.Name);
var getMethod = objType.GetMethod("getValue", BindingFlags.NonPublic | BindingFlags.Instance);
var target = Expression.Call(Expression.Convert(self, objType),
getMethod,
keyExpr);
return new DynamicMetaObject(target,
BindingRestrictions.GetTypeRestriction(self, objType));
}
public override DynamicMetaObject BindSetMember (SetMemberBinder binder, DynamicMetaObject value)
{
var self = this.Expression;
var keyExpr = Expression.Constant(binder.Name);
var valueExpr = Expression.Convert(value.Expression, typeof(object));
var setMethod = objType.GetMethod("setValue", BindingFlags.NonPublic | BindingFlags.Instance);
var target = Expression.Call(Expression.Convert(self, objType),
setMethod,
keyExpr,
valueExpr);
return new DynamicMetaObject(target,
BindingRestrictions.GetTypeRestriction(self, objType));
}
public override IEnumerable<string> GetDynamicMemberNames ()
{
var dynObj = (SerializableDynamicObject)this.Value;
return dynObj.getDynamicMemberNames();
}
}
One warning, dynamic members can be anything, meaning at runtime someone could assign a method to one of these fields. If this is possible in your application, you’ll need to ensure any methods assigned to the dynamic type are not serialized. I’m leaving this as an exercise for the reader.
Taken from Here
Is it possible to allow an "Interfaced" parameter. From that, you could handle multiple things based on the interfaced value setting. Just shooting out a simple sample.
public enum eWhatAmI
{
ListedObjects,
StringArrays,
Other
}
public interface IWhatParmType
{
eWhatAmI whatAmI { get; set; }
}
public class MyListVersion : IWhatParmType
{
public eWhatAmI whatAmI { get; set; }
public List<string> whatever { get; set; }
public MyListVersion()
{
whatAmI = eWhatAmI.ListedObjects;
whatever = new List<string>();
... build out list of strings
}
}
public class MyArrayVersion : IWhatParmType
{
public eWhatAmI whatAmI { get; set; }
public string[] whatever { get; set; }
public MyArrayVersion()
{
whatAmI = eWhatAmI.StringArrays;
... build out array of strings
}
}
etc...
Then in your process for handling whatever the incoming parameter is, you can handle either way.
public UnifiedDTO GetAllCardTitle(IWhatParmType parameter, string procedureName)
{
switch( parameter )
{
case (eWhatAmI.ListedObjects):
// Just for grins, test to make sure object really IS expected list version object
if( parameter is MyListVersion)
DoViaList( (MyListVersion)parameter );
break;
case (eWhatAmI.StringArrays ):
if( parameter is MyArrayVersion )
DoViaArray( (MyArrayVersion)parameter );
break;
}
}
private void DoViaList( MyListVersion parm1 )
{
.. do whatever based on the "List<string>" property
}
private void DoViaArray( MyArrayVersion parm1 )
{
.. do whatever based on the "string []" property
}
Then, if you ever needed to expand a setting per a particular object instance, you could and handle within the specific sub-handler method for populating or forcing whatever defaults to be implied.
I have a complex value object class that has 1) a number or read-only properties; 2) a private constructor; and 3) a number of static singleton instance properties [so the properties of a ComplexValueObject never change and an individual value is instantiated once in the application's lifecycle].
public class ComplexValueClass
{
/* A number of read only properties */
private readonly string _propertyOne;
public string PropertyOne
{
get
{
return _propertyOne;
}
}
private readonly string _propertyTwo;
public string PropertyTwo
{
get
{
return _propertyTwo;
}
}
/* a private constructor */
private ComplexValueClass(string propertyOne, string propertyTwo)
{
_propertyOne = propertyOne;
_propertyTwo = PropertyTwo;
}
/* a number of singleton instances */
private static ComplexValueClass _complexValueObjectOne;
public static ComplexValueClass ComplexValueObjectOne
{
get
{
if (_complexValueObjectOne == null)
{
_complexValueObjectOne = new ComplexValueClass("string one", "string two");
}
return _complexValueObjectOne;
}
}
private static ComplexValueClass _complexValueObjectTwo;
public static ComplexValueClass ComplexValueObjectTwo
{
get
{
if (_complexValueObjectTwo == null)
{
_complexValueObjectTwo = new ComplexValueClass("string three", "string four");
}
return _complexValueObjectTwo;
}
}
}
I have a data context class that looks something like this:
public class DataContextClass : INotifyPropertyChanged
{
private ComplexValueClass _complexValueClass;
public ComplexValueClass ComplexValueObject
{
get
{
return _complexValueClass;
}
set
{
_complexValueClass = value;
PropertyChanged(this, new PropertyChangedEventArgs("ComplexValueObject"));
}
}
}
I would like to write a XAML binding statement to a property on my complex value object that updates the UI whenever the entire complex value object changes. What is the best and/or most concise way of doing this? I have something like:
<Object Value="{Binding ComplexValueObject.PropertyOne}" />
but the UI does not update when ComplexValueObject as a whole changes.
Your original scenario should work just fine because in most cases Bindings recognize change notifications on any part of their property path. I in fact tried out the code you posted to confirm and it does work just fine.
Are there other complexities you may not be expressing in your stripped down sample? The primary one I can think of would be collections->ItemsSource Bindings but there could be something related to the property you're assigning the bound value to (since it's obviously not an Object) or something else entirely.
You don't notify on changes to PropertyOne so UI will not update. Instead bind to ComplexValueObject and use value converter to get the property value.
<Object Value="{Binding Path=ComplexValueObject, Converter={StaticResource ComplexValueConverter}, ConverterParameter=PropertyOne}" />
public class ComplexValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ComplexValue cv = value as ComplexValue;
string propName = parameter as string;
switch (propName)
{
case "PropertyOne":
return cv.PropertyOne;
case "PropertyTwo":
return cv.PropertyTwo;
default:
throw new Exception();
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You need INotifyPropertyChanged on your Complex class. The only notifies if you reassign the entire property in the parent, the properties of that child class need to notify to0 if you are going to bind to them.
When I bind this object
public class MyObject
{
public AgeWrapper Age
{
get;
set;
}
}
public class AgeWrapper
{
public int Age
{
get;
set;
}
}
to a property grid, what is shown in the value section of the property grid is the class name of AgeWrapper, but the value for AgeWrapper.Age.
Is there anyway to make it so that in the property grid I can show the value of the composite object ( in this case, it's AgeWrapper.Age), instead of the class name of that composite object?
You need to create a type converter and then apply that using an attribute to the AgeWrapper class. Then the property grid will use that type converter for getting the string to display. Create a type converter like this...
public class AgeWrapperConverter : ExpandableObjectConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context,
Type destinationType)
{
// Can always convert to a string representation
if (destinationType == typeof(string))
return true;
// Let base class do standard processing
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value,
Type destinationType)
{
// Can always convert to a string representation
if (destinationType == typeof(string))
{
AgeWrapper wrapper = (AgeWrapper)value;
return "Age is " + wrapper.Age.ToString();
}
// Let base class attempt other conversions
return base.ConvertTo(context, culture, value, destinationType);
}
}
Notice that it inherits from ExpandableObjectConverter. This is because the AgeWrapper class has a child property called AgeWrapper.Age that needs to be exposed by having a + button next to the AgeWrapper entry in the grid. If your class did not have any child properties that you wanted to expose then instead inherit from TypeConverter. Now apply this converter to your class...
[TypeConverter(typeof(AgeWrapperConverter))]
public class AgeWrapper