图表中出现异常后未调用 Winforms OnPaint

Winforms OnPaint not being called after Exception in Chart

您好互联网开发者!

如果轴或显示的点取不应该有的值,Winforms.DataVisualization.Charting.Chart 会将自己画成一个大红叉。

当 OnPaint 方法内部发生异常时,就会发生这种情况。本主题的其他问题建议创建一个子类并覆盖 OnPaint。这就是我所做的,这就是我如何找出导致问题的原因。

有趣的问题是:如何摆脱这种错误状态? 我尝试将属性设置为有效值并清除所有内容,调用 Invalidate() 和通常的事情。发生错误后,OnPaint 方法再也不会被调用。

Winforms 中是否有某种行为会导致这种情况?控件可以标记为 "failed beyond recover" 吗? 当我将控件移到失败状态图上方时,红色 x 被重新绘制,因此它被绘制,但不是单独绘制。

我想知道我在这里缺少哪种细节知识。

PS: SetStyle 没有帮助,在错误发生之前调用了 OnPaint 并且实施它并没有解决它。


附录 A:重新编译 我反汇编了整个Winforms.DataVisualization.Charting程序集的源代码并重新编译。通过注释掉抛出;在图表 OnPaint 中,该方法不会留下预期,而只是绘制一个 "error has occured"-图像本身。 在这种情况下,重绘不会永远禁用。

如果控件抛出异常,Winforms 似乎会从绘制顺序中移除控件。

调试提取的程序集源版本使我们对问题有了一些了解。首先,根据提供的 link @TnTinMn

,控件在抛出异常后被永远排除在 OnPaint 之外是正常行为。

然而,图表本身在 OnPaint 中有一个代码路径,当 OnPaint 为 运行 时,该代码路径不会重置用于阻止失效的内部字段。

以下代码是捕获OnPaint异常并使用反射重置私有字段的图表的子类。因此,如果图表进入错误状态,可以通过设置有效值并简单地重绘来重置它。

此外,在创建图表控件后,请确保设置轴的最小值和最大值属性,即使是未使用的。属性 setter 不仅仅是将值放入字段。对我来说,这有助于避免抛出异常。

/// <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);
        }
    }
}