Customizing PropertyGrid Control - c#

I'm trying to customize a property wherein if I click the elipses button [...] a new dialog form will be displayed. Unfortunately, the form won't show. Can you please check the below code and advise where did I go wrong?
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System;
namespace Test01
{
/// <summary>
/// Description of MainForm.
/// </summary>
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
void MainFormLoad(object sender, EventArgs e)
{
Form form = new Form();
propertyGrid1.SelectedObject = new MyType();
}
}
class MyType
{
private string bar;
[Editor(typeof(FooEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ExpandableObjectConverter))]
public string Bar
{
get { return bar; }
set { bar = value; }
}
}
[Editor(typeof(FooEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ExpandableObjectConverter))]
class Foo
{
private string bar;
public string Bar
{
get { return bar; }
set { bar = value; }
}
}
class FooEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, System.IServiceProvider provider, object value)
{
IWindowsFormsEditorService svc = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
Foo foo = value as Foo;
if (svc != null && foo != null)
{
using (FooForm form = new FooForm())
{
form.Value = foo.Bar;
if (svc.ShowDialog(form) == DialogResult.OK)
{
foo.Bar = form.Value; // update object
}
}
}
return value; // can also replace the wrapper object here
}
}
class FooForm : Form
{
private TextBox textbox;
private Button okButton;
public FooForm()
{
textbox = new TextBox();
Controls.Add(textbox);
okButton = new Button();
okButton.Text = "OK";
okButton.Dock = DockStyle.Bottom;
okButton.DialogResult = DialogResult.OK;
Controls.Add(okButton);
}
public string Value
{
get { return textbox.Text; }
set { textbox.Text = value; }
}
}
}

Your editor works with the Foo type (if value isn't Foo then it won't show the dialog) but you create an instance of MyType, it contains one property Bar of type string then it can't be edited by your FooEditor.
To try how your code works you should change the property Bar from string to Foo:
class MyType
{
private Foo bar = new Foo();
[Editor(typeof(FooEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ExpandableObjectConverter))]
public Foo Bar
{
get { return bar; }
set { bar = value; }
}
}
EXAMPLES
Let's see two examples. In the first one you edit the property where the Attribute has been applied (then your editor changes the value of the property itself):
This is the class you'll edit in the PropertyGrid, I removed the Foo class because useless for this example:
class MyType
{
private string bar;
[Editor(typeof(MyStringEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ExpandableObjectConverter))]
public string Bar
{
get { return bar; }
set { bar = value; }
}
}
This is the editor that will edit your Bar property. Actually it works with any property of type string:
class MyStringEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
var svc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
string text = value as string;
if (svc != null && text != null)
{
using (FooForm form = new FooForm())
{
form.Value = text;
if (svc.ShowDialog(form) == DialogResult.OK)
{
return form.Value;
}
}
}
return value;
}
}
Now another example, the editor doesn't change the property value itself but the value of a property of that property (editor is applied to property MyType.Bar (of type Foo) but it changes the value of the property Value of Foo.
Let's introduce again a complex type for your Bar property:
class Foo
{
private string _value;
private object _tag; // Unused in this example
public string Value
{
get { return _value; }
set { _value = value; }
}
public object Tag
{
get { return _tag; }
set { _value = _tag; }
}
}
Change the MyType class to publish one property of the complex type we wrote, note that the EditorAttribute now uses a new editor specific for the Foo type:
class MyType
{
private Foo bar = new Foo();
[Editor(typeof(FooEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ExpandableObjectConverter))]
public Foo Bar
{
get { return bar; }
set { bar = value; }
}
}
Finally we write the editor for the Foo type. Please note that this editor will change only the value of the property Foo.Value, the other property exposed by Foo won't be touched:
class FooEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
var svc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
Foo foo = value as Foo;
if (svc != null && foo != null)
{
using (FooForm form = new FooForm())
{
form.Value = foo.Value;
if (svc.ShowDialog(form) == DialogResult.OK)
{
// Updates the value of the property Value
// of the property we're editing.
foo.Value = form.Value;
}
}
}
// In this case we simply return the original property
// value, the property itself hasn't been changed because
// we updated the value of an inner property
return value;
}
}
Last notes: in the if block of svc.ShowDialog() we have the update value from the Form, the property Bar (where the editor is applied) won't be changed but we'll update the Value property of the Foo instance it contains. Roughly equivalent to write something like this:
MyType myType = new MyType();
myType.Bar.Value = form.Value;
If we just return a new value for the property (like in the previous example) the old value of Tag property will be lost, something like this:
MyType myType = new MyType();
Foo foo = new Foo();
foo.Value = form.Value;
myType.Bar = foo;
Can you see the difference? Anyway it's going to be too more a tutorial than an answer...
Please note that examples are untested, I just wrote them here, I just would like to expose the concept more than to provide a surely working example.
REFERENCES
Here you can find a short list of useful resources:
A comprehensive example about how UITypeEditor works
Main source for PropertyDescriptor documentation is always MSDN but here you can find a pretty short example.
If you just need to localize property names you may take a look to this entry here on SO.

