Laden...

Basisklasse für eigene scrollbare Steuerelemente

Erstellt von Löwenherz vor 17 Jahren Letzter Beitrag vor 17 Jahren 5.959 Views
L
Löwenherz Themenstarter:in
58 Beiträge seit 2006
vor 17 Jahren
Basisklasse für eigene scrollbare Steuerelemente

Hallo Leute,

ich habe mich in letzter Zeit intensiv mit dem Problem auseinandergesetzt, ein eigenes Steuerelement zu schreiben, das dynamisch seinen Clientbereich vergrößern und verkleinern kann. Entsprechend der "virtuellen" Clientgröße, sollten Scrollbars nur bei Bedarf angezeigt oder ausgeblendet werden. Das Problem kam hier im Forum auch schon mehrfach auf und wurde meist mit Autoscrollbars gelöst. Aber genau mit denen hatte ich so meine liebe Müh 🙂.

Kurzum, ich habe ein UserControl genommen, zwei Scrollbars und ein Panel reingesetzt und alles in eine Klasse verpackt. Diese Klasse hat eine abstrakte Property PanelClientSize und ein abstrakte Methode OnPaintPanel().

Die Property _PanelClientSize _wird von einer abgeleiteten Klasse überschrieben und liefert die Größe des geforderten Platzes im Panel. Diese Größe dient zur Dimensionierung der Scrollbars.

Die Methode OnPaintPanel() wird ebenfalls von einer abgeleiteten Klasse überschrieben und sorgt dafür, dass das Panelsteuerelement gepaintet wird.

Ich hoffe, ich kann dem einen oder anderen mit der Klasse das Leben etwas erleichtern.


using System;
using System.Drawing;
using System.Windows.Forms;

namespace BasicControls
{
  public abstract class ScrollablePanel : System.Windows.Forms.UserControl
  {
    protected System.Windows.Forms.HScrollBar m_hScrollBar;
    protected System.Windows.Forms.VScrollBar m_vScrollBar;
    private   System.Windows.Forms.Panel m_ClientPanel;
    private   System.ComponentModel.Container components = null;

    public ScrollablePanel()
    {
      InitializeComponent();
    }

    protected override void Dispose( bool disposing )
    {
      if( disposing ) {
        if( components != null )
          components.Dispose();
      }
      base.Dispose( disposing );
    }

    #region Vom Komponenten-Designer generierter Code
    /// <summary>
    /// Erforderliche Methode für die Designerunterstützung. 
    /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
    /// </summary>
    private void InitializeComponent()
    {
      this.m_hScrollBar = new System.Windows.Forms.HScrollBar();
      this.m_vScrollBar = new System.Windows.Forms.VScrollBar();
      this.m_ClientPanel = new System.Windows.Forms.Panel();
      this.SuspendLayout();
      // 
      // m_hScrollBar
      // 
      this.m_hScrollBar.Anchor = System.Windows.Forms.AnchorStyles.None;
      this.m_hScrollBar.Location = new System.Drawing.Point(0, 216);
      this.m_hScrollBar.Name = "m_hScrollBar";
      this.m_hScrollBar.Size = new System.Drawing.Size(232, 17);
      this.m_hScrollBar.TabIndex = 0;
      this.m_hScrollBar.Scroll += new System.Windows.Forms.ScrollEventHandler(this.OnScrollbarScroll);
      // 
      // m_vScrollBar
      // 
      this.m_vScrollBar.Anchor = System.Windows.Forms.AnchorStyles.None;
      this.m_vScrollBar.Location = new System.Drawing.Point(232, 0);
      this.m_vScrollBar.Name = "m_vScrollBar";
      this.m_vScrollBar.Size = new System.Drawing.Size(17, 216);
      this.m_vScrollBar.TabIndex = 1;
      this.m_vScrollBar.Scroll += new System.Windows.Forms.ScrollEventHandler(this.OnScrollbarScroll);
      // 
      // m_ClientPanel
      // 
      this.m_ClientPanel.Anchor = System.Windows.Forms.AnchorStyles.None;
      this.m_ClientPanel.Location = new System.Drawing.Point(0, 0);
      this.m_ClientPanel.Name = "m_ClientPanel";
      this.m_ClientPanel.Size = new System.Drawing.Size(232, 216);
      this.m_ClientPanel.TabIndex = 0;
      // 
      // ScrollablePanel
      // 
      this.Controls.Add(this.m_ClientPanel);
      this.Controls.Add(this.m_vScrollBar);
      this.Controls.Add(this.m_hScrollBar);
      this.Name = "ScrollablePanel";
      this.Size = new System.Drawing.Size(248, 232);
      this.Resize += new System.EventHandler(this.OnResize);
      this.ResumeLayout(false);

    }
    #endregion


