【问题标题】:How to draw a custom slider control?如何绘制自定义滑块控件?
【发布时间】:2021-12-23 17:09:44
【问题描述】:

我创建了一个滑动条用户控件,但在运行时,当我将滑块向左或向右移动时,为什么它没有到达末端或吞下?

在用户控件设计器中,我添加了一个图片框控件:

然后在我做的代码中:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Extract
{
    public partial class Slider : UserControl
    {
        public float Height;
        public float Min = 0.0f;
        public float Max = 1.0f;

        private float defaultValue = 0.1f;

        public Slider()
        {
            InitializeComponent();            
        }

        private void sliderControl_Paint(object sender, PaintEventArgs e)
        {
            float bar_size = 0.45f;
            float x = Bar(defaultValue);
            int y = (int)(sliderControl.Height * bar_size);

            e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            e.Graphics.FillRectangle(Brushes.DimGray, 0, y, sliderControl.Width, y / 2);
            e.Graphics.FillRectangle(Brushes.Red, 0, y, x, sliderControl.Height - 2 * y);

            using (Pen pen = new Pen(Color.Black, 8))
            {
                e.Graphics.FillRectangle(Brushes.Red, 0, y, x, y / 2);
                FillCircle(e.Graphics, Brushes.Red, x, y + y / 4, y / 2);
            }

            using (Pen pen = new Pen(Color.White, 5))
            {
                DrawCircle(e.Graphics, pen, x, y + y / 4, y/ 2);
            }
        }

        public static void DrawCircle(Graphics g, Pen pen,
                                  float centerX, float centerY, float radius)
        {
            g.DrawEllipse(pen, centerX - radius, centerY - radius,
                          radius + radius, radius + radius);
        }

        public static void FillCircle(Graphics g, Brush brush,
                                      float centerX, float centerY, float radius)
        {
            g.FillEllipse(brush, centerX - radius, centerY - radius,
                          radius + radius, radius + radius);
        }

        private float Bar(float value)
        {
            return (sliderControl.Width - 24) * (value - Min) / (float)(Max - Min);
        }

        private void Thumb(float value)
        {
            if (value < Min) value = Min;
            if (value > Max) value = Max;
            defaultValue = value;

            sliderControl.Refresh();
        }

        private float SliderWidth(int x)
        {
            return Min + (Max - Min) * x / (float)(sliderControl.Width);
        }

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

            MaintainPictureBoxSize();
        }

        private void MaintainPictureBoxSize()
        {
            sliderControl.SizeMode = PictureBoxSizeMode.Normal;

            sliderControl.Location = new Point();
            sliderControl.Size = new Size();

            var clientSize = this.ClientSize;

            if (sliderControl.Image == null)
                sliderControl.Size = clientSize;
            else
            {
                Size s = sliderControl.Image.Size;
                sliderControl.Size = new Size(
                    clientSize.Width > s.Width ? clientSize.Width : s.Width,
                    clientSize.Height > s.Height ? clientSize.Height : s.Height);
            }
        }

        bool mouse = false;
        private void sliderControl_MouseDown(object sender, MouseEventArgs e)
        {
            mouse = true;
            Thumb(SliderWidth(e.X));
        }

        private void sliderControl_MouseMove(object sender, MouseEventArgs e)
        {
            if (!mouse) return;

            Thumb(SliderWidth(e.X));
        }

        private void sliderControl_MouseUp(object sender, MouseEventArgs e)
        {
            mouse = false;
        }
    }
}

当我将控件拖动到 form1 设计器然后运行应用程序时,当我将滑块拖动到左侧或右侧时,滑块的圆圈部分被吞下。

如果我将 form1 设计器中的控件调整为更小,然后运行应用程序让它像以前一样吞下,但向右它根本没有到达终点。

【问题讨论】:

  • 要在绘制区域中完全显示滑块,您需要将“值区域”缩小两倍滑块直径!所以你的 x 数字不能是值也不能是缩放值,而是 x = diameter + factor * value 和反向
  • 制作silder pictueBox.width的宽度 - 圆的直径并使其从x =半径开始。 IE。每个半径大小的左侧和右侧都会有一个填充。并且也许您将不得不添加一到两个像素的额外填充以确保没有任何内容被吞下。
  • @TaW 我还不明白怎么做。我会尝试,但如果你能给我一些例子。谢谢。
  • @OlivierJacot-Descombes 我需要做类似sliderControl.Width = sliderControl.Width - y / 2;在油漆中?

