Cara memperbaiki kedipan di Kontrol pengguna

107

Dalam aplikasi saya, saya terus berpindah dari satu kontrol ke kontrol lainnya. Saya telah membuat no. kontrol pengguna, tetapi selama navigasi, kontrol saya berkedip. perlu 1 atau 2 detik untuk memperbarui. Saya mencoba mengatur ini

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

tetapi tidak membantu ... Setiap kontrol memiliki gambar latar yang sama dengan kontrol yang berbeda. Jadi apa solusinya ..
Terima kasih.

Royson
sumber
Dimana pernyataan ini? Idealnya, letakkan di konstruktor. Apakah Anda menelepon UpdateStylessetelah menyetel ini? Ini didokumentasikan dengan buruk, tetapi terkadang mungkin diperlukan.
Thomas

Jawaban:

306

Ini bukan jenis flicker yang dapat diatasi dengan buffer ganda. Maupun BeginUpdate atau SuspendLayout. Anda memiliki terlalu banyak kontrol, BackgroundImage bisa membuatnya jauh lebih buruk.

Ini dimulai saat UserControl mengecat dirinya sendiri. Ini menggambar BackgroundImage, meninggalkan lubang di mana jendela kontrol anak berada. Setiap kontrol anak kemudian mendapat pesan untuk melukis dirinya sendiri, mereka akan mengisi lubang dengan konten jendela mereka. Jika Anda memiliki banyak kontrol, lubang itu akan terlihat oleh pengguna untuk sementara waktu. Mereka biasanya berwarna putih, sangat kontras dengan BackgroundImage saat gelap. Atau mereka bisa menjadi hitam jika formulir memiliki properti Opacity atau TransparencyKey, sangat kontras dengan apa saja.

Ini adalah batasan yang cukup mendasar dari Windows Forms, ini terjebak dengan cara Windows merender jendela. Diperbaiki oleh WPF btw, tidak menggunakan windows untuk kontrol anak. Yang Anda inginkan adalah melakukan buffer ganda pada seluruh formulir, termasuk kontrol anak. Itu mungkin, periksa kode saya di utas ini untuk solusinya. Ini memiliki efek samping, dan tidak benar-benar meningkatkan kecepatan melukis. Kodenya sederhana, tempelkan ini di formulir Anda (bukan kontrol pengguna):

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

Ada banyak hal yang bisa Anda lakukan untuk meningkatkan kecepatan mengecat, sampai-sampai kedipan tidak terlihat lagi. Mulailah dengan menangani BackgroundImage. Harganya bisa sangat mahal jika gambar sumbernya besar dan perlu diciutkan agar sesuai dengan kontrol. Ubah properti BackgroundImageLayout menjadi "Tile". Jika itu memberikan percepatan yang nyata, kembali ke program pengecatan Anda dan ubah ukuran gambar agar lebih cocok dengan ukuran kontrol biasa. Atau tulis kode dalam metode OnResize () UC untuk membuat salinan gambar dengan ukuran yang tepat sehingga tidak perlu diubah ukurannya setiap kali kontrol menggambar ulang. Gunakan format piksel Format32bppPArgb untuk salinan itu, ini merender sekitar 10 kali lebih cepat daripada format piksel lainnya.

Hal berikutnya yang dapat Anda lakukan adalah mencegah lubang agar tidak terlalu terlihat dan sangat kontras dengan gambar. Anda bisa mematikan bendera gaya WS_CLIPCHILDREN untuk UC, bendera yang mencegah UC mengecat di area tempat kontrol anak berada. Tempel kode ini di kode UserControl:

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

Kontrol anak sekarang akan mewarnai dirinya sendiri di atas gambar latar belakang. Anda mungkin masih melihat mereka mengecat dirinya sendiri satu per satu, tetapi lubang putih atau hitam di tengah yang jelek tidak akan terlihat.

Last but not least, mengurangi jumlah kontrol anak selalu merupakan pendekatan yang baik untuk menyelesaikan masalah melukis yang lambat. Ganti acara OnPaint () UC dan gambar apa yang sekarang ditampilkan pada anak. Label dan PictureBox tertentu sangat boros. Nyaman untuk tunjuk dan klik tetapi alternatifnya yang ringan (menggambar string atau gambar) hanya membutuhkan satu baris kode dalam metode OnPaint () Anda.

Hans Passant
sumber
Matikan WS_CLIPCHILDREN pengalaman pengguna yang lebih baik untuk saya.
Mahesh
Benar-benar sempurna! .. Terima kasih banyak
AlejandroAlis
8

Ini adalah masalah nyata, dan jawaban yang diberikan Hans Passant sangat bagus untuk menyimpan flicker. Namun, ada efek samping seperti yang dia sebutkan, dan mereka bisa jelek (UI jelek). Seperti yang dinyatakan, "Anda dapat mematikan WS_CLIPCHILDRENbendera gaya untuk UC", tetapi itu hanya mematikannya untuk UC. Komponen pada formulir utama masih mengalami kendala.

