How to paint a custom Progress Bar by code (WPF) - c#

I created a WinForms custom progress bar, but it flickers a little. It's double buffered, but it still flickers a little bit, so I'm trying WPF to see if the little flickering can get away.
I'm completely new to WPF. As far as I've read, WPF's OnPaint(PaintEventArgs e) method is called OnRender(DrawingContext drawingContext).
System.Drawing.Image BMP = System.Drawing.Image.FromFile(MyImagePath);
BMP = new Bitmap(BMP, (int)Width, (int)Height);
// Working method I founded in this site for converting a System.Drawing.Bitmap to a BitmapSource
ImageSource Rainbow = CreateBitmapSourceFromGdiBitmap((Bitmap)BMP);
// Working method I founded in this site for converting a System.Drawing.Bitmap to System.Windows.Media.Brush
System.Windows.Media.Brush RainbowBrush = CreateBrushFromBitmap((Bitmap)BMP);
protected override void OnRender(DrawingContext DrawingContext)
{
if (Value > 0)
{
Rect myRect = new Rect(0, 0, ((Width * Value) / (Maximum - Minimum)) + 5, Height);
DrawingContext.DrawRectangle(RainbowBrush, new System.Windows.Media.Pen(), myRect);
}
}
The problem:
My image is not "overriding" the green bar.
Now, if I change Rect myRect = new Rect(0, 0, ((Width * Value) / (Maximum - Minimum)) + 5, Height); to... let's say, Rect myRect = new Rect(0, 50, ((Width * Value) / (Maximum - Minimum)) + 5, Height);, the result is this:
So, the rainbow bar is drawn, but not over the progress bar. If I write Rect myRect = new Rect(0, 0, ((Width * Value) / (Maximum - Minimum)) + 5, Height);, it's drawn but UNDER the progress bar. What do I do to have a rainbow progress bar (and other custom progress bars for that matter)?
Thank you for your help.
Edit: The original progress bar (that one that flickers a little bit) has way more than the rainbow. I just started with the rainbow to make a quick test in WPF and then try and add the other stuff. Just in case if you're wondering why such a simple progress bar was flickering in WinForms. It was because the WinForms one had more than the rainbow. Thank you.

If you need to create a ProgressBar at the start of the Window of WPF application, it is possible to do by using this code:
Your xaml:
<Window x:Class="PasswordBoxMVVM.MainWindow"
<!--The code omitted for the brevity-->
Title="MainWindow" Height="350" Width="525">
<StackPanel x:Name="stackPanel">
<TextBox x:Name="textBox"
<DataGrid />
</StackPanel>
</Window>
Code-Behind:
public MainWindow()
{
InitializeComponent();
PaintProgressBar();
}
private void PaintProgressBar()
{
ProgressBar progressBar = new ProgressBar();
progressBar.IsIndeterminate = true;
progressBar.Margin = new Thickness(10, 0, 10, 10);
progressBar.Visibility = Visibility.Visible;
progressBar.Height = 25;
//progressBar.FlowDirection = FlowDirection.LeftToRight;
progressBar.Foreground = System.Windows.Media.Brushes.Green;
progressBar.Background = System.Windows.Media.Brushes.Red;
progressBar.Value = 50;
stackPanel.Children.Add(progressBar);
}
Where property progressBar.Foreground sets color of your ProgressBar.

Related

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

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.

Image doesn't cover the whole PictureBox

