Automatically expand some properties in PropertyGrid - c#

I would like to automatically expand some nodes in a PropertyGrid loaded with an instance of my SettingsStructure class by using attributes on the properties of that class.
Also, I am attempting to have the instance 'remember' whether each property was expanded or not if the user loads that instance again on the PropertyGrid.
I have made a real HACK that mostly works. It does not always work if the complex property is the first one displayed in the PropertyGrid.
Any suggestions for a better way using attributes/typeconverters/similar?
Here is what I have:
Define a custom Attribute to denote that the property it marks is to start expanded.
[AttributeUsage(AttributeTargets.Property)]
public class StartExpanded : Attribute {}
Derive your own class from ExpandableObjectConverter.
public class MyExpandableObjectConverter : ExpandableObjectConverter
{
private bool _IsFirstUse = true;
private bool _JustShownInPropertyGrid = false;
private bool _WasLastExpanded = false;
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
//This method is called every time the propertygrid shows this property
if (_IsFirstUse)
{
_IsFirstUse = false;
_WasLastExpanded = context.PropertyDescriptor.Attributes[typeof(StartExpanded)] != null;
}
_JustShownInPropertyGrid = true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
//This method is called after CanConvertFrom and also on other operations and when leaving showing this property
if (_JustShownInPropertyGrid)
{
_JustShownInPropertyGrid = false;
if (_WasLastExpanded)
{
var GI = (GridItem)context;
GI.Expanded = true;
}
}
else
{
var GI = (GridItem)context;
_WasLastExpanded = GI.Expanded;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
And now apply them to properties in your class to be shown in the PropertyGrid. ASettingsNode is just an abstract class that I use to mark properties that should load in the TreeView on the left.
[Serializable]
public class SettingsStructure : ASettingsNode
{
public string FamilyName { get; set; } = "Rogers";
[StartExpanded]
[TypeConverter(typeof(MyExpandableObjectConverter))]
public NameAgePair Dad { get; set; } = new NameAgePair() { Name = "Buck", Age = 51};
[StartExpanded]
[TypeConverter(typeof(MyExpandableObjectConverter))]
public NameAgePair Mom { get; set; } = new NameAgePair() { Name = "Wilma", Age = 50};
public string NameOfSomebody { get; set; } = "Phoebe";
//... and other nodes that are derived from ASettingsNode to show up in the TreeView
}
Here NameAgePair is my class.
public class NameAgePair
{
public string Name { get; set; } = "";
public int Age { get; set; } = 0;
public override string ToString()
{
return $"{Name} ({Age})";
}
}
Besides being a HACK, it does not work if the first item in the grid is one of the complex properties that I want to expand and remember. For that first property the ConvertTo method is called out of sequence and the "remember" part fails.

There is no direct solution for setting initial expanded states in the MSPG. Only hacks. And, as you can see, trying to do it in a TypeConverter is not easy. Furthermore, there is no built-in way to save and restore various states of the grid.
As the developer of a custom PropertyGrid made from scratch, I tried to achieve both of your requests. And it definitively works with a few lines of code only. If a commercial product is an option, you can have a look.

Related

Looking for a more elegant way to perform custom get/set functionality on class properties

I'm trying to find a way to refine some code that I have. I work with a 3rd party API that has a REALLY complicated API request object (I'll call it ScrewyAPIObject) that has tons of repetition in it. Every time you want to set a particular property, it can take a page worth of code. So I built a library to provide a simplified wrapper around the setting/getting of its properties (and to handle some value preprocessing).
Here's a stripped-down view of how it works:
public abstract class LessScrewyWrapper
{
protected ScrewyAPIRequest _screwy = new ScrewyAPIRequest();
public void Set(string value)
{
Set(_getPropertyName(), value);
}
public void Set(string property, string value)
{
// Preprocess value and set the appropriate property on _screwy. This part
// has tons of code, but we'll just say it looks like this:
_screwy.Fields[property] = "[" + value + "]";
}
protected string _getPropertyName()
{
// This method looks at the Environment.StackTrace, finds the correct set_ or
// get_ method call and extracts the property name and returns it.
}
public string Get()
{
// Get the property name being access
string property = _getPropertyName();
// Search _screwy's structure for the value and return it. Again, tons of code,
// so let's just say it looks like this:
return _screwy.Fields[property];
}
public ScrewyAPIRequest GetRequest()
{
return _screwy;
}
}
Then I have a child class that represents one specific type of the screwy API request (there are multiple kinds that all have the same structure but different setups). Let's just say this one has two string properties, PropertyA and PropertyB:
public class SpecificScrewyAPIRequest : LessScrewyWrapper
{
public string PropertyA
{
get { return Get(); }
set { Set(value); }
}
public string PropertyB
{
get { return Get(); }
set { Set(value); }
}
}
Now when I want to go use this library, I can just do:
SpecificScrewyAPIRequest foo = new SpecificScrewyAPIRequest();
foo.PropertyA = "Hello";
foo.PropertyB = "World";
ScrewyAPIRequest request = foo.GetRequest();
This works fine and dandy, but there are different kinds of data types, which involves using generics in my Set/Get methods, and it just makes the child classes look a little kludgy when you're dealing with 50 properties and 50 copies of Get() and Set() calls.
What I'd LIKE to do is simply define fields, like this:
public class SpecificScrewyAPIRequest : LessScrewyWrapper
{
public string PropertyA;
public string PropertyB;
}
It would make the classes look a LOT cleaner. The problem is that I don't know of a way to have .NET make a callback to my custom handlers whenever the values of the fields are accessed and modified.
I've seen someone do something like this in PHP using the __set and __get magic methods (albeit in a way they were not intended to be used), but I haven't found anything similar in C#. Any ideas?
EDIT: I've considered using an indexed approach to my class with an object-type value that is cast to its appropriate type afterwards, but I'd prefer to retain the approach where the property is defined with a specific type.
Maybe in your case DynamicObject is a suitable choice:
public class ScrewyDynamicWrapper : DynamicObject
{
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
// get your actual value based on the property name
Console.WriteLine("Get Property: {0}", binder.Name);
result = null;
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
// set your actual value based on the property name
Console.WriteLine("Set Property: {0} # Value: {2}", binder.Name, value);
return true;
}
}
And define your wrapper objects:
public class ScrewyWrapper
{
protected dynamic ActualWrapper = new ScrewyDynamicWrapper();
public int? PropertyA
{
get { return ActualWrapper.PropertyA; }
set { ActualWrapper.PropertyA = value; }
}
public string PropertyB
{
get { return ActualWrapper.PropertyB; }
set { ActualWrapper.PropertyB = value; }
}
}
However, you can't rely on the property type inside ScrewyDynamicWrapper with this approach, so it depends on your actual API requirements - maybe it won't work for you.
Instead of fields, If you define as property in class, It will be more easy.
public class SpecificScrewyAPIRequest
{
public string PropertyA { get; set; }
public string PropertyB { get; set; }
}
Then you can create extension generic method to return ScrewyAPIRequest object.
public static class Extensions
{
public static ScrewyAPIRequest GetRequest<T>(this T obj)
{
ScrewyAPIRequest _screwy = new ScrewyAPIRequest();
var test= obj.GetType().GetProperties();
foreach (var prop in obj.GetType().GetProperties())
{
_screwy.Fields[prop.Name] = prop.GetValue(obj, null);
}
return _screwy;
}
}
Now you can easily get ScrewyAPIRequest from any class object.
Your code will look like following.
SpecificScrewyAPIRequest foo = new SpecificScrewyAPIRequest();
foo.PropertyA = "Hello";
foo.PropertyB = "World";
ScrewyAPIRequest request = foo.GetRequest();

