Issues with calling method from another form - c#

I've found some good posts about using methods in other forms and tried to implement them in my code, but I'm getting a null object error (specifically, the frmAddMaterials object is null). Code compiles fine, error occurs when trying to use the create materials button on the second form.
My first form has an array to hold material information. When the user clicks a link on this first form, they are prompted with a second form where they can enter custom material information in the event that the material they are interested in using is not on designed in. Upon clicking "Add material" on form 2, I would like the RefreshMaterials() method on the first form to run, which creates a new entry in the array based on information from the second form.
Form1:
public partial class frmSnapFitMain : Form
{
public frmMat frmAddMaterials;
public Materials[] material = new Materials[20];
Image[] problemtype = new Image[7];
private void linkLabel1_LinkClicked_1(object sender,LinkLabelLinkClickedEventArgs e)
{
frmMat frmAddMaterials = new frmMat(this);
frmAddMaterials.Show();
}
public void RefreshMaterials()
{
material[Materials.MaterialCount] = new Materials(frmAddMaterials.txtName.Text, Double.Parse(frmAddMaterials.txtFlex.Text), Double.Parse(frmAddMaterials.txtFriction.Text), Double.Parse(frmAddMaterials.txtStrain.Text)); //little m, materials here is for specific instance
cboxMatSelect.Items.Add(frmAddMaterials.txtName.Text);
frmAddMaterials.txtName.Text = ""; //reset fields
frmAddMaterials.txtFlex.Text = "";
frmAddMaterials.txtFriction.Text = "";
frmAddMaterials.txtStrain.Text = "";
}
}
Form 2:
public partial class frmMat : Form
{
private readonly frmSnapFitMain _form1;
public frmMat(frmSnapFitMain Form1)
{
InitializeComponent();
this._form1 = Form1;
}
public void btnCreate_Click(object sender, EventArgs e)
{
this._form1.RefreshMaterials();
this.Close();
}
public void frmMat_Load(object sender, EventArgs e)
{
}
}

In the linkLabel1_LinkClicked_1 method you declare a new instance of frmMat. This instance happens to have the same name of the global variable but it is a local one that disappears when you exit from the method.
private void linkLabel1_LinkClicked_1(object sender,LinkLabelLinkClickedEventArgs e)
{
// This is not the global variable frmAddMaterials.
// It is a local one to this method
frmMat frmAddMaterials = new frmMat(this);
frmAddMaterials.Show();
}
Of course this means that in your RefreshMaterials you use the global variable that has never been initialized
You just need to remove the declaration of a local variable and initialize the global one
private void linkLabel1_LinkClicked_1(object sender,LinkLabelLinkClickedEventArgs e)
{
// This initializes the global variable
frmAddMaterials = new frmMat(this);
frmAddMaterials.Show();
}
Said that it is always a good practice to use a 'defensive programming attitude' when using global variables and test if the variable has been correctly initialized.
public void RefreshMaterials()
{
if(frmAddMaterials != null)
{
material[Materials.MaterialCount] = new Materials(.....)
cboxMatSelect.Items.Add(frmAddMaterials.txtName.Text);
frmAddMaterials.txtName.Text = ""; //reset fields
frmAddMaterials.txtFlex.Text = "";
frmAddMaterials.txtFriction.Text = "";
frmAddMaterials.txtStrain.Text = "";
}
}
I also suggest to subscribe to the FormClosing event of frmAddMaterials to set your instance to null when the form closes
private void linkLabel1_LinkClicked_1(object sender,LinkLabelLinkClickedEventArgs e)
{
if(frmAddMaterials != null)
{
frmAddMaterials = new frmMat(this);
frmAddMaterials.Show();
frmAddMaterials.FormClosing += frmMaterialsClosing;
}
}
private void frmMaterialsClosing(object sender, CancelEventArgs e)
{
frmAddMaterials = null;
}
This allows you to restart the 'cycle' because when you click again the linklabel your global variable is null and you could reinitialize it to the new instance created in the linklabel click event handler.

