draw step indicator as custom controll - c#

I'm trying to create step indicator control that will show on which step user currently is.
I found on dribble some concept that I would like to create:
using very simple code I was able to create result like this:
below is my code:
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace StepIndicator
{
public class StepIndicatorOne : Control
{
public StepIndicatorOne()
{
MinimumSize = new Size(300, 50);
SetStyle(ControlStyles.DoubleBuffer, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
int steps = 3;
int radiusBig = 20;
int radiusSmall = 15;
int bgHeight = 10;
var gradientRect = new Rectangle(ClientRectangle.X + (ClientRectangle.Width - radiusBig*2)/(steps - 1),
ClientRectangle.Y + ClientRectangle.Height/2 - radiusBig - 1, radiusBig*2, radiusBig*2);
var lightGrayBrush = new LinearGradientBrush(ClientRectangle, Color.FromArgb(224, 227, 214), Color.LightGray, LinearGradientMode.Vertical);
var darkGrayBrush = new LinearGradientBrush(gradientRect, Color.DarkGray, Color.Gray, LinearGradientMode.Vertical);
var lightGreenBrush = new LinearGradientBrush(ClientRectangle, Color.FromArgb(206, 217, 79), Color.FromArgb(191, 201, 82), LinearGradientMode.Vertical);
var darkGreenBrush = new LinearGradientBrush(ClientRectangle, Color.YellowGreen, Color.ForestGreen, LinearGradientMode.Vertical);
g.FillRectangle(darkGrayBrush, ClientRectangle.X + radiusBig, ClientRectangle.Y + ClientRectangle.Height/2 - bgHeight/2 - 1,
ClientRectangle.Width - radiusBig*2, bgHeight);
g.FillRectangle(lightGrayBrush, ClientRectangle.X + radiusBig, ClientRectangle.Y + ClientRectangle.Height/2 - bgHeight/2,
ClientRectangle.Width - radiusBig*2, bgHeight);
for (int i = 1; i <= steps; i++)
{
g.FillEllipse(darkGrayBrush, ClientRectangle.X + ((ClientRectangle.Width - radiusBig*2)/(steps - 1))*(i - 1),
ClientRectangle.Y + ClientRectangle.Height/2 - radiusBig - 1, radiusBig*2, radiusBig*2);
g.FillEllipse(lightGrayBrush, ClientRectangle.X + ((ClientRectangle.Width - radiusBig*2)/(steps - 1))*(i - 1),
ClientRectangle.Y + ClientRectangle.Height/2 - radiusBig, radiusBig*2, radiusBig*2);
}
for (int i = 1; i <= steps - 1; i++)
{
g.FillEllipse(darkGreenBrush,
ClientRectangle.X + ((ClientRectangle.Width - radiusBig*2)/(steps - 1))*(i - 1) + radiusBig - radiusSmall,
ClientRectangle.Y + ClientRectangle.Height/2 - radiusSmall - 1, radiusSmall*2, radiusSmall*2);
g.FillEllipse(lightGreenBrush,
ClientRectangle.X + ((ClientRectangle.Width - radiusBig*2)/(steps - 1))*(i - 1) + radiusBig - radiusSmall,
ClientRectangle.Y + ClientRectangle.Height/2 - radiusSmall, radiusSmall*2, radiusSmall*2);
}
}
}
}
My questions are:
Is it possible to create green shape as on design (green bar with circles and hole in one) but with border over it?
I can draw rectangle with border and ellipse above it with border, but how can I combine those two shapes and have border around it (combine those two shapes into one) - is my only option creating complex path?
I know that winforms gdi+ can't use inner shadow, but are there some other options to create nice looking effect simillar to inner glow?
Right now I draw same shape with -1 offset and darker color, but effect isn't what I would like it to look.
I know that propably the best solution would be to switch to WPF and draw it there, I even found sample control on SO - Implementing a wizard progress control in WPF but I must stay in Winforms

Use a GraphicsPath for the complex path. Use a path gradient for the inner shadow.

Related

Visual C# bezier drawing problems

I having an issue in a drawing application what I developing, in what bezier curves don't work as they should.
I have this code line:
g.DrawBezier(pen, points[i - 1], points[i], points[i], points[i + 1]);
It does his job nice, but for some reason it also paints an extra line what is very unhelpful...
As you can see, for some reason, an awful line appears on top of the spline.
Can someone please help me?
for (int i = 0; i < points.Count - 1; i++) {
pen.Color = colors[i];
pen.Width = widths[i];
if (visible[i] == true) {
g.DrawBezier(pen, points[i - 1], points[i], points[i], points[i + 1]);
if (spoints == true) {
g.DrawEllipse(new Pen(Color.LimeGreen, 5), points[i].X - 1, points[i].Y - 1, 2, 2);
if (spositions == true) {
g.DrawString(points[i].X.ToString() + ", " + points[i].Y.ToString(), new Font("Courier", 8), pen.Brush, points[i]);
}
}
}
if (sskeleton == true)
{
g.DrawLine(new Pen(Color.Magenta, 1), points[i].X, points[i].Y, points[i + 1].X, points[i + 1].Y);
}
}
I think here's what's happening.
Say the points are
List<Point> points = new List<Point>() {
new Point(50, 50),
new Point(150, 150),
new Point(150, 250),
new Point(300, 300),
new Point(400, 300)};
The first bezier curve is drawn
The green dots are the actual points. Notice that the curve doesn't actually go through the second point.
The next curve is drawn.
This curve starts at the second point (not where the last curve went through).
Final plot:
The middle two points in the DrawBezier function are control points, so the curve isn't guaranteed to go through those two points. https://msdn.microsoft.com/en-us/library/a7h66bsy(v=vs.110).aspx
Perhaps what you're trying to accomplish could be done with DrawBeziers. https://msdn.microsoft.com/en-us/library/ds101091(v=vs.110).aspx
Another option would be to start the next bezier curve at the end point of the last one (instead of at the control point of the last one).
If you want a curve that goes through all of the points, try DrawCurve(pen, points).

How to draw a text in a shape? C#

The question is simple, assuming you have a shape (lets say a rectangle) and a text ("hello" for example), so it'll write the text all accross the borders of the rectangle for as many times as it fits, for example:
hello hello hello hello
hello hello
hello hello
hello hello hello hello
In order to do it I assume you'd need to use a graphics variable, I just dont know how to do it.
A code for drawing a string in a bitmap object:
Bitmap tempp = new Bitmap(1, 1);
Graphics g = Graphics.FromImage(tempp);
SizeF w = g.MeasureString("22", new Font("Tahoma", 200));//in order to get the size of the string as a pixel measurement
Bitmap bmp = new Bitmap((int)w.Width+1, (int)w.Height+1);//the bitmap that will contain the text as a picture
RectangleF rectf = new RectangleF(0, 0, (int)w.Width+1, (int)w.Height+1);
g = Graphics.FromImage(bmp);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
StringFormat format = new StringFormat()
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};
g.DrawString("22", new Font("Tahoma", 200), Brushes.Black, rectf, format);
g.Flush();
Thanks in advance.
For second comment:
hello hello hello
hel llo
hel llo
hello hello hello
I hope this is what you need. There isn't much to explain here. The logic is pretty straightforward.
public string MyString = "Hello"
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var g = e.Graphics;
var strFont = new Font("Tahoma", 10);
var strSizeF = g.MeasureString(MyString, strFont);
var canvas = new Rectangle
{
Height = 200,
Width = 200,
Location = new Point(10, 10),
};
g.DrawRectangle(new Pen(new SolidBrush(Color.Blue)), canvas);
var nx = (int)(canvas.Width / strSizeF.Width);
var ny = (int)(canvas.Height / strSizeF.Height);
var spacingX = (canvas.Width - nx * strSizeF.Width) / (nx-1);
var spacingY = (canvas.Height - ny * strSizeF.Height) / (ny-1);
//draw top row and bottom row
int i;
for (i = 0; i < nx; i++)
{
g.DrawString(
MyString,
strFont,
Brushes.Black,
new PointF(canvas.X + i*(strSizeF.Width + spacingX), canvas.Y)
);
g.DrawString(
MyString,
strFont,
Brushes.Black,
new PointF(canvas.X + i * (strSizeF.Width + spacingX), canvas.Y + canvas.Height - strSizeF.Height)
);
}
//divide the string into half
var isLengthOdd = MyString.Length % 2 != 0;
var substr1 = MyString.Substring(0, MyString.Length / 2 + (isLengthOdd ? 1 : 0));
var substr2 = MyString.Substring(MyString.Length / 2, MyString.Length - MyString.Length / 2);
var substr2SizeF = g.MeasureString(substr2, strFont);
//draw side rows
for (i = 1; i < ny - 1; i++)
{
g.DrawString(
substr1,
strFont,
Brushes.Black,
new PointF(canvas.X, canvas.Y + i * (strSizeF.Height + spacingY))
);
g.DrawString(
substr2,
strFont,
Brushes.Black,
new PointF(canvas.X + canvas.Width - substr2SizeF.Width, canvas.Y + i * (strSizeF.Height + spacingY))
);
}
}
Result:

