« Defining data consistency in an object worldShooting your screen - a plug for SnagIt »

WinForms controls and the red X

23/03/05

Permalink 07:39:43 pm
Categories: Programming, .NET

WinForms controls and the red X

Most people working with WinForms have probably encountered that red X that is drawn over a control at some point and just doesn't go away as long as the application is running. Originally, I had a look at the source of this some months ago and now, when I saw a relating question again, I thought I might document my findings here.

Note that I did that research with .NET 1 and I haven't checked for .NET 2 yet, so in the latter case YMMV.

So where does the red X come from? Simple: The System.Windows.Forms.Control has an internal state flag for this that gets set when an exception is thrown in the control's drawing code. So if you've never seen the red X but you want to, just throw a panel on a form and create a Paint event handler like this:

private void panel1_Paint(object sender, System.Windows.Forms.EventArgs e) {
  throw new Exception("Boom");
}

Now, the really interesting thing about the red X is that you can't easily get rid of it once it's popped up. The only "official" way is to restart the application. Lucky though that .NET has powerful reflection... that makes it possible to use the following method to reset the state:

void ResetExceptionState(Control control) {
  typeof(Control).InvokeMember("SetState", BindingFlags.NonPublic |
    BindingFlags.InvokeMethod | BindingFlags.Instance, null,
    control, new object[] { 0x400000, false });
}

So you can get that panel in the example above to have another go at drawing itself by going

ResetExceptionState(panel1);
panel1.Invalidate(); // invoke redraw

Of course, if the same exception is still thrown from the paint handler, there won't be much to see as the state is immediately set back to show the X again.

Generally, of course, you should have a very close look at the reason why there's an exception thrown in the paint handling code at all. But there are situations where you might want to control the Control's behaviour in detail and in these cases it's nice to be able to handle that internal state yourself.

Two additional notes:

  1. You do need certain permissions to get that reflection code to run. If you want to configure your application for exact permission sets, you should use [ReflectionPermission(SecurityAction.Demand, MemberAccess=true)] in front of the ResetExceptionState method.
  2. As I got a request for this at the time, I have a translation of the method in VB.NET, too. I don't usually use VB, so there may be more elegant ways to do this, but here goes:
Private Sub ResetExceptionState(ByVal control As Control)
  Dim args() As [Object] = {&H400000, False}
  GetType(System.Windows.Forms.Control).InvokeMember("SetState", _
  BindingFlags.NonPublic Or BindingFlags.InvokeMethod Or _
  BindingFlags.Instance, _
  Nothing, control, args)
End Sub 

17 comments

Comment from: Yiannis [Visitor] Email
YiannisI am facing the same problem when trying to open a form that contains a datagrid from
a .NET Visio Add-In.The datagrid appears with a Red X inside.

If i use the same form but from a windows application the grid loads correctly.
Any ideas,suggestions will be most welcome...

Many Thanks
06/13/05 @ 11:14
Comment from: Oliver Sturm [Visitor] Email
Oliver SturmWell, I think there may be not much you can do in this situation, because if the control in question is a third-party one, you might not be able to catch the exception that's responsible for the red X to show up.

I'm also not getting the distinction you make: "a form that contains a datagrid" and "using the same form from a Windows application"? What's the difference? How were you "using" the form in the first case, where the X shows up?
06/13/05 @ 15:11
Comment from: Yiannis [Visitor] Email
YiannisThe control is not a third party but the usual datagrid.
What i am doing is that i am creating a windows form and drag a datagrid into it.

If i add this form into a windows application and execute the app the grid opens fine (with no data).
When i am trying to open the form from a .NET Visio or excel Add-In i get the Red X.

Below is the source code from the visio add-in.

public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
{

applicationObject = application;
addInInstance = addInInst;
Form1 frm=new Form1();
frm.ShowDialog();
}

In form1 i have simply added the default datagrid with no additional code

Thanks in advance,
Yiannis



06/13/05 @ 15:39
Comment from: Oliver Sturm [Visitor] Email
Oliver SturmOkay, now I understand what you're saying :-) I thought you were doing things the other way round, that's why I was referring to a third-party control.

I've tried to do the same thing you're doing, but it seems to work fine for me. I'm sorry, but I have no idea which specific circumstances might cause an exception to show up in your case.
06/14/05 @ 18:20
Comment from: Walter [Visitor] Email
WalterHi,
thanks for your insight to the "Red X" problem.

