Accessing and Setting Winforms Control at runtime - c#

I've built a Winforms table layout which has many elements in each cell. What I'm trying to do is enumerate all controls of a particular type which I'm concerned with, and remove their corresponding Click values. This something done at runtime, which is why I'm not just setting the controls initial properties.
I've tried working with the Control.ControlCollection property although I've not had much luck, as it's not IEnumerable. Alternative I have the following code but the problem is the control is never found in Controls and a Null Reference exception is thrown because I'm, trying to set .Click on an empty object, but surely this object should exist! Help!?
for (int row = 1; row < tblLayoutPanel.RowCount; row++)
{
for (int column = 0; column < tblLayoutPanel.ColumnCount; column++)
{
Type controlType = tblLayoutPanel.GetControlFromPosition(column, row).GetType();
if (controlType == typeof(CTLLabel) ||
controlType == typeof(OutputLabel))
{
Controls[tblLayoutPanel.GetControlFromPosition(column, row).Name].Click += null;
// Control is never found! *why?*
}
}
}

The reason is that the parent control of the control that you are getting using GetControlFromPosition method is tblLayoutPanel. You are trying to get the child control of the tblLayoutPanel in the parent of the tblLayoutPanel control instead of tblLayoutPanel.Controls collection.
Instead of accessing the control like this, why you don't just use the control you've got by using GetControlFromPosition method. Try this:
tblLayoutPanel.GetControlFromPosition(column, row).Click

Related

Difficulty with tabControl/tabitem refresh

I have a WPF window with a maintabWindow and several tabitems.
It normally works fine and the layout is this:
but when I BEFORE add the following window:
the result is this:
So the problem is related with the tabControl/tabItem refresh.
This is fairly obvious but even more because if I move the window or pass with the mouse on the a tabItem they get refreshed one by one.
I searched and found that here is a solution: http://geekswithblogs.net/NewThingsILearned/archive/2008/08/25/refresh--update-wpf-controls.aspx
so I added:
this.MainTab.Refresh();
this.tabItem1.Refresh();
this.tabItem2.Refresh();
this.tabItem3.Refresh();
this.tabItem4.Refresh();
this.tabItem5.Refresh();
but that didn't change a thing.
Thanx for any help
Ok so in the end it has a quite a weird behavious. If I do
for (int i = 0; i < tbcMain.Items.Count; i++)
{
tbcMain.SelectedIndex = i;
tbcMain.UpdateLayout();
}
it works. But I have to set the 1st tabitem so if I add
tbcMain.SelectedIndex = 0;
it doesn't.
So the solution was put a sleep and it works again.
for (int i = 0; i < tbcMain.Items.Count; i++)
{
tbcMain.SelectedIndex = i;
tbcMain.UpdateLayout();
}
System.Threading.Thread.Sleep(250);
tbcMain.SelectedIndex = 0;
But that is not elegant at all. If anyone has a better solution pls let me know it.
Btw adding the tbcMain.SelectedIndex = 0; on the loaded event of the mainWindow is of no use.
You should be able to set your SelectedIndex first, and without having to include it in your loop:
tbcMain.SelectedIndex = 0;
Then, basing it off of your response, you should be able to either just do .UpdateLayout() on each of your TabItems:
MainTab.UpdateLayout();
tabItem1.UpdateLayout();
tabItem2.UpdateLayout();
tabItem3.UpdateLayout();
tabItem4.UpdateLayout();
tabItem5.UpdateLayout();
Or you should be able to do something like this in your loop:
MainTab.UpdateLayout();
for (int i = 0; i < tbcMain.Items.Count; i++)
{
TabItem tbi = (TabItem)this.FindControl("tabItem"+i);
tbi.UpdateLayout();
}
Updating/refreshing should have nothing to do with setting the selected one. Including the selection of the tab within the loop to i was your problem - not a race condition. Set the tbcMain.SelectedIndex = 0 outside of your loop and you should be fine. Sometimes, however, this doesn't work and you need to set it with Dispatcher:
Dispatcher.BeginInvoke((Action)(() => this.tbcMain.SelectedIndex = 0));
There's a write up/comments on a separate thread regarding why it needs to be sent to Dispatcher:
How to programmatically select a TabItem in WPF TabControl
Though, unfortunately for me, I had a similar issue where I was trying to refresh a ListView on a subtab. Neither .UpdateLayout(), nor .InvalidateVisual() (as I saw on this thread) worked. I just had to rebind my grid in the button event I was using on my main page, so that when the tab was clicked, it was refreshed manually. I added an x:Name property on the tab so I could call it using "dot" syntax, and it exposed the ListView. I simply added the DataTable of results back to that ListView's DataContext.

