Accessing controls from different forms - c#

I have a main form with some buttons, textboxes, labels, etc.
On a second form I would like to copy the text from the main forms textbox onto the second form.
Have tried:
var form = new MainScreen();
TextBox tb= form.Controls["textboxMain"] as TextBox;
textboxSecond.Text = tb.Text;
But it just causes an exception. The main screen textbox is initialised and contains text.
When I hover over form I can see all the controls are there.
What am I doing wrong?

Looking at the original code, there are two potential reasons for the NullReferenceException you are getting. First, tb is not defined in the code you provide so I am not sure what that is.
Secondly, TextBox textbox = form.Controls["textboxMain"] as TextBox can return null if the control is not found or is not a TextBox. Controls, by default, are marked with the private accessor, which leads me to suspect that form.Controls[...] will return null for private members.
While marking the controls as internal will potentially fix this issue, it's really not the best way to tackle this situation and will only lead to poor coding habits in the future. private accessors on controls are perfectly fine.
A better way to share the data between the forms would be with public properties. For example, let's say you have a TextBox on your main screen called usernameTextBox and want to expose it publicly to other forms:
public string Username
{
get { return usernameTextBox.Text; }
set { usernameTextBox.Text = value; }
}
Then all you would have to do in your code is:
var form = new MainForm();
myTextBox.Text = form.Username; // Get the username TextBox value
form.Username = myTextBox.Text; // Set the username TextBox value
The great part about this solution is that you have better control of how data is stored via properties. Your get and set actions can contain logic, set multiple values, perform validation, and various other functionality.
If you are using WPF I would recommend looking up the MVVM pattern as it allows you to do similar with object states.

PhoenixReborn is correct. The problem is that you are creating a new MainScreen, which means that new controls are created, so unless the text in your controls are initialized in the form constructor, they are going to be empty. Usually, the way to handle this is to pass the first form instance to the second form, like this:
SecondForm second = new SecondForm(this);
and in the second form:
public SecondForm (MainForm form)
{
// do something with form, like save it to a property or access it's controls
}
That way, the second form will have access to the first form's controls. You might consider making the properties you need to use public (in the designer properties pane). That way you can just do form.textboxMain.Text.

Related

How to refactor duplicated code in Windows Forms?

