为什么我的 WinForms 控件会闪烁并且调整大小缓慢?

发布于 2024-10-12 04:19:54 字数 208 浏览 2 评论 0原文

我正在制作一个程序,其中有很多面板和面板中的面板。

我在这些面板中有一些自定义绘制的控件。

1 个面板的调整大小函数包含用于调整该面板中所有控件的大小和位置的代码。

现在,一旦我调整程序大小,该面板的调整大小就会被激活。这会导致该面板中的组件出现大量闪烁。

所有用户绘制的控件都是双缓冲的。

有人可以帮我解决这个问题吗?

I'm making a program where I have a lot of panels and panels in panels.

I have a few custom drawn controls in these panels.

The resize function of 1 panel contains code to adjust the size and position of all controls in that panel.

Now as soon as I resize the program, the resize of this panel gets actived. This results in a lot of flickering of the components in this panel.

All user drawn controls are double buffered.

Can some one help me solve this problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

傲影 2024-10-19 04:19:54

要在调整获胜表单大小时消除闪烁,请在调整大小时暂停布局。重写表单 resizebegin/resizeend 方法,如下所示。

protected override void OnResizeBegin(EventArgs e) {
    SuspendLayout();
    base.OnResizeBegin(e);
}
protected override void OnResizeEnd(EventArgs e) {
    ResumeLayout();
    base.OnResizeEnd(e);
}

这将使控件保持完整(与调整大小之前一样),并在调整大小操作完成时强制重绘。

To get rid of the flicker while resizing the win form, suspend the layout while resizing. Override the forms resizebegin/resizeend methods as below.

protected override void OnResizeBegin(EventArgs e) {
    SuspendLayout();
    base.OnResizeBegin(e);
}
protected override void OnResizeEnd(EventArgs e) {
    ResumeLayout();
    base.OnResizeEnd(e);
}

This will leave the controls intact (as they where before resizing) and force a redraw when the resize operation is completed.

明月夜 2024-10-19 04:19:54

看看您发布的项目,当您选择第一个选项卡并使用渐变填充的组框时,闪烁非常糟糕。显示第二个或第三个选项卡时,几乎没有任何闪烁(如果有的话)。

  Your main form

很明显,问题与您在该选项卡页上显示的控件有关。快速浏览一下自定义渐变填充组框类的代码即可了解更具体的原因。每次绘制一个分组框控件时,您都会进行大量非常昂贵的处理。 因为每个组框控件在每次调整表单大小时都必须重新绘制自身,因此该代码执行的次数令人难以置信。

另外,您将控件的背景设置为“透明”,这必须在 WinForms 中通过要求父窗口首先在控件窗口内绘制自身以产生背景像素来伪造。然后控件将自身绘制在其之上。这也比用像 SystemColors.Control 这样的纯色填充控件的背景需要更多的工作,并且它会导致您在调整表单大小时看到正在绘制的表单像素,然后组框才有机会绘制画自己。

这是我正在谈论的来自自定义渐变填充组框控件类的具体代码:

protected override void OnPaint(PaintEventArgs e)
{
    if (Visible)
    {
        Graphics gr = e.Graphics;
        Rectangle clipRectangle = new Rectangle(new Point(0, 0), this.Size);
        Size tSize = TextRenderer.MeasureText(Text, this.Font);
        Rectangle r1 = new Rectangle(0, (tSize.Height / 2), Width - 2, Height - tSize.Height / 2 - 2);
        Rectangle r2 = new Rectangle(0, 0, Width, Height);
        Rectangle textRect = new Rectangle(6, 0, tSize.Width, tSize.Height);

        GraphicsPath gp = new GraphicsPath();
        gp.AddRectangle(r2);
        gp.AddRectangle(r1);
        gp.FillMode = FillMode.Alternate;

        gr.FillRectangle(new SolidBrush(Parent.BackColor), clipRectangle);

        LinearGradientBrush gradBrush;
        gradBrush = new LinearGradientBrush(clipRectangle, SystemColors.GradientInactiveCaption, SystemColors.InactiveCaptionText, LinearGradientMode.BackwardDiagonal);
        gr.FillPath(gradBrush, RoundedRectangle.Create(r1, 7));

        Pen borderPen = new Pen(BorderColor);
        gr.DrawPath(borderPen, RoundedRectangle.Create(r1, 7));
        gr.FillRectangle(gradBrush, textRect);
        gr.DrawRectangle(borderPen, textRect);
        gr.DrawString(Text, base.Font, new SolidBrush(ForeColor), 6, 0);
    }

}

