How to call an event on another form - C# windows forms

H

hoofbeats95

I don't think this should be this complicated, but I can't figure it
out. I've worked with C# for several years now, but in a web
environment, not with windows form. I have a form with a query button
on it. If the query returns multiple results, a new window is opened
with a grid containing the results. When the user double clicks on
the desired row in the grid, I want the first form to populate with
the correct data. I don't know how to access that other form.
Basically on the double click event, I think I need to call an event
on the other form. How do I do that? A friend that has worked with
windows forms before in VB showed me the WithEvent stuff, but C#
doesn't have that. This form are MDI forms as well - which I am not
familiar with, but both of the forms are children - not the parent.
Any help is greatly appreciated!

Thanks

Jen
 
P

Peter Duniho

[...] If the query returns multiple results, a new window is opened
with a grid containing the results. When the user double clicks on
the desired row in the grid, I want the first form to populate with
the correct data. I don't know how to access that other form.
Basically on the double click event, I think I need to call an event
on the other form. How do I do that? [...]

You need a couple of things: some sort of reference back to the form you
want to update, and some sort of mechanism to accomplish that update.

The most direct method is to access an actual reference for the calling
form in the results form, and have that results window directly access the
fields on the original form that you want to update. There are at least
three variations on this, all somewhat "clunky":

1) Access your calling form directly by name. Eg "form1". Nice and
simple. Not very extensible, but it'll get the job done.
2) Access your calling form by type, passing the form instance
reference to the results window constructor or other mechanism (setting a
property after construction for example). Almost as simple as #1, you can
still access controls by field name, but can be extended to any instance
of the calling form (that is, any form with the specific type).

Note that in 1 and 2, the controls in your form need the access modifier
to be changed to public so that the results form can see them.

3) Access your calling form's control by name, using the
ControlCollection.Find() method to get the controls. Icky-pooh! :) If
you're going to do something like this, IMHO you might as well just define
an interface that the query form exposes, because it's just as much work
and at least an interface is something that can be verified at compile
time.

I don't know what the VB "WithEvent stuff" does, but assuming it's similar
to C# events, then that's actually a better, more-maintainable solution.
You can declare an event on the results form, and then the form with the
query button on it can subscribe to the event. The event would be
designed to allow passing the interesting data back from the results form
to the query form. When the query form's event handler is executed it
would then update the query form directly.

For example:

class Results : Form
{
public delegate void DoubleClickHandler(ResultsData data);
public event DoubleClickHandler DoubleClick;

// Hooked to the double-click event of the grid
void grid_DoubleClick(object sender, EventArgs e)
{
DoubleClickHandler handler = DoubleClick;

if (handler != null)
{
ResultsData data = /* initialize results data to be passed
to calling form */

handler(data);
}
}
}

class Query : Form
{
void buttonQuery_Click(object sender, EventArgs e)
{
Results results = new Results();

results.DoubleClick = HandleResultsDoubleClick;
results.ShowDialog();
}

void HandleResultsDoubleClick(ResultsData data)
{
// extract information from "data" and fill in form
appropriately
}
}

The above assumes you've declared some "ResultsData" data structure
(struct or class, as appropriate) that can contain the data you want to
pass. You don't really have to do that though...you could just pass each
piece of data as an individual parameter in the event.

Note that the results form in this case needs to know exactly *nothing*
about the calling form. The results form declares a standard event that
any caller can subscribe to, and it's up to the caller to process that
event in some way that makes sense to itself.

The above is just an example. The specifics will vary according to your
needs. The key thing is to define the delegate, event, and event handler,
to hook the event handler up in the query form, and to call it at the
appropriate time in the results form.

Some people may complain that I didn't use the standard "(object sender,
EventArgs e)" parameter list used by the standard control-style events. I
think those are fine for the standardized events, but for a custom eventI
don't see the point, _unless that pattern actually fits well in your
design_. For example, your query form needs to know which form is
signaling the event, then you'd want to include an "object sender"
parameter.

Finally, I'll mention that you can also accomplish this by defining an
interface that the calling form is required to implement. As with the #2
solution in the "direct method" described above, you'd pass a reference to
your calling form to the results form. You can use an interface in a
similar way to the events. For example, defining a single method that
takes a ResultsData instance as a parameter and having the results form
call that method. The constructor or property on the results form that is
used to pass the interface could just use the interface type rather than
the original form type.

For example:

class Results : Form
{
public interface IDoubleClick
{
void HandleDoubleClick(ResultsData data);
}

public IDoubleClick DoubleClick;

// Hooked to the double-click event of the grid
void grid_DoubleClick(object sender, EventArgs e)
{
ResultsData data = /* initialize results data to be passed to
calling form */

DoubleClick.HandleDoubleClick(data);
}
}