I'm currently working on refactoring a lot of duplicated code in a couple of UserControls in Windows Forms project.
The architecture leaves much to be desired with a lot of logic implemented inside the UI layer. This will be fixed eventually, I'm now working on improving what I have on my hands now.
The problem is, that a lot of duplicated code relates directly to controls, for instance:
private void InitDestinationPathControls(string path)
{
if (someField)
{
tbOne.Enabled = false;
tbOne.Visible = false;
btnTwo.Enabled = false;
btnTwo.Visible = false;
tbOne.Text = string.Empty;
return;
}
// (...)
}
Don't get too attached to the cited code itself, it is just an example.
I'd like to move this code to a common base class, but it relies directly on specific fields (even though they are exactly the same in all controls too). Those fields, on the other hand, are generated by the designer, so I cannot extract them to the base class.
The only thing that comes to my mind is to pass those fields as parameters to a method in base class, but then if some method uses a lot of them, I'll end up with a monstrous interface part and that won't actually improve the readability too much.
How can I deal with such common parts of user controls in Windows Forms?
Apparently you have a combination of several controls that appears in several forms. In other words: you have for instance some buttons, comboboxes, etc, that you want to show on different forms, and you want them to have the same behaviour.
If not only the behaviour of these controls is the same on all forms, but also the layout, then consider to create a class derived from UserControl.
The UserControl hides from the outside world which controls are shown, how they are visualized and how they behave.
I assume that you already pulled the model out of the visualisation of the data.
If all instances of this user control should all call the same functions of possibly a different object of the same class, then give your special user control a property that represents this class, or at least an interface. During construction you can plug in the actual class that should handle the actions after operator input.
If, on the other hand, the layout differs on each form, but the collection of buttons, comboboxes, etc and their behaviour is similar on all forms that show this collection of controls and they have a lot of common behaviour, consider to create your own ControlCollection.
For instance, if on several forms you have a button to select a (text) file, labels with the name, size and creation date of the selected file, and an edit box that shows the content of the text file, but you want to layout them differently, consider something like this:
class FileDisplayControls : IDisposable
{
public Button ButtonSelectFile {get;} = new Button();
public Label labelFileName {get; } = new Label();
public Label labelFileSize {get; } = new Label();
public TextBox textFileContents {get; } = new FileContents();
private void ButtonSelectFile_Clicked(object sender, ...)
{
// TODO: open file dialog, display result in labels and text box
}
}
Constructor can set initial layout properties of the controls, and subscribe to events, such that the controls will react on user input.
The user of the class (= code, not operator) immediately has a collection of controls that have some standard behaviour, like react on button click. All he has to do is set the location of the items in his own form. If desired change other layout properties (colour, background) and put them on his own form.
If you want to prevent that others change other visual aspects of the controls than the position, don't publish the control themselves, only the position of the control:
public System.Drawing.Point LocationSelectFileButton
{
get => this.buttonSelectFile.Location;
set => this.buttonSelectFile.Location = value;
}
public System.Drawing.Point LocationFileContentTextBox
{
get => this.textBoxFileContent.Location;
set => this.textBoxFileContent.Location = value;
}
etc.
If needed, you can add events for users:
public event EventHandler SelectedFileChanged;
public string FileName => this.labelFileName.Text;
public string FileContents => this.textBoxFileContent.Text;
etc.
Conclusion
The solution that you choose depends on the similarity between the controls on the various forms:
if Behaviour and Layout are all the same: UserControl
If only position and a few properties different: special class with the properties that are different. This way you can force a more similar style: all "Select File" buttons look similar.
If only one or two behaviours are different: add Action<...> properties or events
If you want full control of the layout: expose the Controls.
The behaviour that is common for all you forms that show these controls (in my example: how to select a file and what to do when a file is selected) is inside the class.
repeated code can be extracted to method (possibly in base class, or as static method in helper class)
public void DisableControls(params Control[] controls)
{
foreach(var c in Controls)
{
c.Enabled = false;
c.Visible = false;
if (c is TextBox t)
{
t.Text = string.Empty;
}
}
}
private void InitDestinationPathControls(string path)
{
if (someField)
{
DisableControls(tbOne, btnTwo);
return;
}
// (...)
}

Pass form to another class and extract controls and properties

This is more so a general question than an issue I have. I have a form with 30+ controls that I will use to populate a model, which eventually ends up in a database.
I was wondering, however, if I could just pass the whole form object to another class, and pull the contents out in the other class without setting up a whole bunch of getters and setters.
Let's say I have a form Form1, and I make this call:
OtherClass.Validate(this)
Then, in the OtherClass (which is in a different project in the same solution) I have:
public static void Validate(Form1 myForm)
I have played around with this a little. In the Validate() method, if I put a watch on myForm, I can see all the form controls and properties, but I don't know if there is a way to just pull them out. If I type myForm., intellisense shows me all the standard form methods and properties, but not the controls and properties specific to the form. Has anybody tried this successfully?
You may grab controls from a form object using
myForm.Controls
This gives you a collection of controls within the form. You may iterate through them with a foreach loop.
Example with this form containing two buttons. You may use the following code to get the controls text.
public static void Validate(Form1 myForm)
{
foreach (Control control in myForm.Controls)
{
string text = control.Text;
Console.WriteLine(text);
}
}
Triggering the above function prints the following to the console. (Using this form)
button2
button1
This method works for TextBox and other controls too. However, it may be trickier if you have controls within controls. You may solve that by making a recursive function.
Each control on a form has a property called "Modifiers":
It is "Private" by default (for a reason: UI is the most likely subject for changes, any logic outside the form should not depend on controls. One control can be replaced with another, a group of controls can be replaced with a custom control. When controls are public, such change is not incapsulated and triggers changes in many parts of the system)
Controls which are added on a form, are serialized into C# code in form.designer.cs file. Code for textBox1, when it has modifier Private:
private System.Windows.Forms.TextBox textBox1;
Change it to Public like shown on a screenshot, and it will become
public System.Windows.Forms.TextBox textBox1;
Public controls will be accessible like any other public fields:
public static void Validate(Form1 myForm)
{
if (String.IsNullOrEmpty(myForm.textBox1.text))
{
// do smth about empty field
}
}

C# Using a form to load other user controls and have access to a base control or property