I'm creating an image viewer using C# and having a small issue with scaling (zooming) PictureBox.
I have a PictureBox inside a Panel, and I can zoom (scale) an image using the controls at the top left and using mouse wheel just fine. However, at some specific zoom scales, the image doesn't cover the whole PictureBox.
For example, SO logo (100x116 pixels) at 100% and at 200%:
Image at the right is 199x131 pixels, while the PictureBox is 200x132.
I've set the BackColor of the PictureBox to Red to make the issue noticeable.
This doesn't always happen, just at specific zoom levels. Why is that? Am I doing something wrong?
I can set the BackColor of PictureBox to the BackColor of the Panel to give the illusion that the image covers the whole PictureBox, but I rather fix the problem. If I can't, I'll apply the tricky solution.
Relevant code:
float zoom = 1;
Image image = null;
public MainForm(string[] args)
{
InitializeComponent();
image = ImageBox.Image;
this.ImageBox.MouseWheel += ImageBox_MouseWheel;
if (args.Length > 0)
{
LoadImage(args[0]);
}
}
private void ImageBox_Paint(object sender, PaintEventArgs e)
{
// disable interpolation (sharper pixels)
e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
// https://msdn.microsoft.com/en-us/library/ms142046(v=vs.110).aspx
e.Graphics.DrawImage(image,
new Rectangle(0, 0, ImageBox.Width, ImageBox.Height),
0, 0, image.Width, image.Height, GraphicsUnit.Pixel);
}
private void LoadImage(string path)
{
image = Image.FromFile(path);
ImageBox.Width = (int)(image.Width * zoom);
ImageBox.Height = (int)(image.Height * zoom);
ImageBox.Image = image;
CenterImage();
}
private void ScaleImage()
{
ImageBox.Image = null;
ImageBox.Width = (int)(image.Width * zoom);
ImageBox.Height = (int)(image.Height * zoom);
ImageBox.Image = image;
CenterImage();
}
I've also created a repository, in case anyone wants to examine the app live.
You need to adjust the rectangle in ImageBox_Paint. Try this:
private void ImageBox_Paint(object sender, PaintEventArgs e)
{
int add = ImageBox.Width / 200;
// disable interpolation (sharper pixels)
e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
// https://msdn.microsoft.com/en-us/library/ms142046(v=vs.110).aspx
e.Graphics.DrawImage(image,
new Rectangle(0, 0, ImageBox.Width + add, ImageBox.Height + add),
0, 0, image.Width, image.Height, GraphicsUnit.Pixel);
}
Simply set the SizeMode property of the PictureBox to Zoom. Then adapting the size of the picturebox will automatically make the image stretch to its full size.
You don't even need that Paint event listener; it's inbuilt functionality. Just change the dimensions of the PictureBox to the calculated zoomed dimensions of the image and you're done.

Touch on screen in windows phone 8.1 RT

I am building an application to find dead pixels on the screen and I get a big problem that I can not solve by myself. My code uses to draw a rectangle on the screen at the point that user touch on.
My XAML code:
<Grid x:Name="mainGrid" Background="Gray">
<Canvas x:Name="myCanvas" Background="Purple" PointerMoved="digitizerGrid_PointerMoved" PointerReleased="digitizerGrid_PointerReleased">
<Grid x:Name="digitizerGrid" Visibility="Visible"/>
</Canvas>
</Grid>
and my c# code to handle event:
private void digitizerGrid_PointerMoved(object sender, PointerRoutedEventArgs e)
{
PointerPoint pt = e.GetCurrentPoint(myCanvas);
Point currentContactPt = pt.Position;
double x2 = currentContactPt.X;
double y2 = currentContactPt.Y;
Rectangle rect = new Rectangle()
{
Width = 50,
Height = 50,
StrokeThickness = 20.0,
Fill = new SolidColorBrush(Colors.Green)
};
Canvas.SetLeft(rect, x2);
Canvas.SetTop(rect, y2);
myCanvas.Children.Add(rect);
}
When I touch and fill all the screen like this:
I get the wrong result image below with black pixel blocks:
So I can not detect where is dead pixels. What I was wrong?
Thank in advance!

Server-side rendering of WPF UserControl

