WPF PropertyGrid Problem - wpf

I am trying to create a WPF based PropertyGrid.
Recently i tried wpg.codeplex.com project, but i had some problems with this control.
Now, i am trying to develop http://blog.joachim.at/?p=36 this project.
I successfully added Enum Values, support but i have got problems with collections.
For example my custom object has a property that name is City and type is Guid.
I want, users can select City from combobox.
I was fighting with TypeConverts, IValueConverts, and etc..
How can i solve this?

After hours of work i solved my problem.
I had need only TypeConverter to solve this, so i created a class that derives from TypeConverter.
class PierListConverter : TypeConverter
{
ArrayList piers = new ArrayList();
public PierListConverter()
{
}
public override bool
GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection
GetStandardValues(ITypeDescriptorContext context)
{
// This method returns me the list that will use to fill combo at property grid.
piers.Clear();
foreach (var item in GullsEyeModel.GetInstance().GetPiers())
{
piers.Add(item.Id);
}
StandardValuesCollection cols = new StandardValuesCollection(piers);
return cols;
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
// If this method returns true, ConvertFrom method will invoke
if (sourceType == typeof(string))
{
return true;
}
else
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
// In this method i am getting selected text and querying; after that i retrieve proparete Guid value and then returning back to my object that binded property grid.
if (value != null)
{
if (value.ToString() == "Seçiniz")
{
return Guid.Empty;
}
else if (!string.IsNullOrEmpty(value.ToString()))
{
GuidConverter g = new GuidConverter();
PierItem[] pierArray = GullsEyeModel.GetInstance().GetPiers();
PierItem selectedPier = pierArray.Where(item => item.Info.Name == value.ToString()).FirstOrDefault();
if (selectedPier != null)
{
return selectedPier.Id;
}
else
return base.ConvertFrom(context, culture, value);
}
else
return base.ConvertFrom(context, culture, value);
}
else
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
// In this method i am converting ID to string (Name) to display in Property Grid
if (value != null)
{
GuidConverter g = new GuidConverter();
PierItem[] piers = GullsEyeModel.GetInstance().GetPiers();
PierItem selectedPier = piers.Where(item => item.Id== (Guid)g.ConvertFromString(value.ToString())).FirstOrDefault();
if (selectedPier != null)
{
return selectedPier.Info.Name;
}
else
return "Seçiniz";
}
else
return base.ConvertTo(context, culture, value, destinationType);
}
}
Using custom TypeConverter
class MyClass
{
// my some props..
// my some props..
[TypeConverter(typeof(PierListConverter))]
public Guid PierId {get; set;}
// my some methods..
}

Related

How to use custom object's list with typeconverter in winform?