WinForms designer properties of different derived types

Say I have a particular type that I want to make available to the Windows Forms designer...
public class Style
{
public CustomBrush Brush { get; set; }
}
And CustomBrush is implemented like so...
public abstract CustomBrush
{
...
}
public SolidCustomBrush : CustomBrush
{
...
}
public GradientCustomBrush : CustomBrush
{
...
}
Is there a way at design time that I can choose from any of the types derived from CustomBrush, instantiate an instance of the selected type, and modify it via the designer?
So far the only way I've though of to be able to do this is using an enum
enum BrushType
{
Solid,
Gradient
}
When the enum changes, so does type underlying the Brush property, but I don't like this approach...it's dirty!
As an option you can create a custom TypeConverter that provides a list of standard values to show in PropertyGrid.
A type converter can provide a list of values for a type in a
Properties window control. When a type converter provides a set of
standard values for a type, the value entry field for a property of
the associated type in a Properties window control displays a down
arrow that displays a list of values to set the value of the property
to when clicked.
Since you want to be able to edit also sub properties of the CustomBrush in property grid, you should derive from ExpandableObjectConverter.
Result
Implementation
Create a CustomBrushConverter class and derive from ExpandableObjectConverter. Then override these methods:
GetStandardValuesSupported: return true to show a dropdown.
GetStandardValuesExclusive: return true to limit supported values to dropdown list.
GetStandardValues: return a list of available options to show in dropdown. All values should be the same type of the property you are editing (here CustomBrush type).
CanConvertFrom: return true if the sourceType parameter is of type string.
ConvertFrom: return one of standard values based on the string value parameter.
using System;
using System.ComponentModel;
using System.Linq;
class CustomBrushConverter : ExpandableObjectConverter
{
CustomBrush[] standardValues = new CustomBrush[] { new SolidCustomBrush(), new GradientCustomBrush() };
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, System.Globalization.CultureInfo culture, object value)
{
var result = standardValues.Where(x => x.ToString() == value).FirstOrDefault();
if (result != null)
return result;
return base.ConvertFrom(context, culture, value);
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(standardValues);
}
}
Then decorate the Brush property with TypeConverterAttribute this way:
public class Style /*: Component */
{
[TypeConverter(typeof(CustomBrushConverter))]
public CustomBrush Brush { get; set; }
}
You can override ToString method of your CustomBrush classes to provide more friendly names to show in dropdown list in PropertyGrid. For example:
public class GradientCustomBrush : CustomBrush
{
public Color Color1 { get; set; }
public Color Color2 { get; set; }
public override string ToString()
{
return "Gradient";
}
}

