c# wpf how to reference a dynamically added control - c#

Just starting out with C#, so this may be overly simple that I keep overlooking it...
In the main window I have a stackpanel, named targetDocArea, that will hold controls.
Based on user input, controls appear in the panel like so:
var htmlView = new System.Windows.Controls.WebBrowser();
htmlView.MinHeight = 200;
htmlView.Height = deskHeight - 225;
htmlView.Name = "targetDocControl";
htmlView.Navigate(dlg.FileName);
this.targetDocArea.Children.Add(htmlView);
Now I have another function that needs to interact with that control - and that's where I'm a bit lost. I would think there would be some index I could use to reference the children of the panel or directly using the name.
I've been reading about "this.registerName" but I'm not sure if that is correct way to approach this problem.
Any guidance would be greatly appreciated - and I wouldn't mind changing from a stackpanel to something more suited that would allow this interactivity.
Thanks.

You should store the control in a field in your class, like this:
private WebBrowser htmlView;
You can then use this field in any function.
If you need to store a number of copies of the control, you can use a List<WebBrowser> field, like this:
private List<WebBrowser> htmlViews = new List<WebBrowser>();
//Elsewhere:
htmlViews.Add(something);

Related

Recommended way to manipulate User Control panels into a Windows From

I just started working with Visual Studio C# and to be honest I didn't fully understand what happens when we chose to hide a form or a user control.
My intuition tells me this hide/show method is kind of "inefficient" way to get an user through all the functions of my app.
So I am asking you guys if there is another workaround to "load" user control parts in a form.
Right now my main_menu form has all the user control objects placed on the form, but hidden, and I am using buttons to show them.
Is there a better way to achieve the same result? (I was thinking of a workaround like having an empty panel where I can load the User Control - not sure if possible)
Thank you!
You can create the controls on the fly and add them to or remove them from the Controls collection. On the class level, define this field
private Control _currentPanel;
You can use a more specific type here, if you are deriving all your panels from a common base type.
Then change the panel with
// Remove previous one.
if (_currentPanel != null) {
Controls.Remove(_currentPanel);
}
// Add new one
_currentPanel = new MyNewPanel();
//TODO: possibly set the panels Docking property to Fill here.
Controls.Add(_currentPanel);
In the example I am working with the form's Controls collection; however, you might have to use the Controls collection of some container control holding the panel.

Recreate a group of Controls

