Custom control with ScrollableControl with fixed position text

I am creating a custom control that will be used to display a tile map, as the base class that I chose ScrollableControl

because I want to have scrollbars in my control.

I have successfully created a drawing logic that is responsible for drawing only the required elements.

Now I'm trying to add static text that will always appear in one place (in my case, a white box with red text in the upper left corner):

enter image description here

It is not clearly visible in the above gif, but that white box blinks and jumps a little when I scroll with my mouse or scrollbars.

My question is, how do I change my code to have scrollable content and fixed position content on top of this scrollable content?

Is it a ScrollableControl

good choice as a base class?

Below is my code:

class TestControl : ScrollableControl
{
    private int _tileWidth = 40;
    private int _tileHeight = 40;
    private int _tilesX = 20;
    private int _tilesY = 20;

    public TestControl()
    {
        SetStyle(ControlStyles.ResizeRedraw, true);
        SetStyle(ControlStyles.UserPaint, true);
        SetStyle(ControlStyles.AllPaintingInWmPaint, true);
        SetStyle(ControlStyles.Opaque, true);
        SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        UpdateStyles();
        ResizeRedraw = true;
        AutoScrollMinSize = new Size(_tilesX * _tileWidth, _tilesY * _tileHeight);

        Scroll += (sender, args) => { Invalidate(); };
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        e.Graphics.FillRectangle(new SolidBrush(BackColor), ClientRectangle);
        e.Graphics.TranslateTransform(AutoScrollPosition.X, AutoScrollPosition.Y);
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

        var offsetX = (AutoScrollPosition.X * -1) / _tileWidth;
        var offsetY = (AutoScrollPosition.Y * -1) / _tileHeight;

        var visibleX = Width / _tileWidth + 2;
        var visibleY = Height / _tileHeight + 2;

        var x = Math.Min(visibleX + offsetX, _tilesX);
        var y = Math.Min(visibleY + offsetY, _tilesY);

        for (var i = offsetX; i < x; i++)
        {
            for (var j = offsetY; j < y; j++)
            {
                e.Graphics.FillRectangle(Brushes.Beige, new Rectangle(i*_tileWidth, j*_tileHeight, _tileWidth, _tileHeight));
                e.Graphics.DrawString(string.Format("{0}:{1}", i, j), Font, Brushes.Black, new Rectangle(i * _tileWidth, j * _tileHeight, _tileWidth, _tileHeight));
            }
        }

        using (var p = new Pen(Color.Black))
        {
            for (var i = offsetX + 1; i < x; i++)
            {
                e.Graphics.DrawLine(p, i*_tileWidth, 0, i*_tileWidth, y*_tileHeight);
            }

            for (var i = offsetY + 1; i < y; i++)
            {
                e.Graphics.DrawLine(p, 0, i*_tileHeight, x*_tileWidth, i*_tileHeight);
            }
        }

        e.Graphics.FillRectangle(Brushes.White, AutoScrollPosition.X * -1, AutoScrollPosition.Y * -1, 35, 14);
        e.Graphics.DrawString("TEST", DefaultFont, new SolidBrush(Color.Red), AutoScrollPosition.X * -1, AutoScrollPosition.Y * -1);
    }
}

      

EDIT:
I searched a bit and found a UserControl that has similar functionality - https://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView and after reading a little more on the author's blog http : //objectlistview.sourceforge.net/cs/blog1.html#blog-overlays I found out that it uses a transparent form that sits on top of the control. I'd really like to avoid this, but I'm still getting the upper hand over my control.

+3


source to share


2 answers


You are fighting a Windows system option named "Show window contents while dragging." Always on by default, this web page shows you how to turn it off.

Solves the problem but cannot be relied upon as it affects the entire scrollable window across all applications. Requiring the user to turn it off for you is unrealistic, users like this option so they just ignore you. That they didn't provide an option to disable it for a specific window was a pretty serious oversight. This is a good solution in a kiosk app.

In short, the way to use this option is that Windows itself scrolls the contents of the window using ScrollWindowEx () winapi. Using the bitblt of the window content to move pixels and generate a draw request for the portion of the window that was detected by the scroll. Usually only a few lines of pixels, so it is very fast. The problem is bitblt also moves your fixed pixels. Repainting brings them back. Quite noticeably, the human eye is very sensitive to movement like this, and has helped avoid the lion's meal for the past millions of years.



You will need to remove sting from ScrollWindowsEx () not letting it move pixels, even if you cannot stop it from being called. It takes a heavy sledgehammer, LockWindowUpdate () . You will find the code in this post .

using System.Runtime.InteropServices;
...

    protected override void OnScroll(ScrollEventArgs e) {
        if (e.Type == ScrollEventType.First) {
            LockWindowUpdate(this.Handle);
        }
        else {
            LockWindowUpdate(IntPtr.Zero);
            this.Update();
            if (e.Type != ScrollEventType.Last) LockWindowUpdate(this.Handle);
        }
    }

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool LockWindowUpdate(IntPtr hWnd);

      

Not that pretty, using a separate Label control should start to look attractive.

+2


source


You can just add a label to this control (on top), in other words - can you use it as a panel?



0


source







All Articles