Bear with me, I'm new to Stack Overflow, but have used it as a resource for a long time when researching methods of programming that I'm not fond with.
I read up a tutorial on how to create a graph in a C# Windows Forms Application, and was attempting to find out how to make it update itself in real-time if I ever need to use the graph to plot the total amount of data in a sensor. To test it out, I'm using a timer, ticking at every second (1000ms). And before I can use this with a sensor, I'm having two values automatically increment by 1.
The current problem I'm facing is that the chart itself won't update, and only stays the way it was drawn when the form loaded. I thought it was because I have to redraw the chart with chart1.Update();, and I tried using that before/after recreating the chart every second. However, the result is the same regardless. I just wondered if there's something I haven't done or needs to be changed in order to update the chart in real-time.
This is where the code is at currently:
public partial class Form1 : Form
{
int a = 1;
int b = 2;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// Data arrays.
string[] seriesArray = { "Cats", "Dogs" };
int[] pointsArray = { a, b };
// Set palette.
this.chart1.Palette = ChartColorPalette.SeaGreen;
// Set title.
this.chart1.Titles.Add("Pets");
// Add series.
for (int i = 0; i < seriesArray.Length; i++)
{
// Add series.
Series series = this.chart1.Series.Add(seriesArray[i]);
// Add point.
series.Points.Add(pointsArray[i]);
}
}
private void timer1_Tick(object sender, EventArgs e)
{
a++;
b++;
// Data arrays.
string[] seriesArray = { "Cats", "Dogs" };
int[] pointsArray = { a, b };
// Set palette.
this.chart1.Palette = ChartColorPalette.SeaGreen;
// Set title.
this.chart1.Titles.Add("Pets");
// Add series.
for (int i = 0; i < seriesArray.Length; i++)
{
// Add series.
Series series = this.chart1.Series.Add(seriesArray[i]);
// Add point.
series.Points.Add(pointsArray[i]);
}
chart1.Update();
}
}
Your code has several problems:
The timer click event is not hooked up. I know that it isn't because otherwise you'd get an exception telling you that..
..you can add a series only once. You were doing it on each timer.Tick. This and all other setup commands should go into an initial method like the form load.
I have corrected the errors in the code below, but, obviously, the data don't make any sense yet.
Also: While I have added code to hook up the timer.Tick, the button.Click is not hooked up. Usually you let the designer do this by double-clicking the control to hook up the standard event of a control or by double-clicking the event in the control's event tab in the property page.
int a = 1;
int b = 2;
string[] seriesArray = { "Cats", "Dogs" };
private void Form1_Load(object sender, EventArgs e)
{
// Set palette.
this.chart1.Palette = ChartColorPalette.SeaGreen;
// Set title.
this.chart1.Titles.Add("Pets");
// Add series
this.chart1.Series.Clear();
for (int i = 0; i < seriesArray.Length; i++)
{
chart1.Series.Add(seriesArray[i]);
}
// hook up timer event
timer1.Tick += timer1_Tick;
}
private void timer1_Tick(object sender, EventArgs e)
{
a++;
b++;
// Data array
int[] pointsArray = { a, b };
for (int i = 0; i < seriesArray.Length; i++)
{
// Add point.
chart1.Series[i].Points.Add(pointsArray[i]);
}
}
private void button1_Click(object sender, EventArgs e)
{
timer1.Start();
}
Related
writing a windows Form program that takes user input, and changes the color of the label accordingly.
and when the program reaches 2 words it repeats itself. it works fine until it reaches the second
word, where there I added a method to restart from the beginning after "validating and changing color", I can see the program validated the word, but when it comes to change the color, it just skips that part and starts from the beginning.
public partial class Form1: Form
{
public Form1()
{
InitializeComponent();
}
string[] listOfWords = { "bike", "car" };
Label[] labelsToDisplay = new Label[2];
string currentWord = "";
string userInput = "";
int increment = 0;
private void Form1_Load(object sender, EventArgs e)
{
labelsToDisplay[0] = this.label1;
labelsToDisplay[1] = this.label2;
for (int i = 0; i < labelsToDisplay.Length; i++)
{
labelsToDisplay[i].Text = listOfWords[i];
}
increment = 0;
currentWord = listOfWords[0];
}
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == ' ')
{
userInput = textBox1.Text;
if (userInput.Trim() == currentWord)
{
labelsToDisplay[increment].ForeColor = Color.Green;
}
else if (userInput.Trim() != currentWord)
{
labelsToDisplay[increment].ForeColor = Color.Red;
}
increment++;
if (increment < labelsToDisplay.Length)
{
currentWord = listOfWords[increment];
}
else if (increment >= labelsToDisplay.Length)
{
for (int i = 0; i < labelsToDisplay.Length; i++)
{
labelsToDisplay[i].ForeColor = Color.Black;
}
Form1_Load(sender, e);
}
textBox1.Text = "";
userInput = "";
}
}
}
I initialize an array of words and labels.
load Event connects the labels of the designer to the label array.
then make equal the labels to the listOfWords array.
and I initialize currnetWord.
now the user enters data into textBox1.
once the user hits the "space" an event is triggered, that checks if the text the user entered is equal to currentWord. if it is that label turns green. and red if not equal.
I put an "increment" int that adds 1 every time user presses space to know where I'm up to
now once the "increment" is larger than the length of the label array it triggers the load event that starts the process again.
but the issue is that the program is supposed to start the process again only after changing the color of the label but instead it just restarts without changing color.
but when I put a MessageBox before running the LoadEvent or if I remove the load event the label does turn green.
(i tried Thread.Sleep but that just pauses everything)
There needs to be a delay between setting the color to green/red and resetting all label colors to black. Here is a way to do it, however it may not be the best way. I first create a background worker and hook into the DoWork and RunWorkerCompleted events. Inside DoWork we create the delay using the Thread Sleep. Inside of RunWorkerCompleted we reset the label colors and call form load.
private BackgroundWorker bgWorker = new BackgroundWorker();
public Form1()
{
InitializeComponent();
bgWorker.DoWork += BgWorker_DoWork;
bgWorker.RunWorkerCompleted += BgWorker_RunWorkerCompleted;
}
private void BgWorker_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(1000);
}
private void BgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Reset();
}
private void Reset()
{
for (int i = 0; i < labelsToDisplay.Length; i++)
{
labelsToDisplay[i].ForeColor = Color.Black;
}
Form1_Load(null, null);
}
and inside of your existing code i changed where the increment exceeds the array bounds
else if (increment >= labelsToDisplay.Length)
{
bgWorker.RunWorkerAsync();
}
This will allow you to see the color before resetting the colors to black.
One thing to note is that the user will still be able to interact with the textbox and fire the KeyPress event. You could add a boolean to track whether the event should fire or not, something like..
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
if (isResetting)
{
return;
}
.....
}
Just make sure to set that bool to true when starting the background worker, and false once the worker has completed.
Newbie in programming here so please give answers with clear elaboration. Thank you.
When i do this loop for a slideshow with given number of loops, the pictures do not show in the picturebox while the loop is running.
And, i have a selectedindexchanged event somewhere so only when the loop ends, that event fires and only the last picture is shown on the picturebox.
CODE:
if (mtxtloop.Text != "")
{
int intNumberOfLoops = Convert.ToInt16(mtxtloop.Text);
for (int intAlbum = 0; intAlbum < intNumberOfLoops; intAlbum++)
{
for (int intPictures = 0; intPictures < listBoxPicturesInAlbum.Items.Count; intPictures++)
{
//Just to check position.
listboxPicturesInAlbum.SelectedIndex = intPictures;
Thread.Sleep(2000);
//Insert selecteditem into picture
//ERROR HERE: PictureBox doesn't show selecteditem
pBoxOfSelectedPicture.Image = Image.FromFile(listBoxPicturesInAlbum.SelectedItem.ToString());
}
}
}
In your original code you are simply missing a PBox_loop.Refresh();. But you are also tying up the UI thread by sending it to sleep for seconds. Never a good idea.. (Thread.Sleep() can sometimes help resolve race conditions but not here and never for more than 10-100ms)
Here is the way I would do it: Use a Timer and three variables at e.g. class level to keep track of the progress..
int intNumberOfLoops = 1;
int intLoopCounter = 0;
int pictureIndex = 0;
private void startButton_Click(object sender, EventArgs e)
{
pictureIndex = 0;
intLoopCounter = 0;
// insert error checking here!
intNumberOfLoops = Convert.ToInt16(mtxtloop.Text);
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
// final image:
if (intLoopCounter >= intNumberOfLoops)
{ // this assumes there is a selected item!
PBox_loop.ImageLocation = listBoxPicturesInAlbum.SelectedItem.ToString();
timer1.Stop();
return;
}
// the regular loop:
PBox_loop.ImageLocation = listBoxPicturesInAlbum.Items[pictureIndex];
pictureIndex++;
if (pictureIndex >= listBoxPicturesInAlbum.Items.Count)
{
pictureIndex = 0;
intLoopCounter++;
}
}
Using a Timer prevent the UI thread of being blocked without the hassle of starting a Thread of your own..
WARNING: Embedded software delevoper trying to build PC software!
I'm trying to interface a piece of hardware that communicates with the PC via serial interface. The PC software (C#) periodicaly sends a byte array, which I would like to adjust using some trackbars.
Instead of adding 8 trackbars on the design view, I add one to help me align it and then I create a List which I populate on load like so:
public partial class FormDmxTemplate : Form
{
// Controls
// Create a list of tracbars.
List<TrackBar> trackBarDmx = new List<TrackBar>();
public FormDmxTemplate()
{
InitializeComponent();
}
private void FormDmxTemplate_Load(object sender, EventArgs e)
{
// Add first instance on the list
trackBarDmx.Add(trackBarDmx1);
// Generate 7 more, 8 total, of each
// Copy settings, and place them next to each other
for (int i = 1; i < 8; i++)
{
// Trackbars
trackBarDmx.Add(new TrackBar());
trackBarDmx[i].TickStyle = trackBarDmx[0].TickStyle;
trackBarDmx[i].Orientation = trackBarDmx[0].Orientation;
trackBarDmx[i].Minimum = trackBarDmx[0].Minimum;
trackBarDmx[i].Maximum = trackBarDmx[0].Maximum;
trackBarDmx[i].Size = new System.Drawing.Size(trackBarDmx[0].Size.Width, trackBarDmx[0].Size.Height);
trackBarDmx[i].Location = new System.Drawing.Point(trackBarDmx[i-1].Location.X + 60, trackBarDmx[0].Location.Y);
this.Controls.Add(trackBarDmx[i]);
}
}
}
Is it possible to have events for all the List members like this one?
private void trackBarDmx1_Scroll(object sender, EventArgs e)
{
}
Which means I'd like update the relevant byte in my byte array to match the TrackBar value, using events if possible.
NOTE: This is a form template which I load and close via another form.
You can subscribe to the events when creating the TrackBars. All can have the same event handler:
trackBarDmx[i].Scroll += trackBarDmx1_Scroll;
Then in the handler you can find out which is this TrackBar and at which index it is (if necessary)
private void trackBarDmx1_Scroll(object sender, EventArgs e)
{
TrackBar bar = sender as TrackBar;
int trackBarIndex = this.trackBarDmx.IndexOf(bar);
}
Not sure if I understood what you are trying to achieve. Does this do what you need:
for (int i = 0; i < 7; i++)
{
TrackBar trackBar = new TrackBar();
trackBar.Tag = i;
// Other properties
trackBar.Scroll += new EventHandler(trackBar_Scroll);
}
In the handler:
void trackBar_Scroll(object sender, EventArgs e)
{
// Get the trackbar
TrackBar current = sender as TrackBar;
// Do something here. Use tag property to identify which byte array should be changed
}
BTW, do you really need to retain the list of TrackBar?
You can do this :
for (int i = 1; i < 8; i++)
{
// Trackbars
trackBarDmx.Add(new TrackBar());
trackBarDmx[i].TickStyle = trackBarDmx[0].TickStyle;
trackBarDmx[i].Orientation = trackBarDmx[0].Orientation;
trackBarDmx[i].Minimum = trackBarDmx[0].Minimum;
trackBarDmx[i].Maximum = trackBarDmx[0].Maximum;
trackBarDmx[i].Size = new System.Drawing.Size(trackBarDmx[0].Size.Width, trackBarDmx[0].Size.Height);
trackBarDmx[i].Location = new System.Drawing.Point(trackBarDmx[i-1].Location.X + 60, trackBarDmx[0].Location.Y);
this.Controls.Add(trackBarDmx[i]);
// Notice no number in the handler name
trackBarDmx[i].Scroll += trackBarDmx_Scroll;
}
Now in the handler the simplest thing to do would be :
private void trackBarDmx_Scroll(object sender, EventArgs e)
{
var tb = sender as TrackBar;
if(sender == null)
{return;}
switch (sender.Name)
{
case "trackBarDmx1_Scroll" :
// handle changes to bar 1
break;
// and so on
}
}
So I have this GUI application that is supposed to calculate shipping costs. First I need to enter the number of packages that is being shipped (already done), then choose package # from a domainUpDown tool(also complete), and then input dimensions for selected package in a text box. This is where I'm stuck. This is what I have thus far.
const int WEIGHT = 0
const int NMBR_OF_PROPETIES = 7;
const int MAX_PACKAGES = 10;
const double FLAT_RATE = 4.99;
int numOfPackages, packageNumber;
double[,] packagesArray = new double[MAX_PACKAGES, NMBR_OF_PROPETIES];
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//label texts get initialized
}
private void button1_Click(object sender, EventArgs e)
{
if (!int.TryParse(numOfPackagesTextBox.Text, out numOfPackages))
{
//message box tells that number cant be parsed
}
else if(numOfPackages > 0 && numOfPackages < 10)
{
//everything in the program is enabled
}
else
{
//message box saying that you can't ship more than 10 packages
}
//adding package numbers to the domainUpDown tool
DomainUpDown.DomainUpDownItemCollection items = packageUpDown.Items;
for (int packageNumber = 1; packageNumber <= numOfPackages; packageNumber++)
{
items.Add(packageNumber);
}
}
private void weightBox_TextChanged(object sender, EventArgs e)
{
packagesArray[(int)packageUpDown.SelectedItem, WEIGHT] = Convert.ToInt32(weightBox.Text);
//also here goes the rest of the dimension entry
}//from here i don't know what to do, and im sure that this isn't right..
The thing I'm confused about is how to save entered text if user somehow changes package number from domainUpDown tool.
Have you tried registering to the OnChanged or SelectedItemChanged events of the DomainUpDown ?
Perform saving on the OnChanged event handler.
Example:
myDomainUpDown.OnChanged += new EventHandler(myDomainUpDown_OnChanged);
void OnChanged(object sender, EventArgs e)
{
//save all the information you need in whatever collection/file/db you want
}
I have four PictureBoxes (each PictureBox represents one dice) and a Timer that changes every 100ms source pictures (loaded in memory as List<Bitmap> imagesLoadedFromIncludedResources).
Code:
private List<PictureBox> dices = new List<PictureBox>();
private void timer_diceImageChanger_Tick(object sender, EventArgs e)
{
foreach (PictureBox onePictureBox in dices)
{
oneDice.WaitOnLoad = false;
onePictureBox.Image = //... ;
oneDice.Refresh();
}
}
I need to change all the images at once - at this moment, you can see that the images are changing from left to right with a small delay.
I tried variant with one Thread for each PictureBox (using Control.Invoke method from this answer) - it is visually little better but not perfect.
You can try to suspend form's layout logic:
SuspendLayout();
// set images to pictureboxes
ResumeLayout(false);
Parallel.ForEach
(
dices,
new ParallelOptions { MaxDegreeOfParallelism = 4 },
(dice) =>
{
dice.Image = ...;
dice.WaitOnLoad = false;
dice.Refresh();
}
);
The problem is that UI controls can only be accessed from the UI thread. If you want to use this approach, you must create a copy of your PictureBoxes and then replace the UI ones once the operation is done.
Another approach would be creating two PictureBoxes, with the first one just on the top of the other one (hiding the latter)... you change all the images and then, once the processing is complete, you iterate all the ones in the back putting them on the top which would result in a lesser delay.
I'd approach this differently - it's been a while since I've played with WinForms stuff, but I'd probably take more control over the rendering of the images.
In this example I've got the images all in one source bitmap stacked vertically, stored as a resource in assembly:
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Timer timer;
private Bitmap dice;
private int[] currentValues = new int[6];
private Random random = new Random();
public Form1()
{
InitializeComponent();
this.timer = new Timer();
this.timer.Interval = 500;
this.timer.Tick += TimerOnTick;
this.dice = Properties.Resources.Dice;
}
private void TimerOnTick(object sender, EventArgs eventArgs)
{
for (var i = 0; i < currentValues.Length; i++)
{
this.currentValues[i] = this.random.Next(1, 7);
}
this.panel1.Invalidate();
}
private void button1_Click(object sender, EventArgs e)
{
if (this.timer.Enabled)
{
this.timer.Stop();
}
else
{
this.timer.Start();
}
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.White);
if (this.timer.Enabled)
{
for (var i = 0; i < currentValues.Length; i++)
{
e.Graphics.DrawImage(this.dice, new Rectangle(i * 70, 0, 60, 60), 0, (currentValues[i] - 1) * 60, 60, 60, GraphicsUnit.Pixel);
}
}
}
}
}
The source is here if it helps: http://sdrv.ms/Wx2Ets