Winforms graphics function

I'm working on a winforms project where I need to draw tiled images. So far this is what I have:
Update method (hooked to a timer.tick event) :
int elapsedTime = Environment.TickCount - lastTick;
tempLevel.Update(elapsedTime);
Invalidate();
lbl_fps.Text = "FPS: " + FPSHandler.CalculateFrameRate();
lastTick = Environment.TickCount;
Paint method:
public void Paint(PaintEventArgs e)
{
GraphicManager.drawTiledImage(e.Graphics, new Rectangle(0,0,level.Length,1024), "paper");
for (int i = 1; i < 800; i ++)
{
GraphicManager.drawLine(e.Graphics,
new PointF(i + GraphicManager.winpos.x - 1, level[(int)(i + GraphicManager.winpos.x - 1)] + 400),
new PointF(i + GraphicManager.winpos.x, level[(int)(i + GraphicManager.winpos.x)] + 400));
}
}
drawTiledImage method:
ImageAttributes imgA = new ImageAttributes();
imgA.SetWrapMode(WrapMode.Tile);
g.DrawImage(images[image],
ExtraMath.fromRect(
new Rectangle(
(int)Math.Round(rect.X - winPos.x),
(int)Math.Round(rect.Y - winPos.y),
rect.Width, rect.Height)),
new Rectangle(0, 0, rect.Width, rect.Height),
GraphicsUnit.Pixel, imgA);
winpos is a vector, a custom class similar to PointF, except with more features, and uses floating point numbers to store x/y, and is convertible to floats, Points, and PointFs.
When I run this code, it appears to work until I begin to scroll down, and the screen looks like this:
it should look like this (minus the line):
Any help?
Extra info:
- the winPos is basically a wrapped version of PointF that has more operators and such
- The glitch appears very aggressively whenever winpos.y approaches the height of the image
- The glitch does not happen when scrolling to the left/right

