C# create multiple instances of nested controls - c#

I have a tabbed interface - where the tabs will contain multiple nested controls. In particular, Panels containing panels, containing Grids, etc. Even in some cases additional Tab controls. (Yes it can be a little messy)
However, now I need to create some of these tabs dynamically at run time - and still reference the controls on them. So - for example lets say I have something like
Tab
Panel
TabControl
Tab
Grid
Graph/Chart
Panel
Buttons
I need to create multiple new tabs for each result DataTable I need to display(I do not know how many I have to create until Run Time - or I would just do this all at Design time).
I understand I will create these controls at RunTime - and I can do it easily for one additional instance - but then I start running into naming issues. I cannot add three GridControls (all called DataGrid) to the same form - and because C# is strongly typed I cannot create the variable names at Runtime. AND I also need to be able to reference these controls at some times.
So - I looking for possible solutions. I could create arrays for each type of control I am hoping to create. There are over 15 controls (some nested) for each tab - so it could get a little messy. But it would allow me to access each control - as I could use the Tag Property of the Tab page to allow me to access the various arrays. If I created the arrays to hold a max of 50 elements - it should be more than enough (I do not see users creating more then 5 or 10 of these extra tabs).
As I am somewhat new to C# and Visual Studio - I am wondering how others would handle this sort of task? How would you go about creating multiple instances of embedded (nested) controls on your form - knowing that you will have to reference them elsewhere in the code??
Any thoughts and pointers would be much appreciated.
Thank you
Bradley

Since there's an arbitrary amount of tabs, you'll need to manage them programmatically.
So - I looking for possible solutions. I could create arrays for each type of control I am hoping to create. There are over 15 controls (some nested) for each tab - so it could get a little messy. But it would allow me to access each control - as I could use the Tag Property of the Tab page to allow me to access the various arrays. If I created the arrays to hold a max of 50 elements - it should be more than enough (I do not see users creating more then 5 or 10 of these extra tabs).
That would indeed become messy, and is a recipe for bugs. Tag is a very useful property in the right circumstances, but generally you should avoid using it unless you have a good reason. You might inadvertently introduce cyclic references that leak memory. I find it's most suited for storing related metadata such as an ID(however, avoid storing value types in it, as they become boxed).
If you haven't already, I suggest adopting the same naming convention you use with variables for the names you set in the designer. As backing variables reflect those names, it should help you avoid name conflicts with properties, and keep your code more consistent. My team and I use the convention mSomeField for private fields and designer set names and it has served us well in avoiding most name conflicts over the years.
Start by designing a new UserControl that will serve as the content of the most common tab category, adding as much common layout as possible. Design additional UserControls for each additional tab category. Add accessors for each nested control you need to expose.
Note that the following code uses some C#6/7 features.
In practice, here's what that could look like:
// Code-behind for the UserControl representing the content of the 1st tab category.
class Cat1TabContent : UserControl
{
//...
// These all refer to controls created and named from the designer.
public Panel PrimaryPanel => primaryPanel;
public TabControl TabManager => tabManager;
public Panel SecondaryPanel => secondaryPanel;
//...
}
Managing tabs(in the main form and all other controls that have a TabControl):
//...
private List<UserControl> tabContents = new List<UserControl>();
void AddTab<T>() where T : UserControl, new()
{
var tab = new TabPage();
var tabContent = new T();
tabContents.Add(tabContent);
tab.Controls.Add(tabContent);
tabControl.Controls.Add(tab);
}
void RemoveTab(TabPage tab)
{
if (tab == null) throw new ArgumentNullException(nameof(tab));
if (tab.Controls.Count != 0 && tab.Controls[0] is UserControl tabContent) {
tabContents.Remove(tabContent);
}
tabControl.Controls.Remove(tab);
}
//...
Accessing nested controls:
// Form >> Tab Control >> 1st Tab >> 1st Child.
// This assumes AddTab<Cat1Content>() was called.
var firstContent = (Cat1Content)tabContents[0];
// Content >> PrimaryPanel; Enumerates over nested controls.
foreach (var child in firstContent.PrimaryPanel.Controls) {
// ...
}
// Content >> Tab Control; Enumerates over nested tabs.
foreach (var child in firstContent.TabManager.Controls) {
// This is safe, TabControl throws an exception if you try to add anything that isn't a TabPage.
var tab = (TabPage)child;
// ...
}

