Access form component from another class - c#

I hope that the title and this simple example says everything.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void UpdateLabel(string str)
{
label1.Text = str;
MessageBox.Show("Hello");
}
private void buttonIn_Click(object sender, EventArgs e)
{
UpdateLabel("inside");
}
private void buttonOut_Click(object sender, EventArgs e)
{
MyClass Outside = new MyClass();
Outside.MyMethod();
}
}
public class MyClass
{
public void MyMethod()
{
Form1 MyForm1 = new Form1();
MyForm1.UpdateLabel("outside");
}
}
When I'm trying to change lable1 from MyClass it does nothing. But I can get to the UpdateLable method from outside, it says Hello to me, it just doesn't change the label.

Use a delegate for setting your label
public class MyClass {
Action<String> labelSetter;
public MyClass(Action<String> labelSetter) {
this.labelSetter = labelSetter;
}
public void MyMethod() {
labelSetter("outside");
}
}
.
public void buttonOut_Click(object sender, EventArgs e) {
var outside = new MyClass(UpdateLabel);
outside.MyMethod();
}

a bit unsure because the example actually leaves some bits unclear... but here is a try:
public class MyClass
{
public void MyMethod(Form1 F)
{
F.UpdateLabel("outside");
}
}
this works as long as MyClass is NOT running on a different thread - otherwise the call to UpdataLabel must be synchronized with the UI thread...
EDIT:
private void buttonOut_Click(object sender, EventArgs e)
{
MyClass Outside = new MyClass();
Outside.MyMethod(this);
}

Either go with Yahia's way (it has been updated and will work correctly) or try the following (probably overkill for what you're trying to do... whatever that is).
UPDATE:
Based on your comment in the question, you are also doing the work in MyClass on a different thread. Code change is below.
public partial class Form1 : Form
{
// keep a reference to a MyClass object for your Form's lifetime
private MyClass _myClass;
public Form1()
{
InitializeComponent();
// Intstantiate your MyClass object so you can use it.
_myClass = new MyClass();
// Register to the MyClass event called UpdateLabel.
// Anytime MyClass raises the event, your form will respond
// by running the UpdateLabelFromMyClass method.
_myClass.UpdateLabel += UpdateLabelFromMyClass;
}
private void button1_Click(object sender, EventArgs e)
{
// Call MyMethod in your MyClass object. It will raise
// the UpdateLabel event.
// update, you are calling this on a background thread?
_myClass.MyMethod();
}
void UpdateLabelFromMyClass(string message)
{
// Update your label with whatever message is passed in
// from the MyClass.UpdateLabel event.
// UPDATE: If called from a background thread you'll need this:
this.BeginInvoke( (Action) (()=>
{
label1.Text = message;
}));
}
}
public class MyClass
{
// An event that can be raised, allowing other classes to
// subscribe to it and do what they like with the message.
public event Action<string> UpdateLabel;
public void MyMethod()
{
// Raise the UpdateLabel event, passing "Outside" as
// the message.
UpdateLabel("Outside");
}
}

After wasting a ton of time on what should be a simple task and trying every answer on stack overflow, I said, if C# wants to make it stupid hard to change the text of a simple label, I am going to come up with a stupid fix.
Here is what you do:
In Form1 or whatever form has the label you want add:
public void setStatus()
{
lblStatus.Text = status;
}
public static string status;
Now, add a timer to Form1 and have it run "setStatus();" on every tick
Now, in any class, just write:
Form1.status = "Change label to this text";

you need to make both the method MyMethod and the label in question static. But if you do then you cannot access MyMethod through a new instance of the form instead you have to access it directly like Form1.MyMethod(). But if you do make the label the static visual studio will make it non-static one you access the label from the designer so you will have to keep making it static from form1.designer.cs. Also if you do make the label static change every line that refers to any of its properties so if it says this.label1.Text change it to label1.Text. This should give you the desired effect

Related

Add delegate to Windows Form

