What's an efficient way to display text of a progress bar? - c#

I'm working on a GUI for a CNC machine. We have load cells that output a voltage depending on how much force is applied to the cell, which the machine can read and then display to the operator so they know how much force they are clamping a part with.
Microsoft's website says .NET Framework 4.6.1 (which I'm building with) progress bars have a text property, but setting the text itself doesn't display it. I found a different way of doing it like this:
int loadVal = 0;
string progBarText = "";
SizeF textSize;
Graphics graphics = CreateGraphics();
Font font = new Font("Lucida Console", FontHeight = 11, FontStyle.Regular);
leftClampProgBar.SuspendLayout();
rightClampProgBar.SuspendLayout();
//~~~~Left Clamp~~~~~~
loadVal = (PLC_ushLeftClampLoad * 500) / 65535;
leftClampProgBar.Value = (loadVal * 100) / 500;
//setting the text for the progress bar
progBarText = loadVal.ToString() + " Lb(s)";
//have to figure out how big the text is
textSize = graphics.MeasureString(progBarText, font);
//drawing the text to the progress bar
leftClampProgBar.CreateGraphics().DrawString(
progBarText,
font,
Brushes.Black,
new PointF((leftClampProgBar.Width - textSize.Width) / 2,
(leftClampProgBar.Height - textSize.Height) / 2));;
//~~~~~Right Clamp~~~~~~
loadVal = (PLC_ushRightClampLoad * 500) / 65535;
rightClampProgBar.Value = (loadVal * 100) / 500;
//setting the text for the progress bar
progBarText = loadVal.ToString() + " Lb(s)";
//have to figure out how big the text is
textSize = graphics.MeasureString(progBarText, font);
//drawing the text to the progress bar
rightClampProgBar.CreateGraphics().DrawString(
progBarText,
font,
Brushes.Black,
new PointF((rightClampProgBar.Width - textSize.Width) / 2,
(rightClampProgBar.Height - textSize.Height) / 2));
//AddNotification("Right Clamp: " + loadVal, Color.Purple);
leftClampProgBar.ResumeLayout();
rightClampProgBar.ResumeLayout();
However, this leads to the the text sometimes being printed wrong, or it is not refreshing correctly. The method the code above is in gets called by a timer every 500ms and causes the GUI to act a little slower than before. I could make a different timer for this specifically that has a larger interval, but I wanted to know if there was a more efficient way to display the text at all, not worrying about how often the timer repaints it.
progress bar text

You could create custom progress bar to do this. The following class assumes ProgressBarRenderer.IsSupported results in true and does not animate progress bar.
public class TextProgressBar : ProgressBar
{
public TextProgressBar() : base()
{
SetStyle(ControlStyles.UserPaint, true);
DoubleBuffered = true; // remove flicker
}
// unhide Text/Font Properties and force changes to re-render control
[Browsable(true)]
[EditorBrowsable(EditorBrowsableState.Always)]
public override string Text
{
get => base.Text;
set
{
base.Text = value;
Refresh();
}
}
[Browsable(true)]
[EditorBrowsable(EditorBrowsableState.Always)]
public override Font Font
{
get => base.Font;
set
{
base.Font = value;
if (!string.IsNullOrWhiteSpace(Text)) Refresh();
}
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
base.OnPaintBackground(pevent);
// draw progress bar background
ProgressBarRenderer.DrawHorizontalBar(pevent.Graphics, new Rectangle(0, 0, Width, Height));
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// draw progress on progress bar
double percentage = ((double)(Value - Minimum)) / ((double)(Maximum - Minimum));
ProgressBarRenderer.DrawHorizontalChunks(e.Graphics, new Rectangle(0, 0, (int)(Width * percentage), Height));
// draw text on progress bar
using (Brush brush = new SolidBrush(ForeColor))
{
// get rendered size of text
var size = e.Graphics.MeasureString(Text, Font, new SizeF(Width, Height));
// calculate location to center text on progress bar
var location = new PointF((Width - size.Width) * 0.5f, (Height - size.Height) * 0.5f);
// draw text
e.Graphics.DrawString(Text, Font, brush, new RectangleF(location, size));
}
}
}