protected override void OnPaintBackground(PaintEventArgs pevent)
{
    if (this.BackColor == Color.Transparent)
        base.OnPaintBackground(pevent);
}

现在您已经看到了代码,红色警告标志应该升起。 您正在创建一堆 GDI+ 对象(画笔、笔、区域等),但未能释放其中任何一个! 几乎所有代码都应该包含在using语句中。这只是草率的编码。

完成所有这些工作需要付出一些代价。当计算机被迫投入大量时间来渲染控件时,其他事情就会落后。当它努力跟上调整大小时,您会看到闪烁。它与任何其他使计算机超载的东西(例如计算 pi 的值)没有什么不同,当您像这里一样使用尽可能多的自定义绘制控件时,这真的很容易做到。在 Win32 中透明度很困难,许多自定义 3D 绘画也是如此。它使用户界面看起来并且感觉笨重。我不理解为何急于摆脱本机控件的另一个原因。

您实际上只有三个选择:

  1. 处理闪烁。 (我同意,这不是一个好的选择。)
  2. 使用不同的控件,例如标准的内置控件。当然,它们可能没有奇特的渐变效果,但如果用户自定义了 Windows 主题,那么一半的时间看起来会很糟糕。在深灰色背景上阅读黑色文本也相当困难。
  3. 更改自定义控件中的绘制代码以减少工作量。您也许可以通过一些简单的“优化”来完成,而不会花费任何视觉效果,但我怀疑这不太可能。这是速度和视觉效果之间的权衡。什么都不做总是更快。

Looking at the project you posted, the flickering is really bad when you have the first tab selected, with the gradient-filled group boxes. With the second or third tab showing, there's hardly any flicker, if at all.

  Your main form

So clearly the problem has something to do with the controls you're showing on that tab page. A quick glance at the code for the custom gradient-filled group box class gives away the more specific cause. You're doing a lot of really expensive processing each time you draw one of the groupbox controls. Because each groupbox control has to repaint itself each time the form is resized, that code is getting executed an unbelievable number of times.

Plus, you have the controls' background set to "Transparent", which has to be faked in WinForms by asking the parent window to draw itself first inside the control window to produce the background pixels. The control then draws itself on top of that. This is also more work than filling the control's background with a solid color like SystemColors.Control, and it's causing you to see the form's pixels being drawn while you resize the form, before the groupboxes have a chance to paint themselves.

Here's the specific code I'm talking about from your custom gradient-filled groupbox control class:

protected override void OnPaint(PaintEventArgs e)
{
    if (Visible)
    {
        Graphics gr = e.Graphics;
        Rectangle clipRectangle = new Rectangle(new Point(0, 0), this.Size);
        Size tSize = TextRenderer.MeasureText(Text, this.Font);
        Rectangle r1 = new Rectangle(0, (tSize.Height / 2), Width - 2, Height - tSize.Height / 2 - 2);
        Rectangle r2 = new Rectangle(0, 0, Width, Height);
        Rectangle textRect = new Rectangle(6, 0, tSize.Width, tSize.Height);

        GraphicsPath gp = new GraphicsPath();
        gp.AddRectangle(r2);
        gp.AddRectangle(r1);
        gp.FillMode = FillMode.Alternate;

        gr.FillRectangle(new SolidBrush(Parent.BackColor), clipRectangle);

        LinearGradientBrush gradBrush;
        gradBrush = new LinearGradientBrush(clipRectangle, SystemColors.GradientInactiveCaption, SystemColors.InactiveCaptionText, LinearGradientMode.BackwardDiagonal);
        gr.FillPath(gradBrush, RoundedRectangle.Create(r1, 7));

        Pen borderPen = new Pen(BorderColor);
        gr.DrawPath(borderPen, RoundedRectangle.Create(r1, 7));
        gr.FillRectangle(gradBrush, textRect);
        gr.DrawRectangle(borderPen, textRect);
        gr.DrawString(Text, base.Font, new SolidBrush(ForeColor), 6, 0);
    }

}

protected override void OnPaintBackground(PaintEventArgs pevent)
{
    if (this.BackColor == Color.Transparent)
        base.OnPaintBackground(pevent);
}

And now that you've seen the code, red warning flags ought to go up. You're creating a bunch of GDI+ objects (brushes, pens, regions, etc.), but failing to Dispose any of them! Almost all of that code should be wrapped in using statements. That's just sloppy coding.