I have situations where I get the "Red X" very often, only minutes after the application starts.

I'm using 3rd-party products etc. and most of the time the Red X happens completely random and out of my code.

No exception handler will catch the exceptions, just a Red X appears and so far only a reboot is a solution - which is very, very bad.

So, do you know of a way to *avoid* the Red X? There should be an exception, ok, but the Red X destroys the UI and thats a bad thing.

Is it possible to configure .NET to *not* draw a Red X?

Any hints greatly appreciated!
Walter
06/29/05 @ 18:56
Comment from: Oliver Sturm [Visitor] Email
Oliver SturmHi Walter - no, there's no way to simply suppress the Red X completely. It's .NET's way of showing you that an error has occurred in a place where it influences the on-screen representation, and that this error wasn't caught. The only solution to this is to find the error and fix it, or to catch the resulting exception.

The question really is simple: does it (the X) appear on one of your own controls (in which case you have a bug in your drawing code) or does it appear on one of the third-party controls (in which case they have a bug in their drawing code)? You should try tracking that down, maybe also try isolating the issues by testing the controls that are used in your application in smaller test scenarios.

If you are able to scale your application down to a small test and you still see the problem, and you have no idea what it might be about... if you want, I'll have a look at it and see if I can figure anything out. But don't send me largish stuff with lots of external references, please!
06/29/05 @ 19:08
Comment from: Walter [Visitor] Email
WalterThanks for your answer. The problems are:

+ the Red X appears randomly, there is no clear workflow that produces it
+ it appears in 3rd party controls and/or on .NET controls
+ the 3rd party says, "give me a sample"... but, no way, see 1st remark

I'm thinking of fetching the exceptions and then try to "correct" the
problem with your code ("SetState").

Is there a way to check the state of a control, to see if there is a Red X
drawn on it ("GetState")?

So I could check my controls and force a repaint on the broken ones.

Thanks for your help,
Walter
06/29/05 @ 21:24
Comment from: Oliver Sturm [Visitor] Email
Oliver SturmWalter - yes, there is a method bool GetState(int flag) on the System.Windows.Forms.Control as well, so it's possible to query the information in the same way, using Reflection. If you're interested in that kind of thing, you should really get hold of Lutz Roeder's Reflector here and have a look at the implementation of these details.

The other thing, which maybe I shouldn't say, is this: if you were working for me, I'd tell you to go find that problem! As you say, it happens on your own controls as well, but even if it wasn't, the only real chance to get rid of these issues is to find an isolated test case that can reproduce them reliably. Sorry if this sounds harsh, but it's that simple: if you haven't found that test case yet, you haven't looked hard enough. Just my opinion, of course.
06/30/05 @ 08:37
Comment from: John Covalesky [Visitor] Email
John CovaleskyOliver is correct, many times there is a solution to this problem by modifying your own code.

It is important to track these down, because they can rear their head when the bug is reached by another route, not handled by MS code.
The trick to tracking these down is to subclass the failing control, override the OnPaint method,
and catch exception there -- print out stack trace.

This is why that works -- the OnPaint method is called by the .NET framework with a special method named something like PaintWithErrorHandling, which then calls OnPaint.

If you subclass, you can get stack traces of the issue, rather than the framework swallowing the whole thing.

Sample call stack
at System.Windows.Forms.CurrencyManager.get_Item(Int32 index)
at System.Windows.Forms.DataGridRow.PaintHeader(Graphics g, Rectangle visualBounds, Boolean alignToRight, Boolean rowIsDirty)
at System.Windows.Forms.DataGridRelationshipRow.PaintHeaderInside(Graphics g, Rectangle bounds, Brush backBr, Boolean alignToRight, Boolean isDirty)
at System.Windows.Forms.DataGridRelationshipRow.PaintHeader(Graphics g, Rectangle bounds, Boolean alignToRight, Boolean isDirty)
at System.Windows.Forms.DataGrid.PaintRows(Graphics g, Rectangle& boundingRect)
at System.Windows.Forms.DataGrid.PaintGrid(Graphics g, Rectangle gridBounds)
at System.Windows.Forms.DataGrid.OnPaint(PaintEventArgs pe)


