I have created a custom WinForms hosting environment. Which has a toolbox and a PropertyGrid.
The controls displayed in the Toolbox are inherited from existing WinForm controls.
DropDownList Source:
public interface IPropertyFilter : ICustomTypeDescriptor
{
PropertyDescriptorCollection FilterProperties(PropertyDescriptorCollection pdc);
List<string> GetPropertiesToShow();
}
[Serializable]
public class DropDownList : System.Windows.Forms.ComboBox, IPropertyFilter
{
public DropDownList()
{
}
#region IPropertyFilter Members
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public string GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(this, attributes, true);
return FilterProperties(pdc);
}
PropertyDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetProperties()
{
PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(this, true);
return FilterProperties(pdc);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, true);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
public string GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
public PropertyDescriptorCollection FilterProperties(PropertyDescriptorCollection pdc)
{
// Filter out properties that we do not want to display in PropertyGrid
return ControlDesignerHelper.GetBrowsableProperties(pdc, GetPropertiesToShow());
}
// Determines what properties of this control has to be shown in PropertyGrid
public List<string> GetPropertiesToShow()
{
// get a list of common properties that we want to show for all controls
List<string> browsableProps = ControlDesignerHelper.GetBasePropertiesToShow();
// add properties that are specific to this controls
browsableProps.Add("Items");
browsableProps.Add("AutoPostBack");
browsableProps.Add("AppendDataBoundItems");
browsableProps.Add("DataTextField");
browsableProps.Add("DataValueField");
return browsableProps;
}
#endregion
}
I have implemented ICustomTypeDescriptor to filter out properties that I do not want to show in the PropertyGrid.
Problem:
I am facing problem while serializing values of Enabled & Visible properties that are inherited from System.Windows.Forms.Control class.
WriteProperties Method (BasicDesignerLoader):
private void WriteProperties(XmlDocument document, PropertyDescriptorCollection properties, object value, XmlNode parent, string elementName)
{
foreach (PropertyDescriptor prop in properties)
{
System.Diagnostics.Debug.WriteLine(prop.Name);
if (prop.ShouldSerializeValue(value))
{
string compName = parent.Name;
XmlNode node = document.CreateElement(elementName);
XmlAttribute attr = document.CreateAttribute("name");
attr.Value = prop.Name;
node.Attributes.Append(attr);
DesignerSerializationVisibilityAttribute visibility = (DesignerSerializationVisibilityAttribute)prop.Attributes[typeof(DesignerSerializationVisibilityAttribute)];
switch (visibility.Visibility)
{
case DesignerSerializationVisibility.Visible:
if (!prop.IsReadOnly && WriteValue(document, prop.GetValue(value), node))
{
parent.AppendChild(node);
}
break;
case DesignerSerializationVisibility.Content:
object propValue = prop.GetValue(value);
if (typeof(IList).IsAssignableFrom(prop.PropertyType))
{
WriteCollection(document, (IList)propValue, node);
}
else
{
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(propValue, propertyAttributes);
WriteProperties(document, props, propValue, node, elementName);
}
if (node.ChildNodes.Count > 0)
{
parent.AppendChild(node);
}
break;
default:
break;
}
}
}
}
Problem # 1: The ShouldSerializeValue method for the Enabled & Visible property always returns false.
Problem # 2: Even if I skip the ShouldSerializeValue method check the GetValue method of the PropertyDescriptor always returns True.
Current Workaround:
As a workaround I have currently made the Enabled & Visible properties hidden using the BrowsableAttribute, and created two other boolean properties and used the DisplayNameAttribute to change their display name to be Enable & Visible.
But for this workaround I have to write these snippets in every control.
Am I missing something or doing anything wrong? Why are the Enabled & Visible property do not change?
You will find a long discussion about this issue here. (dead link, can't find a new one)
This MSDN page aldo makes this remark:
The InheritedPropertyDescriptor class
modifies the default value of a
property, so that the default value is
the current value at object
instantiation. This is because the
property is inherited from another
instance. The designer defines
resetting the property value as
setting it to the value that was set
by the inherited class. This value may
differ from the default value stored
in metadata.
ShouldSerializeValue's return value is based on the difference between the current value and the default value so I think this is directly related to your problem.
I hope this will help you figure out what happens in your own context.
Related
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
I am adding the Property grid control in my project.
I have to show the Drop down box in one field of Property Grid.
Is there any solution to apply this.
You have to declare a type editor for the property in your PropertyGrid and then add to the list of choices. This example creates a Type Converter and then overrides the GetStandardValues() method to provide choices to the drop-down:
private String _formatString = null;
[Category("Display")]
[DisplayName("Format String")]
[Description("Format string governing display of data values.")]
[DefaultValue("")]
[TypeConverter(typeof(FormatStringConverter))]
public String FormatString { get { return _formatString; } set { _formatString = value; } }
public class FormatStringConverter : StringConverter
{
public override Boolean GetStandardValuesSupported(ITypeDescriptorContext context) { return true; }
public override Boolean GetStandardValuesExclusive(ITypeDescriptorContext context) { return true; }
public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
List<String> list = new List<String>();
list.Add("");
list.Add("Currency");
list.Add("Scientific Notation");
list.Add("General Number");
list.Add("Number");
list.Add("Percent");
list.Add("Time");
list.Add("Date");
return new StandardValuesCollection(list);
}
}
The key is the property being assigned a Type Converter in the line:
[TypeConverter(typeof(FormatStringConverter))]
That provides you with the opportunity to introduce your own behavior via overrides.
Here's a simpler example, which allows the Enum type of a Property to automatically provide its values to the PropertyGrid drop-down:
public enum SummaryOptions
{
Sum = 1,
Avg,
Max,
Min,
Count,
Formula,
GMean,
StdDev
}
private SummaryOptions _sumType = SummaryOptions.Sum;
[Category("Summary Values Type")]
[DisplayName("Summary Type")]
[Description("The summary option to be used in calculating each value.")]
[DefaultValue(SummaryOptions.Sum)]
public SummaryOptions SumType { get { return _sumType; } set { _sumType = value; } }
By virtue of the fact that the property is an Enum type, those enum values pass through to become the drop-down options automatically.
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
I've seen answers mentioning something compact like this:here
List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();
So I tried the following (syntax)
List<InfoControl> withDupes = (List<InfoControl>)listBox1.ItemsSource;
listBox1.ItemsSource = withDupes.Distinct().ToList();
but withDupes is null ? Perhaps I am retrieving the wrong data list. I added InfoControls one at a time.
Is there something else I should be implementing in InfoControl class? (Equal,hashCode)?
Thanks
Addendum 1: [ignore I should not be translating from Java :) ]
Also have (translated from a Java example, not sure it's 100% correct) declared in the InfoControl class..
public Boolean Equals(Object obj)
{ if (obj == this) { return true; }
if (!(obj is InfoControl)) { return false; }
InfoControl other = (InfoControl)obj;
return this.URL.Equals(other.URL); }
public int hashCode()
{ return this.URLFld.Content.GetHashCode(); }
Addendum 2:
When I try to use override based on the msdn link custom type example it says it is sealed :)
It does not seem distinct is stepping thru GetHashCode() and I am still getting the same listbox.items.count after distinct.
bool IEquatable<InfoControl>.Equals(InfoControl other)
{
if (Object.ReferenceEquals(other, null)) return false;
if (Object.ReferenceEquals(this, other)) return true;
return URL.Equals(other.URL);
}
public int GetHashCode(InfoControl obj)
{
return obj.URL.GetHashCode();
}
Addendum 3:
When I try override VS2010 says it is sealed? "cannot override inherited member 'System.Windows.DependencyObject.GetHashCode()' because it is sealed" what am I doing wrong?
public override int GetHashCode()
{
return URL.GetHashCode();
}
public string URL
{
get { return this.URLFld.Content.ToString() ; }
set
{
this.URLFld.Content = value;
}
}
.
Addendum 4:
public partial class InfoControl : UserControl
, IEquatable<YouTubeInfoControl>
{
private string URL_;
public string URL
{
get { return URL_; }
set
{
URL_ = value;
}
}
bool IEquatable<YouTubeInfoControl>.Equals(YouTubeInfoControl other)
{
if (Object.ReferenceEquals(other, null)) return false;
if (Object.ReferenceEquals(this, other)) return true;
return URL == other.URL;
}
public override int GetHashCode()
{
return URL.GetHashCode();
}
}
A ListBox's items can either be set via ListBox.Items or ListBox.ItemsSource, if you add items using listBox1.Items.Add this does not affect the ItemsSource which will stay null. In this case you should get your initial list from listBox1.Items.
If you're adding the InfoControl objects one at a time, the ItemSource of the listBox will remain set to NULL. You're better off binding a List to the listbox which will allow you to get the data back off of the ItemSource property later
Are there any known issues when databinding to a control's visible property?
The control is always NOT visible regardless of what my property is.
Public ReadOnly Property IsRibbonCategory() As Boolean
Get
Return True
End Get
End Property
I tried the control's text property and other properties and they seem to work correctly.
I am trying to set a Panel's visible property.
I've found that life is better if you assume that binding to a control's Visible property is broken, despite the fact that it sometimes works. See http://support.microsoft.com/kb/327305, which says as much (and while the KB article applies to .NET 1.0 and 1.1, it still seems to be a problem in at least 2.0).
I created a utility class for creating bindings which, among other things, gave me a centralized place to add a work-around. Instead of actually creating a binding on Visible it does two things:
It subscribes to the data source's INotifyPropertyChanged.PropertyChanged event and sets the Visible value as appropriate when the event is raised.
It sets the initial value of Visible according to the current data source value.
This required a little reflection code, but wasn't too bad. It is critical that you don't bind the Visible property and do the work-around or it won't work.
Workaround: Set the Visible property on the BindingComplete event.
I had same issue setting a label's Visible property - always stays false, even though setting the Enabled property works fine.
I just hit this issue in .NET 4.7.1 and Visual Studio 2017. To fix it, I changed the Visible property on my control to be initially set to True, as I had it as False previously.
Things to check:
Be sure you've instantiated the class that has the IsRibbonCategory property
Did you set the datasource of property of the binding source to the instance of the class
The datasource update mode should be on "on validation"
Make sure you didn't set the visible property manually to false on the control
Hope that helps. Can you post more code?
A workaround would be to use a Component to databind to a control's visiblity property instead of directly binding to the control's visibility property.
See below code:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public class ControlVisibilityBinding : Component
{
private static readonly object EventControlChanged = new object();
private static readonly object EventVisibleChanged = new object();
private System.Windows.Forms.Control _control;
private bool _visible = true;
public event EventHandler VisibleChanged
{
add { Events.AddHandler(EventVisibleChanged, value); }
remove { Events.RemoveHandler(EventVisibleChanged, value); }
}
public event EventHandler ControlChanged
{
add { Events.AddHandler(EventControlChanged, value); }
remove { Events.RemoveHandler(EventControlChanged, value); }
}
public ControlVisibilityBinding()
{
}
public ControlVisibilityBinding(IContainer container)
{
container.Add(this);
}
[DefaultValue(null)]
public System.Windows.Forms.Control Control
{
get { return _control; }
set
{
if(_control == value)
{
return;
}
WireControl(_control, false);
_control = value;
if(_control != null)
{
_control.Visible = _visible;
}
WireControl(_control, true);
OnControlChanged(EventArgs.Empty);
OnVisibleChanged(EventArgs.Empty);
}
}
[DefaultValue(true)]
public bool Visible
{
get { return _visible; }
set
{
if(_visible != value)
{
_visible = value;
}
if(Control != null)
{
Control.Visible = _visible;
}
OnVisibleChanged(EventArgs.Empty);
}
}
private void WireControl(Control control, bool subscribe)
{
if(control == null)
{
return;
}
if(subscribe)
{
control.VisibleChanged += Control_VisibleChanged;
}
else
{
control.VisibleChanged -= Control_VisibleChanged;
}
}
private void Control_VisibleChanged(object sender, EventArgs e)
{
OnVisibleChanged(EventArgs.Empty);
}
protected virtual void OnVisibleChanged(EventArgs e)
{
EventHandler subscribers = (EventHandler)Events[EventVisibleChanged];
if(subscribers != null)
{
subscribers(this, e);
}
}
protected virtual void OnControlChanged(EventArgs e)
{
EventHandler subscribers = (EventHandler)Events[EventControlChanged];
if(subscribers != null)
{
subscribers(this, e);
}
}
}
static class Program
{
[STAThread]
static void Main()
{
using(Form form = new Form())
using(FlowLayoutPanel groupBoxLayoutPanel = new FlowLayoutPanel())
using(RadioButton visibleButton = new RadioButton())
using(RadioButton hiddenButton = new RadioButton())
using(GroupBox groupBox = new GroupBox())
using(Label text = new Label())
using(ControlVisibilityBinding visibilityBinding = new ControlVisibilityBinding())
using(TextBox inputTextBox = new TextBox())
{
groupBoxLayoutPanel.Dock = DockStyle.Fill;
groupBoxLayoutPanel.FlowDirection = FlowDirection.LeftToRight;
groupBoxLayoutPanel.AutoSize = true;
groupBoxLayoutPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
visibleButton.Text = "Show Label";
visibleButton.AutoSize = true;
hiddenButton.Text = "Hide Label";
hiddenButton.AutoSize = true;
groupBoxLayoutPanel.Controls.Add(visibleButton);
groupBoxLayoutPanel.Controls.Add(hiddenButton);
inputTextBox.Text = "Enter Label Text Here";
inputTextBox.Dock = DockStyle.Top;
groupBox.AutoSize = true;
groupBox.AutoSizeMode = AutoSizeMode.GrowAndShrink;
groupBox.Controls.Add(groupBoxLayoutPanel);
groupBox.Dock = DockStyle.Fill;
text.AutoSize = true;
text.ForeColor = Color.Red;
text.Dock = DockStyle.Bottom;
text.BorderStyle = BorderStyle.FixedSingle;
text.Font = new Font(text.Font.FontFamily, text.Font.Size * 1.25f, FontStyle.Bold | FontStyle.Italic);
text.DataBindings.Add("Text", inputTextBox, "Text", true, DataSourceUpdateMode.Never);
visibilityBinding.Control = text;
visibleButton.DataBindings.Add("Checked", visibilityBinding, "Visible", true, DataSourceUpdateMode.OnPropertyChanged);
Binding binding = hiddenButton.DataBindings.Add("Checked", visibilityBinding, "Visible", true, DataSourceUpdateMode.OnPropertyChanged);
ConvertEventHandler invertConverter = (sender, e) => e.Value = !((bool)e.Value);
binding.Format += invertConverter;
binding.Parse += invertConverter;
form.Controls.Add(inputTextBox);
form.Controls.Add(text);
form.Controls.Add(groupBox);
Application.Run(form);
}
}
}
}
Here is my turn around, it may be stupid but it worked many times.
I put one Panel control in my form, I make it to Fill my form and I put everything in that Panel. All the controls I bind the Visible property see their visibility change according to the objects in my DataGridView.