Doing all of that work costs something. When the computer is forced to devote so much time to rendering controls, other things lag behind. You see a flicker as it strains to keep up with the resize. It's no different than anything else that overloads a computer (like a computing the value of pi), it's just really easy to do so when you use as many custom drawn controls like you do here. Transparency is hard in Win32, and so is a lot of custom 3D painting. It makes the UI look and feel clunky to the user. Yet another reason that I don't understand the rush away from native controls.

You really only have three options:

  1. Deal with the flicker. (I agree, this is not a good option.)
  2. Use different controls, like the standard, built-in ones. Sure, they may not have a fancy gradient effect, but that's going to look broken half of the time anyway if the user has customized their Windows theme. It's also reasonably hard to read black text on a dark gray background.
  3. Change the painting code within your custom controls to do less work. You may be able to get by with some simple "optimizations" that don't cost you any of the visual effects, but I suspect this is unlikely. It's a tradeoff between speed and eye candy. Doing nothing is always faster.
橘和柠 2024-10-19 04:19:54

使用此代码成功消除了表单调整大小时的闪烁。谢谢。

VB.NET

Public Class Form1

Public Sub New()
    Me.SetStyle(ControlStyles.UserPaint Or ControlStyles.OptimizedDoubleBuffer Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.SupportsTransparentBackColor, True)
End Sub

Private Sub Form1_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Resize
    Me.Update()
End Sub

End Class

C#

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        Resize += Form1_Resize;
        this.SetStyle(ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.SupportsTransparentBackColor, true);
    }

    private void Form1_Resize(object sender, System.EventArgs e)
    {
        this.Update();
    }

}

I successfully eliminate flicker when form resize using this code. Thanks.

VB.NET

Public Class Form1

Public Sub New()
    Me.SetStyle(ControlStyles.UserPaint Or ControlStyles.OptimizedDoubleBuffer Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.SupportsTransparentBackColor, True)
End Sub

Private Sub Form1_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Resize
    Me.Update()
End Sub

End Class

C#

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        Resize += Form1_Resize;
        this.SetStyle(ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.SupportsTransparentBackColor, true);
    }

    private void Form1_Resize(object sender, System.EventArgs e)
    {
        this.Update();
    }

}
静谧 2024-10-19 04:19:54

所以我遇到了同样的问题 - 我的具有透明背景的控件重新绘制了大约 34 次,对我有用的是:

在包含该控件的表单上

protected override void OnResize(EventArgs e)
    {
        myControl.Visible = false;
        base.OnResize(e);
        myControl.Visible = true;
    }

并且在控件中也是如此:

protected override void OnResize(EventArgs e)
    {
        this.Visible = false;
        base.OnResize(e);
        this.Visible = true;
    }

这将重新绘制的数量减少到 4 次,这有效地消除了调整控件大小时的任何闪烁。

So I ran into this same problem - my control with a transparent background was repainting like 34 times, and what worked for me was:

On my form that contained the control

protected override void OnResize(EventArgs e)
    {
        myControl.Visible = false;
        base.OnResize(e);
        myControl.Visible = true;
    }

And the same in the control:

protected override void OnResize(EventArgs e)
    {
        this.Visible = false;
        base.OnResize(e);
        this.Visible = true;
    }

This reduced the amount of repainting to 4, which effectively eliminated any flicker when the control was being resized.

暮年慕年 2024-10-19 04:19:54

也许对您来说一个好的解决方案是使用 Form .ResizeBeginForm.ResizeEnd< /a> 事件。

在 ResizeBegin 上将主面板可见性设置为 false,在 ResizeEnd 上将主面板可见性设置为 true。

这样,当有人调整您的表单大小时,面板就不会被重新绘制。

Maybe a good solution for you will be to use Form.ResizeBegin and Form.ResizeEnd events.

On ResizeBegin set main panel visibility to false, on ResizeEnd set main panel visibility to true.

This way panels will not be redrawn while someone is resizing your form.

絕版丫頭 2024-10-19 04:19:54

虽然挂钩 ResizeBegin 和 ResizeEnd 是正确的想法,但我不会隐藏主面板的可见性,而是将任何调整大小计算延迟到 ResizeEnd。在这种情况下,您甚至不需要挂钩 ResizeBegin 或 Resize - 所有逻辑都进入 ResizeEnd。