Related

Label will not show even when told to show

I am making an application that loads a separate form, the user puts in information, and then when done, it will show up on the primary form the application loaded with first.
The issue is that I tried multiple solutions to get this to load in, but it will not load in after the information is put in. I have tried this.Controls.Add(Label); which is what I have seen the most, but it has not worked. Another way I tried was doing Label.Show();, but the same result, with nothing showing. The AddContacts(string Name) method below is how I add the contact
The AddContact_Click(object sender, EventArgs e) method is a button that, when pressed, opens another form that allows information to be inserted.
public partial class Phonebook : Form
{
public Phonebook()
{
InitializeComponent();
MaximumSize = new Size(633, 306);
}
private void AddContact_Click(object sender, EventArgs e)
{
MakeContact MC = new MakeContact();
MC.Show();
}
public void AddContacts(string Name)
{
Label name = new Label();
//Added Style and Location of Label...
name.Text = Name;
name.Location = new Point(98, 13);
name.Font = new Font("Microsoft Sans Serif", 13, FontStyle.Bold);
this.Controls.Add(name);
Refresh();
}
}
Below is the Method I used when the Finish button is pressed, for when the user is done with the information, and then the AddContacts() method is called
public partial class MakeContact : Form
{
public MakeContact()
{
InitializeComponent();
MaximumSize = new Size(394, 377);
}
private void FinishContact_Click(object sender, EventArgs e)
{
//FullName is the name of the TextField when asking for a name
string Name = FullName.Text;
Phonebook PB = new Phonebook();
PB.AddContacts(Name);
//Closes Separate Form and goes back to the
Close();
}
}
Expectation:
It should load the label into the form after the information is put in.
Actual:
It will not show what so ever.
EDIT: Added More to the Code and to the Question since I didn't do too good of asking the question, sorry about that :/
An example of what I described in the comments:
When you do this:
Phonebook PB = new Phonebook();
you create a new instance of the PhoneBook class (your form): this is not the same Form instance (the same object) that created the MakeContact Form and the one you're trying to update. It's a different object.
Whatever change you make to this new object, it will not be reflected in the original, existing, one.
How to solve:
Add a Constructor to the MakeContact Form that a accepts an argument of type PhoneBook and a private object of type Phonebook:
private PhoneBook pBook = null;
public MakeContact() : this(null) { }
public MakeContact(PhoneBook phoneBook)
{
InitializeComponent();
this.pBook = phoneBook;
}
Assign the argument passed in the constructor to the private field of the same type. This Field will then used to call Public methods of the PhoneBook class (a Form is a class, similar in behaviour to other class).
It's not the only possible method. You can see other examples here.
Full sample code:
public partial class Phonebook : Form
{
private void AddContact_Click(object sender, EventArgs e)
{
MakeContact MC = new MakeContact(this);
MC.Show();
}
public void AddContacts(string Name)
{
Label name = new Label();
// (...)
this.Controls.Add(name);
}
}
public partial class MakeContact : Form
{
private PhoneBook pBook = null;
public MakeContact() : this(null) { }
public MakeContact(PhoneBook phoneBook)
{
InitializeComponent();
this.pBook = phoneBook;
}
private void FinishContact_Click(object sender, EventArgs e)
{
string Name = FullName.Text;
this.pBook?.AddContacts(Name);
this.Close();
}
}

could i accessing a form control in another user control without making a new instance?