I made custom type which can use in my custom winform control.
I want to display this custom type property in to default winform property window or my custom smart grid.
So i made type converter for my custom type.
[Serializable]
public class TestObj
{
private int a;
private int b;
public int A { get { return a; } set { a = value; } }
public int B { get { return b; } set { b = value; } }
public TestObj()
{
}
public TestObj(int a, int b)
{
this.a = a;
this.b = b;
}
}
// TestObj Converter
public class TestObjConverter : TypeConverter
{
//can convert string -> testobj?
public override bool CanConvertFrom(ITypeDescriptorContext context, >
Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
// string -> TestObj
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
string[] v = ((string)value).Split(new char[] { ' ' });
return new TestObj(Int32.Parse(v[0]), Int32.Parse(v[1]));
}
return base.ConvertFrom(context, culture, value);
}
// TestObj -> string
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value, Type
destinationType)
{
if (destinationType == typeof(string))
{
return ((TestObj)value).A + " " + ((TestObj)value).B;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
(If i did not made this typeconverter then resx error will occurred.)
Now i can use TestObj type for my custom control's property.
But i want to use list collection property for this type in my custom control class.
private List<TestObj> testData = new List<TestObj>();
[Category("CustomControl"), Description("Property")]
[TypeConverter(typeof(TestObjConverter))]
public List<TestObj> TestData
{
get { return testData; } set { testData = value; }
}
But i can not use this type's list for property.
If i use this, the collection editor was opened and i can add TestObj element too. But visual studio invoke error [invalid resx file, Culture=newtral, PublicKeyTokrn=null ] when i compile this.
How to use my custom type list in winform property?
But
This may be a little late but someone else may run across this. Your TestObj class that is being used for the property must be declared with the type converter and not the actual property you are exposing. the property is typed with the class as any property should.
[TypeConverter(typeof(TestObjConverter))]
public class TestObj
{
...
}
then in your main control class you just expose the property like normal without the type converter declaration like you have done
[Category("CustomControl"), Description("Property")]
public List<TestObj> TestData { get; set; }
I have several custom controls for winforms if you want a working example of this. Just ask me and i will send one to you

Winforms Designer custom property does not keep its value when I build

I have a control that I subclass from Label (as an example) and add a custom property to it. I have a custom editor that allows me to pick an object of a custom type. The custom type is attributed with the converter and and editor. Editing works fine - I click in my property cell in the VS designer property grid and the ellipsis displays. I click that and I get my custom form displaying my list to choose from. I make a selection and that selection shows up in my property grid. All good. However, I can not seem to clear that value by backspacing over it like I can with most properties. And more importantly, as soon as I build the project the property value disappears. Before I build, I can go from control to control and the value is set properly, but as soon as I build (or save and close the form) the property loses its value. My custom control looks like this:
public class MyLabel : Label
{
private MyAlias _alias;
public MyAlias Alias
{
get
{
return _alias;
}
set
{
if (_alias != value)
{
_alias = value;
}
}
}
}
The custom type:
[System.ComponentModel.TypeConverter(typeof(MyConverter))]
[System.ComponentModel.Editor(MyTypeEditor, System.Drawing.Design.UITypeEditor)]
public class MyAlias
{
private string _aliasName;
public string AliasName
{
get
{
return _aliasName;
}
set
{
if (_aliasName != value)
{
_aliasName = value;
}
}
}
public MyAlias(string aliasName)
{
if (aliasName == null)
{
return;
}
_aliasName = aliasName;
}
public override string ToString()
{
return _aliasName;
}
}
Custom converter:
public class MyConverter : 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 (!(value is string))
{
return base.ConvertFrom(context, culture, value);
}
return new MyAlias((string)value);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == null)
{
throw new ArgumentNullException("destinationType");
}
MyAlias alias = (MyAlias)value;
return alias.ToString();
}
}
Custom Editor:
public partial class AliasEditorForm : Form
{
private object _value;
public object Value
{
get
{
return _value;
}
set
{
if (_value != value)
{
_value = value;
}
}
}
public AliasEditorForm()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// Button does have DialogResult property set to OK
_value = new MyAlias(...text string from form controls here...);
this.Close();
}
}
public class MyTypeEditor : UITypeEditor
{
protected IWindowsFormsEditorService edSvc = null;
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (null != context && null != context.Instance && null != provider)
{
edSvc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
if (null != edSvc)
{
AliasEditorForm form = new AliasEditorForm();
form.Value = value;
DialogResult r = edSvc.ShowDialog(form);
return (DialogResult.OK == r) ? form.Value : value;
}
}
return value;
}
}
It's been a long time since I've worked in WinForms and I never really did much dabbling with design-time stuff so forgive me if I've done something terribly stupid. If I didn't provide enough info, let me know. Thanks in advance!
EDIT: I notice it is not generating the code for the property in the form.Designer.cs file. Still not sure why...
EDIT: Changed the code for the type converter. I can now clear the property by backspacing. I still can't get the designer to generate the code to preserve the property value.
Dennis

WPF Propertygrid with custom sorting