我这么说有两个原因。第一,即使面板是隐藏的,调整大小操作可能仍然很昂贵,因此除非调整大小计算被延迟,否则表单不会像应有的那样响应。第二,在调整大小时隐藏窗格的内容可能会让用户感到不舒服。

While hooking into ResizeBegin and ResizeEnd is the right idea, instead of hiding the main panel's visibility I'd instead delay any resize calculations until ResizeEnd. In this case, you don't even need to hook into ResizeBegin or Resize - all the logic goes into ResizeEnd.

I say this for two reasons. One, even though the panel is hidden, the resize operation will likely still be expensive and so the form won't feel as responsive as it should unless the resize calculations are delayed. Two, hiding the pane's contents while resizing can be jarring to the user.

一影成城 2024-10-19 04:19:54

我也有同样的问题。

发生这种情况似乎是因为您使用了圆角。当我将 CornerRadius 属性设置为 0 时,闪烁消失了。

到目前为止我只找到了以下解决方法。这不是最好的,但它可以停止闪烁。

private void Form_ResizeBegin(object sender, EventArgs e)
{
  rectangleShape.CornerRadius = 0;
}

private void Form_ResizeEnd(object sender, EventArgs e)
{
  rectangleShape.CornerRadius = 15;
}

I had the same problem.

It seams that this is happening because you are using rounded corners. When I set CornerRadius property to 0, the flickering was gone.

So far I have only found the following workaround. Not the nicest one, but it stops the flickering.

private void Form_ResizeBegin(object sender, EventArgs e)
{
  rectangleShape.CornerRadius = 0;
}

private void Form_ResizeEnd(object sender, EventArgs e)
{
  rectangleShape.CornerRadius = 15;
}
把梦留给海 2024-10-19 04:19:54

我有类似的问题。我的整个表单正在缓慢地调整大小,并且控件以丑陋的方式绘制它们。所以这对我有帮助:

//I Added This To The Designer File, You Can Still Change The WindowState In Designer View If You Want. This Helped Me Though.
this.WindowState = FormWindowState.Maximized;

在调整大小事件中,将此代码添加到开头

this.Refresh();

I Had A Similar Issue Like This. My Entire Form Was Resizing Slowly And The Controls Painted Them In An Ugly Manner. So This Helped Me:

//I Added This To The Designer File, You Can Still Change The WindowState In Designer View If You Want. This Helped Me Though.
this.WindowState = FormWindowState.Maximized;

And In The Resize Event, Add This Code To The Beginning

this.Refresh();
冬天旳寂寞 2024-10-19 04:19:54

闪烁与暂停布局、恢复布局、调整大小、刷新或此处列出的任何答案无关。为了减少闪烁,只需确保在面板中将控件创建为容器,而不是直接创建到窗体。当直接在没有容器的表单中创建控件时,表单将闪烁与这些控件的数量一样多的次数。如果没有(在包含面板中创建),表单将减少闪烁。当调整窗体大小时(不仅通过 _Resized 事件处理程序,还通过代码),窗体将闪烁的次数与窗体中面板的数量加上直接添加到窗体中的所有控件的数量相同。

关于这个问题,我认为闪烁的主要原因是通过代码调整面板中的控件大小,这导致所有其他面板大小和面板中的所有控件。如果我们为面板设置锚点,并为面板中的控件设置锚点,则无需通过代码调整面板大小或面板中控件的大小。面板中控件的锚点是面板。面板中面板的锚点是包含面板的。如果我们在表单中正确应用面板和锚点,则无需调整面板中控件的大小。

Flickering has nothing to do with suspending layout, resuming layout, resize, refresh, or whatever listed here as an answer. To make less flickering, just make sure controls are created in the panel as container, not directly to the form. When controls are created directly in the form without container, the form will flicker as many times as the number of those controlls. If not (created in the containing panel), form will flicker less. When the form is resized (not only by _Resized event handler but also by code), the form will flicker only as many times as the number of panels in the form plus the number of all the controls directly added to the form.

Regarding the question, I believe the main cause of the flickering is due to the resizing controlls in the panel by code, which causes all the other panels size and the all controlls in the panels. If we set the anchors for the panel and set the anchors for the controlls in the panel, there is no need to adjust panel size or controlls' size in the panel by code. Anchor of the control in the panel is to the panel. Anchor of the panel in the panel is to the containing panel. There is no need to resize controls in the panel if we apply panels and anchors properly in the form.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文