How to draw a custom slider control? - c#

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

Related

C# winforms - How to combine scrollbar with mouse wheel zooming?

Problem - I'm writing a program that draws graphics, and zooming is one of the features. Currently, a picturebox is placed on a panel, and the picturebox has vertical and horizontal scroll bars on the right and bottom. How to combine scrollbar with mouse wheel zooming? And I'm not sure if I should use paint to draw the graphics or set a bitmap to draw the graphics onto it?
Expected - When the mouse wheel is scrolled, the entire canvas(picturebox) include drawn graphics are scaled according to the current mouse position as the center (the horizontal and vertical scroll bars change according to the zoom center). When the mouse wheel is pressed and moved, the canvas can be dragged freely.
Expected as follows:
The initial code
private List<Point> _points;
private int _pointRadius = 50;
private float _scale = 1f;
private float _offsetX = 0f;
private float _offsetY = 0f;
private void picturebox_MouseDown(object sender, MouseEventArgs e)
{
_points.Add(e.Location);
}
private void picturebox_MouseWheel(object sender, MouseEvnetArgs e)
{
if(e.Delta < 0)
{
_scale += 0.1f;
_offsetX = e.X * (1f - _scale);
_offsetY = e.X * (1f - _scale);
}
else
{
_scale -= 0.1f;
_offsetX = e.X * (1f - _scale);
_offsetY = e.X * (1f - _scale);
}
picturebox.Invalidate();
}
private void picturebox_Paint(object sender, PaintEventArgs e)
{
e.Graphics.TranslateTransform(_offsetX, _offsetY);
e.Graphics.ScaleTransform(_scaleX, _scaleY);
foreach (Point p in _points)
{
e.Graphics.FillEllipse(Brushes.Black, p.X, - _pointRadius, p.Y - _pointRadius, 2 * _pointRadius, 2 * _pointRadius);
}
}
Hope the answer is modified based on the initial code.
Thanks in advance to everyone who helped me.
Would it be easier if I drew the graphics on a Bitmap?
Considering the nature of your task and the already implemented solutions in my ImageViewer I created a solution that draws the result in a Metafile, which is both elegant, consumes minimal memory and allows zooming without quality issues.
Here is the stripped version of my ImageViewer:
public class MetafileViewer : Control
{
private HScrollBar sbHorizontal = new HScrollBar { Visible = false };
private VScrollBar sbVertical = new VScrollBar { Visible = false };
private Metafile? image;
private Size imageSize;
private Rectangle targetRectangle;
private Rectangle clientRectangle;
private float zoom = 1;
private bool sbHorizontalVisible;
private bool sbVerticalVisible;
private int scrollFractionVertical;
public MetafileViewer()
{
Controls.AddRange(new Control[] { sbHorizontal, sbVertical });
sbHorizontal.ValueChanged += ScrollbarValueChanged;
sbVertical.ValueChanged += ScrollbarValueChanged;
}
void ScrollbarValueChanged(object? sender, EventArgs e) => Invalidate();
public Metafile? Image
{
get => image;
set
{
image = value;
imageSize = image?.Size ?? default;
InvalidateLayout();
}
}
public bool TryTranslate(Point mouseCoord, out PointF canvasCoord)
{
canvasCoord = default;
if (!targetRectangle.Contains(mouseCoord))
return false;
canvasCoord = new PointF((mouseCoord.X - targetRectangle.X) / zoom, (mouseCoord.Y - targetRectangle.Y) / zoom);
if (sbHorizontalVisible)
canvasCoord.X += sbHorizontal.Value / zoom;
if (sbVerticalVisible)
canvasCoord.Y += sbVertical.Value / zoom;
return true;
}
private void InvalidateLayout()
{
Invalidate();
if (imageSize.IsEmpty)
{
sbHorizontal.Visible = sbVertical.Visible = sbHorizontalVisible = sbVerticalVisible = false;
targetRectangle = Rectangle.Empty;
return;
}
Size clientSize = ClientSize;
if (clientSize.Width < 1 || clientSize.Height < 1)
{
targetRectangle = Rectangle.Empty;
return;
}
Size scaledSize = imageSize.Scale(zoom);
// scrollbars visibility
sbHorizontalVisible = scaledSize.Width > clientSize.Width
|| scaledSize.Width > clientSize.Width - SystemInformation.VerticalScrollBarWidth && scaledSize.Height > clientSize.Height;
sbVerticalVisible = scaledSize.Height > clientSize.Height
|| scaledSize.Height > clientSize.Height - SystemInformation.HorizontalScrollBarHeight && scaledSize.Width > clientSize.Width;
if (sbHorizontalVisible)
clientSize.Height -= SystemInformation.HorizontalScrollBarHeight;
if (sbVerticalVisible)
clientSize.Width -= SystemInformation.VerticalScrollBarWidth;
if (clientSize.Width < 1 || clientSize.Height < 1)
{
targetRectangle = Rectangle.Empty;
return;
}
Point clientLocation = Point.Empty;
var targetLocation = new Point((clientSize.Width >> 1) - (scaledSize.Width >> 1),
(clientSize.Height >> 1) - (scaledSize.Height >> 1));
// both scrollbars
if (sbHorizontalVisible && sbVerticalVisible)
{
sbHorizontal.Dock = sbVertical.Dock = DockStyle.None;
sbHorizontal.Width = clientSize.Width;
sbHorizontal.Top = clientSize.Height;
sbHorizontal.Left = 0;
sbVertical.Height = clientSize.Height;
sbVertical.Left = clientSize.Width;
}
// horizontal scrollbar
else if (sbHorizontalVisible)
sbHorizontal.Dock = DockStyle.Bottom;
// vertical scrollbar
else if (sbVerticalVisible)
sbVertical.Dock = DockStyle.Right;
// adjust scrollbar values
if (sbHorizontalVisible)
{
sbHorizontal.Minimum = targetLocation.X;
sbHorizontal.Maximum = targetLocation.X + scaledSize.Width;
sbHorizontal.LargeChange = clientSize.Width;
sbHorizontal.SmallChange = 32;
sbHorizontal.Value = Math.Min(sbHorizontal.Value, sbHorizontal.Maximum - sbHorizontal.LargeChange);
}
if (sbVerticalVisible)
{
sbVertical.Minimum = targetLocation.Y;
sbVertical.Maximum = targetLocation.Y + scaledSize.Height;
sbVertical.LargeChange = clientSize.Height;
sbVertical.SmallChange = 32;
sbVertical.Value = Math.Min(sbVertical.Value, sbVertical.Maximum - sbVertical.LargeChange);
}
sbHorizontal.Visible = sbHorizontalVisible;
sbVertical.Visible = sbVerticalVisible;
clientRectangle = new Rectangle(clientLocation, clientSize);
targetRectangle = new Rectangle(targetLocation, scaledSize);
if (sbVerticalVisible)
clientRectangle.X = SystemInformation.VerticalScrollBarWidth;
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
InvalidateLayout();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (image == null || e.ClipRectangle.Width <= 0 || e.ClipRectangle.Height <= 0)
return;
if (targetRectangle.IsEmpty)
InvalidateLayout();
if (targetRectangle.IsEmpty)
return;
Graphics g = e.Graphics;
g.IntersectClip(clientRectangle);
Rectangle dest = targetRectangle;
if (sbHorizontalVisible)
dest.X -= sbHorizontal.Value;
if (sbVerticalVisible)
dest.Y -= sbVertical.Value;
g.DrawImage(image, dest);
g.DrawRectangle(SystemPens.ControlText, Rectangle.Inflate(targetRectangle, 1, 1));
}
protected override void OnMouseWheel(MouseEventArgs e)
{
base.OnMouseWheel(e);
switch (ModifierKeys)
{
// zoom
case Keys.Control:
float delta = (float)e.Delta / SystemInformation.MouseWheelScrollDelta / 5;
if (delta.Equals(0f))
return;
delta += 1;
SetZoom(zoom * delta);
break;
// vertical scroll
case Keys.None:
VerticalScroll(e.Delta);
break;
}
}
private void VerticalScroll(int delta)
{
// When scrolling by mouse, delta is always +-120 so this will be a small change on the scrollbar.
// But we collect the fractional changes caused by the touchpad scrolling so it will not be lost either.
int totalDelta = scrollFractionVertical + delta * sbVertical.SmallChange;
scrollFractionVertical = totalDelta % SystemInformation.MouseWheelScrollDelta;
int newValue = sbVertical.Value - totalDelta / SystemInformation.MouseWheelScrollDelta;
SetValueSafe(sbVertical, newValue);
}
internal static void SetValueSafe(ScrollBar scrollBar, int value)
{
if (value < scrollBar.Minimum)
value = scrollBar.Minimum;
else if (value > scrollBar.Maximum - scrollBar.LargeChange + 1)
value = scrollBar.Maximum - scrollBar.LargeChange + 1;
scrollBar.Value = value;
}
private void SetZoom(float value)
{
const float maxZoom = 10f;
float minZoom = image == null ? 1f : 1f / Math.Min(imageSize.Width, imageSize.Height);
if (value < minZoom)
value = minZoom;
if (value > maxZoom)
value = maxZoom;
if (zoom.Equals(value))
return;
zoom = value;
InvalidateLayout();
}
}
And then the updated version of your initial code (add a new point by right click, zoom by Ctrl + mouse scroll):
public partial class RenderMetafileForm : Form
{
private static Size canvasSize = new Size(300, 200);
private List<PointF> points = new List<PointF>();
private const float pointRadius = 5;
public RenderMetafileForm()
{
InitializeComponent();
metafileViewer.MouseClick += MetafileViewer_MouseClick;
UpdateMetafile();
}
private void MetafileViewer_MouseClick(object? sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right && metafileViewer.TryTranslate(e.Location, out var coord))
{
points.Add(coord);
UpdateMetafile();
}
}
private void UpdateMetafile()
{
Graphics refGraph = Graphics.FromHwnd(IntPtr.Zero);
IntPtr hdc = refGraph.GetHdc();
Metafile result;
try
{
result = new Metafile(hdc, new Rectangle(Point.Empty, canvasSize), MetafileFrameUnit.Pixel, EmfType.EmfOnly, "Canvas");
using (var g = Graphics.FromImage(result))
{
foreach (PointF point in points)
g.FillEllipse(Brushes.Navy, point.X - pointRadius, point.Y - pointRadius, pointRadius * 2, pointRadius * 2);
}
}
finally
{
refGraph.ReleaseHdc(hdc);
refGraph.Dispose();
}
Metafile? previous = metafileViewer.Image;
metafileViewer.Image = result;
previous?.Dispose();
}
}
Result:
⚠️ Note: I did not add panning by keyboard or by grabbing the image but you can extract those from the original ImageViewer. Also, I removed DPI-aware scaling but see the ScaleSize extensions in the linked project.

