I have been breaking my head over this for a long time now and I can't seem to find how to solve this. Simply put I need to present Parallel programming to my class and i am making a demo that clearly shows the diffrence between single threaded and multi threaded.
First the program takes a screenshot and loads that in this class into a bitmap.
Then it loads the winform with a picturebox called screenshot. Next it does Form1_Load and loads the screenshot into the picturebox.
Then Form1_Shown Runs the for loop which just scrambles the pixels around based on a randomizer and updates the image from the left to the right.
example: http://gyazo.com/ab04583bb33de59d08407886da1c4870
This all works.
I now want to make it so that the screenshot is being updated from the left to the middle by the first thread, and from the middle to the right with the second thread.
But when i put it in 2 separate threads it says "An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll"
The error implies I am making illegal cross thread calls. Particulary on screenshot and probably also on mybitmap.
Visual studio helps me by linking me to "How to: Make Thread-Safe Calls to Windows Forms Controls"
But i am not getting any wiser out of this information which is probably because i am not that fluent with the C# terminology yet.
How should i approach/fix this ?
This is the class where everything happens (except taking the screenshot):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ScreenOutput
{
public partial class Form1 : Form
{
PictureBox screenshot;
Bitmap myBitmap = new Bitmap(#".\screenshot.jpg");
public Form1()
{
InitializeComponent();
}
private int Xcount;
private int Ycount;
private int maxXValue = Screen.PrimaryScreen.Bounds.Width - 1;
private int maxXValueT1 = 960;
private int maxXValueT2 = 1919;
private int maxYValue = Screen.PrimaryScreen.Bounds.Height - 1;
private int maxYValueT1 = 539;
private int maxYValueT2 = 1079;
private void Form1_Load(object sender, EventArgs e)
{
screenshot.Image = myBitmap;
}
private void Form1_Shown(object sender, EventArgs e)
{
Thread startThread1 = new Thread(new ThreadStart(thread1));
Thread startThread2 = new Thread(new ThreadStart(thread2));
startThread1.Start();
startThread2.Start();
Thread.Sleep(10000); //waiting for completion
startThread1.Abort();
startThread2.Abort();
//this is how it would work without multithreading
/*Random random = new Random();
for (Xcount = 0; Xcount < maxXValue; Xcount++)
{
screenshot.Refresh();
for (Ycount = 0; Ycount < maxYValue; Ycount++)
{
int calculatedX = Xcount + random.Next(0, maxXValue);
if (calculatedX > maxXValue) calculatedX = maxXValue;
myBitmap.SetPixel(Xcount, Ycount, myBitmap.GetPixel(calculatedX, Ycount));
}
}
Thread.Sleep(2000);*/
Application.Exit();
}
public void thread1()
{
Random random = new Random();
for (Xcount = 0; Xcount < maxXValueT1; Xcount++)
{
screenshot.Refresh();
for (Ycount = 0; Ycount < maxYValueT1; Ycount++)
{
int calculatedX = Xcount + random.Next(0, maxXValueT1);
if (calculatedX > maxXValue) calculatedX = maxXValueT1;
myBitmap.SetPixel(Xcount, Ycount, myBitmap.GetPixel(calculatedX, Ycount));
}
}
}
public void thread2()
{
Random random = new Random();
for (Xcount = 0; Xcount < maxXValueT2; Xcount++)
{
screenshot.Refresh();
for (Ycount = 0; Ycount < maxYValueT2; Ycount++)
{
int calculatedX = Xcount + random.Next(0, maxXValueT2);
if (calculatedX > maxXValueT2) calculatedX = maxXValueT2;
myBitmap.SetPixel(Xcount, Ycount, myBitmap.GetPixel(calculatedX, Ycount));
}
}
}
}
}
You will need to Invoke the GUI thread.
I prefer the following solution.
Using a Form member ('screenshot' in this case) check to see if InvokeRequired = true. If so then Invoke a delegate using that members BeginInvoke() function as the following example demonstrates:
private void ScreenshotRefresh()
{
if(screenshot.InvokeRequired)
{
screenshot.BeginInvoke(new MethodInvoker(this.ScreenshotRefresh));
}
else
{
screenshot.Refresh();
}
}
Related
I have a class project to create a tic tac toe simulations (game is not played by people) where O's and X's automatically generate when the New Game button is clicked. I am having trouble with the code to get the labels to show the output.
Using a 2D array type INT to simulate the game board, it should store a 0 or 1 in each of the 9 elements and produce a O or X. There also needs to be a label to display if X or O wins.
Here is my code so far ( I know there isn't much, I'm completely lost):
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void StartButton_Click(object sender, EventArgs e)
{
Random rand = new Random();
const int ROWS = 3;
const int COLS = 3;
int[,] gameBoard = new int[ROWS, COLS];
for (int row = 0; row < ROWS; row++)
{
for (int col = 0; col < COLS; col++)
{ gameBoard[row, col]= rand.Next(2); }
}
}
private void ExitButton_Click(object sender, EventArgs e)
{
this.Close();
}
}
}
Win Forms labels get populated unless the current event process finishes completely. If you want to update the labels with X and O while you may use the control property InvokeRequired (boolean) and after assigning the value to label call label.Refresh() function. I will suggest fork a thread on hitting start button and do the for loop -> random.next() inside the thread. Try with these changes. All the Best!!
Try following :
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;
namespace WindowsFormsApplication4
{
public partial class Form1 : Form
{
const int ROWS = 3;
const int COLS = 3;
const int WIDTH = 100;
const int HEIGHT = 100;
const int SPACE = 20;
static TextBox[,] gameBoard;
public Form1()
{
InitializeComponent();
gameBoard = new TextBox[ROWS, COLS];
for (int row = 0; row < ROWS; row++)
{
for (int col = 0; col < COLS; col++)
{
TextBox newTextBox = new TextBox();
newTextBox.Multiline = true;
this.Controls.Add(newTextBox);
newTextBox.Height = HEIGHT;
newTextBox.Width = WIDTH;
newTextBox.Top = SPACE + (row * (HEIGHT + SPACE));
newTextBox.Left = SPACE + (col * (WIDTH + SPACE));
gameBoard[row, col] = newTextBox;
}
}
}
}
}
I am stuck at trying to call a procedure and use some parameters in a new thread in C#. There is my 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 System.Threading;
namespace Random_colored_rectangles
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Thread th;
Random rand;
System.Drawing.Color[] colors = new System.Drawing.Color[5] {Color.Orange, Color.Red, Color.Pink, Color.Black, Color.Gold };
private void DrawColor(Color color)
{
for (int i = 0; i < 100; i++)
{
DrawRectangle(color, 3 , rand.Next(0, this.Width), rand.Next(0, this.Height), 10, 10);
Thread.Sleep(100);
}
MessageBox.Show(color + " done");
}
private void DrawRectangle(Color barva, float width, int pos_x, int pos_y, int size_x, int size_y)
{
Pen myPen = new Pen(barva, width);
Graphics formGraphics;
formGraphics = plocha.CreateGraphics();
formGraphics.DrawRectangle(myPen, new Rectangle(pos_x, pos_y, size_x, size_y));
myPen.Dispose();
formGraphics.Dispose();
}
private void Form1_Load(object sender, EventArgs e)
{
rand = new Random();
}
private void Form1_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == (char)Keys.R)
{
th = new Thread(DrawColor);
th.Name = Convert.ToString(threadCount);
threadList.Add(th);
threadCount = threadCount + 1;
th.Start(colors[rand.Next(0, colors.Length)]);
}
}
}
}
This code should (after pressing R) make 100 random colored rectangles (the color is chosen from an array of few colors). But, I am unable to make my thread start the procedure DrawColor with a parameter of the random color select.
Can you please help me?
You could do it by using a Task.
Color theColorToPass = someColor;
Task.Factory.StartNew(color => {
DrawColor(color);
}, theColorToPass);
You could aswell access the array directly from within the Task though. I see no point in passing it to the Thread.
Using the advice of those more familiar with the do's and don'ts of C# (that is not me trying to sound mean or anything), I have devised a way to accomplish what you want without the need of CreateGraphics() or Thread.Sleep(). Unfortunately, this throws an OutOfMemoryException when it hits e.Graphics.DrawRectangle(penToUse, rectanglesToUse[0]);. What does that mean?
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace Colors
{
public partial class Form1 : Form
{
Timer timer = new Timer { Interval = 100 };
Random rand = new Random();
Color[] colors = new Color[5]
{
Color.Black,
Color.Blue,
Color.Green,
Color.Purple,
Color.Red
};
List<Pen> usedPens = new List<Pen>();
List<Rectangle> usedRectangles = new List<Rectangle>();
Pen penToUse;
List<Rectangle> rectanglesToUse = new List<Rectangle>();
public Form1()
{
InitializeComponent();
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
if (e.KeyChar == (char)Keys.Enter)
{
penToUse = new Pen(GetRandomColor(), 3);
rectanglesToUse.Clear();
for (int i = 0; i < 100; i++)
rectanglesToUse.Add(GetRandomRectangle());
this.Refresh();
}
}
private Color GetRandomColor()
{
return colors[rand.Next(0, colors.Length)];
}
private Rectangle GetRandomRectangle()
{
return new Rectangle(rand.Next(0, Width), rand.Next(0, Height), 10, 10);
}
protected override void OnPaint(PaintEventArgs e)
{
for (int i = 0; i < usedRectangles.Count; i++)
e.Graphics.DrawRectangle(usedPens[i % 100], usedRectangles[i]);
timer.Tick += delegate
{
if (rectanglesToUse.Count > 0)
{
e.Graphics.DrawRectangle(penToUse, rectanglesToUse[0]);
usedRectangles.Add(rectanglesToUse[0]);
rectanglesToUse.RemoveAt(0);
}
else
{
usedPens.Add(penToUse);
timer.Stop();
}
};
timer.Start();
}
}
}
And my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace inform
{
public partial class Form1 : Form
{
public static TabPage[] TabPages = new TabPage[20];
public static RichTextBox[] TextBoxes = new RichTextBox[20];
public Form1()
{
InitializeComponent();
TabControl.TabPages.Clear();
for (int x = 0; x < 19; x++)
{
TabPages[x].Controls.Add(TextBoxes[x]); //ERROR HERE
//Object reference not set to an instance of an object.
TabControl.TabPages.Add(TabPages[x]);
}
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
}
I am trying to make a basic typing program that uses a tabcontrol to organize each richtextbox in an array. But when I run the program it returns
Object reference not set to an instance of an object.
I have made an array of RichTextBoxes and also TabPages which can both hold 20 elements(is that the right word?) but a problem occurs. The function for Control.Add() is suppose to take a control value.
The for loop is meant to go through each TabPage and add the correct RichTextBox to it.
I have gone onto MSDN to see what they have but all they have is
tabPage1.Controls.Add(new Button());
instead of my:
TabPages[x].Controls.Add(TextBoxes[x]);
But even then it does not work, I have done this before but without the the array, the last one I did capped out at 6 tabs and I wanted to make more.
I tried reading some pages on the internet but nothing seemed to work, I would be grateful for any help.
you have to write something like this
public partial class Form1 : Form
{
public static TabPage[] TabPages = new TabPage[20];
public static RichTextBox[] TextBoxes = new RichTextBox[20];
public Form1()
{
InitializeComponent();
tabControl1.TabPages.Clear();
for (int x = 0; x < 19; x++)
{
TabPages[x] = new TabPage();
TabPages[x].Controls.Add(TextBoxes[x]); //ERROR HERE
//Object reference not set to an instance of an object.
tabControl1.TabPages.Add(TabPages[x]);
}
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
try this
for (int a = 0; a < 20;a++ )
{
RichTextBox textBox = new RichTextBox();
TextBoxes[a] = textBox;
TabPage tabPage = new TabPage();
TabPages[a] = tabPage;
}
for (int x = 0; x < 19; x++)
{
TabPages[x].Controls.Add(t);
TabControl.TabPages.Add(TabPages[x]);
}
Im working on a school project where I need to do some temperature measurements. The task is to randomly create some temperatures and then calculate the average. My code is as following, but I have a problem with the Threads. It can't be called a object before a window handle is created. I searched the net and found out that a background worker is more useful for updating the UI. I'm not that skilled in programming yet, because i just started school.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Collections;
using System.IO;
using System.Threading;
namespace Temperatur
{
public partial class Form1 : Form
{
static Random rnd = new Random();
static ArrayList tempList = new ArrayList();
static SemaphoreSlim w, e, b;
public Form1()
{
InitializeComponent();
w = new SemaphoreSlim(1);
e = new SemaphoreSlim(0);
b = new SemaphoreSlim(6);
Thread t1 = new Thread(randomNr);
t1.Start();
Thread t2 = new Thread(gennemsnit);
t2.Start();
}
public void randomNr()
{
//Thread.Sleep(100);
for (int i = 0; i < 10; i++)
{
//b.Wait();
//w.Wait();
int number = rnd.Next(36, 42);
tempList.Add(number);
listBox1.BeginInvoke((MethodInvoker)delegate
{
listBox1.Items.Add(number);
});
//w.Release();
//e.Release();
}
}
public void gennemsnit()
{
double avg = 0;
double nb = 0;
//Thread.Sleep(200);
for (int i = 0; i < tempList.Count; i++) //i < tempList.Count
{
//e.Wait();
//w.Wait();
nb += Convert.ToDouble(tempList[i]);
avg = nb / tempList.Count;
listBox2.Invoke((MethodInvoker)delegate //listbox2.invoke
{
listBox2.Items.Add(avg);
});
//w.Release();
//b.Release();
}
}
}
}
It can't be called a object before a window handle is created.
Don't start the threads in the constructor of the Form. Use either the Load() or Shown() events instead:
public Form1()
{
InitializeComponent();
w = new SemaphoreSlim(1);
e = new SemaphoreSlim(0);
b = new SemaphoreSlim(6);
this.Load += new EventHandler(Form1_Load);
}
void Form1_Load(object sender, EventArgs e)
{
Thread t1 = new Thread(randomNr);
t1.Start();
Thread t2 = new Thread(gennemsnit);
t2.Start();
}
BackgroundWorker is indeed what you need. If you "report progress" you can pass an object to the GUI thread in the progress report. Set up progress reporting like this:
BackgroundWorker bw = new BackgroundWorker();
...
bw.WorkerReportsProgress = true;
bw.DoWork += bw_DoWork;
bw.ProgressChanged += bw_ProgressChanged;
...
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Here you have passed yourself any object you like. Could be your own class. Could be a string, etc.
MyClass myObject = e.UserState as MyClass;
// Then you can add it to your GUI as necessary, for example
listbox2.Items.Add(myObject);
}
You probably just want to pass a string as your object and then add that string to your listbox.
I'm not sure what your code is trying to do, but your task is quite easy. You don't really have to worry about threads. For simplicity you could schedule a timer to run every say 1s and pick a random number / read temperature and update it on the UI. If you select the timer from the forms controls (System.Windows.Forms.Timer) then you can directly update the UI from the function it calls when it fires. If you use a timer from System.Timers.Timer, you should use BeginInvoke to update the list.
I am doing this lab out of a book on my own, and I made an application in which sharks are racing. There is a radio button that should update a label on the right dynamically, as well as a button that actually starts the race. Everything used to work and then I renamed a few things, and now nothing works.
Screenshot of application:
image http://cl.ly/f08f4e22761464e0c2f3/content
Form Class:
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;
namespace project1
{
public partial class Game : Form
{
private Shark[] sharks;
private Guy[] guys;
private Guy selectedGuy;
public Game()
{
InitializeComponent();
Random moreRandom = new Random();
int start = myTrack.Location.X;
int finish = myTrack.Width - 65;
sharks = new Shark[4]
{
new Shark() {myRandom = moreRandom, myPictureBox = myShark1, myPBStart = start, trackLength = finish},
new Shark() {myRandom = moreRandom, myPictureBox = myShark2, myPBStart = start, trackLength = finish},
new Shark() {myRandom = moreRandom, myPictureBox = myShark3, myPBStart = start, trackLength = finish},
new Shark() {myRandom = moreRandom, myPictureBox = myShark4, myPBStart = start, trackLength = finish}
};
guys = new Guy[3]
{
new Guy() {myName="Joe", cash=50, myRadioButton=rbGuy1, myLabel=labelBet1},
new Guy() {myName="Bob", cash=75, myRadioButton=rbGuy2, myLabel=labelBet2},
new Guy() {myName="Al", cash=45, myRadioButton=rbGuy3, myLabel=labelBet3}
};
selectedGuy = guys[0];
rbGuy1.Tag = guys[0];
rbGuy2.Tag = guys[1];
rbGuy3.Tag = guys[2];
updateGui();
}
private void myChanged(object sender, EventArgs e)
{
selectedGuy = getSelectedGuy(sender);
betterLabel.Text = selectedGuy.myName;
}
private void betAmountValue(object sender, EventArgs e)
{
updateMin();
}
private void Bet_Click(object sender, EventArgs e)
{
int bet = (int) betAmount.Value;
int myFish = (int) sharkNumber.Value;
selectedGuy.placeBet(bet, myFish);
updateGui();
}
private void raceBtn_Click(object sender, EventArgs e)
{
betBtn.Enabled = false;
bool noWinner = true;
while(noWinner)
{
for (int dogFish = 0; dogFish < sharks.Length; dogFish++)
{
Application.DoEvents();
if(sharks[dogFish].Swim())
{
showWinner(dogFish);
collectBets(dogFish);
noWinner = false;
}
}
}
updateGui();
betBtn.Enabled = true;
}
private void showWinner(int fish)
{
MessageBox.Show(string.Format("Winner Winner People Dinner! \nShark {0} won!", fish + 1));
}
private void collectBets(int fish)
{
for (int guyNumber = 0; guyNumber < guys.Length; guyNumber++)
{
guys[guyNumber].collect(fish + 1);
guys[guyNumber].resetBet();
}
}
private void updateMin()
{
minBetLabel.Text = string.Format("Minimum bet: 5 bucks", betAmount.Value);
}
private Guy getSelectedGuy(object sender)
{
RadioButton rb = (RadioButton)sender;
return (Guy)rb.Tag;
}
private void updateGui()
{
for (int guyNumber = 0; guyNumber < guys.Length; guyNumber++)
{
guys[guyNumber].updateLabels();
}
for (int fish = 0; fish < sharks.Length; fish++)
{
sharks[fish].startPosition();
}
updateMin();
}
}
}
Shark Class:
using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace project1
{
public class Shark
{
public int myPBStart; // Where the PictureBox starts
public int trackLength; // How long the racetrack is
public PictureBox myPictureBox = null; // The PictureBox object
public int location = 0; // My location on the racetrack
public Random myRandom; // An instance of Random
public Shark()
{
location = 0;
myPictureBox = new PictureBox();
myRandom = new Random();
trackLength = 100;
myPBStart = 0;
}
public bool Swim()
{
int distance = myRandom.Next(1, 4);
location += distance;
movePB(distance);
return location > trackLength;
}
private void movePB(int distance)
{
Point p = myPictureBox.Location;
p.X += distance;
myPictureBox.Location = p;
}
public void startPosition()
{
location = myPBStart;
Point p = myPictureBox.Location;
p.X = location;
myPictureBox.Location = p;
}
}
}
I can supply more resources if needed, but this is the main gist of it.
Using Visual Studio, make sure of the following:
1) For each radio button, verify the CheckedChanged event is hooked up to your myChanged function.
2) Verify the "Bets" Button.Click event is hooked up to your Bet_Click function.
3) Verify the "Race!" Button.Click event is hooked up to your raceBtn_Click function.
A safe way to rename things is to right click on the variable name, Refactor, Rename. This will ensure any references to the variable are renamed properly
when you renamed them you probably did it by editing the code rather than by changing the control properties.
THe Winforms designer in VS created code for you behind the scenes that wires the invents up. This codes uses the control names. Look for a file called formname_designer.cs. Notice that there are lines that still have the old control names. You can change this code
This is why its a good habit to give controls nice names when you start.
Make sure that the events on your controls are still connected to the correct event handlers in your code. Sometimes when you rename things, this link can get broken.