How to determine which property is the default property for databinding? - c#

Given a Class of X that has multiple properties that are bindable, how do I determine which one should be the default property to select via reflection?
In the Winforms designer you can select Databinding. How does Visual Studio determine that "EditValue" should be the default property to bind to rather than say "Text"?
I already know how to get properties and attributes from the object, but I'm missing something that would tell me which one to use as default.

You can rely on DefaultBindingProperty attribute of the class.
For example a DateTimePicker is decorated with [DefaultBindingProperty("Value")] but a ComboBox is decorated with [DefaultBindingProperty("Text")].
You can create a function like following, to get name of the default binding property of a control:
public string GetDefaultBindingPropertyValue(Control c)
{
var att = c.GetType().GetCustomAttributes(true)
.OfType<DefaultBindingProperty>().FirstOrDefault();
return att?.Name;
}
Side Note
You may be interested to these attributes as well for some complex scenarios:
LookupBindingProperties: Specifies the properties that support lookup-based binding. List controls like ComboBox and ListBox are decorated by this attribute, [LookupBindingProperties("DataSource", "DisplayMember", "ValueMember", "SelectedValue")].
ComplexBindingProperties: Specifies the data source and data member properties for a component that supports complex data binding. DataGridView has been decorated by this attribute, [ComplexBindingProperties("DataSource", "DataMember")].

Related

WinForms DataGridView and PropertyGrid behavior [duplicate]

