วิธีแก้ไขการกะพริบใน User controls


108

ในแอปพลิเคชันของฉันฉันย้ายจากส่วนควบคุมหนึ่งไปยังอีกส่วนหนึ่งอยู่ตลอดเวลา ฉันได้สร้างไม่ ของการควบคุมของผู้ใช้ แต่ในระหว่างการนำทางการควบคุมของฉันสั่นไหว ใช้เวลาอัปเดต 1 หรือ 2 วินาที ฉันพยายามตั้งค่านี้

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

แต่ก็ไม่ได้ผล ... แต่ละตัวควบคุมมีภาพพื้นหลังเหมือนกันโดยมีตัวควบคุมต่างกัน แล้ววิธีแก้คืออะไร ..
ขอบคุณ.


งบเหล่านี้อยู่ที่ไหน? ตามหลักการแล้วให้ใส่ไว้ในตัวสร้าง คุณโทรUpdateStylesหลังจากตั้งค่าเหล่านี้หรือไม่ มีเอกสารไม่ดี แต่บางครั้งอาจจำเป็น
Thomas

คำตอบ:


307

ไม่ใช่การสั่นไหวแบบที่บัฟเฟอร์สองชั้นสามารถแก้ได้ หรือ BeginUpdate หรือ SuspendLayout คุณมีการควบคุมมากเกินไป BackgroundImage สามารถทำให้แย่ลงได้มาก

เริ่มต้นเมื่อ UserControl วาดภาพตัวเอง มันดึง BackgroundImage ออกจากรูที่หน้าต่างควบคุมเด็กไป การควบคุมเด็กแต่ละคนจะได้รับข้อความให้ระบายสีเองพวกเขาจะเติมเนื้อหาในหน้าต่างลงในช่อง เมื่อคุณมีการควบคุมจำนวนมากผู้ใช้จะมองเห็นช่องโหว่เหล่านั้นได้ชั่วขณะหนึ่ง โดยปกติจะเป็นสีขาวตัดกับ BackgroundImage ไม่ดีเมื่ออยู่ในที่มืด หรืออาจเป็นสีดำหากแบบฟอร์มมีชุดคุณสมบัติ Opacity หรือ TransparencyKey ซึ่งตัดกันไม่ดีกับอะไรก็ได้

นี่เป็นข้อ จำกัด พื้นฐานที่ค่อนข้างชัดเจนของ Windows Forms ซึ่งติดอยู่กับวิธีที่ Windows แสดงผล windows แก้ไขโดย WPF btw ไม่ใช้ windows สำหรับการควบคุมเด็ก สิ่งที่คุณต้องการคือการบัฟเฟอร์สองครั้งทั้งแบบฟอร์มรวมถึงตัวควบคุมย่อย เป็นไปได้ตรวจสอบรหัสของฉันในหัวข้อนี้เพื่อหาวิธีแก้ปัญหา แม้ว่าจะมีผลข้างเคียงและไม่ได้เพิ่มความเร็วในการวาดภาพ รหัสเป็นเรื่องง่ายวางสิ่งนี้ในแบบฟอร์มของคุณ (ไม่ใช่ตัวควบคุมผู้ใช้):

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

มีหลายสิ่งที่คุณสามารถทำได้เพื่อปรับปรุงความเร็วในการวาดภาพจนถึงจุดที่ไม่สามารถสังเกตเห็นการสั่นไหวได้อีกต่อไป เริ่มต้นด้วยการจัดการ BackgroundImage อาจมีราคาแพงมากเมื่ออิมเมจต้นฉบับมีขนาดใหญ่และจำเป็นต้องย่อขนาดให้พอดีกับตัวควบคุม เปลี่ยนคุณสมบัติ BackgroundImageLayout เป็น "Tile" หากวิธีนี้ให้ความเร็วที่เห็นได้ชัดเจนให้กลับไปที่โปรแกรมวาดภาพของคุณและปรับขนาดภาพให้เหมาะสมกับขนาดการควบคุมทั่วไป หรือเขียนโค้ดในเมธอด OnResize () ของ UC เพื่อสร้างสำเนาของรูปภาพที่มีขนาดเหมาะสมเพื่อที่จะไม่ต้องปรับขนาดทุกครั้งที่ควบคุมการเขียนซ้ำ ใช้รูปแบบพิกเซล Format32bppPArgb สำหรับสำเนานั้นจะแสดงผลเร็วกว่ารูปแบบพิกเซลอื่น ๆ ประมาณ 10 เท่า

สิ่งต่อไปที่คุณทำได้คือป้องกันไม่ให้หลุมนั้นสังเกตเห็นได้ชัดเจนและตัดกันไม่ดีกับภาพ คุณสามารถปิดแฟล็กสไตล์ WS_CLIPCHILDREN สำหรับ UC ซึ่งเป็นแฟล็กที่ป้องกันไม่ให้ UC วาดภาพในพื้นที่ที่เด็กควบคุมไป วางรหัสนี้ในรหัสของ UserControl:

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