Contoh, panel scroll bar tidak dicat, karena secara teknis berada di area anak. Namun komponen anak tidak menggambar bilah gulir, sehingga tidak dicat sampai mouse di atas (atau peristiwa lain memicunya).

Selain itu, ikon animasi (mengubah ikon dalam loop tunggu) tidak berfungsi. Menghapus ikon pada tabPage.ImageKeytidak mengubah ukuran / mengecat ulang tabPages lain dengan tepat.

Jadi saya sedang mencari cara untuk mematikan WS_CLIPCHILDRENlukisan awal sehingga Formulir saya akan memuat lukisan dengan baik, atau lebih baik lagi hanya menyalakannya saat mengubah ukuran formulir saya dengan banyak komponen.

Caranya adalah dengan mendapatkan aplikasi untuk memanggil CreateParamsdengan WS_EX_COMPOSITED/WS_CLIPCHILDRENgaya yang diinginkan . Saya menemukan peretasan di sini ( https://web.archive.org/web/20161026205944/http://www.angryhacker.com/blog/archive/2010/07/21/how-to-get-rid-of- flicker-on-windows-forms-application.aspx ) dan berfungsi dengan baik. Terima kasih AngryHacker!

Saya melakukan TurnOnFormLevelDoubleBuffering()panggilan dalam bentuk ResizeBeginacara dan TurnOffFormLevelDoubleBuffering()memanggil dalam bentuk acara ResizeEnd (atau biarkan saja WS_CLIPCHILDRENsetelah itu awalnya dicat dengan benar.)

    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;
    }
pengguna2044810
sumber
Kode Anda tidak menyertakan metode TurnOnFormLevelDoubleBuffering () ...
Dan W
@DanW Lihat URL yang diposting di jawaban ini ( angryhacker.com/blog/archive/2010/07/21/… )
ChrisB
Tautan dalam jawaban ini tampaknya sudah mati. Saya ingin tahu solusinya, apakah Anda memiliki tautan ke contoh lain?
Pratt Hinds
6

Jika Anda melakukan pengecatan khusus dalam kontrol (misalnya, menimpa OnPaint), Anda dapat mencoba sendiri buffering ganda.

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

Dan batalkan kendali Anda dengan properti NeedRepaint

Jika tidak, jawaban di atas dengan SuspendLayout dan ResumeLayout mungkin adalah yang Anda inginkan.

Patrick
sumber
Ini adalah metode kreatif untuk mensimulasikan doublebuffer !. Anda dapat menambahkan if (image != null) image.Dispose();sebelumnyaimage = new Bitmap...
S. Serpooshan
2

Pada formulir utama atau kontrol pengguna tempat gambar latar belakang berada, setel BackgroundImageLayoutproperti ke Centeratau Stretch. Anda akan melihat perbedaan besar saat kontrol pengguna melakukan rendering.

revobtz
sumber
2

Saya mencoba menambahkan ini sebagai komentar tetapi saya tidak memiliki cukup poin. Ini adalah satu-satunya hal yang pernah membantu masalah kelap-kelip saya, terima kasih kepada Hans untuk posnya. Untuk siapa pun yang menggunakan c ++ builder seperti saya, inilah terjemahannya

Tambahkan deklarasi CreateParams ke file .h formulir utama aplikasi Anda, misalnya

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

dan tambahkan ini ke file .cpp Anda

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

Letakkan kode di bawah ini di konstruktor Anda atau acara OnLoad dan jika Anda menggunakan semacam kontrol pengguna kustom yang memiliki sub kontrol, Anda harus memastikan bahwa kontrol kustom ini juga buffer ganda (meskipun dalam dokumentasi MS mereka mengatakan ini disetel ke true secara default).

Jika Anda membuat kontrol kustom, Anda mungkin ingin menambahkan flag ini ke ctor Anda:

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

Secara opsional, Anda dapat menggunakan kode ini di Formulir / Kontrol Anda:

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

Kami melakukan iterasi melalui semua kontrol dalam formulir / kontrol dan mengakses DoubleBufferedpropertinya dan kemudian kami mengubahnya menjadi benar untuk membuat setiap kontrol pada formulir buffer ganda. Alasan kami melakukan refleksi di sini, adalah karena bayangkan Anda memiliki kontrol yang memiliki kontrol anak yang tidak dapat diakses, dengan demikian, meskipun kontrol pribadi, kami akan tetap mengubah propertinya menjadi true.

Informasi lebih lanjut tentang teknik buffering ganda dapat ditemukan di sini .

Ada properti lain yang biasanya saya ganti untuk menyelesaikan masalah ini:

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

WS_EX_COMPOSITED - Melukis semua turunan jendela dengan urutan pengecatan dari bawah ke atas menggunakan buffering ganda.

