1

Hello devs of the internet!

The Winforms.DataVisualization.Charting.Chart draws itself as a big red cross if the axes or displayed points take values they should not have.

This happens when an exception occurs inside the OnPaint method. Other questions to this topic advise to make a subclass and override OnPaint. That's what i did and thats how i found out what caused the problem.

The interesting question is: How to i get rid of this error state? I tried settig the properties to valid values and clearing all content, calling Invalidate() and the usual things. The OnPaint method is never called again after the error occured.

Is there some kind of behaviour in winforms that leads to this? Can controls be flagged as "failed beyond recover"? When i move controls above the failed state chart, the red x is redrawn, so it is painted, but not by itself.

I wonder which kind of detail knowledge i am missing here.

PS: SetStyle does not help, OnPaint is called before the error happens and imlementing it does not solve it.


Appendix A: Recompiling I disassembled the source code of the whole Winforms.DataVisualization.Charting assembly and recompiled it. By commenting out the throw; in the charts OnPaint, there method does not leave with an expection but just draws an "error has occured"-image itself. In this case, the repainting is not disabled forever.

Somehow it looks like Winforms removed controls from the drawing order if they throw an exception.

Otterprinz
  • 459
  • 3
  • 10
  • What do you expect the behaviour to be when you give the chart invalid values? A blank chart? – JohanP Jan 09 '18 at 22:05
  • Hey @JohanP. I expect what a gives, a red cross. But after i give it valid values again, i expect the error symbol to go away. The picturebox shows the red X as well if the image is invalid, but giving it a valid image afterwards shows the image. The chart however doesn't seem to recover. – Otterprinz Jan 09 '18 at 22:09
  • 1
    Ye, that's quite strange then. Maybe reinstantiate the chart control? – JohanP Jan 09 '18 at 22:12
  • Disposing it and making a new one is a solution. But it doesn't answer my question. I want to know why that happens. – Otterprinz Jan 09 '18 at 22:16
  • I thought your question was: _the interesting question is: How to i get rid of this error state?_. Disposing and making another one is a way to get rid of the error state. As for _why_ is happens, you have already addressed it in your question _This happens when an exception occurs inside the OnPaint method._ – JohanP Jan 09 '18 at 22:19
  • My question was that yes. I want to get rid of the error in that control. Disposing the control and creating a new one is better than telling our customer to restart the software to solve the problem. It's still unsatisfying. Like deleting call code and writing it again instead of finding the bug. – Otterprinz Jan 09 '18 at 22:30
  • It's a control written by MS, who knows what their intention was with it? If they don't want you to `invalidate` after an exception, then that is their design choice, or a bug on their side, only the authors can tell you. No point banging your head against a wall when you can workaround it. – JohanP Jan 09 '18 at 22:35
  • 1
    The **Big Red X** persistence is a behavior of the WinForm Control.OnPaint error handling. See: [WinForms and the big red ‘X’ of doom](https://blogs.msdn.microsoft.com/shawnhar/2010/11/22/winforms-and-the-big-red-x-of-doom/). – TnTinMn Jan 10 '18 at 00:59
  • _It's a control written by MS_ Actually it isn't. It was bought by MS. – TaW Jan 10 '18 at 01:01

1 Answers1

1

Debugging the extracted source version of the assembly brought some insight into the problem. First, controls being excluded from OnPaint forever after throwing an exception is normal behaviour according to the link @TnTinMn provided.

The chart itself however has one codepath in OnPaint that does not reset an internal field that is used to block invalidation while OnPaint is running.

The following code is a subclass of the chart that catches the OnPaint exception and uses reflection to reset the private field. Thus, if the chart runs into an error state, it can be reset by setting valid values and simply redrawing.

Also after creating a chart control, make sure to set the Minimum and Maximum properties of the Axes, even the unused. The properties setter does more than just put a value into a field. For me that helped not having the exception thrown.

/// <summary>
/// Subclass catches exception thrown from base.OnPaint and thus prevents the red x.
/// Resets base classes private field "disableInvalidates" via reflection to reallow invalidation.
/// </summary>
public class ChartSubclass : Chart
{
    private readonly System.Reflection.FieldInfo disableInvalidatesField;

    public ChartSubclass()
    {
        this.disableInvalidatesField = typeof(Chart).GetField("disableInvalidates",
            System.Reflection.BindingFlags.NonPublic |
            System.Reflection.BindingFlags.Public |
            System.Reflection.BindingFlags.Static |
            System.Reflection.BindingFlags.SetField |
            System.Reflection.BindingFlags.Instance);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        try
        {
            base.OnPaint(e);
        }
        catch (Exception ex)
        {
            this.disableInvalidatesField.SetValue(this, false);
            this.DrawErrorState(e, ex);
        }
    }

    /// <summary>
    /// Draws error message.
    /// </summary>
    private void DrawErrorState(PaintEventArgs e, Exception ex)
    {
        var graphics = e.Graphics;
        graphics.FillRectangle(Brushes.White, 0, 0, base.Width, base.Height);
        var layoutRectangle = new RectangleF(3f, 3f, base.Width - 6, base.Height - 6);
        using (var stringFormat = new StringFormat())
        {
            stringFormat.Alignment = StringAlignment.Center;
            stringFormat.LineAlignment = StringAlignment.Center;
            graphics.DrawString(ex.Message, this.Font, Brushes.Black, layoutRectangle, stringFormat);
        }
    }
}
Otterprinz
  • 459
  • 3
  • 10