TabRenderer with no visual styles enabled?

I want to draw a custom TabControl with custom functionality.
To do this, i inherited the Panel class and overrided OnPaint method to draw with TabRenderer class.
The problem is that TabRenderer working only when visual styles enabled (can be checked with TabRenderer.IsSupported), but what should i do if visual styles disabled?
In this case, I thought using the ControlPaint class to draw tabs without visual styles, but it has no draw methods related to Tabs. I want it basically to behave visually like the regular TabControl.
You have to draw it by yourself, because there is not published API for this. Hopefully this is relatively easy to do it in non-visualstyles way.
You can draw pane border with ControlPaint.DrawBorder3D and use something like the following code for buttons:
int Top = bounds.Top;
int Bottom = bounds.Bottom - 1;
int Sign = 1;
if (tabStrip.EffectiveOrientation == TabOrientation.Bottom)
{
Top = bounds.Bottom - 1;
Bottom = bounds.Top;
Sign = -1;
}
using (Pen OuterLightBorderPen = new Pen(SystemColors.ControlLightLight))
{
e.Graphics.DrawLine(OuterLightBorderPen, bounds.Left, Bottom, bounds.Left, Top + 2 * Sign);
e.Graphics.DrawLine(OuterLightBorderPen, bounds.Left, Top + 2 * Sign, bounds.Left + 2, Top);
e.Graphics.DrawLine(OuterLightBorderPen, bounds.Left + 2, Top, bounds.Right - 3, Top);
}
using (Pen InnerLightBorderPen = new Pen(SystemColors.ControlLight))
{
e.Graphics.DrawLine(InnerLightBorderPen, bounds.Left + 1, Bottom, bounds.Left + 1, Top + 2 * Sign);
e.Graphics.DrawLine(InnerLightBorderPen, bounds.Left + 2, Top + 1 * Sign, bounds.Right - 3, Top + 1 * Sign);
}
using (Pen OuterDarkBorderPen = new Pen(SystemColors.ControlDarkDark))
{
e.Graphics.DrawLine(OuterDarkBorderPen, bounds.Right - 2, Top + 1 * Sign, bounds.Right - 1, Top + 2 * Sign);
e.Graphics.DrawLine(OuterDarkBorderPen, bounds.Right - 1, Top + 2 * Sign, bounds.Right - 1, Bottom);
}
using (Pen InnerDarkBorderPen = new Pen(SystemColors.ControlDark))
e.Graphics.DrawLine(InnerDarkBorderPen, bounds.Right - 2, Top + 2 * Sign, bounds.Right - 2, Bottom);
This is an "out there" answer but is it possible that you could use wpf? As you can see from the answer above it is a pain in the ear to customise controls in winforms where as in WPF each control is lookless. This means that you control what is rendered and how it looks completely.

