2012-05-14 4 views
6

Ich habe zwei Probleme mit einer eigenen Benutzersteuerung, die Bitmaps verwendet:eigene WinForms Kontrolle flackert und hat schlechte Leistung

  1. Es flackert, wenn es über .NET der ‚Refresh‘ Methode neu gezeichnet wird.
  2. Es hat eine schlechte Leistung.

Die Steuerung besteht aus drei Bitmaps:

  • Eine statische Hintergrundbild.
  • Ein rotierender Rotor.
  • Ein anderes Bild, abhängig vom Rotorwinkel.

Alle verwendeten Bitmaps haben eine Auflösung von 500x500 Pixel. Das Steuerelement funktioniert so: https://www.dropbox.com/s/t92gucestwdkx8z/StatorAndRotor.gif (es ist eine GIF-Animation)

Die Benutzersteuerung sollte sich selbst zeichnen, jedes Mal wenn es einen neuen Rotorwinkel bekommt. Daher hat es ein öffentliches Eigentum ‚RotorAngle‘, die wie folgt aussieht:

public double RotorAngle 
{ 
    get { return mRotorAngle; } 
    set 
    { 
     mRotorAngle = value; 
     Refresh(); 
    } 
} 

Refresh wirft die Paint Ereignis. Die OnPaint Event-Handler sieht wie folgt aus:

private void StatorAndRotor2_Paint(object sender, PaintEventArgs e) 
{ 
    // Draw the three bitmaps using a rotation matrix to rotate the rotor bitmap. 
    Draw((float)mRotorAngle); 
} 

Aber wenn ich diesen Code verwenden - die auch in anderen eigenen Bedienelemente arbeitet - der Benutzer die Steuerung überhaupt nicht gezogen, wenn die Kontrolle über Doppel SetStyle(ControlStyles.OptimizedDoubleBuffer, true) gepuffert. Wenn ich dieses Flag nicht auf "true" setze, flackert das Steuerelement, wenn es neu gezeichnet wird.

Im Steuer Konstruktor Ich habe:

SetStyle(ControlStyles.AllPaintingInWmPaint, true); 
SetStyle(ControlStyles.ContainerControl, false); 
// User control is not drawn if "OptimizedDoubleBuffer" is true. 
// SetStyle(ControlStyles.OptimizedDoubleBuffer, true); 
SetStyle(ControlStyles.ResizeRedraw, true); 
SetStyle(ControlStyles.SupportsTransparentBackColor, true); 

Zuerst dachte ich, es flackert, weil der Hintergrund jedes Mal gelöscht wird die Steuerung gezogen wird. Daher setze ich SetStyle(ControlStyles.AllPaintingInWmPaint, true). Aber es hat nicht geholfen.

Also, warum flimmert es? Andere Steuerelemente funktionieren sehr gut mit diesem Setup. Und warum wird die Steuerung nicht gezeichnet, wenn .

Ich fand heraus, dass die Steuerung nicht, ob ich meine Draw Methode nach der Eigenschaft RotorAngle Ändern direkt aufrufen nicht flimmern:

public float RotorAngle 
{ 
    get { return mRotorAngle; } 
    set 
    { 
     mRotorAngle = value; 
     Draw(mRotorAngle); 
    } 
} 

Aber dies führt zu einer sehr schlechten Leistung, vor allem im Vollbildmodus. Es ist nicht möglich, das Steuerelement alle 20 Millisekunden zu aktualisieren. Sie können es selbst ausprobieren. Ich werde die vollständige Visual Studio 2008-Lösung unten anhängen.

Also, warum ist es so eine schlechte Leistung? Es ist kein Problem, andere (eigene) Kontrollen alle 20 Millisekunden zu aktualisieren. Liegt es wirklich nur an den Bitmaps?