ตอนนี้ส่วนควบคุมเด็กจะระบายสีตัวเองที่ด้านบนของภาพพื้นหลัง คุณอาจยังเห็นพวกเขาวาดภาพตัวเองทีละภาพ แต่จะมองไม่เห็นหลุมสีขาวหรือหลุมดำระดับกลางที่น่าเกลียด

สุดท้าย แต่ไม่ท้ายสุดการลดจำนวนการควบคุมเด็กเป็นแนวทางที่ดีในการแก้ปัญหาการระบายสีช้า แทนที่เหตุการณ์ OnPaint () ของ UC และวาดสิ่งที่แสดงในเด็ก โดยเฉพาะ Label และ PictureBox นั้นสิ้นเปลืองมาก สะดวกในการชี้และคลิก แต่ทางเลือกที่มีน้ำหนักเบา (การวาดสตริงหรือรูปภาพ) ใช้โค้ดเพียงบรรทัดเดียวในวิธี OnPaint () ของคุณ


ปิด WS_CLIPCHILDREN ประสบการณ์ผู้ใช้ที่ดีขึ้นสำหรับฉัน
Mahesh

สมบูรณ์แบบแน่นอน! .. ขอบคุณมาก
AlejandroAlis

9

นี่เป็นปัญหาที่แท้จริงและคำตอบที่ Hans Passant ให้ไว้นั้นยอดเยี่ยมสำหรับการบันทึกการสั่นไหว อย่างไรก็ตามมีผลข้างเคียงตามที่เขากล่าวถึงและอาจน่าเกลียด (UI น่าเกลียด) ตามที่ระบุไว้ "คุณสามารถปิดWS_CLIPCHILDRENแฟล็กลักษณะสำหรับ UC ได้" แต่จะปิดเฉพาะสำหรับ UC เท่านั้น ส่วนประกอบในฟอร์มหลักยังคงมีปัญหา

ตัวอย่างแถบเลื่อนแผงไม่ได้ระบายสีเนื่องจากในทางเทคนิคแล้วอยู่ในพื้นที่รอง อย่างไรก็ตามองค์ประกอบลูกไม่ได้วาดแถบเลื่อนดังนั้นจึงไม่ได้รับการวาดภาพจนกว่าจะวางเมาส์เหนือ (หรือมีเหตุการณ์อื่นทริกเกอร์)

นอกจากนี้ไอคอนเคลื่อนไหว (การเปลี่ยนไอคอนในการรอวนซ้ำ) ก็ไม่ทำงาน การลบไอคอนบน a tabPage.ImageKeyไม่ได้ปรับขนาด / ทาสีแท็บอื่นอย่างเหมาะสม

ดังนั้นฉันจึงกำลังมองหาวิธีปิดการWS_CLIPCHILDRENวาดภาพเริ่มต้นเพื่อให้แบบฟอร์มของฉันโหลดอย่างสวยงามหรือดีกว่า แต่เปิดใช้งานในขณะที่ปรับขนาดแบบฟอร์มด้วยส่วนประกอบมากมาย

เคล็ดลับคือการได้รับแอปพลิเคชันในการโทรCreateParamsด้วยWS_EX_COMPOSITED/WS_CLIPCHILDRENรูปแบบที่ต้องการ ฉันพบแฮ็คที่นี่ ( 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 ) และใช้งานได้ดี ขอบคุณ AngryHacker!

ฉันวางTurnOnFormLevelDoubleBuffering()สายในResizeBeginเหตุการณ์แบบฟอร์มและTurnOffFormLevelDoubleBuffering()เรียกในรูปแบบเหตุการณ์ ResizeEnd (หรือทิ้งไว้WS_CLIPCHILDRENหลังจากที่มีการทาสีอย่างถูกต้องในตอนแรก)

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

รหัสของคุณไม่รวมเมธอด TurnOnFormLevelDoubleBuffering () ...
Dan W

@DanW ดู URL ที่โพสต์ในคำตอบนี้ ( angryhacker.com/blog/archive/2010/07/21/… )
ChrisB

ลิงก์ในคำตอบนี้ดูเหมือนจะตายแล้ว ฉันอยากรู้เกี่ยวกับวิธีแก้ปัญหาคุณมีลิงค์ไปยังตัวอย่างอื่นหรือไม่?
Pratt Hinds

6

หากคุณกำลังวาดภาพแบบกำหนดเองในส่วนควบคุม (เช่นการลบล้าง OnPaint) คุณสามารถลองใช้บัฟเฟอร์สองครั้งด้วยตัวเอง

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

และทำให้การควบคุมของคุณเป็นโมฆะด้วยคุณสมบัติ NeedRepaint