What olive bullet on breakpoint means in SharpDevelop

Sometimes when I set a breakpoint and start debugging its color changes from red to olive:
When it happens debugging doesn't stop at all - breakpoint is ignored.
I want to know why this happens and how to avoid it in the future.
Edit:
It does not happens only when breakpoint is set on commented line of code:
It's open source. Downloading it and finding the relevant code took me about 5 minutes:
public void DrawBreakpoint(Graphics g, int y, bool isEnabled, bool isHealthy)
{
int diameter = Math.Min(iconBarWidth - 2, textArea.TextView.FontHeight);
Rectangle rect = new Rectangle(1,
y + (textArea.TextView.FontHeight -
diameter) / 2,
diameter,
diameter);
using (GraphicsPath path = new GraphicsPath()) {
path.AddEllipse(rect);
using (PathGradientBrush pthGrBrush = new PathGradientBrush(path)) {
pthGrBrush.CenterPoint = new PointF(rect.Left + rect.Width / 3 ,
rect.Top + rect.Height / 3);
pthGrBrush.CenterColor = Color.MistyRose;
Color[] colors = {isHealthy ? Color.Firebrick : Color.Olive};
pthGrBrush.SurroundColors = colors;
if (isEnabled) {
g.FillEllipse(pthGrBrush, rect);
} else {
g.FillEllipse(SystemBrushes.Control, rect);
using (Pen pen = new Pen(pthGrBrush)) {
g.DrawEllipse(pen, new Rectangle(rect.X + 1, rect.Y + 1,
rect.Width - 2,
rect.Height - 2));
}
}
}
}
}
That circle color is "Color.Olive". If you want to know why isHealthy is false, use the source. There's a couple of reasons I could find quickly: the source file has changed or the module isn't loaded, There may be more.
This occurs if you compile the programm as "release".
Your line is commented where the breakpoint is set.

Categories