85458


C#でpagePanel（Panel
コントロール）上に配置したListViewがサイズ変更時やリストアイテム更新時にちらつくため、以下のコードにてちらつきを抑えています。  
しかし、この方法ではListViewのLargeIcon、SmallIcon、Listでは問題なくダブルバッファが働きますがDetails状態の時、ListViewが真っ黒になってしまいます。対処方法をご存じでしたらお教えください。  
なお、ListViewを継承した独自クラスを作成し、DoubleBuffered =
trueとする方法ではちらつきは解消されませんので、他の解決方法を知りたいです。

[code]

        [DllImport("user32.dll")]
        private static extern UInt32 GetWindowLong(IntPtr hWnd, int index);
    
        [DllImport("user32.dll")]
        private static extern UInt32 SetWindowLong(IntPtr hWnd, int index, UInt32 unValue);
    
        const int GWL_EXSTYLE = -20;
        const UInt32 WS_EX_COMPOSITED = 0x02000000;
    
        public void PagePanelDoubleBufferOn()
        {
            UInt32 exStyle = GetWindowLong(pagePanel.Handle, GWL_EXSTYLE);
            SetWindowLong(pagePanel.Handle, GWL_EXSTYLE, exStyle | WS_EX_COMPOSITED);
        }
    
        public void PagePanelDoubleBufferOff()
        {
            UInt32 exStyle = GetWindowLong(pagePanel.Handle, GWL_EXSTYLE);
            SetWindowLong(pagePanel.Handle, GWL_EXSTYLE, exStyle & ~WS_EX_COMPOSITED);
        }
    
[/code]


CC BY-SA 4.0
2022-01-01T12:00:49.203
85431
2022-01-03T11:57:51.650
question
c#
C#のListView（details）でダブルバッファすると真っ黒になる


ダブルバッファを使うようにしてみました。  
カラムがパラパラ表示されることはなくなりますが、少し重たい印象をうけるかもしれません。

[code]

    using System;
    using System.Drawing;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    public class ListViewEx : ListView
    {
        protected override void WndProc(ref Message m) {
            switch (m.Msg) {
                case WM_PAINT:
                    WmPaint(ref m);
                    break;
    
                case WM_ERASEBKGND:
                    // 無視
                    break;
    
                default:
                    base.WndProc(ref m);
                    break;
            }
        }
    
        private void WmPaint(ref Message m) {
            if (m.WParam == IntPtr.Zero) {
                PAINTSTRUCT ps = new PAINTSTRUCT();
                IntPtr dc = BeginPaint(m.HWnd, ref ps);
                try {
                    var context = BufferedGraphicsManager.Current;
                    using (var bufferedGraphics = context.Allocate(dc, ClientRectangle)) {
                        var clip = Rectangle.FromLTRB(ps.rcPaint.Left, ps.rcPaint.Top,
                                                     ps.rcPaint.Right, ps.rcPaint.Bottom);
                        bufferedGraphics.Graphics.SetClip(clip);
                        DrawNative(bufferedGraphics.Graphics, clip);
                        bufferedGraphics.Render();
                    }
                } finally {
                    EndPaint(m.HWnd, ref ps);
                }
            } else {
                using (var g = Graphics.FromHdc(m.WParam)) {
                    DrawNative(g, ClientRectangle);
                }
            }
        }
    
        private void DrawNative(Graphics g, Rectangle clip) {
            using (var brush = new SolidBrush(BackColor)) {
                g.FillRectangle(brush, clip);
            }
            IntPtr hdc = g.GetHdc();
            try {
                IntPtr flags = (IntPtr)(PRF_CHILDREN | PRF_CLIENT | PRF_ERASEBKGND);
                Message msg = Message.Create(Handle, WM_PRINT, hdc, flags);
                base.DefWndProc(ref msg);
            } finally {
                g.ReleaseHdc();
            }
        }
    
        private const int
            WM_PAINT            = 0x000F,
            WM_ERASEBKGND       = 0x0014,
            WM_PRINT            = 0x0317;
    
        private const int
            PRF_CHECKVISIBLE    = 0x00000001,
            PRF_NONCLIENT       = 0x00000002,
            PRF_CLIENT          = 0x00000004,
            PRF_ERASEBKGND      = 0x00000008,
            PRF_CHILDREN        = 0x00000010;
    
        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int Left, Top, Right, Bottom;
        }
    
        [StructLayout(LayoutKind.Sequential)]
        private struct PAINTSTRUCT
        {
            public IntPtr hdc;
            public bool fErase;
            public RECT rcPaint;
            public bool fRestore;
            public bool fIncUpdate;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] 
            public byte[] rgbReserved;
        }
    
        [DllImport("user32.dll")]
        private static extern IntPtr BeginPaint(IntPtr hWnd, ref PAINTSTRUCT lpPaint);
    
        [DllImport("user32.dll")]
        private static extern bool EndPaint(IntPtr hWnd, ref PAINTSTRUCT lpPaint);
    }
    
[/code]

【解説】

コントロールの描画は WM_PAINT メッセージに応答することで行われています。

「WM_PAINT メッセージの使用」  
<https://docs.microsoft.com/ja-jp/windows/win32/gdi/using-the-wm-paint-
message>

「クライアント領域での描画」  
<https://docs.microsoft.com/ja-jp/windows/win32/gdi/drawing-in-the-client-
area>

通常は BeginPaint API で得られた HDC (デバイスコンテキストハンドル)
に対して描画処理を行うのですが、描画するものが多くなるとちらつきが発生します。  
そこで、いったんバッファにそれらを書き込み、完了したら HDC に一挙に転送するのがダブルバッファ処理です。

BufferedGraphicsManager で BufferedGraphics(バッファ)を確保し、書き込みを行った後、Render
メソッドで一挙に転送しています。

バッファへの書き込みには WM_PRINT メッセージを使用しています。

「WM_PRINT メッセージ」  
<https://docs.microsoft.com/ja-jp/windows/win32/gdi/wm-print>

Control.DrawToBitmap で呼ばれているメッセージです。  
wParam に HDC, lParam にどの部分を描画するかをセットしてメッセージを送ると HDC に描画してくれます。

この方法は、DrawToBitmap が正常に動作するコントロールならどれでも使えます。  
Windows Forms のコントロールの中では RichTextBox, WebBrowser が使えないようです。

TexBox 等、多くのコントロールは WM_PAINT の wPram に HDC をセットすると描画してくれるのですが、ListView
はそうなっていなかったようです。

このしくみを使うと、Bitmap に描画させ、それを加工して描画することもできます。

TextBox にプレースホルダを表示したり、DateTimePicker の文字色や背景色を変更することも可能です。

「WM_PAINTでTextBoxがちらつく」  
<https://social.msdn.microsoft.com/Forums/ja-
JP/d5ac1426-1082-43a1-a02f-677dad1ee3ca/wmpaint12391textbox1236412385124251238812367?forum=csharpgeneralja>

「DateTimePickerの文字色を変更したい」  
<https://social.msdn.microsoft.com/Forums/ja-
JP/80860829-22a7-4647-999e-ad594ccff48b/datetimepicker12398259912338333394124342279326356123751238312356?forum=csharpgeneralja>


CC BY-SA 4.0
2022-01-03T03:56:27.167
85458
2022-01-03T11:57:51.650
2022-01-03T11:57:51.650
50741
50741
85431
answer