标签: c# winforms


【解决方案1】:

解释它的最简单方法是显示图像:

现在,在图片框内,想象拇指圈位于最左边和最右边的位置。这意味着条形必须从 x = 半径开始,并且条形的宽度必须是图片框的宽度减去半径的两倍。

所有内容都必须在图片框(虚线)内绘制。但这不需要在PictureBox 中放置在UserControl 上。让我们从Control 派生滑块。

public class Slider : Control
{
    ...
}

现在,在第一次编译此代码后,此滑块会自动出现在工具箱窗口中,并准备好放置在表单设计器中的表单上。

由于我们希望能够在属性窗口中设置它的属性,并且我们希望能够在滑动后读取当前值,所以让我们添加一个事件和一些属性。

public event EventHandler ValueChanged;

private float _min = 0.0f;
public float Min
{
    get => _min;
    set {
        _min = value;
        RecalculateParameters();
    }
}

private float _max = 1.0f;
public float Max
{
    get => _max;
    set {
        _max = value;
        RecalculateParameters();
    }
}

private float _value = 0.3f;
public float Value
{
    get => _value;
    set {
        _value = value;
        ValueChanged?.Invoke(this, EventArgs.Empty);
        RecalculateParameters();
    }
}

这需要一些字段和RecalculateParameters 方法。

private float _radius;
private PointF _thumbPos;
private SizeF _barSize;
private PointF _barPos;

private void RecalculateParameters()
{
    _radius = 0.5f * ClientSize.Height;
    _barSize = new SizeF(ClientSize.Width - 2f * _radius, 0.5f * ClientSize.Height);
    _barPos = new PointF(_radius, (ClientSize.Height - _barSize.Height) / 2);
    _thumbPos = new PointF(
        _barSize.Width / (Max - Min) * Value + _barPos.X,
        _barPos.Y + 0.5f * _barSize.Height);
    Invalidate();
}

在这个派生控件中,我们覆盖事件处理程序(On... 方法)而不是订阅事件:

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);

    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    e.Graphics.FillRectangle(Brushes.DimGray,
        _barPos.X, _barPos.Y, _barSize.Width, _barSize.Height);
    e.Graphics.FillRectangle(Brushes.Red,
        _barPos.X, _barPos.Y, _thumbPos.X - _barPos.X, _barSize.Height);

    e.Graphics.FillCircle(Brushes.White, _thumbPos.X, _thumbPos.Y, _radius);
    e.Graphics.FillCircle(Brushes.Red, _thumbPos.X, _thumbPos.Y, 0.7f * _radius);
}

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

现在让我们编译这段代码并为表单添加一个滑块。看看我们如何在设计器中调整它的大小。

还要注意,在属性窗口中,我们会在“其他”部分看到新的 Slider 属性 MaxMinValue。我们可以在这里更改它们,拇指位置会自动更新。

我们仍然需要启用移动滑块的代码。当我们点击拇指时,我们可能点击了一点偏离它的中心。在移动鼠标时保持这个偏移是很自然的。因此,我们将此差异存储在变量_delta中。

bool _moving = false;
SizeF _delta;

protected override void OnMouseDown(MouseEventArgs e)
{
    base.OnMouseDown(e);

    // Difference between tumb and mouse position.
    _delta = new SizeF(e.Location.X - _thumbPos.X, e.Location.Y - _thumbPos.Y);
    if (_delta.Width * _delta.Width + _delta.Height * _delta.Height <= _radius * _radius) {
        // Clicking inside thumb.
        _moving = true;
    }
}

我们还使用Pythagorean theorem 计算OnMouseDown 中鼠标位置到拇指位置的距离。只有当鼠标在拇指内时,我们才会通过设置_moving = true;来移动拇指

OnMouseMove 中,我们计算并设置新的Value。这会自动触发重新计算参数并重新绘制滑块。

protected override void OnMouseMove(MouseEventArgs e)
{
    base.OnMouseMove(e);
    if (_moving) {
        float thumbX = e.Location.X - _delta.Width;
        if (thumbX < _barPos.X) {
            thumbX = _barPos.X;
        } else if (thumbX > _barPos.X + _barSize.Width) {
            thumbX = _barPos.X + _barSize.Width;
        }
        Value = (thumbX - _barPos.X) * (Max - Min) / _barSize.Width;
    }
}

protected override void OnMouseUp(MouseEventArgs e)
{
    base.OnMouseUp(e);
    _moving = false;
}