I'm looking for a PropertyGrid for my WPF project that allows me to customize the ordering the properties / categories are listed. Right now I'm using Extended WPF Toolkits (Community Edition) PropertyGrid with CustomPropertyDescriptors. My researches showed, that it's not possible to have custom sorting with that PropertyGrid.
Is there a (preferably free) solution?
Ordering of properties in the Extended WPF Toolkit can be achieved by decorating the property with the PropertyOrderAttribute attribute.
If you don't want to pollute POCO's by decorating them with attributes at design time, or the order is dynamic in some way, then it's possible to add the attribute at run time by creating a type converter and overriding the GetProperties method. For example, if you wish to maintain the index order of a generic IList type:
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using System.ComponentModel;
public class MyExpandableIListConverter<T> : ExpandableObjectConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
if (value is IList<T>)
{
IList<T> list = value as IList<T>;
PropertyDescriptorCollection propDescriptions = new PropertyDescriptorCollection(null);
IEnumerator enumerator = list.GetEnumerator();
int counter = -1;
while (enumerator.MoveNext())
{
counter++;
propDescriptions.Add(new ListItemPropertyDescriptor<T>(list, counter));
}
return propDescriptions;
}
else
{
return base.GetProperties(context, value, attributes);
}
}
}
With the ListItemPropertyDescriptor being defined as follows:
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using System.ComponentModel;
public class ListItemPropertyDescriptor<T> : PropertyDescriptor
{
private readonly IList<T> owner;
private readonly int index;
public ListItemPropertyDescriptor(IList<T> owner, int index) : base("["+ index+"]", null)
{
this.owner = owner;
this.index = index;
}
public override AttributeCollection Attributes
{
get
{
var attributes = TypeDescriptor.GetAttributes(GetValue(null), false);
//If the Xceed expandable object attribute is not applied then apply it
if (!attributes.OfType<ExpandableObjectAttribute>().Any())
{
attributes = AddAttribute(new ExpandableObjectAttribute(), attributes);
}
//set the xceed order attribute
attributes = AddAttribute(new PropertyOrderAttribute(index), attributes);
return attributes;
}
}
private AttributeCollection AddAttribute(Attribute newAttribute, AttributeCollection oldAttributes)
{
Attribute[] newAttributes = new Attribute[oldAttributes.Count + 1];
oldAttributes.CopyTo(newAttributes, 1);
newAttributes[0] = newAttribute;
return new AttributeCollection(newAttributes);
}
public override bool CanResetValue(object component)
{
return false;
}
public override object GetValue(object component)
{
return Value;
}
private T Value
=> owner[index];
public override void ResetValue(object component)
{
throw new NotImplementedException();
}
public override void SetValue(object component, object value)
{
owner[index] = (T)value;
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override Type ComponentType
=> owner.GetType();
public override bool IsReadOnly
=> false;
public override Type PropertyType
=> Value?.GetType();
}
Portions of this code were adapted from the following SO answer

c# TypeConverter and InstanceDescriptor pain

I made an example of UserControl and still (spent whole day) unable to fix my problem.
I want UserControl to have a complex property, to see that property inside property grid at design-time, able to change it so on.
Complex property is simple - its a class with one string property.
Now i have 2 problems:
1) When i change the value of text at property grid - the value dont goes to Form1.Designer.cs
2) Sometimes when i rebuild project (even dont need to run) i have a VS's popup where said SettingsCoverter is unable to convert Settings to InstanceDescriptor. Those Settings classes are mine.
Please help to fix that.
[TypeConverter(typeof(SettingsConverter))]
public class Settings : INotifyPropertyChanged
{
private string stringText = "123";
public string StringText
{
get { return stringText; }
set
{
stringText = value;
OnPropertyChanged("StringText");
}
}
public Settings()
{
}
public Settings(string fText)
{
StringText = fText;
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(name));
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
and
class SettingsConverter : ExpandableObjectConverter
{
public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
{
return true;
}
public override object CreateInstance(ITypeDescriptorContext context, System.Collections.IDictionary propertyValues)
{
return new Settings((string)propertyValues["StringText"]);
}
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) || destinationType == typeof(InstanceDescriptor))
return true;
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (value is Settings)
{
if (destinationType == typeof(InstanceDescriptor))
{
Settings settings = (Settings)value;
object[] properties = new object[1];
Type[] types = new Type[1];
types[0] = typeof(string);
properties[0] = settings.StringText;
ConstructorInfo ci = typeof(Settings).GetConstructor(types);
return new InstanceDescriptor(ci, properties);
}
if (destinationType == typeof(string))
{
Settings settings = (Settings)value;
return settings.StringText;
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value == null)
return "";
if (value is string)
return new Settings(value as string);
return base.ConvertFrom(context, culture, value);
}
}
2) here is the advise
http://forums.asp.net/t/1309871.aspx?TypeConverter+Error+InstanceDescriptor+
The type converters go straight to the CLR's cache of type information and
do not go through the type resolution service implemented by the project
systems. This means that the CLR will return the type from the assembly it
had previously loaded prior to the rebuild - and the conversion will fail
since the type on the design surface is being loaded from the newly built
assembly. I verified that you can work around this by ensuring the version
number of the class library project gets auto-incremented on each build.
You do this by the following:
1) Bring up properties on the class library project
2) Select the "Application" tab and click the "Assembly Information..."
button.
3) In version the field set the last entry to a "*" - so it should say: 1 0
0 *
Now whenever the classlibrary is built, the revision number (the last digit
of the version) will be auto-incremented. This forces the CLR to invalidate
the entry it has cached and load the new one.

