2

A little new to C#, and approaching something beyond me. Apologies for length.

I have a Windows Form application in Visual Studio C# Express, using the default classes VS spawns. I want to start and stop a Marquee style progressBar from a class other than the default Form1 in which it is declared. These seems surprisingly difficult, I am sure I am missing something important.

My project has the usual classes that Visual Studio auto generates: Form1.cs, Form1.Designer.cs , Program.cs . I added myClass.cs that wants to talk the load bar. I add progressBar1 bar to my form using the designer, setting Style:Marquee.

In Form1.cs' Form() constructor, I write

this.progressBar1.Visible = false;

This works. Intellisense 'sees' progresBar1. code in Form1.cs can see and control progressBar1 declared in Form1.Designer.cs. this makes sense to me.

But the functions which need to start and stop the load bar must live in myClass.cs. I want to be able to code like this, within myClass.cs:

public void myFunction(){
    Form1.progressBar1.visible=true
    //do stuff that takes a bit of time
    Form1.progressBar1.visible=false
}

This does not work. Intellisense cannot 'see' progresBar1 when typing code in myClass.cs. In fact, intellisense cannot 'see' anything in Form1.cs from within myClass.cs. No public propeties or functions added to Form1 ever become visible to intellisense. This does not make sense to me, I am confused. This seems like something you would want to do often and easily.

Some searching indicates that this blocking of external access to Form controls is by design. Something to do with 'decoupling' your logic code from GUI code, which makes sense in principal.So clearly there is an expected approach, yet an clear example is hard to find. I can only find examples of loadbars controlled from entirely within the Forms that declare them, or terse half-examples about creating and registering Events or using Invoke or other things I know too little about. There are many apparent solutions but none that I can see clearly apply to me, or that I am able to implement, in my ignorance.

I think I could do it if my Form were an instance. [EDIT] nope. instance or not, Form1 controls never become exposed outside of Form1.cs

So, How do I to start and stop a Marquee style progressBar from a class other than the default Form1 in which it is declared, in the proper way? Is there a clear and useful example somewhere?

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
Logan Bender
  • 367
  • 1
  • 5
  • 12
  • Where do you create the instance of your myClass? Inside code of Form1? – Steve Mar 13 '14 at 16:56
  • myClass is a separate class , not within Form1. It is a big class and does many things that take time. I used "add class" in the solution explorer to add it. It shares its namespace with Program.cs and all other classes. – Logan Bender Mar 13 '14 at 16:58
  • check the link for this answer [How to Access a Control in Another Form](https://stackoverflow.com/a/38898134/7675357). – Adarsh Sunil Feb 09 '18 at 04:00

3 Answers3

2

You can't access your properties this way:

Form1.progressBar1

because Form1 is a type (not an instantiated object). The only methods or properties you can access with this approach have to be marked as static.

To answer your question of how to communicate, you probably want to use the event approach that you mentioned. First you need an event in your logic class:

public event Action<int> UpdateProgress;

Which is called just like a function:

if (UpdateProgress != null)
   UpdateProgress(10);

This declares a new event using the Action generic delegate, which means the listening function has to return void and take one int as a parameter.

Then in your forms code, you'll have:

MyClass logic = new MyClass();
private void SomeFunction
{       
    logic.UpdateProgress += UpdateProgressBar;
}

private void UpdateProgressBar(int newProgress)
{
    progressBar1.BeginInvoke(new Action(() =>
    {
       progressBar1.Value = newProgress;
    }));
}

This creates a new instance of your logic class, and assigns the function "UpdateProgressBar" to be called whenever your logic class raises the UpdateProgressBar event. The function itself uses Dispatcher.BeginInvoke because your logic class is likely not running on the UI thread, and you can only do UI tasks from that thread.

There is a lot going on here, so please let me know if I can clarify anything for you!

BradleyDotNET
  • 60,462
  • 10
  • 96
  • 117
  • I would start explaining this concept starting from the UpdateProgressBar method and then going back to explain how do you pass the delegate to myClass ending with the call inside the myClass method, but I think you have it right. – Steve Mar 13 '14 at 17:11
  • yes a lot going on, but seems like an important OOP principle in operation. I will see If I can get it to work and then ask questions. – Logan Bender Mar 13 '14 at 17:13
  • ' Dispatcher.' does not exist in the current context. what name space should I be 'using:'? – Logan Bender Mar 13 '14 at 17:16
  • @LoganBender, sorry bit of WPF creep, you want to use the control's BeginInvoke (answer updated). You might be able to call it on the form itself (just BeginInvoke with nothing before) but I believe the control is considered better practice. – BradleyDotNET Mar 13 '14 at 17:23
  • the line 'logic.UpdateProgress += UpdateProgressBar;' is not working, intellisense alerts me : WindowsFormapplication1.Form1.logic is a 'field' but is used like a 'type.' – Logan Bender Mar 13 '14 at 17:34
  • @LoganBender, Sorry, should have specified that the event "registration" has to be in a function. It could be the constructor, Window_Loaded, or a number of other places but it can't go in the class definition. Answer updated to try and reflect this. – BradleyDotNET Mar 13 '14 at 17:37
  • ok. I threw the line into the Form1() constructor, no more intellisense squawking. – Logan Bender Mar 13 '14 at 17:39
  • MyClass.cs was originally using a bunch of static functions, I think this is part of the issue. I am converting things to work with MyClass instantiated... – Logan Bender Mar 13 '14 at 17:41
  • Good plan, large numbers of statics are a "code-smell" that can cause you a lot of problems (as you seem to be discovering). – BradleyDotNET Mar 13 '14 at 17:41
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/49681/discussion-between-logan-bender-and-lordtakkera) – Logan Bender Mar 13 '14 at 18:39
0

