I am trying to make a Progress Bar on a WinForm work by making two methods public and then calling the methods from another class (see my code example below). But no luck, the bar does not move.
Here are the two classes:
namespace GP_Avantis_Integration
{
//Class B
public partial class GP_Avantis_Integration_Window : Form
{
public GP_Avantis_Integration_Window()
{
InitializeComponent();
}
DataSet ds = new DataSet();
SqlDataAdapter sqlda = new SqlDataAdapter();
SqlCommand sqlcomm = new SqlCommand();
public static int recno;
public void button1_Click(object sender, EventArgs e)
{
try
{
//Fetch data into memory
//Fill in Header table
//Fill in Line table
//Cleaning open connection
//Creating relationship in the Dataset between Header and Line table
// Instantiating and Crearintg Header and Line source
//Binding the Header source to the Header table
//Binding the Line source to the relationship
}
catch (ApplicationException ae)
{
}
finally
{
}
}
public void button2_Click(object sender, EventArgs e)
{
try
{
//Calling CreateJE Class
//Class Method ProcessData
CreateJE JE = new CreateJE(); --------> Calls the Class B
JE.ProcessData(ds);
MessageBox.Show("Complete!");
}
catch (ApplicationException ae)
{
}
finally
{
}
}
public void progress_Bar_setup()
{
progressBar1.Minimum = 0;
progressBar1.Maximum = CreateJE.max;
}
public void progressBar_updates(int recno)
{
progressBar1.Value = recno;
progressBar1.Update();
}
}
// Class B
class CreateJE
{
static public int max;
public void ProcessData (DataSet ds)
{
//Create an eConnect Trx type object
//POPTransactionType po = new POPTransactionType();
// ***** PO Header and Line
int ln;
ln = 0;
//Setting up ProgressBar
int recno = 1;
max = ds.Tables[0].Rows.Count;
GP_Avantis_Integration_Window w = new GP_Avantis_Integration_Window();
w.progress_Bar_setup();
// Create an eConnect PO Header node object
// Create an array for lineitems
foreach (DataRow dtrHDR in ds.Tables["Header"].Rows)
{
//ProgressBar Updates
w.progressBar_updates(recno);
//Instantiating GetJE object
//Retrieves the next JE from GP
//Create an eConnect PO Header node object
//Add the header node to the trx type object
ln = 0;
foreach (DataRow dtrLine in dtrHDR.GetChildRows("HdrLine"))
{
// Populate the elements of the taPoLIne_ItemsTaPOLine XML node
//Avantis Inv Trx Key
// Avantis GL Trx Type
//Add POLine to an Array
ln ++;
}
// Add the header node to the trx type object
// Add the lineitem node to the trx type object
// ***** Process information only
// Create an eConnect document object
// Create a file on the HD
// Serialize using the XmlTextWriter to the file
// Call the eConnectMethods
// Separate Class
// Instantiating the object for eConnectMethods class
// Passing last JRNENTRY retreived using the GetJE class
// so if there is an error on the eConnectEntry Method of eConnectMethods Class
// I can pass the last JE number back to GP
recno++;
}
}
}
}
It's hard to say what is the problem. May be the max value doesn't get initialized, may be the update() method doesn't redraw the progress bar.
One more thing: may be it's better to use the BackgroundWorker, so your UI won't lock? Here's an example: http://www.codeproject.com/Tips/83317/BackgroundWorker-and-ProgressBar-demo
I think you need to invoke the updating with render priority, like this:
public void progressbar_updates(int recno)
{
Dispatcher.Invoke(new Action(() =>
{
progressbar1.Value += recno;
progressbar1.UpdateLayout();
}), DispatcherPriority.Render);
}
Implement a backgroundWorker and use backgroundworker.ReportProgress to update the progressbar. It will then implement your code in one thread and the UI in another thead. Then the form will be responding to updates. Just be cautious when you read and write to the UI (because it is in another thead) Read the UI over when you start the Backgroundworker and write in ReportProgress.
C# Winform ProgressBar and BackgroundWorker
Use backgrounworker.
This is the best example
Click here for BackGroundWorker
I implemented a "Worker Form" which displays progress of background tasks.
Here's my code (WorkerForm)
#region Form
private void frmLoader_Load(object sender, EventArgs e)
{
if (OnExecute != null)
{
OnExecute(this, null);
}
}
#endregion
#region "Public"
public void OnInitialize(object sender, EventArgs<String, int> e)
{
if (pbLoader.InvokeRequired)
{
pbLoader.Invoke(new Action<object, EventArgs<String, int>>(OnInitialize), new Object[] { sender, e });
}
else
{
lblDynamicText.Text = e.Param1;
pbLoader.Step = 1;
pbLoader.Minimum = 1;
pbLoader.Value = 1;
pbLoader.Maximum = e.Param2;
}
}
public void OnCreate(object sender, EventArgs<String> e)
{
if (lblDynamicText.InvokeRequired)
{
lblDynamicText.Invoke(new Action<object, EventArgs<String>>(OnCreate), new Object[] { sender, e });
}
else
{
lblDynamicText.Text = e.Param1;
pbLoader.PerformStep();
}
}
public void OnFinished(object sender, EventArgs<String> e)
{
if (lblDynamicText.InvokeRequired)
{
lblDynamicText.Invoke(new Action<object, EventArgs<String>>(OnFinished), new Object[] { sender, e });
}
else
{
lblDynamicText.Text = e.Param1;
}
}
In my presenter (I'm using a ModelViewPresenter architecture) I inject both a reference to the Winform and the actual worker class.
internal WorkerPresenter(IWorkerView WorkerView,IWorker ConcreteWorker)
{
this.WorkerView = WorkerView;
this.ConcreteWorker = ConcreteWorker;
WorkerView.OnExecute += StartExecute;
}
StartExecute is being raised during the Form Load Event of my Worker Form.
private void StartExecute(Object sender, EventArgs e)
{
ConcreteWorker.OnCreate += Create;
ConcreteWorker.OnFinish += Finished;
ConcreteWorker.OnInitialize += Initialize;
var task = new Task<bool>(ConcreteWorker.Execute);
task.Start();
task.ContinueWith(c_task =>
{
((frmWorker)WorkerView).DialogResult = System.Windows.Forms.DialogResult.OK;
});
}
IWorker interface:
public interface IWorker {
event EventHandler<EventArgs<String>> OnCreate;
event EventHandler<EventArgs<String>> OnFinish;
event EventHandler<EventArgs<String, int>> OnInitialize;
bool Execute();
}
Concrete Worker: OnInitialize => Set a text and the amount of steps in your progress bar. OnCreate => Increments the step counter by one and sets another text.
OnFinish => displays a text and closes the Worker form again.
Related
I'm having trouble figuring out what is wrong with my C# code.
I'm trying to learn how to use ConcurrentQueue class in System.Collections.Concurrent namespace.
In order to do this, I'm creating 2 instances of the same class in different threads, passing to the constructors a different Listbox control.
I am expecting each class instance of EventGenerator to raise events at random intervals, updating the Listbox the were passed with randomly generated number, and adding that number to a ConcurrentQueue which is also passed to the constructor.
In my main thread, is the method to DeQueue the ConcurrentQueue of objects EnQueued to it by both spawned threads.
But what I'm getting is the 2 EnQueue Listboxes displaying the same data and the DeQueue Listbox seeming reporting to have deQueued them both.
I apologize if my description is not good enough, and my code follows, along with a link to an image of my form in case it might better help visualize what I'm trying to do...
Form
public partial class Form1 : Form
{
ConcurrentQueue<int> CQ;
EventGenerator eventGenerator1;
EventGenerator eventGenerator2;
public Form1()
{
InitializeComponent();
CQ = new ConcurrentQueue<int>();
eventGenerator1 = new EventGenerator(CQ, listBox1);
eventGenerator1.OnRandomEvent += new EventGenerator.RandomEventHandler(RandomEvent);
eventGenerator2 = new EventGenerator(CQ, listBox2);
eventGenerator2.OnRandomEvent += new EventGenerator.RandomEventHandler(RandomEvent);
}
private void RandomEvent(object sender, IncomingConnectionEventArgs e)
{
string s = e.Property_Int.ToString()
+ " "
+ e.Property_String;
UpdateListbox(s, e.LB);
}
private void UpdateListbox(string argstring, ListBox argListBox)
{
if (InvokeRequired)
{
Invoke(new Action<string, ListBox>(UpdateListbox), new object[] { argstring, argListBox });
return;
}
int n;
bool b = false;
//do
//{
b = CQ.TryDequeue(out n);
//} while (!b);
argListBox.Items.Add(argstring);
argListBox.SelectedIndex = argListBox.Items.Count -1;
listBoxDeQueue.Items.Add(n.ToString());
listBoxDeQueue.SelectedIndex = listBoxDeQueue.Items.Count - 1;
}
private void button_Start_Click(object sender, EventArgs e)
{
Thread methodThread1 = new Thread(new ThreadStart(TheThread1));
methodThread1.Start();
Thread methodThread2 = new Thread(new ThreadStart(TheThread2));
methodThread2.Start();
}
private void TheThread2()
{
eventGenerator2.Start();
}
private void TheThread1()
{
eventGenerator1.Start();
}
private void button_Stop_Click(object sender, EventArgs e)
{
eventGenerator1.Stop();
eventGenerator2.Stop();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
eventGenerator1.Stop();
eventGenerator2.Stop();
}
}
IncomingConnectionEventArgs
class IncomingConnectionEventArgs : EventArgs
{
public System.Windows.Forms.ListBox LB;
public int Property_Int { get; set; }
public string Property_String { get; set; }
public IncomingConnectionEventArgs(int argInt, string argString,
System.Windows.Forms.ListBox lb)
{
LB = lb;
Property_Int = argInt;
Property_String = argString;
}
}
EventGenerator
class EventGenerator
{
public delegate void RandomEventHandler(
object sender,
IncomingConnectionEventArgs e);
public event RandomEventHandler OnRandomEvent;
public Random r = new Random();
public ListBox listBox;
public bool Generate = true;
public ConcurrentQueue<int> CQ;
public EventGenerator(ConcurrentQueue<int> argCQ, ListBox argListBox)
{
CQ = argCQ;
listBox = argListBox;
}
public void Start()
{
Generate = true;
while (Generate)
{
Thread.Sleep(r.Next(100, 2000));
RandomEvent();
}
}
public void Stop()
{
Generate = false; ;
}
public void RandomEvent()
{
if (OnRandomEvent == null)
{
return;
}
int n = r.Next(1000, 10000);
CQ.Enqueue(n);
IncomingConnectionEventArgs Args =
new IncomingConnectionEventArgs(n, "", listBox);
OnRandomEvent(this, Args);
}
}
The problem is with your use of Random. Unless you use a single instance of Random or explicitly seed each instance differently, the two threads calling Random.Next(...) will typically generate the same values.
A better way to seed your instance is this:
Random r = new Random(Guid.NewGuid().GetHashCode());
I have a global variable called:
string tweet;
I run several background workers, that does nothing but wait on value change of the tweet variable. Then run a function called: ProcessTweet( object sender, MyCustomEventArgs args )
My question is what is the best way to handle the property changed event from all those background workers, and later process the results based on the tweet value and another argument passed to the ProcessTweet function.
I tried to take a look at INotifyPropertyChanged but I am not sure how to handle OnValueChange event from each background worker. Will it run the same ProcessTweet function once or each background worker will run an instance of that function?
EDIT:
private ITweet _LastTweet;
public ITweet LastTweet
{
get { return this._LastTweet; }
set
{
this._LastTweet = value;
}
}
Still not sure how to handle property change event the best way ^
And below is the rest of the code
private void bgworker_DoWork(object sender, DoWorkEventArgs e)
{
MyCustomClass myCustomClass = e.Argument as MyCustomClass;
//here I want to listen on the LastTweet Value Change event and handle it
}
List<BackgroundWorker> listOfBGWorkers = new List<BackgroundWorker>();
private BackgroundWorker CreateBackgroundWorker()
{
BackgroundWorker bgworker = new BackgroundWorker();
//add the DoWork etc..
bgworker.DoWork += new System.ComponentModel.DoWorkEventHandler(bgworker_DoWork);
return bgworker;
}
private void buttonStart_Click(object sender, EventArgs e)
{
for (int i = 0; i < 10; i++)
{
//Create the background workers
var bgworker = CreateBackgroundWorker();
listOfBGWorkers.Add(bgworker);
//get the MYCustomClass value;
var myCustomClass = SomeFunction();
bgworker.RunWorkerAsync(myCustomClass);
}
}
Ok - here's a small console app that demonstrates what I think you're trying to do.
It creates a 'source of tweets' in a thread.
You can subscribe to this 'source' and be notified when a new tweet 'arrives'.
You create TweetHandlers which have internal queues of tweets to process
You subscribe these TweetHandlers to the source
When a new tweet arrives, it is added to the queues of all the TweetHandlers by the event subscription
The TweetHandlers are set to run in their own Tasks. Each TweetHandler has its own delegate for performing a customizable action on a Tweet.
The code is as follows:
interface ITweet
{
object someData { get; }
}
class Tweet : ITweet
{
public object someData { get; set; }
}
class TweetSource
{
public event Action<ITweet> NewTweetEvent = delegate { };
private Task tweetSourceTask;
public void Start()
{
tweetSourceTask = new TaskFactory().StartNew(createTweetsForever);
}
private void createTweetsForever()
{
while (true)
{
Thread.Sleep(1000);
var tweet = new Tweet{ someData = Guid.NewGuid().ToString() };
NewTweetEvent(tweet);
}
}
}
class TweetHandler
{
public TweetHandler(Action<ITweet> handleTweet)
{
HandleTweet = handleTweet;
}
public void AddTweetToQueue(ITweet tweet)
{
queueOfTweets.Add(tweet);
}
public void HandleTweets(CancellationToken token)
{
ITweet item;
while (queueOfTweets.TryTake(out item, -1, token))
{
HandleTweet(item);
}
}
private BlockingCollection<ITweet> queueOfTweets = new BlockingCollection<ITweet>();
private Action<ITweet> HandleTweet;
}
class Program
{
static void Main(string[] args)
{
var handler1 = new TweetHandler(TweetHandleMethod1);
var handler2 = new TweetHandler(TweetHandleMethod2);
var source = new TweetSource();
source.NewTweetEvent += handler1.AddTweetToQueue;
source.NewTweetEvent += handler2.AddTweetToQueue;
// start up the task threads (2 of them)!
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var taskFactory = new TaskFactory(token);
var task1 = taskFactory.StartNew(() => handler1.HandleTweets(token));
var task2 = taskFactory.StartNew(() => handler2.HandleTweets(token));
// fire up the source
source.Start();
Thread.Sleep(10000);
tokenSource.Cancel();
}
static void TweetHandleMethod1(ITweet tweet)
{
Console.WriteLine("Did action 1 on tweet {0}", tweet.someData);
}
static void TweetHandleMethod2(ITweet tweet)
{
Console.WriteLine("Did action 2 on tweet {0}", tweet.someData);
}
}
The output looks like this:
Did action 2 on tweet 892dd6c1-392c-4dad-8708-ca8c6e180907
Did action 1 on tweet 892dd6c1-392c-4dad-8708-ca8c6e180907
Did action 2 on tweet 8bf97417-5511-4301-86db-3ff561d53f49
Did action 1 on tweet 8bf97417-5511-4301-86db-3ff561d53f49
Did action 2 on tweet 9c902b1f-cfab-4839-8bb0-cc21dfa301d5
I have a C# library, inside which there is a timer that keeps checking a boolean variable ProcessFinished. ProcessFinished is initialized as false.
What I want is that the main application needs to watch the variable Status from the library, and a message box should display once this ProcessFinished becomes true.
The problem I had is the message box never display if I simple execute the main application, but it displays if I step in the main application.
Here is the timer_tick code in main application:
public Window1()
{
_fl = new FijiLauncherControl();
this._statusTimer = new System.Windows.Forms.Timer(); // read log 4 times per sec
this._statusTimer.Interval = 125;
this._statusTimer.Tick += new EventHandler(_statusTimer_Tick);
InitializeComponent();
}
void _statusTimer_Tick(object sender, EventArgs e)
{
try
{
if (_fl.ProcessFinished)
{
System.Windows.MessageBox.Show("Process is finished");
_statusTimer.Stop();
}
}
catch (Exception ex)
{
}
}
private void FijiLaucherButton_Click(object sender, RoutedEventArgs e)
{
_statusTimer.Start();
_fl.LaunchFiji();
}
where the _fl is the object of the class from the other library.
Inside the library, the timer code is like this:
public FijiLauncherControl()
{
_ijmFile = "";
_fijiExeFile = "";
_logFile = "";
_outputDir = "";
_isLogOn = false;
_processOn = false;
_processFinished = false;
_headless = true;
_doneStr = "Procedure is finished.";
_logFileCheckTimer = new System.Timers.Timer(500); // read log 4 times per sec
_logFileCheckTimer.Enabled = true;
_logFileCheckTimer.Elapsed += new System.Timers.ElapsedEventHandler(_logFileCheckTimer_Elapsed);
}
void _logFileCheckTimer_Elapsed(object sender, EventArgs e)
{
if (_processOn && IsLogOn)
{
try
{
_processFinished = CheckStatuts();
}
catch (Exception ex)
{
}
}
}
I am wondering what is going on here? Is there anyway I can see the message box shows up without stepping in? What is the right way to watch ProcessFinished from the main application?
Would it not be better to fire an event from the thread and catch it. Then show the message box?
Like this maybe?
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click( object sender, EventArgs e )
{
var logChecker = new LogChecker();
logChecker.FinishedExvent += () => MessageBox.Show( "Finished" );
logChecker.Start();
}
}
internal class LogChecker
{
public void Start()
{
var thread = new Thread( CheckLog );
thread.Start();
}
private void CheckLog()
{
var progress = 0;
while ( progress < 3000 )
{
Thread.Sleep( 250 );
progress += 250;
}
FinishedExvent();
}
public event TestEventHandler FinishedExvent;
}
internal delegate void TestEventHandler();
}
Try
volatile bool _processFinished;
The problem that I am working on deals with out to use a functional Lock, or monitor structure, to provided exclusive access to only one member on separate threads. Below, is my class definition of the monitor (note that it is different than the actual monitor class given by c# library). What I am trying to do is make pictureboxes appear or disappear on my form.
I have attempted to add in an instance of the form so I can access the individual pictureboxes, however, my program seems to freeze.
namespace SmokersProblem
{
class monitor
{
Form1 form = new Form1();
Random num = new Random();
object obj = new object();
public monitor()
{
}
public void agent(){
form.pictureBox4.Visible = false;
int r = num.Next(1, 4);
if (r == 1)
{
// put lighter and paper on table
smoker1();
}
else if (r == 2)
{
// put lighter and tobacco on table
smoker2();
}
else
{
// put paper and tobacco on table
smoker3();
}
}
public void smoker1()
{
//lock (obj)
//{
form.pictureBox9.Visible = true;
form.pictureBox1.Visible = false;
System.Threading.Thread.Sleep(5000);
//agent();
// }
}
public void smoker2()
{
//lock (obj)
//{
form.pictureBox10.Visible = true;
form.pictureBox3.Visible = false;
System.Threading.Thread.Sleep(5000);
//agent();
//}
}
public void smoker3()
{
//lock (obj)
//{
form.pictureBox11.Visible = true;
form.pictureBox2.Visible = false;
System.Threading.Thread.Sleep(5000);
//agent();
// }
}
}
}
Below is my form code, as you can see here, i try to create three seperate threads, one for each smoker.
namespace SmokersProblem
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Random rnd = new Random();
int num = rnd.Next(1, 4);
Object obj = new Object();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
pictureBox1.Visible = true;
pictureBox2.Visible = true;
pictureBox3.Visible = true;
pictureBox8.Visible = false;
pictureBox7.Visible = false;
pictureBox6.Visible = false;
monitor one = new monitor();
one.agent();
Thread vone = new Thread(one.smoker1);
Thread two = new Thread(one.smoker2);
Thread three = new Thread(one.smoker3);
vone.Start();
two.Start();
three.Start();
}
}
}
After implementing this, I went looking for the Smoker Thread Problem that it looks like OP is trying to implement. This code should be easily adaptable to that problem.
The reason your UI is freezing is that you're calling one.agent() without putting it in a new thread. one.agent() sleeps, which keeps your UI from processing events.
OK, I've implemented some code to do the smoker problem with labels. Obviously it could be improved, for instance by not coupling the form to the threads.
I put two different locking mechanisms in, and left one commented out.
Essentially, there are three labels that can either be "Smoking" or "Not Smoking". The main UI thread creates three threads:
Smoker1
Smoker2
Smoker3
Each of the threads continually tries to take the lock in a while loop. When they take the lock, they set their label to "Smoking", wait a few seconds, and then set their label to "Not Smoking". This uses thread safe code from this answer.
public partial class Form1 : Form
{
private bool running = false;
public Label OneLabel { get; set; }
public Label TwoLabel { get; set; }
public Label ThreeLabel { get; set; }
private MyMonitor one;
private Thread vone;
private Thread two;
private Thread three;
public Form1()
{
InitializeComponent();
OneLabel = new Label();
OneLabel.Text = "Not Smoking";
OneLabel.Location = new Point(10, 50);
OneLabel.AutoSize = true;
this.Controls.Add(OneLabel);
TwoLabel = new Label();
TwoLabel.Text = "Not Smoking";
TwoLabel.Location = new Point(150, 50);
this.Controls.Add(TwoLabel);
ThreeLabel = new Label();
ThreeLabel.Text = "Not Smoking";
ThreeLabel.Location = new Point(300, 50);
this.Controls.Add(ThreeLabel);
}
private void MainButton_Click(object sender, EventArgs e)
{
if (!running)
{
vone.Start();
two.Start();
three.Start();
MainButton.Text = "Stop";
running = true;
}
else
{
one.RequestStop();
MainButton.Text = "Run";
running = false;
}
}
private void Form1_Load(object sender, EventArgs e)
{
one = new MyMonitor(this);
vone = new Thread(one.Smoker1);
two = new Thread(one.Smoker2);
three = new Thread(one.Smoker3);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (running)
{
one.RequestStop();
running = false;
}
}
}
class MyMonitor
{
private int x = 1;
private Object obj = new Object();
private Form1 _form;
bool _finished = false;
public MyMonitor(Form1 form)
{
_form = form;
}
public void Smoker1()
{
while (!_finished)
{
//lock (obj)
//{
// _form.OneLabel.SetPropertyThreadSafe(() => _form.OneLabel.Text, "Smoking");
// System.Threading.Thread.Sleep(2000);
// _form.OneLabel.SetPropertyThreadSafe(() => _form.OneLabel.Text, "Not Smoking");
//}
try
{
Monitor.Enter(obj);
try
{
_form.OneLabel.SetPropertyThreadSafe(() => _form.OneLabel.Text, "Smoking");
System.Threading.Thread.Sleep(2000);
_form.OneLabel.SetPropertyThreadSafe(() => _form.OneLabel.Text, "Not Smoking");
}
finally
{
Monitor.Exit(obj);
}
}
catch (SynchronizationLockException SyncEx)
{
Console.WriteLine(SyncEx.Message);
}
}
}
public void Smoker2()
{
while (!_finished)
{
//lock (obj)
//{
// _form.TwoLabel.SetPropertyThreadSafe(() => _form.TwoLabel.Text, "Smoking");
// System.Threading.Thread.Sleep(2000);
// _form.TwoLabel.SetPropertyThreadSafe(() => _form.TwoLabel.Text, "Not Smoking");
//}
try
{
Monitor.Enter(obj);
try
{
_form.TwoLabel.SetPropertyThreadSafe(() => _form.TwoLabel.Text, "Smoking");
System.Threading.Thread.Sleep(2000);
_form.TwoLabel.SetPropertyThreadSafe(() => _form.TwoLabel.Text, "Not Smoking");
}
finally
{
Monitor.Exit(obj);
}
}
catch (SynchronizationLockException SyncEx)
{
Console.WriteLine(SyncEx.Message);
}
}
}
public void Smoker3()
{
while (!_finished)
{
//lock (obj)
//{
// _form.ThreeLabel.SetPropertyThreadSafe(() => _form.ThreeLabel.Text, "Smoking");
// System.Threading.Thread.Sleep(2000);
// _form.ThreeLabel.SetPropertyThreadSafe(() => _form.ThreeLabel.Text, "Not Smoking");
//}
try
{
Monitor.Enter(obj);
try
{
_form.ThreeLabel.SetPropertyThreadSafe(() => _form.ThreeLabel.Text, "Smoking");
System.Threading.Thread.Sleep(2000);
_form.ThreeLabel.SetPropertyThreadSafe(() => _form.ThreeLabel.Text, "Not Smoking");
}
finally
{
Monitor.Exit(obj);
}
}
catch (SynchronizationLockException SyncEx)
{
Console.WriteLine(SyncEx.Message);
}
}
}
public void RequestStop()
{
_finished = true;
}
}
//Thread Safe Extension Method
public static class Extensions
{
private delegate void SetPropertyThreadSafeDelegate<TResult>(Control #this, Expression<Func<TResult>> property, TResult value);
public static void SetPropertyThreadSafe<TResult>(this Control #this, Expression<Func<TResult>> property, TResult value)
{
var propertyInfo = (property.Body as MemberExpression).Member as PropertyInfo;
if (propertyInfo == null ||
!#this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
#this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null)
{
throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
}
if (#this.InvokeRequired)
{
#this.Invoke(new SetPropertyThreadSafeDelegate<TResult>(SetPropertyThreadSafe), new object[] { #this, property, value });
}
else
{
#this.GetType().InvokeMember(propertyInfo.Name, BindingFlags.SetProperty, null, #this, new object[] { value });
}
}
}
my program seems to freeze
My one.agent() is the part of the code that allows one of the smokers
to be called on, so they can smoke. Why wouldnt I want it in the main
code?
Because you shouldn't be using Sleep() from the main UI thread, which is what happens when you call one.agent() from the Button Click event. When Sleep(5000) is hit, you're telling the forms main UI thread to not do ANYTHING for five seconds, thus the freezing you're seeing.
To fix this, you'd need agent() to execute smoker1(), smoker2(), or smoker3() in a separate thread like you're doing down below.
There are several other problems with the code, however, that must also be addressed before you can "fix" your code...
The next problem lies in you creating a new instance of Form1() inside your monitor() class. This instance of Form1() is not the same one that is visible on your screen. Acting upon it is modifying an invisible form that has never even been shown. To act upon the form that is actually visible on your screen would require you to either (a) pass a reference to it into your monitor() class when you create it, or (b) have your monitor() class raise custom events that Form1() subscribes to, again when it creates monitor().
The last problem lies in you attempting to change UI controls from within a thread other than the main UI thread. This will result in a cross-thread exception (unless you've turn this off, which you shouldn't). There are various ways to overcome this problem, the most basic of which involves using delegates and the Invoke() method of the Form/Control to which you are trying to update.
I ve read lots of information about this stuff and i tried this:
class Server
{
...
public Server(int Port, ListBox ex_lb, PictureBox ex_pb)
{
ServerWork = new Thread(() => ServerFunction(Listener, ex_lb, ex_pb));
ServerWork.Start();
}
static void ServerFunction(TcpListener ex_listener, ListBox ex_lb, PictureBox ex_pb)
{
//and any access to ex_lb throws exception, didnt debug to access to ex_pb
}
}
this:
private static IncomingDataClass g_IDC = new IncomingDataClass();
private class IncomingDataClass
{
static string data = "";
public string Data
{
get { return data; }
set {
data = value;
SomeEvent(this,null,data);
}
}
}
void IncomingDataClass_SomeEvent(object sender, EventArgs e, string ex_data)
{
if (ex_data.Contains("listbox"))
{
ex_data = ex_data.Remove(ex_data.IndexOf("listbox"), "listbox".Length);
listBox1.Items.Add(ex_data);
}
}
delegate void MyEventHandler(object sender, EventArgs e, string ex_data);
static event MyEventHandler SomeEvent;
// in form load event
SomeEvent += IncomingDataClass_SomeEvent;
class Server
{
...
public Server(int Port, ListBox ex_lb, PictureBox ex_pb)
{
ServerWork = new Thread(() => ServerFunction(Listener));
ServerWork.Start();
}
static void ServerFunction(TcpListener ex_listener)
{
//and any change of g_IDC.Data throws exception here
}
}
this:
private static ListBox listBox1 = new ListBox();
private void Form1_Load(object sender, EventArgs e)
{
...
listBox1.FormattingEnabled = true;
listBox1.Location = new System.Drawing.Point(12, 256);
listBox1.Name = "listBox1";
listBox1.Size = new System.Drawing.Size(258, 108);
listBox1.TabIndex = 6;
Controls.Add(listBox1);
}
//anyways, even if i create new ListBox lb = listBox1 in ServerFunction(..), it throws System.InvalidOperationException => Access attempt to listBox1 not from thread where is was created.
What do i do wrong? I thought that creating static control is an ultimate problem solve for this, but even this doesnt work...
If I remember correctly in order to update the UI from another thread you need to use this invocation:
this.Invoke((MethodInvoker)delegate
{
MethodForUpdatingUI();
});
This will start MethodForUpdatingUI() from the UI thread, enabling you to access the controls.
So, for use this in your code I'll try to change IncomingDataClass_SomeEvent:
void IncomingDataClass_SomeEvent(object sender, EventArgs e, string ex_data)
{
this.Invoke((MethodInvoker)delegate
{
UpdateListBox(ex_data);
});
}
UpdateListBox(string ex_data)
{
if (ex_data.Contains("listbox"))
{
ex_data = ex_data.Remove(ex_data.IndexOf("listbox"), "listbox".Length);
listBox1.Items.Add(ex_data);
}
}
Please feel free to correct me if this is not the case you are asking for or this is a bad pratice. I've not tested the code.
You can only manipulate UI controls from UI thread. It doesn't matter if those controls are static or not, afaik. Use Dispatcher object or SynchronizationContext object to modify controls from different thread.