INotifyPropertyChanged does not update Value Converter

I have some properties which implement the INotifyPropertyChanged interface. It works fine. But in my code I also use some value converters (if value < 3 - make grid red, if value >3 and value < 10 - make grid blue, etc.).
The problem is how to refresh value converter after PropertyChanged was raised?
Is there simple code behind solution?
Thanks all and sorry for my bad English!
Here some code:
public class NotifyColors : INotifyPropertyChanged
{
private Color _TodayColor;
public Color TodayColor
{
get
{
return _TodayColor;
}
set
{
if (_TodayColor != value)
{
_TodayColor = value;
OnPropertyChanged("TodayColor");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
// it raised correctly when I change color with color picker control
}
}
}
// here is value converter
[ValueConversion(typeof(object), typeof(Brush))]
public class PositionToBackgroundConverter : IValueConverter
{
ModulePreferences ModulePrefs;
public PositionToBackgroundConverter(ModulePreferences ModulePrefs)
{
this.ModulePrefs = ModulePrefs;
}
#region IValueConverter Member
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (ModulePrefs.UseColoringByPosition)
{
try
{
if (value != null)
{
short value_short = (short)value;
if (value_short <= 3)
return (Brush)new SolidColorBrush(ModulePrefs.NotifyColorsObj._TodayColor); // here is changing property
else
return (Brush)new SolidColorBrush(ModulePrefs.NotifyColorsObj.T100PlusColor);
}
else
return Brushes.Transparent;
}
catch
{
return Brushes.Transparent;
}
}
else
return Brushes.Transparent;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
#endregion
}
And here I apply my value converter to the grid:
// assign backgroundconverter
var grid = new FrameworkElementFactory(typeof(Grid));
bin = new Binding();
bin.Path = new PropertyPath(string.Format("DataItem.{0}", LastPositionColumnName));
bin.Converter = new PositionToBackgroundConverter(ProjectViewObj.ModulePrefs);
grid.SetValue(Grid.BackgroundProperty, bin);
If you have done it "correctly" the PropertyChanged event will cause an update of the bindings which bind to that property, when this occurs any converters that are used in the same binding will reconvert the values. So normally the conversions happen on their own. If this is not the case you are probably using the converters "inappropriately", please post some code because without it it's quite impossible to tell what exactly you are doing wrong.
Edit: You use grid.SetValue(Grid.BackgroundProperty, bin), you should use grid.SetBinding(Grid.BackgroundProperty, bin) instead since it is a binding.
Edit2: This really has nothing to do with converters.
In your sample code you bind to IntValue, then you change TodayColor and expect the binding to be updated, not gonna happen. If you want the binding to react to both properties you have to either use a MultiBinding or raise the respective events since your properties are interdependent.
i.e.
private Color _TodayColor;
public short _IntValue;
public short IntValue
{
get { return _IntValue; }
set
{
if (_IntValue != value)
{
_IntValue = value;
OnPropertyChanged("IntValue");
OnPropertyChanged("TodayColor");
}
}
}
public Color TodayColor
{
get { return _TodayColor; }
set
{
if (_TodayColor != value)
{
_TodayColor = value;
OnPropertyChanged("TodayColor");
OnPropertyChanged("IntValue");
}
}
}

Resources