how to animate custom property in xamarin.android

I have drawn one line.I have added AngleProperty in my view. Using that angle property i need to animate that line to that angle.
Here is my View in which line is drawn
public class DrawView : View
{
Paint paint = new Paint();
private double mvalue = 90;
public double Angle
{
get { return mvalue; }
set {
ObjectAnimator anim = ObjectAnimator.OfFloat(this, "Angle", (float)this.Angle, (float)value);
anim.SetDuration(500);
anim.Start();
mvalue = value;
}
}
public DrawView(Context context):base(context)
{
paint.Color = Color.Green;
}
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Value = Angle* Math.Pi / 180;
var startX = 300;
var startY = 300;
var endX = 500 + 40 * Math.Sin(Value);
var endY = 500 + 40 * Math.Cos(Value);
canvas.DrawLine(startX, startY, (float)endX, (float)endY, paint);
}
}
In main Activity, i have added button in which angle is given,
public class MainActivity : Activity
{
DrawView drawview;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
drawview = new DrawView(this);
Button b = new Button(this);
b.SetHeight(50);
b.SetWidth(50);
b.Click += B_Click;
LinearLayout lay = new LinearLayout(this);
lay.AddView(b);
lay.AddView(drawview);
}
private void B_Click(object sender, System.EventArgs e)
{
drawview.Angle= 180;
}
}
Anyone please suggest how to animate that line to certain angle
Logic is correct, but there are several problems. Since you have defined a Angle property, you should use the Angle property to control the Animation instead of using Value property, then it can work fine like this. For more information, you could read the document.
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Angle = Angle * Math.PI / 180;
var startX = 300;
var startY = 300;
var endX = 500 + 40 * Math.Sin(Angle);
var endY = 500 + 40 * Math.Cos(Angle);
canvas.DrawLine(startX, startY, (float)endX, (float)endY, paint);
}
When click a button, start the Animation:
private void B_Click(object sender, System.EventArgs e)
{
//certain angle decided by EditText's text
Int32 number = int.Parse(et.Text.ToString());
drawview.Angle = number;
}

