Cách khắc phục hiện tượng nhấp nháy trong Điều khiển của người dùng


107

Trong ứng dụng của tôi, tôi liên tục chuyển từ điều khiển này sang điều khiển khác. Tôi đã tạo ra không. trong số các điều khiển của người dùng, nhưng trong quá trình điều hướng, các điều khiển của tôi bị nhấp nháy. mất 1 hoặc 2 giây để cập nhật. Tôi đã cố gắng thiết lập cái này

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
or
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true); 
SetStyle(ControlStyles.DoubleBuffer, true);

nhưng nó không giúp được gì ... Mỗi điều khiển có hình nền giống nhau với các điều khiển khác nhau. Vì vậy, giải pháp cho nó là gì ..
Cảm ơn.


Những câu này ở đâu? Tốt nhất, hãy đặt chúng vào hàm tạo. Bạn đã gọi điện UpdateStylessau khi đặt chúng? Nó được ghi chép không tốt, nhưng đôi khi có thể cần thiết.
Thomas

Câu trả lời:


306

Nó không phải là loại nhấp nháy mà đệm kép có thể giải quyết. Cũng không phải BeginUpdate hoặc SuspendLayout. Bạn có quá nhiều điều khiển, BackgroundImage có thể làm cho nó nhiều trở nên tồi tệ hơn .

Nó bắt đầu khi UserControl tự vẽ. Nó vẽ BackgroundImage, để lại các lỗ hổng nơi các cửa sổ điều khiển trẻ em đi vào. Mỗi điều khiển trẻ em sau đó sẽ nhận được một thông báo để vẽ chính nó, chúng sẽ điền vào lỗ với nội dung cửa sổ của chúng. Khi bạn có nhiều điều khiển, những lỗ hổng đó sẽ hiển thị cho người dùng trong một thời gian. Chúng thường có màu trắng, tương phản xấu với Hình ảnh nền khi trời tối. Hoặc chúng có thể có màu đen nếu biểu mẫu có thuộc tính Opacity hoặc TransparencyKey, tương phản không tốt với bất kỳ thứ gì.

Đây là một hạn chế khá cơ bản của Windows Forms, nó bị mắc kẹt với cách Windows hiển thị các cửa sổ. Đã được sửa chữa bởi WPF btw, nó không sử dụng cửa sổ cho điều khiển trẻ em. Những gì bạn muốn là đệm kép toàn bộ biểu mẫu, bao gồm cả các điều khiển con. Điều đó có thể, hãy kiểm tra mã của tôi trong chủ đề này để biết giải pháp. Tuy nhiên, nó có tác dụng phụ và không thực sự làm tăng tốc độ vẽ. Mã rất đơn giản, hãy dán mã này vào biểu mẫu của bạn (không phải do người dùng kiểm soát):

protected override CreateParams CreateParams {
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    return cp;
  }
} 

Có nhiều điều bạn có thể làm để cải thiện tốc độ vẽ, đến mức hiện tượng nhấp nháy không còn đáng chú ý nữa. Bắt đầu bằng cách giải quyết BackgroundImage. Họ có thể thực sự đắt tiền khi hình ảnh nguồn lớn và cần được thu nhỏ để phù hợp với điều khiển. Thay đổi thuộc tính BackgroundImageLayout thành "Tile". Nếu điều đó giúp tăng tốc đáng kể, hãy quay lại chương trình vẽ của bạn và thay đổi kích thước hình ảnh để phù hợp hơn với kích thước điều khiển thông thường. Hoặc viết mã trong phương thức OnResize () của UC để tạo bản sao có kích thước thích hợp của hình ảnh để không phải thay đổi kích thước mỗi khi điều khiển sửa lại. Sử dụng định dạng pixel Format32bppPArgb cho bản sao đó, nó hiển thị nhanh hơn khoảng 10 lần so với bất kỳ định dạng pixel nào khác.

Điều tiếp theo bạn có thể làm là ngăn không cho các lỗ trở nên quá rõ ràng và tương phản xấu với hình ảnh. Bạn có thể tắt cờ kiểu WS_CLIPCHILDREN cho UC, cờ ngăn UC sơn trong khu vực mà trẻ điều khiển. Dán mã này vào mã của UserControl:

