views:

25348

answers:

15

First off, this is a question about a desktop app using Windows Forms, not an ASP.Net question.

I need to interact with controls on other forms. Trying to access the controls by using, for example, the following...

otherForm.Controls["nameOfControl"].Visible = false;

...doesn't work the way I would expect. I end up with an exception thrown from Main. However, if I make the controls public instead of private, I can then access them directly, as so...

otherForm.nameOfControl.Visible = false;

But is that the best way to do it? Is making the controls public on the other form considered "best practice"? Is there a "better" way to access controls on another form?

Further Explanation:

This is actually a sort of follow-up to another question I asked: Best method for creating a “tree-view preferences dialog” type of interface in C#? The answer I got was great and solved many, many organizational problems I was having in terms of keeping the UI straight and easy to work with both in run-time and design-time. However, it did bring up this one niggling issue of easily controlling other aspects of the interface.

Basically, I have a root form that instantiates a lot of other forms that sit in a panel on the root form. So, for instance, a radio button on one of those sub-forms might need to alter the state of a status strip icon on the main, root form. In that case, I need the sub-form to talk to the control in the status strip of the parent (root) form. (I hope that makes sense, not in a "who's on first" kind of way.)

Final Answer (so far):

Note to Jeff Atwood: Can we please "accept" multiple answers? So it seems the best practice would be to either only expose the actual property that's needed, or to handle it through raising events in the form whose controls I need to manipulate. Hard to say which will be best in my case. I will have to develop the application further before I see which will be easier to manage. Many thanks, though, for the speedy replies.

+12  A: 

I personally would recommend NOT doing it.. If its is responding to some sort of action and it needs to change its appearance, I would preference raising an event and letting it sort itself out..

This kind of coupling between forms always makes me nervous. I always try to keep the UI as light and independent as possible..

Hope this helps, perhaps you could expand on the scenario if not?

Rob Cooper
A: 

I would handle this in the parent form. You can notify the other form that it needs to modify itself through an event.

Ed Swangren
+11  A: 

Instead of making the control public, you can create a property controlling its visibility:

public boolean ControlIsVisible
{
     get { return control.Visible; }
     set { control.Visible = value; }
}

This creates a proper accessor to that control that won't expose the control's whole set of properties.

Jon Limjap
+3  A: 

The first is not working of course. The controls on a form are private, visible only for that form by design.

To make it all public is also not the best way.

If I would like to expose something to the outer world (which also can mean an another form), I make a public property for it.

public Boolean nameOfControlVisible
{
    get { return this.nameOfControl.Visible; }
    set { this.nameOfControl.Visible = value; }
}

You can use this public property to hide or show the control or to ask the control current visibility property:

otherForm.nameOfControlVisible = true;

You can also expose full controls, but I think it is too much, you should make visible only the properties you really want to use from outside the current form.

public ControlType nameOfControlP
{
    get { return this.nameOfControl; }
    set { this.nameOfControl = value; }
}
Biri
+2  A: 

After reading the additional details, I agree with robcthegeek: raise an event. Create a custom EventArgs and pass the neccessary parameters through it.

Biri
+1  A: 

@Dylan: I got to agree with rob on this one. Don't couple the forms together, but let them sort it out via events.

@Ed: Please, please don't pass a form down to a child class. This gives you a dependency that will be hell to sort out later. A few weeks ago I was tasked with refactoring an app that did this all over. It even passed forms down to its "Business Layer", if you want to call it that.
It's hard to maintain, and nearly impossible to refactor into something sane later..

Lars Mæhlum
A: 

Do your child forms really need to be Forms? Could they be user controls instead? This way, they could easily raise events for the main form to handle and you could better encapsulate their logic into a single class (at least, logically, they are after all classes already).

@Lars: You are right here. This was something I did in my very beginning days and have not had to do it since, that is why I first suggested raising an event, but my other method would really break any semblance of encapsulation.

@Rob: Yup, sounds about right :). 0/2 on this one...

Ed Swangren
A: 

@Lars, good call on the passing around of Form references, seen it as well myself. Nasty. Never seen them passed them down to the BLL layer though! That doesn't even make sense! That could have seriously impacted performance right? If somewhere in the BLL the reference was kept, the form would stay in memory right?