sample OnPaint

protected override void OnPaint( Grapics g )
{
try
{
base.OnPaint(g);
}
catch( Exception e )
{
System.Console.Writeline( e.StackTrace);
}

}
07/11/05 @ 18:33
Comment from: Walter [Visitor] Email
WalterThanks for all your suggestions.

In the meantime I have implemented an application exception handler
that fetches all unhandled exceptions, including the ones that
lead to a red cross.

When this happens I try to automatically repaint all forms/controls. This
approach now works fine.

I know this is just a workaround, but when the "Red X" occurs there is none
of my code involved... The call stack often just shows System.* - classes.
Even worse, I only have to move windows across the screen and suddenly a
"Red X" appears. In other situations its a 3rd party control that does
repainting etc. Nothing I can debug or fix here.

The only reason I can guess about is an old 3rd party tool we have to
incorporate in our .NET Forms using the tricky and unsupported way of
using SetParent(). This is a bad thing, I know, but it's a *must*. And
replacing that old tool with new .NET Forms will take a long time
- but it will be done.

Thanks,
Walter
07/23/05 @ 12:12
Comment from: Oliver Sturm [Visitor] Email
Oliver SturmHi Walter, nice to hear you found a solution. I think that the tool you are mentioning may be a good guess for the source of your problems. Thanks for keeping us updated!
07/24/05 @ 09:39
Comment from: Mehdy Mohajery [Visitor] Email
Mehdy MohajeryHi,
I have this problem when I reject changes to dataset of a datagrid.
I set the .Visible property of the datagrid to false and after
changing data I set it to true back.
this workaround fixed my datagrid RED X / RED Cross problem.

best RegardsZ
Mehdy Mohajery
--
12/06/05 @ 11:34
Comment from: Andrew [Visitor] Email
AndrewHi,

I have being seeing a "red x" appearing in place of a datagrid, but this is very rare. I do have a custom column style, and am leaning towards its' overriden OnPaint() method as being the culprit. The custom paint method is attempting to color the rows in the columnn based on the cell contents.

Wondering if you could point out any errors in the following method? Thanks so much!


protected override void Paint(System.Drawing.Graphics g,
System.Drawing.Rectangle bounds, System.Windows.Forms.CurrencyManager
source, int rowNum, System.Drawing.Brush backBrush, System.Drawing.Brush
foreBrush, bool alignToRight)

{
// the idea is to conditionally set the foreBrush and/or backbrush
// depending upon some crireria on the cell value
// Here, we color anything that begins with a letter higher than 'F'
try
{
object o = this.GetColumnValueAtRow(source, rowNum);
if( o != null)
{
if( ((string)o) == "Complete" )
{
backBrush = new SolidBrush(Color.Green);
foreBrush = new SolidBrush(Color.White);
}
else if( ((string)o) == "Error" )
{
backBrush = new SolidBrush(Color.FromArgb(255, 128, 128));
}
else
{
backBrush = new SolidBrush(Color.White);
}
}

base.Paint(g, bounds, source, rowNum, backBrush, foreBrush, alignToRight);
}
catch(Exception ex)
{ /* empty catch */ }
}
02/06/06 @ 15:27
Comment from: Oliver Sturm [Visitor] Email
Oliver SturmWell, obviously your code could easily throw an exception as soon as the type of o is one that can't be cast to a string. I would rewrite that section as


string stringContent = o as string;
if (stringContent != null) {
if (stringContent == "Complete")
...
else if (stringContent == "Error")
...
else
...
}


Looks nicer and can't go wrong. Now, as you're still catching everything in the outer try/catch, I still have no idea why that routine would throw an exception. I don't remember things as closely as I did when I wrote this post, maybe there are other interesting code paths due to the fact that you're using an overridden method instead of an event handler? I suggest you get Reflector and find out :-)
02/07/06 @ 20:53
Comment from: Smit Shah [Visitor] Email
Smit Shah---------------------------
Object reference not set to an instance of an object.
---------------------------
at System.Data.DataView.GetRecord(Int32 recordIndex)

at System.Data.DataView.IsOriginalVersion(Int32 index)

at System.Data.DataRowView.GetColumnValue(DataColumn column)

at System.Data.DataColumnPropertyDescriptor.GetValue(Object component)

