Why Doesn't This Work?

J

Jonathan Wood

I'm having problems with the following code, which is an event handler for a
GridView control:

protected void grdWorkout_RowDataBound(object sender, GridViewRowEventArgs
e)
{
if (e.Row.RowType == DataControlRowType.DataRow) // Not header, footer,
etc.
{
System.Data.DataRowView drv = (System.Data.DataRowView)e.Row.DataItem;
if (drv != null)
{
if ((e.Row.RowState & DataControlRowState.Selected) != 0)
e.Row.CssClass = "gridsel";
else if (drv.Row.ItemArray[5].ToString() == "Arm")
e.Row.CssClass = "gridalt";
}
}
}

When the page comes up, it looks fine. But if I select a row in the
GridView, it only appears selected if the 5th column is not equal to "Arm".
Put another way, if I set e.Row.CssClass = "gridalt", it appears that row
will always use that style whether it is selected or not.

So I tried putting similar code in the RowCreated event handler. That also
looks fine when the page comes up. But then when I select a row, it appears
selected, but NONE of the rows then use the gridalt CSS class. The reason is
that drv is null except for when the page is first loaded.

Can anyone make some suggestions? I need to control the row styles because
my alternating style will not occur on every other row. But I can't find any
way to make this compatible with selecting rows.

Thanks.
 
S

Stan

I'm having problems with the following code, which is an event handler fora
GridView control:

 protected void grdWorkout_RowDataBound(object sender, GridViewRowEventArgs
e)
 {
  if (e.Row.RowType == DataControlRowType.DataRow) // Not header, footer,
etc.
  {
   System.Data.DataRowView drv = (System.Data.DataRowView)e.Row.DataItem;
   if (drv != null)
   {
    if ((e.Row.RowState & DataControlRowState.Selected) != 0)
     e.Row.CssClass = "gridsel";
    else if (drv.Row.ItemArray[5].ToString() == "Arm")
     e.Row.CssClass = "gridalt";
   }
  }
 }

When the page comes up, it looks fine. But if I select a row in the
GridView, it only appears selected if the 5th column is not equal to "Arm"..
Put another way, if I set e.Row.CssClass = "gridalt", it appears that row
will always use that style whether it is selected or not.

So I tried putting similar code in the RowCreated event handler. That also
looks fine when the page comes up. But then when I select a row, it appears
selected, but NONE of the rows then use the gridalt CSS class. The reason is
that drv is null except for when the page is first loaded.

Can anyone make some suggestions? I need to control the row styles because
my alternating style will not occur on every other row. But I can't find any
way to make this compatible with selecting rows.

Thanks.

Hi again Jonathan

Just as I posted a response to you on another thread I came across
this one. The answer I suggested there may still work.

From your description of the behaviour and the logic of your code it
looks like the RowState.Selected condition that your are testing for
is not being picked up, i.e. the comparison always returns false. This
may be because the RowState properties have yet to be applied when
RowDataBound is being executed. A better bet is the GridView
SelectedIndex property which should equate to e.Row.RowIndex for the
selected row. That particular GridView property (which is just an
integer) has to exist at the outset. Let us know if I'm right.

Cheers
 
J

Jonathan Wood

Stan,
From your description of the behaviour and the logic of your code it
looks like the RowState.Selected condition that your are testing for
is not being picked up, i.e. the comparison always returns false. This
may be because the RowState properties have yet to be applied when
RowDataBound is being executed. A better bet is the GridView
SelectedIndex property which should equate to e.Row.RowIndex for the
selected row. That particular GridView property (which is just an
integer) has to exist at the outset. Let us know if I'm right.

Well dangit! Now that I try this, I see the RowDataBound event is doing the
same thing that the RowCreated event was doing. That is, e.Row.DataItem is
null on postbacks. I was thinking this wasn't the case in the RowDataBound
handler but that's what it's doing now.

Since the control has no selected row when the page is initially displayed,
and e.Row.DataItem is null on postbacks, I'm not able to test either version
of checking if the row is selected.

This really doesn't seem like it should be this hard.

I appreciate your help. I don't know if you have any more ideas.
 
S

Stan