Admin is that form that have a label an i want to access it during the runtime from another form or another user control
public partial class Admin : Form
{
public Admin()
{
InitializeComponent();
}
public Label lbl
{
get { return label8; }
set { label8.Text = value.ToString(); }
}
}
User control:
Admin.lbl.text="something"; //could i do this and how ??
but if i made a new instance , i should close the old one to show changes
but i don't want to make that during all time of the Run time
Abanoub - If I understand what you are trying to do - you want to set the label of an already displayed form without creating a new instance of the form. At least one way to do that would be with a singleton class that holds the form instance. So there will only be one instance of the form. Please try the following:
First, we create a singleton class that keeps the form instance:
public class Singleton
{
// Modified from: http://csharpindepth.com/articles/general/singleton.aspx
// This will keep ONE instance of the Admin Form
private Admin _adminForm;
public Admin AdminForm
{
get
{
if (_adminForm == null)
{
_adminForm = new Admin();
}
return _adminForm;
}
}
private static Singleton instance = null;
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
Now you instantiate the from from this instance - for example:
Button 1 will display the form:
private void button1_Click(object sender, EventArgs e)
{
var singleton = Singleton.Instance;
var f = singleton.AdminForm;
f.Show();
}
Button 2 will set the already displayed form's label (BTW - I think you want the property to set the text of the label not the label right?)
private void button2_Click(object sender, EventArgs e)
{
// Assuming you clicked button 1 first,
// this will not cause a new instance but use the existing one
var singleton = Singleton.Instance;
var f = singleton.AdminForm;
f.LabelText = "Hello world!";
}
Assuming you want to set the text of the label - here is the modified property in Admin:
public string LabelText
{
get { return label8.Text; }
set { label8.Text = value; }
}
I hope this will be helpful to you - good luck!!

Extending ref keyword to another variable

I'm developing wpf application.
I'm trying to pass object by reference using ref keyword and then pass it to another variable in constructor to change it later. But when I change the variable to which I passed the reference in constructor varaible doesn't change outside scope.
To explain, first I create variable and pass it to another window's constructor.
private void LocatonEditButton_Click(object sender, RoutedEventArgs e)
{
var location = new Location(SelectedLocation.Name, SelectedLocation.X, SelectedLocation.Y, SelectedLocation.Update);
//Creating object
var result = new EditWindow(ref location,true).ShowDialog();
//And passing it to another window with ref
}
Here I assign variable to InitialLocation, if I try to change it here it works on outside variable.
public partial class EditWindow : Window
{
public EditWindow(ref Location location, bool isEdit)
{
InitializeComponent();
InitialLocation = location;
//InitialLocation = ref location; //This is what I want my code to do
location.Name = "new"; //this changes varaiable outside scope
}
private Location InitialLocation;
Here I change InitialLocation but change doesn't persits outside the scope.
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
InitialLocation = new Location(CurrentLocation.Name, CurrentLocation.X, CurrentLocation.Y, InitialLocation.Update);
//But this doesn't change varaible outside scope
this.Close();
}
I want to keep object that was passed with ref until the window I passed it to is disposed.
Is it possible to do without waiting for closed event?
I suggest to declare InitialLocation as public property.
public partial class EditWindow : Window
{
public EditWindow(Location location, bool isEdit)
{
InitializeComponent();
InitialLocation = location;
location.Name = "new";
}
public Location InitialLocation { get; set; }
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
InitialLocation = new Location(CurrentLocation.Name, CurrentLocation.X, CurrentLocation.Y, InitialLocation.Update);
this.Close();
}
}
after editing read the value of that property:
private void LocatonEditButton_Click(object sender, RoutedEventArgs e)
{
var location = new Location(SelectedLocation.Name, SelectedLocation.X, SelectedLocation.Y, SelectedLocation.Update);
var editWindow = new EditWindow(location, true);
var result = editWindow.ShowDialog();
var changedLocation = editWindow.InitialLocation;
}

Add a UserControl that is created in another thread to a form

