C# _Click Event Casting Sender and Using FindControl - c#

I have a _Click event that is firing when a chart is clicked on. In this event I immediately cast the sender to type Chart. I often follow this paradigm but feel icky each time that I do.
In this particular instance I am also immediately running out to find a corresponding UpdatePanel so I can add a dynamically rendered GridView to it. The Chart and UpdatePanel are cobbled together by having similar IDs. Both the chart and updatepanel are dynamically created at runtime.
I am wondering if there is a better/preferred way to implement this sort of behavior.
protected void Chart_Click(object sender, ImageMapEventArgs e)
{
Chart chart = (Chart)sender;
UpdatePanel up = (UpdatePanel)chart.Parent.FindControl(chart.ID + "UP");
GridView gv = new GridView();
Dictionary<string, string> displayFields =
new Dictionary<string, string>();
// add data to displayFields by using the ImageMapEventArgs.PostBackValue
// to create data for dictionary ...
gv.DataSource = displayFields;
gv.DataBind();
up.ContentTemplateContainer.Controls.Add(gv);
}

I'm not sure what else can be done about casting sender to a chart (other than using as), but there are many ways to deal with the associated controls problem.
Add a property to the Chart called
"LinkedPanel" and assign your update panel to it
You'll see a similar pattern used in a notification icon, where you associate it with a context menu)
I think this is particularly nice, because when it's done right you can just assign the linked control in the form designer. (Of course it won't help with your dynamically generated controls)
Combine both controls into a single
UserControl (if the controls always show right next to each other, this might be the right thing to do)
Create an object that knows about both controls, and have it handle the events that they raise
I agree that depending on the name just feels wrong; I'd be too embarassed/nervous to use it in production code :-/ (If you're the only developer, then I guess it's up to you...)
Using these other approaches makes it less likely that an "innocent change" made by another developer is going to start causing unexpected exceptions.

Use as instead.
Your cast will throw if it fails.
Chart chart = sender as Chart;
if (chart == null)
return; // or do something else
// the rest of your handler.