Let's say that I have a panel with like... 3 controls in it. I may end up adding more controls to it or changing the positioning within that panel. When the program starts, I will programmatically HIDE the control. Eventually, the user can click a button that will create a duplicate of the original panel to populate an area on the form. The button should have the option for another click eventually, meaning that multiple instances of these can come about to populate this area. Remember that these controls may have text labels within them that can be individually set or altered later on, programmatically. I am assuming that to do this program, I need to make a List of controls, maybe a List of panels? I'm not exactly sure how to do this considering the fact that I need multiple controls duplicated multiple times.
Is there a nice, simple way to do this? I really don't want to do the duplication with any kind of 3rd-party package.
You will have to do it in code and therefore it'll be as nice as you can code ;-)
Seriously the course most often taken is to
create a UserControl which is a class related to a form, with the layout you want..
..and add more and more instances of it..
..often to a FlowLayoutPanel, often with AutoScroll
This is pretty nice and simple imo.
Here is a short walk-though..:
first we start, as usual, by picking a nice name for the UserObject class, maybe 'DataPanel' or 'BookItem'..
Next we create it: Go to the project explorer and right-click, choosing Add-New UserControl and give it the class name you chose. I'll use 'BookItem'.
Now you can see the Designer showing you a small empty control.
Look closer: You can also see that in the project explorer ther is now not only the new 'BookItem.cs' file but also the complementary 'BookItem.Designer.cs' and even a 'BookItem.resx' file; so this works very much like creating a new Form..
Let's add a few controls from the toolbox, I chose to add a PictureBox, four Labels and a NumericUpDown.
Have a look at the BookItem.Designer.cs file: Here you can see the very things you see in a Form.Desginer.cs file: All settings and all declarations for all controls you add to the layout. Note especially the declarations (at the bottom of the file): Just like for a Form, all controls by default are declared as private!
We can now work on the layout and script the controls. We also can add functions and properties to the UC, just like a Form.
Please note: Anything you need to access from outside, read from your form or its methods must be public! So if you want to access the NUpDown, let call it 'nud_quantity' you have a choice
You can change its declaration in the BookItem.Designer.cs from private to public or in the Designer by changing the Modifiers property
Or you can write a public function in the UC to get/set its value
Chosing between those two ways is a matter of taste; if other developers will work with the UC class, it will probably be better to put close control over what you expose by writing access methods.
After you have compiled the project you can see the new UC in the Toolbox.
You can now either add it from the Toolbox or
you can add it in code like any control you create dynamically.
Let's look at an example:
Imagine a simple order system in a bookstore: The customer has done a search on the books in our store and is presented with a list of books in a DataGridView 'dgv_bookList', readonly, multiselect. To the right there is a FlowLayoutPanel 'flp_cart' represeting a shopping cart. And we have a command button 'cb_addItems' to add selected books to the cart.
The Button might be scripted like this:
private void cb_addItems_Click(object sender, EventArgs e)
{
if (dgv_bookList.SelectedRows.Count <= 0) return;
foreach (DataGridViewRow row in dgv_bookList.SelectedRows)
{
BookItem book = new BookItem (row);
book.label1.Text = "#00" + book.label1.Text;
book.Name = book.label1.Text;
flp_cart.Controls.Add(book);
}
}
This will add one BookItem for each selected row in the DGV.
A few things to note on the above code:
I pass a DataGridViewRow into the constructor of the UC so it can directly set its labels! This means that, in addition to the parameterless contructor the desginer has built for us, we need to write a second contructor, maybe like this:
public bookItem()
{
InitializeComponent();
}
public bookItem(DataGridViewRow bookData)
{
InitializeComponent();
label1.Text = bookData.Cells[0].FormattedValue.ToString();
label2.Text = bookData.Cells[1].FormattedValue.ToString();
label3.Text = bookData.Cells[2].FormattedValue.ToString();
label4.Text = bookData.Cells[3].FormattedValue.ToString();
}
Instead you could write a public setData(DataGridViewRow bookData) function.
Also note how stupid my labels are named! You can do better than that, I hope!
Also note how I access 'label1' and modify its Text from a Button in the Form; to do that I had to change its declaration in the Desginer.cs file:
private System.Windows.Forms.PictureBox pb_cover;
public System.Windows.Forms.Label label1; // <<----expose this label !
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.NumericUpDown numericUpDown1;
Often preferrable: An access function, maybe like this:
public int quantity() { return (int) numericUpDown1.Value; }
Or, of course a Property:
public int quantity { get { return (int)numericUpDown1.Value; } }
Also note, that I set the Name of the BookData item to some variant of the 1st data item, my book id. This might as well, or better, happen in the constructor; and there should be a check to prevent adding the same item twice..
All in all one can say, that using UserControls is very much like working with Forms, including all the usual ways or tricks for inter-form communication: keep references, expose members, create properties and functions..
One final Note: Like with forms or subclassed controls there is one catch: By placing them in the designer, you assign the designer the responsiblity to display your UC during design time.
This is normally just fine; however it is also possible to introduce subtle mistakes which make it impossible for the designer to display the control. You need to correct these problems before the designer will be able to show a control or any form that contains it. Let have a look at a simple example of such a problem:
Let's script the Paint event of the PictureBox 'pb_cover' in the UC:
public Brush myBrush = null;
private void pb_cover_Paint(object sender, PaintEventArgs e)
{
if (pb_cover.Image == null)
{
Size s = pb_cover.ClientSize;
e.Graphics.FillRectangle(myBrush, 0, 0, s.Width, s.Height);
e.Graphics.DrawLine(Pens.Red, 0, 0, s.Width, s.Height);
e.Graphics.DrawLine(Pens.Red, s.Height, 0, 0, s.Width);
}
}
And let's modify the code in the Add button:
BookItem book = new BookItem (row);
book.label1.Text = "#00" + book.label1.Text;
book.myBrush = Brushes.OliveDrab;
flp_cart.Controls.Add(book);
Now, if you run the program all will be fine. Even if you try to look at the UC in the designer there may or may not be problems. But once you try to open a Form on which the UC was placed, the Desginer will crash and tell you that it can't work, since the Brush is null. Here the remedy is simple: add a default value to the Brush declaration and all is well. Other situations may need a little more thinking..
I don't even run into the problem btw, since I have not placed an instance of BookItem on the Form; they are only created in the Add Button..
I hope that gets you started!