我们可以通过在表单中​​添加TextBox 并响应ValueChanged 事件来测试滑块。我们可以通过单击闪烁符号将属性窗口切换到“事件”来添加事件处理程序,然后双击“其他”部分中的ValueChanged

private void Slider1_ValueChanged(object sender, EventArgs e)
{
    textBox1.Text = slider1.Value.ToString();
}

现在,当我们移动拇指时,文本框会显示值。


这里又是滑块的整个代码(使用 C# 10.0 文件作用域命名空间):

using System.Drawing.Drawing2D;

namespace WinFormsSliderBar;

public class Slider : Control
{
    private float _radius;
    private PointF _thumbPos;
    private SizeF _barSize;
    private PointF _barPos;

    public event EventHandler ValueChanged;

    public Slider()
    {
        // This reduces flicker
        DoubleBuffered = true;
    }

    private float _min = 0.0f;
    public float Min
    {
        get => _min;
        set {
            _min = value;
            RecalculateParameters();
        }
    }

    private float _max = 1.0f;
    public float Max
    {
        get => _max;
        set {
            _max = value;
            RecalculateParameters();
        }
    }

    private float _value = 0.3f;
    public float Value
    {
        get => _value;
        set {
            _value = value;
            ValueChanged?.Invoke(this, EventArgs.Empty);
            RecalculateParameters();
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        e.Graphics.FillRectangle(Brushes.DimGray,
            _barPos.X, _barPos.Y, _barSize.Width, _barSize.Height);
        e.Graphics.FillRectangle(Brushes.Red,
            _barPos.X, _barPos.Y, _thumbPos.X - _barPos.X, _barSize.Height);

        e.Graphics.FillCircle(Brushes.White, _thumbPos.X, _thumbPos.Y, _radius);
        e.Graphics.FillCircle(Brushes.Red, _thumbPos.X, _thumbPos.Y, 0.7f * _radius);
    }

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

    private void RecalculateParameters()
    {
        _radius = 0.5f * ClientSize.Height;
        _barSize = new SizeF(ClientSize.Width - 2f * _radius, 0.5f * ClientSize.Height);
        _barPos = new PointF(_radius, (ClientSize.Height - _barSize.Height) / 2);
        _thumbPos = new PointF(
            _barSize.Width / (Max - Min) * Value + _barPos.X,
            _barPos.Y + 0.5f * _barSize.Height);
        Invalidate();
    }

    bool _moving = false;
    SizeF _delta;

    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);

        // Difference between tumb and mouse position.
        _delta = new SizeF(e.Location.X - _thumbPos.X, e.Location.Y - _thumbPos.Y);
        if (_delta.Width * _delta.Width + _delta.Height * _delta.Height <= _radius * _radius) {
            // Clicking inside thumb.
            _moving = true;
        }
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        base.OnMouseMove(e);
        if (_moving) {
            float thumbX = e.Location.X - _delta.Width;
            if (thumbX < _barPos.X) {
                thumbX = _barPos.X;
            } else if (thumbX > _barPos.X + _barSize.Width) {
                thumbX = _barPos.X + _barSize.Width;
            }
            Value = (thumbX - _barPos.X) * (Max - Min) / _barSize.Width;
        }
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        base.OnMouseUp(e);
        _moving = false;
    }
}

以及画圆的图形扩展:

namespace WinFormsSliderBar;

public static class GraphicsExtensions
{
    public static void DrawCircle(this Graphics g, Pen pen,
                                  float centerX, float centerY, float radius)
    {
        g.DrawEllipse(pen, centerX - radius, centerY - radius,
                      radius + radius, radius + radius);
    }

    public static void FillCircle(this Graphics g, Brush brush,
                                  float centerX, float centerY, float radius)
    {
        g.FillEllipse(brush, centerX - radius, centerY - radius,
                      radius + radius, radius + radius);
    }
}

【讨论】:

  • 工作得很好。如何添加使用鼠标滚轮的功能?如果我想左右滚动,就像移动一样,但是用鼠标滚轮。受保护的覆盖无效 OnMouseWheel(MouseEventArgs e) { base.OnMouseWheel(e); }
  • 我从未使用过鼠标滚轮,但我认为它在 EventArgs 中有一个 Delta 值,您可以使用它来更改我们的 Value 属性。您必须使用适当的因子缩放此值。例如。 Value += factor * e.Delta;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-30
  • 1970-01-01
  • 2017-02-16
  • 2010-12-29
  • 2012-04-03
相关资源
最近更新 更多