UserControl
(1)
Windows
(1)
User
(1)
Control
(1)
ActiveForm
(1)

How do I update a MainForm label from within a UserControl?

29-May-06 06:36 PM
Using C#, I have a Windows form app (MainForm) which contains a label (MainFormLabel) and a composite User Control (UC1).  UC1 consists of a groupbox containing two buttons: UCButton1 and UCButton2.



What I want is to click UCButton1 and have MainFormLabel's text show "Button1", and MainFormLabel's text to show "Button2" when UCButton2 is clicked.



I can't get this to work -- I'm unable to reference MainFormLabel from the UC1.cs code.  At best I get an "Object reference not set to an instance of an object" exception, despite using every conceivable variation of MainForm.ActiveForm... in the UC1.cs code.



--

Take care,

Ken

(to reply directly, remove the cool car. <sigh>)

How do I update a MainForm label from within a UserControl?

29-May-06 04:58 PM
Since your user control is (theoretically, at least) a reusable



component, it shouldn't contain a direct reference to MainForm.



The correct way to do this is using events. Your user control should



expose either one or two events depending upon what the button presses



mean. I'll describe the one-event case first.



1. If the two buttons represent two different answers to the same



question, then your user control should expose a property and an event.



The event should be a simple event that just takes System.EventArgs,



and just indicates that "the user responded". You could call the event:



public System.EventHandler UserReponded;



You would then have a property to indicate how the user responded:



public bool UserReponse



{



get { return this._response; }



}



Each button in your user control would then set the _response flag and



raise a UserResponded event.



Your MainForm would then subscribe to the UserResponded event. The



event handler in the MainForm would then read the UserResponse from the



relevant user control and set the text box contents appropriately.



However, there's another possibility, and that's a scenario in which



the two buttons represent completely different actions that the user



wants to take, not really two answers to the same question. A nicer



design in that case is to have your UserControl expose two events:



public System.EventHandler UserCanceled;



public System.EventHandler UserChangedBackgroundColor;



or some such thing. Your MainForm would then subscribe to both events



and do the right thing in each case.



For your simple example: setting the text box contents, I would go for



the first option: an event and a property. However, as I mentioned, it



doesn't fit all problems.



The basic idea here is to hide the structure of the user control. Say,



for example, the two buttons are two different answers to the same



question. If tomorrow you decide on a different design, and want to



show the user a radio button pair and a button to press, then what? If



you expose events like Button1Clicked and Button2Clicked then you have



to fix your MainForm, too. If, on the other hand, you expose events and



properties based on what the user control is _for_, rather than how it



is _built_, then you shouldn't have to change anything on the outside



if you change the way the user control looks.



As well, because it's the MainForm's responsibility to subscribe to the



user control's events, you can drop as many user controls on a form as



you like, or use the user control on as many forms as you like, and the



user control itself doesn't need to understand the context in which



it's being used. It just notifies "the outside world" when something



happens.

How do I update a MainForm label from within a UserControl?

30-May-06 05:25 PM
Thanks for the explanation.  Raising an event is what I need, since my MainForm will need to update the appropriate label according to which tab page is selected.  I followed the code from the SDK documentation to add the events and subscriptions, but it's not working.  Here's my test case:



MainForm.cs:



namespace test01 {

public partial class MainForm : Form {

public void ReceiveBIEvent(object sender, EventArgs e) {

MessageBox.Show("It worked!");

}



public void SubscribeBIEvent(BIGroup BISource) {

test01.BIGroup.SendButtonClick temp = new test01.BIGroup.SendButtonClick(ReceiveBIEvent);

BISource.BIButtonClicked += temp;

}

}

}





BIGroup.cs:



namespace test01 {

public partial class BIGroup : UserControl {

private void button1_Click(object sender, EventArgs e) {

RaiseBIEvent();

}



public delegate void SendButtonClick(object sender, EventArgs e);

public event SendButtonClick BIButtonClicked;



private void RaiseBIEvent() {

// Safely invoke an event:

SendButtonClick temp = BIButtonClicked;



if (temp != null) {

temp(this, new System.EventArgs());

}

}

}

}





It appears that the RaiseBIEvent() method can't see that MainForm has an event handler for the event -- when I step through the application with the debugger, the "if (temp != null)" exits the RaiseBIEvent() method and the event is never fired.  Any ideas?



--

Take care,

Ken

(to reply directly, remove the cool car. <sigh>)

How do I update a MainForm label from within a UserControl?

30-May-06 03:43 PM
Where in your MainForm do you call SubscribeBIEvent() ?  If it is never



called, then MainForm will never subscribe to the event, so there will



be no subscribers, and you'll see exactly the behaviour you describe.
How do I update a MainForm label from within a UserControl?
30-May-06 09:50 PM
OK, the docs never mentioned calling SubscribeBIEvent().  Now, my problem is that wherever I try to call it I get an "Object not set to an instance" error.  Where and how should I call that subscribe method?