Filter the dropdown list for an Enum property in PropertyGrid

I am displaying the properties of an object in a PropertyGrid. One of those properties is an enum. So it is displayed with a combobox editor that lists all of the values of the enum. That's all great, but I need to filter that list of enum values at runtime. I can't just decorate some of the enum values with the Browsable attribute, because which of the values I want to hide will vary. At the moment I'm leaning towards a custom UITypeEditor, but I thought I should check with the smart people first.
A TypeConverter may be all you need, especially if the legal subset "changes from moment to moment". Given an enum:
public enum AnimalSpecies
{
Canine, Feline, Rodent, Dragon, Unicorn, Robot
}
...which is used on a property:
public class Animal
{
...
[TypeConverter(typeof(AnimalSpeciesConverter))]
public AnimalSpecies Species { get; set; }
If you decorate the enum, the converter will apply to anything/everything which uses it; on the property, it affects only that. For the converter, you will want to override GetStandardValues():
class AnimalSpeciesConverter : TypeConverter
{
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
Animal test = context.Instance as Animal;
if (test != null)
return true;
else
return base.GetStandardValuesSupported();
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
string[] names = Enum.GetNames(typeof(AnimalSpecies))
.Where(x => !x.StartsWith("Rob")).ToArray();
return new StandardValuesCollection(names);
}
}
GetStandardValuesSupported() returns true to indicate the TypeConverter can and will supply the values.
GetStandardValuesExclusive() indicates that, no the user can't opt to type in their own value.
GetStandardValues() filters the list and returns the new subset.
Result:
No Robots!
If the filtering logic is more complex, you might want to let the class instance determine the contents. Or perhaps you'd just prefer that logic to remain in the class. To do that I like to use an interface:
public interface IValuesProvider
{
string[] GetValues();
}
public class Animal : IValuesProvider
{
public string Name { get; set; }
[TypeConverter(typeof(AnimalSpeciesConverter))]
public AnimalSpecies Species { get; set; }
public int Value { get; set; }
...
public string[] GetValues()
{
List<string> names = Enum.GetNames(typeof(AnimalSpecies)).ToList();
// your logic here
if (Value < 10)
names.Remove(AnimalSpecies.Feline.ToString());
else if (Value < 50)
names.Remove(AnimalSpecies.Robot.ToString());
return names.ToArray();
}
Now, when the PropertyGrid asks for the values, we can get them from the instance:
class AnimalSpeciesConverter : TypeConverter
{
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
IValuesProvider test = context.Instance as IValuesProvider;
if (test != null)
return true;
else
return base.GetStandardValuesSupported();
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
IValuesProvider item = context.Instance as IValuesProvider;
return new StandardValuesCollection(item.GetValues());
}
}
As an alternative, you can skip the interface and make the GetValues method private if you dont like exposing it that wayand get the values by Reflection. In that case, I might return true for GetStandardValuesSupported() only if the method exists. That way you don't ever have a case where you have returned true for Get...Supported() and then not have a way to supply them.
This is dynamic: the values list is refreshed/repolled every time the drop down opens. Each time you change the Value, the drop down list is updated each time it opens.

Create custom display attribute using or inherits DisplayAttribute in ASP.NET MVC

