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
Related
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
I have a class that derives from DynamicObject class. On calling JsonConvert.SertializeObject, none of the dynamic properties are serialized.
Class is defined as,
public class Component : DynamicObject
{
// The inner dictionary.
public Dictionary<string, object> dictionary
= new Dictionary<string, object>();
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
// If you try to get a value of a property
// not defined in the class, this method is called.
public override bool TryGetMember (
GetMemberBinder binder, out object result)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
string name = binder.Name.ToLower();
// If the property name is found in a dictionary,
// set the result parameter to the property value and return true.
// Otherwise, return false.
return dictionary.TryGetValue(name, out result);
}
// If you try to set a value of a property that is
// not defined in the class, this method is called.
public override bool TrySetMember (
SetMemberBinder binder, object value)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
dictionary[binder.Name.ToLower()] = value;
// You can always add a value to a dictionary,
// so this method always returns true.
return true;
}
}
then i do this,
dynamic component = new Component();
component.test = "123";
JsonConvert.SerializeObject(component);
You need to override DynamicObject.GetDynamicMemberNames:
public override IEnumerable<string> GetDynamicMemberNames()
{
return dictionary.Keys;
}
I want to create a class with fixed properties and the capability to extend them as dynamic or ExpandoObject can.
e.g:
public class DynamicInstance : DynamicObject
{
public string FixedTestProperty { get; set; }
}
Usage:
DynamicInstance myCustomObj = new DynamicInstance();
myCustomObj.FixedTestProperty = "FixedTestValue";
myCustomObj.DynamicCreatedTestProperty = "Custom dynamic property value";
Finally if I serialize that class with json.net or something else output something like that:
{
FixedTestProperty: 'FixedTestValue',
DynamicCreatedTestProperty: 'Custom dynamic property value'
}
You need to inherit DynamicObject and override the TryGetMember and TrySetMember methods. Here is a class which has one property named One. However, you can add more to it dynamically.
public class ExpandOrNot : DynamicObject
{
public string One { get; set; }
// The inner dictionary.
Dictionary<string, object> dictionary
= new Dictionary<string, object>();
// This property returns the number of elements
// in the inner dictionary.
public int Count
{
get
{
return dictionary.Count;
}
}
// If you try to get a value of a property
// not defined in the class, this method is called.
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
string name = binder.Name.ToLower();
// If the property name is found in a dictionary,
// set the result parameter to the property value and return true.
// Otherwise, return false.
return dictionary.TryGetValue(name, out result);
}
// If you try to set a value of a property that is
// not defined in the class, this method is called.
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
dictionary[binder.Name.ToLower()] = value;
// You can always add a value to a dictionary,
// so this method always returns true.
return true;
}
}
Usage
dynamic exp = new ExpandOrNot { One = "1" };
exp.Two = "2";
More info here.
<== Fiddle Me ==>
This is possible using TrySetMember on DynamicObject.
The example at the bottom of this shows how to do it: https://msdn.microsoft.com/en-us/library/system.dynamic.dynamicobject.trysetmember(v=vs.110).aspx
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;
}
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.