簡體   English   中英

如何在調整大小 (C#) 時防止無邊框 Windows 窗體閃爍?

[英]How to prevent a borderless Windows Form from flickering when resizing (C#)?

[C# .NET 4.0]

我正在學習 C# 並且我正在嘗試使用 C# 構建一個 Windows 窗體,它具有FormBorderStyle = FormBorderStyle.None並且可以使用 Windows API 移動/調整大小。 例如,我使用用於 Google Chrome 和 Norton 360 的圓角或自定義(可移動/可調整大小)邊框設計作為表單的基礎。

到目前為止,我已經取得了很大的進步,並且一切正常,除了當我調整表單大小時,當您快速調整表單大小時,沿着右邊框和下邊框的長度會出現黑色/白色閃爍

我試過在構造函數中添加this.DoubleBuffer = true並且也試過this.SetStyles(ControlStyles.AllPaintInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true); .

因為我是圖形方面的傻瓜,我喜歡控制表單的完整設計,所以我可以看到這是永遠困擾我的東西......所以如果有人可以幫助我解決這個問題,那么閃爍不再發生,這對我的學習過程非常有用。

我還應該提到我使用的是 Windows XP,所以我不確定這篇文章是否會對我有所幫助,因為它似乎專注於 Vista/7(使用 DWM)......並不是說我還不夠先進了解該帖子中的所有內容。

與 API 一起使用的代碼的兩部分如下。 我有一個 Windows API 的 WM_NCHITTEST 的公共枚舉......你可以在這個鏈接中看到這些值。

OnPaint 覆蓋方法

protected override void OnPaint(PaintEventArgs e)
{
    System.IntPtr ptrBorder = CreateRoundRectRgn(0, 0,
        this.ClientSize.Width, this.ClientSize.Height, 15, 15);

    SetWindowRgn(this.Handle, ptrBorder, true);

    Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip,
        this.ClientSize.Height - cGrip, cGrip, cGrip);
    ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc);
    rc = new Rectangle(0, 0, this.ClientSize.Width, 32);
    e.Graphics.FillRectangle(Brushes.SlateGray, rc);
}

WndProc 覆蓋方法

protected override void WndProc(ref Message m)
{
    if (m.Msg == (int)HitTest.WM_NCHITTEST)
    {
        // Trap WM_NCHITTEST
        Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
        pos = this.PointToClient(pos);

        if (pos.Y < cCaption)
        {
            m.Result = (IntPtr)HitTest.HTCAPTION;
            return;
        }

        if (pos.X <= cGrip && pos.Y >= this.ClientSize.Height - cGrip)
        {
            m.Result = (IntPtr)HitTest.HTBOTTOMLEFT;
            return;
        }

        if (pos.X >= this.ClientSize.Width - cGrip &&
            pos.Y >= this.ClientSize.Height - cGrip)
        {
            m.Result = (IntPtr)HitTest.HTBOTTOMRIGHT;
            return;
        }

        if (pos.X >= this.ClientSize.Width - cBorder)
        {
            m.Result = (IntPtr)HitTest.HTRIGHT;
            return;
        }

        if (pos.Y >= this.ClientSize.Height - cBorder)
        {
            m.Result = (IntPtr)HitTest.HTBOTTOM;
            return;
        }

        if (pos.X <= cBorder)
        {
            m.Result = (IntPtr)HitTest.HTLEFT;
            return;
        }
    }

    base.WndProc(ref m);
}

這是完整的代碼

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace PracticeForm
{
    public partial class Form2 : Form
    {
        [DllImport("user32.dll")]
        private static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw);

        [DllImport("gdi32.dll")]
        private static extern IntPtr CreateRoundRectRgn(int x1, int y1, int x2, int y2, int cx, int cy);

        [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
        private static extern bool DeleteObject(System.IntPtr hObject);

        private const int cGrip = 20;
        private const int cCaption = 35;
        private const int cBorder = 7;
        private Point mouseOffset;

        public Form2()
        {
            InitializeComponent();
            this.FormBorderStyle = FormBorderStyle.None;
            this.MaximumSize = new Size(670, 440);
            this.DoubleBuffered = true;
            this.SetStyle(ControlStyles.ResizeRedraw |
                          ControlStyles.OptimizedDoubleBuffer |
                          ControlStyles.AllPaintingInWmPaint |
                          ControlStyles.UserPaint, true);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            System.IntPtr ptrBorder = CreateRoundRectRgn(0, 0,
                this.ClientSize.Width, this.ClientSize.Height, 15, 15);

            SetWindowRgn(this.Handle, ptrBorder, true);

            Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip,
                this.ClientSize.Height - cGrip, cGrip, cGrip);
            ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc);
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == (int)HitTest.WM_NCHITTEST)
            {
                // Trap WM_NCHITTEST
                Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
                pos = this.PointToClient(pos);

                if (pos.Y < cCaption)
                {
                    m.Result = (IntPtr)HitTest.HTCAPTION;
                    return;
                }

                if (pos.X <= cGrip && pos.Y >= this.ClientSize.Height - cGrip)
                {
                    m.Result = (IntPtr)HitTest.HTBOTTOMLEFT;
                    return;
                }

                if (pos.X >= this.ClientSize.Width - cGrip &&
                    pos.Y >= this.ClientSize.Height - cGrip)
                {
                    m.Result = (IntPtr)HitTest.HTBOTTOMRIGHT;
                    return;
                }

                if (pos.X >= this.ClientSize.Width - cBorder)
                {
                    m.Result = (IntPtr)HitTest.HTRIGHT;
                    return;
                }

                if (pos.Y >= this.ClientSize.Height - cBorder)
                {
                    m.Result = (IntPtr)HitTest.HTBOTTOM;
                    return;
                }

                if (pos.X <= cBorder)
                {
                    m.Result = (IntPtr)HitTest.HTLEFT;
                    return;
                }
            }

            base.WndProc(ref m);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void button2_MouseClick(object sender, MouseEventArgs e)
        {
            this.WindowState = FormWindowState.Minimized;
        }

        private void panel1_MouseDown(object sender, MouseEventArgs e)
        {
            mouseOffset = new Point(-e.X, -e.Y);
        }

        private void panel1_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Point p = Control.MousePosition;
                p.Offset(mouseOffset.X, mouseOffset.Y);
                Location = p;
            }
        }

        private void label1_MouseDown(object sender, MouseEventArgs e)
        {
            mouseOffset = new Point(-e.X, -e.Y);
        }

        private void label1_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                Point p = Control.MousePosition;
                p.Offset(mouseOffset.X, mouseOffset.Y);
                Location = p;
            }
        }
    }
}

