Reflection to loop through properties performing slowly - c#

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

Related

Check which Controls have Borders c#

I am making a Winforms application. Because I want to redraw some borders I want to loop through the controls and check which controls have a border. Unfortunately I have no idea how to accomplish this.
I know panels and textboxes, etc. have a property BorderStyle but I can not access it while looping through Controls. I use the function from this link : https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.controls?view=netframework-4.8 , to loop through the controls.
If you have a panel you can foreach on the panel. I use form load as an event
private void Form1_Load(object sender, EventArgs e)
{
foreach (var item in this.Controls)
{
switch (item.GetType().Name)
{
case "Label":
if (((Label)item).BorderStyle == BorderStyle.None)
{
//Your commands
}
break;
case "TextBox":
if (((TextBox)item).BorderStyle == BorderStyle.None)
{
//Your commands
}
break;
}
}
}
Or you can check them dynamic
I recommend you to use the dynamic way.
in this way, your app wouldn't encounter exceptions or errors
foreach (var item in this.Controls)
{
//Get item from form
dynamic element = item;
//check if there is any property called Borderstyle exists
var res = item.GetType().GetProperties().Where(p => p.Name.Equals("BorderStyle")).FirstOrDefault();
//if it exists and value is not equal None(Control have border)
if (res !=null && !(res.GetValue(item).Equals("None")))
{
res.SetValue(item, BorderStyle.FixedSingle, null);
//your other commands
}
}
You could use a foreach for every type of control you use (TextBox, Label, etc.)
var controlsWithoutBorders = new List<Control>();
// Check textboxes
foreach (var control in controls.OfType<TextBox>())
{
if (control.BorderStyle != BorderStyle.None)
{
controlsWithoutBorders.Add(control);
}
}
//Check labels
foreach (var control in controls.OfType<Label>())
{
if (control.BorderStyle != BorderStyle.None)
{
controlsWithoutBorders.Add(control);
}
}
Alternatively, you could use a single foreach on all the controls, and try to cast the control to each type. The cast will be successful if the control is actually what you want to cast it to, otherwise it will return null (e.g. trying to cast a Control that is a TextBox to a Label will return null)
var controlsWithoutBorders = new List<Control>();
foreach (var control in controls)
{
var controlAsTextBox = control as TextBox;
var controlAsLabel = control as Label;
if (controlAsTextBox != null && controlAsTextBox.BorderStyle != BorderStyle.None)
{
controlsWithBorders.Add(control);
}
if (controlAsLabel != null && controlAsLabel.BorderStyle != BorderStyle.None)
{
controlsWithBorders.Add(control);
}
}
Though this method is not the fastest at run time, your problem could be cleanly solved with the use of C#'s dynamic typing feature. Consider the below snippet of code.
public void DealWithBorder(List<Control> lsControls) {
foreach(var element in lsControls){
dynamic dynamicElement = element;
try{
BorderStyle style = dynamicElement.BorderStyle;
// Handle if property does exist in the control
}
catch{
// Handle if property doesnt exist in the control
}
}
}
In English, it will try to act as if the property exists in the object but if it does not, an exception will thrown. For more info on dynamic typing, click here.

Foreach Control in Form