class Query : Form, Query.IDoubleClick
{
void buttonQuery_Click(object sender, EventArgs e)
{
Results results = new Results();

results.DoubleClick = this;
results.ShowDialog();
}

void HandleDoubleClick(ResultsData data)
{
// extract information from "data" and fill in form
appropriately
}
}

This allows different forms to support the same interface and in some ways
is no different than providing an event handler. The main difference
being that if you use an event, it is trivial to attach multiple event
handlers to the event. An interface reference provides no such
functionality; if you wanted to support multiple event handlers, that
would have to be done explicitly.

Pete
 
J

Jon Davis

What instantiates and displays the two forms? That object should act as a
proxy of information or objects so that one form can "see" the other,
whether by passing a reference of one form to the other, or by implementing
event handlers itself.

Jon
 
C

Christof Nordiek

I don't think this should be this complicated, but I can't figure it
out. I've worked with C# for several years now, but in a web
environment, not with windows form. I have a form with a query button
on it. If the query returns multiple results, a new window is opened
with a grid containing the results. When the user double clicks on
the desired row in the grid, I want the first form to populate with
the correct data.

I'd recommend, that the first form opens the sencond with ShowDialog. The
second Form then will be modal, and the method calling the ShowDialog will
wait until the modal form closes.
The dialog form should have one or more properties, exposing the selected
data. The calling form then can read this properties and populate itself.

By examining the return value of the ShowDialog value or DialogResult
propoertie of the modal form, the calling form can detect, wehter the user
actually selected something or not. The modal form would communicate this by
setting its own DialogResult property, wich autmotically closes the form.

You will have to call Dispose on that modal form or use using, since a form
opend by ShowDialog isn't disposed while closing.

hth
Christof
 
J