謝謝您的幫助。

閃爍的發生是因為您的顯示器區域正在迅速改變顏色,而這又是因為您過度繪制 - 在同一像素上繪制了不止一件東西。

發生這種情況是因為:

  • 如果您的重繪速度很慢,那么您正在繪制的屏幕上的內容(例如窗口邊框)將在一段時間內可見。 例如,用戶可能會看到滾動條的兩個副本,直到您用表單的內容擦除舊的滾動條。
  • windows 會自動為您擦除窗口的背景,通常為白色。 因此,在您用正確的圖像過度繪制之前,您繪圖的任何非白色區域都會閃爍白色。
  • 如果你在同一個地方繪制多個東西,當你不斷改變屏幕那個區域的顏色時,你會看到閃爍

要解決這些問題,您需要綜合考慮(越多越好)

  • 禁用擦除背景,或將擦除顏色設置為圖像中的主色
  • 優化您的重繪代碼以使其更快,因此閃爍不那么突出
  • 優化您的重繪代碼以消除過度繪制。 例如,要在矩形頁面周圍放置邊框,您可以繪制背景顏色並隨頁面過度繪制,但這會閃爍。 相反,將頂部邊框繪制為矩形,然后繪制左下方和右下方,然后在中間繪制頁面。 由於 nopixels 被繪制多次,它不會閃爍
  • 在您的控件上啟用 DoubleBuffered 模式。 這樣,您的所有繪圖實際上都發生在內存中的位圖圖像中,然后將最終圖像復制到屏幕上,這樣每個像素只顯示一次並且沒有閃爍。

雖然這是一個相當老的線程,並且 OP 可能已經找到了解決他的問題的方法並繼續前進,但我想添加一些額外的要點,以防它們被證明對正在解決類似問題的 .NET 開發人員有益.

首先,我要感謝您嘗試在 Windows XP 上解決這個問題。 我去過那里,在那里度過了很多小時,並因此吸取了所有艱難的教訓。 不幸的是,由於 Windows XP 缺少我們大多數人已經習慣的 DWM,因此沒有簡單的解決方案。

正確設置 ControlStyles 絕對重要——我還將包括:

SetStyle(ControlStyles.Opaque, True)

對您打算繪制的控件進行雙緩沖很重要,因為閃爍主要是由在監視器垂直回掃中間重繪的控件引起的。 僅僅因為您調用了 Invalidate(),並不一定意味着該控件會在您想要的時候重新繪制——您受 Windows 的支配,操作系統會在它准備好時執行它。 您可以在 Windows XP 上解決這個問題(就像我所做的那樣),方法是利用 DirecDraw 7 中的 WaitForVerticalBlank 之類的功能(Windows XP 上對該 API 有很多支持),還可以使用 GetVerticalBlankStatus 和 GetScanLine 相應地為渲染和演示計時。

僅在 Form 實際更改SIZE時才設置 Region ,而不是每次都在 Paint() 事件中:

    protected override void OnSizeChanged(EventArgs e)
    {
        base.OnSizeChanged(e);

        System.IntPtr ptrBorder = CreateRoundRectRgn(0, 0,
            this.ClientSize.Width, this.ClientSize.Height, 15, 15);

        SetWindowRgn(this.Handle, ptrBorder, true);
    }

    protected override void OnPaint(PaintEventArgs e)
    {

        Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip,
            this.ClientSize.Height - cGrip, cGrip, cGrip);
        ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc);
    }

試試這個:

如何停止閃爍的 C# winforms

我的無邊框表單上有面板作為標題欄,標題欄面板上出現閃爍問題,並在表單加載中添加了此代碼,閃爍消失了。

var prop = TitleBar_panel.GetType().GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic);
            prop.SetValue(TitleBar_panel, true, null);

TitleBar_panel是閃爍的控件。

編輯:現在只有當我從表單的左側調整它的大小時它才會閃爍。 所以它不是 100% 由這段代碼解決的

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM