Saya memiliki kontrol yang harus saya modifikasi. Saya ingin benar-benar mencegahnya menggambar ulang sementara saya melakukan itu - SuspendLayout dan ResumeLayout tidak cukup. Bagaimana cara menangguhkan lukisan untuk kontrol dan anak-anaknya?
Saya memiliki kontrol yang harus saya modifikasi. Saya ingin benar-benar mencegahnya menggambar ulang sementara saya melakukan itu - SuspendLayout dan ResumeLayout tidak cukup. Bagaimana cara menangguhkan lukisan untuk kontrol dan anak-anaknya?
Jawaban:
Di pekerjaan saya sebelumnya, kami berjuang untuk mendapatkan aplikasi UI kami yang kaya untuk melukis secara instan dan lancar. Kami menggunakan kontrol .Net, kontrol kustom, dan kontrol devexpress standar.
Setelah banyak menggunakan googling dan reflektor saya menemukan pesan win32 WM_SETREDRAW. Ini benar-benar berhenti menggambar kontrol sementara Anda memperbaruinya dan dapat diterapkan, IIRC ke panel induk / yang mengandung.
Ini adalah kelas yang sangat sangat sederhana yang menunjukkan cara menggunakan pesan ini:
class DrawingControl
{
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
private const int WM_SETREDRAW = 11;
public static void SuspendDrawing( Control parent )
{
SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
}
public static void ResumeDrawing( Control parent )
{
SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
parent.Refresh();
}
}
Ada diskusi yang lebih lengkap tentang ini - google untuk C # dan WM_SETREDRAW, mis
Dan kepada siapa yang berkepentingan, ini adalah contoh serupa di VB:
Public Module Extensions
<DllImport("user32.dll")>
Private Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Boolean, ByVal lParam As IntPtr) As Integer
End Function
Private Const WM_SETREDRAW As Integer = 11
' Extension methods for Control
<Extension()>
Public Sub ResumeDrawing(ByVal Target As Control, ByVal Redraw As Boolean)
SendMessage(Target.Handle, WM_SETREDRAW, True, 0)
If Redraw Then
Target.Refresh()
End If
End Sub
<Extension()>
Public Sub SuspendDrawing(ByVal Target As Control)
SendMessage(Target.Handle, WM_SETREDRAW, False, 0)
End Sub
<Extension()>
Public Sub ResumeDrawing(ByVal Target As Control)
ResumeDrawing(Target, True)
End Sub
End Module
Control
kelas dasar untuk semua kontrol WinForms sudah melakukan untuk BeginUpdate
dan EndUpdate
metode. Mengirim pesan sendiri tidak lebih baik daripada menggunakan metode-metode itu untuk melakukan pekerjaan berat untuk Anda, dan tentu saja tidak dapat menghasilkan hasil yang berbeda.
Control.Handle
akan memaksa pegangan jendela dibuat dan dapat memengaruhi kinerja. Misalnya jika Anda memindahkan kontrol pada formulir sebelum ditampilkan, jika Anda memanggil ini SuspendDrawing
sebelumnya, gerakan Anda akan lebih lambat. Mungkin harus if (!parent.IsHandleCreated) return
diperiksa di kedua metode.
Berikut ini adalah solusi yang sama dari ng5000 tetapi tidak menggunakan P / Invoke.
public static class SuspendUpdate
{
private const int WM_SETREDRAW = 0x000B;
public static void Suspend(Control control)
{
Message msgSuspendUpdate = Message.Create(control.Handle, WM_SETREDRAW, IntPtr.Zero,
IntPtr.Zero);
NativeWindow window = NativeWindow.FromHandle(control.Handle);
window.DefWndProc(ref msgSuspendUpdate);
}
public static void Resume(Control control)
{
// Create a C "true" boolean as an IntPtr
IntPtr wparam = new IntPtr(1);
Message msgResumeUpdate = Message.Create(control.Handle, WM_SETREDRAW, wparam,
IntPtr.Zero);
NativeWindow window = NativeWindow.FromHandle(control.Handle);
window.DefWndProc(ref msgResumeUpdate);
control.Invalidate();
}
}
Message
dan di mana NativeWindow
; mencari dokumentasi untuk kelas bernama Message
tidak benar-benar menghibur.
Invalidate()
tidak bekerja sebagus Refresh()
kecuali diikuti oleh satu lagi pula.
Saya biasanya menggunakan versi modifikasi dari jawaban ngLink .
public class MyControl : Control
{
private int suspendCounter = 0;
private void SuspendDrawing()
{
if(suspendCounter == 0)
SendMessage(this.Handle, WM_SETREDRAW, false, 0);
suspendCounter++;
}
private void ResumeDrawing()
{
suspendCounter--;
if(suspendCounter == 0)
{
SendMessage(this.Handle, WM_SETREDRAW, true, 0);
this.Refresh();
}
}
}
Ini memungkinkan penangguhan / melanjutkan panggilan untuk disarangkan. Anda harus memastikan untuk mencocokkan masing SuspendDrawing
- masing dengan a ResumeDrawing
. Oleh karena itu, mungkin bukan ide yang baik untuk mempublikasikannya.
SuspendDrawing(); try { DrawSomething(); } finally { ResumeDrawing(); }
. Pilihan lain adalah untuk mengimplementasikan ini di IDisposable
kelas dan untuk melampirkan bagian gambar di- using
pernyataan. Pegangan akan diteruskan ke konstruktor, yang akan menangguhkan gambar.
DllImport
declare wParam
sebagai bool
?
Untuk membantu dengan tidak lupa mengaktifkan kembali gambar:
public static void SuspendDrawing(Control control, Action action)
{
SendMessage(control.Handle, WM_SETREDRAW, false, 0);
action();
SendMessage(control.Handle, WM_SETREDRAW, true, 0);
control.Refresh();
}
pemakaian:
SuspendDrawing(myControl, () =>
{
somemethod();
});
action()
melempar pengecualian? (Gunakan percobaan / akhirnya)
Solusi yang bagus tanpa menggunakan interop:
Seperti biasa, cukup aktifkan DoubleBuffered = true pada CustomControl Anda. Kemudian, jika Anda memiliki wadah seperti FlowLayoutPanel atau TableLayoutPanel, dapatkan kelas dari masing-masing jenis ini dan di konstruktor, aktifkan buffering ganda. Sekarang, cukup gunakan Wadah asal Anda dan bukan Wadah Windows.
class TableLayoutPanel : System.Windows.Forms.TableLayoutPanel
{
public TableLayoutPanel()
{
DoubleBuffered = true;
}
}
class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel
{
public FlowLayoutPanel()
{
DoubleBuffered = true;
}
}
Berdasarkan jawaban ng5000, saya suka menggunakan ekstensi ini:
#region Suspend
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
private const int WM_SETREDRAW = 11;
public static IDisposable BeginSuspendlock(this Control ctrl)
{
return new suspender(ctrl);
}
private class suspender : IDisposable
{
private Control _ctrl;
public suspender(Control ctrl)
{
this._ctrl = ctrl;
SendMessage(this._ctrl.Handle, WM_SETREDRAW, false, 0);
}
public void Dispose()
{
SendMessage(this._ctrl.Handle, WM_SETREDRAW, true, 0);
this._ctrl.Refresh();
}
}
#endregion
Menggunakan:
using (this.BeginSuspendlock())
{
//update GUI
}
Berikut ini adalah kombinasi dari ceztko's dan ng5000's untuk menghadirkan versi ekstensi VB yang tidak menggunakan pinvoke
Imports System.Runtime.CompilerServices
Module ControlExtensions
Dim WM_SETREDRAW As Integer = 11
''' <summary>
''' A stronger "SuspendLayout" completely holds the controls painting until ResumePaint is called
''' </summary>
''' <param name="ctrl"></param>
''' <remarks></remarks>
<Extension()>
Public Sub SuspendPaint(ByVal ctrl As Windows.Forms.Control)
Dim msgSuspendUpdate As Windows.Forms.Message = Windows.Forms.Message.Create(ctrl.Handle, WM_SETREDRAW, System.IntPtr.Zero, System.IntPtr.Zero)
Dim window As Windows.Forms.NativeWindow = Windows.Forms.NativeWindow.FromHandle(ctrl.Handle)
window.DefWndProc(msgSuspendUpdate)
End Sub
''' <summary>
''' Resume from SuspendPaint method
''' </summary>
''' <param name="ctrl"></param>
''' <remarks></remarks>
<Extension()>
Public Sub ResumePaint(ByVal ctrl As Windows.Forms.Control)
Dim wparam As New System.IntPtr(1)
Dim msgResumeUpdate As Windows.Forms.Message = Windows.Forms.Message.Create(ctrl.Handle, WM_SETREDRAW, wparam, System.IntPtr.Zero)
Dim window As Windows.Forms.NativeWindow = Windows.Forms.NativeWindow.FromHandle(ctrl.Handle)
window.DefWndProc(msgResumeUpdate)
ctrl.Invalidate()
End Sub
End Module
Saya tahu ini adalah pertanyaan lama, sudah dijawab, tetapi inilah pendapat saya tentang ini; Saya refactored penangguhan pembaruan menjadi IDisposable - dengan cara itu saya bisa melampirkan pernyataan yang ingin saya jalankan dalam using
pernyataan.
class SuspendDrawingUpdate : IDisposable
{
private const int WM_SETREDRAW = 0x000B;
private readonly Control _control;
private readonly NativeWindow _window;
public SuspendDrawingUpdate(Control control)
{
_control = control;
var msgSuspendUpdate = Message.Create(_control.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);
_window = NativeWindow.FromHandle(_control.Handle);
_window.DefWndProc(ref msgSuspendUpdate);
}
public void Dispose()
{
var wparam = new IntPtr(1); // Create a C "true" boolean as an IntPtr
var msgResumeUpdate = Message.Create(_control.Handle, WM_SETREDRAW, wparam, IntPtr.Zero);
_window.DefWndProc(ref msgResumeUpdate);
_control.Invalidate();
}
}
Ini bahkan lebih sederhana, dan mungkin hacky - karena saya dapat melihat banyak otot GDI di utas ini , dan jelas hanya cocok untuk skenario tertentu. YMMV
Dalam skenario saya, saya menggunakan apa yang akan saya sebut sebagai UserControl "Induk" - dan selama Load
acara tersebut, saya cukup menghapus kontrol-yang-akan-dimanipulasi dari .Controls
koleksi Induk , dan IndukOnPaint
mengurus sepenuhnya melukis anak itu kontrol dengan cara khusus apa pun .. sepenuhnya mengambil kemampuan melukis anak offline.
Sekarang, saya menyerahkan rutinitas cat anak saya ke metode ekstensi berdasarkan konsep ini dari Mike Gold untuk mencetak formulir windows .
Di sini saya membutuhkan sub-set label untuk merender tegak lurus terhadap tata letak:
Kemudian, saya membebaskan kontrol anak dari dicat, dengan kode ini di ParentUserControl.Load
pengendali acara:
Private Sub ParentUserControl_Load(sender As Object, e As EventArgs) Handles MyBase.Load
SetStyle(ControlStyles.UserPaint, True)
SetStyle(ControlStyles.AllPaintingInWmPaint, True)
'exempt this control from standard painting:
Me.Controls.Remove(Me.HostedControlToBeRotated)
End Sub
Kemudian, di ParentUserControl yang sama, kami melukis kontrol-untuk-dimanipulasi dari bawah ke atas:
Protected Overrides Sub OnPaint(e As PaintEventArgs)
'here, we will custom paint the HostedControlToBeRotated instance...
'twist rendering mode 90 counter clockwise, and shift rendering over to right-most end
e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
e.Graphics.TranslateTransform(Me.Width - Me.HostedControlToBeRotated.Height, Me.Height)
e.Graphics.RotateTransform(-90)
MyCompany.Forms.CustomGDI.DrawControlAndChildren(Me.HostedControlToBeRotated, e.Graphics)
e.Graphics.ResetTransform()
e.Graphics.Dispose()
GC.Collect()
End Sub
Setelah Anda meng-host ParentUserControl di suatu tempat, misalnya Formulir Windows - Saya menemukan bahwa Visual Studio 2015 saya merender formulir dengan benar pada Waktu Desain serta runtime:
Sekarang, karena manipulasi khusus saya memutar kontrol anak 90 derajat, saya yakin semua hot spot dan interaktivitas telah dihancurkan di wilayah itu - tetapi, masalah yang saya pecahkan adalah semua untuk label paket yang perlu melihat dulu dan mencetak, yang bekerja dengan baik untuk saya.
Jika ada cara untuk memperkenalkan kembali hot spot dan kontrol-ke kontrol yatim sengaja saya - saya ingin belajar tentang itu suatu hari nanti (bukan untuk skenario ini, tentu saja, tapi .. hanya untuk belajar). Tentu saja, WPF mendukung kegilaan OOTB .. tapi .. hei .. WinForms masih sangat menyenangkan, oke?