I am writing a server side console app in C#/.Net 4.5 that gets some data and creates static chart images that are saved to be displayed by a web server.
I am mostly using the method described here:
http://lordzoltan.blogspot.com/2010/09/using-wpf-to-render-bitmaps.html
However, I added a mainContainer.UpdateLayout(); after the Arrange() so that the databindings would update and be visible in the rendered image, as well as a Measure() before it for good... ah, I'm not gonna go there.
Here is the method that does the rendering:
void RenderAndSave(UIElement target, string filename, int width, int height)
{
var mainContainer = new Grid
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
};
mainContainer.Children.Add(target);
mainContainer.Measure(new Size(width, height));
mainContainer.Arrange(new Rect(0, 0, width, height));
mainContainer.UpdateLayout();
var encoder = new PngBitmapEncoder();
var render = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
render.Render(mainContainer);
encoder.Frames.Add(BitmapFrame.Create(render));
using (var s = File.Open(filename, FileMode.Create))
{
encoder.Save(s);
}
}
The target parameter to the method will be an instance of a WPF/XAML UserControl I made - fairly simple at this point, just a grid with some text databinding to a ViewModel object that I assigned to the DataContext.
The saved image on disk looks good EXCEPT for the OxyPlot Plot object - it is entirely white.
Now, when I am in the designer in Visual Studio 2013, I can see it. I have added a design-time DataContext which is the same object that I use at runtime (this is a spike I am doing - the viewmodel is not in its final form yet, just having a bunch of default data while I work out the kinks). In the designer I see the chart as OxyPlot paints it.
Is there anything special I need to do in order to get my rendering to also contain this OxyPlot chart? It is more or less the point of the exercise so it would be awesome to actually get it to show up!
Thanks in advance for any insights and suggestions!
If you're correctly binding data at runtime as well, then it should work.
[STAThread]
static void Main(string[] args)
{
string filename = "wpfimg.png";
RenderAndSave(new UserControl1(), filename, 300, 300);
PictureBox pb = new PictureBox();
pb.Width = 350;
pb.Height = 350;
pb.Image = System.Drawing.Image.FromFile(filename);
Form f = new Form();
f.Width = 375;
f.Height = 375;
f.Controls.Add(pb);
f.ShowDialog();
}
XAML:
<UserControl x:Class="WpfApp92.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:oxy="http://oxyplot.org/wpf"
xmlns:local="clr-namespace:WpfApp92"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<oxy:PlotView Model="{Binding Model}"/>
</Grid>
</UserControl>
CS:
public partial class UserControl1 : UserControl
{
public PlotModel Model { get; set; }
public UserControl1()
{
InitializeComponent();
Model = new PlotModel();
Model.LegendBorderThickness = 0;
Model.LegendOrientation = LegendOrientation.Horizontal;
Model.LegendPlacement = LegendPlacement.Outside;
Model.LegendPosition = LegendPosition.BottomCenter;
Model.Title = "Simple model";
var categoryAxis1 = new CategoryAxis();
categoryAxis1.MinorStep = 1;
categoryAxis1.ActualLabels.Add("Category A");
categoryAxis1.ActualLabels.Add("Category B");
categoryAxis1.ActualLabels.Add("Category C");
categoryAxis1.ActualLabels.Add("Category D");
Model.Axes.Add(categoryAxis1);
var linearAxis1 = new LinearAxis();
linearAxis1.AbsoluteMinimum = 0;
linearAxis1.MaximumPadding = 0.06;
linearAxis1.MinimumPadding = 0;
Model.Axes.Add(linearAxis1);
var columnSeries1 = new ColumnSeries();
columnSeries1.StrokeThickness = 1;
columnSeries1.Title = "Series 1";
columnSeries1.Items.Add(new ColumnItem(25, -1));
columnSeries1.Items.Add(new ColumnItem(137, -1));
columnSeries1.Items.Add(new ColumnItem(18, -1));
columnSeries1.Items.Add(new ColumnItem(40, -1));
Model.Series.Add(columnSeries1);
var columnSeries2 = new ColumnSeries();
columnSeries2.StrokeThickness = 1;
columnSeries2.Title = "Series 2";
columnSeries2.Items.Add(new ColumnItem(12, -1));
columnSeries2.Items.Add(new ColumnItem(14, -1));
columnSeries2.Items.Add(new ColumnItem(120, -1));
columnSeries2.Items.Add(new ColumnItem(26, -1));
Model.Series.Add(columnSeries2);
DataContext = this;
}
}
I don't know anything about this OxyPlat, but I do know that most charts are often rendered using hardware APIs. Hardware acceleration is usually error-prone when working outside the expected environment (i.e. a client showing a visible Desktop window).
On application initialization, try disabling hardware acceleration:
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
Another possible tweak is to call DoEvents() before you render your bitmap. Possibly with priority set to SystemIdle. This will make sure your DataContext has been successfully bound.
public static void DoEvents(
DispatcherPriority priority = DispatcherPriority.Background)
{
Dispatcher.CurrentDispatcher.Invoke(new Action(delegate { }), priority);
}

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