protected override CreateParams CreateParams {
  get {
    var parms = base.CreateParams;
    parms.Style &= ~0x02000000;  // Turn off WS_CLIPCHILDREN
    return parms;
  }
}

Giờ đây, các điều khiển con sẽ tự tô lên trên hình nền. Bạn vẫn có thể thấy chúng tự vẽ từng cái một, nhưng lỗ đen hoặc trắng trung gian xấu xí sẽ không thể nhìn thấy được.

Cuối cùng nhưng không kém phần quan trọng, giảm số lượng trẻ kiểm soát luôn là một cách tiếp cận tốt để giải quyết các vấn đề vẽ chậm. Ghi đè sự kiện OnPaint () của UC và vẽ những gì hiện được hiển thị trong một đứa trẻ. Riêng Label và PictureBox rất lãng phí. Thuận tiện cho việc trỏ và nhấp chuột nhưng giải pháp thay thế trọng lượng nhẹ của chúng (vẽ một chuỗi hoặc một hình ảnh) chỉ mất một dòng mã trong phương thức OnPaint () của bạn.


Tắt WS_CLIPCHILDREN trải nghiệm người dùng được cải thiện cho tôi.
Mahesh

Hoàn toàn hoàn hảo! .. Cảm ơn rất nhiều
AlejandroAlis

8

Đây là một vấn đề thực sự và câu trả lời mà Hans Passant đưa ra là rất tốt để cứu vãn hiện tượng nhấp nháy. Tuy nhiên, có những tác dụng phụ như anh ấy đã đề cập, và chúng có thể xấu đi (giao diện người dùng xấu xí). Như đã nêu, "Bạn có thể tắt WS_CLIPCHILDRENcờ kiểu cho UC", nhưng điều đó chỉ tắt nó cho một UC. Các thành phần trên biểu mẫu chính vẫn có vấn đề.

Ví dụ, một thanh cuộn của bảng điều khiển không sơn, vì về mặt kỹ thuật, nó nằm trong vùng con. Tuy nhiên, thành phần con không vẽ thanh cuộn, vì vậy nó không được vẽ cho đến khi di chuột qua (hoặc một sự kiện khác kích hoạt nó).

Ngoài ra, các biểu tượng động (thay đổi biểu tượng trong một vòng lặp chờ) không hoạt động. Xóa các biểu tượng trên a tabPage.ImageKeysẽ không thay đổi kích thước / sơn lại các Trang tab khác một cách thích hợp.

Vì vậy, tôi đang tìm cách tắt chế độ WS_CLIPCHILDRENvẽ ban đầu để Biểu mẫu của tôi sẽ tải được vẽ đẹp, hoặc tốt hơn là chỉ bật nó trong khi thay đổi kích thước biểu mẫu của tôi với nhiều thành phần.

