I'm learning C# and what I need is to access control on a Form from other class (the same namespace).
I know there is a lot of posts on this topic here but didn't find complete solution 'for dumbs' so I write here what I figured out and please tell me - is this the correct way ?
Background: I have some 'debugging' form in my app and I need all other forms to be able to log their activity into this form. There is some ListBox control where all the logs from other forms are written. When I (or one of my tester-friends without Visual Studio) play around with app and something bad happens, I can look on that debug-form to see all detailed logs what happened just before that 'moment of error'.
My main form of the app (frmMain):
namespace myNamespace {
public partial class frmMain : Form {
private frmDebug debug; // frmDebug is declared in other class
// we will hold reference to frmDebug form in 'debug'
public frmMain() { // constructor of the main form 'frmMain'
InitializeComponent();
debug = new frmDebug(); // we create new instance of frmDebug immediately when
} // our main form is created (app started) and whole time
// of running this app we can access frmDebug from
// within frmMain through 'debug' variable
// clicking button 'btnLoggingTest', the log is written
// in the 'frmDebug' form even if it is closed (not visible)
private void btnLoggingTest_Click(object sender, EventArgs e) {
debug.Log("log this text for me please");
}
// Click handler of the button 'btnShowDebug' :
private void btnShowDebug_Click(object sender, EventArgs e) {
debug.ShowDialog(); // here we can show frmDebug (in 'modal' style)
} // just to see what log-information is written there
} // frmMain class
} // namespace
And here is the code of class frmDebug itself :
(there is only one Listbox placed on the form)
namespace myNamespace {
public partial class frmDebug : Form {
public frmDebug() {
InitializeComponent();
}
public void Log(string txt) { // this is the actual 'Log' function (or method)
this.listBox1.Items.Add(txt);
Application.DoEvents(); // if the logging takes place in some
} // computing-intensive 'loop' or function,
// (or in my case FTP login and upload process)
// 'DoEvents' ensures every log appears immediately
// after the 'Log()' was called. Otherwise all logs
// would appear together at once, as soon as the
// computing-intensive 'loop' is finished
} // class frmDebug
} // namespace
I have a strange feeling in my stomach I'm doing it all wrong so please tell me how to do it properly :) If it's OK, hope it helps somebody like me.
Thank you !
Your application has probably a class called "Program". There you will find the code
var mainForm = new frmMain();
Application.Run(frmMain);
Create a static property for the debugging form in this class
public static frmDebug DebuggingForm { get; private set; }
Change the startup code like this
DebuggingForm = new frmDebug();
var mainForm = new frmMain();
Application.Run(frmMain);
From other classes you can access this form like this
Program.DebuggingForm.Log("log this text for me please");
Program.DebuggingForm.Show();
I think you don't have to keep debugging form in memory. You can write logs to some object. E.g. static log:
public static Log
{
private static List<string> _messages = new List<string>();
public static Write(string message)
{
_messages.Add(message);
}
public static IEnumerable<string> Messages
{
get { return _messages; }
}
}
You can add log messages from every point of your application via
Log.Write("log this text for me please");
If you need to view those messages just create and show debug form:
private void btnShowDebug_Click(object sender, EventArgs e) {
using (frmDebug debug = new frmDebug())
debug.ShowDialog();
}
In debug form on load assign Log.Messages to your listbox.
A different approach would be to have an Event Sink that would act as a publish subscribe hub for your debug information, that way you don't get a dependency on the debug form all over the place, something like:
public class EventSink
{
private static readonly IList<Action<string>> _listeners = new List<Action<string>>();
public static void RegisterListener(Action<string> listener)
{
_listeners.Add(listener);
}
public static void RaiseEvent(string message)
{
foreach (var l in _listeners)
l(message);
}
}
in the constructor for your frmDebug you would do:
EventSink.RegisterListener(msg=>listBox1.Items.Add(msg));
and every time you need to add a message to the debug console you would do:
EventSink.RaiseEvent("this is a debug message");
This way you could then register new listeners to do different things, like send you an email when some specific event happens, etc. and you are not coupled with your debug form (decoupling is good:)
Related
I am struggling with passing a Variable (a string) in C# for a special problem:
Overview:
I am writing a plugin for a purchased program at my company. The program (or better: the programs support) gives the user basic C#-Code which basically just opens a form, and connects the program with whatever I write down in the forms code.
As it is a Visual-Studio-Solution I get some files: "MyUserInterface.cs" and "MyUserInterface.Designer.cs".
"MyUserInterface.Designer.cs" defines the look of my form, i thing the most importand parts for my problem are:
partial class MyUserInterface
{
[...]
private void InitializeComponent()
{
[...]
this.f_status = new System.Windows.Forms.Label();
this.SuspendLayout();
[...]
//
// status
//
this.f_status.Name = "status";
this.f_status.Text = "WELCOME TO MYPLUGIN v2";
[...]
this.Controls.Add(this.f_status);
this.ResumeLayout(false);
this.PerformLayout();
}
[...]
private System.Windows.Forms.Label f_status;
[...]
}
The most important code from "MyUserInterface.cs" is:
partial class MyUserInterface
{
[...]
public MyUserInterface()
{
InitializeComponent();
}
[...]
private void click_compute(object sender, EventArgs e)
{
//Basically everythings runs here!
//The code is opend in other classes and other files
}
}
Now as i marked in the code section, my whole code runs in the "click-compute" Function and is "outsourced" into other classes.
One important part of my code is found in "statushandler.cs":
class statushandler
{
[...]
public static void status_msg(string c_msg)
{
[...]
f_status.Text = c_msg; // And here is my problem!!
[...]
}
}
Problem:
In my special case, i try to change the text of the "f_status"-Lable while running my code by using the "status_msg" Function!
While I pass variables between classes a few times in my code. A cannot figure out, why this explicit one cant be found inside "statushandler". (It is no problem as long as I stay inside the original "click_compute", without going into a different class).
What I already tried:
1.) I tried to change basically everything in "MyUserInterface" into "public",
2.) Also I tried to call f_status in status_msg like MyUserInterface.f_status.Text,
3.) Write a Getter/Setter-Function in "MyUserInterface.(Designer.)cs" (both), which was catastrophic because i couldn't define the Label in the InitializeComponent anymore.
4.)
a.)Read a lot of Stackoverflow-Threads about passing variables between classes, which all didn't helped, all solutions I found, are working between classes, but not in this special case.
b.)Watched a lot of youTube tutorials, same result.
c.)Read some stackoverflow-Threds about passing variables between different Forms, but they all had in common, that the "displaying-form" was opend AFTER the variable was known. In my special case the form is opened all the time, and can't be closed, nor reopened...
And now I am out of ideas!
I wouldn't be surprised, if I do not see some details, but I can't found them... I would be very happy, when somebody could help me!
My question:
How can I change the text of my lable from another class?
Your method is static while your form has instance. So your static method does not know anything about your form. You can add MyUserInterface parameter to static method
public static void status_msg(MyUserInterface form, string c_msg)
{
[...]
form.f_status.Text = c_msg; // And here is my problem!!
[...]
}
If you have single instance form (only one instance is created at a time) you can have static property with it's reference:
partial class MyUserInterface
{
public static MyUserInterface Instance { get; private set; }
[...]
public MyUserInterface()
{
InitializeComponent();
Instance = this;
}
}
With this solution you can use your old method:
class statushandler
{
[...]
public static void status_msg(string c_msg)
{
[...]
MyUserInterface.Instance.f_status.Text = c_msg; // You have instance of yout form here
[...]
}
}
Of course you should protect against null/ Disposed form etc.
Create a public property on the specific class in your 1st Form that gets the label's value like this:
public string Name {get {return Label1.Text}; set {Label1.Text = value}; }
Then in your 2nd Form:
public Form2(Form1 form)
{
string name;
name = form.Name;
}
I was wondering how you would close the Form that is currently in focus or the one which a control is contained in. For example, I have an imported header with a menu that I import into all forms in my application.
This is the (simplified) code in my Header class:
public static Panel GetHeader()
{
...
menuItem.Text = "Menu Item";
menuItem.Name = "Next form to open";
menuItem.Click += toolStrip_Click;
...
}
public static void toolStrip_Click(object sender, EventArgs e)
{
ToolStripMenuItem menuItem = sender as ToolStripMenuItem;
NavigationClass.SaveNextForm(menuItem.Name);
}
The navigation class is just something I made which will select the next form to open but I couldn't find anything to then close the current one (since Close() isn't an option due to it being imported with Controls.Add(HeaderClass.GetHeader))
Edit
Just to make clear, this form is in another file which is just a normal class file. That's where the difficulty lies because I'm trying to avoid a severe violation of the DRY principle
Don't use static handlers as #Hans Passant suggests. That is important.
Try sending your main form to your class as a parameter, and store it in that class. This can be done either when you are instantiating your class, or after that. Then, when you need to close the form, call it's Close method. Since you don't include your codes in more details, here is my example with some assumptions.
public class MainForm : Form
{
private HeaderClass HeaderClass;
public MainForm()
{
HeaderClass = new HeaderClass(this);
}
}
public class HeaderClass
{
private MainForm MainForm;
public HeaderClass(MainForm mainForm)
{
MainForm = mainForm;
}
public void MethodThatYouNeedToCloseTheFormFrom()
{
...
MainForm.Close();
...
}
}
Let us know if you require any more elaboration.
here some beginners question after 2 hours of googling.
I have got a WindowsForm named GUI with an listbox item (it not have to be a listbox)
What I want to realize is having a box in the GUI an send from every classes in my project text to that box.
Example:
in Programm.cs I want simply write something like this GUI.WriteToLog("Hello World");
and it should appear in that box.
This GUI.WriteToLog should work in every class.
I tried to write a static function WriteToLog in the GUI class but if its static I cant use the listBox1 in that function.
public partial class GUI : Form
{
public void WriteToLog(string msg)
{
listBox1.Items.Add(msg);
}
}
Here the class that should access the box:
class FileManager
{
internal static void RenameFiles(string filePath)
{
GUI g = new GUI();
g.WriteToLog("Moving Files");
try {
File.Move(filePath, filePath + ".RDY");
}
catch (Exception e)
{
string message = e.ToString();
string caption = "Error";
MessageBox.Show(message, caption);
}
}
EDIT:
More Details what I want to do:
I have to access the ListBox from all of my classes, because it should inform about status. After some more google searches. I figured out that the best way to do this is to write an event ? I this right ? How du I do this ? Example what I want: GUI with the ListBox ... I'm in class "FileManager" Function "RenameFile" there I want write one line to the ListBox "Hey, I'm renaming files now" or I'm in Class "Communicator" in Function "SendEmail" so I want to add a line to the ListBox "Hey dude, I'm sending a fabilous email" ...
When you call the method RenameFiles you create a new instance of the class called GUI. So you're not using the same form.
You can fix your code by using dependency injection when creating your GUI class so that the FileManager has access without needing to create a new class.
By adding something like this to the FileManager and calling the setter on creation of your GUI.
private GUI gui;
public void SetGUI(GUI g)
{
this.gui = g;
}
A solution is use singleton pattern below an example:
public partial class GUI
{
private static GUI _instance;
public static GUI Instance
{
get { return _instance ?? (_instance = new GUI()); }
}
public void WriteToLog(string msg)
{
//[your code]...
}
}
class FileManager
{
internal static void RenameFiles(string filePath)
{
GUI.Instance.WriteToLog("Moving Files");
// [other code]...
}
}
First of all, here is the simple application I build using C# in Visual Studio 2010, the filename is program.cs, all process will be displayed in command prompt.
public static void Main(string[] args)
{
int input = Convert.ToInt32(Console.ReadLine());
switch (input)
{
case 1:
Console.WriteLine("A");
break;
case 2:
Console.WriteLine("B");
break;
case 3:
Console.WriteLine("C");
break;
default:
Console.WriteLine("default");
break;
}
}
I want to build a GUI to make it more user friendly.
I created a form with a ComboBox, a Label, and a Button. The values in the ComboBox are [1,2,3,default]. I want to let the user select a value in the ComboBox, hit the Button, and the program will update the label to [A,B,C,default].
How can I keep the logic in program.cs, and achieve the above goal?
I created a form and visual studio generate a Form1.cs that looks like this
namespace quickTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}
So I think the problem I ran into is I don't know how program.cs can get/set value of Form1
In Main(), I added Application.Run(new Form1()); so it runs the form instead of command prompt, but then I'm stuck. I tried comboBox1.SelectedValue but I can only get value in From1.cs and not program.cs, I need it to be in program.cs so I can apply the logic.
Just for clarification, this is just a sample I build. The actual program.cs contains a lot more logic but I don't think it's affect what I want to do here so I didn't put it in the description. I need a way to get and set value from program.cs to the form.
I don't believe the best solution is adding a GUI to a console application, but I've been in a similar situation before and was able to do it successfully. The best option would be to refactor the logic into libraries that could be referenced from a separate GUI application.
Create events in the form class and subscribe to them from program.cs to drive the logic that needs to happen. You can pass values to and from the logic with your EventArgs class. That is essentially what you do when you write the code that drives a form, you're just offloading it to a separate class.
Update: Added Example Code
This is basic event-based programming. Through the use of generics we can greatly reduce the amount of boilerplate code, but it would be good to get an understanding of the delegates we're creating automatically through generics. Shortcuts can be a hindrance if you don't understand how they work (or don't) when bugs arise.
Events how to: http://msdn.microsoft.com/en-us/library/w369ty8x.aspx
Generic delegates: http://msdn.microsoft.com/en-us/library/sx2bwtw7.aspx
For an example I created a pair of forms. MainWindow has a textbox OutputBox, and DetachedForm has a combobox OptionComboBox and a button TriggerButton, which we will use to fire the event.
MainWindow Class:
public partial class MainWindow : Form
{
public MainWindow()
{
InitializeComponent();
DetachedForm detachedForm = new DetachedForm();
detachedForm.SelectionMade += new EventHandler<SelectionMadeEventArgs>(detachedForm_SelectionMade);
detachedForm.Show();
}
void detachedForm_SelectionMade(object sender, SelectionMadeEventArgs e)
{
OutputBox.Text = e.ActualSelection;
}
}
DetachedForm Class
public partial class DetachedForm : Form
{
public event EventHandler<SelectionMadeEventArgs> SelectionMade;
public DetachedForm()
{
InitializeComponent();
}
private void OnSelectionMade(SelectionMadeEventArgs e)
{
EventHandler<SelectionMadeEventArgs> handler = SelectionMade;
if (handler != null)
{
handler(this, e);
}
}
private void TriggerButton_Click(object sender, EventArgs e)
{
if (OptionComboBox.SelectedItem != null)
{
SelectionMadeEventArgs args = new SelectionMadeEventArgs(OptionComboBox.SelectedItem.ToString());
OnSelectionMade(args);
}
}
}
public class SelectionMadeEventArgs : EventArgs
{
public SelectionMadeEventArgs(String actualSelection)
{
ActualSelection = actualSelection;
}
public String ActualSelection { get; set; }
}
You can expose a public function or property in Form1.cs that gets/sets the value of the combo box, then in program.cs you can access that function to set or get the combo box.
I want my project to be started through an class instead of a form, is there any way to do this? Or to be more precise is there any good way to make sure that the first class, except Program, that is started isn't a form-class.
I tried to change to my class in Program.main() but it looks like Application.run() needs a ApplicationContext.
I guess that I could change the Program-class to start another class and let that class start the form with Application.run() but I think that it will cause a lot of problem since I don't want the same form to be started first each time and Application.run() have to be used at least once and at most once. So I think it will be hard to keep track of if Application.run() has been used or not.
Another question that might be even more important; Is this a good way to do things in .net? The reason I want to do so is because I want to create some sort of MVC project where the class I want to start with is the controller and all forms I'll use will be views.
A sample implementation of a controller:
public class Controller : ApplicationContext {
public Controller() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
mInstance = this;
}
public Controller Instance { get { return mInstance; } }
public void Start() {
Application.Run(this);
}
public void Exit() {
this.ExitThread();
}
public void CreateView(Form frm) {
Views.Add(frm);
frm.FormClosed += FormClosed;
frm.Show();
}
private void FormClosed(object sender, FormClosedEventArgs e) {
Views.Remove(sender as Form);
// NOTE: terminate program when last view closed
if (Views.Count == 0) Exit();
}
private List<Form> Views = new List<Form>();
private Controller mInstance;
}
You could use it like this:
static class Program {
[STAThread]
static void Main() {
var c = new Controller();
c.CreateView(new Form1());
c.Start();
}
}
Also check out the WindowsFormsApplicationBase class as a good base class for your controller. Nice support for singleton apps and splash screens.
To decide which class should run first, you should simply put in the Main method of your application in that class.
So basically, create a new class, put in the Main method (and remove it from Program.cs) do the logic you need and then launch the window as follows:
[STAThread]
static void FormLauncher()
{
Application.EnableVisualStyles();
Application.Run(new Form1());
}
Form1 is the name of the form that has to be launched.