Related

How do I use the propful aspect in Visual studio?

How do I use the propfull tab tab gadget in visual studio?
Class Foo
{
public int regirsterR0
{
get { return R0; }
set { R0 = value; }
}
}
How would I use the get and set methods from another class? Let's say this method is in a class called foo. How would I use the get and set from foo in goo?
Class Goo
{
Foo g= new Foo();
g.regirsterR0.Get?????
}
First, thats called a snippet (and there are a bunch of others!). This one creates a full property (MSDN) definition.
To answer your question; you just use it as if it were a field:
var test = g.Register0; //invokes get
g.Register0 = 2; //invokes set
get and set are nice method abstractions that are called when the associated property is accessed or assigned to.
Note that you don't even need the snippet; you could have used an auto-property:
public int RegisterR0 { get; set; } //Properties are PascalCase!
Get and Set is not a value or method. Actually they are property like a control mechanism. (encapsulation principle)
for ex:
var variable = g.Register0; // so it is get property. // like a var variable = 5;
g.Register0 = 5; // so it is set property.
Look msdn explaining.
You just forgot create method. :-)
class Foo
{
private int regirsterR0;
public int RegirsterR0
{
get { return regirsterR0; }
set { regirsterR0 = value; }
}
}
class Goo
{
Foo g = new Foo();
void myMethod()
{
// Set Property
g.RegirsterR0 = 10;
// Get property
int _myProperty = g.RegirsterR0;
}
}
If you want initialize new object of class Foo with Value you can:
class Foo
{
private int regirsterR0;
public int RegirsterR0
{
get { return regirsterR0; }
set { regirsterR0 = value; }
}
}
class Goo
{
Foo g = new Foo() { RegirsterR0 = 10 };
void myMethod()
{
Console.WriteLine("My Value is: {0}", g.RegirsterR0);
}
}
But usualy you don't need use propfull. Will be fine if you use prop + 2xTAB. Example:
class Foo
{
public int RegirsterR0 { get; set; }
}
class Goo
{
Foo g = new Foo() { RegirsterR0 = 10 };
void myMethod()
{
Console.WriteLine("My Value is: {0}", g.RegirsterR0);
}
}
Wrok the same and easer to read.

DefaultValue not working as expected when used with Custom Type Convertor

Per the below sample code posted I am able to see the values as Risk and Default in the dropdown.
But since I have a setting [DefaultValue("Risk")] above the property named "DummyProperty" I would expect the Risk value selected in the Property Grid Dropdown. But it’s not happening. What am I missing here?
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
string sDummy;
[DefaultValue("Risk")]
[Category("Test")]
[ParamDesc("SystemType")]
[TypeConverter(typeof(PropertyGridTypeConverter))]
public String DummyProperty
{
get { return sDummy; }
set { sDummy = value; }
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class ParamDesc : Attribute
{
public ParamDesc(string PD)
{ PropDesc = PD; }
public string PropDesc
{ get; set; }
}
class PropertyGridTypeConverter : TypeConverter
{
List<string> lst = new List<string>();
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
if (context != null)
{
AttributeCollection ua = context.PropertyDescriptor.Attributes;
ParamDesc cca = (ParamDesc)ua[typeof(ParamDesc)];
switch (cca.PropDesc)
{
case "SystemType":
lst = new List<string> {"Risk", "Default"};
break;
case "DateType":
lst = new List<string> {"Daily", "Monthly"};
break;
}
}
lst.Sort();
return new StandardValuesCollection(lst);
}
}
Somewhat confusingly, the DefaultValue custom attribute isn't used to set default values on properties like you want. In fact, it isn't directly used by the runtime at all. It's intended instead for use by the Visual Studio designer.
You'll probably just want to initialize the value elsewhere (such as in the UserControl1 constructor).
More information here:
.Net DefaultValueAttribute on Properties

Use business objects as return value from a test data builder class