Working with ListBox elements in a user control

I have a user control (ucMarket) which contains (for the purpose of simplicity) two controls: a ListBox (ucListBox) and a Label (ucLabel). I need to create multiple instances of that user control on the page dynamically (depending on the results from a DataSet), and I add them using a foreach statement and the following:
Panel1.Controls.Add(ucMarket1);
But how do I get access to the ListBox properties like Rows ? The only thing I have found so far is to cast the control as a ListBox:
ListBox listBox1 = (ListBox)ucMarket1.FindControl("ucListBox");
listBox1.Rows = 10;
For the Label part, I guess I can also do something similar:
label1 = (Label)ucMarket1.FindControl("ucLabel");
But then, how do I put that information back into the user control ? Is there a way to work directly with the user control instead of casting ?
Ok a couple of things. from a naming convention point of view, don't call the label & listbox, ucSOMETHING. This is very confusing and not clear from your example whether you're referring to the asp:Label control or some custom userControl you've written. As for accessing your controls.
I'm assuming you are creating and adding a bunch of user controls in the following manner.
for(int i = 0; i < 5; i++)
{
var control = Page.LoadControl("~/Path/To/ucMarket.ascx");
control.Id = "ucMarket" + i;
Panel1.Controls.Add(control);
}
So your best bet is to expose the Listbox on your Control as a public property.
public class ucMarket : UserControl
{
public ListBox TheListBox
{
get { return ucListBox; }
}
}
That way you could access your listbox in the following way.
var ctrl = Panel1.FindControl("ucMarket1") as ucMarket;
ctrl.TheListBox.Rows ;

How does one get a ListView (GridView) cell's contents when clicked with a mouse