Anda dapat menemukan lebih banyak dari bendera gaya ini di sini .

Semoga membantu!


sumber
1

Hanya untuk menambah jawaban yang diberikan Hans:

(Versi TLDR: Transparansi lebih berat dari yang Anda pikirkan, gunakan hanya warna solid di mana saja)

Jika WS_EX_COMPOSITED, DoubleBuffered dan WS_CLIPCHILDREN tidak menyelesaikan flicker Anda (bagi saya WS_CLIPCHILDREN membuatnya lebih buruk), coba ini: melalui SEMUA kontrol Anda dan semua kode Anda, dan di mana pun Anda memiliki transparansi atau semi-transparansi untuk BackColor, ForeColor, atau warna lain, hapus saja, gunakan hanya warna solid. Dalam kebanyakan kasus di mana Anda berpikir Anda hanya perlu menggunakan transparansi, Anda tidak melakukannya. Rancang ulang kode dan kontrol Anda, dan gunakan warna solid. Saya mengalami kedipan yang sangat buruk dan program berjalan lamban. Setelah saya menghapus transparansi, itu dipercepat secara signifikan, dan ada 0 flicker.

EDIT: Untuk menambahkan lebih jauh, saya baru saja menemukan bahwa WS_EX_COMPOSITED tidak harus selebar jendela, itu dapat diterapkan hanya untuk kontrol tertentu! Ini menyelamatkan saya dari banyak masalah. Cukup buat kontrol kustom yang diwarisi dari kontrol apa pun yang Anda butuhkan, dan tempelkan penggantian yang sudah diposting untuk WS_EX_COMPOSITED. Dengan cara ini Anda mendapatkan buffer ganda tingkat rendah pada kontrol ini saja, menghindari efek samping yang tidak menyenangkan di seluruh aplikasi!

Daniel
sumber
0

Saya tahu pertanyaan ini sudah sangat tua, tetapi ingin memberikan pengalaman saya tentang itu.

Saya memiliki banyak masalah dengan Tabcontrolkedipan dalam bentuk diganti OnPaintdan / atau OnPaintBackGrounddi Windows 8 menggunakan .NET 4.0.

Hanya berpikir bahwa bekerja telah TIDAK MENGGUNAKAN dengan Graphics.DrawImagemetode OnPaintmenimpa, dengan kata lain, ketika imbang dilakukan langsung ke Graphics disediakan oleh PaintEventArgs, bahkan lukisan semua persegi panjang, kerlip menghilang. Tetapi jika memanggil DrawImagemetode, bahkan menggambar Bitmap yang terpotong, (dibuat untuk buffering ganda) flicker akan muncul.

Semoga membantu!

ZeroWorks
sumber
0

Saya menggabungkan perbaikan flicker ini dan perbaikan font ini , lalu saya harus menambahkan sedikit kode saya sendiri untuk memulai timer pada paint untuk Membatalkan TabControl saat keluar dari layar dan mundur, dll ..

Ketiganya membuat ini:

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

Saya bukan pencipta tetapi dari apa yang saya pahami bahwa bitmap melakukan semua bug melewati.

Ini adalah satu-satunya hal yang secara definitif memecahkan tabControl (dengan Ikon) berkedip untuk saya.

perbedaan hasil video: vanilla tabcontrol vs tabcontrolex

http://gfycat.com/FineGlitteringDeermouse

ps. Anda perlu menyetel HotTrack = true, karena ini memperbaiki bug itu juga

pengguna3732487
sumber
-2

Apakah Anda mencoba Control.DoubleBufferedProperti?

Mendapat atau menyetel nilai yang menunjukkan apakah kontrol ini harus menggambar ulang permukaannya menggunakan buffer sekunder untuk mengurangi atau mencegah flicker.

Juga ini dan ini mungkin bisa membantu.

KMån
sumber
-9

Tidak perlu buffering ganda dan semua itu guys ...

Solusi sederhana ...

Jika Anda menggunakan MDI Interface, cukup tempelkan kode di bawah ini pada formulir utama. Ini akan menghapus semua kedipan dari halaman. Namun beberapa halaman yang membutuhkan lebih banyak waktu untuk memuat akan muncul dalam 1 atau 2 detik. Tapi ini lebih baik daripada menampilkan halaman yang berkedip-kedip di mana setiap item muncul satu per satu.

Ini adalah satu-satunya solusi terbaik untuk seluruh aplikasi. Lihat kode untuk dimasukkan ke dalam formulir utama:

protected override CreateParams CreateParams {
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    return cp;
  }
} 
Kshitiz
sumber
12
Jadi, apa yang Anda katakan adalah bahwa jawaban yang diberikan Hans lebih dari dua tahun yang lalu adalah benar? Terima kasih, Kshitiz. Itu memang sangat membantu!
Fernando