3

我有一个控件 ( System.Windows.Forms.ScrollableControl),它可能非常大。它有自定义OnPaint逻辑。出于这个原因,我正在使用此处描述的解决方法。

public class CustomControl : ScrollableControl
{
public CustomControl()
{
    this.AutoScrollMinSize = new Size(100000, 500);
    this.DoubleBuffered = true;
}

protected override void OnScroll(ScrollEventArgs se)
{
    base.OnScroll(se);
    this.Invalidate();
}

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    var graphics = e.Graphics;
    graphics.Clear(this.BackColor);
    ...
}
}

绘画代码主要绘制滚动时移动的“正常”事物。绘制的每个形状的原点偏移this.AutoScrollPosition.

graphics.DrawRectangle(pen, 100 + this.AutoScrollPosition.X, ...);

但是,该控件还包含“静态”元素,这些元素始终绘制在相对于父控件的相同位置。为此,我只是不AutoScrollPosition直接使用和绘制形状:

graphics.DrawRectangle(pen, 100, ...);

当用户滚动时,Windows 会将整个可见区域沿与滚动相反的方向平移。通常这是有道理的,因为这样滚动看起来很流畅且响应迅速(并且只有新的部分必须重新绘制),但是静态部分也受到这种翻译的影响(因此this.Invalidate()in OnScroll)。在下一次OnPaint调用成功重绘表面之前,静态部分会稍微偏离。这会在滚动时导致非常明显的“抖动”效果。

有没有办法可以创建一个不存在静态部件问题的可滚动自定义控件?

4

2 回答 2

3

您可以通过完全控制滚动来做到这一点。目前,您只是在关注事件以执行您的逻辑。我以前遇到过滚动问题,我曾经设法让一切顺利进行的唯一方法是通过覆盖 WndProc 来实际处理 Windows 消息。例如,我有这段代码来同步几个 ListBox 之间的滚动:

protected override void WndProc(ref Message m) {
    base.WndProc(ref m);
    // 0x115 and 0x20a both tell the control to scroll. If either one comes 
    // through, you can handle the scrolling before any repaints take place
    if (m.Msg == 0x115 || m.Msg == 0x20a) 
    {
        //Do you scroll processing
    }
}

使用 WndProc 将在重新绘制任何内容之前为您获取滚动消息,因此您可以适当地处理静态对象。我会用它来暂停滚动,直到发生 OnPaint。它看起来不会那么平滑,但静态物体移动不会有问题。

于 2013-05-11T21:34:57.633 回答
2

因为我真的需要这个,所以我最终编写了一个控件,专门用于在可滚动表面上有静态图形(其大小可以大于 65535)的情况。

它是一个常规的,上面Control有两个ScrollBar控件,一个用户可分配ControlContent. 当用户滚动时,容器会相应地设置其Content' AutoScrollOffset。因此,可以使用使用AutoScrollOffset绘图方法而不改变任何内容的控件。的Content实际大小始终是它的可见部分。它允许通过按住 shift 键进行水平滚动。

用法:

var container = new ManuallyScrollableContainer();
var content = new ExampleContent();
container.Content = content;
container.TotalContentWidth = 150000;
container.TotalContentHeight = 5000;
container.Dock = DockStyle.Fill;
this.Controls.Add(container); // e.g. add to Form

代码:

它变得有点冗长,但我可以避免丑陋的黑客攻击。应该与单声道一起使用。我认为结果非常合理。

public class ManuallyScrollableContainer : Control
{
    public ManuallyScrollableContainer()
    {
        InitializeControls();
    }

    private class UpdatingHScrollBar : HScrollBar
    {
        protected override void OnValueChanged(EventArgs e)
        {
            base.OnValueChanged(e);
            // setting the scroll position programmatically shall raise Scroll
            this.OnScroll(new ScrollEventArgs(ScrollEventType.EndScroll, this.Value));
        }
    }

    private class UpdatingVScrollBar : VScrollBar
    {
        protected override void OnValueChanged(EventArgs e)
        {
            base.OnValueChanged(e);
            // setting the scroll position programmatically shall raise Scroll
            this.OnScroll(new ScrollEventArgs(ScrollEventType.EndScroll, this.Value));
        }
    }

    private ScrollBar shScrollBar;
    private ScrollBar svScrollBar;