select two point to Draw a circle

I am using visual studio c# windows form, I need help to draw a circle using the Mouse click.. first click will give me the center of the circle equal to the cursor position and the second click will give me a point on the border of the circle equal to the second position of the cursor, the distance between the to points will give me the radius..now I have radius and point ..I can draw a circle ..The code doesn't work because I only can get one position of the cursor no matter how many times I click the mouse
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
int lastX = Cursor.Position.X;//the first click x cursor position
int lastY = Cursor.Position.Y;//the first click y cursor position,
//is there any way to reuse the Cursor.Position for different point ??
int x = Cursor.Position.X;//the second click x cursor position
int y = Cursor.Position.Y;//the second click y cursor position
Graphics g;
double oradius=Math.Sqrt(((lastX-x)^2) +((lastY-y)^2));
//double newy = Math.Sqrt(lastY);
// int newxv = Convert.ToInt32(newx);
int radius= Convert.ToInt32(oradius);
g = this.CreateGraphics();
Rectangle rectangle = new Rectangle();
PaintEventArgs arg = new PaintEventArgs(g, rectangle);
DrawCircle(arg, x, y,radius,radius);
}
private void DrawCircle(PaintEventArgs e, int x, int y, int width, int height)
{
System.Drawing.Pen pen = new System.Drawing.Pen(System.Drawing.Color.Red, 3);
e.Graphics.DrawEllipse(pen, x - width / 2, y - height / 2, width, height);
}
}
You need to store the first click as well before you start doing the calculations. One way to do this is to create a class that simply throws an event every second time you pass it x and y coordinates like this:
public class CircleDrawer
{
private int _firstX;
private int _firstY;
private int _secondX;
private int _secondY;
private bool _isSecondClick;
private event EventHandler OnSecondClick;
public void RegisterClick(int x, int y)
{
if(_isSecondClick)
{
_secondX = x;
_secondY = y;
if(OnSecondClick != null)
OnSecondClick(this, null);
}
else
{
_firstX = x;
_firstY = y;
_isSecondClick = true;
}
}
}
You can then in your code simply call your methods:
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
int lastX = Cursor.Position.X;//the first click x cursor position
int lastY = Cursor.Position.Y;//the first click y cursor position,
_circleDrawer.RegisterClick(lastX, lastY);
}
And in your constuctor:
public MyForm()
{
_circleDrawer = new CircleDrawer();
_circleDrawer.OnSecondClick += DrawCircle();
}
public void DrawCircle()
{
// Your drawing code
}
Your lastX and lastY are local variables, and you initialize them in the beginning of the MouseDown event handler. They should be class level variables and should be populated at the end of the MouseDown event handler.
Also, you should test if they already have a value, and only if they have value then draw the circle and then clear them (so that the next circle will have it's own center).
Here is an improvement of your code. Note I've used the using keyword with the graphics object and with the pen - get used to use it every time you are using an instance of anything that's implementing the IDisposable interface.
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (_lastPosition != Point.Empty)
{
var currentPosition = Cursor.Position;
var oradius = Math.Sqrt(((_lastPosition.X - currentPosition.X) ^ 2) + ((_lastPosition.Y - currentPosition.Y) ^ 2));
var radius = Convert.ToInt32(oradius);
using (var g = this.CreateGraphics())
{
var arg = new PaintEventArgs(g, new Rectangle());
DrawCircle(arg, currentPosition, radius, radius);
}
_lastPosition = Point.Empty;
}
else
{
_lastPosition = Cursor.Position;
}
}
private void DrawCircle(PaintEventArgs e, Point position, int width, int height)
{
using (var pen = new System.Drawing.Pen(System.Drawing.Color.Red, 3))
{
e.Graphics.DrawEllipse(pen, position.X - width / 2, position.Y - height / 2, width, height);
}
}
Note: This code can be improved even further.
There are many things fundamentally wrong with this code, here is a complete, working example.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private Point clickCurrent = Point.Empty;
private Point clickPrev = Point.Empty;
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
clickPrev = clickCurrent;
clickCurrent = this.PointToClient(Cursor.Position);
if (clickPrev == Point.Empty) return;
Graphics g;
double oradius = Math.Sqrt((Math.Pow(clickPrev.X - clickCurrent.X, 2)) + (Math.Pow(clickPrev.Y - clickCurrent.Y, 2)));
int radius = Convert.ToInt32(oradius);
g = this.CreateGraphics();
Rectangle rectangle = new Rectangle();
PaintEventArgs arg = new PaintEventArgs(g, rectangle);
DrawCircle(arg, clickPrev.X, clickPrev.Y, radius * 2, radius * 2);
clickCurrent = Point.Empty;
}
private void DrawCircle(PaintEventArgs e, int x, int y, int width, int height)
{
System.Drawing.Pen pen = new System.Drawing.Pen(System.Drawing.Color.Red, 3);
e.Graphics.DrawEllipse(pen, x - width / 2, y - height / 2, width, height);
}
}
private int _firstX;
private int _firstY;
private int _secondX;
private int _secondY;
private bool _isSecondClick;
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (_isSecondClick)
{
_secondX = Cursor.Position.X;
_secondY = Cursor.Position.Y;
var radious1 = Math.Pow(_firstX - _secondX, 2);
var radious2 = Math.Pow(_firstY - _secondY, 2);
var radious = Math.Sqrt(radious1 + radious2);
Graphics g = this.CreateGraphics();
Rectangle rectangle = new Rectangle();
PaintEventArgs arg = new PaintEventArgs(g, rectangle);
DrawCircle(arg, _secondX, _secondY, radious, radious);
}
else
{
_firstX = Cursor.Position.X;
_firstY = Cursor.Position.Y;
_isSecondClick = true;
}
}
private void DrawCircle(PaintEventArgs arg, int x, int y, double width, double height)
{
System.Drawing.Pen pen = new System.Drawing.Pen(System.Drawing.Color.Red, 3);
var xL = Convert.ToInt32(x - width / 2);
var yL = Convert.ToInt32(y - height / 2);
var hL = Convert.ToInt32(height);
var wL = Convert.ToInt32(width);
arg.Graphics.DrawEllipse(pen, xL, yL, wL, hL);
}