habe ich eine einfache visuelle Visual Studio 2008-Lösung, die beiden Probleme zu demonstrieren: bin\Debug https://www.dropbox.com/s/mckmgysjxm0o9e0/WinFormsControlsTest.zip (289,3 KB)

Es ist eine ausführbare Datei im Verzeichnis.

Danke für Ihre Hilfe.

+0

ich nicht in alle Details jetzt gehen, aber Sie brauchen, um das doppelte Pufferung zu suchen. Glücklicherweise ist es sehr einfach in 2008+ Die Art, wie Sie die Aktualisierung in der Eigenschaft tun, ist auch schlecht. – Ian

+0

Bitte double-buffer nicht, wenn Ihre Anwendung über Remote-Desktop verwendet wird - siehe [diesen Link] (http://blogs.msdn.com/b/oldnewthing/archive/2006/01/03/508694.aspx) . Sie können prüfen, ob es sich bei der aktuellen Sitzung um einen Remote-Desktop handelt: 'if (SystemInformation.TerminalServerSession) ...' – Bridge

Antwort

4

Zuerst pro LarsTech Antwort, sollten Sie den Graphics Kontext im PaintEventArgs vorgesehen verwenden. Wenn Sie innerhalb des Paint Handlers CreateGraphics() aufrufen, verhindern Sie, dass OptimizedDoubleBuffer ordnungsgemäß funktioniert.

Zweitens in Ihrem SetStyle Block hinzu:

SetStyle(ControlStyles.Opaque, true); 

... die Basisklasse Control von Füllung in der Hintergrundfarbe zu verhindern, bevor Ihr Lack-Handler aufrufen.

Ich testete dies in Ihrem Beispielprojekt, es schien das Flimmern zu beseitigen.

2

Das Zeichnen auf dem Bildschirm bei Paint ist ein schwerer Vorgang. Malen auf Speicherpuffer ist vergleichsweise sehr schnell.

Double buffering wird die Leistung verbessern. Erstellen Sie eine neue Grafik, und zeichnen Sie alle Grafiken darauf.

Sobald Bitmap Zeichnung fertig ist, kopieren Sie die gesamte Bitmap e.Graphics empfangen von PaintEventArgs

Flicker free drawing using GDI+ and C#

4

Verwenden CreateGraphics nicht, aber die Grafik-Objekt verwenden, die Sie aus dem Paint-Ereignis übergeben wurde: eine klare seit Ändern der Größe der Geisterbild zeigen würde

ich es wie so verändert und hinzugefügt:

private void Draw(float rotorAngle, Graphics graphics) 
{ 
    graphics.Clear(SystemColors.Control); 
    graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; 

    // yada-yada-yada 

    // do not dispose since you did not create it: 
    // graphics.Dispose(); 
} 

private void StatorAndRotor2_Paint(object sender, PaintEventArgs e) 
{ 
    Draw((float)mRotorAngle, e.Graphics); 
} 

im Konstruktor einschalten double-Buffering, aber ich denke, dass die Transparenz nicht erforderlich ist:

von genannt

SetStyle(ControlStyles.AllPaintingInWmPaint, true); 
SetStyle(ControlStyles.ContainerControl, false); 
SetStyle(ControlStyles.OptimizedDoubleBuffer, true); 
SetStyle(ControlStyles.ResizeRedraw, true); 
//SetStyle(ControlStyles.SupportsTransparentBackColor, true); 
0

Für meine Antwort habe ich mich von https://stackoverflow.com/a/2608945/455904 inspirieren lassen und ich habe Zeug von LarsTechs Antwort oben beantwortet.

Um zu vermeiden, dass das gesamte Bild auf allen OnPaints neu generiert werden muss, können Sie eine Variable verwenden, um das generierte Bild zu halten.

private Bitmap mtexture; 

Verwenden Draw(), um die Textur

private void Draw(float rotorAngle) 
{ 
    using (var bufferedGraphics = Graphics.FromImage(mtexture)) 
    { 
     Rectangle imagePosition = new Rectangle(0, 0, Width, Height); 
     bufferedGraphics.DrawImage(mStator, imagePosition); 
     bufferedGraphics.DrawImage(RotateImage(mRotor, mRotorAngle), imagePosition); 

     float normedAngle = mRotorAngle % cDegreePerFullRevolution; 

     if (normedAngle < 0) 
      normedAngle += cDegreePerFullRevolution; 

     if (normedAngle >= 330 || normedAngle <= 30) 
      bufferedGraphics.DrawImage(mLED101, imagePosition); 
     if (normedAngle > 30 && normedAngle < 90) 
      bufferedGraphics.DrawImage(mLED001, imagePosition); 
     if (normedAngle >= 90 && normedAngle <= 150) 
      bufferedGraphics.DrawImage(mLED011, imagePosition); 
     if (normedAngle > 150 && normedAngle < 210) 
      bufferedGraphics.DrawImage(mLED010, imagePosition); 
     if (normedAngle >= 210 && normedAngle <= 270) 
      bufferedGraphics.DrawImage(mLED110, imagePosition); 
     if (normedAngle > 270 && normedAngle < 330) 
      bufferedGraphics.DrawImage(mLED100, imagePosition); 
    } 
} 

Haben die Überschreibung von OnPaint zeichnen die Textur auf Ihrer Kontrolle

protected override void OnPaint(PaintEventArgs e) 
{ 
    base.OnPaint(e); 
    Rectangle imagePosition = new Rectangle(0, 0, Width, Height); 
    e.Graphics.DrawImage(mtexture, imagePosition); 
} 

Aufschalten OnInvalidated(), um zu Draw(), um die Textur zu erzeugen, bei Bedarf

protected override void OnInvalidated(InvalidateEventArgs e) 
{ 
    base.OnInvalidated(e); 

    if (mtexture != null) 
    { 
     mtexture.Dispose(); 
     mtexture = null; 
    } 

    mtexture = new Bitmap(Width, Height); 
    Draw(mRotorAngle); 
} 

Anstatt Draw aufzurufen, wird das Bild ungültig. Dies führt dazu, dass es mit OnInvalidated und OnPaint neu gezeichnet wird.

public float RotorAngle 
{ 
    get { return mRotorAngle; } 
    set 
    { 
     mRotorAngle = value; 
     Invalidate(); 
    } 
} 

Ich hoffe, dass ich alles davon bekam es in :)