    public ScrollBar HScrollBar
    {
        get { return this.shScrollBar; }
    }

    public ScrollBar VScrollBar
    {
        get { return this.svScrollBar; }
    }

    private void InitializeControls()
    {
        this.Width = 300;
        this.Height = 300;

        this.shScrollBar = new UpdatingHScrollBar();
        this.shScrollBar.Top = this.Height - this.shScrollBar.Height;
        this.shScrollBar.Left = 0;
        this.shScrollBar.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;

        this.svScrollBar = new UpdatingVScrollBar();
        this.svScrollBar.Top = 0;
        this.svScrollBar.Left = this.Width - this.svScrollBar.Width;
        this.svScrollBar.Anchor = AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;

        this.shScrollBar.Width = this.Width - this.svScrollBar.Width;
        this.svScrollBar.Height = this.Height - this.shScrollBar.Height;

        this.Controls.Add(this.shScrollBar);
        this.Controls.Add(this.svScrollBar);

        this.shScrollBar.Scroll += this.HandleScrollBarScroll;
        this.svScrollBar.Scroll += this.HandleScrollBarScroll;
    }

    private Control _content;
    /// <summary>
    /// Specifies the control that should be displayed in this container.
    /// </summary>
    public Control Content
    {
        get { return this._content; }
        set
        {
            if (_content != value)
            {
                RemoveContent();
                this._content = value;
                AddContent();
            }
        }
    }

    private void AddContent()
    {
        if (this.Content != null)
        {
            this.Content.Left = 0;
            this.Content.Top = 0;
            this.Content.Width = this.Width - this.svScrollBar.Width;
            this.Content.Height = this.Height - this.shScrollBar.Height;
            this.Content.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right;
            this.Controls.Add(this.Content);
            CalculateMinMax();
        }
    }

    private void RemoveContent()
    {
        if (this.Content != null)
        {
            this.Controls.Remove(this.Content);
        }
    }

    protected override void OnParentChanged(EventArgs e)
    {
        // mouse wheel events only arrive at the parent control
        if (this.Parent != null)
        {
            this.Parent.MouseWheel -= this.HandleMouseWheel;
        }
        base.OnParentChanged(e);
        if (this.Parent != null)
        {
            this.Parent.MouseWheel += this.HandleMouseWheel;
        }
    }

    private void HandleMouseWheel(object sender, MouseEventArgs e)
    {
        this.HandleMouseWheel(e);
    }

    /// <summary>
    /// Specifies how the control reacts to mouse wheel events.
    /// Can be overridden to adjust the scroll speed with the mouse wheel.
    /// </summary>
    protected virtual void HandleMouseWheel(MouseEventArgs e)
    {
        // The scroll difference is calculated so that with the default system setting
        // of 3 lines per scroll incremenet,
        // one scroll will offset the scroll bar value by LargeChange / 4
        // i.e. a quarter of the thumb size
        ScrollBar scrollBar;
        if ((Control.ModifierKeys & Keys.Shift) != 0)
        {
            scrollBar = this.HScrollBar;
        }
        else
        {
            scrollBar = this.VScrollBar;
        }
        var minimum = 0;
        var maximum = scrollBar.Maximum - scrollBar.LargeChange;
        if (maximum <= 0)
        {
            // happens when the entire area is visible
            return;
        }
        var value = scrollBar.Value - (int)(e.Delta * scrollBar.LargeChange / (120.0 * 12.0 / SystemInformation.MouseWheelScrollLines));
        scrollBar.Value = Math.Min(Math.Max(value, minimum), maximum);
    }