Bí quyết là làm cho ứng dụng gọi điện CreateParamsvới WS_EX_COMPOSITED/WS_CLIPCHILDRENphong cách mong muốn . Tôi đã tìm thấy một bản hack ở đây ( https://web.archive.org/web/20161026205944/http://www.angryhacker.com/blog/archive/2010/07/21/how-to-get-rid-of- flicker-on-windows-form-Applications.aspx ) và nó hoạt động rất tốt. Cảm ơn AngryHacker!

Tôi đặt TurnOnFormLevelDoubleBuffering()cuộc gọi trong ResizeBeginsự kiện biểu mẫu và TurnOffFormLevelDoubleBuffering()gọi trong sự kiện ResizeEnd biểu mẫu (hoặc chỉ để nó WS_CLIPCHILDRENsau khi nó được vẽ đúng cách ban đầu.)

    int originalExStyle = -1;
    bool enableFormLevelDoubleBuffering = true;

    protected override CreateParams CreateParams
    {
        get
        {
            if (originalExStyle == -1)
                originalExStyle = base.CreateParams.ExStyle;

            CreateParams cp = base.CreateParams;
            if (enableFormLevelDoubleBuffering)
                cp.ExStyle |= 0x02000000;   // WS_EX_COMPOSITED
            else
                cp.ExStyle = originalExStyle;

            return cp;
        }
    }

    public void TurnOffFormLevelDoubleBuffering()
    {
        enableFormLevelDoubleBuffering = false;
        this.MaximizeBox = true;
    }

Mã của bạn không bao gồm các phương pháp TurnOnFormLevelDoubleBuffering () ...
Dan W

@DanW Có một cái nhìn tại URL posted in câu trả lời này ( angryhacker.com/blog/archive/2010/07/21/... )
chrisb

Liên kết trong câu trả lời này dường như đã chết. Tôi tò mò về giải pháp, bạn có liên kết đến ví dụ khác không?
Pratt Hinds

6

Nếu bạn đang thực hiện bất kỳ bức tranh tùy chỉnh nào trong điều khiển (tức là ghi đè OnPaint), bạn có thể tự thử bộ đệm kép.

Image image;
protected override OnPaint(...) {
    if (image == null || needRepaint) {
        image = new Bitmap(Width, Height);
        using (Graphics g = Graphics.FromImage(image)) {
            // do any painting in image instead of control
        }
        needRepaint = false;
    }
    e.Graphics.DrawImage(image, 0, 0);
}

Và làm mất hiệu lực quyền kiểm soát của bạn với một thuộc tính NeedRepaint

Nếu không, câu trả lời ở trên với SuspendLayout và ResumeLayout có thể là những gì bạn muốn.


Đây là một phương pháp sáng tạo để mô phỏng bộ đệm kép !. Bạn có thể thêm if (image != null) image.Dispose();trướcimage = new Bitmap...
S.Serpooshan


2

Trên biểu mẫu chính hoặc điều khiển của người dùng nơi hình nền nằm, hãy đặt BackgroundImageLayout tính thành Centerhoặc Stretch. Bạn sẽ nhận thấy sự khác biệt lớn khi điều khiển người dùng đang hiển thị.


2

Tôi đã cố gắng thêm điều này như một nhận xét nhưng tôi không có đủ điểm. Đây là điều duy nhất đã từng giúp đỡ các vấn đề chập chờn của tôi, rất cảm ơn Hans về bài đăng của anh ấy. Đối với bất kỳ ai đang sử dụng trình tạo c ++ như tôi, đây là bản dịch

Thêm khai báo CreateParams vào tệp .h biểu mẫu chính của ứng dụng của bạn, ví dụ:

class TYourMainFrom : public TForm
{
protected:
    virtual void __fastcall CreateParams(TCreateParams &Params);
}

và thêm cái này vào tệp .cpp của bạn

void __fastcall TYourMainForm::CreateParams(TCreateParams &Params)
{
    Params.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    TForm::CreateParams(Params);
}

2

Đặt mã bên dưới trong hàm tạo hoặc sự kiện OnLoad của bạn và nếu bạn đang sử dụng một số loại điều khiển người dùng tùy chỉnh có điều khiển phụ, bạn sẽ cần đảm bảo rằng các điều khiển tùy chỉnh này cũng được đệm kép (mặc dù trong tài liệu MS họ nói nó được đặt thành true theo mặc định).

Nếu bạn đang thực hiện một điều khiển tùy chỉnh, bạn có thể muốn thêm cờ này vào ctor của mình:

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

Bạn có thể tùy ý sử dụng mã này trong Biểu mẫu / Điều khiển của mình:

foreach (Control control in Controls)
{
    typeof(Control).InvokeMember("DoubleBuffered",
        BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
        null, control, new object[] { true });
}

Chúng tôi lặp lại tất cả các điều khiển trong biểu mẫu / điều khiển và truy cập thuộc tính của chúng DoubleBufferedvà sau đó chúng tôi thay đổi nó thành true để làm cho mỗi điều khiển trên biểu mẫu được đệm kép. Lý do chúng tôi phản ánh ở đây là vì hãy tưởng tượng bạn có một điều khiển có các điều khiển con không thể truy cập được, theo cách đó, ngay cả khi chúng là các kiểm soát riêng tư, chúng tôi vẫn sẽ thay đổi thuộc tính của chúng thành true.

Thông tin thêm về kỹ thuật đệm kép có thể được tìm thấy tại đây .

Có một thuộc tính khác mà tôi thường ghi đè để sắp xếp vấn đề này:

protected override CreateParams CreateParams
{
    get
    {
        CreateParams parms = base.CreateParams;
        parms.ExStyle |= 0x00000020; // WS_EX_COMPOSITED
        return parms;
    }
}

WS_EX_COMPOSITED - Vẽ tất cả các con của cửa sổ theo thứ tự sơn từ dưới lên trên bằng cách sử dụng đệm kép.

Bạn có thể tìm thêm các loại cờ kiểu này tại đây .

Hy vọng rằng sẽ giúp!


1

Chỉ để thêm vào câu trả lời mà Hans đã đưa ra:

(Phiên bản TLDR: Độ trong suốt nặng hơn bạn nghĩ, chỉ sử dụng màu đồng nhất ở mọi nơi)

Nếu WS_EX_COMPOSITED, DoubleBuffered và WS_CLIPCHILDREN không giải quyết được hiện tượng nhấp nháy của bạn (đối với tôi WS_CLIPCHILDREN thậm chí còn tồi tệ hơn), hãy thử cách này: xem qua TẤT CẢ các điều khiển và tất cả mã của bạn, và ở bất kỳ nơi nào bạn có Bất kỳ độ trong suốt hoặc nửa trong suốt nào cho BackColor, ForeColor hoặc bất kỳ màu nào khác, chỉ cần loại bỏ nó, chỉ sử dụng màu đặc. Trong hầu hết các trường hợp bạn nghĩ rằng bạn chỉ sử dụng minh bạch, bạn không. Thiết kế lại mã và điều khiển của bạn và sử dụng các màu đồng nhất. Tôi đã bị nhấp nháy khủng khiếp, khủng khiếp và chương trình chạy chậm. Sau khi tôi loại bỏ độ trong suốt, nó tăng tốc đáng kể và không có hiện tượng nhấp nháy.

CHỈNH SỬA: Để bổ sung thêm, tôi vừa phát hiện ra rằng WS_EX_COMPOSITED không nhất thiết phải là cửa sổ rộng, nó có thể được áp dụng chỉ cho các điều khiển cụ thể! Điều này đã cứu tôi rất nhiều rắc rối. Chỉ cần tạo điều khiển tùy chỉnh được kế thừa từ bất kỳ điều khiển nào bạn cần và dán ghi đè đã được đăng cho WS_EX_COMPOSITED. Bằng cách này, bạn chỉ nhận được bộ đệm kép cấp thấp trên điều khiển này, tránh các tác dụng phụ khó chịu trong phần còn lại của ứng dụng!


0

Tôi biết câu hỏi này rất cũ, nhưng tôi muốn cung cấp kinh nghiệm của tôi về nó.

Tôi đã gặp rất nhiều sự cố với việc Tabcontrolnhấp nháy trong biểu mẫu bị ghi đè OnPaintvà / hoặc OnPaintBackGroundtrong Windows 8 sử dụng .NET 4.0.

Các chỉ nghĩ rằng làm việc đã KHÔNG SỬ DỤNG các Graphics.DrawImagephương pháp trong OnPaintghi đè, hay nói cách khác, khi bốc thăm đã được thực hiện trực tiếp với đồ họa được cung cấp bởi PaintEventArgs, thậm chí vẽ tất cả các hình chữ nhật, nhấp nháy biến mất. Nhưng nếu gọi DrawImagephương thức, ngay cả khi vẽ một Bitmap được cắt bớt, (được tạo để đệm kép), hiện tượng nhấp nháy sẽ xuất hiện.

Hy vọng nó giúp!


0

Tôi đã kết hợp sửa lỗi nhấp nháy nàysửa lỗi phông chữ này , sau đó tôi phải thêm một chút mã của riêng mình để bắt đầu hẹn giờ trên sơn để Vô hiệu hóa TabControl khi nó chuyển ra ngoài màn hình và quay lại, v.v.

Cả ba đều tạo nên điều này:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class TabControlEx:TabControl
{
    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
    private const int WM_PAINT = 0x0f;
    private const int WM_SETFONT = 0x30;
    private const int WM_FONTCHANGE = 0x1d;
    private System.Drawing.Bitmap buffer;
    private Timer timer = new Timer();
    public TabControlEx()
    {
        timer.Interval = 1;
        timer.Tick += timer_Tick;
        this.SetStyle(ControlStyles.UserPaint | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
    }
    void timer_Tick(object sender, EventArgs e)
    {
        this.Invalidate();
        this.Update();
        timer.Stop();
    }
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_PAINT) timer.Start();
        base.WndProc(ref m);
    }
    protected override void OnPaint(PaintEventArgs pevent)
    {
        this.SetStyle(ControlStyles.UserPaint, false);
        base.OnPaint(pevent);
        System.Drawing.Rectangle o = pevent.ClipRectangle;
        System.Drawing.Graphics.FromImage(buffer).Clear(System.Drawing.SystemColors.Control);
        if (o.Width > 0 && o.Height > 0)
        DrawToBitmap(buffer, new System.Drawing.Rectangle(0, 0, Width, o.Height));
        pevent.Graphics.DrawImageUnscaled(buffer, 0, 0);
        this.SetStyle(ControlStyles.UserPaint, true);
    }

    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);
        buffer = new System.Drawing.Bitmap(Width, Height);
    }
    protected override void OnCreateControl()
    {
        base.OnCreateControl();
        this.OnFontChanged(EventArgs.Empty);
    }
    protected override void OnFontChanged(EventArgs e)
    {
        base.OnFontChanged(e);
        IntPtr hFont = this.Font.ToHfont();
        SendMessage(this.Handle, WM_SETFONT, hFont, (IntPtr)(-1));
        SendMessage(this.Handle, WM_FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
        this.UpdateStyles();
    }
}

