WinForm 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:

 view plain | print | copy to clipboard | ?
1private void panel1_Paint(object sender, System.Windows.Forms.EventArgs e) {  
2  throw new Exception("Boom");  
3
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:

 view plain | print | copy to clipboard | ?
1void ResetExceptionState(Control control) {  
2  typeof(Control).InvokeMember("SetState", BindingFlags.NonPublic |  
3    BindingFlags.InvokeMethod | BindingFlags.Instance, null,  
4    control, new object[] { 0x400000, false });  
5
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

 view plain | print | copy to clipboard | ?
1ResetExceptionState(panel1);  
2panel1.Invalidate(); // invoke redraw 
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:
 view plain | print | copy to clipboard | ?
1Private Sub ResetExceptionState(ByVal control As Control)  
2  Dim args() As [Object] = {&H400000, False}  
3  GetType(System.Windows.Forms.Control).InvokeMember("SetState", _  
4  BindingFlags.NonPublic Or BindingFlags.InvokeMethod Or _  
5  BindingFlags.Instance, _  
6  Nothing, control, args)  
7End Sub 
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

14 Comments »

  1. I 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

    Commen

  2. Well, 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?

    Comment by Oliver Sturm — 13/6/2005 @ 3:11 pm - 10 months, 2 weeks ago

  3. The 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

    Comment by Yiannis — 13/6/2005 @ 3:39 pm - 10 months, 2 weeks ago

  4. Okay, 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.

    Comment by Oliver Sturm — 14/6/2005 @ 6:20 pm - 10 months, 2 weeks ago

  5. Hi,
    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

    Comment by Walter — 29/6/2005 @ 6:56 pm - 10 months ago

  6. Hi 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!

    Comment by Oliver Sturm — 29/6/2005 @ 7:08 pm - 10 months ago

  7. Thanks 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

    Comment by Walter — 29/6/2005 @ 9:24 pm - 10 months ago

  8. Walter - 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.

    Comment by Oliver Sturm — 30/6/2005 @ 8:37 am - 10 months ago

  9. Oliver 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

     view plain | print | copy to clipboard | ?
    1protected override void OnPaint( Grapics g )  
    2{  
    3  try 
    4    {  
    5        base.OnPaint(g);  
    6    }  
    7    catch( Exception e )  
    8    {  
    9        System.Console.Writeline( e.StackTrace);  
    10    }  
    11      
    12
    protected override void OnPaint( Grapics g )
    {
      try
        {
            base.OnPaint(g);
        }
        catch( Exception e )
        {
            System.Console.Writeline( e.StackTrace);
        }
    	
    }
    

    Comment by John Covalesky — 11/7/2005 @ 6:33 pm - 9 months, 3 weeks ago

  10. Thanks 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

    Comment by Walter — 23/7/2005 @ 12:12 pm - 9 months, 1 week ago

  11. Hi 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!

    Comment by Oliver Sturm — 24/7/2005 @ 9:39 am - 9 months, 1 week ago

  12. Hi,
    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

    Comment by Mehdy Mohajery — 6/12/2005 @ 11:34 am - 4 months, 3 weeks ago

  13. Hi,

    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!

     view plain | print | copy to clipboard | ?
    1protected override void Paint(System.Drawing.Graphics g,  
    2    System.Drawing.Rectangle bounds, System.Windows.Forms.CurrencyManager  
    3    source, int rowNum, System.Drawing.Brush backBrush, System.Drawing.Brush  
    4    foreBrush, bool alignToRight)   
    5      
    6{  
    7  // the idea is to conditionally set the foreBrush and/or backbrush  
    8  // depending upon some crireria on the cell value  
    9  // Here, we color anything that begins with a letter higher than 'F'  
    10  try 
    11  {  
    12    object o = this.GetColumnValueAtRow(source, rowNum);  
    13    if( o != null)  
    14    {  
    15      if( ((string)o) == "Complete" )  
    16      {  
    17        backBrush = new SolidBrush(Color.Green);  
    18    foreBrush = new SolidBrush(Color.White);  
    19      }  
    20      else if( ((string)o) == "Error" )  
    21      {  
    22    backBrush = new SolidBrush(Color.FromArgb(255, 128, 128));  
    23      }  
    24      else 
    25      {  
    26    backBrush = new SolidBrush(Color.White);  
    27      }  
    28    }   
    29      
    30    base.Paint(g, bounds, source, rowNum, backBrush, foreBrush, alignToRight);  
    31  }  
    32  catch(Exception ex)  
    33  { /* empty catch */ }  
    34
    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 */ }
    }
    

    Comment by Andrew — 6/2/2006 @ 3:27 pm - 2 months, 3 weeks ago

  14. Well, 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

     view plain | print | copy to clipboard | ?
    1 string stringContent = o as string;  
    2  if (stringContent != null) {  
    3    if (stringContent == "Complete")  
    4       ...  
    5    else if (stringContent == "Error")  
    6       ...  
    7    else 
    8       ...  
    9  } 
      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 :-)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值