Problem: I want to write the same message to a textbox control that I am writing to a log file.
I have a windows form (Form1.cs) that calls a crosscutting class of static methods. In each of the crosscutting methods, they call WriteLogEntry to update a log file of what they are doing. I'd like to send back an event to Form1 so I can write the same log message to a control on the form.
I have looked at events but do not understand enough to make sense of the examples and have not found a simple enough example to do what I want. Can someone show me a simiple example of how to add an event to my code to accomplish this?
namespace MainForm
{
public delegate void MyDel(string str);
public partial class Form1 : Form
{
public event MyDel MyEvent;
public Form1()
{
InitializeComponent();
MyEvent += new MyDel(WriteSomething);
Crosscutting.DoSomething();
}
public void WriteSomething(string message)
{
Console.WriteLine(message);
}
}
//Crosscutting.cs
public class Crosscutting
{
private static void WriteLogEntry(string message)
{
// Code to write message to log file.
}
public static void DoSomething()
{
WriteSomething obj = new WriteSomething();
// Code to do something.
WriteLogEntry("I'm doing something");
}
}
}
After not being able to figure out how to use a delegate to get back to the form, I tried another way. By creating an instance of Form1 on "MyClass", I was able to use a public method to write back to the form. Not the way I wanted, but it is a way to get it done for now. If anyone can explain how to do this a better way, please do so.
public partial class Form1 : Form
{
private string message = string.Empty;
public static Form1 form;
public Form1()
{
InitializeComponent();
form = this;
}
public void UpdateTextBox(string message)
{
textBox1.Text += message + Environment.NewLine;
this.Update();
}
private void button1_Click(object sender, EventArgs e)
{
var myClass = new MyClass();
myClass.DoSomething();
}
}
public class MyClass
{
public void DoSomething()
{
Log("I did something");
}
private void Log(string message)
{
Console.WriteLine(message);
Form1.form.UpdateTextBox(message);
}
}

C# callbacks into windows forms

I'm trying to create a class (in the context of a Windows Application) that can update progress (or send some user message) back to the main form UI via delegates. The problem I have is that the compiler won't allow any of the constructs I attempt because of missing object references. This has been discussed here but no answers had to do with writing to an object on a Form.
in c++ I would do this:
void LogToUI(string s)
{
if(Form1)
Form1->update(s);
}
void TForm1::update(string s)
{
listbox->Items->Add(s);
}
// so that any function, anywhere, can update the listbox (thread safety aside)
in C#:
namespace test
{
public delegate void callback(String msg);
public partial class Form1 : Form
{
public void writeToListbox(String s)
{
listbox.Items.Add(s);
}
public static void writeToConsole(String s)
{
System.Console.WriteLine(s);
}
public void createclass
{
callback ui_update = writeToConsole; // this is ok
callback ui_update = writeToListbox; // not allowed
someclass SC = new someclass(ui_update);
}
}
class someclass
{
callback cb;
void someclass(callback T)
{
this.cb = T;
}
void logthis(string s)
{
cb("it's me!");
}
}
}
I understand the problem with having to assign a static method to the delegate, and the Form1 method is non-static. I would like to use the delegate method because it seems the cleanest; I just can't find a way to write this in such a way as to make it work, short of passing a pointer back to the Form, which seems messy.
I believe I just came across the answer. You have to expose a static reference to a UI object, in this case a ListBox. Then you can assign the callback delegate to a function that makes sure the listbox reference is not null. You just need to make sure you assign the static reference when the form is created:
namespace test
{
public delegate void callback(String msg);
public partial class Form1 : Form
{
public static ListBox callbackListBox; // add this
public void writeToListbox(String s)
{
if(null == callbackListBox)return; // add this check
// also make this threadsafe:
if (callbackListBox.InvokeRequired)
{
callbackListBox.Invoke(new MethodInvoker(() => { writeToListbox(s); }));
}else{
callbackListBox.Items.Add(s);
callbackListBox.TopIndex = callbackListBox.Items.Count - (callbackListBox.Height / callbackListBox.ItemHeight);
}
}
public static void writeToConsole(String s)
{
System.Console.WriteLine(s);
}
public void createclass
{
callback ui_update = writeToListbox; // now OK
someclass SC = new someclass(ui_update);
}
// and add this to the form's constructor:
public Form1()
{
InitializeComponent();
callbackListBox = listbox1;
}
}
class someclass
{
callback cb;
void someclass(callback T)
{
this.cb = T;
}
void logthis(string s)
{
cb("it's me!");
}
}
}
I still have to try this, but at least the compiler is not complaining.

Update a winform from an Interface callback from another class and thread