มิฉะนั้นคำตอบข้างต้นด้วย SuspendLayout และ ResumeLayout อาจเป็นสิ่งที่คุณต้องการ


นี่เป็นวิธีที่สร้างสรรค์ในการจำลอง doublebuffer! คุณสามารถเพิ่มif (image != null) image.Dispose();ก่อนimage = new Bitmap...
เซอร์ภูซาน


2

ในรูปแบบหลักหรือการควบคุมผู้ใช้ที่อยู่ของภาพพื้นหลังตั้งค่าBackgroundImageLayoutคุณสมบัติการหรือCenter Stretchคุณจะสังเกตเห็นความแตกต่างอย่างมากเมื่อการแสดงผลการควบคุมของผู้ใช้


2

ฉันพยายามเพิ่มสิ่งนี้เป็นความคิดเห็น แต่ฉันมีคะแนนไม่เพียงพอ นี่เป็นสิ่งเดียวที่เคยช่วยปัญหาการกะพริบของฉันได้มากขอบคุณฮันส์สำหรับโพสต์ของเขา สำหรับใครก็ตามที่ใช้ตัวสร้าง c ++ เหมือนตัวเองนี่คือคำแปล

เพิ่มการประกาศ CreateParams ในไฟล์. h แบบฟอร์มหลักของแอปพลิเคชันของคุณเช่น

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

และเพิ่มสิ่งนี้ลงในไฟล์. cpp ของคุณ

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

2

ใส่โค้ดร้องในตัวสร้างหรือเหตุการณ์ OnLoad ของคุณและหากคุณใช้การควบคุมผู้ใช้แบบกำหนดเองบางประเภทที่มีการควบคุมย่อยคุณจะต้องตรวจสอบให้แน่ใจว่าการควบคุมแบบกำหนดเองเหล่านี้ได้รับบัฟเฟอร์สองครั้งด้วย (แม้ว่าในเอกสาร MS จะระบุว่า มันถูกตั้งค่าเป็นจริงตามค่าเริ่มต้น)

หากคุณกำลังทำการควบคุมแบบกำหนดเองคุณอาจต้องการเพิ่มแฟล็กนี้ใน ctor ของคุณ:

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

คุณสามารถเลือกที่จะใช้รหัสนี้ในแบบฟอร์ม / การควบคุมของคุณ:

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

เราทำซ้ำการควบคุมทั้งหมดในรูปแบบ / การควบคุมและการเข้าถึง DoubleBufferedคุณสมบัติจากนั้นเราเปลี่ยนเป็นจริงเพื่อให้การควบคุมแต่ละตัวในฟอร์มบัฟเฟอร์สองครั้ง เหตุผลที่เราไตร่ตรองที่นี่เป็นเพราะจินตนาการว่าคุณมีส่วนควบคุมที่มีการควบคุมเด็กที่ไม่สามารถเข้าถึงได้ด้วยวิธีนั้นแม้ว่าจะเป็นการควบคุมส่วนตัว แต่เราก็ยังคงเปลี่ยนคุณสมบัติให้เป็นจริง

ข้อมูลเพิ่มเติมเกี่ยวกับเทคนิคบัฟเฟอร์คู่สามารถพบได้ที่นี่

มีคุณสมบัติอื่นที่ฉันมักจะแทนที่เพื่อจัดเรียงปัญหานี้:

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

WS_EX_COMPOSITED - ระบายสีลูกหลานทั้งหมดของหน้าต่างตามลำดับการวาดภาพจากล่างขึ้นบนโดยใช้การบัฟเฟอร์สองครั้ง

ท่านสามารถหาข้อมูลเพิ่มเติมของธงรูปแบบเหล่านี้ที่นี่

หวังว่าจะช่วยได้!


1

เพียงเพื่อเพิ่มคำตอบที่ฮันส์ให้:

(รุ่น TLDR: ความโปร่งใสหนักกว่าที่คุณคิดใช้เฉพาะสีทึบทุกที่)

หาก WS_EX_COMPOSITED, DoubleBuffered และ WS_CLIPCHILDREN ไม่สามารถแก้ปัญหาการกะพริบของคุณได้ (สำหรับฉัน WS_CLIPCHILDREN ทำให้แย่ลงไปอีก) ลองทำสิ่งนี้: ใช้การควบคุมทั้งหมดและรหัสทั้งหมดของคุณและทุกที่ที่คุณมีความโปร่งใสหรือกึ่งโปร่งใสสำหรับ BackColor, ForeColor หรือ สีอื่น ๆ เพียงลบออกใช้สีทึบเท่านั้น ในกรณีส่วนใหญ่ที่คุณคิดว่าคุณต้องใช้ความโปร่งใสคุณไม่ทำ ออกแบบโค้ดและส่วนควบคุมของคุณใหม่และใช้สีทึบ ฉันมีอาการวูบวาบแย่มากและโปรแกรมก็ทำงานอืด เมื่อฉันลบความโปร่งใสมันจะเร่งความเร็วขึ้นอย่างมากและมีการกะพริบ 0 ครั้ง