I have an application that has a form named as MainAppForm (Thread1). I have a panel in this form which will host UserControls.
When a user clicks the button, I want to create another thread (Thread2) which will create an instance of the UserControl and call a method that is on the Thread1 to add UserControl to the panel in mentioned in the first paragraph.
This is how I call main Thread1 from Thread2
public class SecondThread
{
public void start()
{
ModuleWindow userControl = new ModuleWindow(new Module.ModuleLayer());
Global.SetModuleWindowThreadSafe(userControl);
}
}
My method that will add the passed in user control to the panel.
public static class Global
{
private delegate void SetModuleWindowThreadSafeDelegate(UserControl userControl);
public static void SetModuleWindowThreadSafe(UserControl userControl)
{
if (Global.mainAppForm.pnlMain.InvokeRequired)
{
Global.mainAppForm.pnlMain.Invoke(
new SetModuleWindowThreadSafeDelegate(SetModuleWindowThreadSafe),
userControl);
}
else
{
Global.mainAppForm.pnlMain.Controls.Add(userControl);
}
}
}
After I do the call in the SetModuleWindowThreadSafe() method it raises
Cross-thread operation not valid: Control 'menuStrip1' accessed from a thread other than the thread it was created on.
Note: menuStrip1 is a control on UserControl.
How can I add the UserControl that is created in the second thread to the panel???
UPDATED:
Thanks for the answers. I am sure they're helpful in some ways but not in my condition. The reason is my MainAppForm(AKTAP project) and the generated UserControl's(KKM project) are being created in different projets even solutions. The project output of KKM is a .dll and I am loading those dll files on runtime using reflections. So MainAppForm does not know what type of usercontrols and controls are being generated in each dll.
What I want to do is in the following order:
1- AKTAP project has an interface which is implemented by a class in KKM project.
2- KKM project is being built and puts dll files to a specified directory.
3- AKTAP starts to run and loads dll files using reflections by filtering the interface mentioned in 1.
4- AKTAP calls a method in KKM hich will generate and return the usercontrol.
5- AKTAP adds the returned usercontrol to the MainAppForm. (And this is where I get the exception above.)
How can I add the UserControl that is created in the second thread to the panel?
You don't. You create the UserControl in the UI thread, rather than in some background thread.
If you have some expensive CPU bound computation to do in order to figure out what data the user control will need then use another thread to compute that data and then have the UI thread take that data and create the UI controls to display it.
Servy is correct - you don't.
However, you can! Meaning, it is possible.
Passing data from a thread is complicated, but the System.ComponentModel.BackgroundWorker (part of WinForms) greatly simplifies threading operations and makes tasks like this rather fun to do.
Here is a generic technique that uses two (2) Windows Forms, one as a variable inside the other. Both are in the same namespace (same project, etc).
Form1:
public partial class Form1 : Form
{
private Button btnGetInteger;
private Button btnGetMenuStrip;
private Button btnGetString;
private Form2 _form2;
private Form2.ReturnType _getType;
private Object _form2Argument;
public Form1()
{
InitializeComponent();
btnGetInteger = new Button();
btnGetInteger.Click += Form2_GetInteger;
btnGetMenuStrip = new Button();
btnGetMenuStrip.Click += Form2_GetInteger;
btnGetString = new Button();
btnGetString.Click += Form2_GetString;
Shown += (s, e) => { Form2_CreateMenuStrip(s, EventArgs.Empty); };
}
public void Form2_ThreadChanged(object sender, ProgressChangedEventArgs e)
{
var returned = (Form2.ReturnType)e.ProgressPercentage;
switch (returned)
{
case Form2.ReturnType.MenuStrip:
var menuStrip = (MenuStrip)e.UserState;
this.Controls.Add(menuStrip);
break;
case Form2.ReturnType.Integer:
var numberBack = (int)e.UserState;
Text = String.Format("Form1 : (int){0}", numberBack);
break;
case Form2.ReturnType.String:
var stringBack = e.UserState.ToString();
Text = String.Format("Form1 : (String){0}", stringBack);
break;
}
}
public void Form2_ThreadCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_form2Argument = null;
if (e.Error != null)
{
String title;
if (_form2 != null)
{
title = String.Format("{0}: {1}", _form2.Text, e.Error.GetType());
} else
{
title = String.Format("Form2: {0}", e.Error.GetType());
}
MessageBox.Show(e.Error.Message, title, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
if (_form2 != null)
{
_form2.Close();
_form2.Dispose();
_form2 = null;
}
btnGetInteger.Enabled = true;
btnGetMenuStrip.Enabled = true;
btnGetString.Enabled = true;
}
private void Form2_CreateMenuStrip(object sender, EventArgs e)
{
if (_form2 == null)
{
_getType = Form2.ReturnType.MenuStrip;
var item = new ToolStripMenuItem(Text);
item.Click += Form2_GetInteger;
_form2Argument = item;
Form2_StartWork();
}
}
private void Form2_GetInteger(object sender, EventArgs e)
{
if (_form2 == null)
{
_getType = Form2.ReturnType.Integer;
Form2_StartWork();
}
}
private void Form2_GetString(object sender, EventArgs e)
{
if (_form2 == null)
{
_getType = Form2.ReturnType.String;
Form2_StartWork();
}
}
private void Form2_StartWork()
{
btnGetInteger.Enabled = false;
btnGetMenuStrip.Enabled = false;
btnGetString.Enabled = false;
_form2 = new Form2();
_form2.Show(); // Show returns immediately
_form2.StartThread(this, _form2Argument, _getType);
}
}
Form2_ThreadChanged and Form2_ThreadCompleted are both set to PUBLIC so that they can be visible by the instance of Form2.
Form2:
public partial class Form2 : Form
{
private ReturnType _getType; // thread safe
private BackgroundWorker _bwThread;
public enum ReturnType { MenuStrip, String, Integer }
public Form2() // Do Not Call this method
{
InitializeComponent();
}
public void StartThread(Form1 parent, Object argument, ReturnType getType)
{
_getType = getType;
if (_bwThread == null)
{
_bwThread = new BackgroundWorker() {
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_bwThread.DoWork += ThreadWork;
_bwThread.ProgressChanged += parent.Form2_ThreadChanged;
_bwThread.RunWorkerCompleted += parent.Form2_ThreadCompleted;
}
if (!_bwThread.IsBusy)
{
_bwThread.RunWorkerAsync(argument);
}
}
private void ThreadWork(object sender, DoWorkEventArgs e)
{
switch (_getType)
{
case ReturnType.MenuStrip:
var menuStrip = new MenuStrip();
if (e.Argument != null)
{
var mi = (ToolStripMenuItem)e.Argument;
menuStrip.Items.Add(mi);
}
_bwThread.ReportProgress((int)_getType, menuStrip);
break;
case ReturnType.Integer:
var numberBack = 1;
_bwThread.ReportProgress((int)_getType, numberBack);
break;
case ReturnType.String:
var stringBack = "Worker String";
_bwThread.ReportProgress((int)_getType, stringBack);
break;
}
}
}
If you make a new, small project with 2 empty forms in it called Form1 and Form2, you can go into the code and simply paste everything from above into those two forms.
With that done, just put breakpoints on all of the methods (both public and private) to see how they work.

Make a radio button input from a form public in C#?

I've got code and i know I'm 99% of the way there. C# coding in MS VS2008.
Basically I have a form that has 4 radio buttons and a Continue button. the user clicks one of the radio buttons and clicks continue, and this all works fine.
However, I want to use the value entered by the user (i.e. if they click the first button, I want a variable equal to 1, 2nd button equals 2 and so on). I tried doing this in various points but the only place I can get it to run is in the private void btnOkClick line, which means I can use the values outside this void, which is what I really want.
I've tried playing around with setting some enums and such (commented out in the code below), but I can't quite get it. I know I must be close but my novice-ness is truly showing as I keep reading posts and can't quite grasp it.
In short, I want to be able to have other classes in my VS2008 project be able to reference whatever value the user selected in the initial form.
namespace AmortClient
{
public partial class frmLoadACTFCST : Form
{
public frmLoadACTFCST()
{
InitializeComponent();
//set the parent of the form to the container
//this.MdiParent = parent;
}
//public enum ACTFCST
//{
// ACT = 1,
// FCST = 2,
// PLAN = 3,
// FiveYearPlan2012=4
//}
//private ACTFCST _actfcst = ACTFCST.ACT;
//public ACTFCST actfcst
//{
// get { return _actfcst; }
// set { _actfcst = value; }
//}
private void frmLoadACTFCST_Load(object sender, EventArgs e)
{
}
private void groupBox1_Enter(object sender, EventArgs e)
{
}
private void btnActual_CheckedChanged(object sender, EventArgs e)
{
}
private void btnForecast_CheckedChanged(object sender, EventArgs e)
{
}
private void btnPlan_CheckedChanged(object sender, EventArgs e)
{
}
private void btn5YrPlan2012_CheckedChanged(object sender, EventArgs e)
{
}
private void btnContinue_Click(object sender, EventArgs e)
{
string ACTFCSTtext = "";
int dataTypeKey = 0;
if (btnActual.Checked)
{
ACTFCSTtext = btnActual.Text;
dataTypeKey = 1;
}
else if (btnForecast.Checked)
{
ACTFCSTtext = btnForecast.Text;
dataTypeKey = 2;
}
else if (btnPlan.Checked)
{
ACTFCSTtext = btnPlan.Text;
dataTypeKey = 3;
}
else if (btn5YrPlan2012.Checked)
{
ACTFCSTtext = btn5YrPlan2012.Text;
dataTypeKey = 4;
}
string msg = "";
msg = ACTFCSTtext + " " + dataTypeKey;
//btn5YrPlan2012
MessageBox.Show(msg);
Close();
}
}
}
Your dataTypeKey and ACTFCSTtext variables need to be declared as instance variables for your Form object if you want to access them from any other methods within your form. If you want to use them with some other form, you can pass them either as constructor arguments, or set some properties of said other form.
So you'd declare them just after the class declaration if you want them to be instance variables. They should still be private, meaning they can only be accessed from within your frmLoadACTFCST class.
public partial class frmLoadACTFCST : Form
{
private string ACTFCSTtext = "";
private int dataTypeKey = 0;
...
EDIT: if you want to access variables from one object in a different object (or static class), your options are as follows...
1) Declare your variables as public instance variables (same as shown above but public; these are known as Properties when you give them getter and setter methods). Your class that needs access to these variables would need to have a reference to the class that owns the variables.
Example:
FormA has a public property named SomeString.
FormB needs to access SomeString.
FormB needs a reference to FormA, and would access the variable as...
formAReference.SomeString
2) Pass the values of the variables as arguments to some method for the class that needs access.
Example:
FormA has a private instance variable named SomeString.
FormB needs access to SomeString.
If FormA instantiates FormB, it can pass the value of SomeString to FormB's constructor...
//From within FormA's code
FormB formB = new FormB(SomeString);
//FormB's constructor
public FormB(string someString)
{
this.someString = someString;
}
Maybe there is a smarter way to do it.
public partial class frmLoadACTFCST : Form
{
public frmLoadACTFCST()
{
InitializeComponent();
actfcst = ACTFCST.ACT;
btnActual.Tag = ACTFCST.ACT;
btnActual.Checked = true;
btnForecast.Tag = ACTFCST.FCST;
btnPlan.Tag = ACTFSCT.PLAN;
btn5YrPlan2012.Tag = ACTFCST.FiveYearPlan2012;
}
public enum ACTFCST
{
ACT = 1,
FCST = 2,
PLAN = 3,
FiveYearPlan2012=4
}
public static ACTFCST actfcst { get; private set; }
private void CheckedChanged(object sender, EventArgs e)
{
// All the buttons uses this Click-event.
actfcst = (sender as Button).Tag as ACTFCST;
}
private void btnContinue_Click(object sender, EventArgs e)
{
MessageBox.Show(actfcst.ToString());
Close();
}
}
The point is that all the buttons calls CheckedChanged when clicked.
Using a static means that others can access the value using something like this:
frmLoadACTFCST.ACTFCST value = frmLoadACTFCST.actfcst;
// Do something based on value.
I hope this helps you in yoyr quest.
If you select a control in design view, the properties window contains an item named "Modifiers". You can make the control public here.
A better way would be to create a new public property on your form that yields the value of the currently selected radio button.

Categories