Let's say I have a property which I want shown in a DataGridView, but not when the same object is shown in a PropertyGrid. I know I can use [Browsable(false)], but that hides it in both views. I can also do a gridView.Columns["blah"].Visible = false;, but this is the opposite of what I want, as it hides in the DataGridView but not in PropertyGrid. Is there some way to do the reverse? (Short of creating a whole new DataTable just to hold the same data minus one field, and rebinding everything to that instead - that's really a kludge way to do things.) Alternatively, I could live with a solution which adds a column to the DataGridView that is not present on the actual class.
it is possible to solve this issue by using the BrowsableAttributes property of a PropertyGrid.
First, create a new attribute like this:
public class PropertyGridBrowsableAttribute : Attribute
{
private bool browsable;
public PropertyGridBrowsableAttribute(bool browsable){
this.browsable = browsable;
}
}
Then add this attribute to all those properties which you want to be shown in your PropertyGrid:
[DisplayName("First Name"), Category("Names"), PropertyGridBrowsable(true)]
public string FirstName {
get { return ... }
set { ... }
}
Then set the BrowsableAttributes property like this:
myPropertyGrid.BrowsableAttributes = new AttributeCollection(
new Attribute[] { new PropertyGridBrowsableAttribute(true) });
This will only show the attributed properties in your property grid and the DataGridView can still access all properties with only a little bit more coding effort.
I would still go with Tergiver and call this behaviour a bug, since the documentation of the Browsable attribute clearly states its use for property windows only.
(Credit goes to user "maro" at http://www.mycsharp.de/wbb2/thread.php?postid=234565)

View model properties has changing validation rules at run time

I'm new to C# MVC and I'm trying to add some dynamic validation checks to my view models that are used in a form. For example, I have a string property called FirstName. I can add the attribute StringLength(10) and Required() to it.
My problem is, depending on some other field, the FirstName StringLength could vary from 10 to 20, etc. I still want to use the MVC validations but be able to modify it. I know that attributes are bound to the class so maybe I'm using the wrong thing.
I want the abilities for attribute validation but have it modifiable at run time. Is this possible?
The values in an attribute have to be literals. You can still use attribute based validation, but you will need to use the CustomValidation tag and point it at a method to use. If it depends on multiple fields in the object, you will want to put this on the class rather than the property.
It seems you can add validation attributes at runtime by implementing DataAnnotationsModelValidatorProvider:
Dynamic Attributes # forums.asp.net

Application-wide control defaults

I'm looking for a way to set up my own default property values for different types of controls in my C# .NET windows application. The default property values should 'override' the existing default values of the controls, but still be 'overridable' by setting the property values explicitly in the designer.
This is to simplify the process of changing default appearance/behaviour of controls when the client (or myself) change their mind for the 10th time. This relates especially to controls like the DataGridView or 3rd party controls where there are tons of layout-related properties to maintain.
I am aware of the ability to create inherited controls and use the DefaultValue attribute, but this is not the solution I'm looking for for a couple of reasons:
It's a hassle having to inherit of every type of control I want to specify custom properties for, not to mention overriding/shadowing the properties and setting the DefaultValue attribute.
I can no longer use the standard .NET controls, but have to use the inherited controls.
The number of inherited controls increases over time and clutters up the toolbox.
Myself or other developers on the project forget to use the new inhertied types in times of haste, resulting in inconsitent behaviour/appearance of controls.
This is how I imagined that it will work:
Example 1: A DataGridView by default has background color
SystemColors.Window. I set my own
default value to Color.Blue (how
outrageous!). In the designer, the
default background color is used,
i.e. the background color is not set
explicitly in the .designer.cs file.
When running the application, a portion of code is executed, causing the grid to turn
blue, as specified by me.
Example 2: The background color of the same DataGridView is set to
Color.Red in the designer. This
overrides my own default value of blue, showing a red background
in the grid, both in design-time and run-time.
Solution
The solution for me was to use reflection to check the DefaultValue attribute, as suggested by Daniel Brückner.
I recurse through all controls on a form, calling SetDefaultValues for each control. For each property value to set, I call the SetValue method, which makes sure only properties that haven't been changed from their default values, are set.
There is one flaw in this approach, though. Properties that have been set explicitly in the designer, but do not differ from their default values, will be overwritten by the SetValue method.
void SetDefaultValues(Control control)
{
if (control is DataGridView)
{
SetValue(control, "BackColor", Color.Blue);
}
else if (control is TextBox)
{
// etc.
}
}
private static void SetValue(object control, string propertyName, object newValue)
{
System.Reflection.PropertyInfo prop = control.GetType().GetProperty(propertyName);
if (prop == null)
{
throw new ArgumentException(string.Format(
"Specified property \"{0}\" does not exist on type \"{1}\".", prop.Name, control.GetType().FullName),
"propertyName");
}
bool defaultValueFound = false;
object defaultValue = null;
foreach (object attr in prop.GetCustomAttributes(true))
{
if (attr is DefaultValueAttribute)
{
defaultValue = ((DefaultValueAttribute)attr).Value;
defaultValueFound = true;
break;
}
}
if (!defaultValueFound && prop.PropertyType.IsValueType)
{
// Get default value for value types if no default value was specified by attributes:
defaultValue = Activator.CreateInstance(prop.PropertyType);
}
if (defaultValue == null || defaultValue.Equals(prop.GetValue(control, null)))
{
// If default value matches current value, set new value:
prop.SetValue(control, newValue, null);
}
}
While not as pretty as generics, you might me able to do something with Control Builders to pull this off.
Edit:
Last night I did a quick prototype of a generic wrapper control with the ControlBuilder. I am not happy with the results. While you can probably get it to work, I beleive a new Page or Container class might be a much simpler result. The source code I used in my test is avalible on my blog.
There are several solutions I have used or I can think of.
Inheriting the control, but you mentioned that already.
Some more advanced control libraries (like DevExpress) have the build-in ability to load the layout from configuration files (XML in the case of DevExpress) or are even completly skinnable (true for DevExpress, too).
Sometimes I create extension method for the controls and call them in the constructor of the user control or form. This is an easy way to enable or disable sets of functionalities like sorting. multiselect, or column reordering in data grids and gives a consistent behavior and look.
Use data binding and bind the properties to some configuration data. I believe there is even a build in functionality - user settings or something like that - but I have never used this feature.
Calling the extension method on all controls like proposed above is not very handy in larger projects. You could recursivly visit all controls in a form when it is created, look at the properties, compare to the default value (using reflection to get the DefaultValue attribute), and if they don't match (that is the value has been overriden in the designer) load your default value from some file or in-memory store and apply it.
you could overide page instead and have a loop through all the controls e.g.
foreach (Control c in Page.Controls)
{
if (c is Textbox)
{
(Textbox)c.Color.blah.blah.blah ;)
}
///etc
Recurse through (c.Controls);
}