Tôi không phải là người tạo ra nhưng từ những gì tôi hiểu, bitmap thực hiện tất cả các lỗi bỏ qua.

Đây là điều duy nhất giải quyết dứt điểm hiện tượng nhấp nháy TabControl (có Biểu tượng) cho tôi.

video kết quả khác biệt: vani tabcontrol vs tabcontrolex

http://gfycat.com/FineGlitteringDeermouse

ps. bạn sẽ cần đặt HotTrack = true, vì điều này cũng sửa lỗi đó


-2

Bạn đã thử Control.DoubleBufferedTài sản?

Nhận hoặc đặt một giá trị cho biết liệu điều khiển này có nên vẽ lại bề mặt của nó bằng cách sử dụng bộ đệm thứ cấp để giảm hoặc ngăn hiện tượng nhấp nháy hay không.

Ngoài ra điều nàyđiều này có thể giúp ích.


-9

Không cần bất kỳ bộ đệm kép nào và tất cả những thứ đó ...

Một giải pháp đơn giản ...

Nếu bạn đang sử dụng Giao diện MDI, chỉ cần dán mã bên dưới vào biểu mẫu chính. Nó sẽ loại bỏ tất cả nhấp nháy khỏi các trang. Tuy nhiên, một số trang cần nhiều thời gian hơn để tải sẽ hiển thị sau 1 hoặc 2 giây. Nhưng điều này tốt hơn là hiển thị một trang chập chờn trong đó từng mục xuất hiện một.

Đây là giải pháp tốt nhất duy nhất cho toàn bộ ứng dụng. Xem mã để đặt trong biểu mẫu chính:

protected override CreateParams CreateParams {
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    return cp;
  }
} 

12
Vì vậy, những gì bạn đang nói là câu trả lời Hans đã cung cấp hơn hai năm trước, trên thực tế, chính xác? Cảm ơn bạn, Kshitiz. Điều đó thực sự rất hữu ích!
Fernando
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.