I think it's safe to cast sender to Chart because you know it's always a Chart. Note the method name is even Chart_Click.
You could, however, save a reference to the UpdatePanel in the Chart's Tag property. That saves the ugliness and risk of a name-search in the parent. Could get hard to maintain if you're constantly switching parents or moving panels around.
If you know 100% the UpdatePanel's going to be there and named properly, however, there's nothing necessarily "wrong" with your approach IMHO. You might want to throw a "don't change this name" comment next to the UpdatePanel.Name = line where you initialize it though for security. (BTW, if you're not the only one with access to your code, the 100% instantly drops to at most a 99.9%.)

There is nothing really wrong in what you have done. But you can prevent typecasting error by using 'as' instaed of explicit casting as follows.
if you want to find if the control is an UpdatePanel you can do so by using GetType() method
example:
sender.GetType().name will give you the control name
protected void Chart_Click(object sender, ImageMapEventArgs e)
{
Chart chart = (Chart)sender;
control c = Parent.FindControl(chart.ID + "UP");
UpdatePanel up ;
if (c != null)
{
up = c as UpdatePanel;**
GridView gv = new GridView();
Dictionary<string, string> displayFields =
new Dictionary<string, string>();
// add data to displayFields by using the ImageMapEventArgs.PostBackValue
// to create data for dictionary ...
gv.DataSource = displayFields;
gv.DataBind();
up.ContentTemplateContainer.Controls.Add(gv);
}
}

Related

Add and read Multiple textboxes

What is the best way to create and read multiple textboxes in a windows form? In my application, I have a windows form where customer can enter multiple addresses, email addresses
Right now I have a form like this,
TextBoxAddress1 TextBoxEmail1
TextBoxAddress2 TextBoxEmail2
.....
.....
.....
TextBoxAddressN TextBoxEmailN
For this I dragged and dropped multiple controls on a form and named each one of them.
If I use this method I had to write lengthy code to see if first row (TextBoxAddress1 TextBoxEmail1) is filled for validation and even for reading I had to write many lines of code.
Is there a better to way achieve this?
You can use the following code to add a TextBox dynamically to your form:
private int m_CurrTexboxYPos = 10;
private List<TextBox> m_TextBoxList = new List<TextBox>();
private void CreateCheckBox()
{
m_CurrTexboxYPos += 25;
TextBox textbox = new TextBox();
textbox.Location = new System.Drawing.Point(0, m_CurrTexboxYPos);
textbox.Size = new System.Drawing.Size(100,20);
Controls.Add(textbox);
m_TextBoxList.Add(textbox);
}
I would have a listbox/listview with your emails and Add/Edit/Delete buttons which show a popup form - the logic for validating emails, etc. would then be in the one place and your list can grow without you ever needing to add controls to the form.
You could dynamically create textboxes - but you end up writing code to make sure they layout nicely on the form, etc. - having some type of list is easier IMO and also lends itself to binding (e.g. to an email object)
Dynamically adding controls is pretty simple, provided you can use DockStyle and an exclusive container for them (e.g. a Panel). If you can't use DockStyle, then you need to write logic to determine Location and Size (which isn't fun).
On a simple form, I have two buttons and a panel, Button1 adds a new TextBox to Panel1, Button2 iterates through the controls in Panel1 and then checks that they are the correct type or throws an exception. This is where you you would put validation or reading logic. Panel1 needs to have AutoScroll = true; otherwise you will run controls off of the viewable screen.
This concept can be switched for anything that inherits from UserControl (all .Net native controls or your own custom controls).
private void button1_Click(object sender, EventArgs e)
{
TextBox NewEmailBox = new TextBox();
NewEmailBox.Name = "NewEmailBox" + this.panel1.Controls.Count;
NewEmailBox.Dock = DockStyle.Top;
this.panel1.Controls.Add(NewEmailBox);
}
private void button2_Click(object sender, EventArgs e)
{
foreach (Control item in this.panel1.Controls)
{
if (item is TextBox)
{
//Do your reading/validating here.
}
else
{
throw new InvalidCastException(string.Format("{0} was in Panel1 and is of type {1} not TextBox!", item.Name, item.GetType()));
}
}
}
Write a user control for each of the groupings you need. at least one One for address, one for email etc. then all of your validation, calls to your database access is contained in a single location
That is just good design. this way if you have multiple tabs for things like Home Information, Work Information, Emergency Contact Information, you can just place them on the form. This is pretty common for a user profile.
Then a listview for each grouping on a user profile page or whatever, that has edit/delete/add then popup a dialog with the appropriate user control in it.
Most simply, ListBox adove TextBox with Button.
Also you can use DataGridView, BuiltIn functionality for Add\Edit\Delete.
Here using DataGridView (ShowHeader set to false, EditMode to On Enter, with one Column with AutoSizeMode in Fill property)
The less of repeatable code you have, the better programmer you are.
Whenever you see a pattern (something what is repeatable), you could and you should try to optimize it. Unless it's something too small to worry.
In your case, determine first what is the basic of repeatable thing. Do you always have to enter address and email address? Then combine them into a control, which can carry out validation. Do you have to use this control often (or repeat N times)? Then maybe it make sense to switch to a list instead (ListBox, ListView or DataGridView).
Are you too lazy to bother configuring things? Then just optimize something what is obviously going to repeat: put validation into common method and call it from each TextBox event. Or make own TextBox with method build-in. Or do validation at once in the Ok button event by using loop.. or not by using loop.
To find best method you have to first decide best for who. Because customer want something shiny,easy to use, animated, with cats and boobs.. ok, without cats and boobs. The point is: how much work are you willing to put to have it best for the customer.
If I would have to enter table data (or data which form table), I'd go with DataGridView so it would looks like this.. or better:

Best way to handle controls automatically updating each other?

