【问题标题】:Custom control using ScrollableControl with fixed position text使用带有固定位置文本的 ScrollableControl 自定义控件
【发布时间】:2017-08-29 17:40:21
【问题描述】:

我正在构建将用于显示图块地图的自定义用户控件,作为我选择 ScrollableControl 的基类,因为我希望在我的控件中有滚动条。

我已经成功创建了负责绘制只需要的元素的绘制逻辑。

现在我正在尝试添加将始终在同一个位置可见的静态文本(在我的例子中,左上角带有红色文本的白色框):

这在上面的 gif 中不是很明显,但是当我使用鼠标或滚动条滚动时,那个白框会闪烁和跳跃。

我的问题是我应该如何更改我的代码以在该可滚动内容之上具有可滚动内容和固定位置内容?

ScrollableControl 作为基类是不错的选择吗?

下面是我的代码:

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);
    }
}

编辑:
我进行了一些搜索,发现具有类似功能的 UserControl - https://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView 并在控件作者博客http://objectlistview.sourceforge.net/cs/blog1.html#blog-overlays 上阅读了更多内容后,我发现他正在使用位于控件顶部的透明表单。 我真的很想避免这种情况,但仍然有覆盖在我的控制之上。

【问题讨论】:

    标签: c# .net winforms user-controls


    【解决方案1】:

    您正在使用名为“拖动时显示窗口内容”的 Windows 系统选项进行战斗。默认情况下始终打开,this web page 显示如何关闭它。

    解决了这个问题,但它不是您可以依赖的东西,因为它会影响所有应用程序中的all 可滚动窗口。要求用户为你关闭它是不现实的,用户喜欢这个选项,所以他们会忽略你。他们没有提供为特定窗口关闭它的选项,这是一个相当大的疏忽。在自助服务终端应用中,这是一个不错的解决方案。

    简而言之,该选项的工作方式是 Windows 本身使用 ScrollWindowEx() winapi 函数滚动窗口内容。使用窗口内容的 bitblt 移动像素,并且只为滚动显示的窗口部分生成绘制请求。通常只有几行像素,所以完成速度非常快。问题是,bitblt 也会移动您的固定像素。重绘将它们移回。很明显,人眼对这样的运动非常敏感,在过去的一百万年里帮助避免成为狮子的午餐。

    您必须消除 ScrollWindowsEx() 的麻烦,防止它移动像素,即使您无法阻止它被调用。这需要大锤LockWindowUpdate()。您可以在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);
    

    不是很漂亮,使用单独的 Label 控件应该开始听起来很有吸引力。

    【讨论】:

    • 非常感谢您的回复。再次您的代码帮助(一如既往)!现在,当我使用滚动条时,我可以平滑滚动,并且我的标签位于顶部。不幸的是,当我使用鼠标滚轮滚动时,没有任何改变,我的白框仍然跳动。使用鼠标滚轮滚动也可以修复吗?
    • 我已经为OnMouseWheel 添加了覆盖:LockWindowUpdate(Handle);base.OnMouseWheel(e);LockWindowUpdate(IntPtr.Zero);Update(); 这解决了我的鼠标滚轮问题。请检查并更新您的答案。这样,遇到同样问题的人就会有可行的解决方案。
    • 我注意到当在 OnScroll e.Type==ThumbPosition 内部时,我在调整控制(整个表单)i63.tinypic.com/2zfikbr.gif 时会出现奇怪的行为@我已经设法通过更改来解决这个问题if (e.Type != ScrollEventType.Last) LockWindowUpdate(this.Handle);if (e.Type != ScrollEventType.Last &amp;&amp; e.Type != ScrollEventType.ThumbPosition) LockWindowUpdate(Handle); 你能再看看这个并更新你的答案吗?
    • 好的,@Misiu。还有一个修复。您还需要处理鼠标滚轮。受保护的覆盖无效 OnMouseWheel(MouseEventArgs e) { LockWindowUpdate(Handle); base.OnMouseWheel(e); LockWindowUpdate(IntPtr.Zero); }
    【解决方案2】:

    您能否只为该控件添加一个标签(在顶部),换句话说 - 您不能将它用作面板吗?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-16
      • 1970-01-01
      • 2021-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多