0

Ihnen sehr für Ihre Hilfe danken.

Flackern ist gelöst. :)

Jetzt folge ich LarsTech's Vorschlag und verwenden Sie das Graphics Objekt von PaintEventArgs.

Danke lnmx für den Hinweis, dass CreateGraphics() innerhalb Paint Handler die korrekte Funktion von OptimizedDoubleBuffer verhindert. Dies erklärt das Flackern Problem, auch wenn OptimizedDoubleBuffer aktiviert wurde. Ich wusste das nicht und ich habe das auch nicht bei MSDN Library gefunden. In meinen vorherigen Kontrollen habe ich auch das Objekt Graphics von PaintEventArgs verwendet.

Danke Sallow für Ihre Bemühungen. Ich werde deinen Code heute testen und werde Feedback geben. Ich hoffe, dies wird die Performance verbessern, da es immer noch ein Performance-Problem gibt - trotz korrekter Doppelpufferung.

Es gab noch ein weiteres Leistungsproblem in meinem ursprünglichen Code.

Changed
graphics.DrawImage(mStator, imagePosition); 
graphics.DrawImage(RotateImage(mRotor, rotorAngle), imagePosition); 

zu

graphics.DrawImage(mStator, imagePosition); 
Bitmap rotatedImage = RotateImage(mRotor, rotorAngle); 
graphics.DrawImage(rotatedImage, imagePosition); 
rotatedImage.Dispose(); // Important, otherwise the RAM will be flushed with bitmaps.