Problem with layout of control when dynamically creating winform and controls for the Form

I get this response from a web service call. Something like this
<Response>
<Control1 type = "DropdownList" value= "USA,UK,Sweden,UAE"/>
<Control2 type = "Textbox" value= "Contries"/>
<Control3 type = "Button" value= "None">
</Response>
Based on this I de-serialize it into List<Controls>.
Now I need to be able to dynamically create a winform based on these controls. My problem is the layout. I want to be able to create them nicely separated (If possible vertically aligned) in batches of lets say 5.So If I need 15 controls I have 3 columns and 5 rows.
What would be best way to achieve this? I know that I can use the inbuilt positioning properties like top, width etc., but maybe someone out there has done something similar in a better way.
I think you should use TableLayoutPanel. Also you can read best practices to use this control.
One benefit of using TableLayoutPanel from above article:
Layouts that will be modified or generated dynamically at run time, such as data entry forms that have user-customizable fields added or subtracted based on preferences.
So basically I am gonna do something like this(might be useful for someone else)
Form op = new Form();
FlowLayoutPanel panel = new FlowLayoutPanel();
op.Controls.Add(panel);
for (int i = 0; i < 10; i++)
{
Button b = new Button();
b.Text = "Button" + i.ToString();
panel.Controls.Add(b);
}

Can we create a 1-D array of UserControl. ..?