I am trying to get the text value of a "cell" inside of a GridView that is set as the view of a ListView. I do not want to get the SelectedItem of the ListView as that just returns my entire View Model (but not which property the cell refers to).
I am able to get the text value by responding to direct mouse events (up down or whatever) and if the value is a textblock, obviously I can use the text. This works great and as of right now this is my only solution, although its currently limited.
I would like to take it a step further and be able to click anywhere with in the cell area, navigate around to find the appropriate textblock and then use that value. I have tried a half million ways to do this but what seems logical doesn't seem to quite work out like it should.
Setup:
I have a dynamic GridView that creates its own columns and bindings based on data models that I pass to it. I am using a programmatic cell template (shown below) to have individual control over the cells, particularly so I can add a "border" to it making it actually separate out each cell. I have named the objects so I can access them easier when I'm navigating around the VisualTree.
Here is the Template Code. (Note that the content presenter originally was a textblock itself, but this was changed for later flexibility)
private DataTemplate GetCellTemplate(string bindingName)
{
StringBuilder builder = new StringBuilder();
builder.Append("<DataTemplate ");
builder.Append("xmlns='http://schemas.microsoft.com/winfx/");
builder.Append("2006/xaml/presentation' ");
builder.Append("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' ");
builder.Append("xmlns:local = 'clr-namespace:XXXXXXXX");
builder.Append(";assembly=XXXXXXXXX'>");
builder.Append("<Border Name=\"border\" BorderThickness=\"1,0,0,0\" BorderBrush=\"Gray\" Margin=\"-6,-3,-6,-3\">");
builder.Append("<Grid Margin=\"6,3,6,3\">");
builder.Append("<ContentPresenter Name=\"content\" HorizontalAlignment=\"Stretch\" Content=\"{Binding ");
builder.Append(string.Format("{0}", bindingName));
builder.Append("}\"/>");
builder.Append("</Grid>");
builder.Append("</Border>");
builder.Append("</DataTemplate>");
DataTemplate cellTemplate= (DataTemplate)XamlReader.Parse(builder.ToString());
return cellTemplate;
}
What I have Tried:
The logical approach for me was to react to a Mouse event. From the object that had the mouse event I would do either
A. Look at its children to find a textblock, or
B. Get its parent then look for child with a textblock.
My assumption is that if I click in white space I'm clicking in a container that has my textblock. So far the two things that come up are a Border and a Rectangle (if I don't click the text itself). A. Returns absolutely nothing except for the recangle and the border. When I do B i can find textblocks but they are every single text block in the entire row.
So what I try to do from that is get all textblocks, then go backwards till I find which one has a IsMouseOver property as true. It turns out none of these objects EVER have a IsMouseOver except the content presenter for the entire row. So this seems to indicate to me is that the whitespace in the cells does not actually contain the textblock.
What I find is that when I click on the Border and start looking at children, I eventually get to a container that has a rectangle (the rectangle I click) and a grid row view presenter. The presenter shows all of the objects inside the row (hence why i would get all textblocks when i do this recursive scan).
Here is some of the code used to do this to get an idea of what i'm doing. I have written about 10 different versions of this same recursive code generally attempting to find who has the Mouse over it and is related to a textbox.
private void OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
{
object original = e.OriginalSource;
if (original is TextBlock)
{
this.valueTextBlock.Text = ((TextBlock)original).Text;
}
else if (original is FrameworkElement)
{
var result = GetAllNestedChildren<Border>(VisualTreeHelper.GetParent((DependencyObject)original)).Where(x => x.Name == "border").Where(x => HasAChildWithMouse(x)).ToList();
}
else
{
this.valueTextBlock.Text = string.Empty;
}
}
private bool HasAChildWithMouse(UIElement element)
{
if (element.IsMouseOver || element.IsMouseDirectlyOver)
return true;
var childCount = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < childCount; ++i)
{
var child = VisualTreeHelper.GetChild(element, i);
if (child is UIElement)
if (HasAChildWithMouse((UIElement)child))
return true;
}
return false;
}
private IEnumerable<T> GetAllNestedChildren<T>(DependencyObject obj) where T : UIElement
{
if (obj is T)
yield return obj as T;
var childCount = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < childCount; ++i)
{
var child = VisualTreeHelper.GetChild(obj, i);
foreach (var nested in GetAllNestedChildren<T>(child))
yield return nested;
}
}
private T GetObjectByTypeParentHasMouse<T>(DependencyObject obj) where T : UIElement
{
if (obj is T)
{
if ((VisualTreeHelper.GetParent(obj) as UIElement).IsMouseOver )
{
return obj as T;
}
}
var childCount = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < childCount; ++i)
{
var child = VisualTreeHelper.GetChild(obj, i);
var correctType = GetObjectByTypeParentHasMouse<T>(child);
if (correctType != null)
return correctType;
}
return null;
}
private T GetContainedType<T>(DependencyObject obj, bool checkForMouseOver) where T : UIElement
{
if (obj is T && ((T)obj).IsMouseOver)
return obj as T;
var childCount = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < childCount; ++i)
{
var child = VisualTreeHelper.GetChild(obj, i);
var correctType = GetContainedType<T>(child, checkForMouseOver);
if (correctType != null)
return correctType;
}
return null;
}
The other approach I took was to start with the TextBlock itself, find its containing parent and find out how i can navigate to the answer. I find the templateparent is the ContentPresenter (named ="content") I find the grid, and then the border. The parent of the border is a content presenter whos content is the data view model for the entire row. The parent of this contentpresenter is the grid column's presenter. This is the same one that i was navigating up to in the other one.
It would appear that the first approach objects while are contain the cell do not actually contain the textblock or the entire cell templated items. It would appear to me there is no way to go from the Border or Rectangle that is clicked, back to the actual text field.
"Long story short" is there ANY way to make this connection?
(Btw I am not willing to give up this ListView/GridView because its payoffs far outweigh this negative and I'd gladly give up on this idea to keep the rest).
I think you sjould be able to either
1) Add some kind of (toggle)button to the root of your data template, and either bind to Command and handle it on your viewmodel or bind to IsChecked/IsPressed and handle changes via data triggers or w/e on the view side.
2) Add EventTrigger to your datatemplate at some point, and handle PreviewNouseUp/Down events there via simple animations.