Stan,


Well dangit! Now that I try this, I see the RowDataBound event is doing the
same thing that the RowCreated event was doing. That is, e.Row.DataItem is
null on postbacks. I was thinking this wasn't the case in the RowDataBound
handler but that's what it's doing now.

Since the control has no selected row when the page is initially displayed,
and e.Row.DataItem is null on postbacks, I'm not able to test either version
of checking if the row is selected.

This really doesn't seem like it should be this hard.

I appreciate your help. I don't know if you have any more ideas.

I was thinking about this problem a bit further after my last post. It
occurs to me that in order to modify the rendering of the control in
such a way that it will not be overridden and at a time when the whole
construction is in memory and stable the PreRender event might be the
answer.
You would need to iterate through the rows of the grid after they have
been loaded but that shouldn't be a problem. It does have a Rows
property which exposes it them all in a collection object.
It makes sense really to do this type of modification at this point
because that is the stage where the grid is ready to convert to client
html. No further changes to styles will imposed by the system and any
you do yourself should stick.

HTH
 
J

Jonathan Wood

Hi Stan,
I was thinking about this problem a bit further after my last post. It
occurs to me that in order to modify the rendering of the control in
such a way that it will not be overridden and at a time when the whole
construction is in memory and stable the PreRender event might be the
answer.

That makes sense.

I really can't believe how much trouble this has given me. As it turns out,
this suggestion actually made it easier for me to determine which rows need
to be a different color. Unfortunately, row.DataItem is still null. But I
worked around that using the DataKeys collection. And that seems okay.

I still had one problem. Once I set the selection style (CssClass =
"gridsel"), that row remains selected until this property was changed again.
Since I was only setting the color of some rows, the selection never went
away from the other rows. But I was able to fix that by setting CssClass to
null.

In the end, the result is messier than I would have preferred, especially
since I need similar logic in other grids. But it does seem to work and I
greatly appreciate your input!

Here's what I ended up with.

protected void grdWorkout_PreRender(object sender, EventArgs e)
{
int category = -1;
int categoryNum = -1;

for (int i = 0; i < grdWorkout.Rows.Count; i++)
{
GridViewRow row = grdWorkout.Rows;
if (row.RowType == DataControlRowType.DataRow)
{
if (row.RowIndex == grdWorkout.SelectedIndex)
row.CssClass = "gridsel";
else
{
if (category != (int)grdWorkout.DataKeys[1])
{
category = (int)grdWorkout.DataKeys[1];
categoryNum++;
}
if ((categoryNum & 0x01) != 0)
row.CssClass = "gridodd";
else
row.CssClass = null;
}
}
}
}

Jonathan Wood
SoftCircuits
http://www.softcircuits.com
 
S

Stan

Hi Jonathan

I couldn't resist the challenge so I had to try it for myself. No
regrets it's surprising what is learned from these exercises.

You are right about the irksome complexity of this. I didn't realise
that the dataitem is null during PreRender. I guess that's because the
underlying data has served its purpose and has been disposed of.

Unfortunately for us we still need it. So the only option is to
recreate it somehow.

At first I resurrected the RowDataBound event to create and store a
purpose built Datatable (just one column) for use later on. That
worked except for the fact that databinding doesn't always happen
during postback, so if you click on a select button in one of the rows
the auxilary DataTable isn't recreated. I got round this by executing
DataBind during the SelectedIndexChanged event.

The other quirk I hadn't realised is that the DataKeys are still there
even though the rest of the row data has gone. That must be kept in
ViewState like all the other row content - probably needed for
updating and deleting. One could I suppose read the controls
themselves for the data but I dislike that approach because it may not
be in a suitable form due to display formatting.

I still wasn't satisfied with my first attempt because it relied on
the order and content of the data in the rows being an exact match for
the auxiliary table. Who knows what might happen if sorting and/or
paging is enabled on the grid! It then occurred to me that since the
datakeys are available the underlying data source could be used to
recover the data for each row based on the primary key - a much more
solid approach.

I reproduce my solution below. Note that the data is different (a
table of imaginary shapes and colors just to play with).

