I am using a property grid that uses a dictionary using the adapter found here.
I now need the ability to use a custom editor. More specifically a file chooser. If the object in the dictionary is a string it just uses the default string editor.
Could I implement a new class called FilePath or something that would just act as a wrapper for string but would cause the property grid to use the OpenFileDialog, and display the result as a string in the PropertyGrid once chosen?
Is this possible? And if so how?
If you want to have the file path editor in the property grid using the dictionary adapter you have referenced, I would make the FilePath class like you suggest. You will need to also implement two additional classes to make this all work with the property grid: An editor and a type converter.
Let's assume your FilePath object is a simple one:
class FilePath
{
public FilePath(string inPath)
{
Path = inPath;
}
public string Path { get; set; }
}
Your property grid will display the class name in light gray, not very useful. Let's write a TypeConverter to display the string that this class really wraps around
class FilePathConverter : TypeConverter
{
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)
{
if (IsValid(context, value))
return new FilePath((string)value);
return base.ConvertFrom(context, culture, value);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
return destinationType == typeof(string);
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
return ((FilePath)value).Path;
return base.ConvertTo(context, culture, value, destinationType);
}
public override bool IsValid(ITypeDescriptorContext context, object value)
{
if (value.GetType() == typeof(string))
return true;
return base.IsValid(context, value);
}
}
Add the TypeConverter attribute to our FilePath class to convert to and from a string.
[TypeConverter(typeof(FilePathConverter))]
class FilePath
{
...
}
Now the property grid will display the string and not the type name, but you want the ellipsis to bring up a file selection dialog, so we make a UITypeEditor:
class FilePathEditor : UITypeEditor
{
public override System.Drawing.Design.UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return System.Drawing.Design.UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
FilePath path = (FilePath)value;
OpenFileDialog openFile = new OpenFileDialog();
openFile.FileName = path.Path;
if (openFile.ShowDialog() == DialogResult.OK)
path.Path = openFile.FileName;
return path;
}
}
Add the Editor attribute to our FilePath class to use the new class:
[TypeConverter(typeof(FilePathConverter))]
[Editor(typeof(FilePathEditor), typeof(UITypeEditor))]
class FilePath
{
...
}
Now you can add FilePath objects to your IDictionary and have them editable through the property grid
IDictionary d = new Dictionary<string, object>();
d["Path"] = new FilePath("C:/");
Related
I've checked the answers of this question:
Modifying structure property in a PropertyGrid
And also SizeConverter from .net.
But not helpful, my property is still not saved.
I have a struct, a user control, and a custom type converter.
public partial class UserControl1 : UserControl
{
public Bar bar { get; set; } = new Bar();
}
[TypeConverter(typeof(BarConverter))]
public struct Bar
{
public string Text { get; set; }
}
public class BarConverter : ExpandableObjectConverter
{
public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
{
return true;
}
public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
{
if (propertyValues != null && propertyValues.Contains("Text"))
return new Bar { Text = (string)propertyValues["Text"] };
return new Bar();
}
}
After compile, I drag the control in a form then I can see the property Bar.Text showed in the properties window, I also can edit the value and it seems be saved.
But nothing is generated in the InitializeComponent method
So if I reopen the designer, the Text field in the properties window become empty.
Please notice the struct hasn't a custom constructor, so I cannot use InstanceDescriptor.
Do I miss any important steps?
You are missing a few method overrides in the type descriptor:
public class BarConverter : ExpandableObjectConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(InstanceDescriptor))
return true;
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(InstanceDescriptor))
{
ConstructorInfo ci = typeof(Bar).GetConstructor(new Type[] { typeof(string) });
Bar t = (Bar)value;
return new InstanceDescriptor(ci, new object[] { t.Text });
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override object CreateInstance(ITypeDescriptorContext context,
IDictionary propertyValues)
{
if (propertyValues == null)
throw new ArgumentNullException("propertyValues");
object text = propertyValues["Text"];
return new Bar((string)text);
}
public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
{
return true;
}
}
And add constructor to the struct:
[TypeConverter(typeof(BarConverter))]
public struct Bar
{
public Bar(string text)
{
Text = text;
}
public string Text { get; set; }
}
And this is how the Bar property serializes:
//
// userControl11
//
this.userControl11.Bar = new SampleWinApp.Bar("Something");
And the bar property will be shown like following image in property grid, having Text property editable:
You may also want to provide a better string representation for the struct by overriding its ToString() method of the struct, and also make the property convertible from string in property grid, by overriding CanConvertFrom and ConvertFrom like PointConverter or SizeConverter.
I have following enumeration.
public enum Digits
{One, Two, Three}
and a property with two entries.
public List<Digits> DigitList{get;set;}
DigitList.Add(Digits.One); DigitList.Add(Digits.Three);
When this object is bound to PropertyGrid it is displayed as (Collection) and when it is opened (using small browse button) an exception with (no useful message) is displayed. I am confused how the PropertyGrid interprets list of enumerations.
I searched for a solution, but all i could find was about how to bind a enum value, not list of enums.
You have to create a TypeConverter Class that will help the PropertyEditor to parse the Enum into a PropertyEditor.
Sample TypeConverter
public class FooDataTypeConverter : TypeConverter
{
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return base.GetStandardValues(context);
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
return (sourceType.Equals(typeof(Enum)));
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (destinationType.Equals(typeof(String)));
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
return (ValidationDataType)Enum.Parse(typeof(ValidationDataType), value.ToString(), true);
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (!destinationType.Equals(typeof(string)))
throw new ArgumentException("Can only convert to string", "destinationType");
if (!value.GetType().BaseType.Equals(typeof(Enum)))
throw new ArgumentException("Can only convert an instance of enum", "value");
string name = value.ToString();
object[] attr =
value.GetType().GetField(name).GetCustomAttributes(typeof(DescriptionAttribute), false);
return (attr.Length > 0) ? ((DescriptionAttribute)attr[0]).Description : name;
}
}
After add this declaration to the enums that you want to parse in a propertyeditor.
[TypeConverter(typeof(FooDataTypeConverter ))]
public enum ValidationDataType
{
/// <summary>
/// None
/// </summary>
[Description("None")]
None,
.....
}
The last step is to add it to the property of your component that will show in the propertyeditor
[Category("Behavior")]
[Description("Gets or sets the type of data that will be compared")]
[TypeConverter(typeof(DataTypeConverter))]
[EditorAttribute(typeof(ValidatorTypeEditor), typeof(System.Drawing.Design.UITypeEditor))]
public ValidationDataType Type
{
get { return this.type; }
set
{
this.type = value;
if (this is RangeValidator)
{
this.SetRange();
}
}
}
i have hashtable in videoformates class inherited from StringConvertor class. Two functions ConvertFrom and ConvertTo override there. How iplement these two functions to show video formates as string.
public class VideoFormate : StringConverter
{
private Hashtable VideoFormates;
public VideoFormate() {
VideoFormates = new Hashtable();
VideoFormates[".3g2"] = "3GPP2";
VideoFormates[".3gp"] = "3GPP";
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(VideoFormates);
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return base.ConvertTo(context, culture, value, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return base.ConvertFrom(context, culture, value);
}
class for video properties is
class videoProperties
{
private string _VideoFormat;
[TypeConverter(typeof(VideoFormate)),
CategoryAttribute("Video Setting"),
DefaultValueAttribute(0),
DescriptionAttribute("Select a Formate from the list")]
public string VideoFormat
{
get { return _VideoFormat; }
set { _VideoFormat = value; }
}
}
i want to display 3GPP2 as display member and .3gp2 as value member in combo box in propertyGrid.
After debugging the code i found the answer.
On first run, in above code, propertyGrid dropdown fill with Hashtable collection. ConvertTo method convert it into string. so it save as System.Collections.DictionaryEntry.
when select an element from combo box it set videoProperties.VideoFormat as System.Collections.DictionaryEntry. So i changed ConvertTo function as below.
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value is DictionaryEntry)
{
return ((DictionaryEntry)value).Key;
}
else if (value != null)
{
return VideoFormates[value];
}
return base.ConvertTo(context, culture, value, destinationType);
}
function check if value type is DictionaryEntry then it cast it and return the key element of value parameter. So combo box fill out with key values. when form shows ConvertTo function again called and second time return combo box values. then simplay return Hashtable value on the base of combo box value.
I'm using some third party code which uses TypeConverters to "cast" objects to types specified as generic parameters.
The 3rd party code gets the string type converter, and expects to do all conversions through that e.g.
var typeConverter = TypeDescriptor.GetConverter(typeof(string));
I've written a custom type, and type converter for it (and registered it with the TypeDescriptor attribute) but it's not getting used by the 3rd party code, which fails on the call to typeConverter.CanConvertTo(MyCustomType)
Until today I'd only encountered TypeConverters in the abstract, I've seen mentions of them but never built or used one.
Has anyone any idea what I'm doing wrong here?
My - cut down - code
using System;
using System.ComponentModel;
namespace IoNoddy
{
[TypeConverter(typeof(TypeConverterForMyCustomType))]
public class MyCustomType
{
public Guid Guid { get; private set; }
public MyCustomType(Guid guid)
{
Guid = guid;
}
public static MyCustomType Parse(string value)
{
return new MyCustomType(Guid.Parse(value));
}
}
public class TypeConverterForMyCustomType
: TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return ((sourceType == typeof(string)) || base.CanConvertFrom(context, sourceType));
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
string strValue;
if ((strValue = value as string) != null)
try
{
return MyCustomType.Parse(strValue);
}
catch (FormatException ex)
{
throw new FormatException(string.Format("ConvertInvalidPrimitive: Could not convert {0} to MyCustomType", value), ex);
}
return base.ConvertFrom(context, culture, value);
}
}
}
static void Main(string[] args)
{
// Analogous to what 3rd party code is doing:
var typeConverter = TypeDescriptor.GetConverter(typeof(string));
// writes "Am I convertible? false"
Console.WriteLine("Am I convertible? {0}", typeConverter.CanConvertTo(typeof(MyCustomType)));
}
You check CanConvertTo so add to yours converter:
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (destinationType == typeof(MyCustomType)) || base.CanConvertTo(context, destinationType);
}
to some where:
public static void Register<T, TC>() where TC : TypeConverter
{
Attribute[] attr = new Attribute[1];
TypeConverterAttribute vConv = new TypeConverterAttribute(typeof(TC));
attr[0] = vConv;
TypeDescriptor.AddAttributes(typeof(T), attr);
}
and to main:
Register<string, TypeConverterForMyCustomType>();
var typeConverter = TypeDescriptor.GetConverter(typeof(string));
yours sample shuld work after that.
I have a FileNameEditor inside a property grid, which has a few entries like
Main File : "C:\blah1"
Sec File: "C:\blah2"
and so on.
My problem is that I cannot copy and paste from one property entry to another, and I cannot type in the fields manually as well. Is there a specific property that will enable editing inside the FileNameEditor.
Example
public class MyEditor : FileNameEditor
{
public override bool GetPaintValueSupported(ITypeDescriptorContext context)
{
return false;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
object e = base.EditValue(context, provider, value);
if ((value as MyFileS) != null)
{
(value as MyFilesS).FileName = (String) e;
}
return e;
}
protected override void InitializeDialog(OpenFileDialog openFileDialog)
{
base.InitializeDialog(openFileDialog);
}
}
Thanks
Why are you using a custom editor? If you just want a string property on an object then Marc Gravell's answer works.
However if the "File" property on the object within the property grid is a custom class you also need to implement a custom type converter.
Eg:
namespace WindowsFormsApplication
{
using System;
using System.ComponentModel;
using System.Drawing.Design;
using System.Globalization;
using System.Windows.Forms;
using System.Windows.Forms.Design;
class MyEditor : FileNameEditor
{
public override bool GetPaintValueSupported(ITypeDescriptorContext context)
{
return false;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
string s = Environment.CurrentDirectory;
object e = base.EditValue(context, provider, value);
Environment.CurrentDirectory = s;
var myFile = value as MyFile;
if (myFile != null && e is string)
{
myFile.FileName = (string)e;
return myFile;
}
return e;
}
}
class MyFileTypeConverter : 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 is string)
return new MyFile { FileName = (string)value };
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
var myFile = value as MyFile;
if (myFile != null && destinationType == typeof(string))
return myFile.FileName;
return base.ConvertTo(context, culture, value, destinationType);
}
}
[TypeConverter(typeof(MyFileTypeConverter))]
[Editor(typeof(MyEditor), typeof(UITypeEditor))]
class MyFile
{
public string FileName { get; set; }
}
class MyFileContainer
{
public MyFileContainer()
{
File1 = new MyFile { FileName = "myFile1.txt" };
File2 = new MyFile { FileName = "myFile2.txt" };
}
public MyFile File1 { get; set; }
public MyFile File2 { get; set; }
}
static public class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using (var f = new Form())
using (var pg = new PropertyGrid())
{
pg.Dock = DockStyle.Fill;
pg.SelectedObject = new MyFileContainer();
f.Controls.Add(pg);
Application.Run(f);
}
}
}
}
To support editing the filename in place and also cut and paste the PropertyGrid needs to know how to convert from a string to your "File" type and back again. If you do not implement the convert to string methods in the TypeConverter the property will display the result of the object's ToString().
I suggest whipping out Reflector.Net and reading the source to some of the UITypeEditors and TypeConverters in the BCL as it can be quite informative to see how Microsoft supports editing Color, TimeSpan, DateTime etc in property grids.
Also, be careful opening file dialogs. The standard WinForms open file dialog can change your applications current working directory if you're not careful. I don't think FileNameEditor has this problem, but I've only tested this under Windows 7.
Cannot reproduce; this works fine - can copy/paste both in the grid and the popup (does your property have a setter?):
using System;
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
class Foo
{
[Editor(typeof(FileNameEditor), typeof(UITypeEditor))]
public string Name { get; set; }
[STAThread]
static void Main()
{
using (Form form = new Form())
using (PropertyGrid grid = new PropertyGrid())
{
grid.Dock = DockStyle.Fill;
form.Controls.Add(grid);
grid.SelectedObject = new Foo();
Application.Run(form);
}
}
}