You have my sympathy! ;)


@Ed, RE your comment about making the Forms UserControls. Dylan has already pointed out that the root form instantiates many child forms, giving the impression of an MDI application (where I am assuming users may want to close various Forms). If I am correct in this assumption, I would think they would be best kept as forms. Certainly open to correction though :)

Rob Cooper
A: 

I agree with using events for this. Since I suspect that you're building an MDI-application (since you create many child forms) and creates windows dynamically and might not know when to unsubscribe from events, I would recommend that you take a look at the "Weak event pattern" at http://msdn.microsoft.com/en-us/library/aa970850.aspx. Alas, this is only available for framework 3.0 and 3.5 but something similar can be implemented fairly easy with weak references.

However, if you want to find a control in a form based on the form's reference, it's not enough to simply look at the form's control collection. Since every control have it's own control collection, you will have to recurse through them all to find a specific control. You can do this with these two methods (which can be improved).

public static Control FindControl(Form form, string name)
{
    foreach (Control control in form.Controls)
    {
        Control result = FindControl(form, control, name);
        if (result != null)
            return result;
    }

    return null;
}

private static Control FindControl(Form form, Control control, string name)
{
    if (control.Name == name) {
        return control;
    }

    foreach (Control subControl in control.Controls)
    {
        Control result = FindControl(form, subControl, name);
        if (result != null)
            return result;
    }

    return null;
}
Patrik
A: 

Are you accessing these controls externally from a separate thread?

Tanerax
A: 

You should only ever access one view's contents from another if you're creating more complex controls/modules/components. Otherwise, you should do this through the standard Model-View-Controller architecture: You should connect the enabled state of the controls you care about to some model-level predicate that supplies the right information.

For example, if I wanted to enable a Save button only when all required information was entered, I'd have a predicate method that tells when the model objects representing that form are in a state that can be saved. Then in the context where I'm choosing whether to enable the button, I'd just use the result of that method.

This results in a much cleaner separation of business logic from presentation logic, allowing both of them to evolve more independently — letting you create one front-end with multiple back-ends, or multiple front-ends with a single back-end with ease.

It will also be much, much easier to write unit and acceptance tests for, because you can follow a "Trust But Verify" pattern in doing so:

  1. You can write one set of tests that set up your model objects in various ways and check that the "is savable" predicate returns an appropriate result.

  2. You can write a separate set of that check whether your Save button is connected in an appropriate fashion to the "is savable" predicate (whatever that is for your framework, in Cocoa on Mac OS X this would often be through a binding).

As long as both sets of tests are passing, you can be confident that your user interface will work the way you want it to.

Chris Hanson
A: 

This looks like a prime candidate for separating the presentation from the data model. In this case, your preferences should be stored in a separate class that fires event updates whenever a particular property changes (look into INotifyPropertyChanged if your properties are a discrete set, or into a single event if they are more free-form text-based keys).

In your tree view, you'll make the changes to your preferences model, it will then fire an event. In your other forms, you'll subscribe to the changes that you're interested in. In the event handler you use to subscribe to the property changes, you use this.InvokeRequired to see if you are on the right thread to make the UI call, if not, then use this.BeginInvoke to call the desired method to update the form.

Garo Yeriazarian
A: 

If you want to access any control from another page do like this- 1 step.let we want to access textbox1(window1) from window2 2 step.in window1- Window2 win2=new Window2(); win2.Win1_to_win2=this; 3 step.in window2- public window2 Win1_to_win2; string text=((window1)this.Win1_to_win2).textbox1.text;

similarly u can access any window control from different window.

A: 

If you want to access any control from another page do like this-
1 step. let we want to access textbox1(window1) from window2
2 step. in window1-
Window2 win2=new Window2();
win2.Win1_to_win2=this;
3 step. in window2-
public window2 Win1_to_win2;
string text=((window1)this.Win1_to_win2).textbox1.text;

similarly u can access any window control from different window.

A: 

You can

  1. Create a public method with needed parameter on child form and call it from parent form (with valid cast)
  2. Create a public property on child form and access it from parent form (with valid cast)
  3. Create another constructor on child form for setting form's initialization parameters
  4. Create custom events and/or use (static) classes

Best practice would be #4 if you are using non-modal forms.

Davorin