Related

Designing WPF GUIs with minor differences

Three GUIs with almost the same design are started from different application menus and all of them will have a:
Search TextField
ComboBox for category selection
ListBox showing the result based on category
and of course a couple of buttons for searching etc.
Suppose the menus are named Add new A, Add new B, Add new C.
When Add new A is clicked, it should start the "base/parent" GUI with the above components.
When Add new B is clicked, it should start a GUI the same as base except it should also have a couple of radiobuttons.
The same goes for Add new C, only differences are a couple of simple controls.
How is this effectively done using WPF? Should I design one GUI with all elements and hide e.g. the radiobuttons if started from "Add new A"?
Or should I use three different XAMLs and end up with most of the code and design elements being the same.
Or should I use three different XAMLs and end up with most of the code and design elements being the same.
I think this depends on what it is you're trying to represent graphically. When I worked on a color picker, I created multiple controls that all contained the same controls, but different properties and conversion algorithms. In most MVVM implementations I've seen, it is indeed done this way.
Another thing you can consider is making one base UserControl, such as Panel that contains all the controls and properties that all the UserControls require, and then creating subsequent UserControls like CalendarPanel or NotebookPanel, which are based of Panel, but specify new controls and properties.
An example of this can be found here:
How can a WPF UserControl inherit a WPF UserControl?
It is complicated to learn at first, but super fun once you get the hang of it :)
If all three views can have single view model, then add a Mode enum to your view model, and basing on this enum show/hide some parts in your view. This approach will be easiest to test and maintain.
Sometimes though, there are places in the application that should look similar, but have different view models. Then I suggest splitting these view models to smaller ones, that can be shared. Then you'll be able to compose your views from smaller, shared components.

Windows Store App: prevent duplicate code in two almost identical User Controls

So I've got two User Controls in my Windws Store App, that look and work almost the same:
Control 1 http://image-upload.de/image/FbKYsa/319b71644f.png
Control 2 http://image-upload.de/image/Hd9eMW/b78fca7c85.png
The events and event handling on the controls is identical, the only difference is that the second control has an additional ComboBox as you can see.
How can I prevent code duplication and doubled work whenever I change some functionality?
I tried to pack the identical controls into a resource dictionary but could not figure out how to access the control elements in the dictionary's code-behind.
As far as I know I also can't derive the second control, since the first uses XAML.
My last idea would be to use only one class and give a flag to the constructor to add a ComboBox if needed. But it just doesn't appear to be the right way. Any suggestions?
I have just done something similar on a app. My functionality required two text boxes for password entry and the OK, Cancel buttons to be available in both controls, however one of the controls also needed to display Radio Buttons for encoding types (these buttons were in a StackPanel). I added a parameter to the constructor as shown below:
public PasswordInputBox(bool isForImage)
{
this.InitializeComponent();
if (isForImage)
//initialize actions for that part of the EmbedTypePanel;
else
EmbedTypePanel.Visibility = Visibility.Collapsed;
}
Then if you need the control with the extra combo box you can initialize it, else you just collapse it so the user is none the wiser.

Controlling multiple custom tools from an array

I'm building a simple WinForms GUI that utilizes multiple (~50) of the same custom tool. I have the custom tools imported just fine and is part of my main form. My problem is I'd like a elegant way of handling each custom tool property (ie BackColor, Text, Visible, Enable, etc).
The tool is simple; it has a couple labels on it and each has some strings I use for storing values. I want to be able to control these labels on the custom tool at will without typing up huge amounts of code.
I have a textbox on my Form1 Design that controls the number of custom tools I want enabled and visible, but I cannot figure out a good method to control it. Do I explicitly put 50 custom tools into an array of some sort? If so, how? Since it's a custom tool, I cannot make CustomToolName[] customTools = new CustomToolName[50] for example. I've racked my head around this and can't figure out a way without making a really ugly and long solution.
Any suggestions? How would you do this?
EDIT: The List suggestion works. I'm able to control them by using a List<UserControl> CustomTool = new List<UserControl>(); and then adding each custom tool to the list. A small test of CustomTool[0].Visible = false; worked. I can control them with for loops as well. Thanks a lot.
This a classic usage of Polymorphism!!! Create an array or list of Base class and put all your controls inside. Run throuhg the list with loop and use properties. If you need to get sepecial properties that only specific classes have, verify type of current object. Something like that:
List<Control> controls = new List<Control>();
//Add controls
foreach (Control item in controls)
{
item.Visible = true;
if (item is MyCustomButton)
{
((MyCustomButton)item).CustomProperty = "123";
}
}

