Here's the documentation. I haven't found any explanation anywhere. There is a data binding overview but is for WPF, and I'm using WinForms. I thought what it does is to call whatever method I assign to the event Format of the Binding class, but it will call it even if I set formattingEnabled to false as long as I assign a method. So now I don't know what it does, and I don't understand where is people supposed to get this kind of information.
It looks like you need a couple pieces... First here's the Reflector'd bit on the Format event that you've added
protected virtual void OnFormat(ConvertEventArgs cevent)
{
if (this.onFormat != null)
{
this.onFormat(this, cevent);
}
if (((!this.formattingEnabled && !(cevent.Value is DBNull)) && ((cevent.DesiredType != null) && !cevent.DesiredType.IsInstanceOfType(cevent.Value))) && (cevent.Value is IConvertible))
{
cevent.Value = Convert.ChangeType(cevent.Value, cevent.DesiredType, CultureInfo.CurrentCulture);
}
}
and then there's this:
private object FormatObject(object value)
{
if (this.ControlAtDesignTime())
{
return value;
}
Type propertyType = this.propInfo.PropertyType;
if (this.formattingEnabled)
{
ConvertEventArgs args = new ConvertEventArgs(value, propertyType);
this.OnFormat(args);
if (args.Value != value)
{
return args.Value;
}
TypeConverter sourceConverter = null;
if (this.bindToObject.FieldInfo != null)
{
sourceConverter = this.bindToObject.FieldInfo.Converter;
}
return Formatter.FormatObject(value, propertyType, sourceConverter, this.propInfoConverter, this.formatString, this.formatInfo, this.nullValue, this.dsNullValue);
}
ConvertEventArgs cevent = new ConvertEventArgs(value, propertyType);
this.OnFormat(cevent);
object obj2 = cevent.Value;
if (propertyType == typeof(object))
{
return value;
}
if ((obj2 != null) && (obj2.GetType().IsSubclassOf(propertyType) || (obj2.GetType() == propertyType)))
{
return obj2;
}
TypeConverter converter2 = TypeDescriptor.GetConverter((value != null) ? value.GetType() : typeof(object));
if ((converter2 != null) && converter2.CanConvertTo(propertyType))
{
return converter2.ConvertTo(value, propertyType);
}
if (value is IConvertible)
{
obj2 = Convert.ChangeType(value, propertyType, CultureInfo.CurrentCulture);
if ((obj2 != null) && (obj2.GetType().IsSubclassOf(propertyType) || (obj2.GetType() == propertyType)))
{
return obj2;
}
}
throw new FormatException(SR.GetString("ListBindingFormatFailed"));
}
So it's still going to format the object according to what you've bound to the Format event handler.
Came across this question 10 years later and didn't find the answer particularly helpful. The source is now available online. Just looking at the comments:
private object FormatObject(object value) {
...
if (formattingEnabled) {
// -------------------------------
// Behavior for Whidbey and beyond
// -------------------------------
...
} else {
// ----------------------------
// Behavior for RTM and Everett [DO NOT MODIFY!]
// ----------------------------
...
// Approved breaking-change behavior between RTM and Everett: Fire the Format event even if the control property is of type
// Object (RTM used to skip the event for properties of this type). NOTE: This change contains a bug (fixed in the new
// Whidbey logic above); Everett always returns the *original* object in this case, ignoring any attempt by the event handler
// to replace this with a different object.
...
}
}
Whidbey, RTM and Everett appear to be different codenames for visual basic, so it looks to me like you should use formattingEnabled=true if you want the more "up-to-date" version of FormatObject.
Related
I am using a PropertyGrid to display the content of an object to the user.
This PropertyGrid is synchronized with an Excel sheet, assuming a cell value match a property value.
As the user selects a property in the PropertyGrid, the application highlights the corresponding cell in the Excel sheet which is opened beside. I can do this using the SelectedGridItemChanged event.
Now, I want to have a property selected in my PropertyGrid when the user selects a cell in the Excel sheet.
void myWorkbook_SheetSelectionChangeEvent(NetOffice.COMObject Sh, Excel.Range Target)
{
if (eventMask > 0)
return;
try
{
eventMask++;
this.Invoke(new Action(() =>
{
propertyGrid1.SelectedGridItem = ... // ?
}
}
finally
{
eventMask--;
}
}
I noticed that the SelectedGridItem can be written to.
Unfortunately I do not find a way to access the GridItems collection of my PropertyGrid so I can look for the right GridItem and select it.
How can I do this?
You can get all the GridItems from the root. I am using the below code to retrieve all the griditems within a property grid
private GridItem Root
{
get
{
GridItem aRoot = myPropertyGrid.SelectedGridItem;
do
{
aRoot = aRoot.Parent ?? aRoot;
} while (aRoot.Parent != null);
return aRoot;
}
}
and pass the root to the below method
private IList<GridItem> GetAllChildGridItems(GridItem theParent)
{
List<GridItem> aGridItems = new List<GridItem>();
foreach (GridItem aItem in theParent.GridItems)
{
aGridItems.Add(aItem);
if (aItem.GridItems.Count > 0)
{
aGridItems.AddRange(GetAllChildGridItems(aItem));
}
}
return aGridItems;
}
I came up against this quite a bit a project so I wrote an extension method for it:
if (!SetupManagerSettings.BootStrapperLocation.IsFile()) // Just another extension method to check if its a file
{
settingsToolStripMenuItem.Checked = true; // Event handler OnChecked ensures the settings panel is unhidden
settingsPropertyGrid.ActivateControl();
settingsPropertyGrid.SelectPropertyGridItemByName("BootStrapperLocation"); // Here is the extension method
return false;
}
Here is the extension method with a private, supporting method for traversing the hierarchy of objects (if this applies to your object model):
public static bool SelectPropertyGridItemByName(this PropertyGrid propertyGrid, string propertyName)
{
MethodInfo getPropEntriesMethod = propertyGrid.GetType().GetMethod("GetPropEntries", BindingFlags.NonPublic | BindingFlags.Instance);
Debug.Assert(getPropEntriesMethod != null, #"GetPropEntries by reflection is still valid in .NET 4.6.1 ");
GridItemCollection gridItemCollection = (GridItemCollection)getPropEntriesMethod.Invoke(propertyGrid, null);
GridItem gridItem = TraverseGridItems(gridItemCollection, propertyName);
if (gridItem == null)
{
return false;
}
propertyGrid.SelectedGridItem = gridItem;
return true;
}
private static GridItem TraverseGridItems(IEnumerable parentGridItemCollection, string propertyName)
{
foreach (GridItem gridItem in parentGridItemCollection)
{
if (gridItem.Label != null && gridItem.Label.Equals(propertyName, StringComparison.OrdinalIgnoreCase))
{
return gridItem;
}
if (gridItem.GridItems == null)
{
continue;
}
GridItem childGridItem = TraverseGridItems(gridItem.GridItems, propertyName);
if (childGridItem != null)
{
return childGridItem;
}
}
return null;
}
I looked at the above options and did not like them that much, I altered it a bit found that this works well for me
bool TryFindGridItem(PropertyGrid grid, string propertyName, out GridItem discover)
{
if (grid is null)
{
throw new ArgumentNullException(nameof(grid));
}
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentException("You need to provide a property name", nameof(propertyName));
}
discover = null;
var root = pgTrainResult.SelectedGridItem;
while (root.Parent != null)
root = root.Parent;
foreach (GridItem item in root.GridItems)
{
//let's not find the category labels
if (item.GridItemType!=GridItemType.Category)
{
if (match(item, propertyName))
{
discover= item;
return true;
}
}
//loop over sub items in case the property is a group
foreach (GridItem child in item.GridItems)
{
if (match(child, propertyName))
{
discover= child;
return true;
}
}
//match based on the property name or the DisplayName if set by the user
static bool match(GridItem item, string name)
=> item.PropertyDescriptor.Name.Equals(name, StringComparison.Ordinal) || item.Label.Equals(name, StringComparison.Ordinal);
}
return false;
}
it uses a local method named match, if you're version of C# does not allow it then just put it external to the method and perhaps give it a better name.
Match looks at the DisplayName as well as the property name and returns true if either is a match, this might not be what you would like so then update that.
In my usecase I need to select the property based on a value the user can select so call the above method like this:
if (TryFindGridItem(pgMain, propertyName, out GridItem gridItem))
{
gridItem.Select();
}
It could theoretically never not find it unless the user selects a string that is not proper this way the if can be updated using an else .. I rather keep it safe then have a null-pointer exception if I can't find the name specified.
Also I am not going into sub classes and lists of classes as my use case doesn't need that, if yours does than perhaps make recursive calls in the GridItem.
Small note, replace GridItem with var and the code brakes as at compile time the IDE has no clue what a GridItemCollection returns…
I am designing a system that maps an object to a page through reflection, by looking at the field names and the property names, then attempting to set the values of the controls. The issue is that the system takes massive amounts of time to finish. I'm hoping that someone may be able to assist in speeding this up a little bit
public static void MapObjectToPage(this object obj, Control parent) {
Type type = obj.GetType();
foreach(PropertyInfo info in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)){
foreach (Control c in parent.Controls ) {
if (c.ClientID.ToLower() == info.Name.ToLower()) {
if (c.GetType() == typeof(TextBox) && info.GetValue(obj, null) != null)
{
((TextBox)c).Text = info.GetValue(obj, null).ToString();
}
else if (c.GetType() == typeof(HtmlInputText) && info.GetValue(obj, null) != null)
{
((HtmlInputText)c).Value = info.GetValue(obj, null).ToString();
}
else if (c.GetType() == typeof(HtmlTextArea) && info.GetValue(obj, null) != null)
{
((HtmlTextArea)c).Value = info.GetValue(obj, null).ToString();
}
//removed control types to make easier to read
}
// Now we need to call itself (recursive) because
// all items (Panel, GroupBox, etc) is a container
// so we need to check all containers for any
// other controls
if (c.HasControls())
{
obj.MapObjectToPage(c);
}
}
}
}
I realize that I can do this manually via
textbox.Text = obj.Property;
however, this defeats the purpose of making it so that we can map an object to the page without all the manual setting of values.
The 2 major bottlenecks I have identified are the foreach loops, seeing as it loops through each control / property and in some of my objects there are 20 or so properties
Instead of looping N*M, Loop properties once, put them into a dictionary and then use that dictionary while looping controls
I have the following code in the WPF application
if (panel != null)
{
IList listOfValues = new ComparableListOfObjects();
var childControls = panel.GetChildren<Control>(x => x.Visibility == Visibility.Visible);
foreach (Control childControl in childControls)
{
var textBox = childControl as TextBox;
if (textBox != null)
{
listOfValues.Add(textBox.Text);
continue;
}
var comboBox = childControl as ComboBox;
if (comboBox != null)
{
listOfValues.Add(comboBox.SelectedItem);
continue;
}
var datePicker = childControl as DatePicker;
if (datePicker != null)
{
listOfValues.Add(datePicker.SelectedDate.GetValueOrDefault());
continue;
}
var numericBox = childControl as NumericUpDown;
if (numericBox != null)
{
listOfValues.Add(numericBox.Value);
continue;
}
}
What is the best approach to refactor this code with repetition the same logic for extract value from different controls like?
var numericBox = childControl as NumericUpDown;
if (numericBox != null)
{
listOfValues.Add(numericBox.Value);
continue;
}
In the same class in other method there is the same code
private static object GetControlValue(Control control)
{
if (control == null)
throw new ArgumentNullException("control");
var textBox = control as TextBox;
if (textBox != null)
return textBox.Text;
var comboBox = control as ComboBox;
if (comboBox != null)
return comboBox.SelectedValue;
var datePicker = control as DatePicker;
if (datePicker != null)
return datePicker.SelectedDate.GetValueOrDefault();
var numericUpDown = control as NumericUpDown;
if (numericUpDown != null)
return numericUpDown.Value;
throw new NotSupportedException();
}
May by I should use the strategy design pattern but in this case I need to create additional classes for each type of control?
Could you suggest me better solotion for this prolem?
Thanks in advance.
Having if and switch statements are not a bad thing. Even doing some rudimentary type checking is not necessarily a bad thing, particularly when the types in use can't be used polymorphically. Having that logic expressed more than once is what is frowned upon, because you are repeating yourself, and you have multiple maintenance points for the same change.
In your original code snippet, you do
var blah = obj as Foo;
if (blah != null)
{
someList.Add(blah.Value);
}
And repeat this for several more control types. But then in your private method later, you have basically the same logic expressed the same number of times.
var blah = obj as Foo;
if (blah != null)
return blah.Value;
The only difference is that in the first snippet, you take the value and add it to the list. In the second, you return the value. The first snippet should do away with its type-checking logic, it should use the logic already expressed in the other method.
foreach (var control in childControls)
{
listOfValues.Add(GetControlValue(control));
}
The idea is don't repeat yourself. DRY.
I believe you are looking for Visitor pattern. One class per controller is one way to do it but to quote the referenced article:
Note: A more flexible approach to this pattern is to create a wrapper
class implementing the interface defining the accept method. The
wrapper contains a reference pointing to the CarElement which could be
initialized through the constructor. This approach avoids having to
implement an interface on each element. [see article Java Tip 98
article below]
You might be able to get away with this.
Bit of a hack, but here's a way to use delegates and collection-initializers to eliminate the redundancy (you may prefer not to use this as is, but rather the idea).
First create a class like this:
// Needs argument validation. Also, extending Dictionary<TKey, TValue>
// probably isn't a great idea.
public class ByTypeEvaluator : Dictionary<Type, Func<object, object>>
{
public void Add<T>(Func<T, object> selector)
{
Add(typeof(T), x => selector((T)x));
}
public object Evaluate(object key)
{
return this[key.GetType()](key);
}
}
And then the usage becomes:
// Give this variable longer lifetime if you prefer.
var map = new ByTypeEvaluator
{
(ComboBox c) => c.SelectedItem,
(TextBox t) => t.Text,
(DateTimePicker dtp) => dtp.Value,
(NumericUpDown nud) => nud.Value
};
Control myControl = ...
var myProjection = map.Evaluate(myControl);
You could do it as a case select in a generic method, but there is still some work with this style:
public static string GetValue<T>(T obj) where T:Control
{
switch (obj.GetType().ToString())
{
case "TextBox":
return (obj as TextBox).Text;
break;
case "ComboBox":
return (obj as ComboBox).SelectedValue.ToString();
break;
//..etc...
}
}
you could use an approach like this, relying on is :
private static object GetControlValue(Control control)
{
if (control == null)
throw new ArgumentNullException("control");
if (control is TextBox) return (control as TextBox).Text;
if (control is ComboBox) return (control as ComboBox).SelectedValue;
...
}
and
if (panel != null)
{
IList listOfValues = new ComparableListOfObjects();
var childControls = panel.GetChildren<Control>(x => x.Visibility == Visibility.Visible);
foreach (Control childControl in childControls)
{
if(childControl is TextBox) { listOfValues.Add((childControl as TextBox).Text); continue; }
if(childControl is ComboBox) { listOfValues.Add((childControl as ComboBox).SelectedValue); continue; }
...
}
}
the continue in the second block is probably not even needed but that requires some testing.
In a parentclass I call a function defined in the child class and parse the values I need through.
ParentClass.ascx
protected void Page_Load
{
if(info != null)
ControlIWantToGetInformationTo.SetInfo(info);
}
ChildClass.ascx
public void SetInfo(Info info)
{
someTextBox.Text = info.TheVariableWithin.ToString();
}
What I can gather is that that ParentClass(control) loads and does the method, but when the ChildClass(control) page loads it resets the previously set variable to null how can I work around this?
Use Session. In your method, instead of setting the values of your controls, use an object and fill the properties of your object and save it to Session when you are done. In your childclass, load your values from the object which you saved into Session.
//Parentclass
protected void Page_Load
{
if(info != null)
{
MyControlObject myObj = new MyControlObject();
myObj.prop1 = txt1.Text;
myObj.prop2 = txt2.Text;
Session["myObj"] = myObj;
}
}
//Childclass
public void SetInfo(Info info)
{
MyControlObject myObj = Session["myObj"] as MyControlObject;
if(myObj != null)
{
//assign the values to your controls
Session["myObj"] = null; //when you are done, clear the session.
}
}
I think you are facing problem for case sensitivity.
try this
someTextBox.Text = info.TheVariableWithin.ToString();
I have an control that inherits from another control (TxTextControl). I have a SelectedText property that basicaly wraps the base SelectedText property, which is apparently needed because my control is implementing an interface with that property. The code is this:
public string SelectedText
{
get
{
return base.Selection.Text; // Error here (#1042)
}
set
{
if (base.Selection == null)
{
base.Selection = new TXTextControl.Selection(0, 0);
}
base.Selection.Text = value;
}
}
When I drop this control on a form, no problems. It compiles and runs. Everything looks great. However, when I save, close then reopen the form, the form designer shows this error:
Object reference not set to an instance of an object.
1. Hide Call Stack
at Test.FormattedTextBox2.get_SelectedText() in C:\Projects\Test\FormattedTextBox2.cs:line 1042
Anyone know what is going on? I'm about to pull out my last hair...
UPDATE:
darkassisin93's answer wasn't exactly correct, but that was because my posted code wasn't exactly accurate. I needed to test if base.Selection was null before attempting to access a property of that object. In any case, that answer got me headed in the right direction. Here is the actual solution:
public string SelectedText
{
get
{
string selected = string.Empty;
if (base.Selection != null)
{
selected = base.Selection.Text;
}
return selected;
}
set
{
if (base.Selection == null)
{
base.Selection = new TXTextControl.Selection(0, 0);
// Have to check here again..this apparently still
// results in a null in some cases.
if (base.Selection == null) return;
}
base.Selection.Text = value;
}
}
Try replacing
return base.SelectedText;
with
return base.SelectedText ?? string.Empty;
It's most likely because the base class's SelectedText property is set to null.