I have a lot of SimpleButton (DevExpress Controls) in my form. I want to set AllowFocus to false for them through code.
foreach (Control x in this.Controls)
{
if (x is SimpleButton)
{
((SimpleButton)x).AllowFocus = false;
}
}
Nothing really happen when I use this code. It still allow focus.
From your comment, it is clear that SImpleButton objects are not directly on the Form, so iterating the Form's Controls collection is not going to return those.
You need to iterate the GroupControl's Controls collection.
Cheers
Solved :
foreach (Control x in groupControl1.Controls)
{
if (x is SimpleButton)
{
((SimpleButton)x).AllowFocus = false;
}
}
Try it this way:
var buttons = this.Controls.OfType<Control>()
.SelectMany(x => x.Controls.OfType<SimpleButton>());
foreach(var button in buttons)
button.AllowFocus = false;
I suggest better to have Re-cursive function, I generally place all controls in main container panel, and you just need pass that container to Function, rest of things function will do for you.
private void FocusControls(Control ctl)
{
if ((ctl.GetType() == typeof(GroupBox)) ||
(ctl.GetType() == typeof(DevExpress.XtraEditors.GroupControl)) ||
(ctl.GetType() == typeof(DevExpress.XtraEditors.PanelControl)) ||
(ctl.GetType() == typeof(DevExpress.XtraTab.XtraTabControl)) ||
(ctl.GetType() == typeof(DevExpress.XtraTab.XtraTabPage))
)
{
foreach (Control obj in ctl.Controls)
FocusControls(obj);
}
if (ctl.GetType() == typeof(SimpleButton))
{
SimpleButton objTemp = (SimpleButton)ctl;
objTemp.AllowFocus = false;
}
}
Might just be a case of checking the types:
if (typeof(x) == typeof(SimpleButton))

Select a property in the property grid

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…

What is the best way to refactor code with many if and duplicated logic for extracting a value from different Controls classes

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.

find properties that has an attribute in a control

I have a page with a number of controls.
As the page is rendering I want to loop through all controls on the page and find any control that has a property with certain attribute. I am attempting to do this with c# - any ideas how I might achieve this?
I dont know how big is your control tree. This is what I would do. I'm not promissing the best performance.
Case 1. Looking for .NET Attributes
IEnumerable<Control> GetMarkedControls(ControlCollection controls)
{
foreach(Control c in controls)
{
var props = c.GetType().Properties();
if(props.Count(x => x.GetCustomAttributes(false).OfType<YourAttribute>().Count() > 0) > 0)
yield return c;
foreach (Control ic in GetMarkedControls(c.Controls))
yield return ic;
}
}
Case 2. Looking for HTML attributes
IEnumerable<WebControl> GetMarkedControls(ControlCollection controls)
{
foreach(Control c in controls)
{
if(c is WebControl)
{
var wc = c as WebControl;
if (wc.Attributes.FirstOrDeafult(x => x.Name == "yourAttribute") != null)
yield return c;
}
foreach (Control ic in GetMarkedControls(c.Controls))
yield return ic;
}
}
Now you can call it this way: var controlsWAttribute = GetMarkedControls(this.Controls); from your page or any control. This way you are not forced to call it at the page level.
With this method you explore the whole control tree in your page or control recursively.
You need to use Reflection
http://msdn.microsoft.com/en-us/library/system.reflection.aspx
using reflection you can get all the attributes of an object
Every control that is on the page has a "controls" property which contains all of its children controls. I have written recursive functions to loop through these before but do not have any on hand. Let me try to write one real quick:
public Collection<Control> findControlsWithAttributes(Control startingControl)
{
Collection<Control> toReturn = new Collection<Control>();
foreach (Control curControl in startingControl.controls)
{
if (DO COMPARISON HERE WITH CURCONTROL) toReturn.add(curControl);
if (curControl.Count() > 0) findControlsWithAttributes(curControl, toReturn);
}
return toReturn;
}
private void findControlsWithAttributes(Control startingControl, Collection<Control> inputCollection)
{
foreach (Control curControl in startingControl.controls)
{
if (DO COMPARISON HERE WITH CURCONTROL) inputCollection.add(curControl);
if (curControl.Count() > 0) findControlsWithAttributes(Control startingControl, Collection<Control> inputCollection);
}
}
Its been a little while since i've done this and I can't remember off the top of my head if Collection.Count is a method or property so make sure you check that first, but if you pass the page in then this will check against every server-visible control on your page and return a collection containing controls that match your comparison.
Lastly, Control.Attributes will return an AttributeCollection which you should be able to subsequently compare against.
Not sure what attributes you are after but if class attributes is what you are after look to #user751975 other wise you can do something like ...
page.Controls.Cast<System.Web.UI.WebControls.WebControl>().First().Attributes["class"]

Categories