I would create a model that has properties matching your form, and pass that around.

So you would make a new class like this...

using Windows.Forms;

public class Form1Model {
    public ProgressBar progressBar { get; set; }
}

Then when you want to get to your other class holding that function you would create an instance of Form1Model, fill it, and call your function

var fm = new Form1Model {
    progressBar = this.progressBar1;
};
otherClass.MyFunction(fm);

now you would have to change your function to accept the new model

public void MyFunction(Form1Model fm){
    // do stuff
}

Another option is just making the function take an instance of the form, and not creating a model, but then you are going to be passing a lot of extra bits you probably won't care about

public void MyFunction(Form1 form){
    // do stuff
}

Then on your form you would call the function like this

otherClass.myFunction(this);

I would recommend the first way over the second, you can control what data is being passed around

mmeasor
  • 459
  • 3
  • 19
  • Definitely the first over the second, you don't want to be passing your whole form object around unless you have to. The only problem I have with this approach is it tightly couples the "logic" class to the UI type. – BradleyDotNET Mar 13 '14 at 17:08
  • i could have got into interfaces and that stuff, but he said he was new to C#, so i tried to keep it simple – mmeasor Mar 13 '14 at 17:08
0

You are trying to access the type Form1 instead of the forms instance. I'll show you, how you can access the instance below.

I assume that Form1 is the applications main form that stays open as long as the application runs. When you create a WinForms application VS creates this code in Program.cs:

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

A simple way to make your main form accessible throughout the application is to make it accessible via a public static property. Change the code like this

static class Program
{
    public static Form1 MainForm { get; private set; }

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        MainForm = new Form1();
        Application.Run(MainForm);
    }
}

In Form1 create a property that exposes the progress bar's visibility:

public bool IsProgressBarVisible
{ 
   get { return this.progressBar1.Visible; }
   set { this.progressBar1.Visible = value; }

}

Now you can make the progress bar visible from any part of the program like this:

Program.MainForm.IsProgressBarVisible = true;

Another way of accessing the main form is, since it is always opened as the first form:

((Form1)Application.OpenForms(0)).IsProgressBarVisible = true;

However, it requires the form to be casted to the right type, since OpenForms returns a Form.


And don't forget: A Form is just a class like any other class. You can do almost everything you can make with other classes. So, communicating with forms is not very different than communication with other objects, as long as you are not using multithreading.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188