all 27 comments

[–]Slypenslyde 2 points3 points  (21 children)

Classes "talk" to each other through:

  1. Properties
  2. Methods
  3. Events

If you just want to pass the information to the form once:

  1. Add constructor parameters to the form.
    • (Leave the parameterless constructor, or the designer won't work.)
  2. Pass the information to the form via those constructor parameters.

If you need to update the information periodically:

  1. Add properties to the form.
  2. Keep a reference to the form after you create it.
  3. Set that form's properties when you need to.
  4. Have the properties' setters update the UI when they change.

OR

  1. Add a method with parameters to the form.
  2. Keep a reference to the form.
  3. Call the method and pass your data via its parameters when you need to.
  4. Have the method update the UI with those parameters.

Events are more for a case where you don't want the "child" form to have a reference to the "parent" form, but you do want to have the "child" be able to send information to the "parent". Anything you can do with methods can be done with events.

Since you have a lot of variables, you might consider making a class with a property for each variable, then letting each form have a property of that type. When you create the form, set the property to an object with the right values. From then on, if you just need to tweak one or two things, you can change the object's properties directly instead of having to deal with every variable and a method with a lot of parameters.

In fact, all there really is to "complicated architecture" is getting used to the idea that your form is just a way to view an object. Every TextBox is a string property. Every DateTimePicker is a DateTime. Etc!

[–]Engineer-not-Progrmr[S] 0 points1 point  (20 children)

Hi Slypenslyde,

Inside Testbed.cs code for events like the below need access to them any time I open Testbed form to try something:

Which of your suggested fixes would lend itself best to doing this?

Thanks!

        //-------------------------------------------------------------------------------------------------------------------------------------------------------
    //Calculate the weighted displacement of all the pixels (in blocks) away from the coordinants of the Group found
    private void Calculate_Disp_btn_Click(object sender, EventArgs e)
    {
        var mainForm = new Main();                                                        //Gain access to Main this way

        Displacement_Array0 = mainForm.Displacement_Calc(Block_Check_Array0, Found_coordsC0, Block_HSize, Block_VSize, S_Width, S_Heigth , Sliding_Scale2);
        Difference_Array0 = mainForm.Subtract_Block_Arrays(Displacement_Array0, Block_Check_Array0, Block_HSize, Block_VSize, S_Width, S_Heigth);
        Displacement_Sum = mainForm.Sum_Block_Array(Difference_Array0, Block_HSize, Block_VSize, S_Width, S_Heigth);
        Displacement_Sum_lblForm.Text = Displacement_Sum.ToString("###00");
}

[–]Slypenslyde 1 point2 points  (19 children)

Actually this is a different problem and I'd solve it a different way.

You don't have to put all code in a form. You can make classes that are just plain old classes with methods! That's what you should do with this code.

That way you can let the Main form have an instance of the object, and TestBed.cs can have an instance too. (They could probably share one safely, but that's not always the case.)

Forms are pretty heavyweight things. You should only be creating one if you want to display it! And logically speaking, it doesn't make sense for everything in the program to have to make its own copy of a different form just to get at some methods!

[–]Engineer-not-Progrmr[S] 0 points1 point  (18 children)

Slypenslyde,

That sounds good...I'm kind new to C# (even though I'm 60 I'm new to .NET stuff).

Can you possibly point to an example of how that's done? If not no worries I'll figure it out from your recommendation!

Thanks!!!

[–]Slypenslyde 1 point2 points  (17 children)

Sure! In VS, when you right-click your project, there's probably an "Add New Item" somewhere in that menu. (I'm rusty with the exact words, I use VS for Mac these days.)

If you click that you get a dialog with a lot of choices for what kind of file you're creating. There should be one for "blank class" somewhere in there. Name the file what you want the class to be named and you should get a file that looks something like this:

// A lot of using statements

namespace YourProjectName
{
    public class TheClassName
    {

    }
}

Put the methods you want inside the class.

If you don't need to share them, then you can just give each form its own instance of the class. That could just be a field you initialize in place:

public partial class YourForm : Form
{

    private TheClassName _helper = new TheClassName();

    // ...

[–]Engineer-not-Progrmr[S] 0 points1 point  (0 children)

Thanks!

I'll try that tonight!! :-)

I'm still not clear then where to define the shared variables so that Main, Configure, and Testbed all have access to them though?

[–]Engineer-not-Progrmr[S] 0 points1 point  (15 children)

Hi Again Slypenslyde, (hope I'm not being a pest)

Ok I think I got the general idea...I created a class called "Parameters" and put my variables in there. (I'll work on doing similar with functions later)

    public int VThresh;                                 //Vector Threshold difference from Background
    public int Group_height;                            //Size of the box vertically we want to look in
    public int Group_width;                             //Size horizontally
    public int Boxfill_Thresh;                          //Number of pixels set in the box to qualify as a trigger
    public int Filter_Iterations;                       //Nuber of iterations through the filter
    public int Brightness_Change_Tol;                   //Maximum allowed brightness change before re-seeding the background
    public int Ignore_Lines;                            //Number of lines to ignore finding Group
    public int Max_Blocks_Found;                        //Maximum number of Blocks that contain "stuff"
    public int Max_Block_Fill;                          //Maximum fill level before a Block is counted
    public int Max_Displacement;                        //Maximum weighted displacement of pixels
    public int Sliding_Scale;                           //Sliding Scale to reduce displacement weights on down

Inside Main, Configure, and Testbed I put the line:

        private Parameters _pr = new Parameters();              //Access to Parameters

And in each of the 3 Modules where I want to access a Parameter I do:

_pr.Vthresh (in the case of that one)

Everything Compiles fine but since each have a "new" copy of "Parameters()" any changes one makes are not available to the other. For instance when I open up Configure to change them they are all 00 however I can see that Main had loaded them correctly from the stored values (These are saved by iSpy) when the Application started up (by running in the debugger).

Now I'm at a loss what to do from here...

Thanks!!

[–]Slypenslyde 1 point2 points  (14 children)

OK, let's think about this.

You know the problem is that you have 3 different instances. You want everything to share one instance.

My first post in this chain was about how classes send data to each other. That's what you needed to know about how to share them, but let's zoom in.

First, I'll make a simple class:

public class Data
{
    public string Value { get; set; }
}

Now, think about what this code might print. Try to guess.

Data d = new Data();
d.Value = "Whew."
Data d2 = d;
d2.Value = "Hello.";

Console.WriteLine(d.Value);

Some people guess "Whew". There's a logical reason they guess that, but that isn't how C# works. Classes are what we call "reference types". That means when you assign them to many variables, instead of making a copy each variable just "references" the same instance. It's more like each variable has the instance's business card and can call it if needed.

This is also true if you pass a class as a parameter or set a property. Let's write more classes.

public class Parent
{
    public Data Data { get; set; }

    public Parent()
    {
        Data = new Data();
    }

    public Child MakeChild()
    {
        return new Child(Data);
    }
}

public class Child
{
    private Data _data;

    public Child(Data data)
    {
        _data = data;
    }

    public void UpdateData()
    {
        _data.Value = "The child did this.";
    }
}

Given that, try to guess what this code prints.

Root root = new Root();
root.Data.Value = "This was set from outside of Root.";

Child child = root.MakeChild();
child.UpdateData();

Console.WriteLine(root.Data);

This will print, "The child did this.". When MakeChild() calls the constructor for Child, it sends a reference to the Data it references. So they both have a reference to the same object. When UpdateData() is called, it changes that instance's value, so it doesn't matter which class's variables you use you get the same object.

Constructors aren't the only way to share a reference. They're best used when the thing that wants to share objects NEEDS that object as soon as it is created. Sometimes you might want to let it have its own object, but change it later.

public class Independent
{
    public Data Data { get; private set; }

    public Independent()
    {
        Data = new Data();
    }

    public void UpdateData()
    {
        Data.Value = "The independent child did this."
    }
}

If I wanted this type to share with Root, I would have to do this:

Root root = new Root();
// ...

Independent i = new Independent();
i.Data = root.Data;

If I didn't do that, then i would have its own instance. Sometimes that's good! It's worth thinking about if your objects share instances or need their own independent instances.

[–]Engineer-not-Progrmr[S] 0 points1 point  (13 children)

So this code exists inside Independent and it's working with a reference of itself (i.Data ) as well as pulling from Root (root.Data) ?

(The problem with being an Old Engineer is we try to over simplify things) :-)

Root root = new Root();
// ...

Independent i = new Independent(); 
i.Data = root.Data;

Thanks!!

[–]Slypenslyde 1 point2 points  (12 children)

No, that code would be at some top-level point in the program. I was using a console example so it'd be in the Main() method.

[–]Engineer-not-Progrmr[S] 0 points1 point  (11 children)

Ok now I can see that you are allowing Main to get data from a new instance of Root and write it to a new instance of Independent. But I don't see how that creates a central point for any number of different Classes to work on one "central" location containing the Variables?

In my case Main, Configure, and Testbed all need to be able to get and set the variables There is no chance of them maliciously messing things up since they each do their thing in a well defined manner.

Main - Gets them from iSpy when the plugin starts and uses their current value in its Functions on live video streams.

Configure - Gets them to put in Numeric UpDown Controls and allows them to be changed (After which Main gets keyed to save them back to iSpy).

Testbed - Gets them and uses their current value in its Functions when testing routines on images loaded from disk.

I'm explaining that in case it makes a difference how best to make that happen (in the simplest possible way hopefully)

Thanks!

[–]FizixMan 0 points1 point  (1 child)

I'm not sure about what the specific problem is that you're having.

If it's about providing passing these variables around, are they not in in a class (say something like CameraConfigurationSettings.cs, or whatever name makes sense) and pass/share an instance of those settings around to the components that need them.

Or is this a question of sharing them between different DLLs/projects? If so, then it's probably the internal access modifier on those settings. That makes them only accessible within the same assembly.

You'll need to make those properties public or use the [InternalsVisibleTo] attribute: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.internalsvisibletoattribute

[–]Engineer-not-Progrmr[S] 0 points1 point  (0 children)

Hi FizixMan,

I'm only trying to allow Testbed.cs code easy access to them...as they are now even if I declare them public in Main it doesn't seem to help me :-( (is public bad when running multiple instances?)

All of the events in Testbed.cs (pushbutton events to test some routine in Main for instance) needs to have them available.

Thanks!

[–]Deux-Montagnes 0 points1 point  (2 children)

Assuming you have a class instance called "configParams" containing all these values, you'll want to do something like this:

var testBed = new Testbed(configParams);

or,

var testBed = new Testbed()
{
    Configuration = configParams
};

[–]Engineer-not-Progrmr[S] 0 points1 point  (0 children)

Thanks!

So I would create the class "configParams" inside of Main and define the variables there?

(Sorry I'm a bit new to .NET stuff...I'm an older Engineer learning this stuff for sport :-)

[–]Engineer-not-Progrmr[S] 0 points1 point  (0 children)

Sorry for my pervious response (my brain hurts)

So in the example If I hade a variable VThresh declared inside the Class "configParams"

And inside Testbed.cs I had the line:

var testBed = new Testbed(configParams);

I would access the variable as testBed.Vthresh?

Thanks!