    protected abstract Size PanelClientSize { get; }
    protected abstract void OnPaintPanel(object sender, System.Windows.Forms.PaintEventArgs e);

    protected Panel Panel
    {
      get {
        return m_ClientPanel;
      }
      set {
        // remove old panel
        if (m_ClientPanel != null) {
          Controls.Remove(m_ClientPanel);
          m_ClientPanel.Dispose();
        }
        // add new panel
        m_ClientPanel = value;
        Controls.Add(m_ClientPanel);
        m_ClientPanel.Paint += new PaintEventHandler(OnPaintPanel);
        m_ClientPanel.Resize += new EventHandler(OnResize);
        RefreshControls();
        m_ClientPanel.Show();
        m_ClientPanel.Invalidate();
      }
    }

    private bool WillGetHorizontalScrollbar()
    {
      if (PanelClientSize.Width > ClientSize.Width)
        return true;
      
      if (PanelClientSize.Height > ClientSize.Height) {
        // we will get a vertical scrollbar due to needed dimensions, consider it!
        if (PanelClientSize.Width + m_vScrollBar.Width > ClientSize.Width)
          return true;
      }

      return false;
    }

    private bool WillGetVerticalScrollbar()
    {
      if (PanelClientSize.Height > ClientSize.Height)
        return true;

      if (PanelClientSize.Width > ClientSize.Width) {
        // we will get a horizontal scrollbar due to needed dimensions, consider it!
        if (PanelClientSize.Height + m_hScrollBar.Height > ClientSize.Height)
          return true;
      }

      return false;
    }

    protected void RefreshControls()
    {
      // dock horizontal scrollbar
      Point pt;
      pt = new Point(0, ClientSize.Height - m_hScrollBar.Height);      
      m_hScrollBar.Location = pt;
      if (WillGetVerticalScrollbar()) {
        m_hScrollBar.Width = ClientSize.Width - m_vScrollBar.Width;
      } else {
        m_hScrollBar.Width = ClientSize.Width;
      }

      // dock vertical scrollbar
      pt = new Point(ClientSize.Width - m_vScrollBar.Width, 0);
      m_vScrollBar.Location = pt;
      if (WillGetHorizontalScrollbar()) {
        m_vScrollBar.Height = ClientSize.Height - m_hScrollBar.Height;
      } else {
        m_vScrollBar.Height = ClientSize.Height;
      }

      // horizontal scrollbar
      if (WillGetHorizontalScrollbar()) 
      {
        m_hScrollBar.Minimum = 0;
        m_hScrollBar.Maximum = PanelClientSize.Width + 1; // Fehler beseitigt
        m_hScrollBar.SmallChange = 10;
        m_hScrollBar.LargeChange = m_ClientPanel.ClientSize.Width;
        m_hScrollBar.Show();
      } 
      else 
      {
        m_hScrollBar.Minimum = 0;
        m_hScrollBar.Maximum = 0;
        m_hScrollBar.Hide();
      }

      if (WillGetVerticalScrollbar()) 
      {
        m_vScrollBar.Minimum = 0;
        m_vScrollBar.Maximum = PanelClientSize.Height + 1; // Fehler beseitigt
        m_vScrollBar.SmallChange = 10;
        m_vScrollBar.LargeChange = m_ClientPanel.ClientSize.Height;
        m_vScrollBar.Show(); 
      } 
      else 
      {
        m_vScrollBar.Maximum = 0;
        m_vScrollBar.Minimum = 0;
        m_vScrollBar.Hide();
      }

      // recalculate panel size
      Size size = new Size(0,0);
      if (m_hScrollBar.Visible) {
        size.Height = m_hScrollBar.Top;
      } else {
        size.Height = ClientSize.Height;
      }

      if (m_vScrollBar.Visible) {
        size.Width = m_vScrollBar.Left;
      } else {
        size.Width = ClientSize.Width;
      }

      // set new panel size
      m_ClientPanel.Size = size;

      // adjust horizontal scrollbar value to new range
      int nMax = m_hScrollBar.Maximum - m_hScrollBar.LargeChange - 1;
      if (nMax > 0) {
        if (m_hScrollBar.Value > nMax) {
          m_hScrollBar.Value = nMax;
        } 
      } else {
        m_hScrollBar.Value = 0;
      }
      
      // adjust vertical scrollbar value to new range
      nMax = m_vScrollBar.Maximum - m_vScrollBar.LargeChange - 1;
      if (nMax > 0) {
        if (m_vScrollBar.Value > nMax) {
          m_vScrollBar.Value = nMax;
        } 
      } else {
        m_vScrollBar.Value = 0;
      }
    }

