Working on a page where I create new instances of a usercontrol I wrote, based on data. I pass in filtered data to the user control using constructor injection. However, when the user controls render, only the last set of data injected in is rendered for all user controls. It appears I'm referencing the same instance rather than creating a new, independent one. Ideas?
protected void Page_Init(object sender, EventArgs e)
{
var data = <my data comes from here>;
var yearsInData = data.OrderByDescending(x=>x.Year).Select(x => x.Year).Distinct();
foreach(var year in yearsInData)
{
var employers = data.OrderBy(x=>x.EmployerName).Where(x => x.Year == year).Select(x => x.EmployerName).Distinct();
foreach (var employer in employers)
{
var eob = data.Where(x => x.Year == year).Where(x => x.EmployerName == employer);
if (eob.Count() > 0)
{
var ctl = LoadControl(#"~\Shared\Controls\AnnualEOBControl.ascx", eob);
pnlMain.Controls.Add(ctl);
}
}
}
}
Here is the LoadControl method:
private UserControl LoadControl(string userControlPath, params object[] constructorParameters)
{
var constParamTypes = new List<Type>();
foreach (var constParam in constructorParameters)
{
constParamTypes.Add(constParam.GetType());
}
var ctl = Page.LoadControl(userControlPath) as UserControl;
// Find the relevant constructor
if (ctl != null)
{
var constructor = ctl.GetType().BaseType.GetConstructor(constParamTypes.ToArray());
// And then call the relevant constructor
if (constructor == null)
{
throw new MemberAccessException("The requested constructor was not found on : " + ctl.GetType().BaseType);
}
constructor.Invoke(ctl, constructorParameters);
}
// Finally return the fully initialized UC
return ctl;
}
It smells like a closure issue. My hunch is eob is merely being reassigned a value during each iteration as opposed to recreated. Since your controls all reference eob as opposed to the data returned by eob, they would all use the same value when it's time to render.
What does your LoadControl overload look like? I read through the article you referenced, but I'd like to see what you did, specifically. I wonder if simply reassigning values from eob to a variable declared inside your LoadControl method would get you over the hump. Force the controls to use a variable from a tighter scope so they can't physically see each other's arguments.
Edit: Found an SO reference on the topic: What are 'closures' in .NET?
Give this a whirl:
private UserControl LoadControl(string userControlPath, params object[] constructorParameters)
{
var constParamTypes = new List<Type>();
foreach (var constParam in constructorParameters)
{
constParamTypes.Add(constParam.GetType());
}
var ctl = Page.LoadControl(userControlPath) as UserControl;
// Find the relevant constructor
if (ctl != null)
{
var constructor = ctl.GetType().BaseType.GetConstructor(constParamTypes.ToArray());
// And then call the relevant constructor
if (constructor == null)
{
throw new MemberAccessException("The requested constructor was not found on : " + ctl.GetType().BaseType);
}
// constructor.Invoke(ctl, constructorParameters);
object[] cp = constructorParameters;
constructor.Invoke(ctl, cp);
}
// Finally return the fully initialized UC
return ctl;
}
If I had this issue and there was a base class I could instrument and a way to produce a trace of some kind without too much trouble, I'd
put a const instance member Guid and a DateTime, both initializes it in the constructor
put a few ms sleep between separate calls to the constructor so the timestamps
I'm just thinking that might give a hint as to what's going on and lead to the root of it.
Related
I have a ToolStripMenuItem called myMenu. How can I access this like so:
/* Normally, I would do: */
this.myMenu... etc.
/* But how do I access it like this: */
String name = myMenu;
this.name...
This is because I am dynamically generating ToolStripMenuItems from an XML file and need to reference MenuItems by their dynamically generated names.
Use the Control.ControlCollection.Find method.
Try this:
this.Controls.Find()
string name = "the_name_you_know";
Control ctn = this.Controls[name];
ctn.Text = "Example...";
Assuming you have the menuStrip object and the menu is only one level deep, use:
ToolStripMenuItem item = menuStrip.Items
.OfType<ToolStripMenuItem>()
.SelectMany(it => it.DropDownItems.OfType<ToolStripMenuItem>())
.SingleOrDefault(n => n.Name == "MyMenu");
For deeper menu levels add more SelectMany operators in the statement.
if you want to search all menu items in the strip then use
ToolStripMenuItem item = menuStrip.Items
.Find("MyMenu",true)
.OfType<ToolStripMenuItem>()
.Single();
However, make sure each menu has a different name to avoid exception thrown by key duplicates.
To avoid exceptions you could use FirstOrDefault instead of SingleOrDefault / Single, or just return a sequence if you might have Name duplicates.
Control GetControlByName(string Name)
{
foreach(Control c in this.Controls)
if(c.Name == Name)
return c;
return null;
}
Disregard this, I reinvent wheels.
Using the same approach of Philip Wallace, we can do like this:
public Control GetControlByName(Control ParentCntl, string NameToSearch)
{
if (ParentCntl.Name == NameToSearch)
return ParentCntl;
foreach (Control ChildCntl in ParentCntl.Controls)
{
Control ResultCntl = GetControlByName(ChildCntl, NameToSearch);
if (ResultCntl != null)
return ResultCntl;
}
return null;
}
Example:
public void doSomething()
{
TextBox myTextBox = (TextBox) this.GetControlByName(this, "mytextboxname");
myTextBox.Text = "Hello!";
}
I hope it help! :)
this.Controls.Find(name, searchAllChildren) doesn't find ToolStripItem because ToolStripItem is not a Control
using SWF = System.Windows.Forms;
using NUF = NUnit.Framework;
namespace workshop.findControlTest {
[NUF.TestFixture]
public class FormTest {
[NUF.Test]public void Find_menu() {
// == prepare ==
var fileTool = new SWF.ToolStripMenuItem();
fileTool.Name = "fileTool";
fileTool.Text = "File";
var menuStrip = new SWF.MenuStrip();
menuStrip.Items.Add(fileTool);
var form = new SWF.Form();
form.Controls.Add(menuStrip);
// == execute ==
var ctrl = form.Controls.Find("fileTool", true);
// == not found! ==
NUF.Assert.That(ctrl.Length, NUF.Is.EqualTo(0));
}
}
}
One of the best way is a single row of code like this:
In this example we search all PictureBox by name in a form
PictureBox[] picSample =
(PictureBox)this.Controls.Find(PIC_SAMPLE_NAME, true);
Most important is the second paramenter of find.
if you are certain that the control name exists you can directly use it:
PictureBox picSample =
(PictureBox)this.Controls.Find(PIC_SAMPLE_NAME, true)[0];
You can use find function in your Form class. If you want to cast (Label) ,(TextView) ... etc, in this way you can use special features of objects. It will be return Label object.
(Label)this.Controls.Find(name,true)[0];
name: item name of searched item in the form
true: Search all Children boolean value
this.Controls["name"];
This is the actual code that is ran:
public virtual Control this[string key]
{
get
{
if (!string.IsNullOrEmpty(key))
{
int index = this.IndexOfKey(key);
if (this.IsValidIndex(index))
{
return this[index];
}
}
return null;
}
}
vs:
public Control[] Find(string key, bool searchAllChildren)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException("key", SR.GetString("FindKeyMayNotBeEmptyOrNull"));
}
ArrayList list = this.FindInternal(key, searchAllChildren, this, new ArrayList());
Control[] array = new Control[list.Count];
list.CopyTo(array, 0);
return array;
}
private ArrayList FindInternal(string key, bool searchAllChildren, Control.ControlCollection controlsToLookIn, ArrayList foundControls)
{
if ((controlsToLookIn == null) || (foundControls == null))
{
return null;
}
try
{
for (int i = 0; i < controlsToLookIn.Count; i++)
{
if ((controlsToLookIn[i] != null) && WindowsFormsUtils.SafeCompareStrings(controlsToLookIn[i].Name, key, true))
{
foundControls.Add(controlsToLookIn[i]);
}
}
if (!searchAllChildren)
{
return foundControls;
}
for (int j = 0; j < controlsToLookIn.Count; j++)
{
if (((controlsToLookIn[j] != null) && (controlsToLookIn[j].Controls != null)) && (controlsToLookIn[j].Controls.Count > 0))
{
foundControls = this.FindInternal(key, searchAllChildren, controlsToLookIn[j].Controls, foundControls);
}
}
}
catch (Exception exception)
{
if (ClientUtils.IsSecurityOrCriticalException(exception))
{
throw;
}
}
return foundControls;
}
Assuming you have Windows.Form Form1 as the parent form which owns the menu you've created. One of the form's attributes is named .Menu. If the menu was created programmatically, it should be the same, and it would be recognized as a menu and placed in the Menu attribute of the Form.
In this case, I had a main menu called File. A sub menu, called a MenuItem under File contained the tag Open and was named menu_File_Open. The following worked. Assuming you
// So you don't have to fully reference the objects.
using System.Windows.Forms;
// More stuff before the real code line, but irrelevant to this discussion.
MenuItem my_menuItem = (MenuItem)Form1.Menu.MenuItems["menu_File_Open"];
// Now you can do what you like with my_menuItem;
Since you're generating them dynamically, keep a map between a string and the menu item, that will allow fast retrieval.
// in class scope
private readonly Dictionary<string, ToolStripMenuItem> _menuItemsByName = new Dictionary<string, ToolStripMenuItem>();
// in your method creating items
ToolStripMenuItem createdItem = ...
_menuItemsByName.Add("<name here>", createdItem);
// to access it
ToolStripMenuItem menuItem = _menuItemsByName["<name here>"];
Have a look at the ToolStrip.Items collection. It even has a find method available.
You can do the following:
private ToolStripMenuItem getToolStripMenuItemByName(string nameParam)
{
foreach (Control ctn in this.Controls)
{
if (ctn is ToolStripMenuItem)
{
if (ctn.Name = nameParam)
{
return ctn;
}
}
}
return null;
}
A simple solution would be to iterate through the Controls list in a foreach loop. Something like this:
foreach (Control child in Controls)
{
// Code that executes for each control.
}
So now you have your iterator, child, which is of type Control. Now do what you will with that, personally I found this in a project I did a while ago in which it added an event for this control, like this:
child.MouseDown += new MouseEventHandler(dragDown);
Okay so I'm adapting a C# program to an asp program and I have a main form which contains a list box and another which adds new information to the list box. I can fill in the 2nd form and hold values in Application["getData"]; but when I go to the other page I need to run the following code.
public void AddGig()
{
AddGigForm frm = new AddGigForm();
if (Application["getData"] != null)
{
Application["saveData"] = Application["getData"];
gigList.addGig(frm.GetData());
UpdateListbox();
}
I run into problems at gigList.addGig as it goes back to the method GetData() on the 2nd form. I just have no idea what else to use.
GetData method:
public GigOpportunity GetData()
{
Application["GetData"] = new GigOpportunity
(txtId.Text, gigDate.SelectedDate, txtVenue.Text, txtGenre.Text,
Convert.ToDouble(txtCost.Text), Convert.ToInt32(txtCapacity.Text), chkHeadliner.Checked, txtMainAct.Text, chkEngineer.Checked);
return new GigOpportunity(txtId.Text, gigDate.SelectedDate, txtVenue.Text, txtGenre.Text, Convert.ToDouble(txtCost.Text), Convert.ToInt32(txtCapacity.Text), chkHeadliner.Checked, txtMainAct.Text, chkEngineer.Checked);
}
addGig method:
public void addGig(GigOpportunity gigOpportunity)
{
//Make sure a gig with this id does not already exist
foreach (GigOpportunity g in gigList)
{
if (g.GigId == gigOpportunity.GigId)
{
throw new DuplicateIdException();
}
}
gigList.Add(gigOpportunity);
}
I understand now your problem. You musn't think like in windows form. You declared those method inside other form. When you call it by assigning a new Form object you will not get the value inside as they have been disposed after you change the page.
So in your case:
if (Application["getData"] != null)
{
Application["saveData"] = Application["getData"];
gigList.addGig((GigOpportunity)Application["getData"]);
UpdateListbox();
}
But I will suggest you to use Session object instead of Application object.
You can read more about it here
So you have to do like this:
if (Session["getData"] != null)
{
Session["saveData"] = Session["getData"];
gigList.addGig((GigOpportunity)Session["getData"]);
UpdateListbox();
}
You don't need to create the second form object AddGigForm and you must be sure to call your methodGetData in the form where is it declared to assign your Session.
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 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();