How to use ScrollableControl with AutoScroll set to false

南楼画角 提交于 2019-12-05 23:56:12

问题


I have a custom control that zooms on a custom drawn document canvas.

I tried using AutoScroll but it was not giving satisfactory results. When I would set AutoScrollPosition and AutoScrollMinSize back to back (in any order) it would force a paint and cause jitter each time the zoom changes. I assume this was because it was calling an Update and not Invalidate when I modified both properties.

I am now manually setting the HorizontalScroll and VerticalScroll properties with AutoScroll set to false like so each time the Zoom level or the client size changes:

int canvasWidth = (int)Math.Ceiling(Image.Width * Zoom) + PageMargins.Horizontal;
int canvasHeight = (int)Math.Ceiling(Image.Height * Zoom) + PageMargins.Vertical;

HorizontalScroll.Maximum = canvasWidth;
HorizontalScroll.LargeChange = ClientSize.Width;

VerticalScroll.Maximum = canvasHeight;
VerticalScroll.LargeChange = ClientSize.Height;

if (canvasWidth > ClientSize.Width)
{
    HorizontalScroll.Visible = true;
}
else
{
    HorizontalScroll.Visible = false;
    HorizontalScroll.Value = 0;
}

if (canvasHeight > ClientSize.Height)
{
    VerticalScroll.Visible = true;
}
else
{
    VerticalScroll.Visible = false;
    VerticalScroll.Value = 0;
}

int focusX = (int)Math.Floor((FocusPoint.X * Zoom) + PageMargins.Left);
int focusY = (int)Math.Floor((FocusPoint.Y * Zoom) + PageMargins.Top);

focusX = focusX - ClientSize.Width / 2;
focusY = focusY - ClientSize.Height / 2;

if (focusX < 0)
    focusX = 0;
if (focusX > canvasWidth - ClientSize.Width)
    focusX = canvasWidth - ClientSize.Width;

if (focusY < 0)
    focusY = 0;
if (focusY > canvasHeight - ClientSize.Height)
    focusY = canvasHeight - ClientSize.Height;

if (HorizontalScroll.Visible)
    HorizontalScroll.Value = focusX;

if (VerticalScroll.Visible)
    VerticalScroll.Value = focusY;

In this case, FocusPoint is a PointF structure that holds the coordinates in the bitmap which the user is focused on (for example, when they mouse wheel to zoom in they are focusing on the current mouse location at that time). This functionality works for the most part.

What does not work is the scroll bars. If the user tries to manually scroll by clicking on either scroll bar, they both keep returning to 0. I do not set them anywhere else in my code. I have tried writing the following in the OnScroll() method:

if (se.ScrollOrientation == ScrollOrientation.VerticalScroll)
{
    VerticalScroll.Value = se.NewValue;
}
else
{
    HorizontalScroll.Value = se.NewValue;
}

Invalidate();

But this causes some very erratic behavior including flicking and scrolling out of bounds.

How am I supposed to write the code for OnScroll? I've tried the base.OnScroll but it didn't do anything while AutoScroll is set to false.


回答1:


I ended up implementing my own custom scrolling by creating 3 child controls: an HScrollBar, a VScrollBar, and a Panel.

I hide ClientSize and ClientRectangle like so:

public new Rectangle ClientRectangle
{
    get
    {
        return new Rectangle(new Point(0, 0), ClientSize);
    }
}

public new Size ClientSize
{
    get
    {
        return new Size(
            base.ClientSize.Width - VScrollBar.Width,
            base.ClientSize.Height - HScrollBar.Height
        );
    }
}

The layout is done in OnClientSizeChanged:

protected override void OnClientSizeChanged(EventArgs e)
{
    base.OnClientSizeChanged(e);

    HScrollBar.Location = new Point(0, base.ClientSize.Height - HScrollBar.Height);
    HScrollBar.Width = base.ClientSize.Width - VScrollBar.Width;

    VScrollBar.Location = new Point(base.ClientSize.Width - VScrollBar.Width, 0);
    VScrollBar.Height = base.ClientSize.Height - HScrollBar.Height;

    cornerPanel.Size = new Size(VScrollBar.Width, HScrollBar.Height);
    cornerPanel.Location = new Point(base.ClientSize.Width - cornerPanel.Width, base.ClientSize.Height - cornerPanel.Height);
}

Each ScrollBar has their Scroll event subscribed to the following:

private void ScrollBar_Scroll(object sender, ScrollEventArgs e)
{
    OnScroll(e);
}

And finally we can allow MouseWheel events to scroll with the following:

protected override void OnMouseWheel(MouseEventArgs e)
{
    int xOldValue = VScrollBar.Value;

    if (e.Delta > 0)
    {
        VScrollBar.Value = (int)Math.Max(VScrollBar.Value - (VScrollBar.SmallChange * e.Delta), 0);
        OnScroll(new ScrollEventArgs(ScrollEventType.ThumbPosition, xOldValue, VScrollBar.Value, ScrollOrientation.VerticalScroll));
    }
    else
    {
        VScrollBar.Value = (int)Math.Min(VScrollBar.Value - (VScrollBar.SmallChange * e.Delta), VScrollBar.Maximum - (VScrollBar.LargeChange - 1));
        OnScroll(new ScrollEventArgs(ScrollEventType.ThumbPosition, xOldValue, VScrollBar.Value, ScrollOrientation.VerticalScroll));
    }
}

For custom painting, you would use the following statement:

e.Graphics.TranslateTransform(-HScrollBar.Value, -VScrollBar.Value);

This worked perfectly without the glitches present when using AutoScroll.



来源:https://stackoverflow.com/questions/10286265/how-to-use-scrollablecontrol-with-autoscroll-set-to-false

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!