I am binding a properties Grid to a bunch of custom Objects that are being written by other developers. These objects are constantly being changed and updated, so they include properties that just throw NotImplemented Exceptions. Sometimes they include properties like
[Obsolete("use thingy instead to get other thing", true)]
Instead of annoying the other developers. with things that I know will be changed later. What can I do to make sure my properties Grid doesn't break on those specific properties?
Thanks for the help. the other Developers appreciate it ;)
I imagine that you are trying to bind the PropertyGrid to the objects at runtime, not in the designer. If you mean the propertygrid in the winform designer, the answer would be different, and you should look at the postFilterEvents method of ControlDesigner.
The simplest solution would be to set the BrowsableAttribute to false for the properties that you want to hide. This means that when the other developers add the ObsoleteAttribute, they should add [Browsable(false)], too. But I understand that you'd like something more "automatic". You could write a method that changes the browsable attributes of the properties of an object before passing it to the PropertyGrid. This can be done getting the TypeDescriptor for each property, then getting its BrowsableAttribute, and setting its value according to the fact that there is an ObsoleteAttribute, or that it throws an exception (this has to be done via reflection, since browsable is private). The code could be something like this:
private static void FilterProperties(object objToEdit)
{
Type t = objToEdit.GetType();
PropertyInfo[] props = t.GetProperties();
// create fooObj in order to have another instance to test for NotImplemented exceptions
// (I do not know whether your getters could have side effects that you prefer to avoid)
object fooObj = Activator.CreateInstance(t);
foreach (PropertyInfo pi in props)
{
bool filter = false;
object[] atts = pi.GetCustomAttributes(typeof(ObsoleteAttribute), true);
if (atts.Length > 0)
filter = true;
else
{
try
{
object tmp = pi.GetValue(fooObj, null);
}
catch
{
filter = true;
}
}
PropertyDescriptor pd = TypeDescriptor.GetProperties(t)[pi.Name];
BrowsableAttribute bAtt = (BrowsableAttribute)pd.Attributes[typeof(BrowsableAttribute)];
FieldInfo fi = bAtt.GetType().GetField("browsable",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
fi.SetValue(bAtt, !filter);
}
}
This should work, but it's got a limit. There must be at least a BrowsableAttribute in the class you are editing (it doesn't matter if it's set to true or false), otherwise the PropertyGrid will always be empty.
Related
I've just started on my first WPF project and I ran into a problem this morning.
There is this large collection of locations (50.000) I want to bind to a GridControl.
public void BindData()
{
//disabling the control seemed to shorten the UI lock.
gcLocations.IsEnabled = false;
Task.Factory.StartNew(() =>
{
gcLocations.SetPropertyThreadSafe("ItemsSource", OceanData.OceanPorts.Values);
});
gcLocations.IsEnabled = true;
}
public static void SetPropertyThreadSafe(this Control control, string propertyName, object value)
{
Type type = control.GetType();
var prop = type.GetProperty(propertyName);
if(prop == null)
{
throw new Exception(string.Format("No property has been found in '{0}' with the name '{1}'", control, propertyName));
}
object[] param = new object[] { propertyName, prop.PropertyType, value };
if(prop.PropertyType != typeof(object) && prop.PropertyType != value.GetType())
{
throw new Exception(string.Format("Property types doesn't match - property '{0}' (type:{1}) and value '{2}'(type:)", param));
}
if(control.Dispatcher.CheckAccess())
{
prop.SetValue(control, value);
}
else
{
control.Dispatcher.BeginInvoke(new Action(() =>
{
prop.SetValue(control, value);
}), DispatcherPriority.ContextIdle, null);
}
}
Because I want my applications to remain responsive to the user I've been looking for an alternative method to bind this data in one go. So i came to this idea.. Is it possible to pause the binding operation when a lock occurs in the interface so that the interface can update itself? I'm pretty new to programming so excuse my ignorance :)
thanks ~~
The DevExpress GridControl supports data virtualization, whereby only the controls/items that are visible on screen are constructed and added to the visual tree. Furthermore, those visible controls are usually recycled as you scroll down your list, saving the costs of rebuilding them.
If you're not familiar with virtualization, consider a simple list view example: You may bind a data source containing, say, 10,000 items to it, but only 20 items might be visible to the user at any one time. The virtualization mechanism ensures that only the list items that are visible are created. Without this feature, the list box would have to create 10,000 WPF list items (which in turn may contain several controls or UI elements), and hold those in memory AND add them to the visual tree, even though they are not visible. WPF controls can only be added to the visual tree on the UI thread, which is what will be causing your code to hang.
It appears that the DevExpress GridControl supports virtualization out of the box, but it may be enhanced by use of their own DevExpress Datasource classes. Check out this documentation ... it may be you need to make use of the LinqServerModeDataSource or LinqInstantFeedbackDataSource classes to give you the performance you're looking for.
Good day!
I'm writing a .vsix to replace old controls to new ones. I have designerHost which is the current designer instance. Then I start converting like this:
foreach (OldCombo oldCmbx in OldCmbxs())
{
...
NewCombo newCmbx = designerHost.CreateComponent(NewComboType, oldcmbx.Name) as NewCmbx;
...
newCmbx.Visible = oldCmbx.Visible;
...
designerHost.DestroyComponent(oldCmbx);
}
The thing is -oldCmbx is always Visible=true, no matter how it's written in the designer.cs file. I'm always creating Visible=true newCmbx's. If I force newCmbx to be Visible=false, then designer doesn't show newCmbx after the conversion, but the visible property is still true, so Visible property is definitely not what I'm searching for. So how can I force newCmbx's to be Visible=false in designer.cs?
After digging through .NET source code I've found that ControlDesigner is shadowing Visible property of the Control, so what is going to be serialized/deserialized in InitializeComponent is far related from actual Visible property of Control.
Designer.Visible property is initialized like this:
public override void Initialize(IComponent component)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(component.GetType());
PropertyDescriptor descriptor = properties["Visible"];
if (((descriptor == null) || (descriptor.PropertyType != typeof(bool))) || !descriptor.ShouldSerializeValue(component))
{
this.Visible = true;
}
else
{
this.Visible = (bool) descriptor.GetValue(component);
}
...
}
descriptor.ShouldSerializeValue(component) for Control.Visible is always false in case of newly created control.
Designer.Visible property:
private bool Visible
{
get
{
return (bool) base.ShadowProperties["Visible"];
}
set
{
base.ShadowProperties["Visible"] = value;
}
}
In the Designer.PreFilterProperties() actual Visible property of the Control is shadowed by Visible property of the designer.
Now, when the designer is initialized(in my code that's happening when I'm creating component designerHost.CreateComponent) newCmbx.Visible is always true.
Why it is so? Because Visible property of the Control is used in painting of the control(on the designer surface as well). If I set newCmbx.Visible = false it just disappears from the design surface (but still serializes from the Visible property of the designer) - that's bad, so by design of the Control class, when Control is instantiated, it is always Visible so that it could be visible on the design surface. Any subsequent changes in Visible property influence Visible property of the designer, not Control itself (in the context of working in Designer mode).
So, what I need in order to solve that problem is Visible property of the designer.
Correct code looks like this:
foreach (OldCombo oldCmbx in OldCmbxs())
{
bool _visible = GetVisiblePropThroughReflection(designerHost.GetDesigner(oldCmbx));
...
NewCombo newCmbx = designerHost.CreateComponent(NewComboType, oldcmbx.Name) as NewCmbx;
...
SetVisiblePropThroughReflection(designerHost.GetDesigner(newCmbx), _visible);
...
designerHost.DestroyComponent(oldCmbx);
}
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
It is possible to copy all the properties of a certain control? (C# window forms)
I have to create some controls similar to a control created as design time. The created control should have same properties as a predefined control, or in other words I want to copy a control. Is there any single line of code for that purpose? or I have to set each property by a line of code?
I am doing right now is:
ListContainer_Category3 = new FlowLayoutPanel();
ListContainer_Category3.Location = ListContainer_Category1.Location;
ListContainer_Category3.BackColor = ListContainer_Category1.BackColor;
ListContainer_Category3.Size = ListContainer_Category1.Size;
ListContainer_Category3.AutoScroll = ListContainer_Category1.AutoScroll;
Generally speaking you can use reflection to copy the public properties of an object to a new instance.
When dealing with Controls however, you need to be cautious. Some properties, like WindowTarget are meant to be used only by the framework infrastructure; so you need to filter them out.
After filtering work is done, you can write the desired one-liner:
Button button2 = button1.Clone();
Here's a little code to get you started:
public static class ControlExtensions
{
public static T Clone<T>(this T controlToClone)
where T : Control
{
PropertyInfo[] controlProperties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
T instance = Activator.CreateInstance<T>();
foreach (PropertyInfo propInfo in controlProperties)
{
if (propInfo.CanWrite)
{
if(propInfo.Name != "WindowTarget")
propInfo.SetValue(instance, propInfo.GetValue(controlToClone, null), null);
}
}
return instance;
}
}
Of course, you still need to adjust naming, location etc. Also maybe handle contained controls.
I'm new in C# but not new to coding --being doing it for almost two decades--, and have a problem with properties in a custom control I'm building, which inherits from a Panel. When I put my properties, I can see them in the Designer properties list and can even set them, but when running my little application, it seems these properties values are not used. The same if I change a property programatically: no error but my control does nothing, it is like they are not properly set. However, if I do it programatically whithin the class, they do work. My guess is that something in my properties set/get stuff is not right. Please see the following code chunk of how I'm doing it:
public class ColorStrip : Panel
{
// properties
// ------------------------------------------
// size of color clusters (boxes)
private int _clusterSize = 20;
// controls if show the buttons panel
private Boolean _showButtons;
// property setters/getters
// ------------------------------------------
// clusterSize...
public int clusterSize
{
get { return _clusterSize; }
set { _clusterSize = value; }
}
// showButtons...
public Boolean showButtons
{
get { return _showButtons; }
set { Console.Write(_showButtons); _showButtons = value; }
}
....
So in my form, for instance in the load or even in a click event somewhere, if I put colorStrip1.showButtons = false; or colorStrip1.showButtons = true; whatever (colorStrip1 would be the instance name after placing the control in the form in design mode)... console.write says always 'false'; Even if I set it in the design properties list as 'true' it will not reflect the settled value, even if I default it to true, it will never change externally. Any ideas? Non of the methods get the new and externally settled property value neither, obviously the getter/setter thing is not working. Seems to me I'm not doing right the way I set or get my properties outside the class. It works only inside it, as a charm...Any help...very appreciate!
Cheers
lithium
p.s. TO CLARIFY SOLUTION:
Setting the property in this case didn't work because I was trying to use a new set value within the constructor, which seems can't get the new values since it is, well, building the thing. If I change the property value in Design mode > Property editor or in code externally to the object, say in it's parent form's load event, it will change it but readable for all methods except the constructor, of course :)
It's likely an issue of the order of execution. Your property setter just sets a variable, but doesn't actually trigger anything on the control to update the state related to this variable (e.g. adding or showing the buttons I assume).
When you set the property befre the rest of the initialization is done, the value is being used, otherwise it isn't because during the initial go the default value is still the property value.
You need to act on the setter, here's some pseudocode to illustrate:
set {
_showButtons = value;
if (alreadyInitialized) {
UpdateButtons();
}
}
Note: make sure to first set the value, then act - otherwise you end up using the old value (just like your Console.Write() is doing).
The quoted code doesn't look problematic. Are you sure you're referencing the same instance of ColorStrip? Also, check your .Designer.cs file to ensure that the code setting the property is there.
In fact, try simplifying your code by using auto-implementing properties:
public int clusterSize { get;set;}
public Boolean showButtons {get;set;}
public ColorStrip() { ... clusterSize = 20; ... }
I love PropertyGrid, well, at least the concept behind it - use reflection and attributes to edit your objects without writing much UI code.
My excitement died out pretty quickly though, the default PropertyGrid shipping with WinForms flat-out sucks. Well, it's fine for editing simple objects and such, but that's as far as it goes.
It doesn't display appropriate UITypeEditors for dynamic properties which have type "Object".
As soon as your objects contain collections, you might be able to edit them with so called CollectionEditor. However, it won't fire PropertyValueChanged event. So once you need to add undo functionality, you're screwed.
And I still haven't found an elegant way to add validation for CollectionEditor.
It's also problematic to implement undo if you have multiple objects selected, because in that case PropertyValueChanged event args ChangedItem is null.
I soon found myself writing hacks to address those issues with less than agreeable results.
What would you do?
Is there an elegant solution to at least the first three issues?
Is there an alternative propertygrid? Preferably free & without PInvokes?
A lot of the PropertyGrid's elegance comes from its simplicity. Above all else, it's designed to play nice with Visual Studio, and i'd expect to see it used primarily in custom UITypeEditors and extensions, rather than in application code.
Presumably the objects you are attaching to the PropertyGrid are classes of your own design? I've found that, in order to make good use of the property grid, you have to heavily decorate your classes and members with attributes.
You may find some joy in writing your own subclasses of CollectionEditor (and other types of editors) and attaching them to class members using the [Editor] attribute - if you can attach this attribute to your dynamic properties, you can force the use of a particular editor.
The only way I can think of adding validation to CollectionEditor is to override the CreateCollectionForm() method, returning an instance of your own, custom subclass of CollectionEditor.CollectionForm. There's a chance you will be able to fire the change events from here.
Unfortunately all I can do is nod and agree with the assertion about implementing undo. You might have to resort to 'backing up' the affected objects via cloning or serialization in order to implement undo.
I've seen alternatives to the built-in property grid control, but they exist mainly to offer different visual styles.
If someone is interested - here is a workaround for the PropertyValueChanged problem that simulates a change by invoking the MemberwiseClone function of System.Object if the CollectionEditor's PropertyValueChanged had been fired ...
public class FixedCollectionEditor : CollectionEditor
{
bool modified;
public FixedCollectionEditor(Type type)
: base(type)
{ }
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value)
{
value = base.EditValue(context, provider, value);
if (value != null && modified)
{
value = value.GetType()
.GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic)
.Invoke(value, new object[] { });
}
modified = false;
return value;
}
protected override CollectionForm CreateCollectionForm()
{
CollectionForm collectionForm = base.CreateCollectionForm();
foreach (Control table in collectionForm.Controls)
{
if (!(table is TableLayoutPanel)) { continue; }
foreach (Control c1 in table.Controls)
{
if (c1 is PropertyGrid)
{
PropertyGrid propertyGrid = (PropertyGrid)c1;
propertyGrid.PropertyValueChanged += new PropertyValueChangedEventHandler(GotModifiedHandler);
}
if (c1 is TableLayoutPanel)
{
foreach (Control c2 in c1.Controls)
{
if (!(c2 is Button)) { continue; }
Button button = (Button)c2;
if (button.Name == "addButton" || button.Name == "removeButton")
{
button.Click += new EventHandler(GotModifiedHandler);
if (button.ContextMenuStrip != null)
{
button.ContextMenuStrip.ItemClicked += new ToolStripItemClickedEventHandler(GotModifiedHandler);
}
}
}
}
}
}
return collectionForm;
}
void GotModifiedHandler(object sender, EventArgs e)
{
modified = true;
}
}
Visualhint sells a replacement for the property grid that may help. As I have never used it in a real project, I don't know how well it works.