at System.Windows.Forms.DataGridColumnStyle.GetColumnValueAtRow(CurrencyManager source, Int32 rowNum)

at System.Windows.Forms.DataGridTextBoxColumn.Paint(Graphics g, Rectangle bounds, CurrencyManager source, Int32 rowNum, Brush backBrush, Brush foreBrush, Boolean alignToRight)

at System.Windows.Forms.DataGridRelationshipRow.PaintCellContents(Graphics g, Rectangle cellBounds, DataGridColumnStyle column, Brush backBr, Brush foreBrush, Boolean alignToRight)

at System.Windows.Forms.DataGridRow.PaintData(Graphics g, Rectangle bounds, Int32 firstVisibleColumn, Int32 columnCount, Boolean alignToRight)

at System.Windows.Forms.DataGridRelationshipRow.Paint(Graphics g, Rectangle bounds, Rectangle trueRowBounds, Int32 firstVisibleColumn, Int32 numVisibleColumns, Boolean alignToRight)

at System.Windows.Forms.DataGrid.PaintRows(Graphics g, Rectangle& boundingRect)

at System.Windows.Forms.DataGrid.PaintGrid(Graphics g, Rectangle gridBounds)

at System.Windows.Forms.DataGrid.OnPaint(PaintEventArgs pe)

at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer, Boolean disposeEventArgs)

at System.Windows.Forms.Control.WmPaint(Message& m)

at System.Windows.Forms.Control.WndProc(Message& m)

at System.Windows.Forms.ControlNativeWindow.OnMessage(Message& m)

at System.Windows.Forms.ControlNativeWindow.WndProc(Message& m)

at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)

at System.Windows.Forms.ComponentManager.System.Windows.Forms.UnsafeNativeMethods+IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)

at System.Windows.Forms.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)

at System.Windows.Forms.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)

at System.Windows.Forms.Application.Run(Form mainForm)

at eBuilder.Apps.TransferManagerApp.modMain.main() in C:\VSS\TransferManager\TransferManagerApplication\modMain.vb:line 45
---------------------------
OK
---------------------------




dvQueue = New DataView(modMain.objClsDSTMW.dsTransferManager.transferManagerFile, "fFileStatus<>'Success' and fFileStatus<>'Cancelled'", "fFilePriority ASC", DataViewRowState.CurrentRows)
dgQueue.DataSource = dvQueue


the datagrid is bound to a dataset's datatable and teh table is updated via another thread in the process... this causes the above exception... the thread updates the dataset at times may be 100 times a second.. so hwo do I work arround this.

I tried your suggestion, but without success.

thanks
-smit.

pelase help
10/25/06 @ 20:59
Comment from: alonesword [Visitor] Email
aloneswordgood.

i meet the same thing as Mehdy Mohajery.but the red X doesn't display on my computer except my pateners. and can't catch any exception.

<code>
private void colConfigQuantity_BeforeCellPaint(object sender, ZTE.CCG.Common.ClientComponent.DataGrid.BeforeCellPaintEventArgs e)
{
DataRowView drcur = ((DataView)e.DataSource.List)[e.RowNum]; // maybe throw exception here. i will check it soon.
// check the data
if(drcur.Row.Table.Columns.Contains("ITEMENABLE") && (Convert.ToString(drcur["ITEMENABLE"]) == NOUSING_STR || Convert.ToString(drcur["ITEMENABLE"]) == "N"))
{
e.ForeBrush = mBrushConfigQuantity;
}
}
</code>

After read the post,i decide to check the code again.
thanks a lot.
09/24/08 @ 02:13
Comment from: mike [Visitor] Email
mikei had this problem but
but i solved it differently
i hided the control before modifying it
then after calculations happen i show the control again simply and easily solved
05/26/13 @ 08:18

This post has 2467 feedbacks awaiting moderation...

Leave a comment


Your email address will not be revealed on this site.
(Line breaks become <br />)
(For my next comment on this site)
(Allow users to contact me through a message form -- Your email will not be revealed!)
Please complete the song title below. Hint: enter 's', 'a', 't', 'i', 's', 'f', 'a', 'c', 't', 'i', 'o', 'n'
antispam test

Enter your email address:

Search

Oliver
MVP logo
December 2014
Sun Mon Tue Wed Thu Fri Sat
 << <   > >>
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31