Currently I have a C# program with a windows form and then a user control template put onto the form. The user control template is really just used as a placeholder. I have a series of other controls which inherit from this user control template.
Each of those controls have navigation buttons like 'Continue' and 'Back' on them and each control knows which control needs to be loaded next. However what I need to figure out is an easier way to have variables that are global to these controls.
The only workaround I have is that I pass the form to each control when they are loaded and use variables inside of the form to read and write to. What would be the proper way to have each of these user control screens be built off of a base control which contained objects all of the controls could get to?
Sorry for the rambling nature of the post but I've been thinking about this problem all morning.
Here is some of the code:
Most of what I have written was based on hiding and showing the user controls so that content in the controls wouldn't be lost during navigation. I won't be needing to do that as eventually it will be loading the fields of data from a database.
Code for initially loading control from form click:
conTemplate1.Controls.Clear();
conInbound Inbound = new conInbound(this);
Inbound.Dock = DockStyle.Fill;
Inbound.Anchor = (AnchorStyles.Left | AnchorStyles.Top);
conTemplate1.Controls.Add(Inbound);
Code for Continue button inside of one of the controls:
if ((Parent.Controls.Count - 1) <= Parent.Controls.IndexOf(this))
{
UserControl nextControl = new conPartialClear();
nextControl.Dock = DockStyle.Fill;
Parent.Controls.Add(nextControl);
this.Hide();
Parent.Controls[Parent.Controls.IndexOf(this) + 1].Show();
}
else
{
this.Hide();
Parent.Controls[Parent.Controls.IndexOf(this) + 1].Show();
}
The best-practice for communicating from a control to a parent is to use events, and for communicating from a parent to a control is to call methods.
However, if you don't want to or can't follow this practice, here's what I would recommend.
Each UserControl has a ParentForm property that returns the Form that contains the control. If you know that the UserControl will always be attached to MyParentForm, you just cast the ParentForm and then you can access all public controls, methods, etc.
Here's what I mean:
public class conTemplate
{
public MyParentForm MyParentForm
{
get
{
return (MyParentForm)this.ParentForm;
}
}
}
This way, you can easily access any public members of MyParentForm. Your conInbound class could have code such as this.MyParentForm.GlobalSettings.etc..., and could even have access to any public controls.
I'm not totally sure I understand your problem. It sounds like you want the user control to "do something" with it's parent form. If that's the case, you may want to consider adding events to the UC and then handle them on the form itself.
Basically, for your UC's "continue", you'll have an event that's fired when it's pressed. You'll want to handle that in your form. I'm not real sure about the syntax from memory, or I'd work something out for you code-wise. But I think that's the route you'll want to take. Think of your UC like any other windows form control. If you add a button to your form, you assign it it's event method. Do the same with the UC.
I found this and thought it may be helpful. Scroll down to where it talks about UC's and events.
http://www.akadia.com/services/dotnet_user_controls.html
Hope this helps.
EDIT after new info from OP.
You could declare a global variable inside the UC of type yourForm and then set that variable to the ParentForm at run-time, if I'm understanding you correctly.
So, inside your UC Class, you could do:
private parentFormInstance;
then inside the constructor of the UC, you could set it as such:
parentFormInstance = this.ParentForm; (or whatever the property name is).
This allows you at design-time to use:
parentFormInstance.DoSomething();
without the compiler yelling at you.
Just basic advice, but if you can go back and make it easier on yourself, even if it takes some additional time re-working things, it'd be worth it. It may save you time in the long run.

How to reset a form to its default appearance, after interaction with user

I wanted to get default view of my form after some interaction with user.
In other words after some changes that has been implied by user, what command will return the form to the initial pop up appearance?
I have many controls, and calculations, so I dont want to go over the control one by one and set them null or their default value.
It would be great, as if I initate the form once more, some how.
You could try clearing the controls on the form, then calling the InitializeComponent() method.
while (Controls.Count > 0)
{
Controls[0].Dispose();
}
InitializeComponent();
EDIT:
Another Option that wouldn't cause any performance issues would be to utilize data binding. Create a data object that maps one to one with all of the fields you'd like to reset, then once it is time to reset the form simply set the data source of your form to a new instance of the data object.
You could do something like this too, assuming you just wanted to reset text on the controls. Call the following function with ResetControl(this) where "this" is your form. You'd want to check the control type so you don't have unintended consequences like wiping out label text.
private void ResetControl(Control control)
{
if (control.HasChildren)
foreach (var ctl in control.Controls)
ResetControl((Control)ctl);
if (control is TextBox)
control.ResetText();
}
Although, I still think you'll have far less bugs if you just dispose the form and create a new one (you could load an initial form that loads a user control, and dispose the user control, or use a mdi container form to load another form or something).
The best way is to write a personalized function ResetForm() and reset each component alone :
for TextBox ==> TextBox1.Text = "default text";
for ComboBox ==> ComboBox1.ResetText();
for DataGridView ==> DataGridView1.RowCount=1;
etc...