I want to use DisplayAttribute with Name property.
The problem is that class is sealed and I cannot inherits it to override some methods.
Why I want this ?
I want to pass some a code in order to translate strings to Name property. And add one property for language.
Something like:
[MyDisplay(Code = TRANSLATION_CODE, Language = "FR-FR")]
public string Fr { get; set; }
And inside MyDisplayAttribute, I want to do like:
public class MyDisplayAttribute: DisplayAttribute // it won't work the inherits
{
public int Code { get; set; }
public string Language { get; set; }
// somewhere, I don't know what method
// I want to assing `Name = GetTranslation(Code, Language);`
}
There is another way to do that ?
UPDATE
I tried also this:
public class MyDisplayAttribute : DisplayNameAttribute
{
private int _code;
private string _language;
public MyDisplayAttribute( int code, string language )
: base( language )
{
_code = code;
_language = language;
}
public override string DisplayName
{
get
{
// here come LanguageTranslatorManager
if ( _code == 1 && _language == "en" ) {
return "test";
}
return base.DisplayName;
}
}
}
and in model:
[MyDisplay( 1, "en" )]
public string Test
{
get;
set;
}
I'm expecting to display test in view, but doesn't !
Where is my mistake ?
In order to set a specific display name for your property, you need to set the metadata property DisplayName. If you need to write custom attributes, you need to make sure that you create a custom metadata provider. Inside you need to set the DisplayName of your property, based on the values provided.
public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes,
Type containerType, Func<object> modelAccessor,
Type modelType, string propertyName)
{
var modelMetadata = base.CreateMetadata(attributes, containerType,
modelAccessor, modelType, propertyName);
if (attributes.OfType<MyDisplay>().ToList().Count > 0)
{
modelMetadata.DisplayName = GetValueFromLocalizationAttribute(attributes.OfType<MyDisplay>().ToList()[0]);
}
return modelMetadata;
}
private string GetValueFromLocalizationAttribute(MyDisplay attribute)
{
return computedValueBasedOnCodeAndLanguage;
}
}
You are doing it wrong. The DisplayAttribute already supports 'translations' using the built-in .net internationalization
[Display(Name = "property_name", ResourceType = typeof(MyResources))]
Only if that's not enough you should create your own attribute, but not deriving from Display.
Edit:
The DisplayAttribute is a sealed class, so there is no way to inherit from it.

Object with user-configurable property "visibility"

I have a User object with a bunch of properties. I have a requirement that states when a user sets up their information, they need the ability to state which properties of their profile are visible to others.
The way I had envisioned this was adding an additional property - a list of string that would contain the property names that were publicly visible. I could then implement a method called ToPublicView() or something similar that would use reflection to set non-public properties to null or default.
Is this a reasonable approach, or is there a better way?
I think it's the simplest of the options. If reflection start to kill your performance, you may want to have a dictionary of property-delegate for accessing the values.
And as the requirement is not to have dynamic properties but just to mark the existing ones, it doesn't make sense to have all the properties in a dynamic way (like a list of property objects). Also, having them as actual properties will make the code more readable when you have to use it for the rest of the application.
In such a situation, if possible, I would suggest simply having a list of your properties, such as:
public Class Property<T>
{
public Property(string name, bool visible, T value)
{
Name = name;
Visible = visible;
Value = value;
}
string Name { get; set; }
bool Visible { get; set; }
T Value { get; set; }
}
Then you could create a list of the properties like this:
List<Property> properties = new List<Property>();
properties.Add(new Property<string>("FirstName", true, "Steve"));
If you need to be able to set the visibility today, you may need to set other meta-properties as well tomorrow. Color? Required/Optional? Size? Etc. Having your own Property type allows you to easily expand it in the future.
What? No. If that's the demand, then User properties should not be realized with actual properties, but instead using some sort of IEnumerable and Property objects, where each Property has its visibility, etc.
Can use, may be, some combination of custom DynamicObject implementation
EDIT
//custom property class
public class MyProperty
{
public bool IsVisible { get; set; }
public string PropertyName { get; set; }
}
public class Dymo: DynamicObject
{
Dictionary<MyProperty, object> dictionary
= new Dictionary<MyProperty, object>();
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
result = false;
var prop = PropertyFromName(binder.Name);
if (prop != null && prop.IsVisible)
return dictionary.TryGetValue(prop, out result);
return false;
}
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
var prop = PropertyFromName(binder.Name);
if (prop != null && prop.IsVisible)
dictionary[prop] = value;
else
dictionary[new MyProperty { IsVisible = true, PropertyName = binder.Name}] = value;
return true;
}
private MyProperty PropertyFromName(string name)
{
return (from key in dictionary.Keys where key.PropertyName.Equals(name) select key).SingleOrDefault<MyProperty>();
}
public void SetPropertyVisibility(string propertyName, bool visibility)
{
var prop = PropertyFromName(propertyName);
if (prop != null)
prop.IsVisible = visibility;
}
}
and use this, after like this.
dynamic dynObj = new Dymo();
dynObj.Cartoon= "Mickey" ;
dynObj.SetPropertyVisibility("Mickey", false); //MAKE A PROPERTY "INVISIBLE"

Categories

Resources