I seem to be have a great deal of trouble with a simple issue. Yes I'm new to c# but I try to learn what I can without jumping to post a question. In this case I just think I'm not asking the right question.
No code samples will help here because I'm talking about the basics ( implementation ). I have not really coded anything yet, just use the visual builder to create my windows forms and menus.
The issue I'm having is when I select a menu item (call it: set paths ) I want that list view on my main form to load from the path selected when I hit ok on form2. So I did a simple find folder dialog and I have my new path stored on form2 in a text box. When I hit ok on that form2 I want my listview form1 to populate. I know how to do all of this but I can not for the life of me access form1 from form2 or vice versa.
I tried making a call back function but I get that non-static variable cannot be referenced... error because my form1 is static, so I can't create any non static methods. I looked in to EventArgs but that just seems like an over kill for such a common request.
So what is the general way to do this?
Robert's answer is correct as far as accessing members on another form. However, in general you should be storing the state of your application (call it the "model") separately from the state of your user interface (call it the "view"). This becomes very important as your application grows beyond one or two interactions. There are several philosophies or patterns about how to tie the two together (Google "model-view-controller" (MVC) and "model-view-viewmodel" (MVVM) for example), and if you really want to do this correctly I would recommend learning about those. My preference is for the MVVM approach, and you can do it fairly easily with Windows Forms even though it was designed with WPF applications in mind.
In .NET, the basic piece of code you should use to implement the connection between your viewmodel and your view is an interface called INotifyPropertyChanged. You create a class that implements this interface and sends notifications whenever a property changes, so for example for your path property you would create this class:
class ViewModel : INotifyPropertyChanged
{
private string path;
public string Path
{
get { return path; }
set {
if (value != path)
{
path = value;
NotifyPropertyChanged();
}
}
}
// This event gets triggered whenever a property changes.
public event PropertyChangedEventHandler PropertyChanged;
// This will cause the event to actually be triggered. It automatically determines the name of the property that triggered it using the [CallerMemberName] attribute - just a bit of .NET 4.5 sweetness. :)
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
It may seem like a lot of work, but now in your form1 you can create a new "ViewModel" instance, subscribe to the event, and then pass the instance to form2. form2 then simply updates the Path property on the viewmodel instance whenever the user selects a different path.
So, Form1 needs this code near the top:
private ViewModel viewmodel = new ViewModel();
And this goes in the Form1 constructor:
viewmodel.PropertyChanged += new EventHandler(OnPathChanged);
And when you create/show form2:
var form2 = new Form2(viewmodel); // Note, the viewmodel instance is being passed to the form2 constructor
form2.Show();
The form2 constructor then stores its own reference to the "viewmodel" instance, and sets the Path property whenever the path is changed by the user.
private ViewModel viewmodel;
public Form2(ViewModel viewmodel)
{
this.viewmodel = viewmodel;
... // Other stuff to set up your controls etc. goes here
}
private void PathChanged(object sender, EventArgs e) // This may be named differently in your code; it's the event handler that gets called when the path changes
{
// This will automatically notify the event handler in Form1! It's super-elegant and flexible.
this.viewmodel.Path = txtPath.Text; // Assuming you have a textbox called txtPath
}
And finally the event handler in Form1:
private void OnPathChanged(object sender, EventArgs e)
{
var newPath = viewmodel.Path; // Get the updated path back from the viewmodel
//TODO: Whatever you want to do when the path changes.
}
Here's a link to a really good MVVM intro using Windows Forms, it uses two forms like you have in your example. MVVM (Model-View-ViewModel) Pattern For Windows Form Applications, using C#
If you need to access something on another form, just hold a reference to it from your first form, like this:
form2 = new Form2();
form2.Show();
form2.WhateverYouWantToAccess
That said, if you just want to get a file path from the user, you want to use the OpenFileDialog class.
private void button1_Click(object sender, System.EventArgs e)
{
Stream myStream = null;
OpenFileDialog openFileDialog1 = new OpenFileDialog();
openFileDialog1.InitialDirectory = "c:\\" ;
openFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*" ;
openFileDialog1.FilterIndex = 2 ;
openFileDialog1.RestoreDirectory = true ;
if(openFileDialog1.ShowDialog() == DialogResult.OK)
{
try
{
if ((myStream = openFileDialog1.OpenFile()) != null)
{
using (myStream)
{
// Insert code to read the stream here.
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
}
}
}
Related
I'm having trouble manipulating forms when from another thread.
I've overcome the issue by loading the form at runtime, showing it then hiding it. This means the form is created on the right thread and can be manipulated using invokes.
This is not the right way to do it. I have 3 problems that come from using this method
I can't spawn another popup box I have to use the one I created at runtime
The forms flash briefly on load - now that I have 3 forms its pretty obvious what I'm doing.
I have to use a variable bool to hold if the popup is open or not.
If anyone could point me in the right direction It would be much appreciated. Currently my code looks like:
On Main form Load:
CallerIDfrm = new frmCallerID();
CallerIDfrm.Show();
CallerIDfrm.Hide();
to manipulate the popup Im using
delegate void StringArgReturningVoidDelegate1(string CallerIDnum, string CallerIDname, string ContactID);
private void CallerID(string CallerIDnum, string CallerIDname, string ContactID)
{
if (CallerIDfrm.InvokeRequired)
{
StringArgReturningVoidDelegate1 d = new StringArgReturningVoidDelegate1(CallerID);
CallerIDfrm.Invoke(d, new object[] { CallerIDnum, CallerIDname, ContactID });
}
else
{
if (ContactID != null || ContactID != "0")
{
CallerIDfrm.ContactID = ContactID;
}
CallerIDfrm.Mainfrm = this;
CallerIDfrm.TopLevel = true;
CallerIDfrm.TopMost = true;
CallerIDfrm.lblCallerIDname.Text = CallerIDname;
CallerIDfrm.lblCallerIDnum.Text = CallerIDnum;
CallerIDfrm.Show();
CallerIDOpen = true;
}
}
To Hide the popup until required again im using:
delegate void StringArgReturningVoidDelegate2();
private void CallerIDClose()
{
if (CallerIDfrm.InvokeRequired)
{
StringArgReturningVoidDelegate2 d = new StringArgReturningVoidDelegate2(CallerIDClose);
CallerIDfrm.Invoke(d, new object[] { });
}
else
{
try
{
CallerIDfrm.Hide();
CallerIDOpen = false;
}
catch
{
}
}
}
I've tried otherways but the Popup loads as if it is not responding and I loose access to the popup.
Ultimately I'd like to be able to spawn multiple popups and have the ability to close them from the Main Form.
What I gather from your question: You have an caller api/lib/class and you like to show CallerId on a popup form when a call is received. Have a look at Events and Event Driven programming.
The following codes has not been tested, I wrote it from top of my head. Might not compile, they are here to show an example:
Create an CallReceived event in api/lib class as follows:
public event EventHandler<CallReceivedEventArgs> CallReceived;
protected void OnCallReceived(EventArgs e)
{
var handler = CallReceived;
if (handler != null)
handler(this, e);
// Note: For C# 6.0 and later, above statements can be simplified to
// CallReceived?.Invoke(this, e);
}
Note: If you don't have access to this api/lib code, create a Gateway class and put your event in there along with mechanism to trigger it.
Also create a CallReceivedEventArgs, this will be used to transfer event data:
public class CallReceivedEventArgs : EventArgs
{
public string CallerIDnum {get; set;}
public string CallerIDname {get; set;}
public string ContactID {get; set;}
}
Now, in your api/lib class raise this event whenever a call is received:
// a call received, replace dummy values with actual values
OnCallReceived(new CallReceivedEventArgs() { CallerIDnum="5554443322", CallerIDname="SOME_NAME", ContactID="SOME_CONTACT" });
Finally in your GUI form, register to said event and process accordingly.
// inside your main form class
private CallerAPI callerApi = new CallerAPI();
// somewhere inside you main form class, register to event
// from your use case, I would place it inside Main Form's constructor
callerApi.CallReceived += callerApi_Callreceived;
// receive event
void callerApi_Callreceived(object sender, CallReceivedEventArgs e)
{
var callerIDnum = e.CallerIDnum;
// etc.
// show callerId form with details
// you need to change frmCallerID's constructor accordingly
CallerIDfrm = new frmCallerID(e.CallerIDnum, CallerIDname, ContantID);
// to be able to track opened popups, you can store them inside a list
// private List<Form> openPopupList = new List<Form>();
//
// alternatively, you can assign CallerIDnum to form's name property
// and store these inside a List<string> instead of List<Form>
openPopupList.add(CallerIDfrm);
CallerIDfrm.Show();
}
Don't forget to unregister from event.
callerApi.CallReceived -= callerApi_Callreceived;
To wrap it up:
I can't spawn another popup box I have to use the one I created at runtime
You can create and show multiple frmCallerID, independent from each other.
The forms flash briefly on load - now that I have 3 forms its pretty obvious what I'm doing.
Since new approach creates CallerID forms based on events, you won't see these form flashing. It'll open whenever a CallReceived event is received.
I have to use a variable bool to hold if the popup is open or not.
A better approach would be: Register to forms FormClosed event, and remove from openPopupList accordingly.
frmCallerID.FormClosed += frmCallerID_FormClosed;
void frmCallerID_FormClosed(object sender, EventArgs e)
{
// remove form from openPopupList
frmCallerID closedPopup = (frmCallerID) sender;
openPopupList.remove(closedPopup);
}
I have the following snippet of code that allows me to pull the properties from an object in my list and assign them to variables in other forms. However, I need to be able to pull the data from my variables in the other form and use those to set the properties of the given object.
My class Account is used to populate my list accounts. On my next form AccountMenu I have a class Variables1 that contains accessible variables that are used throughout the rest of my forms to keep track of the checking balance and saving balance. When logging off from the AccountMenu, I want to be able to pass the values from Variables1 to the account that was initially used.
I know how to pass variables from one form to another, but I'm not really sure how to update the form automatically, without a button, on the original form. Thus, the solution that I see is that I have a button on my AccountMenu form that "logs" the user out, via this.close(); Additionally, I guessed that under that button, I need to have some code that assigns the variables as properties to the object. I'm just not sure how I can access the set properties of the object, since it is dynamically called with the code below.
Can someone help me figure out what I need to do? Below is some of the relevant code so that you can see how I have things set up. I am just not sure how to access "matches" from the other form in order to update that specific object properties. Thank you, anyone, who can help!
//variable that will be used to check textbox1.Text
string stringToCheck;
//array of class Account
List<Account> accounts = new List<Account>();
public MainMenu()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//set value to user's input
stringToCheck = textBox1.Text;
//set a var that only returns a value if the .Name already exists
var matches = accounts.FirstOrDefault(p => p.Name == stringToCheck);
//check through each element of the array
if (matches == null)
{
accounts.Add(new Account(stringToCheck));
textBox1.Text = "";
label3.Visible = true;
}
else if (matches != null)
{
//set variables in another form. not sure if these are working
Variables1.selectedAccount = matches.Name;
//is this calling the CheckBalance of the instance?
Variables1.selectedCheckBalance = matches.CheckBalance;
//same thing?
Variables1.selectedSaveBalance = matches.SaveBalance;
//switch to form
AccountMenu acctMenu = new AccountMenu();
this.Hide();
acctMenu.Show();
}
}
As per my understanding I think what you required is kind of trigger on your parent form that needs to be called from your child application.
If that is what you required than you can go with defining an event on your AccountMenu form. and register this event from your Accounts form.
Than simply raise this event from your AccountMenu subform.
Deletegates and Events are really works like magic :)
Let me show you some code how to do this.
Code required in AccountMenu window:
public delegate void PassDataToAccounts(string result);
public event PassDataToAccounts OnPassDataToAccount;
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
if (OnPassDataToAccount != null)
OnPassDataToAccount("result");
base.OnClosing(e);
}
Code required in Accounts window button1_Click event where the AccountMenu will open:
//set variables in another form. not sure if these are working
Variables1.selectedAccount = matches.Name;
//is this calling the CheckBalance of the instance?
Variables1.selectedCheckBalance = matches.CheckBalance;
//same thing?
Variables1.selectedSaveBalance = matches.SaveBalance;
//switch to form
AccountMenu acctMenu = new AccountMenu();
acctMenu..OnPassDataToAccount += childwindow_OnPassDataToAccount;
this.Hide();
acctMenu.Show();
}
void childwindow_OnPassDataToAccount(string result)
{
if (result == "result")
{
// Processing required on your parent window can be caried out here
//Variables1 can be processed directly here.
}
}
Ok so I'm attempting to create a simple game. In a nutshell it's a resource management game where the player will attempt to manage a thieves guild. In regards to running missions I've created a Thief class, a new instance of which is created when a new thief is recruited. I have coded within the thief class the ability to gain experience and level up.
Here's my specific problem:
I want the player to be able to select which thief/thieves to send on a mission. I have thought about it and figured that opening a new form and populating it with checkboxes is the easiest way to allow this. These checkboxes will be related to a List<thief> of thieves, the player then checks the thieves s/he wants to send and these are then stored in another List<thief> and passed on to the run mission function.
I've built a separate project with the intention of testing and playing around with this before putting it into the main program. The test project consists of two forms: The first (frmMain) with a textbox to hold the selected options and a button to open the second form (frmSelect). Currently I can open and populate the second form (frmSelect) but when I try to add the checked options to the textbox I simply...well can't.
So far I have tried directly accessing the textbox by typing frmMain.txtOptionsDisplay in the cs file of frmSelect but it causes the following error:
An object reference is required for the non-static field, method or
property
I tried to create a new form in frmSelect and make it equal to the active instance of frmMain with: Form frmTemp = frmMain.ActiveForm; and then alter the textbox using frmTemp as a go-between but that produced the error:
'System.Windows.Forms.Form' does not contain a definition for
'txtOptionsDisplay'.
Having searched both google and stackoverflow forums I've encountered answers that I either have never heard of (Threading) or answers that I kind've recognise but can't interpret the code pasted to make it relevant to my problem (delegates).
Any advice or pointers would be fantastic.
EDIT:
frmMain code:
public frmMain()
{
InitializeComponent();
selections.Add("Option 1");
selections.Add("Option 2");
}
private void btnClick_Click(object sender, EventArgs e)
{
frmSelectOptions.Show();
int length = selections.Count();
for (int i = 0; i < length; i++)
{
CheckBox box = new CheckBox();
box.Text = selections[i];
box.AutoSize = true;
box.Location = new Point(50, 50*(i+1));
frmSelectOptions.grpControls.Controls.Add(box);
}
}
public void updateText(string option)
{
txtOptionsDisplay.Text += option;
}
}
frmSelect code:
public List<CheckBox> selectedOptions = new List<CheckBox>();
Form frmTemp = frmMain.ActiveForm;
public frmSelect()
{
InitializeComponent();
}
private void btnSelect_Click(object sender, EventArgs e)
{
foreach (CheckBox box in grpControls.Controls)
{
if (box.Checked == true)
selectedOptions.Add(box);
}
this.Hide();
}
}
I hope this formats correctly... I'm kinda new and don't know how to indent. Oh look there's a preview...
Does this help?
Your problem is that controls defined within a form by default receive the private access identifier. Hence you could just add a property along the lines of
public ControlType ProxyProperty {
get {
return txtOptionsDisplay;
}
}
Besides from that you should think about wether what you're trying is actually a good solution. Manipulating forms from one to another will become a huge clusterfuck in terms of maintenance later on.
I'd suggest using the Singleton pattern for your frmMain. This will help safeguard you from accidentally launching another instance of frmMain and at the same time, will give you access to frmMain's objects. From there, you can either write accessors to Get your txtOptionsDisplay or you can make it public. Below is an example:
public class frmMain
{
private static frmMain Instance = null;
private static object LockObj = new object();
public static frmMain GetMain()
{
// Thread-safe singleton
lock(LockObj)
{
if(Instance == null)
Instance = new frmMain();
return Instance;
}
}
public string GetOptionsDisplayText()
{
return txtOptionsDisplay.Text;
}
}
public class frmSelect
{
private void frmSelect_Load(object sender, EventArgs e)
{
// Set whatever text you want to frmMain's txtOptionsDisplay text
txtDisplay.Text = frmMain.GetMain().GetOptionsDisplayText();
}
}
If you do go this route, don't forget to update Program.cs to use frmMain's singleton.
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// Application.Run(new frmMain()); - Old method
Application.Run(frmMain.GetMain());
}
Im currently facing the problem that when i try to set focus on some control (textBox), nothing happens, maybe i just overlooked something.(somewhere i found that focus is "low-level" method and that select() should be used instead, however, it doesnt work as well)
From form Login, i launch new instance of EncryptPSW form
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
EncryptPSW ePSW = new EncryptPSW();
ePSW.setOsLog(false, this);
ePSW.ShowDialog();
}
On Button(which is located on EncryptPSW form ) click event i call fill method
public void fill()
{
if (textBoxPSW.Text.Length == 8)//psw has to be 8 chars long
{
if (save)//determinating whether save or fetch of data should be done
{ login.launchSave(textBoxPSW.Text,this); }
else { login.launchOpen(textBoxPSW.Text,this); }
}
else { MessageBox.Show("The password must contain 8 characters");}
}
Which launches either save or open method from Login (my problem is just with open, since during save i dont need to do anything with Focus)
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
setFocus();
}
After all the work is done, setFocus() should be called in order to set focus and other properties.
public void setFocus()
{
textBoxDatabase.Focus();
textBoxDatabase.SelectionStart = textBoxDatabase.TextLength - 1;
textBoxDatabase.SelectionLength = 0;
}
I tried so many different ways, like:
Calling setFocus() from within EncryptPSW_FormClosed
Calling whole open process after the EncryptPSW is closed (from within EncryptPSW_FormClosed)
and many more, however i dont remember it all.
In the case of Form_Closed the weird thing is, that when i tried to show a message box from there instead of setting focus (just to see where the problem might be), it's showed before the EncryptPSW form is closed.
My only guess about this is that the instance of EncryptPSW is somehow blocking Login form and it's controls
I hoped i described my problem well enough and that it makes at least a bit of sense ;]
Thanks in advance,
Regards,
Releis
Since the textbox is in the login form, and you are opening the EcryptPWS from it as a dialog (child), your login form will not be able to set focus to anything. You will need to set focus after it is closed. You can do this:
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
using(EncryptPSW ePSW = new EncryptPSW())
{
ePSW.setOsLog(false, this);
if (ePSW.ShowDialog() == DialogResult.OK)
{
textBoxDatabase.Focus();
}
}
}
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.DialogResult = DialogResult.OK;
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
}
OK this maybe the ugliest thing I saw round this but.
using
public void setFocus()
{
textBoxDatabase.Focus();
textBoxDatabase.SelectionStart = textBoxDatabase.TextLength - 1;
textBoxDatabase.SelectionLength = 0;
}
Change your code at
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
setFocus();
}
to
delegate void settingfocus();
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
settingfocus sf = new settingfocus(setFocus);
this.BeginInvoke(sf);
}
This worked for me
(Sorry for apparently thinking insert "this" before procedure, and change line x to this was legable)
The point is to notify the user using the memo when a packet is received/sent in a TCP Client.
The old code was extra dumb,I used a Timer that used to add text in the memo since the Timer has access to the form members,lol.
The old code:
//Memo.Text += txt + "\n";
I played with it today,this is what I've done
In Form1's class
public string TextValue
{
get
{
return Memo.Text;
}
set
{
this.Memo.Text += value + "\n";
}
}
I call the code like that:
Form1 myForm = new Form1();
myForm.TextValue = "test asdasd";
The memo modifiers are private,but that's not the problem.
The problem is that no text is displayed on the memo when i call the code.
By typing this:
Form1 myForm = new Form1();
you create a new instance of your form (Form1), but instead I guess you should use existing instance which most likely has been initialized already.
One of the ways to do it:
var form = Form.ActiveForm as Form1;
if (form != null)
{
form.TextValue = "test asdasd";
}
Though this is not very good design. Try to use custom events instead.
Maybe you should consider publishing an event in your tcpclient. Then your form will be able to listen to this event and display proper information.
Assuming Memo inherits from Control and assuming you set it with the proper modifier, the problem you may be going through is that you're likely trying to set the text from a worker thread (the one that's used to run the TCP client). If that's the case, then you need to check the InvokeRequired field of your control and if true invoke a delegate that will set the text for you.
Below is a short and easy C# snippet.
private void SetTextOnMemo(string txt){
if(Memo.InvokeRequired){
Memo.Invoke(SetTextOnMemo, txt);
}
else{
Memo.Text = txt;
}
}