Building C# .NET windows application with multiple views

I'm rewriting an old application and use this as a good opportunity to try out C# and .NET development (I usually do a lot of plug-in stuff in C).
The application is basically a timer collecting data. It has a start view with a button to start the measurement. During the measurement the app has five different views depending on what information the user wants to see.
What is the best practice to switch between the views?
From start to running?
Between the running views?
Ideas:
Use one form and hide and show controls
Use one start form and then a form with a TabControl
Use six separate forms
Creating a bunch of overlaid panels is a design-time nightmare.
I would suggest using a tab control with each "view" on a separate tab, and then picking the correct tab at runtime. You can avoid showing the tab headers by putting something like this in your form's Load event:
tabControl1.Top = tabControl1.Top - tabControl1.ItemSize.Height;
tabControl1.Height = tabControl1.Height + tabControl1.ItemSize.Height;
tabControl1.Region = new Region(new RectangleF(tabPage1.Left, tabPage1.Top, tabPage1.Width, tabPage1.Height + tabControl1.ItemSize.Height));
What I do is to have a Panel where your different views will sit on the main form.
then create user controls for your different views.
Then when I want to switch between a'view' you dock it to Panel on the main form.. code looks a little like this.
i preffer this because you can then reuse your views, like if you want to open up a view in a tab you can dock your user controls inside tab pages.. or even inherit from
tabpage instead of usercontrol to make things a bit more generic
public partial class MainForm : Form
{
public enum FormViews
{
A, B
}
private MyViewA viewA; //user control with view a on it
private MyViewB viewB; //user control with view b on it
private FormViews _formView;
public FormViews FormView
{
get
{
return _formView;
}
set
{
_formView = value;
OnFormViewChanged(_formView);
}
}
protected virtual void OnFormViewChanged(FormViews view)
{
//contentPanel is just a System.Windows.Forms.Panel docked to fill the form
switch (view)
{
case FormViews.A:
if (viewA != null) viewA = new MyViewA();
//extension method, you could use a static function.
this.contentPanel.DockControl(viewA);
break;
case FormViews.B:
if (viewB != null) viewB = new MyViewB();
this.contentPanel.DockControl(viewB);
break;
}
}
public MainForm()
{
InitializeComponent();
FormView = FormViews.A; //simply change views like this
}
}
public static class PanelExtensions
{
public static void DockControl(this Panel thisControl, Control controlToDock)
{
thisControl.Controls.Clear();
thisControl.Controls.Add(controlToDock);
controlToDock.Dock = DockStyle.Fill;
}
}
Tabbed forms are usually good... but only if you want the user to be able to see any view at any time... and it sounds like you might not.
Separate forms definitely works, but you need to make sure that the switch is seemless...if you make sure the new form appears the same exact size and location of the old form, it will look like it thew same for with changing controls.
The method I often use is actually to pre-setup all my controls on individual "Panel" controls and then show and hide these panels as I need them. The "Panel" control is basically a control container... you can move the panel and all controls on it move relative. And if you show or hide the panel, the controls on it do the same. They are great for situations like this.
The method I often use is actually to
pre-setup all my controls on
individual "Panel" controls and then
show and hide these panels as I need
them.
Instead of making each view a panel within a single form you could make each view a UserControl. Then create a single form and write code to create and display the correct UserControl in the Form and to switch from one to the next. This would be easier to maintain because you will have a separate class for each view instead of a single Form class with 6 panels each with their own controls -- that seems difficult and error prone to maintain.
I would also check out Composite Application Guidance for WPF or Smart Client Software Factory

Categories