    private void OnScrollbarScroll(object sender, ScrollEventArgs e)
    {
      m_ClientPanel.Invalidate();
    }

    private void OnResize(object sender, EventArgs e)
    {
      RefreshControls();
    }
  }
}

Und so kann eine Klasse aussehen, die von ScrollableControl abgeleitet ist:


using System;
using System.Drawing;
using System.Windows.Forms;

public class MyControl : BasicControls.ScrollablePanel
{
  private System.ComponentModel.Container components = null;

  public MyControl()
  {
  }

  protected override void Dispose( bool disposing )
  {
    if( disposing ) {
      if( components != null )
        components.Dispose();
    }
    base.Dispose( disposing );
  }

  protected override Size PanelClientSize
  {
    get {
      return new Size(100,100);
    }
  }

  protected override void OnPaintPanel(object sender, System.Windows.Forms.PaintEventArgs e)
  {
    Graphics g = e.Graphics;
    // Hier was tolles painten
  }
}

Ich freue mich auch über konstruktive Kommentare, die mir helfen können, den Code zu verbessern 🙂

N
177 Beiträge seit 2006
vor 17 Jahren

Resize sollte scrollbar.Value nicht automatisch auf 0 setzen
m_hScrollBar.Value = 0;
m_vScrollBar.Value = 0;

m_ClientPanel.ClientSize wird benutzt bevor m_ClientPanel.Size gesetzt wird. Kommt mir seltsam vor. Überhaupt ist mir der Sinn von "recalculate panel size" nicht klar.

Warum benutzt du nicht die "automatic docking properties" (falls es die gibt)?

Ich bin mir nicht sicher, ob die "- 1" hier stimmen:
nMax = m_vScrollBar.Maximum - m_vScrollBar.LargeChange - 1;
nMax = m_hScrollBar.Maximum - m_hScrollBar.LargeChange - 1;

Bonus (optional): Was ist der Unterschied zwischen bzw. die Definition von

  • PanelClientSize
  • m_ClientPanel.Size
  • m_ClientPanel.ClientSize
  • ClientSize
L
Löwenherz Themenstarter:in
58 Beiträge seit 2006
vor 17 Jahren

Resize sollte scrollbar.Value nicht automatisch auf 0 setzen

Das betreffende Code-Fragment:


      // adjust horizontal scrollbar value to new range
      int nMax = m_hScrollBar.Maximum - m_hScrollBar.LargeChange - 1;
      if (nMax > 0) {
        if (m_hScrollBar.Value > nMax) {
          m_hScrollBar.Value = nMax;
        } 
      } else {
        m_hScrollBar.Value = 0;
      }