    public event ScrollEventHandler Scroll;
    protected virtual void OnScroll(ScrollEventArgs e)
    {
        var handler = this.Scroll;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    /// <summary>
    /// Event handler for the Scroll event of either scroll bar.
    /// </summary>
    private void HandleScrollBarScroll(object sender, ScrollEventArgs e)
    {
        OnScroll(e);
        if (this.Content != null)
        {
            this.Content.AutoScrollOffset = new System.Drawing.Point(-this.HScrollBar.Value, -this.VScrollBar.Value);
            this.Content.Invalidate();
        }
    }

    private int _totalContentWidth;
    public int TotalContentWidth
    {
        get { return _totalContentWidth; }
        set
        {
            if (_totalContentWidth != value)
            {
                _totalContentWidth = value;
                CalculateMinMax();
            }
        }
    }

    private int _totalContentHeight;
    public int TotalContentHeight
    {
        get { return _totalContentHeight; }
        set
        {
            if (_totalContentHeight != value)
            {
                _totalContentHeight = value;
                CalculateMinMax();
            }
        }
    }

    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);
        CalculateMinMax();
    }

    private void CalculateMinMax()
    {
        if (this.Content != null)
        {
            // Reduced formula according to
            // http://msdn.microsoft.com/en-us/library/system.windows.forms.scrollbar.maximum.aspx
            // Note: The original formula is bogus.
            // According to the article, LargeChange has to be known in order to calculate Maximum,
            // however, that is not always possible because LargeChange cannot exceed Maximum.
            // If (LargeChange) == (1 * visible part of control), the formula can be reduced to:

            if (this.TotalContentWidth > this.Content.Width)
            {
                this.shScrollBar.Enabled = true;
                this.shScrollBar.Maximum = this.TotalContentWidth;
            }
            else
            {
                this.shScrollBar.Enabled = false;
            }

            if (this.TotalContentHeight > this.Content.Height)
            {
                this.svScrollBar.Enabled = true;
                this.svScrollBar.Maximum = this.TotalContentHeight;
            }
            else
            {
                this.svScrollBar.Enabled = false;
            }

            // this must be set after the maximum is determined
            this.shScrollBar.LargeChange = this.shScrollBar.Width;
            this.shScrollBar.SmallChange = this.shScrollBar.LargeChange / 10;
            this.svScrollBar.LargeChange = this.svScrollBar.Height;
            this.svScrollBar.SmallChange = this.svScrollBar.LargeChange / 10;
        }
    }
}

示例内容:

public class ExampleContent : Control
{
    public ExampleContent()
    {
        this.DoubleBuffered = true;
    }

    static Random random = new Random();

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        var graphics = e.Graphics;

        // random color to make the clip rectangle visible in an unobtrusive way
        var color = Color.FromArgb(random.Next(160, 180), random.Next(160, 180), random.Next(160, 180));
        graphics.Clear(color);

        Debug.WriteLine(this.AutoScrollOffset.X.ToString() + ", " + this.AutoScrollOffset.Y.ToString());

        CheckerboardRenderer.DrawCheckerboard(
            graphics, 
            this.AutoScrollOffset,
            e.ClipRectangle,
            new Size(50, 50)
            );

        StaticBoxRenderer.DrawBoxes(graphics, new Point(0, this.AutoScrollOffset.Y), 100, 30);
    }
}

public static class CheckerboardRenderer
{
    public static void DrawCheckerboard(Graphics g, Point origin, Rectangle bounds, Size squareSize)
    {
        var numSquaresH = (bounds.Width + squareSize.Width - 1) / squareSize.Width + 1;
        var numSquaresV = (bounds.Height + squareSize.Height - 1) / squareSize.Height + 1;

        var startBoxH = (bounds.X - origin.X) / squareSize.Width;
        var startBoxV = (bounds.Y - origin.Y) / squareSize.Height;

        for (int i = startBoxH; i < startBoxH + numSquaresH; i++)
        {
            for (int j = startBoxV; j < startBoxV + numSquaresV; j++)
            {
                if ((i + j) % 2 == 0)
                {
                    Random random = new Random(i * j);
                    var color = Color.FromArgb(random.Next(70, 95), random.Next(70, 95), random.Next(70, 95));
                    var brush = new SolidBrush(color);
                    g.FillRectangle(brush, i * squareSize.Width + origin.X, j * squareSize.Height + origin.Y, squareSize.Width, squareSize.Height);
                    brush.Dispose();
                }
            }
        }
    }
}

public static class StaticBoxRenderer
{
    public static void DrawBoxes(Graphics g, Point origin, int boxWidth, int boxHeight)
    {
        int height = origin.Y;
        int left = origin.X;
        for (int i = 0; i < 25; i++)
        {
            Rectangle r = new Rectangle(left, height, boxWidth, boxHeight);
            g.FillRectangle(Brushes.White, r);
            g.DrawRectangle(Pens.Black, r);
            height += boxHeight;
        }
    }
}
于 2013-05-17T21:12:37.033 回答