I just created a "Usercontrol" in WINFORMS- it just contains 1-Button with some style.
And i need to use the same as array(10) and load it to a form.
Ex:
Dim myButton() As Button = New ucSpecialButton(dataset4Category(i).Tables(0).Rows.Count - 1) {}
Here my usercontrol name is ucSpecialButton
can we create a ONE-Dimensional Array of a WINFORM usercontrol.?
With MAKKAM's words: Yes, you can. I guess you're actually uncertain about whether you can add a dynamic number of controls to a form, because in the designer you cannot define any arrays, you just drag and drop a certain number of controls on the form.
However, in fact Visual Studio simply generates some code in background that adds these controls to a collection. You can just as well write your own code to add an arbitrary number of UserControls to the collection dynamically. Just look at the forms' .designer.cs file to see how it works.
Taking MAKKAM's array controls it could look like this, e.g.:
public MyForm()
{
InitializeComponent(); // this is the call to the auto-generated code
// Here you could add you own code:
foreach (Control control in controls)
{
this.Controls.Add(control); // this is how to add a control to the form.
}
}
Yes, you can.
Control[] controls = new Control[10];
So, what's the problem?
I've just noticed that you edited your question. If I got it right, the only thing you're missing now is (I'm a C# guy, might be that there are some flaws in the following VB.NET code):
for i = 0 to dataset4Category(i).Tables(0).Rows.Count - 1
myButton(i) = New ucSpecialButton();
// ... specific button properties ...
next
For, the code you pasted in your question doesn't create the buttons yet, it only allocates memory for the array:
Dim myButton() As Button = New ucSpecialButton(
dataset4Category(i).Tables(0).Rows.Count - 1) {}
New in this place means to create a new array for the references, not to create new objects. ucSpecialButton(...) in this place is not the constructor for an object. Instead it only denotes the type of object you want to prepare the array for. You can IMHO just as well write New Button(...).
By the way: IMHO it should be
`New ucSpecialButton(dataset4Category(i).Tables(0).Rows.Count)`
Without the - 1. In the for loop however, the - 1 is correct (an array of size 10 goes from 0..9).

WPF - FindName Returns null when it should not

FindName is broken for me :(
The object I am looking for is there. I have proof.
Here is the scenario:
ToggleButton button = (ToggleButton)sender;
Popup popup = (Popup)button.FindName("popSelectIteration");
popup is null but not always. Just sometimes. But even when it is set to null the child I am looking for is there.
I put a break point in when it was null and grabbed these two screenshots.
The is where FindName is returning null for "popSelectIteration".
But if you dig into the watch, you see that the child is there.
So what am I missing? Why does FindName not find it? As you can see from the screen shot this is not a timing issue (the FindName watch is null but the direct path is fine).
Is there a better way to find a control?
Side Note: If you are intersted in the XAML for the toggle button in question it can be found in this question: WPF - FrameworkElement - Enumerate all decendents?.
Update: I did some digging to see why this fails some times and other times it works. I have an animation that calls NameScope.SetNameScope((DependencyObject)form, new NameScope()); (Full method code here). Right after that call the FindName starts to fail.
I don't really understand that call. I think I copied and pasted the code. Anyway, I commented it out. But I would love know why this is failing.
I would guess it has to do with the difference between the visual and logical tree. The control is in the logical tree but maybe the template for this control has not been applied yet and therefore FindName won't return anything useful.
You could try to call ApplyTemplate(); on the container first.
This would also explain why it returns something sometimes.
Try
LogicalTreeHelper.FindLogicalNode(button, "popSelectIteration");
Little late to the party (and not actually answer to OP question), but
when you add elements dynamically, they are not findable by FindName.
You need to register them by calling RegisterName.
Example:
string number = GenerateNumber();
Button myButton = new Button();
myButton.Content = number;
myButton.Name = "button_" + number;
RegisterName(myButton.Name, myButton);
Panel.Children.Add(myButton);
object o = Panel.FindName(myButton.Name);
Maybe someone might find this useful.
In my experience, this happens when you add items via code-behind. I've found that you can fool FindName() (or the animation framework) via name scopes. That is, when you create your control, you do
NameScope.GetNameScope(yourContainer).RegisterName("name of your control", yourControlInstance);
For this to work reliably, though, you must make sure that you unregister the name:
NameScope.GetNameScope(yourContainer).UnregisterName("name of your control");
Posting this for future reference.
I have meet the same question now, but I use the method like so:
#region Override - OnApplyTemplate
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.PART_ListViewLeft = GetTemplateChild(cPART_ListViewLeft) as ListView;
this.PART_ListViewCenter = GetTemplateChild(cPART_ListViewCenter) as ListView;
this.PART_ListViewRight = GetTemplateChild(cPART_ListViewRight) as ListView;
this.PART_GridViewLeft = GetTemplateChild(cPART_GridViewLeft) as DsxGridView;
this.PART_GridViewCenter = GetTemplateChild(cPART_GridViewCenter) as DsxGridView;
this.PART_GridViewRight = GetTemplateChild(cPART_GridViewRight) as DsxGridView;
if(this.PART_ListViewLeft!=null)
this.PART_ListViewLeft .AlternationCount = this.AlternatingRowBrushes.Count;
if(this.PART_ListViewCenter!=null)
this.PART_ListViewCenter .AlternationCount = this.AlternatingRowBrushes.Count;
if(this.PART_ListViewRight!=null)
this.PART_ListViewRight .AlternationCount = this.AlternatingRowBrushes.Count;
// ApplyTempleted = true;
CreateColumnLayout();
}
#endregion
If the Control is dynamic create and of which or whose container the 'Visibility' is set to hide or Collapsed, then the code this.PART_ListViewLeft = GetTemplateChild(cPART_ListViewLeft) as ListView; will always return null, the reason is that the datatemplete has not yet been applied before OnApplyTemplate being called.
I would suggest to avoid using FindName function, based on my experience, expecially problematic when you try to find something in the DataTemplate applied to some control.
Instead , if it possible (based on your software architecture) declare Popup in XAML and
refer to it like resource or use Binding to set some Model property to it's reference.
Good luck.
Try to use button.FindResource("popSelectIteration")
ellipseStoryboard.Children.Add(myRectAnimation);
containerCanvas.Children.Add(myPath);
After you add register the controls like
RegisterName("TextBlock1", Var_TextBox);
or
RegisterName(myRectAnimation.Name,myRectAnimation);
RegisterName(myPath.Name,myPath);

Categories

Resources