Changing and passing content inside tabControls tabPage

I am working in windows forms application and have a following issue. I use tabControl in my application and there is a need to change a content inside certain tabPages when users perform specific actions.
for example tabPage one contains a text area and a button, when user clicks button information inside a text area should be stored somehow, and that same tabPage should display new content e.g. more text areas, buttons etc, I assume it is easier to do by using views inside it, so one view can be hidden and another can be shown.
This a to a degree a matter of taste. You can chose to show and hide controls one by one in a method or you can group them in a UserControl which you then show or hide in one command.
I would base my decision one way or the other by these questions:
Are there controls, that will always be visible and how is the layout for these?
How many controls are there to show/hide?
Is there a need to reuse one or more of your views?
The last question may make the big difference: If you want re-use, do go for the UserControl. It is basically meant to do just that: Group controls, like a form does.
For just a few controls doing it in a one by one (in a switchViewMode-method) would suffice, imo.
To add UCs you right-click your project in the project-explorer and chose add - usercontrol. Then chose a nice name, like UC_Goods or UC_Services or whatever your shop policy suggests.
You are then presented with the empty GUI. Now add the controls you need.
Here a decision is to be made: If you will reuse it make sure the controls get generic names! If not it doesn't matter. The reason is, that when you add two instances of the same UC, their controls will have the same names and you will have to qualify them by the parent (the UC)
Here you also script events etc..
Finally add instances to the TabPage as need like this:
public UC_Goods uc_goodsDelivered = new UC_Goods();
public UC_Goods uc_goodsOnHold = new UC_Goods();
public UC_Services uc_ItServices = new UC_Services ();
public Form1()
{
InitializeComponent();
tab.tp_goodsPage.Controls.Add(uc_goodsDelivered);
tab.tp_goodsPage.Controls.Add(uc_goodsOnHold);
goodsOnHold.Hide();
tab.tp_goodsPage.Controls.Add(uc_ItServices);
uc_ItServices .Hide();
// take care of dock style or anchors..
// ..and initialzing fields..
}
This delclares two UC classes and two and one instance of each respectively. Only one is visible. Since one class is used twice its controls have ambigous names until you qualify them e.g. like this: uc_goodsDelivered.Status...
hth

Passing user controls as variables

I am writing what i will call a survey system. The surveys will consist of a series of questions, split into sections. The survey definitions will be stored in a database. The questions can be of various types, that have different input methods and validation (e.g.textbox, numeric textbox, selectlist etc.) I am creating a set of user controls that map to different question types. I would like to completely abstract the survey rendering logic from the, survey definition logic. The way i thought i would do this is to use a dumb rendering control that simply takes a collection of controls and renders them out in a for each loop a super simplified version of what I am doing.
private void RenderControls()
{
foreach (UserControl CurrentControl in this.ControlsToRender)
{
MyPlaceholder.Controls.Add(CurrentControl)
}
}
This works in the sense that the correct controls get added to the place holder,however nothing renders. Presumably this is because it does not have the markup in the asxc file.
If i do something like this
private void RenderControls()
{
foreach (UserControl CurrentControl in this.ControlsToRender)
{
MyPlaceholder.Controls.Add(this.LoadControl("path to ascx file")
}
}
It will create the correct number of controls, but i don't want to instantiate the controls here because i would need to to set various properties based on cconditional logic i want to abstract away from this pure rendering layer.
what i want to do is instantiate the controls else where, set the relevant properties and then simply pass a collection of these into the rendering control to render out.
Is there a way to do this with user controls? Would i have to create custom controls instead?
.ascx usercontrols are difficult to make into distributable, portable components. There are ways of doing it, but they have a lot of restrictions and requires several steps. In the end it's not worth it.
Your best bet would be to make custom controls that encapsulate their own rendering.
You could do either, but it sounds to me like you might want to use custom controls.
You could use user controls by exposing properties on the user control.
MyPlaceholder.Controls.Add(CurrentControl)
by the time you get to the above line, the control has already been instantiated. just set those properties on instantiation and you should be set.
alternatively you could set the properties anytime before render

Categories