I have an winform and an interface callback that continuously sends updates. I want to be able to update a label1.Text from the callback interface. However since the intrface runs on an seperate thread I do not think i can do it directly so I was trying to use a delegate and invoke.
However I am running into an error:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created - at
form1.Invoke(form1.myDelegate, new Object[] { so.getString() });
Here is the full code.
public partial class Form1 : Form
{
MyCallBack callback;
public delegate void UpdateDelegate(string myString);
public UpdateDelegate myDelegate;
public Form1()
{
InitializeComponent();
myDelegate = new UpdateDelegate(UpdateDelegateMethod);
callback = new MyCallBack(this);
CallBackInterfaceClass.SetCallBack(callback);
callback.OnUpdate();
}
public void UpdateDelegateMethod (String str)
{
label1.Text = str;
}
}
class MyTestCallBack : Callback
{
public Form1 form1;
public SomeObject so;
public MyTestCallBack(Form1 form)
{
this.form1 = form;
}
public void OnUpdate(SomeObject someobj)
{
so = someobj;
OnUpdate();
}
public void OnUpdate()
{
form1.Invoke(form1.myDelegate, new Object[] { so.getString() });
}
}
Two questions.
Can anyone explain what I am doing wrong?
Is this actually best method to do this?
Here is the answer based on the reply by bokibeg (see below) with a couple of modifications to make it work:
public partial class Form1 : Form {
MyTestCallBack _callback;
public Form1()
{
InitializeComponent();
_callback = new MyTestCallBack();
_callback.MyTestCallBackEvent += callback_MyTestCallBackEvent;
_callback.OnUpdate();
}
private callback_MyTestCallBackEvent(MyTestCallBackEventArgs e)
{
if (InvokeRequired)
{
Invoke(new Action(() =>
{
callback_MyTestCallBackEvent(sender, e);
}));
return;
}
label1.Text = e.SomeObject.GetDisplayString();
}
class MyTestCallBackEventArgs : EventArgs
{
public SomeObject SomeObj { get; set; }
}
class MyTestCallBack : Callback
{
public event EventHandler<MyTestCallBackEventArgs> MyTestCallBackEvent;
private SomeObject _so;
protected virtual void OnMyTestCallBackEvent(MyTestCallBackEventArgs e)
{
if (MyTestCallBackEvent != null)
MyTestCallBackEvent(this, e);
}
public void OnUpdate(SomeObject someobj)
{
_so = someobj;
OnMyTestCallBackEvent(new MyTestCallBackEventArgs { SomeObject = _so });
} }
Here's what I'd do:
public partial class Form1 : Form
{
MyTestCallBack _callback;
public Form1()
{
InitializeComponent();
_callback = new MyTestCallBack();
_callback.MyTestCallBackEvent += callback_MyTestCallBackEvent;
_callback.OnUpdate();
}
private callback_MyTestCallBackEvent(MyTestCallBackEventArgs e)
{
// TODO: Invoke code here with e.g. label1.Text = e.SomeObj.ToString()...
}
}
class MyTestCallBackEventArgs : EventArgs
{
public SomeObject SomeObj { get; set; }
}
class MyTestCallBack : Callback
{
public event EventHandler<MyTestCallBackEventArgs> MyTestCallBackEvent;
private SomeObject _so;
public MyTestCallBack()
{
//
}
protected virtual void OnMyTestCallBackEvent(MyTestCallBackEventArgs e)
{
if (MyTestCallBackEvent != null)
MyTestCallBackEvent(e);
}
public void OnUpdate(SomeObject someobj)
{
_so = someobj;
OnMyTestCallBackEvent(new MyTestCallBackEventArgs { SomeObject = _so });
}
}
It separates the GUI logic from whatever that thread is doing. It fires an event and it's Form's duty to do whatever it pleases with it.
I'm not sure if this compiles, I wrote it in text pad. Tell me if you have questions.
You probably just learned about delegates and got carried away with it, this is similar as it uses an event but the event is properly placed in the "back end" logic - form may or may not use it. Also notice how form's code is a lot better, it doesn't have so much boilerplate code just to implement some background service class.
Note however that MyTestCallBackEvent event may keep firing even after you close the form so make sure you unsubscribe from it when the form closes or disposes (or whenever you feel like form doesn't need it anymore).
Oh and I almost forgot: the original error you were getting is because you called Invoke when one wasn't required and Form definitely wasn't ready for it. Read this question to see how to safely invoke controls.

Calling functions between different classes

I am used to writing embedded c and poorly skilled in c#.
My problem is that I want to be able to run the function openAnotherForm() from Welcome_Form and right now the code does not work. I patiently tried different things but only managed to push my frustration.
I simplified my relevant code to illustrate the problem.
File 1 - This will run and open file 2.
class UIcode
{
private Welcome_Form Welcome;
private AnotherForm_Form AnotherForm;
public UIcode()
{
Welcome = new Welcome_Form();
Application.Run(Welcome);
}
public void openAnotherForm()
{
Welcome.Hide();
AnotherForm = new AnotherForm_Form();
AnotherForm.ShowDialog();
}
}
File 2 - When I click TheButton, the program should run the function openAnotherFrom from file 1.
public partial class Welcome_Form : Form
{
public Welcome_Form()
{
InitializeComponent();
}
private void TheButton_Click(object sender, EventArgs e)
{
// Function from file 1
UIcode.openAnotherForm();
}
}
I realize the problem might be quite trivial but I would still be grateful for an explanation on how to do this.
Preferable: The functions from UIcode should only be recognized by classes specified by UIcode.
You can change the constructor to take a reference to the instance of UIcode that opened it:
private static UIcode myParent;
public Welcome_Form(UIcode parent)
{
myParent = parent;
InitializeComponent();
}
Now in UIcode:
public UIcode()
{
Welcome = new Welcome_Form(this);
Application.Run(Welcome);
}
And finally, back in Welcome_Form:
private void TheButton_Click(object sender, EventArgs e)
{
// Function from file 1
myParent.openAnotherForm();
}
Your openAnotherForm() method is not static, so it needs an instance reference in order to be used. Either instantiate a UICode object, or mark the method as static.
You to create an instance of the class in File1 to call the method. You've called the class UICode, so the constructor should be renamed from public UserInterface() to public UICode().
class UIcode
{
private Welcome_Form Welcome;
private AnotherForm_Form AnotherForm;
public UIcode() // Renamed Constructor
{
Welcome = new Welcome_Form();
Application.Run(Welcome);
}
public void openAnotherForm()
{
Welcome.Hide();
AnotherForm = new AnotherForm_Form();
AnotherForm.ShowDialog();
}
}
public partial class Welcome_Form : Form
{
public Welcome_Form()
{
InitializeComponent();
}
private void TheButton_Click(object sender, EventArgs e)
{
// Create an instance UICode
UICode instance = new UICode();
// Call the method from the instance, not from the class.
instance.openAnotherForm();
}
}
Alternatively, you can make openAnotherForm() a static method, but you'll also need to make the instance variables (Welcome and AnotherForm) static. You will also need to initialize them, but you can do that by making the constructor static as well.
class UIcode
{
private static Welcome_Form Welcome;
private static AnotherForm_Form AnotherForm;
public static UIcode() // Renamed Constructor
{
Welcome = new Welcome_Form();
Application.Run(Welcome);
}
public static void openAnotherForm()
{
Welcome.Hide();
AnotherForm = new AnotherForm_Form();
AnotherForm.ShowDialog();
}
}

Passing data to a non-static listBox, by calling function from another class

I have a simple forms program that I have been fighting with for a while now. I simply want to be able to call a method from a different class file (when a certain step is triggered in the code in that class file) in order to insert a string in the listBox.
Here is my main method, pretty standard:
class Program
{
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
Here is the function which resides in my MainForm.cs file, which I can call just fine from that class file (via 'TextToBox(myString);'):
public partial class MainForm : Form
{
...
// Function to output results to main Listbox window
public void TextToBox(string aString)
{
// Place messages in Main Display list box window
this.listBox1.Items.Insert(0, aString);
}
...
}
But my problem is when I am in another class and I want to call 'TextToBox(myString);'. If I create another object reference of the MainForm, the code compiles fine but nothing will show up in the listBox. How do I do this? I cannot simply make TextToBox() static. I know I must create the object reference but I can't figure out how to reference the ORIGINAL MainForm, the one that was created in the Main method. Thanks in advance...
This will work, but only when you have one instans of MainForm.
public class MainForm : Form
{
public MainForm()
{
Instance = this;
}
public static MainForm Instance { get; private set; }
// Function to output results to main Listbox window
public void TextToBox(string aString)
{
// Place messages in Main Display list box window
this.listBox1.Items.Insert(0, aString);
}
}
public class Other
{
public void AddTextToListBox()
{
MainForm.Instance.TextToBox("Test");
}
}
...Edit...
Alternative:
class Program
{
public static MainForm MainFormInstance;
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
MainFormInstance = new MainForm();
Application.Run(MainFormInstance);
}
}
public class Other
{
public void AddTextToListBox()
{
Program.MainFormInstance.TextToBox("Test");
}
}
I would just pass a delegate to the other class.
/* this runs (previous code was not guaranteed to run) */
class OtherClass
{
public delegate void TextToBox(string s);
TextToBox textToBox;
int next = 0;
public OtherClass(TextToBox ttb)
{
textToBox = ttb;
}
public void SendSomeText()
{
textToBox(next.ToString());
next++;
}
}
I'm assuming you'll be instantiating OtherClass from MainForm. Is this how you're calling "OtherClass"?
public partial class MainForm : Form
{
OtherClass otherClass;
public MainForm()
{
/* just two controls -- listBox1 and button1 */
InitializeComponent();
otherClass = new OtherClass(this.TextToBox);
}
public void TextToBox(string aString)
{
listBox1.Items.Add(aString);
}
private void button1_Click(object sender, EventArgs e)
{
otherClass.SendSomeText();
}
}
On a button click the next numeric value is added at the beginning of the ListBox. You'll have to post some of your code if you need further help.
alternatively you could use a singleton pattern, or static methods and make sure you include the class in a 'using' statement at the top of your program

Categories