แก้ไข: เพื่อเพิ่มเพิ่มเติมฉันเพิ่งค้นพบว่า WS_EX_COMPOSITED ไม่จำเป็นต้องกว้างหน้าต่างสามารถใช้ได้กับการควบคุมเฉพาะ! สิ่งนี้ช่วยฉันได้มาก เพียงสร้างการควบคุมแบบกำหนดเองที่สืบทอดมาจากการควบคุมที่คุณต้องการและวางการแทนที่ที่โพสต์ไว้แล้วสำหรับ WS_EX_COMPOSITED ด้วยวิธีนี้คุณจะได้รับบัฟเฟอร์สองเท่าในการควบคุมนี้เท่านั้นหลีกเลี่ยงผลข้างเคียงที่น่ารังเกียจในแอปพลิเคชันที่เหลือ!


0

ฉันรู้ว่าคำถามนี้เก่ามาก แต่ต้องการให้ประสบการณ์ของฉันกับคำถามนี้

ฉันมีปัญหามากมายเกี่ยวกับการTabcontrolกะพริบในรูปแบบที่มีการแทนที่OnPaintและ / หรือOnPaintBackGroundใน Windows 8 โดยใช้. NET 4.0

เพียงคนเดียวที่เขาคิดว่าทำงานได้ไม่ใช้Graphics.DrawImageวิธีการในOnPaintการแทนที่ในคำอื่น ๆ เมื่อวาดได้ทำโดยตรงกับกราฟิกให้โดยPaintEventArgsแม้ภาพวาดทั้งหมดสี่เหลี่ยมผืนผ้าริบหรี่ dissapeared แต่ถ้าเรียกใช้DrawImageเมธอดแม้กระทั่งการวาดบิตแมปที่ถูกตัด (สร้างขึ้นสำหรับการบัฟเฟอร์สองครั้ง) การกะพริบจะปรากฏขึ้น

หวังว่าจะช่วยได้!


0

ฉันรวมการแก้ไขการกะพริบนี้และการแก้ไขแบบอักษรนี้จากนั้นฉันต้องเพิ่มรหัสของตัวเองเล็กน้อยเพื่อเริ่มตัวจับเวลาในการระบายสีเพื่อยกเลิกการตรวจสอบ TabControl เมื่อออกจากหน้าจอและย้อนกลับเป็นต้น

ทั้งสามทำสิ่งนี้:

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

ฉันไม่ใช่ผู้สร้าง แต่จากสิ่งที่ฉันเข้าใจว่าบิตแมปทำให้ข้อผิดพลาดทั้งหมดข้ามไป

นี่เป็นสิ่งเดียวที่แก้ไข TabControl (พร้อมไอคอน) กะพริบได้อย่างชัดเจนสำหรับฉัน

วิดีโอผลลัพธ์ความแตกต่าง: vanilla tabcontrol vs tabcontrolex

http://gfycat.com/FineGlitteringDeermouse

ปล. คุณจะต้องตั้งค่า HotTrack = true เนื่องจากจะแก้ไขข้อบกพร่องนั้นด้วย


-2

คุณลองControl.DoubleBufferedProperty?

รับหรือตั้งค่าที่ระบุว่าตัวควบคุมนี้ควรวาดพื้นผิวของมันใหม่โดยใช้บัฟเฟอร์รองเพื่อลดหรือป้องกันการสั่นไหวหรือไม่

นอกจากนี้และนี้อาจช่วยให้


-9

ไม่จำเป็นต้องมีการบัฟเฟอร์สองเท่าและสิ่งที่พวก ...

วิธีง่ายๆ ...

หากคุณใช้อินเทอร์เฟซ MDI เพียงวางโค้ดด้านล่างในแบบฟอร์มหลัก มันจะลบการกะพริบทั้งหมดออกจากหน้า อย่างไรก็ตามบางหน้าซึ่งต้องใช้เวลาในการโหลดนานขึ้นจะปรากฏใน 1 หรือ 2 วินาที แต่จะดีกว่าการแสดงหน้ากะพริบซึ่งแต่ละรายการมาทีละรายการ

นี่เป็นทางออกเดียวที่ดีที่สุดสำหรับการใช้งานทั้งหมด ดูรหัสที่จะใส่ในรูปแบบหลัก:

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

12
สิ่งที่คุณกำลังพูดคือคำตอบที่ฮันส์ให้ไว้เมื่อสองปีที่แล้วคือถูกต้องหรือไม่? ขอบคุณ Kshitiz มีประโยชน์มากจริงๆ!
Fernando
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.