Tôi có một điều khiển mà tôi phải sửa đổi lớn. Tôi muốn ngăn chặn hoàn toàn việc vẽ lại trong khi tôi làm điều đó - SuspendLayout và ResumeLayout là không đủ. Làm thế nào để tôi đình chỉ vẽ tranh cho một điều khiển và con của nó?
Tôi có một điều khiển mà tôi phải sửa đổi lớn. Tôi muốn ngăn chặn hoàn toàn việc vẽ lại trong khi tôi làm điều đó - SuspendLayout và ResumeLayout là không đủ. Làm thế nào để tôi đình chỉ vẽ tranh cho một điều khiển và con của nó?
Câu trả lời:
Ở công việc trước đây, chúng tôi đã vật lộn để có được ứng dụng UI phong phú của mình để vẽ ngay lập tức và trơn tru. Chúng tôi đã sử dụng các điều khiển .Net tiêu chuẩn, điều khiển tùy chỉnh và điều khiển phát hiện.
Sau rất nhiều lần sử dụng Google và phản xạ, tôi đã thấy thông báo win32 WM_SETREDRAW. Điều này thực sự dừng các điều khiển vẽ trong khi bạn cập nhật chúng và có thể được áp dụng, IIRC cho bảng điều khiển cha / chứa.
Đây là một lớp rất đơn giản thể hiện cách sử dụng thông điệp này:
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();
}
}
Có nhiều cuộc thảo luận về vấn đề này - google cho C # và WM_SETREDRAW, vd
Và với người mà nó có thể quan tâm, đây là ví dụ tương tự trong 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
lớp cơ sở cho tất cả các điều khiển WinForms đã làm cho các phương thức BeginUpdate
và EndUpdate
. Tự gửi tin nhắn không tốt hơn là sử dụng các phương pháp đó để thực hiện công việc nặng nhọc cho bạn và chắc chắn không thể tạo ra kết quả khác nhau.
Control.Handle
sẽ buộc điều khiển cửa sổ được tạo và có thể ảnh hưởng đến hiệu suất. Ví dụ nếu bạn đang di chuyển một điều khiển trên biểu mẫu trước khi được hiển thị, nếu bạn gọi này SuspendDrawing
trước, di chuyển của bạn sẽ chậm hơn. Có lẽ nên có if (!parent.IsHandleCreated) return
kiểm tra trong cả hai phương pháp.
Sau đây là cùng một giải pháp của ng5000 nhưng không sử dụng P / Gọi.
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
và NativeWindow
đang ở đâu ; tìm kiếm tài liệu cho một lớp có tên Message
không thực sự thú vị.
Invalidate()
không hoạt động tốt như Refresh()
trừ khi theo sau bởi một.
Tôi thường sử dụng một phiên bản sửa đổi nhỏ của câu trả lời của 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();
}
}
}
Điều này cho phép tạm dừng / tiếp tục các cuộc gọi được lồng nhau. Bạn phải chắc chắn để khớp từng SuspendDrawing
với a ResumeDrawing
. Do đó, có lẽ sẽ không phải là một ý tưởng tốt để công khai chúng.
SuspendDrawing(); try { DrawSomething(); } finally { ResumeDrawing(); }
. Một lựa chọn khác là thực hiện điều này trong một IDisposable
lớp và bao gồm phần vẽ trong một phần using
. Tay cầm sẽ được chuyển cho nhà xây dựng, sẽ tạm dừng vẽ.
DllImport
tuyên bố wParam
là bool
?
Để giúp không quên vẽ lại:
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();
}
sử dụng:
SuspendDrawing(myControl, () =>
{
somemethod();
});
action()
ném ngoại lệ? (Sử dụng thử / cuối cùng)
Một giải pháp tốt đẹp mà không cần sử dụng interop:
Như mọi khi, chỉ cần bật DoubleBuffered = true trên CustomControl của bạn. Sau đó, nếu bạn có bất kỳ vùng chứa nào như FlowLayoutPanel hoặc TableLayoutPanel, hãy lấy một lớp từ mỗi loại này và trong các hàm tạo, kích hoạt bộ đệm đôi. Bây giờ, chỉ cần sử dụng các Container có nguồn gốc của bạn thay vì các Container của Windows.Forms.
class TableLayoutPanel : System.Windows.Forms.TableLayoutPanel
{
public TableLayoutPanel()
{
DoubleBuffered = true;
}
}
class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel
{
public FlowLayoutPanel()
{
DoubleBuffered = true;
}
}
Dựa trên câu trả lời của ng5000, tôi thích sử dụng tiện ích mở rộng này:
#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
Sử dụng:
using (this.BeginSuspendlock())
{
//update GUI
}
Đây là sự kết hợp giữa ceztko và ng5000 để mang đến một phiên bản mở rộng VB không sử dụng 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
Tôi biết đây là một câu hỏi cũ, đã được trả lời, nhưng đây là ý kiến của tôi về vấn đề này; Tôi đã cấu trúc lại việc đình chỉ các bản cập nhật thành IDis Dùng - theo cách đó tôi có thể gửi các câu lệnh tôi muốn chạy trong một using
câu lệnh.
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();
}
}
Điều này thậm chí còn đơn giản hơn và có lẽ là hacky - vì tôi có thể thấy rất nhiều cơ GDI trên luồng này , và rõ ràng chỉ phù hợp với một số tình huống nhất định. YMMV
Trong kịch bản của tôi, tôi sử dụng cái mà tôi sẽ gọi là UserControl "Parent" - và trong Load
sự kiện, tôi chỉ cần loại bỏ quyền điều khiển khỏi .Controls
bộ sưu tập của Parent và Parent OnPaint
sẽ chăm sóc hoàn toàn cho đứa trẻ kiểm soát theo bất cứ cách nào đặc biệt .. hoàn toàn lấy khả năng vẽ của trẻ em nhé.
Bây giờ, tôi đưa ra thói quen vẽ con của tôi cho một phương pháp mở rộng dựa trên khái niệm này từ Mike Gold để in các mẫu cửa sổ .
Ở đây tôi cần một bộ nhãn phụ để hiển thị vuông góc với bố cục:
Sau đó, tôi miễn kiểm soát trẻ em khỏi bị vẽ, với mã này trong ParentUserControl.Load
trình xử lý sự kiện:
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
Sau đó, trong cùng ParentUserControl, chúng tôi vẽ điều khiển được thao tác từ đầu lên:
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
Khi bạn lưu trữ ParentUserControl ở đâu đó, ví dụ: Biểu mẫu Windows - Tôi thấy rằng Visual Studio 2015 của tôi hiển thị biểu mẫu chính xác tại Giờ thiết kế cũng như thời gian chạy:
Bây giờ, do thao tác cụ thể của tôi xoay điều khiển trẻ 90 độ, tôi chắc chắn tất cả các điểm nóng và tính tương tác đã bị phá hủy trong khu vực đó - nhưng, vấn đề tôi đang giải quyết là tất cả cho nhãn gói cần xem trước và in, mà làm việc tốt cho tôi
Nếu có nhiều cách để giới thiệu lại các điểm nóng và kiểm soát đối với sự kiểm soát mồ côi có chủ đích của tôi - tôi rất muốn tìm hiểu về điều đó một ngày nào đó (tất nhiên không phải cho kịch bản này, nhưng .. chỉ để tìm hiểu). Tất nhiên, WPF hỗ trợ OOTB điên rồ như vậy .. nhưng .. này .. WinForms vẫn còn rất nhiều niềm vui, phải không?