Jon Skeet [C# MVP]

I don't think this should be this complicated, but I can't figure it
out. I've worked with C# for several years now, but in a web
environment, not with windows form. I have a form with a query button
on it. If the query returns multiple results, a new window is opened
with a grid containing the results. When the user double clicks on
the desired row in the grid, I want the first form to populate with
the correct data. I don't know how to access that other form.
Basically on the double click event, I think I need to call an event
on the other form. How do I do that?

You can't *call* an event - events themselves just allow you to
subscribe to them and unsubscribe from them. What you need is for the
owner of the event to provide a means (eg a method) which will raise
the event for you when you call it.

See http://pobox.com/~skeet/csharp/events.html for more information.

Jon
 
H

hoofbeats95

Wow. I am totally confused. I appreciate all the replies, but I
still can't figure this out. Maybe if I put in part of my code that
will help? I understand the whole WithEvent in VB, even though I have
never programmed in VB! All I want is when the grid_doubleClick
occurs on QueryResults, to populate the data on frmWaypts. Why is
that so hard? In a C# web application that would be no biggie. I
tried the ShowDialog like someone suggested, but then I have top-level
form parent issues! Again, I never worked with MDI parent-child stuff
before either! So that makes no sense to me either.


My Waypts form looks like this:

public class frmWaypts : System.Windows.Forms.Form
{
public frmWaypts()
{
InitializeComponent();
}

public frmWaypts(string[,] sPKeys, SqlConnection dbCn)
{
//Constructor when Modifying a Runway
//keep primary keys & values
InitializeComponent();
sArrWptKeys = sPKeys;
Wpt = new clsWaypoints(dbCn,sArrWptKeys);
dbConn = dbCn;
}

private void btnQuery_Click(object sender, EventArgs e)
{
//just putting the important pieces here. . .
frmQueryResults newFrmQueryResults = new
frmQueryResults(dtResults, clsDBQC.tWayPt, clsQB, dbConnect);
newFrmQueryResults.MdiParent = this.ParentForm;

//some if. . .else stuff going on
else
{
newFrmQueryResults.Show();
}
}
}


Now the results screen is this:

public class frmQueryResults : System.Windows.Forms.Form
{

public frmQueryResults(DataTable dt, string sFrmType,
clsQueryBuilder clsQBuilder, clsDBConn DBConn)
{
InitializeComponent();

//keep query results table
dtResults = dt;

//bind table to grid
ultraGrid1.DataSource = dt;
ultraGrid1.DataBind();

//keep DB query
clsQB = clsQBuilder;

sFormType = sFrmType;

dbConnection = DBConn.cn;
}

private void ultraGrid1_DoubleClick_1(object sender, System.EventArgs
e)
{

******* What goes here?????

//Currently it does this:
//get primary keys to find desired record & send to
form
string[] sArrayPK = clsQB.GetPrimaryKey(sFormType);
int iLen = sArrayPK.GetUpperBound(0) + 1;
sArrPK = new string[iLen,3];

//loop through primary keys array and build array w/
primary keys & their values
for(int i=0;i < iLen;i++)
{
//Column Name
sArrPK[i,0] = sArrayPK;
//Column Value
sArrPK[i,1] =
Convert.ToString(dtResults.Rows[ultraGrid1.ActiveRow.Index]
[sArrayPK]);
//Column Data Type
sArrPK[i,2] = dtResults.Columns[sArrayPK].DataType.ToString();
}

//load form
OpenForm();
this.Close();
}

//So open form calls a new instance of the original Waypts form
through a few different functions the end result is this code:

frmWaypts newFrmWayPts = new frmWaypts(sArrPK,dbConnection);
newFrmWayPts.MdiParent=this.ParentForm;
newFrmWayPts.Show();
((frmMDI)this.ParentForm).sKeys = sArrPK;


Ok - so i just want to STAY on the same Waypts form! Please - this
can't be that hard right???? What am I missing here??? I hope I
put all the pertinent info in here. I took this application over (new
job) and I actually use to work with the guy that wrote it. Sometimes
he does things totally backwards. But I'm hoping he didn't lock me
into something I can't get out of. Not being able to go from form to
form severely limits capability I would think? I appreciate all
responses! I'm going gray!

Jen
 
P

Peter Duniho

[...] All I want is when the grid_doubleClick
occurs on QueryResults, to populate the data on frmWaypts. Why is
that so hard? In a C# web application that would be no biggie.

It's not in a Windows application either. It's not that hard, and I can't
answer the question as to why you are finding it hard. Perhaps we should
not have offered so much variety in the answers. For my own part, I can
say that my answer was partly driven by your mention of events. Using an
event is indeed the "accepted" way to deal with this sort of thing, but by
no means required. So if that part of my reply is confusing you, just
ignore it, at least for now.
I tried the ShowDialog like someone suggested, but then I have top-level
form parent issues!

What does that mean? "top-level form parent issues!" doesn't really
describe your problem. It's not possible for anyone to help you if you
use vague descriptions of your problems.
Again, I never worked with MDI parent-child stuff
before either! So that makes no sense to me either.

Well, I haven't done MDI since the olden days (Windows 3.x). But way back
then, it was a way to have new windows contained within another window.
Assuming it still works that way, then I don't see how that's relevant to
your question. Sure, you could use MDI, but it doesn't have anything to
do with populating the original form based on a double-click on the
results form. MDI would have more to do with the *appearance* of the
forms than their functionality. You shouldn't be using MDI just to create
a parent/child reference, if the resulting appearance isn't what you
want. There are different, more appropriate ways to do that.

So, all that said, let's see if we can come up with *something* that helps
you. :)

I'm going to post excerpts from you code, changed to do what you want to
do, in the simplest way I can think of. That means not using events.
Even though events are a "cleaner" way to do this, they aren't required
and based on your current design the simplest mechanism is to simply
reference the original form. So here you go:

First, change your query form and constructor to take a reference to the
original form:

private frmWaypts _form; // new field added to frmQueryResults
class

public frmQueryResults(DataTable dt, string sFrmType,
clsQueryBuilder clsQBuilder, clsDBConn DBConn, frmWaypts form)
{
InitializeComponent();

//keep query results table
dtResults = dt;

//bind table to grid
ultraGrid1.DataSource = dt;
ultraGrid1.DataBind();

//keep DB query
clsQB = clsQBuilder;

sFormType = sFrmType;

dbConnection = DBConn.cn;

_form = form;
}

Then instead of this code:

frmWaypts newFrmWayPts = new frmWaypts(sArrPK,dbConnection);
newFrmWayPts.MdiParent=this.ParentForm;
newFrmWayPts.Show();
((frmMDI)this.ParentForm).sKeys = sArrPK;

Do this:

_form.sKeys = sArrPK;

This assumes that the above assignment is the only thing you need to
actually populate the data in the parent form.

This is by no means the only way to do this. If you wanted to take a step
further, you could instead of storing the parent form's reference
explicitly, take advantage of the fact that dialog boxes have a parent
already (passed in to the ShowDialog(IWin32Window) method, or determined
implicitly in the parameterless version of ShowDialog()). Then instead of
storing a reference to a frmWaypts in the _form field, don't bother with
the field and get your reference from the Owner property of your
frmQueryResults form (casting it to frmWaypts):

((frmWaypts)Owner).sKeys = sArrPK;

And of course, you can take it even a step beyond that by using events.
But that's not strictly necessary in this case, and might be more trouble
to you than it's worth, at least in the short run.

Pete
 
H

hoofbeats95

Ok - I think I figured it out. I need to do some major clean up. But
I can get it to work. I used the ShowDialog and managed to mess with
some keys that are stored to get the original form to load again with
the new data. :) Thanks for all your help! I'd still like to
understand the events better though. So I will work on that.
 