Get values from dynamic controls with c#?

I created a few radiobuttonlist controls on my project, they're created every time the page is loaded, i want to get the value of the radiobutton that the user has selected, but since my radiobuttons were created dynamically, i don't know how to acces to their values nor how to create their event handlers. Is there a way to assign a name or id to the control when i create it?
i hope you can help me.
I create a seires of radiobuttlist on the page_load event, with the text and their values been pulled out of a database. now, the user has to choose one of the options from that radiobuttlist and i want to get the value of the radiobutton the user checked. how do i do that if i don't know the name nor the id of the radiobuttlist since they're created dynamically.
this is what i've got:
for (int i = 3; i < numfields; i++) {
if (dr[i].ToString() != "" && dr[i] != null){
r.Items.Add(new ListItem(dr[i].ToString(), dr[i].ToString()));
//r.SelectedIndexChanged += new EventHandler(rowSelectedIndex);
}
}
so basically i use my datareader to loop through the data in the database, if the value from the field isn't empty or null, then i add an item to the radiobuttlist called "r"
i tried to create an eventhandler for that too, but since i have never worked with them i really don't know what to do. :(
I'm so sorry if i seem way too pathetic.
Taking a quick look at your code:
for (int i = 3; i < numfields; i++) {
if (dr[i].ToString() != "" && dr[i] != null){
r.Items.Add(new ListItem(dr[i].ToString(), dr[i].ToString()));
//r.SelectedIndexChanged += new EventHandler(rowSelectedIndex);
}
}
The most obvious thing that jumps out is your if statement. You should first check for null:
if (dr[i] != null && dr[i].ToString() != ""){
As if dr[i] is null, you'll get an exception (as you'll be trying to call the ToString() method on a null object.
If the contents of dr are always going to be strings, you might consider writing:
if(!String.IsNullOrEmpty(dr[i]){
I also note you start your indexing at 3 - is this because you want to skip the first 3 fields?
Wherever you create your variable, 'r', you can set the name and ID properties. You can use the ID property to look for the control on PostBack. So if you created your radiolist like so:
RadioButtonList r = new RadioButtonList();
r.Id = "MyRadioButtonList";
r.SelectedIndexChanged += MyRadioButton_SelectedIndexChanged;
Which would point at the following event handler:
private void MyRadioButton_SelectedIndexChanged(Object sender, EventArgs e) {
... Do Stuff ...
}
There are several ways of finding your control when you post back; you can look in the Request.Forms collection for a control matching the name of the control you submitted, or, more appropriately, you can use the FindControl method with the ID you gave the control. See C#, FindControl for a post with a method (by Jeff Atwood!) that will search the entire hierarchy of controls for your control.
When you add a dynamic control is important, too. If you add it too late in the page lifecycle then it will not be available on PostBack. See http://support.microsoft.com/kb/317515 for more details on just when to add a control. There are plenty of resources for Dynamic ASP.Net controls around too.
You could put your RadioButton into a list as you create them. This is also when you want to add your handlers.
RadioButton rb;
for (int i = 1; i < 5; i++)
{
rb = new RadioButton();
rb.AutoSize = true;
rb.Location = new System.Drawing.Point(25, (i*25) + 25);
rb.Name = "radioButton" + i.ToString();
rb.Text = "radioButton" + i.ToString();
//Add some event handler?
this.Controls.Add(rb);
lstRadioButton.Add(rb);
}
Whenever you want to know which one is selected you can do a foreach loop of your list and look if your RadioButton is checked.
foreach (RadioButton rButton in lstRadioButton)
{
if (rButton.Checked == true)
{
//Do something
}
}
You are maybe searching for TagName property if the programmatic name isn't enough for you.
The problem is that you are creating the controls in page_load. In order for their values to be posted back into the controls correctly, you must move this creation into the page_init method and recreate them every time.
Then, in page_load, you can access the values in the controls correctly. If you give them IDs using a consistent naming convention, you will be able to find them using the FindControl method or, in page_init, you can store them in a collection at the page or user control level.

How to Dynamically get All controls (and it's IDs) in an aspx Page?

HEllo, I need to dynamically activate fields in a page according to the service that is going to be executed...
Let me explain:
There's a page with all the possible fields and a ListBox with all the selected services to be executed, then when the user selects which service to execute (change a car plate, for example), then I need to activate only the field(s) that the service require... (The realationship between Services and Fields are stored in a database).
public void CheckAll(int pService_Id, Control pPage)
{
foreach (Control control in pPage.Controls)
{
busExecutaServico vExecuta = new busExecutaServico();
if (vExecuta.EnableField(control.ID.ToString(), Convert.ToInt32(listBoxServices.SelectedValue)))
{
switch (control.GetType().ToString())
{
case "TextBox":
TextBox controleText = (TextBox)Page.FindControl(control.ID.ToString());
controleText.Enabled = true;
break;
Note that busExecutaServico is the class which contains the method (EnableField) for checking if the selected item matches any field on the database..
I can't seem to get the control.ID.ToString() to work properly (the ID always comes as NULL)
If anyone can help me solve this, or if there's another way (even if it's completely different from what i'm trying), it would be of great help. thanks
I like to use a recursive function for locating controls by either type or ID.
public Control FindControlRecursive(Control rootControl, string controlId)
{
if (rootControl.ID == controlId)
return rootControl;
foreach (Control control in rootControl.Controls)
{
Control foundControl = FindControlRecursive(control, controlId);
if (foundControl != null)
{
return foundControl;
}
}
return null;
}
public Control FindControlRecursive(Control rootControl, Type type)
{
if (rootControl.GetType().Equals(type))
return rootControl;
foreach (Control control in rootControl.Controls)
{
Control foundControl = FindControlRecursive(control, type);
if (foundControl != null)
{
return foundControl;
}
}
return null;
}
You can adapt these to first return a collection of controls, then process them later. Might be easier to keep track of what's happening.
I learned this technique here: http://www.west-wind.com/Weblog/posts/5127.aspx
Be aware that FindControl only searches the current naming container so Page.FindControl will only find controls that are added directly to Page. For example, if you had a repeater control that had the controls you were looking for and it was added to Page, you could find your repeater control via Page.FindControl but it wouldn't find child controls within your repeater, you'd have to recursively perform the FindControl on all container controls in the page.
This might seem a bit strange but it allows you to have controls with the same ID existing on the same page. For example, if you had 10 instances of a user control with textboxes within them called "MyName", you'd really want them to not being over-writing each other's 'MyName' fields!
Your code will come across a null for an ID unless every control has been given an ID.
Also why use:-
TextBox controleText = (TextBox)Page.FindControl(control.ID.ToString());
at all instead of:-
TextBox controleText = (TextBox)control;
and indeed since you only want to change the Enabled property consider:-
((WebControl)control).Enabled = False;
That I suspect will eliminate many case statements.
In your code you don't need to search any control - you already have it in 'control' variable. You even don't need to cast it to TextBox, just to a WebControl, just do this:
...
if (vExecuta.EnableField(control.ID.ToString(), Convert.ToInt32(listBoxServices.SelectedValue)))
((WebControl)control).Enabled = true;
P.S. control.ID is already string, so you should remove any ID.ToString() also.

Categories

Resources