The business object is Foo.cs
What if Foo`s properties run custom logic? Would it then not a bad idea to create Foo objects which could change the data inside the Foo object and it returns values I do not expect?!
public class FooBuilder
{
private string bar = "defaultBar";
private string baz = "defaultBaz";
private string bling = "defaultBling";
public FooBuilder Bar(string value)
{
bar = value;
return this;
}
public FooBuilder Baz(string value)
{
baz = value;
return this;
}
public FooBuilder Bling(string value)
{
bling = value;
return this;
}
public Foo Build()
{
return new Foo {Bar = bar, Baz = baz, Bling = bling};
}
}
If I understand what you are asking...
It is ok for proprties to execute some code more than setting a backing fields. However if you set a property to one value and then use the getter and discover that it has returned a different value to what you set then that is unexpected. So either this should be avoided within properties or use methods that provide descriptive meaning as to the behaviour/data change.
so this IMO is bad
var foo = new Foo();
foo.Bar = "hello bar";
string bar = foo.Bar;
Console.WriteLine(bar); // if this does not print "hello bar" then it is bad/unexpected

fully initialized class

I am using initiazling property in a class
and i want to run a validation method after it fully initialized.
i cant use the constructor for obvious reasons. is there a way to do that in some kind of Class initialized event ?
var t = new Foo
{
foo = "";
}
class Foo
{
public string foo {get; set;}
...
public bool validate {get ; set;}
private void validation()
{
if(foo == "")
validate = false;
if ...
}
}
(Note: for clarity, I renamed the property to Bar, in order to easily distinguish it from the type Foo)
If the Bar property must be valid upon construction, why are you not requiring it in the constructor? Why are you allowing the construction of invalid objects?
class Foo
{
public Foo(string bar) {
if(!IsValidBar(bar))
throw new ArgumentException("bar is not valid.", "bar");
this.Bar = bar;
}
public string Bar {get; set;}
private bool IsValidBar(string bar)
{
// blah blah
}
}
Alternatively, if you can construct an instance of Foo without the value of the Bar property, but you don't want to allow setting Bar to an invalid value, you can validate this in the setter:
class Foo
{
private string bar;
public string Bar
{
get { return bar; }
set
{
if(!IsValidBar(value))
throw new ArgumentException("bar is not valid.", "value");
bar = value;
}
}
private bool IsValidBar(string bar)
{
// blah blah
}
}
You can add verification logic to properties. Verify if class initialized after property assigned and raise static event if initialization completed. You can get reference to instance by casting event sender to Foo.
public string Foo
{ get { return _foo; }
set
{
_foo = value;
if (IsInitialized)
OnClassInitialized();
}
}
public static event EventHandler ClassInitialized;
private OnClassInitialized()
{
if (ClassInitialized != null)
ClassInitialized(this, EventArgs.Empty);
}
Usage:
Foo.ClassInitialized += (sender, e) =>
{
Foo foo = sender as Foo;
...
};
One approach is an interface designed for validation purposes; IValidation for instance. IValidation could then contain a Validate method. Classes which need to provide the behavior can now do so in a manageable way.
This prevents bloating within the constructor which IMHO is bad design.
You can use Aspect Oriented Programming like postsharp. http://www.postsharp.org/. But you lose on performance.
You can avoid using proprty initializers, and just move all that code to the constructor, using optional parameters if there are lots of properties. That way, you'll get kind-of property initializer constructor, and yet still be able to validate the class after the initialization is done. Something like this:
class Foo
{
public string Foo {get; set;}
public string Bar {get; set;}
public bool IsValid {get ; set;}
private void Validation()
{
if(foo == "")
IsValid = false;
if ...
}
public void Foo(string foo = string.Empty, string bar = string.Empty)
{
Foo = foo;
Bar = bar;
Validation();
}
}
.....
var t = new Foo (Foo = "SomeString");
The downside is that this is relatively new C# 4 syntax.
If you can't use c# 4, you could use the property accessors to enable validation, e.g. like:
public string Foo
{
get { return foo; }
set
{
foo = value;
Validation();
}
}
but this will evaluate the validity on each set, and might be slow if you set a lot of properties at once. You could also use the get accessor on combination with some lazy loading, something like this:
public bool IsValid
{
get
{
if (!isValidated)
Validation();
return isValid;
}
private set { isValid = value; }
}
public string Foo
{
get { return foo; }
set
{
foo = value;
isValidated := false;
}
}

DataGridView List<T> column?

I have a datagridView, that is bound to a List. This List is made up of my class which contains 2 public properties, a String Name, and another List CustomList. See below:
public class MyClass2
{
public string Name
{ get; set;}
public string Description
{
get;
set;
}
}
public class MyClass
{
List<MyClass2> myList;
public string Name
{
get;
set;
}
public List<MyClass2> CustomList
{
get { return myList ?? (myList= new List<MyClass2>()); }
}
}
And then in my designer page:
List<MyClass> myClassList = new List<MyClass>();
dataGridView.DataSource = myClassList;
As it is right now, the only column that appears in the grid, is the MyClass:Name column, and the CustomList column does not show up. What I'd like is the CustomList column to show and to display something like "Collection" with the "..." button showing, and when it is clicked to have the "Collection Editor" to popup.
Does anyone know if this is possible and how to enable it? If there's a tutorial or anything that would help me out I'd appreciate that too. Thanks.
Using generics, I think, is a clean solution:
public class Sorter<T>: IComparer<T>
{
public string Propiedad { get; set; }
public Sorter(string propiedad)
{
this.Propiedad = propiedad;
}
public int Compare(T x, T y)
{
PropertyInfo property = x.GetType().GetProperty(this.Propiedad);
if (property == null)
throw new ApplicationException("El objeto no tiene la propiedad " + this.Propiedad);
return Comparer.DefaultInvariant.Compare(property.GetValue(x, null), property.GetValue(y, null));
}
}
Usage example:
string orderBy = "propertyName";
bool orderAsc = true;
List<MyExampleClass> myClassList = someMethod();
if (!string.IsNullOrEmpty(orderBy))
{
myClassList.Sort(new Sorter<MyExampleClass>(orderBy));
if (!orderAsc) myClassList.Reverse();
}
Short answer: Yes, you can do it with some code.
Long answer: To write the code is gonna be a pain in the ass, as you would have to know not only how the DataGridView behaves with custom columns, but you would need to know how to expose design time elements at runtime, which requires quite a bit of plumbing. Extensive knowledge about the PropertyGrid must also be known.
Note: This might a fun component to write. (I might actually tackle it if I get some time)
So using the 'button' approach posted by Dave, and some code that I found that implements the CollectionEditor, I can edit the CustomList in MyClass2
Here's my solution, although not quite as clean as I'd like:
Put this class somewhere:
class MyHelper : IWindowsFormsEditorService, IServiceProvider, ITypeDescriptorContext
{
public static void EditValue(IWin32Window owner, object component, string propertyName)
{
PropertyDescriptor prop = TypeDescriptor.GetProperties(component)[propertyName];
if (prop == null) throw new ArgumentException("propertyName");
UITypeEditor editor = (UITypeEditor)prop.GetEditor(typeof(UITypeEditor));
MyHelper ctx = new MyHelper(owner, component, prop);
if (editor != null && editor.GetEditStyle(ctx) == UITypeEditorEditStyle.Modal)
{
object value = prop.GetValue(component);
value = editor.EditValue(ctx, ctx, value);
if (!prop.IsReadOnly)
{
prop.SetValue(component, value);
}
}
}
private readonly IWin32Window owner;
private readonly object component;
private readonly PropertyDescriptor property;
private MyHelper(IWin32Window owner, object component, PropertyDescriptor property)
{
this.owner = owner;
this.component = component;
this.property = property;
}
#region IWindowsFormsEditorService Members
public void CloseDropDown()
{
throw new NotImplementedException();
}
public void DropDownControl(System.Windows.Forms.Control control)
{
throw new NotImplementedException();
}
public System.Windows.Forms.DialogResult ShowDialog(System.Windows.Forms.Form dialog)
{
return dialog.ShowDialog(owner);
}
#endregion
#region IServiceProvider Members
public object GetService(Type serviceType)
{
return serviceType == typeof(IWindowsFormsEditorService) ? this : null;
}
#endregion
#region ITypeDescriptorContext Members
IContainer ITypeDescriptorContext.Container
{
get { return null; }
}
object ITypeDescriptorContext.Instance
{
get { return component; }
}
void ITypeDescriptorContext.OnComponentChanged()
{ }
bool ITypeDescriptorContext.OnComponentChanging()
{
return true;
}
PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor
{
get { return property; }
}
#endregion
Add a button column to the data grid:
DataGridViewButtonColumn butt = new DataGridViewButtonColumn();
butt.HeaderText = "CustomList";
butt.Name = "CustomList";
butt.Text = "Edit CustomList...";
butt.UseColumnTextForButtonValue = true;
dataGridView.Columns.Add(butt);
dataGridView.CellClick += new DataGridViewCellEventHandler(dataGridView_CellClick);
Then call it in the button handler of the cell click.
if (e.RowIndex < 0 || e.ColumnIndex != dataGridView.Columns["CustomList"].Index)
return;
//get the name of this column
string name = (string)dataGridView[dataGridView.Columns["Name"].Index, e.RowIndex].Value;
var myClassObject= myClassList.Find(o => o.Name == name);
MyHelper.EditValue(this, myClassObject, "CustomList");
I'd still be interested in hearing other approaches, and not having to implement my own CollectionEditor. And I'm still interested in having it look more like what the TabControl uses to add TabPages in the PropertyGrid...by displaying the "..." button...but this might work for now.
What you want to do is add a column template with a button in it:
http://geekswithblogs.net/carmelhl/archive/2008/11/11/126942.aspx
In the handler for the button, get the selected MyClass item from the collection and bind its list property to a grid in your popup.

Categories

Resources