--

Take care,

Ken

(to reply directly, remove the cool car. <sigh>)
How do I update a MainForm label from within a UserControl?
30-May-06 08:22 PM
If I were you I would do it the easy way, using the Visual Studio



designer.



I assume that you've dropped at least one of your BIGroup user controls



onto your MainForm using the Designer, right? So your MainForm has a



BIGroup to which to listen.



In the Designer, right-click on the BIGroup that you placed on the



MainForm, and choose Properties. At the top of the property grid there



should be a lightning bolt symbol (this is VS2003... I assume that



VS2005 is similar). Click on this, and you should see a list of names



of events that your user control can raise. Find the BIButtonClicked



event in the list.



Beside the BIButtonClicked name, if you click in the empty space you



should get a combo box arrow. If you click on it, you should see



ReceiveBIEvent as one of the choices. Choose it.



Save your MainForm and recompile. That should be it.



In case this doesn't work, or isn't clear, you can do it the



long-handed way. In the constructor for MainForm, just call



SubscribeBIEvent and pass the name of the BIGroup that you placed on



MainForm:



public MainForm()



{



... other stuff that the Designer puts in here...



SubscribeBIEvent(this.BIGroup1);



}



I'm not 100% sure of the way that VS2005 writes code using partial



classes... I'm still on VS2003, myself. However, the theory is sound:



you have to subscribe to the event in the constructor, or in some event



that happens as the form is being loaded... (the Load event handler is



another good choice).
How do I update a MainForm label from within a UserControl?
31-May-06 12:06 AM
Ah, nothin's ever easy....








Actually, no.  Two of my reasons for using User Controls is to add them 1) automatically when the form is initialized, and 2) when the user clicks a button.



I *have* found where to call the SubscribeBIEvent() method, though: in the method I use to add the User Control.  It goes something like this:



private void AddUserControl() {

Control BIGroup_tp1 = new BIGroup();

panel1.SuspendLayout();

panel1.Controls.Add(BIGroup_tp1);

// do a lot more stuff here.

panel1.ResumeLayout();

SubscribeBIEvent((BIGroup)BIGroup_tp1);

}



The final problem was a compiler error about a Contol not being a BIGroup (?!), which I solved by doing the cast.  Thanks again for all your help.  These User Controls have a lot of potential, but I'm finding that manipulating them through the code can be onerous.



--

Take care,

Ken

(to reply directly, remove the cool car. <sigh>)
How do I update a MainForm label from within a UserControl?
31-May-06 09:32 AM
Tweak your code to get rid of the casts:



private void AddUserControl() {



BIGroup BIGroup_tp1 = new BIGroup();



panel1.SuspendLayout();



panel1.Controls.Add(BIGroup_tp1);



// do a lot more stuff here.



panel1.ResumeLayout();



SubscribeBIEvent(BIGroup_tp1);



}



I think you'll find that UserControls are the easiest way to do this.



It may be difficult, but the other ways are worse. :-)



By the way, I usually don't write a whole separate method just to



subscribe to an event. As well, there's really no need for the



ReceiveBIEvent event type, since it's just a System.EventHandler at



heart. I would have written it more like this:



namespace test01 {



public partial class MainForm : Form {



private void ReceiveBIEvent(object sender, EventArgs e)



{



MessageBox.Show("It worked!");



}



private void AddUserControl() {



BIGroup BIGroup_tp1 = new BIGroup();



panel1.SuspendLayout();



panel1.Controls.Add(BIGroup_tp1);



// do a lot more stuff here.



panel1.ResumeLayout();



BIGroup.tp1.BIButtonClicked += new



System.EventHandler(ReceiveBIEvent);



}



}



}



BIGroup.cs:



namespace test01 {



public partial class BIGroup : UserControl {



private void button1_Click(object sender, EventArgs e)



{



RaiseBIEvent();



}



public event System.EventHandler BIButtonClicked;



private void RaiseBIEvent() {



// Safely invoke an event:



System.EventHandler temp = BIButtonClicked;



if (temp != null) {



temp(this, new System.EventArgs());



}



}



}



}
How do I update a MainForm label from within a UserControl?
31-May-06 05:09 PM
Done.  Thanks.







Yep.  UserControls are a more efficient and elegant solution so far and I really want to use them, but I still have to test a couple of other use cases before I commit to using them in this app.  I suspect serialization is going to be another hassle....









I wondered about that when reading the Subscribe Event docs.  It seemed overly complicated when all that was needed was something as simple as setting an event handler for, say, a TextBox.TextChanged event.  I've implemented your suggestion and it's working well.  Thanks again.



--

Take care,

Ken

(to reply directly, remove the cool car. <sigh>)
How to...
29-May-06 11:40 AM
.... make COM exposed object to have



both constructors COM visible not only



the first one?
Post Question To EggHeadCafe