B

Bruce Wood

Wow. I am totally confused. I appreciate all the replies, but I
still can't figure this out. Maybe if I put in part of my code that
will help? I understand the whole WithEvent in VB, even though I have
never programmed in VB! All I want is when the grid_doubleClick
occurs on QueryResults, to populate the data on frmWaypts. Why is
that so hard? In a C# web application that would be no biggie. I
tried the ShowDialog like someone suggested, but then I have top-level
form parent issues! Again, I never worked with MDI parent-child stuff
before either! So that makes no sense to me either.

My Waypts form looks like this:

public class frmWaypts : System.Windows.Forms.Form
{
public frmWaypts()
{
InitializeComponent();
}

public frmWaypts(string[,] sPKeys, SqlConnection dbCn)
{
//Constructor when Modifying a Runway
//keep primary keys & values
InitializeComponent();
sArrWptKeys = sPKeys;
Wpt = new clsWaypoints(dbCn,sArrWptKeys);
dbConn = dbCn;
}

private void btnQuery_Click(object sender, EventArgs e)
{
//just putting the important pieces here. . .
frmQueryResults newFrmQueryResults = new
frmQueryResults(dtResults, clsDBQC.tWayPt, clsQB, dbConnect);
newFrmQueryResults.MdiParent = this.ParentForm;

//some if. . .else stuff going on
else
{
newFrmQueryResults.Show();
}
}

}

Now the results screen is this:

public class frmQueryResults : System.Windows.Forms.Form
{

public frmQueryResults(DataTable dt, string sFrmType,
clsQueryBuilder clsQBuilder, clsDBConn DBConn)
{
InitializeComponent();

//keep query results table
dtResults = dt;

//bind table to grid
ultraGrid1.DataSource = dt;
ultraGrid1.DataBind();

//keep DB query
clsQB = clsQBuilder;

sFormType = sFrmType;

dbConnection = DBConn.cn;
}

private void ultraGrid1_DoubleClick_1(object sender, System.EventArgs
e)
{

******* What goes here?????

//Currently it does this:
//get primary keys to find desired record & send to
form
string[] sArrayPK = clsQB.GetPrimaryKey(sFormType);
int iLen = sArrayPK.GetUpperBound(0) + 1;
sArrPK = new string[iLen,3];

//loop through primary keys array and build array w/
primary keys & their values
for(int i=0;i < iLen;i++)
{
//Column Name
sArrPK[i,0] = sArrayPK;
//Column Value
sArrPK[i,1] =
Convert.ToString(dtResults.Rows[ultraGrid1.ActiveRow.Index]
[sArrayPK]);
//Column Data Type
sArrPK[i,2] = dtResults.Columns[sArrayPK].DataType.ToString();
}

//load form
OpenForm();
this.Close();

}

//So open form calls a new instance of the original Waypts form
through a few different functions the end result is this code:

frmWaypts newFrmWayPts = new frmWaypts(sArrPK,dbConnection);
newFrmWayPts.MdiParent=this.ParentForm;
newFrmWayPts.Show();
((frmMDI)this.ParentForm).sKeys = sArrPK;

Ok - so i just want to STAY on the same Waypts form! Please - this
can't be that hard right???? What am I missing here??? I hope I
put all the pertinent info in here. I took this application over (new
job) and I actually use to work with the guy that wrote it. Sometimes
he does things totally backwards. But I'm hoping he didn't lock me
into something I can't get out of. Not being able to go from form to
form severely limits capability I would think? I appreciate all
responses! I'm going gray!


You're right: this won't be very difficult. Here's the solution:

1. Your frmQueryResults should have a public event that it raises
whenever someone double-clicks on an item to be displayed in the
frmWaypts form.
2. frmQueryResults should also expose a public property or method that
returns the item the frmWaypts needs to display.
3. When frmWaypts brings up the frmQueryResults, it should subscribe
to the event:

private void btnQuery_Click(object sender, EventArgs e)
{
//just putting the important pieces here. . .
frmQueryResults newFrmQueryResults = new
frmQueryResults(dtResults, clsDBQC.tWayPt, clsQB, dbConnect);
newFrmQueryResults.MdiParent = this.ParentForm;

//some if. . .else stuff going on
else
{
newFrmQueryResults.ItemSelected += new
System.EventHandler(newFrmQueryResults_ItemSelected);
newFrmQueryResults.Show();
}
}

Then, in frmWaypts:

private void newFrmQueryResults_ItemSelected(object sender,
System.EventArgs e)
{
frmQueryResults qr = (frmQueryResults)sender;
... read the property / call the method in qr to get what is to be
displayed ...
... display the information ...
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top