I would recommend just having a separate label above or to the side of the progress-bar that you update. This should be far simpler, and probably also being easier to read.
If you insist on printing the text on top of the progress-bar you should consider creating your own class that derives from progress-bar and override the onPaint method. But if you use this approach you need to consider contrast and legibility as the text and progress overlaps.

Related

How to select / copy text from "Graphics" string element in WinForms

I am currently working on a chat window and have created tiles for messages.
I have a problem because the text in tiles draws using the DrawString method and I`m not really sure if there is any such possibility that this text can be copied? How can it enable the user to select and copy text? Below is part of the source code.
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var graphics = e.Graphics;
...
using (SolidBrush brush = new SolidBrush(this.ForeColor))
{
if (_BoxPosition == BoxPositionEnum.Left)
{
graphics.DrawString(Text, Font, brush, new Rectangle(20 , Height / 2, renderWidth - 5, Height / 2 - 5));
}
else
{
StringFormat format = new StringFormat()
{
LineAlignment = StringAlignment.Center,
Alignment = StringAlignment.Far
};
graphics.DrawString(Text, Font, brush,
new Rectangle(_mc.BoxIndent - 10 , Height / 2, renderWidth - 5, Height / 2 - 5),format);
}
}
For simple: just use TextBox or RichTextBox and set ReadOnly = true and color as what you want.
For more advanced: create your own message box user control, and you can do many thing with it (react, select all, ...).
Here the tutorial for create your own user control: https://www.c-sharpcorner.com/UploadFile/7d3362/user-control-in-C-Sharp/

How to use windows forms report with radial gauge

I tried to find information on how to use a radial gauge in a windows form report.
I really can't find anything on this. Not sure if there is not much info on this.
Is there anyone who can get me some info on this? How would I be able to use a value from a text box in a report viewer to show this on a radial gauge and even using a track bar to get some idea how to use it.
Even if getting a small example to build on this would be really great :-)
You have several options even without any external stuff.
You can draw a gauge needle onto a gauge image. Here is an example.
You can draw the needle onto an image by either calulating the outer point and drawing a line to the center or by rotating the canvas as in the link.
Or you can use the built-in MSChart control and its Doughnut charttype.
Here is an example for this:
The code is simple:
first we set up the chart by adding three DataPoints; then we code a function to update the value.
The points are for
the open, transparent part
the value of the gauge in green
the rest of the scale in red
For testing I use these variables:
double valMin = 0; // user data minimum
double valMax = 100; // ~ maximum
float angle = 60; // open pie angle at the bottom
string valFmt = "{0}°"; // a format string
My current value is pulled from a trackbar.
Setup code:
void setupChartGauge(double val, double vMin, double vMax, float a)
{
valMin = vMin;
valMax = vMax;
angle = a;
Series s = gaugeChart.Series[0];
s.ChartType = SeriesChartType.Doughnut;
s.SetCustomProperty("PieStartAngle", (90 - angle/2) + "");
s.SetCustomProperty("DoughnutRadius", "10");
s.Points.Clear();
s.Points.AddY(angle);
s.Points.AddY(0);
s.Points.AddY(0);
setChartGauge(0);
s.Points[0].Color = Color.Transparent;
s.Points[1].Color = Color.Chartreuse;
s.Points[2].Color = Color.Tomato;
}
and setting a value:
void setChartGauge(double val)
{
Series s = gaugeChart.Series[0];
double range = valMax - valMin;
double aRange = 360 - angle;
double f = aRange / range;
double v1 = val * f;
double v2 = (range - val) * f;
s.Points[1].YValues[0] = v1;
s.Points[2].YValues[0] = v2;
gaugeChart.Titles[0].Text = String.Format(valFmt, val);
gaugeChart.Refresh();
}
I have added minimal styling:
The Chart has a Title docked centered bottom which I also update
I have set a back color
I paint an inner circle in the Paint event like so:
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle r = chart1.ClientRectangle;
r.Inflate(-10, -10);
using (SolidBrush brush = new SolidBrush(Color.FromArgb(55, Color.Beige)))
e.Graphics.FillEllipse(brush, r);
Note that Pie and Doughnut charts can have only one series. To show a 2nd one you would need an overlapping 2nd chartarea with the exact same Position.
There are infinite ways to draw stuff, both from scratch or updating the MsChart control. Various gradient brushes come to mind. Adding ticks and a needle will involve rotation code, which basically consists of 3 lines of code..
Update:
Here is an example of drawing a gauge needle.
The code should be called from a Paint event and should pass out a valid Graphics object (read: e.Graphics), a float for the data value, a Rectangle to place the gauge in, a Color and a float for the percentage of the rectangle size to use.
private void drawNeedle(Graphics g, float val, Rectangle r, Color c, float length)
{
Point pc = new Point(r.X + r.Width / 2, r.Y + r.Height / 2);
Point p2 = new Point((int)( pc.X + r.Width / 2 * length / 100f), pc.Y);
using (Pen pen = new Pen(c, 3f)
{ StartCap = LineCap.RoundAnchor, EndCap = LineCap.ArrowAnchor })
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TranslateTransform(pc.X, pc.Y);
g.RotateTransform(val - (270 - angle / 2));
g.TranslateTransform(-pc.X, -pc.Y);
g.DrawLine(pen, pc, p2);
g.ResetTransform();
}
}
You can use it in any control that support owner-drawing including the chart..:
drawNeedle(e.Graphics, (float)gaugeChart.Series[0].Points[1].YValues[0], r, Color.White, 70f);
Here is a simple example with a TrackBar:
private Syncfusion.Windows.Forms.Gauge.RadialGauge radialGauge1;
private System.Windows.Forms.TrackBar trackBar1;
private Syncfusion.Windows.Forms.Gauge.Needle needle1;
private void InitializeComponent()
{
this.needle1 = new Syncfusion.Windows.Forms.Gauge.Needle();
this.needle1.Value = 0F;
this.trackBar1 = new System.Windows.Forms.TrackBar();
this.radialGauge1 = new Syncfusion.Windows.Forms.Gauge.RadialGauge();
this.trackBar1.Value = (int) needle1.Value;
this.radialGauge1.EnableCustomNeedles = true;
this.radialGauge1.NeedleCollection.Add(needle1);
this.radialGauge1.Size = new System.Drawing.Size(230, 230);
this.radialGauge1.TabIndex = 0;
this.trackBar1.Scroll += new System.EventHandler(this.trackBar1_Scroll);
}
And a scroll event which sync between gauge and trackBar:
private void trackBar1_Scroll(object sender, EventArgs e)
{
needle1.Value = trackBar1.Value;
}

UI glitch when writing own group box

in my program i have a group box, i didnt like that the groupbx provided in visual studio doesnt have a border colour property so i used this code to create my own group box.
public class MyGroupBox : GroupBox
{
private Color _borderColor = Color.Black;
public Color BorderColor
{
get { return this._borderColor; }
set { this._borderColor = value; }
}
protected override void OnPaint(PaintEventArgs e)
{
//get the text size in groupbox
Size tSize = TextRenderer.MeasureText(this.Text, this.Font);
Rectangle borderRect = e.ClipRectangle;
borderRect.Y = (borderRect.Y + (tSize.Height / 2));
borderRect.Height = (borderRect.Height - (tSize.Height / 2));
ControlPaint.DrawBorder(e.Graphics, borderRect, this._borderColor, ButtonBorderStyle.Solid);
Rectangle textRect = e.ClipRectangle;
textRect.X = (textRect.X + 6);
textRect.Width = tSize.Width;
textRect.Height = tSize.Height;
e.Graphics.FillRectangle(new SolidBrush(this.BackColor), textRect);
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(this.ForeColor), textRect);
}
}
which works "fine", i got myself a black border group box instead of grey, except when the window is moved the group box glitches out like so,
is there a fix for this or will i have to use the visual studio group box to prevent this issue? i am using C# winforms
The documentation for PaintEventArgs.ClipRectangle is misleading - Gets the rectangle in which to paint.. Actually this property represents the invalidated rectangle of the window, which is not always the full rectangle. It can be used to skip painting of elements that are outside that rectangle, but not as base for painting.
But the base rectangle for all painting should be the ClientRectangle property of the control being painted. So simply replace e.ClipRectangle with this.ClientRectangle.

Custom Control to use Cursor Hand

I've made a custom control in C# and anytime that the user's cursor is hovering over the custom control I want the cursor to be displayed as the 'Hand'. Where do i place the code to do such a thing?
????.Cursor = Cursors.Hand;
in order to make it so the Hand Cursor is being displayed when hovering over this custom control?
namespace CustomRangeBar
{
public partial class RangeBar : UserControl
{
public RangeBar()
{
InitializeComponent();
label1.ForeColor = Color.Black;
this.ForeColor = SystemColors.Highlight; // set the default color the rangeBar
this.Click += new EventHandler(RangeBar_Click);
}
protected float percent = 0.0f; // Protected because we don't want this to be accessed from the outside
// Create a Value property for the rangeBar
public float Value
{
get
{
return percent;
}
set
{
// Maintain the value between 0 and 100
if (value < 0) value = 0;
else if (value > 100) value = 100;
percent = value;
label1.Text = value.ToString();
//redraw the rangeBar every time the value changes
this.Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Brush b = new SolidBrush(this.ForeColor); //create brush that will draw the background of the range bar
// create a linear gradient that will be drawn over the background. FromArgb means you can use the Alpha value which is the transparency
LinearGradientBrush lb = new LinearGradientBrush(new Rectangle(0, 0, this.Width, this.Height), Color.FromArgb(255, Color.White), Color.FromArgb(50, Color.White), LinearGradientMode.Vertical);
// calculate how much has the rangeBar to be filled for 'x' %
int width = (int)((percent / 100) * this.Width);
e.Graphics.FillRectangle(b, 0, 0, width, this.Height);
e.Graphics.FillRectangle(lb, 0, 0, width, this.Height);
b.Dispose(); lb.Dispose();
}
private void RangeBar_SizeChanged(object sender, EventArgs e)
{
// maintain the label in the center of the rangeBar
label1.Location = new Point(this.Width / 2 - 21 / 2 - 4, this.Height / 2 - 15 / 2);
}
}
}
public void RangeBar_Click(object obj, EventArgs ea)
{
// This get executed if the pictureBox gets clicked
label1.text = "Increment 1";
}
UserControl derives from Control and therefore should already have a Cursor property inherited from that class. Do you not see a Cursor property in code/Properties?

Split text into 2 colors

I am making a custom progress bar in C#, and I want to show the percent on top of the bar. I need it so that when the bar reaches the text, it changes color. Take for example the image I made below:
Pretend that the orange rectangle on the left is the progress bar, and the black rectangle is blank space.
Is there anyway I can recreate this using GDI?
Thanks in advance,
Pat
You can do it by overriding paint on the control of your choice,
First draw the Black background and orange text
e.Graphics.FillRectangle(Brushes.Black, panel1.ClientRectangle);
e.Graphics.DrawString("StackOverflow", Font, Brushes.Orange, panel1.ClientRectangle);
Then Draw the overlay and clip to the size of the progress value
var clipRect = new Rectangle(0, 0, (panel1.Width / 100) * _progress, panel1.Height);
e.Graphics.SetClip(clipRect);
e.Graphics.FillRectangle(Brushes.Orange, clipRect);
e.Graphics.DrawString("StackOverflow", Font, Brushes.Black, 0, 0);
This is a working example using Panel as the control to override paint on (Just add a panel to a Form)
Example:
public partial class Form1 : Form
{
private Timer _progresstimer = new Timer();
private int _progress = 0;
public Form1()
{
InitializeComponent();
panel1.Paint += new PaintEventHandler(panel1_Paint);
_progresstimer.Interval = 250;
_progresstimer.Tick += (s, e) =>
{
if (_progress < 100)
{
_progress++;
panel1.Invalidate();
return;
}
_progress = 0;
panel1.Invalidate();
};
_progresstimer.Start();
}
void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.Black, panel1.ClientRectangle);
e.Graphics.DrawString("StackOverflow", Font, Brushes.Orange, panel1.ClientRectangle);
var clipRect = new Rectangle(0, 0, (panel1.Width / 100) * _progress, panel1.Height);
e.Graphics.SetClip(clipRect);
e.Graphics.FillRectangle(Brushes.Orange, clipRect);
e.Graphics.DrawString("StackOverflow", Font, Brushes.Black, 0, 0);
}
}
You will want to set DoubleBuffering etc as this will flicker without, but this should be a good starting example.
Result:

Categories

Resources