Bir kontrol ve çocukları için tabloyu nasıl askıya alabilirim?


184

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?


3
Birisi bana bu bağlamda burada çizilen veya resim yapan şeyin ne olduğunu açıklayabilir mi? (.net için yeniyim) en azından bir bağlantı sağlayın.
18_12

1
Net 15 yıldan beri dışarıda olan bir utanç (ya da gülünç) ve bu hala bir sorun. Microsoft, Windows X kötü amaçlı yazılım al dediği gibi ekran titremesi gibi gerçek sorunları düzeltmek için çok fazla zaman harcamışsa , bu uzun zaman önce düzeltilmiş olurdu.
jww

@jww Düzeltdiler; buna WPF denir.
philu

Yanıtlar:


304

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.

C # Değişimi

Düzenleri Askıya Alma

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

45
Ne harika bir cevap ve muazzam bir yardım! SuspendDrawing ve ResumeDrawing uzantı yöntemlerini Control sınıfı için yaptım, böylece onları herhangi bir bağlamda herhangi bir denetim için çağırabilirim.
Zach Johnson

2
Çöktükten sonra çocuklarını yeniden düzenlemede bir düğümü düzgün bir şekilde yenileyecek ağaç benzeri bir kontrol vardı, sonra genişledi, bu da çirkin titremeye yol açtı. Bu, etrafta dolaşmak için mükemmel çalıştı. Teşekkürler! (Eğlenceli bir şekilde, kontrol zaten SendMessage'ı içe aktardı ve WM_SETREDRAW'ı tanımladı, ancak aslında hiçbir şey için kullanmadı. Şimdi öyle.)
neminem

7
Bu özellikle kullanışlı değil. Bu tam olarak ne Controliçin taban sınıf tüm WinForms kontrollerin zaten yapar için BeginUpdateve EndUpdateyö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.
Cody Gray

13
@Cody Grey - Örneğin, TableLayoutPanels'ın BeginUpdate'i yoktur.
TheBlastOne

4
Kodunuz, denetim görüntülenmeden önce bu yöntemleri çağırmayı mümkün kılarsa dikkatli olun; bu çağrı 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) returnher iki yöntemde de kontroller olmalıdır .
oatsoda

53

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();
    }
}

Denedim, ancak Askıya Alma ve Devam Etme arasında orta aşamada, sadece geçersiz. Garip gelebilir, ancak geçici durumda askıya alınmadan önce durumunu tutabilir mi?
kullanıcı

2
Bir kontrolün askıya alınması, kontrol alanında hiç çizim yapılmayacağı anlamına gelir ve kalan yüzeyleri o yüzeydeki diğer pencerelerden / kontrollerden çekebilirsiniz. Denetim yeniden başlatılıncaya kadar ("ne demek istediğinizi anladıysam) önceki" durum "un çizilmesini istiyorsanız DoubleBuffer öğesi true olarak ayarlanmış bir denetim kullanmaya çalışabilirsiniz, ancak çalışacağını garanti edemem. Her neyse, bu tekniği kullanma noktasını kaçırdığınızı düşünüyorum: kullanıcının nesnelerin aşamalı ve yavaş bir çizimini görmekten kaçınması amaçlanmıştır (hepsinin birlikte görünmesini sağlamak daha iyidir). Diğer ihtiyaçlar için diğer teknikleri kullanın.
ceztko

4
Güzel cevap, ama nerede Messageve NativeWindowne olduğunu göstermek çok daha iyi olurdu ; adlı bir sınıfın belgelerini aramak Messagegerçekten eğlenceli değil.
darda

1
@pelesl 1) otomatik ad alanı içe aktarma kullanın (sembol üzerine tıklayın, ALT + Üst Karakter + F10), 2) Invalidate () tarafından boyanmayan çocuklar beklenen davranıştır. Ebeveyn kontrolünü sadece kısa bir süre için askıya almalı ve ilgili tüm çocuklar geçersiz kılındığında yeniden başlatmalısınız.
ceztko

2
Invalidate()Refresh()zaten bunu izlemedikçe iyi çalışmaz .
Eugene Ryabtsev

16

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 SuspendDrawingile eşleştirdiğinizden emin olmalısınız ResumeDrawing. Bu nedenle, onları herkese açık hale getirmek muhtemelen iyi bir fikir olmaz.


4
Bu dengeli hem aramaları tutmaya yardımcı olur: SuspendDrawing(); try { DrawSomething(); } finally { ResumeDrawing(); }. Diğer bir seçenek, bunu bir IDisposablesı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.
Olivier Jacot-Descombes

Eski soru, biliyorum, ama "false" gönderme c # / VS son sürümlerinde çalışmıyor gibi görünüyor. False değerini 0 ve true değerini 1 olarak değiştirmek zorunda kaldım.
Maury Markowitz

@MauryMarkowitz sizin yapar DllImportdeclare wParamolarak bool?
Ozgur Ozcitak

Merhaba, bu cevabı küçümsemek bir kazaydı, üzgünüm!
Geoff

13

Ç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();
});

1
Ne zaman action()bir istisna atar? (Sonunda deneyin / sonunda)
David Sherret

8

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;
    }
}

2
Bu kesinlikle yararlı bir tekniktir - ListViews için oldukça sık kullandığım bir tekniktir - ancak yeniden çizimlerin gerçekleşmesini engellemez; hala ekran dışında oluyorlar.
Simon

4
Haklısın, özellikle ekran dışı yeniden çizim sorununu değil, titreme sorununu çözüyor. Titreyen bir çözüm ararken, bunun gibi birkaç ilgili iş parçacığının karşısına geldim ve bulduğumda, en alakalı iş parçacığında yayınlamamış olabilirim. Ancak, çoğu kişi resmi askıya almak istediğinde, muhtemelen ekranda boyamaya atıfta bulunurlar, bu da gereksiz ekran dışı boyamadan daha açık bir sorundur, bu yüzden hala diğer izleyicilerin bu çözümü bu konuda yararlı bulabileceğini düşünüyorum.
Eugenio De Hoyos

OnPaint'i geçersiz kılın.

6

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
}

4

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

4
Ben wpf formları ile karıştırılmış winforms kullanan ve ekran titreme ile ilgili bir WPF uygulaması w / çalışıyorum. Ben bu kod kaldıraç nasıl karıştı - bu winform veya wpf penceresinde gider? Yoksa bu benim özel durumum için uygun değil mi?
nocarrier

3

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 usingdeyimde ç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();
    }
}

2

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ı LoadKontrolü olarak anlatacağım şeyi kullanıyorum - ve etkinlik sırasında, üstlenilecek denetimi Ebeveynin .Controlskoleksiyonundan 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 :

Visual Studio IDE'nizin basit diyagramı

Sonra, ParentUserControl.Loadolay 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 : ParentUserControl bir Windows Formunda veya başka bir kullanıcı denetiminde barındırılıyor

Ş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?


-4

Veya sadece Control.SuspendLayout()ve kullanın Control.ResumeLayout().


9
Düzen ve Resim iki farklı şeydir: Düzen, çocuk denetimlerinin kaplarında nasıl düzenlendiğidir.
Larry
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.