Create a custom control with the form of a pie without tip?

I want to create a own Control with the form of a pie without the tip of it like in the picture afterwards. I just dont get how to get this working.
http://www.directupload.net/file/d/3563/a3hvpodw_png.htm
//EDIT:
Ok I forgot to mention that i want to fill it afterwards. So if I'm right I need a region for that but i don't know how to do this. And to be honest i didn't think about your idea so far. I just used a Pie so far, like this:
Graphics gfx = pe.Graphics;
Pen p = new Pen(Color.Red);
gfx.DrawPie(p, 0, 0, 200, 200, 0, 45);
base.OnPaint(pe);
It's my first time with custom controls, so sorry if it is a little bit goofy what I'm asking.
Try this:
class ShapedControl : Control
{
private float startAngle;
private float sweepAngle;
private float innerRadius;
private float outerRadius;
public ShapedControl()
{
InnerRadius = 30;
OuterRadius = 60;
StartAngle = 0;
SweepAngle = 360;
}
[DefaultValue(0)]
[Description("The starting angle for the pie section, measured in degrees clockwise from the X-axis.")]
public float StartAngle
{
get { return startAngle; }
set
{
startAngle = value;
Invalidate();
}
}
[DefaultValue(360)]
[Description("The angle between StartAngle and the end of the pie section, measured in degrees clockwise from the X-axis.")]
public float SweepAngle
{
get { return sweepAngle; }
set
{
sweepAngle = value;
Invalidate();
}
}
[DefaultValue(20)]
[Description("Inner radius of the excluded inner area of the pie")]
public float InnerRadius
{
get { return innerRadius; }
set
{
innerRadius = value;
Invalidate();
}
}
[DefaultValue(30)]
[Description("Outer radius of the pie")]
public float OuterRadius
{
get { return outerRadius; }
set
{
outerRadius = value;
Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
g.Clear(this.BackColor);
GraphicsPath gp1 = new GraphicsPath();
GraphicsPath gp2 = new GraphicsPath();
float xInnerPos = -innerRadius / 2f + this.Width / 2f;
float yInnerPos = -innerRadius / 2f + this.Height / 2f;
float xOuterPos = -outerRadius / 2f + this.Width / 2f;
float yOuterPos = -outerRadius / 2f + this.Height / 2f;
if (innerRadius != 0.0)
gp1.AddPie(xInnerPos, yInnerPos, innerRadius, innerRadius, startAngle, sweepAngle);
gp2.AddPie(xOuterPos, yOuterPos, outerRadius, outerRadius, startAngle, sweepAngle);
Region rg1 = new System.Drawing.Region(gp1);
Region rg2 = new System.Drawing.Region(gp2);
g.DrawPath(Pens.Transparent, gp1);
g.DrawPath(Pens.Transparent, gp2);
rg1.Xor(rg2);
g.FillRegion(Brushes.Black, rg1);
this.Region = rg1;
}
//Just for testing purpose. Place a breakpoint
//in here and you'll see it will only get called when
//you click inside the "pie" shape
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
}
}
EDIT: made the code better by centering the shape and adding properties for the VS Designer , stolen from another answer ;-)
MORE EDITS: taking care of the case where Inner Radius == 0
Try this code sample of a complex shaped control.
You can control its shape using StartAngle, SweepAngle and InnerPercent properties.
public partial class PathUserControl : UserControl
{
private readonly GraphicsPath outerPath = new GraphicsPath();
private readonly GraphicsPath innerPath = new GraphicsPath();
private float startAngle;
private float sweepAngle = 60;
private float innerPercent = 30;
public PathUserControl()
{
base.BackColor = SystemColors.ControlDark;
}
[DefaultValue(0)]
[Description("The starting angle for the pie section, measured in degrees clockwise from the X-axis.")]
public float StartAngle
{
get { return startAngle; }
set
{
startAngle = value;
SetRegion();
}
}
[DefaultValue(60)]
[Description("The angle between StartAngle and the end of the pie section, measured in degrees clockwise from the X-axis.")]
public float SweepAngle
{
get { return sweepAngle; }
set
{
sweepAngle = value;
SetRegion();
}
}
[DefaultValue(30)]
[Description("Percent of the radius of the excluded inner area of the pie, measured from 0 to 100.")]
public float InnerPercent
{
get { return innerPercent; }
set
{
if (value < 0 || value > 100f)
throw new ArgumentOutOfRangeException("value", "Percent must be in the range 0 .. 100");
innerPercent = value;
SetRegion();
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
SetRegion();
}
private void SetRegion()
{
if (Region != null)
{
Region.Dispose();
Region = null;
}
if (ClientSize.IsEmpty)
return;
float innerCoef = 0.01f * InnerPercent;
outerPath.Reset();
innerPath.Reset();
outerPath.AddPie(0, 0, ClientSize.Width, ClientSize.Height, StartAngle, SweepAngle);
innerPath.AddPie(ClientSize.Width * (1 - innerCoef) / 2, ClientSize.Height * (1 - innerCoef) / 2, ClientSize.Width * innerCoef, ClientSize.Height * innerCoef, StartAngle, SweepAngle);
Region region = new Region(outerPath);
region.Xor(innerPath);
Region = region;
}
}
EDIT The #LucMorin idea with XOR is great, I've stolen it.

Uniform circular motion

I need to implement a simple animation of a ball moving in uniform circular motion. I've tried several formulas and the following version seems the best so far.However, there are still 2 issues and I really can't figure out what's wrong.
First, a couple of seconds right after the program starts, the ball moves erratically. I think that the values for theta (the angle in radians) are not computed correctly, but I don't know why.
Secondly, the movement becomes more uniform after a while, but it seems to decrease over time.
The value for 'speed' indicates the number of seconds it takes to do a full revolution.
What I want is an uniform, correct circular movement (according to the value of speed) and without the jerkiness in the beginning.
My code so far:
public partial class ServerForm : Form
{
Stopwatch watch;
//Angular velocity
float angularVelocity;
//Angle
float theta = 20;
//Speed - time to complete a full revolution, in seconds
private float speed = 3;
//Circle center
private int centerX = 250;
private int centerY = 200;
//Circle radius
private float R = 120;
//Current position
private LocationData currentLocation;
public ServerForm()
{
InitializeComponent();
}
public void UpdateUI()
{
currentLocation.CoordX = (float)(centerX + Math.Cos(theta) * R);
currentLocation.CoordY = (float)(centerY + Math.Sin(theta) * R);
currentLocation.Speed = speed;
try
{
this.Invoke(new Action(() => { this.Invalidate(); }));
}
catch (Exception ex)
{
watch.Stop();
Application.Exit();
}
theta += (float)((angularVelocity * 1000 / watch.ElapsedMilliseconds));
//Console.Out.WriteLine("elapsed miliseconds: " + watch.ElapsedMilliseconds + " theta = " + theta);
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Brush color = new SolidBrush(Color.BlueViolet);
g.FillEllipse(color, currentLocation.CoordX, currentLocation.CoordY, 30, 30);
//Draw circle & center
g.DrawEllipse(new Pen(color), centerX, centerY, 5, 5);
float x = centerX - R;
float y = centerY - R;
float width = 2 * R;
float height = 2 * R;
g.DrawEllipse(new Pen(color), x, y, width, height);
base.OnPaint(e);
}
private void button1_Click(object sender, EventArgs e)
{
if (!String.IsNullOrEmpty(textSpeed.Text))
{
ResetValues(float.Parse(textSpeed.Text));
}
}
private void ResetValues(float newSpeed)
{
speed = newSpeed;
angularVelocity = (float)(2 * Math.PI / speed); // radians / sec
//Start at the top
currentLocation.CoordX = centerX;
currentLocation.CoordY = centerY - R;
theta = 90;
watch.Restart();
}
private void ServerForm_Load(object sender, EventArgs e)
{
watch = new Stopwatch();
timer1.Enabled = true;
timer1.Interval = 100;
timer1.Tick += timer1_Tick;
currentLocation = new LocationData();
ResetValues(speed);
}
void timer1_Tick(object sender, EventArgs e)
{
UpdateUI();
}
}
LocationData is just a class holding the coordinates & current speed.
Are the units for time & angular velocity (and the transformations to use miliseconds) correct?
I changed BackgroundWorker to Timer, but I still get that erratic motion and the movement slows down after a while.
Try using a System.Windows.Forms.Timer instead of a BackgroundWorker. I believe you'll get more consistent results. This is definitely not a good case for using a BackgroundWorker.
Here's a more-or-less complete solution. Note that I'm scaling the swing radius and the ball radius by the size of the Form.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.Opaque, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.DoubleBuffer, true);
SetStyle(ControlStyles.UserPaint, true);
}
private void Form1_Load(object sender, System.EventArgs e)
{
_stopwatch.Start();
}
private void Timer1_Tick(object sender, System.EventArgs e)
{
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.Clear(BackColor);
const float rotationTime = 2000f;
var elapsedTime = (float) _stopwatch.ElapsedMilliseconds;
var swingRadius = Math.Min(ClientSize.Width, ClientSize.Height) / 4f;
var theta = Math.PI * 2f * elapsedTime / rotationTime;
var ballRadius = Math.Min(ClientSize.Width, ClientSize.Height) / 10f;
var ballCenterX = (float) ((ClientSize.Width / 2f) + (swingRadius * Math.Cos(theta)));
var ballCenterY = (float) ((ClientSize.Height / 2f) + (swingRadius * Math.Sin(theta)));
var ballLeft = ballCenterX - ballRadius;
var ballTop = ballCenterY - ballRadius;
var ballWidth = ballRadius * 2f;
var ballHeight = ballRadius * 2f;
e.Graphics.FillEllipse(Brushes.Red, ballLeft, ballTop, ballWidth, ballHeight);
e.Graphics.DrawEllipse(Pens.Black, ballLeft, ballTop, ballWidth, ballHeight);
}
private readonly Stopwatch _stopwatch = new Stopwatch();
}

Categories