I have the following graphic in my Windows Form:
I set the X axis to display Time, as shown below:
Here are the methods I call to draw the graph:
private void funcaoQualquer()
{
while (true)
{
funcaoArray[funcaoArray.Length - 1] = hScrollBar1.Value;
Array.Copy(funcaoArray, 1, funcaoArray, 0, funcaoArray.Length - 1);
if (chart1.IsHandleCreated)
{
this.Invoke((MethodInvoker)delegate { atualizaGraficoFuncaoQualquer(hScrollBar1.Value); });
}
else
{
//...
}
Thread.Sleep(250);
}
}
private void atualizaGraficoFuncaoQualquer(int valor)
{
for (int i = 0; i < funcaoArray.Length - 1; ++i)
{
chart1.Series["Series1"].Points.Add(valor);
}
}
However, when drawing the graph and over time, the X axis does not change the values. I think it's being displayed as hours. How do I change to minutes or seconds?
you could set the Format like this:
this.chart1.ChartAreas[0].AxisX.LabelStyle.Format = "mm:ss";
if you have a sampling rate of 4 Hz you could also use seconds and milliseconds:
this.chart1.ChartAreas[0].AxisX.LabelStyle.Format = "ss.fff";
EDIT:
I would suggest to catch the sampling times in an extra List<DateTime> and feed the chart via AddXY with values. Here is a simple programm that draws a curve with a timer. The timer is set to 500 msec.
// Constructor
public Form1()
{
InitializeComponent();
// Format
this.chart1.ChartAreas[0].AxisX.LabelStyle.Format = "ss.fff";
// this sets the type of the X-Axis values
chart1.Series[0].XValueType = ChartValueType.DateTime;
timer1.Start();
}
int i = 0;
List<DateTime> TimeList = new List<DateTime>();
private void timer1_Tick(object sender, EventArgs e)
{
DateTime now = DateTime.Now;
TimeList.Add(now);
chart1.Series[0].Points.AddXY(now, Math.Sin(i / 60.0));
i+=2;
}
Now you can watch the x-axis values increment in the format that you like. Hope this helps
Related
I have this code
Color[] colours = new Color[5]{Color.Red, Color.Blue, Color.Green, Color.Yellow, Color.Black};
public int randGen(int lower, int upper)
{
Random random = new Random();
return random.Next(lower, upper);
}
public PlayGame()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
changeColour();
}
public void changeColour()
{
int milliseconds = randGen(1000, 5000);
int count = 0;
Device.StartTimer(TimeSpan.FromMilliseconds(milliseconds), () =>
{
var layout = new StackLayout { Padding = new Thickness(5, 10) };
var label = new Label { Text = "Time: ", TextColor = Color.Green, FontSize = 25 };
layout.Children.Add(label);
label.Text += milliseconds.ToString();
this.Content = layout;
if (count < 4)
{
BackgroundColor = colours[count];
count++;
milliseconds = randGen(1000, 5000);
return true;
}
else
{
BackgroundColor = Color.Black;
return false;
}
}
);
}
Which has an array of colours. The idea is that every 1-5 seconds (which should be random each time), the background colour should change, and the text should write how long the screen was on for.
Currently, however, the time shown in the text is not reflective of the time each screen shows for, and I have some speculative concern that milliseconds in:
Device.StartTimer(TimeSpan.FromMilliseconds(milliseconds)
doesn't change at all. Any ideas?
this is what I would do - have your timer fire every second (or whatever granularity you need) but only execute your code every X times
using System.Timers;
// these are class variables
Timer timer;
int timecount = 0;
// adjust this dynamically so your code only executes every 1-n seconds
int interval = 1;
// to this wherever you want to start the timer
timer = new Timer();
timer.Elapsed += Timer_Elapsed;
// fire every 1 sec
timer.Interval = 1000;
timer.Start();
// timer event handler
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
timecount++;
if (timecount == interval)
{
timecount = 0;
// do other stuff here
}
}
So, I'm trying to create a method that animates the movement of a control on a form. I generate all the points the control is going to travel to beforehand, like this:
private static List<decimal> TSIncrement(int durationInMilliseconds, decimal startPoint, decimal endPoint)
{
List<decimal> tempPoints = new List<decimal>();
decimal distance = endPoint - startPoint;
decimal increment = distance / durationInMilliseconds;
decimal tempPoint = (decimal)startPoint;
for (decimal i = durationInMilliseconds; i > 0; i--)
{
tempPoint += increment;
tempPoints.Add(tempPoint);
}
return tempPoints;
}
This outputs a list with as many points as there are milliseconds in the duration of the animation. I think you can guess what I'm doing afterwards:
public static void ControlAnimation(Control control, Point locationEndpoint, int delay)
{
if (delay > 0)
{
List<decimal> tempXpoints = TSIncrement(delay, control.Location.X, locationEndpoint.X);
List<decimal> tempYpoints = TSIncrement(delay, control.Location.Y, locationEndpoint.Y);
for (int i = 0; i < delay; i++)
{
control.Location = new Point((int)Math.Round(tempXpoints[i]), (int)Math.Round(tempYpoints[i]));
Thread.Sleep(1); //I won't leave this obviously, it's just easier for now
}
}
}
In the actual method, I go through this list of points and use those to create the new location of the control (I actually use two lists for the abscissa and the ordinate).
My problem lies in creating one millisecond of delay between each shifting. Since the code in the loop takes a bit of time to execute, I usually end up with approximately 5 seconds more duration.
I tried using a stopwatch to measure the time it takes to set control.location, and subtracting that to the 1 millisecond delay. The stopwatch adds some delay as well though, since I gotta start, stop and reset it everytime.
So what should I do, and how could I improve my code? Any feedback is greatly appreciated :)
You won't get a reliable delay below around 50 milliseconds in WinForms, so that is the delay I used below:
private Random R = new Random();
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
label1.AutoSize = false;
label1.Size = button2.Size;
Point p = new Point(R.Next(this.Width - button2.Width), R.Next(this.Height - button2.Height));
label1.Location = p;
label1.SendToBack();
await MoveControl(button2, p, R.Next(2000, 7001));
button1.Enabled = true;
}
private Task MoveControl(Control control, Point LocationEndPoint, int delayInMilliseconds)
{
return Task.Run(new Action(() =>
{
decimal p;
int startX = control.Location.X;
int startY = control.Location.Y;
int deltaX = LocationEndPoint.X - startX;
int deltaY = LocationEndPoint.Y - startY;
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
while(sw.ElapsedMilliseconds < delayInMilliseconds)
{
System.Threading.Thread.Sleep(50);
p = Math.Min((decimal)1.0, (decimal)sw.ElapsedMilliseconds / (decimal)delayInMilliseconds);
control.Invoke((MethodInvoker)delegate {
control.Location = new Point(startX + (int)(p * deltaX), startY + (int)(p * deltaY));
});
}
}));
}
I'm sending to my method different images and I want insert some effect to this change.
How can I fade in and fade out images?
private void ShowImage(Image image, ImageLayout imageLayout, int numberOfSeconds)
{
try
{
if (this.image_timer != null)
this.KillImageTimer();
this.customer_form.DisplayImage(image, imageLayout);
this.image_timer = new Timer();
this.image_timer.Tick += (object s, EventArgs a) => NextImage();
this.image_timer.Interval = numberOfSeconds* 1000;
this.image_timer.Start();
}
catch
{
//Do nothing
}
public void DisplayImage(Image image, ImageLayout imageLayout)
{
panel1.BackgroundImage = image;
panel1.BackgroundImageLayout = imageLayout;
}
There are no built-in fading transitions in Winforms.
So you will need to write one yourself.
The simplest one I can think of uses a second Panel, that is layered upon the first one and in fact needs to be inside the first Panel or else the transparency effect won't work..
Here is the setup, using two Panels:
public Form1()
{
InitializeComponent();
pan_image.BackgroundImage = someImage;
pan_layer.Parent = pan_image;
pan_layer.BackColor = pan_image.BackColor;
pan_layer.Size = pan_image.Size;
pan_layer.Location = Point.Empty;
}
For the fading animation I use a Timer. This is a quick code example:
Timer timer1 = new Timer();
int counter = 0;
int dir = 1; // direction 1 = fade-in..
int secondsToWait = 5;
int speed1 = 25; // tick speed ms
int speed2 = 4; // alpha (0-255) change speed
void timer1_Tick(object sender, EventArgs e)
{
// we have just waited and now we fade-out:
if (dir == 0)
{
timer1.Stop();
dir = -speed2;
counter = 254;
timer1.Interval = speed2;
timer1.Start();
}
// the next alpha value:
int alpha = Math.Min(Math.Max(0, counter+= dir), 255);
button1.Text = dir > 0 ? "Fade In" : "Fade Out";
// fully faded-in: set up the long wait:
if (counter >= 255)
{
timer1.Stop();
button1.Text = "Wait";
timer1.Interval = secondsToWait * 1000;
dir = 0;
timer1.Start();
}
// fully faded-out: try to load a new image and set direction to fade-in or stop
else if (counter <= 0)
{
if ( !changeImage() )
{
timer1.Stop();
button1.Text = "Done";
}
dir = speed2;
}
// create the new, semi-transparent color:
Color col = Color.FromArgb(255 - alpha, pan_image.BackColor);
// display the layer:
pan_layer.BackColor = col;
pan_layer.Refresh();
}
I start it in a Button, on which I also show the current state:
private void button1_Click(object sender, EventArgs e)
{
dir = speed2;
timer1.Tick += timer1_Tick;
timer1.Interval = speed1;
timer1.Start();
}
As you can see I use two speeds you can set: One to control the speed of the Timer and one to control the steps by which the transparency changes on each Tick.
The effect is created by simply changing the Color from the BackgroundColor of the image Panel to fully transparent and back, waiting in between for a specified number of seconds.
And the end of the effect I call a function changeImage() to change the images. If this function returns false the Timer is stopped for good..
I'm pretty sure this could be written in a cleaner and more elegant way, but as it is it seems to work..
Update
for flicker-free display use a double-buffered control, like this Panel subclass:
class DrawPanel : Panel
{
public DrawPanel() { DoubleBuffered = true; }
}
Here is a sample implementation for changeImage:
bool changeImage()
{
if (pan_image.BackgroundImage != null)
{
var img = pan_image.BackgroundImage;
pan_image.BackgroundImage = null;
img.Dispose();
}
pan_image.BackgroundImage = Image.FromFile(imageFiles[index++]);
return index < imageFiles.Count;
}
It assumes two class level variables: a List<string> imageFiles filled with file names of images for a slide-show and an int index = 0.
Ok, this could be a little tough to explain. I have a method that computes X and Y values that I want to plot. This method is purely backend and runs inside a backgroundworker called from my main GUI thread.
Separate from my main form I have a form that contains only a zedgraph and a ticker. I use the combination to display the rolling X,Y spit out from my background thread. This works fine, everything goes great here.
When I click a button on my main GUI the backgroundworker is closed, and zedgraph stops updating. Here's where my problem starts
When I click the stop button the graph needs to stay up. It does this just fine... iff it's the very first time it was run. On all future graphs this happens: (Top image is the first graph, second image is the second graph.)
The first graph keeps updating when it isn't supposed to. How do I keep this from happening? Is there a way to "shut down" the first zedgraph and keep it from listening for new data?
Below is my zedgraph code, I'm pretty sure the problem is in here somewhere and not my main GUI code.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ZedGraph;
namespace RTHERM
{
public partial class Readout : Form
{
// Starting time in milliseconds
public static float Time_old = 0.0f;
public static float Tsurf_old;
public static float Tmidr_old;
public static float Tcent_old;
public static float Tenvi_old;
// Every "redrawInterval" secods plot a new point (if one is available)
public static int redrawInterval;
public int plotRange = 15; // Plot will span "plotRange" minutes
int tickStart = 0;
public Readout()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//timer1.Equals(0);
GUI gui = new GUI();
GraphPane graph = zedGraph.GraphPane;
graph.Title.Text = GUI.plotTitle;
graph.XAxis.Title.Text = "Time [min]";
graph.YAxis.Title.Text = "Temperature [F]";
graph.Legend.Position = ZedGraph.LegendPos.BottomCenter;
// Save 1200 points. At 50 ms sample rate, this is one minute
// The RollingPointPairList is an efficient storage class that always
// keeps a rolling set of point data without needing to shift any data values
RollingPointPairList surfList = new RollingPointPairList(1200);
//surfList.Clear();
RollingPointPairList midrList = new RollingPointPairList(1200);
//midrList.Clear();
RollingPointPairList centList = new RollingPointPairList(1200);
//centList.Clear();
RollingPointPairList furnList = new RollingPointPairList(1200);
//furnList.Clear();
// Initially, a curve is added with no data points (list is empty)
// Color is blue, and there will be no symbols
LineItem surf = graph.AddCurve("Surface", surfList, Color.DarkBlue, SymbolType.None);
LineItem midr = graph.AddCurve("Mid-Radius", midrList, Color.DarkOliveGreen, SymbolType.None);
LineItem cent = graph.AddCurve("Center", centList, Color.DarkOrange, SymbolType.None);
LineItem furn = graph.AddCurve("Ambient", furnList, Color.Red, SymbolType.None);
surf.Line.Width = 2;
midr.Line.Width = 2;
cent.Line.Width = 2;
furn.Line.Width = 2;
// Check for new data points
timer1.Interval = redrawInterval;
timer1.Enabled = true;
//timer1.Start();
// Just manually control the X axis range so it scrolls continuously
// instead of discrete step-sized jumps
graph.XAxis.Scale.Min = 0;
graph.XAxis.Scale.Max = plotRange;
graph.XAxis.Scale.MinorStep = 1;
graph.XAxis.Scale.MajorStep = 5;
// Scale the axes
zedGraph.AxisChange();
// Save the beginning time for reference
tickStart = Environment.TickCount;
}
// USING A TIMER OBJECT TO UPDATE EVERY FEW MILISECONDS
private void timer1_Tick(object sender, EventArgs e)
{
// Only redraw if we have new information
if (Transfer.TTIME != Time_old)
{
GraphPane graph = this.zedGraph.GraphPane;
// Make sure that the curvelist has at least one curve
if (zedGraph.GraphPane.CurveList.Count <= 0)
return;
// Grab the three lineitems
LineItem surf = this.zedGraph.GraphPane.CurveList[0] as LineItem;
LineItem midr = this.zedGraph.GraphPane.CurveList[1] as LineItem;
LineItem cent = this.zedGraph.GraphPane.CurveList[2] as LineItem;
LineItem furn = this.zedGraph.GraphPane.CurveList[3] as LineItem;
if (surf == null)
return;
// Get the PointPairList
IPointListEdit surfList = surf.Points as IPointListEdit;
IPointListEdit midrList = midr.Points as IPointListEdit;
IPointListEdit centList = cent.Points as IPointListEdit;
IPointListEdit enviList = furn.Points as IPointListEdit;
// If these are null, it means the reference at .Points does not
// support IPointListEdit, so we won't be able to modify it
if (surfList == null || midrList == null || centList == null || enviList == null)
return;
// Time is measured in seconds
double time = (Environment.TickCount - tickStart) / 1000.0;
// ADDING THE NEW DATA POINTS
// format is List.Add(X,Y) Finally something that makes sense!
surfList.Add(Transfer.TTIME, Transfer.TSURF);
midrList.Add(Transfer.TTIME, Transfer.TMIDR);
centList.Add(Transfer.TTIME, Transfer.TCENT);
enviList.Add(Transfer.TTIME, Transfer.TENVI);
// Keep the X scale at a rolling 10 minute interval, with one
// major step between the max X value and the end of the axis
if (GUI.isRunning)
{
Scale xScale = zedGraph.GraphPane.XAxis.Scale;
if (Transfer.TTIME > xScale.Max - xScale.MajorStep)
{
xScale.Max = Transfer.TTIME + xScale.MajorStep;
xScale.Min = xScale.Max - plotRange;
}
}
// Make sure the Y axis is rescaled to accommodate actual data
zedGraph.AxisChange();
// Force a redraw
zedGraph.Invalidate();
}
else return;
}
public void reset()
{
Time_old = 0.0f;
Tsurf_old = 0.0f;
Tmidr_old = 0.0f;
Tcent_old = 0.0f;
Tenvi_old = 0.0f;
}
private void Form1_Resize(object sender, EventArgs e)
{
if (GUI.isRunning)
{
SetSize();
}
}
// Set the size and location of the ZedGraphControl
private void SetSize()
{
// Control is always 10 pixels inset from the client rectangle of the form
Rectangle formRect = this.ClientRectangle;
formRect.Inflate(-10, -10);
if (zedGraph.Size != formRect.Size)
{
zedGraph.Location = formRect.Location;
zedGraph.Size = formRect.Size;
}
}
private void saveGraph_Click(object sender, EventArgs e)
{
GUI.Pause();
zedGraph.DoPrint();
//SaveFileDialog saveDialog = new SaveFileDialog();
//saveDialog.ShowDialog();
}
private void savePlotDialog_FileOk(object sender, CancelEventArgs e)
{
// Get file name.
string name = savePlotDialog.FileName;
zedGraph.MasterPane.GetImage().Save(name);
GUI.Resume();
}
private bool zedGraphControl1_MouseMoveEvent(ZedGraphControl sender, MouseEventArgs e)
{
// Save the mouse location
PointF mousePt = new PointF(e.X, e.Y);
// Find the Chart rect that contains the current mouse location
GraphPane pane = sender.MasterPane.FindChartRect(mousePt);
// If pane is non-null, we have a valid location. Otherwise, the mouse is not
// within any chart rect.
if (pane != null)
{
double x, y;
// Convert the mouse location to X, and Y scale values
pane.ReverseTransform(mousePt, out x, out y);
// Format the status label text
toolStripStatusXY.Text = "(" + x.ToString("f2") + ", " + y.ToString("f2") + ")";
}
else
// If there is no valid data, then clear the status label text
toolStripStatusXY.Text = string.Empty;
// Return false to indicate we have not processed the MouseMoveEvent
// ZedGraphControl should still go ahead and handle it
return false;
}
private void Readout_FormClosed(object sender, FormClosedEventArgs e)
{
}
private void Readout_FormClosing(object sender, FormClosingEventArgs e)
{
//e.Cancel = true;
//WindowState = FormWindowState.Minimized;
}
}
}
The last point is connected to the first point in the graph. I suspect, because all your RollingPointPairList contains the data multiple times. Verify this using a breakpoint in your timer1_Tickfunction.
On private void Form1_Load you will add the complete list to the LineItem.
graph.CurveList.Clear();
LineItem surf = graph.AddCurve("Surface", surfList, Color.DarkBlue, SymbolType.None);
LineItem midr = graph.AddCurve("Mid-Radius", midrList, Color.DarkOliveGreen, SymbolType.None);
LineItem cent = graph.AddCurve("Center", centList, Color.DarkOrange, SymbolType.None);
LineItem furn = graph.AddCurve("Ambient", furnList, Color.Red, SymbolType.None);
my application runs files and every file has it's own running time.
this function get in millisecond the time that the progress time should run:
timerProgress = my timer
pbStatus = my progress bar
public void AnimateProgBar(int milliSeconds)
{
if (!timerProgress.Enabled && milliSeconds != 0)
{
pbStatus.Value = 0;;
timerProgress.Interval = milliSeconds / 100;
timerProgress.Enabled = true;
}
}
and this is my timer that fill the progress bar:
private void timerProgress_Tick(object sender, EventArgs e)
{
if (pbStatus.Value < 100)
{
pbStatus.Value += 1;
pbStatus.Refresh();
}
else
{
timerProgress.Enabled = false;
}
}
my problem is that progress bar runs too fast for example if AnimateProgBar get the value of 12000 (12 seconds) the progress bar runs only for 6-7 seconds.
It's fishy that your code doesn't work. I tried it a few times, and it missed with about 0.6 seconds each time; it seems like the timer is just imprecise.
What you could do is to take care of the time yourself instead of trusting a timer:
WithEvents Tmr As New Timer With {.Interval = 100}
Dim startTime As Date, AnimationTime%
Sub AnimateProgress(ms%)
If ms <= 0 Then Exit Sub
ProgressBar1.Value = 0
AnimationTime = ms
startTime = Now
Tmr.Start()
End Sub
Private Sub Tmr_Tick() Handles Tmr.Tick
ProgressBar1.Value = Math.Min((Now - startTime).TotalMilliseconds / AnimationTime, 1) * 100
If ProgressBar1.Value = 100 Then Tmr.Stop()
End Sub
EDIT - Response to the reply bellow:
Oh sorry, no it's vb.net. I know both the languages just as well, but I prefer vb, and tend to think that everypony else does so too.
Here's the c# version:
DateTime startTime; int animationTime;
void AnimateProgress(int ms) {
if (ms <= 0) return;
progressBar1.Value = 0;
animationTime = ms;
startTime = DateTime.Now;
Tmr.Start();
}
private void Tmr_Tick(object sender, EventArgs e) {
progressBar1.Value = (int)(Math.Min((DateTime.Now - startTime).TotalMilliseconds / animationTime, 1) * 100);
if (progressBar1.Value == 100) Tmr.Stop();
}
You can try with this sample, based on PerformStep method
var progressBar = new System.Windows.Forms.ProgressBar();
progressBar.Maximum = 100;
progressBar.Minimum = 0;
progressBar.Step = 10;
//begin loop
//Your treatment of step
progressBar.PerformStep();
//end loop
msdn link : http://msdn.microsoft.com/fr-fr/library/system.windows.forms.progressbar.performstep(v=vs.80).aspx
You have sample here
private void CopyWithProgress(string[] filenames)
{
// Display the ProgressBar control.
pBar1.Visible = true;
// Set Minimum to 1 to represent the first file being copied.
pBar1.Minimum = 1;
// Set Maximum to the total number of files to copy.
pBar1.Maximum = filenames.Length;
// Set the initial value of the ProgressBar.
pBar1.Value = 1;
// Set the Step property to a value of 1 to represent each file being copied.
pBar1.Step = 1;
// Loop through all files to copy.
for (int x = 1; x <= filenames.Length; x++)
{
// Copy the file and increment the ProgressBar if successful.
if(CopyFile(filenames[x-1]) == true)
{
// Perform the increment on the ProgressBar.
pBar1.PerformStep();
}
}
}
link : http://msdn.microsoft.com/fr-fr/library/system.windows.forms.progressbar.performstep(v=vs.80).aspx
I couldn't reproduce your issue. I just tested a new form, with a ProgressBar and a Timer as you detailed in your question, and merely added one button to start the test, and one label to show the elapsed time:
DateTime start;
private void button1_Click(object sender, EventArgs e)
{
start = DateTime.Now;
AnimateProgBar(12000);
}
private void timer1_Tick(object sender, EventArgs e)
{
label1.Text = DateTime.Now.Subtract(start).TotalSeconds.ToString();
//the rest of your code, starting with "if (pbStatus.Value < 100)"
I consistently got 12.6 seconds until the progressBar filled (and the timer stopped, freezing the label text)... Maybe part of your progressBar is hidden?
[Edit]
If you're curious about the 0.6 extra seconds that BlackCap also noticed, it's because you're setting the timer interval to 120 milliseconds, but timer events have a resolution of about 18 milliseconds, so it will actually fire at 126 instead of 120.