Ich will damit nachschauen, ob der aktuelle Wert der ScrollBar über oder unter dem Maximum der ScrollBar liegt. Wenn man Minimum und Maximum ändert, wird ja nicht automatisch der Wert angepasst. So kann es dazu kommen, dass der Wert einer ScrollBar nach Änderung von Minimum und Maximum außerhalb der zulässigen Grenzen liegt. Daher diese kleine Deckelung nach oben hin. Ich hoffe, ich konnte das verständlich darlegen 🙂

m_ClientPanel.ClientSize wird benutzt bevor m_ClientPanel.Size gesetzt wird. Kommt mir seltsam vor. Überhaupt ist mir der Sinn von "recalculate panel size" nicht klar.

Hmm, gib mir mal bitte einen Tipp, wo ich m_ClientPanel.ClientSize nutze, bevor es gesetzt wird. Ich habe irgendwie gerade Tomaten auf den Augen. Der Abschnitt "recalculate panel size" sorgt dafür, dass je nach Sichtbarkeit der ScrollBars die Breite/Höhe der jeweils anderen Scrollbar angepasst wird. Mein Ziel war, dass die horizontale Scrollbar die die gesamte Breite des Fensters einnimmt, sofern sie gebraucht wird und es keine vertikale ScrollBar gibt. Gibt es aber eine horizontale und eine vertikale ScrollBar, so stellen sich die ScrollBars "über Eck". Es entsteht dann ein kleiner rechteckiger "Hohlraum" zwischen dem Down-Button der vertikalen und dem Right-Button der horizontalen ScrollBar.


     // recalculate panel size
      Size size = new Size(0,0);
      if (m_hScrollBar.Visible) {
        size.Height = m_hScrollBar.Top;
      } else {
        size.Height = ClientSize.Height;
      }

      if (m_vScrollBar.Visible) {
        size.Width = m_vScrollBar.Left;
      } else {
        size.Width = ClientSize.Width;
      }

Warum benutzt du nicht die "automatic docking properties" (falls es die gibt)?

Die gibt es, allerdings entsteht dann nicht der eben angesprochene Hohlraum zwischen den ScrollBars. Beim Docking nimmt eine ScrollBar immer die ganze Höhe bzw. Breite ein. Das sah in meinen Augen furchtbar aus 😁

Ich bin mir nicht sicher, ob die "- 1" hier stimmen:
nMax = m_vScrollBar.Maximum - m_vScrollBar.LargeChange - 1;
nMax = m_hScrollBar.Maximum - m_hScrollBar.LargeChange - 1;

*grübel* Ich glaube, du hast recht, die ist wohl falsch. Müsste +1 sein, oder?
Mal laut denken: Wenn ich einen Bereich von 800 Pixeln habe (Minimum stelle ich dann auf 0 und Maximum auf 799 ein, LargeChange bekommt den Wert 400). Dann habe ich zwei gleichgroße Scrollbereiche. Der erste würde von 0 bis 399 gehen und der zweite von 400 bis 799. Der größte Scrollwert ist dann ja 400. Auf 400 komme ich auch, wenn ich Maximum (799) - LargeChange(400) + 1 rechne. Oder habe ich da einen grundsätzlichen Denkfehler drin?

Ich habe den Quelltext oben bereits angepasst. Steht nun auch +1 drin.

Bonus (optional): Was ist der Unterschied zwischen bzw. die Definition von

  • PanelClientSize
  • m_ClientPanel.Size
  • m_ClientPanel.ClientSize
  • ClientSize

PanelClientSize ist abstract und wird von der abgeleiteten Klasse implementiert und gibt an, wie groß der virtuelle Clientbereich sein soll. Wenn ich ein Bild in das Steuerelement reinpacken will, dann gibt diese Property die Breite und Höhe des Bildes an, damit die ScrollBars sich so einstellen können, dass auch das ganze Bild "angescrollt" werden kann.

m_ClientPanel.Size gibt an, wie groß das Steuerelement inklusive Rahmen ist. Ich habe einen dünnen Rahmen um das Element gezogen, der gehört zum Steuerelement dazu, ist aber nicht Clientbereich.