C# INotifyPropertyChanged on properties of a dynamically created object?

(update) ICustomTypeDescriptor works for my Windows Forms app, but not for Silverlight; Not supported. I will keep investigating this idea though and see where i get to.
(/update)
I have, say a few switch panels (for those that like analogies).
Each of these switch panels has switches that have a Name(string) can be in state(bool) of On or Off.
The switchpanel and switches are objects that have INotify interface on them.
Using the switches Names, I create a list of all possible switch names over the collection and create a dynamic class that has all these Names as properties.
SwitchPanel1 (Switches( Switch1 ("Main",On) , Switch2("Slave",Off)))
SwitchPanel2 (Switches( Switch1 ("Bilge",On) , Switch2("Main",Off)))
Produces a collection of
(Main,Bilge,Slave)
And a dynamic class is produced that has the properties:
SwitchPanel : (SwitchPanel)
Main : (Switch)
Bilge : (Switch)
Slave: (Switch)
The idea is that if the switch panel has a switch with the Name of the property, it is placed on that property. So using a bit of linq
propeties["Main"].SetValue(newSwitchType,SwitchPanel.Switches.FirstOrDefault(sw => sw.Name == "Main"));
I want to cast this new dynamic class to INotfyPropertyChanged AND catch the actual changes on these new properties, so if a switch changes state the dynamic object will report it.
Why? It needs to be displayed in a list view and the list view I'm using has its binding by supplying the Property name, and not the binding path.
It also attempts to catch INotify events by casting the object against INotifyPropertyChanged. This means it will sort and/or group when things change.
If you know of a better way to do this let me know. Please.
You probably don't need a dynamic class. You can implement runtime binding properties via ICustomTypeDescriptor / GetProperties(), creating your own PropertyDescriptor implementation that returns the named switch. It isn't clear what knows first about the change, but you could either use INotifyPropertyChanged, or the older property-specific change event, again tied to each property (so each PropertyDescriptor attaches to, for example, the event in the named switch.
Not trivial, but not impossible either.

DataGridView - setting object's displayed value in a column

I bind some collection to a DataGridView. Collection contains KeyValuePair objects where key is a string and value is an object of my class Field. DataGridView displays two columns - one containing the key and the other one containing the value. The value (Field object) is displayed with its ToString() method. But I would like it to be displayed using its Name property. The problem is the column contains no DisplayMember property.
How can i do it?
Edit: I know I could override ToString() to return the name of the object but I don't want to do that.
DataGridView (in common with most direct list-based bindings) can only bind to immediate properties of the row item. You could perhaps create a facade object for this? i.e. a class that accepts the instance and returns the name as a direct property:
public string Name {
get {return innerObject.Name;}
set {innerObject.Name = value;}
}
// snipped: other properties - Key etc
Alternatively, you could project into a new object? For example, data-bindings work (read-only, at least) with anonymous types pretty well:
grid.DataSource = originalData.Select(x=>
new {x.Key, Name = x.Field.Name}).ToList();
Finally, you can hack around in ComponentModel to flatten the model at runtime, but it really isn't worth it just for this.
You could put the DataGridView into virtual mode (view.VirtualMode = true), and handle the CellValueNeeded (and possibly the CellValuePushed) events to access the "Name" property. This would avoid creating lots of wrapper objects, but does make the code somewhat less elegant.

Categories

Resources