简体   繁体   中英

How to draw a custom slider control?

I created a slider bar user control but at run time when I move the slider to the left or right why it's not getting to the end or swallow?

In the user control designer I added a pictureBox control:

用户控件设计器中的图片框

Then in the code I did:

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

When I drag the control to the form1 designer and then running the application then when I drag the slider for example to the left or to the right the circle of the slider is partly swallow.

and if I resize the control in form1 designer to be smaller and then running the application to left it swallow as before but to the right it's not getting to the end at all.

滑块不适合侧面

The easiest way to explain it is to show an image:

在此处输入图像描述

Now, inside the picture box, imagine the thumb circle to be in the left most and right most positions. This means that the bar must start at x = radius and that the width of the bar must be the width of the picture box minus twice the radius.

Everything must be drawn inside the picture box (dotted line). But this needs not to be in a PictureBox placed on a UserControl . Let's derive the slider from Control instead.

public class Slider : Control
{
    ...
}

Now, after having compiled this code for the first time, this Slider automatically appears in the Toolbox window and is ready to be placed on a form in the forms designer.

Since we want to be able to set its properties in the properties window and we want to be able to read the current value after sliding, let's add an event and some properties.

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

This requires some fields and the RecalculateParameters method.

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

Inside this derived control we override the event handlers ( On... methods) instead of subscribing to the events:

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

Now let's compile this code and let's add a slider to a form. See how we can resize it in the designer.

Note also, that in the properties window we see the new Slider properties Max , Min and Value in the "Misc" section. We can change them here and the thumb position is automatically updated.

We still need the code to enable moving the slider. When we click on the thumb, we might have clicked a bit off the center of the thumb. 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;
}

We can test the slider by adding a TextBox to the form and responding to the ValueChanged event. We can add an event handler by switching the properties window to "Events" by clicking on the flash symbol and then double click on ValueChanged in the "Misc" section.

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

Now, when we move the thumb, the text box displays the values.


Here again the whole code of the slider (using C# 10.0 file scoped namespaces):

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

and the graphic extensions for drawing circles:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM