拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 如何绘制自定义滑块控制元件?

如何绘制自定义滑块控制元件?

白鹭 - 2022-02-13 1978 0 0

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

在用户控制元件设计器中,我添加了一个 pictureBox 控制元件:

如何绘制自定义滑块控制元件?

然后在我做的代码中:

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 设计器中的控制元件大小调整为更小,然后运行应用程序使其像以前一样吞下,但在右侧它根本不会结束。

如何绘制自定义滑块控制元件?

uj5u.com热心网友回复:

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

如何绘制自定义滑块控制元件?

现在,在图片框内,想象拇指圆圈位于最左侧和最右侧的位置。这意味着条形必须从 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在“杂项”部分。我们可以在这里更改它们,拇指位置会自动更新。

We still need the code to enable moving the slider. When we click on the thumb, we might have clicked a bit off its center. It feels natural to keep this offset while moving the mouse. Therefore, we store this difference in a variable _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;
    }
}

We also calculate the distance of the mouse position to the thumb position in OnMouseDown by using the Pythagorean theorem. Only if the mouse is inside the thumb, we initiate moving the thumb by setting _moving = true;

In OnMouseMove we calculate and set the new Value. This automatically triggers recalculating the parameters and redraws the slider.

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表单添加 a并回应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);
    }
}
标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *