Büyük değişiklikler yapmam gereken bir kontrolüm var. Bunu yaparken tamamen yeniden çizilmesini önlemek istiyorum - SuspendLayout ve ResumeLayout yeterli değil. Bir kontrol ve çocukları için tabloyu nasıl askıya alabilirim?
Büyük değişiklikler yapmam gereken bir kontrolüm var. Bunu yaparken tamamen yeniden çizilmesini önlemek istiyorum - SuspendLayout ve ResumeLayout yeterli değil. Bir kontrol ve çocukları için tabloyu nasıl askıya alabilirim?
Yanıtlar:
Bir önceki işimde zengin UI uygulamamızı anında ve sorunsuz bir şekilde boyamakla uğraştık. Standart .Net kontrolleri, özel kontroller ve devexpress kontrolleri kullanıyorduk.
Birçok googling ve reflektör kullanımından sonra WM_SETREDRAW win32 mesajıyla karşılaştım. Bu, kontrolleri çiziminizi güncellerken gerçekten durdurur ve IIRC ana / içeren panele uygulanabilir.
Bu, bu mesajın nasıl kullanılacağını gösteren çok basit bir sınıftır:
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();
}
}
Bununla ilgili daha ayrıntılı tartışmalar var - C # ve WM_SETREDRAW için google, ör.
Ve endişe duyabileceği için, bu VB'de benzer bir örnektir:
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
için taban sınıf tüm WinForms kontrollerin zaten yapar için BeginUpdate
ve EndUpdate
yöntemlerle. Mesajı kendiniz göndermek, sizin için ağır kaldırma yapmak için bu yöntemleri kullanmaktan daha iyi değildir ve kesinlikle farklı sonuçlar üretemez.
Control.Handle
, pencere tutamacının oluşturulmasını zorlar ve performansı etkileyebilir. Örneğin, bir formu görüntülenmeden önce bir form üzerinde taşıyorsanız, bunu SuspendDrawing
önceden çağırırsanız , hareketiniz daha yavaş olacaktır. Muhtemelen if (!parent.IsHandleCreated) return
her iki yöntemde de kontroller olmalıdır .
Aşağıdaki ng5000 ile aynı çözümdür, ancak P / Invoke kullanmaz.
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
ve NativeWindow
ne olduğunu göstermek çok daha iyi olurdu ; adlı bir sınıfın belgelerini aramak Message
gerçekten eğlenceli değil.
Invalidate()
Refresh()
zaten bunu izlemedikçe iyi çalışmaz .
Genellikle ngLink'in cevabının biraz değiştirilmiş bir versiyonunu kullanıyorum .
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();
}
}
}
Bu, aramaları askıya alma / devam ettirmeye olanak sağlar. Her birini a SuspendDrawing
ile eşleştirdiğinizden emin olmalısınız ResumeDrawing
. Bu nedenle, onları herkese açık hale getirmek muhtemelen iyi bir fikir olmaz.
SuspendDrawing(); try { DrawSomething(); } finally { ResumeDrawing(); }
. Diğer bir seçenek, bunu bir IDisposable
sınıfta uygulamak ve çizim bölümünü bir using
-statement içine almaktır. Sap, çizimi askıya alacak olan kurucuya geçirilecekti.
DllImport
declare wParam
olarak bool
?
Çizimi yeniden etkinleştirmeyi unutmamanıza yardımcı olmak için:
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();
}
kullanımı:
SuspendDrawing(myControl, () =>
{
somemethod();
});
action()
bir istisna atar? (Sonunda deneyin / sonunda)
Birlikte çalışma özelliği kullanmadan hoş bir çözüm:
Her zaman olduğu gibi, CustomControl üzerinde DoubleBuffered = true özelliğini etkinleştirmeniz yeterlidir. Daha sonra, FlowLayoutPanel veya TableLayoutPanel gibi herhangi bir kapınız varsa, bu türlerin her birinden bir sınıf türetin ve yapıcılarda çift arabelleğe almayı etkinleştirin. Şimdi, Windows.Forms Kapsayıcıları yerine türetilmiş Kapsayıcılarınızı kullanın.
class TableLayoutPanel : System.Windows.Forms.TableLayoutPanel
{
public TableLayoutPanel()
{
DoubleBuffered = true;
}
}
class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel
{
public FlowLayoutPanel()
{
DoubleBuffered = true;
}
}
Ng5000'in cevabına dayanarak, bu uzantıyı kullanmayı seviyorum:
#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
kullanın:
using (this.BeginSuspendlock())
{
//update GUI
}
Pinvoke kullanmayan bir VB uzantıları sürümü getirmek için ceztko ve ng5000'lerin bir kombinasyonu
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
Bunun eski bir soru olduğunu biliyorum, zaten cevaplanmış, ama işte benim bu konuya olan yaklaşımım; Güncelleştirmelerin askıya alınmasını IDisposable olarak yeniden düzenledim - bu şekilde bir using
deyimde çalıştırmak istediğim ifadeleri ekleyebilirim .
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();
}
}
Bu da basit olduğu bu parçacığı üzerinde GDI kas bir çok görebileceğiniz gibi - ve belki hacky ve olduğu besbelli belirli senaryolar için sadece iyi bir uyum.YMMV
Senaryomda, "Üst" Kullanıcı Load
Kontrolü olarak anlatacağım şeyi kullanıyorum - ve etkinlik sırasında, üstlenilecek denetimi Ebeveynin .Controls
koleksiyonundan ve EbeveyninOnPaint
çocuğu tamamen boyamaya özen gösteriyor hangi şekilde olursa olsun kontrol .. tamamen çocuğun boya yeteneklerini çevrimdışı alarak.
Şimdi, çocuk boyama rutinimi, Windows formlarını yazdırmak için Mike Gold'un bu konseptine dayanan bir uzatma yöntemine teslim ediyorum .
Burada düzene dik hale getirmek için bir etiket alt kümesine ihtiyacım var :
Sonra, ParentUserControl.Load
olay işleyicisinde bu kodla, çocuk denetimini boyanmaktan muaf tutuyorum:
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
Daha sonra, aynı ParentUserControl'de, manipüle edilecek kontrolü sıfırdan boyayacağız:
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
Bir yerde ParentUserControl, örneğin bir Windows formu barındırdığınızda - Visual Studio 2015'imin formu Tasarım zamanında ve çalışma zamanında doğru bir şekilde oluşturduğunu görüyorum :
Şimdi, özel manipülasyonum çocuk kontrolünü 90 derece döndürdüğünden, eminim o bölgedeki tüm sıcak noktalar ve etkileşimler yok edildi - ancak, çözdüğüm sorun tamamen önizleme ve baskı için gereken bir paket etiketi içindi, bu benim için iyi çalıştı.
Sıcak noktaları yeniden kontrol etmenin ve bilerek yetim olan kontrolüme kontrol etmenin yolları varsa - bunu bir gün öğrenmek isterim (bu senaryo için değil, elbette .. sadece öğrenmek için). Tabii ki, WPF böyle çılgınlığı destekliyor OOTB .. ama .. hey .. WinForms hala çok eğlenceli, amiright?