I have a question regarding updating Controls in .Net in such a way so that if the user updates one field, the other field will be updated with some data automatically and vice versa. I am using two NumericUpDown controls which convert some data.
The problem I am having is that, I am using the ValueChanged event. Because of this, sometimes these controls get stuck in a loop where one controls updates the other and the other tries to update the first one. The result is somewhat random.
So, what would be the best approach to handle this situation? In short, I only want to update the other control, if the first one was modified by a user himself.
Any help would be greatly appreciated.
Just use a boolean guard in the class to check whether you are inside an update method.
While you are updating, all future events fired from the NUDs will be ignored.
private boolean updating = false; // Class level variable
void event_handler(...) // The function hooked up to the ValueChanged event
{
if( !updating )
{
updating = true;
// Do your calculations and update the NUDs
updating = false;
}
}
I would recommend that you use data binding and bind to an object which serves as your model. Your model then is where the logic goes that alters the other value based on changing of a property. The model also raises IPropertyChanged / IPropertyChanging events, which the UI will pick up on. Not only will this prevent the issue you describe, it also keeps this business logic outside of your UI layer should you move to something else (say from WinForms to WPF, or Asp.Net MVC).
If method Foo handles the event of one control and method Bar handles the event for the other, then Foo should change the values of Bar's control and vice-versa. But you should use a control variable somewhere (say, a reference to the control that fired the event is a good idea). So that if Foo is called:
Foo updates the value of Bar's control;
Bar's control fires its event, and Bar is called;
Bar checks the reference for the control that shot first, sees that it's not its control, and does nothing.
Same logic applies to Bar.
that way you don't get an infinite loop.
In code, it'd, look like this:
nud1.ValueChanged += new Eventhandler(Foo);
nud2.ValueChanged += new Eventhandler(Bar);
NumericUpDown shooter = null;
private void Foo (object sender, EventArgs e)
{
if (this.shooter == null)
{
this.shooter = nud1;
nud2.Value = nud1.Value;
}
else this.shooter = null;
}
private void Bar (object sender, EventArgs e)
{
if (this.shooter == null)
{
this.shooter = nud2;
nud1.Value = nud2.Value;
}
else this.shooter = null;
}
Of course, this is a crude example (for example, it assumes the value of both controls is always changing. Adapt to your situation.
I like Andy's response about going with an MVC pattern, but if that's too radical of a change for this specific situation, you should set the values only if the current value is different than the value being assigned. That would prevent the ValueChanged event from firing again and stop the infinite loop the first time recursion happens.
// Inside your value changed handler for Control2,
// instead of directly setting the value of Control1, do this:
if(Control1.Value != valueBeingSet)
{
Control1.Value = valueBeingSet;
}

ASP.NET - Accessing All ImageButtons on a Page and Putting Two Images on an ImageButton

I actually have two questions:
(1) Is it possible to put another image on top of an ImageButton that already has an ImageUrl set (without changing the ImageUrl - literally just add the second image 'on top')? Even by using CSS?
(2) I have a dynamically set number of ImageButtons contained within a ListView. When a user clicks on an ImageButton, I change the .CssClass property of the one clicked in order to 'highlight' it. My question is this: whenever an ImageButton is click, I need to not only highlight it, but make sure I unhighlight all the others. However, I'm having trouble getting the others. I get the clicked ImageButton using
((ImageButton)sender).CssClass = "SelectedImageButton";
in the event handler. However, how do I get all the others so I can set their style 'back' to the unhighlighted style?
Thanks in advance for any help!
UPDATE: ANSWERED!
I've solved the issue mentioned in (2) using the following algorithm. Note, I've marked #OFConsulting's answer below as the correct answer because without his algorithm, I would have never gotten the following algorithm (which came from tweaking his algorithm slightly). Thanks #OFConsulting!
// Cast the sender to an ImageButton to have the clicked ImageButton
ImageButton clickedImageButton = sender as ImageButton;
// The ListView has ListViewDataItems and the ImageButtons are in
// THOSE children controls, thus match on the ImageButtons' Parents' IDs
Control parentControl = clickedImageButton.Parent;
List<ListViewDataItem> allOtherImageButtons = MyListView.Controls.OfType<ListViewDataItem().AsQueryable().Where(i => i.ID != clickedImageButton.Parent.ID).ToList();
// Highlight
clickedImageButton.CssClass = "HighlightedStyle";
// Unhighlight
foreach (ListViewDataItem button in allOtherImageButtons)
{
// The ImageButton is always the 2nd child control of the ListViewDataItem
ImageButton childImageButton = (ImageButton)button.Controls[1];
childImageButton.CssClass = "NoHighlightedStyle";
}
For Part (1) of that question, setting the background image within your css class might do the trick, but you never really explained why you just couldn't change the ImageUrl. You can always throw everything on an update panel if you need it to be dynamic without the hassle of a bunch of script.
Part (2) seems pretty straight forward. Just use a little bit of linq against the relevant control collection within your page.
protected void ImageButton5_Click(object sender, ImageClickEventArgs e)
{
ImageButton clickImageButton = sender as ImageButton;
// This example assumes all the image buttons have the same parent.
// Tweak as needed depending on the layout of your page
Control parentControl = clickImageButton.Parent;
List<ImageButton> allOtherImageButtons = parentControl.Controls.OfType<ImageButton>().AsQueryable().Where(i => i.ID != clickImageButton.ID).ToList();
// Highlight
clickImageButton.CssClass = "WhateverHighlights";
// Unhighlight
foreach (ImageButton button in allOtherImageButtons)
{
button.CssClass = "WhateverClears";
}
}
Edit: One more thing. Make sure any controls you are adding dynamically get added before Page_Load (I.E. during Init). There are some viewstate issues associated with adding control too late.

Find Unchecked Checkboxes with jQuery and pass the Names to C# Code

This is my code:
public class MyCollection {
internal static Dictionary<string, CheckBox> MyCheckBox = new Dictionary<string, CheckBox>();
}
protected void Page_Load(object sender, EventArgs e) {
if (!IsPostBack) {
CheckBox chk1 = new CheckBox();
chk1.ID = "chk1";
chk1.Checked = true;
if (!MyCollection.MyCheckBox.ContainsKey(chk1.ID))
MyCollection.MyCheckBox.Add(chk1.ID, chk1);
CheckBox chk2 = new CheckBox();
chk2.ID = "chk2";
chk2.Checked = true;
if (!MyCollection.MyCheckBox.ContainsKey(chk2.ID))
MyCollection.MyCheckBox.Add(chk2.ID, chk2);
pl1.Controls.Add(chk1);
pl2.Controls.Add(chk2);
}
}
protected void btn1Click(object sender, EventArgs e) {
lit1.Text = "Chk1.Checked: " + MyCollection.MyCheckBox["chk1"].Checked.ToString();
lit1.Text += "<br />Chk2.Checked: " + MyCollection.MyCheckBox["chk2"].Checked.ToString();
}
i have 2 checkboxes, but always when press the button, Checkboxes.Checked are true, also when i unchecked them,
how can i hold checkboxes in some dictionary like above and check their Checked property in right way?
actualy i do't want any event for change Checkboxes, I Think about a jQuery script, that when i press the button retrive me the name of checkboxes that unchecked
i find a jQuery that can find the checkboxe names that unchecked but i don't know how can pass the names to the c# code?
my above code is just a sample and real project is a user control that dynamically define and render controls from a type, hmm the scenario is like this: the clients add the assembly of user control and declare a property of that named target type, user control read target and for each property decide which control must be add so by overrided RenderContents and other methods can add controls to page, so every control automatically have a Checkbox that enable/disable control and i need the checked property
i find this code in Stackoverflow...
var sList = "";
$('input[type=checkbox]').each(function () {
var sThisVal = (this.checked ? "1" : "0");
sList += (sList=="" ? sThisVal : "," + sThisVal);
});
console.log (sList);
but this code just write sList on console how can retrive the sList values in C# Code?
Firstly, you should never store an instance of a control such as a CheckBox in a static member since this is a recipe for a memory leak in your application. ASP.NET Control classes hold a reference to their containing Page and so by adding the Control to a static collection you're going to keep the entire Page instance and all its child Controls from being garbage-collected.
Secondly, there's no point in doing this in your case since it isn't going to achieve what you're trying to do. The instance of the CheckBox class that you add to the Dictionary isn't going to be the same instance that you want to interact with on subsequent requests; the instance you store in the Dictionary is for that request only which is why the CheckBox instances are always returning true from their Checked property.
I don't think that JQuery is relevant here since that will not help you reconstitute the CheckBox controls on the server. What you need to do is add the CheckBox controls to the Page on every request. In order for the CheckBox controls to have their state loaded from the Form collection you need to add the Controls early enough in the Page lifecycle: I would recommend trying to add them during the Page.Init event.
public void Page_Init(object sender, EventArgs e)
{
CheckBox chk1 = new CheckBox();
chk1.ID = "Chk1";
CheckBox chk2 = new CheckBox();
ck2.ID = "Chk2";
if(!IsPostBack)
{
ck1.Checked = true;
ck2.Checked = true;
}
p1.Controls.Add(chk1);
p2.Controls.Add(chk2);
}
Without the actual code it is difficult to understand what you're trying to do and how you're going about it. The fact remains, though, that if you don't add the CheckBox controls to the Page on every request then they cannot participate in the Page lifecycle and won't have their state loaded from the Form collection when the Page is submitted. It sounds to me like you want to manipulate the control tree of the Page on the server but that needs to be reconstituted on every request and only exists during the handling of the request. After the Page is rendered to the client the control tree no longer exists.
So, you could POST the values of the HTML input checkboxes to the server using JavaScript but where would you send them and what do you expect to happen? It seems like you're fighting against the ASP.NET Webforms way of doing things which revolves around post-backs and the Page object on the server that handles the request.
Use this one maybe useful this is all of your needed test it and let me know about the result:
Loop through checkboxes and count each one checked or unchecked
Remember please some simple thing: YOU MUST ADD DYNAMIC CONTROLS ON EACH AND EVERY POSTBACK. This will save a lot of your time.

Dynamically Populated TableLayoutPanel Performance Degredation

I have a user control that contains a 2-column TableLayoutPanel and accepts commands to dynamically add rows to display details of an item selected in a separate control. So, the user will select a row in the other control (a DataGridView), and in the SelectedItemChanged event handler for the DataGridView I clear the detail control and then regenerate all the rows for the new selected item (which may have a totally different detail display from the previously selected item). This works great for a while. But if I keep moving from one selected item to another for quite a long time, the refreshes become VERY slow (3-5 seconds each). That makes it sound like I'm not disposing everything properly, but I can't figure out what I'm missing. Here's my code for clearing the TableLayoutPanel:
private readonly List<Control> controls;
public void Clear()
{
detailTable.Visible = false;
detailTable.SuspendLayout();
SuspendLayout();
detailTable.RowStyles.Clear();
detailTable.Controls.Clear();
DisposeAndClearControls();
detailTable.RowCount = 0;
detailTable.ColumnCount = 2;
}
private void DisposeAndClearControls()
{
foreach (Control control in controls)
{
control.Dispose();
}
controls.Clear();
}
And once I get finished loading up all the controls I want into the TableLayoutPanel for the next detail display here's what I call:
public void Render()
{
detailTable.ResumeLayout(false);
detailTable.PerformLayout();
ResumeLayout(false);
detailTable.Visible = true;
}
I'm not using anything but labels (and a TextBox very rarely) inside the TableLayoutPanel, and I add the Labels and TextBoxes to the controls list (referenced in DisposeAndClearControls()) when I create them. I tried just iterating over detailTable.Controls and disposing them that way, but it seemed to miss half the controls (determined by stepping through it in the debugger). This way I know I get them all.
I'd be interested in any suggestions to improve drawing performance, but particularly what's causing the degradation over multiple selections.
Just use a custom control that inherits from TableLayoutPanel and set the DoubleBuffered property on true, works great... especially when you dynamically add or remove rows.
public CustomLayout()
{
this.DoubleBuffered = true;
InitializeComponent();
}
I had a similar issue with TableLayout. If I used TableLayout.Controls.Clear() method, the child controls never got disposed but when I simply dropped the TableLayout without clearing it, the leak stopped. In retrospect, it's funny I used the Clear method to prevent some kind of leak.
Apparently, Clear method does not explicitly dispose of the controls (which makes sense, because the fact that you removed them from the TableLayout does not mean you are done with them) and removing the child controls from the TableLayout prevents the cleanup routine to dispose of the children when the LayoutTable itself gets disposed (it simply does not know about them anymore).
My recommendation: Delete the detailTable.Controls.Clear(); line, remove the detailTable itself from the parent's Controls collection and dispose it, then create a brand new TableLayout for the next round. Also lose the DisposeAndClearControls method entirely since you won't need it. In my experience, it worked nicely.
This way, you won't have to recycle your entire user control anymore but only the TableLayout within.
Unfortunately, the only advice I can offer is to take care of the placement of your controls yourself. In my experience the .NET TableLayoutPanel, while very useful, is leaking SOMETHING and becomes unusably slow as it grows (and it doesn't take an unreasonable number of cells to get to this point, either). This behavior can be seen in the designer as well.
I changed the containing form to just construct a new version of my user control on each selection change. It disposes the old one and constructs a new one. This seems to perform just fine. I'd originally gone with reusing just one for performance reasons anyway. Clearly that doesn't improve the performance. And the performance isn't a problem if I dispose the old one and create a new one.
Unfortunate that the TableLayoutPanel leaks like that, though.
I faced the same problem and found a good way without changing too much:
in VB.net
Dim tp As Type = tlpMyPanel.GetType().BaseType
Dim pi As Reflection.PropertyInfo = _
tp.GetProperty("DoubleBuffered", _
Reflection.BindingFlags.Instance _
Or Reflection.BindingFlags.NonPublic)
pi.SetValue(tlpMyPanel, True, Nothing)
or in C#:
Type tp = tlpMyPanel.GetType().BaseType;
System.Reflection.PropertyInfo pi =
tp.GetProperty("DoubleBuffered",
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic);
pi.SetValue(tlpMyPanel, true, null);
TableLayoutPanel.Controls.Clear() works fine for me, maybe its because i clear it from a different tab than its displayed in.
List<Control> controls = new List<Control>();
foreach (Control control in tableLayoutPanelEnderecoDetalhes.Controls)
{
controls.Add(control);
}
foreach (Control control in controls)
{
control.Dispose();
}

Categories

Resources