m_ClientPanel.ClientSize gibt die für Painting nutzbare Fläche im Panel an.

ClientSize oder besser this.ClientSize gibt die Größe der für Painting nutzbaren Fläche an. Da die ScrollBars in der Klasse als normale Steuerelemente eingebaut sind und nicht vom Fenster bereitgestellt werden, werden die ScrollBars wie andere Steuerelemente behandelt und im Clientbereich des Fensters angelegt. Das verhält sich anders bei AutoScrollbars, die den Clientbereich verkleinern, wenn sie aktiviert werden.

Ich hoffe, das konnte ein bisschen meine Ideen, die ich so bei der Implementierung hatte, erklären 🙂

Falls ich irgendwo Unfug erzählt haben sollte, bitte melden!

L
Löwenherz Themenstarter:in
58 Beiträge seit 2006
vor 17 Jahren

Ahhhh, jetzt weiß ich, was du meintest mit

m_ClientPanel.ClientSize wird benutzt bevor m_ClientPanel.Size gesetzt wird. Kommt mir seltsam vor.

Die korrigierte Methode sieht etwas anders aus 😉


    protected void RefreshControls()
    {
      // dock horizontal scrollbar and calculate new panel width
      Size size = new Size(0,0);
      m_hScrollBar.Location = new Point(0, ClientSize.Height - m_hScrollBar.Height);
      if (WillGetVerticalScrollbar()) {
        m_hScrollBar.Width = ClientSize.Width - m_vScrollBar.Width;
        size.Width = m_vScrollBar.Left;
      } else {
        m_hScrollBar.Width = ClientSize.Width;
        size.Width = ClientSize.Width;
      }

      // dock vertical scrollbar and calculate new panel height
      m_vScrollBar.Location = new Point(ClientSize.Width - m_vScrollBar.Width, 0);
      if (WillGetHorizontalScrollbar()) {
        m_vScrollBar.Height = ClientSize.Height - m_hScrollBar.Height;
        size.Height = m_hScrollBar.Top;
      } else {
        m_vScrollBar.Height = ClientSize.Height;
        size.Height = ClientSize.Height;
      }

      // set new panel size
      m_ClientPanel.Size = size;

      // horizontal scrollbar
      if (WillGetHorizontalScrollbar()) 
      {
        m_hScrollBar.Minimum = 0;
        m_hScrollBar.Maximum = PanelClientSize.Width - 1;
        m_hScrollBar.SmallChange = 10;
        m_hScrollBar.LargeChange = m_ClientPanel.ClientSize.Width;
        m_hScrollBar.Show();
      } 
      else 
      {
        m_hScrollBar.Minimum = 0;
        m_hScrollBar.Maximum = 0;
        m_hScrollBar.Hide();
      }

      if (WillGetVerticalScrollbar()) 
      {
        m_vScrollBar.Minimum = 0;
        m_vScrollBar.Maximum = PanelClientSize.Height - 1;
        m_vScrollBar.SmallChange = 10;
        m_vScrollBar.LargeChange = m_ClientPanel.ClientSize.Height;
        m_vScrollBar.Show(); 
      } 
      else 
      {
        m_vScrollBar.Maximum = 0;
        m_vScrollBar.Minimum = 0;
        m_vScrollBar.Hide();
      }

      // adjust horizontal scrollbar value to new range
      int nMax = m_hScrollBar.Maximum - m_hScrollBar.LargeChange + 1;
      if (nMax > 0) {
        if (m_hScrollBar.Value > nMax) {
          m_hScrollBar.Value = nMax;
        } 
      } else {
        m_hScrollBar.Value = 0;
      }
      
      // adjust vertical scrollbar value to new range
      nMax = m_vScrollBar.Maximum - m_vScrollBar.LargeChange + 1;
      if (nMax > 0) {
        if (m_vScrollBar.Value > nMax) {
          m_vScrollBar.Value = nMax;
        } 
      } else {
        m_vScrollBar.Value = 0;
      }
    }