I reduced it to a single coded event in Prerender but my structure is
a bit different to yours. I simplified it by applying all the
CssClasses as though they were unselected and then applying the
selected row CssClass to the only row that needed it (if at all) with
a separate statement. I know the switch statement is necessary with
only two categories but I did that to show how easily it could be
extended to an indefinite number.

(The data is in a SQL table and I used a typed DataSet (.xsd file) to
read the data. The grid uses an SqlDataSource)

protected void GridView1_PreRender(object sender, EventArgs e)
{
DataSet1 ds = new DataSet1();
DataSet1TableAdapters.shapesTableAdapter ta =
new DataSet1TableAdapters.shapesTableAdapter();
ta.Fill(ds.shapes);
ds.shapes.DefaultView.Sort = "shape_id";
string shapeName;
int shape_id, ixs;
foreach (GridViewRow gvr in GridView1.Rows)
{
if (gvr.RowType == DataControlRowType.DataRow)
{
shape_id = (int)GridView1.DataKeys[gvr.RowIndex][0];
ixs = ds.shapes.DefaultView.Find(shape_id);
shapeName = (string)ds.shapes.DefaultView[ixs]
["shape_name"];
switch (shapeName)
{
case "square":
{
gvr.CssClass = "band1";
break;
}
case "circle":
{
gvr.CssClass = "band2";
break;
}
}
}
}
if (GridView1.SelectedIndex >= 0)
GridView1.SelectedRow.CssClass =
"sel";
}
 
J

Jonathan Wood

Hi Stan,
You are right about the irksome complexity of this. I didn't realise
that the dataitem is null during PreRender. I guess that's because the
underlying data has served its purpose and has been disposed of.

Yeah, it seems like this could be made easier in a few ways.
The other quirk I hadn't realised is that the DataKeys are still there
even though the rest of the row data has gone. That must be kept in
ViewState like all the other row content - probably needed for
updating and deleting.

I was thinking it's there for the very reason I'm using it. Seems pretty
handy actually.
I reproduce my solution below. Note that the data is different (a
table of imaginary shapes and colors just to play with).

As it turns out, the data I'm storing in the DataKeys will not be displayed
in my grid. In addition, my data will never need to be sorted or paged. So,
overall, my approach is seeming pretty good to me. As far as simplifying it
to iterate through the list as though there were no selection, and then
simply setting the selection class, I do understand what you are saying
there. Normally, I don't like setting a variable more than once. But by
eliminating the test each time through the loop, that will probably be more
efficient and I'll follow your lead with respect to that.

ADO.NET is my weakest point. If I'm not mistaken, your code would result in
an additional database hit--in addition to the original one that loaded the
grid data. Is that correct? That would definitely be a concern to me.

At this point, my routine is now looking like this. Not sure I can get it
much smaller than that.

protected void grdWorkout_PreRender(object sender, EventArgs e)
{
int category = -1;
int categoryNum = -1;

foreach (GridViewRow row in grdWorkout.Rows)
{
if (row.RowType == DataControlRowType.DataRow)
{
if (category != (int)grdWorkout.DataKeys[row.RowIndex][1])
{
category = (int)grdWorkout.DataKeys[row.RowIndex][1];
categoryNum++;
}
row.CssClass = ((categoryNum & 0x01) != 0) ? "gridodd" : null;
}
}
if (grdWorkout.SelectedIndex != -1)
grdWorkout.Rows[grdWorkout.SelectedIndex].CssClass = "gridsel";
}

Thanks!
 
S

Stan

ADO.NET is my weakest point. If I'm not mistaken, your code would result in
an additional database hit--in addition to the original one that loaded the
grid data. Is that correct? That would definitely be a concern to me.

Yes it does. I doubt it's much of an overhead unless there is a lot of
data to load. However the alternative is to handle the databinding
manually. Retrieve the data and put it in a database table (in my
solution that means move the lines of code that do the db query into
the page_load event). Assign that table as the datasource for the
GridvVew, execute DataBind and then use the PreRender event as before.
The datatable will still be available and only one hit is required.

Anyway I guess we've done this to death now and I'm glad I was able to
help.

Good luck with your project.
 

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