Read multiple stackoverflow, codeproject solution, could not integrate to my problem.
Have a datagrid in a usercontrol which is loaded in a window. Each DataRow in the DataGrid represents a timer setting.
Like:
timer name : Test 1 , Timer : 1h 3m
timer name : Test 2 , Timer : 2h 2m
timer name : Test 3 , Timer : 3h 1m
Selecting a row, clicking on the button Start, Starts the timer of that row. And with dispatcher tick event, it updates the grid I have done till this. Now I have to start another(or two or ...) timer which will do the same at the same time. I am stuck on this. Let me share what I have tried!
btnStartClickEvent in mainwindow.xaml.cs
if (btnStart.Content.ToString() == "Start")
{
if (_AUC == ActiveUserControl.Grid)
{
runningRow = (TaskGridData)_TG.dgEmployee.SelectedItem;
if (runningRow != null)
{
currentlyRunningID.Add(runningRow.ID);
btnStart.Content = "Stop";
//worker.RunWorkerAsync(runningRow);
StartTimer(runningRow);
}
}
}
else if (btnStart.Content.ToString() == "Stop")
{
btnStart.Content = "Start";
StopTimer();
}
private DateTime TimerStart { get; set; }
private void StartTimer(TaskGridData tgd)
{
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 1, 0);
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
TimerStart = DateTime.Now;
dispatcherTimer.Start();
//worker.RunWorkerAsync();
//string etime = DateTime.Now.Second.ToString();
}
private void StopTimer()
{
dispatcherTimer.Stop();
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
var currentValue = DateTime.Now - TimerStart;
runningRow.Duration = DurationValueToString(currentValue);
temp = (List<TaskGridData>)_TG.dgEmployee.ItemsSource;
foreach (TaskGridData item in temp)
{
if (item.ID == runningRow.ID)
{
item.Duration = DurationValueToString(DurationStringToVlaue(item.Duration) - DurationStringToVlaue(runningRow.Duration));
break;
}
}
//_TG.dgEmployee.ItemsSource = null;
//_TG.dgEmployee.ItemsSource = temp;
Thread NewThreadforStartProcessAfterTraining = new Thread(() => UpdateGrid());
NewThreadforStartProcessAfterTraining.IsBackground = true;
NewThreadforStartProcessAfterTraining.SetApartmentState(ApartmentState.STA);
NewThreadforStartProcessAfterTraining.Start();
}
private void UpdateGrid()
{
this.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
{
_TG.dgEmployee.ItemsSource = null;
_TG.dgEmployee.ItemsSource = temp;
}));
}
I know this code is for single timer. If I click a 2nd row and try to start timer, then it gets error in tick event, running row is found null.
I am wondering how can I keep this code and make it work for multiple timer. May be multithreading. A guide to do that, will be very helpful.
Thread NewThreadforStartProcessAfterTraining = new Thread(() => UpdateGrid());
NewThreadforStartProcessAfterTraining.IsBackground = true;
NewThreadforStartProcessAfterTraining.SetApartmentState(ApartmentState.STA);
NewThreadforStartProcessAfterTraining.Start();
All the above part where you start a new STA thread is unneeded and wrong in this context, since you can't update the visual tree in this way.
You can find a correct example of using a STA thread in one of my previous answers: https://stackoverflow.com/a/42473167/6996876
Try to understand the concept of thread affinity in WPF.
You simply need an UpdateGrid() where you have to delegate UI work to the dispatcher.
Furthermore, passing an argument to the Tick event is already explained here: https://stackoverflow.com/a/16380663/6996876
In your case you may want to change the current unique runningRow so that it's passed to the event instead.
Related
I was trying to update status on UI for a Long Running Operating. I've created a demo form based application, task it have multiple rows, each row is having days and default values in each column of datagrid is 0, once computation file computes one iteration for one day it will update UI and set 1 for that day.
I am using threading, delegates and events to implement this and it is working as expected if I put Thread.Sleep(100) between two event calls. If I put "Thread.Sleep(100)" inside last nested for loop then it updates UI as expected but as soon as I remove it and run loop without sleep, then it skips some of the columns on UI and directly update last few/random columns, as you can see in attached image link(Image of output of my code without thread sleep) only last column is getting updated.
If I am not mistaken all the events are getting fired in sequence then they should update UI in sequence too but it's not happening and I don't know why. I don't want to do this Sleep thing because I have around 14 calls in actual application for UI status update and it will run under a loop so if It put sleep(100) then it will cost me a lot, is there any way to do it without SLEEP?
Image of output of my code without thread sleep
public class Class1 : IGenerate
{
public event MessageEventHandler OnMessageSending;
public void LongOperationMethod(BindingList<Status> _statusData)
{
if (OnMessageSending != null)
{
MessageEventArgs me = new MessageEventArgs();
/// Loop for 2-3 Weeks
for (; ; ){
/// Loop for 7 day
for (; ; )
{
/// Calculation on everyday
for (int j = 0; j != 1000; ++j)
{
// to do
}
me.weekNo = k;
me.DayNo = i;
OnMessageSending(me);
}
}
me.Message = "Process completed successfully...";
OnMessageSending(me);
}
else
{
throw new ArgumentException("Event hasn`t been rised, so we cannot continue working.");
}
}
}
**UI file:**
<pre><code>
public partial class Form1 : Form
{
BindingList<Status> _statusData = new BindingList<Status>();
delegate void StringParameterDelegate(string value);
Class1 cls = new Class1();
public Form1()
{
InitializeComponent();
labelProgress.Text = "";
}
private void button1_Click_1(object sender, EventArgs e)
{
for (int i = 1; i <= 2; ++i)
{
_statusData.Add(new Status { Week = "Week" + i, Day1 = 0, Day2 = 0, Day3 = 0, Day4 = 0, Day5 = 0, Day6 = 0, Day7 = 0 });
}
dataGridView1.DataSource = _statusData;
}
private void button2_Click(object sender, EventArgs e)
{
Thread t1 = new Thread(() => StartingThread(_statusData));
t1.Start();
}
void StartingThread(BindingList<Status> _statusData)
{
IGenerate generate = new Class1();
generate.OnMessageSending += new MessageEventHandler(generate_OnMessageSending);
generate.LongOperationMethod(_statusData);
}
private void generate_OnMessageSending(MessageEventArgs e)
{
int weekNo = e.weekNo;
int dayNo = e.DayNo;
this.dataGridView1.BeginInvoke(new MethodInvoker(() => dataGridView1.Rows[e.weekNo].Cells[e.DayNo + 1].Value = 1));
this.labelProgress.BeginInvoke(new MethodInvoker(() => this.labelProgress.Text = e.Message));
}
}
</code></pre>
It looks like you are sending the same instance of MessageEventArgs every time, and just updating that one instance on the background thread. This means that your event handler on the UI thread will retrieve the exact same instance of MessageEventArgs that is being updated in the loop! By the time your UI handler gets the MessageEventArgs, its .weekNo and .DayNo properties could well have been modified by the next iteration of the loop, since they are running on separate threads.
To fix this, create a new instance of MessageEventArgs every time you call OnMessageSending().
Relevant snippet:
MessageEventArgs me = new MessageEventArgs();
me.weekNo = k;
me.DayNo = i;
OnMessageSending(me);
I want a DispatcherTimer to restart everytime the conditions are not met. Only when the if-condition is met for 5 seconds, the method can continue.
How should I stop the Dispatchertimer? The timeToWait variable is set to 3000, that works as intended.
Below is the code in C#. It is not responding as I want. It only starts, but never stops or restarts. I am making a WPF application.
dispatcherTimerStart = new DispatcherTimer();
if (average >= centerOfPlayingField - marginOfDetection && average <= centerOfPlayingField + marginOfDetection)
{
dispatcherTimerStart.Interval = TimeSpan.FromMilliseconds(timeToWait);
dispatcherTimerStart.Tick += new EventHandler(tick_TimerStart);
startTime = DateTime.Now;
dispatcherTimerStart.Start();
} else
{
dispatcherTimerStart.Stop();
dispatcherTimerStart.Interval = TimeSpan.FromMilliseconds(timeToWait);
}
private void tick_TimerStart(object sender, EventArgs args)
{
DispatcherTimer thisTimer = (DispatcherTimer) sender;
thisTimer.Stop();
}
you need to preserve the dispatcherTimer that enter your if block because in your else block you are stopping the new instance of DispatcherTimer not the one that entered the if block.
take a class level field
DispatcherTimer preservedDispatcherTimer=null;
var dispatcherTimerStart = new DispatcherTimer();
if (average >= centerOfPlayingField - marginOfDetection && average <= centerOfPlayingField + marginOfDetection)
{
**preservedDispatcherTimer = dispatcherTimerStart;**
dispatcherTimerStart.Interval = TimeSpan.FromMilliseconds(timeToWait);
dispatcherTimerStart.Tick += new EventHandler(tick_TimerStart);
startTime = DateTime.Now;
dispatcherTimerStart.Start();
}
//use preservedDispatcherTimer in else
else if(preservedDispatcherTimer!=null)
{
preservedDispatcherTimer.Stop();
preservedDispatcherTimer.Interval = TimeSpan.FromMilliseconds(timeToWait);
}
I want to make an image viewer in C# Visual Studio 2010 which displays images one by one after seconds:
i = 0;
if (image1.Length > 0) //image1 is an array string containing the images directory
{
while (i < image1.Length)
{
pictureBox1.Image = System.Drawing.Image.FromFile(image1[i]);
i++;
System.Threading.Thread.Sleep(2000);
}
When the program starts, it stops and just shows me the first and last image.
Thread.Sleep blocks your UI thread use System.Windows.Forms.Timer instead.
Use a Timer.
First declare your Timer and set it to tick every second, calling TimerEventProcessor when it ticks.
static System.Windows.Forms.Timer myTimer = new System.Windows.Forms.Timer();
myTimer.Tick += new EventHandler(TimerEventProcessor);
myTimer.Interval = 1000;
myTimer.Start();
Your class will need the image1 array and an int variable imageCounter to keep track of the current image accessible to the TimerEventProcessor function.
var image1[] = ...;
var imageCounter = 0;
Then write what you want to happen on each tick
private static void TimerEventProcessor(Object myObject, EventArgs myEventArgs) {
if (image1 == null || imageCounter >= image1.Length)
return;
pictureBox1.Image = Image.FromFile(image1[imageCounter++]);
}
Something like this should work.
Yes, because Thread.Sleep blocks the UI thread during the 2s.
Use a timer instead.
If you want to avoid using Timer and defining an event handler you can do this:
DateTime t = DateTime.Now;
while (i < image1.Length) {
DateTime now = DateTime.Now;
if ((now - t).TotalSeconds >= 2) {
pictureBox1.Image = Image.FromFile(image1[i]);
i++;
t = now;
}
Application.DoEvents();
}
I have the following Play method for playing musical notes stored in an arraylist:
public void Play(Duration d){
int duration = 1;
if (d == Duration.SemiBreve)
{
duration = 16;
}
else if (d == Duration.DotMin)
{
duration = 10;
}
else if (d == Duration.minim)
{
duration = 8;
}
else if (d == Duration.Crotchet)
{
duration = 4;
}
else if (d == Duration.Quaver)
{
duration = 2;
}
else if (d == Duration.SemiQuaver)
{
duration = 1;
}
player = new SoundPlayer();
player.SoundLocation = "pathToLocation";
//set timer
time = new Timer();
time.Tick += new EventHandler(clockTick);
time.Interval = duration * 150;
player.Play();
time.Start();
}
When I call the method with a button:
private void PlayButton_Click(object sender, EventArgs e)
{
//Loops through the collection and plays each note one after the other
foreach (MusicNote music in this.staff.Notes)
{
music.Play(music.Dur)
}
}
Only the last note gets played. With PlaySync() all the notes get played but the duration isn't recognized. I also tried using threads like so:
foreach (MusicNote music in this.staff.Notes)
{
Thread t = new Thread(playMethod=>music.Play(music.Dur));
t.Start();
t.Join();
}
But it doesn't work either. Any suggestions as to how I can get the files to play consecutively like with PlaySync but using their set duration?
You don't wait for the timer anywhere. So in practice all notes get played almost simultaneously, causing just the last one to be effectively audible.
UPDATE: Using the System.Timers.Timer class the way to go is registering a handler for the Elapsed event and playing the notes one-by-one in the event handler. This way you avoid freezing the UI.
Another option is playing in a background thread and the using Thread.Sleep to wait. Here you will have to make sure the thread is stopped according to the state of the UI (i.e. the use closes the current dialog etc.).
In either case to avoid race conditions you will have to solve concurrent access to staff.Notes or make a copy of it for the sake of playing it.
So I am making a simple brick breaking game in c#/wpf. I am running into an issue using timers, I feel like it is probably a simple fix but here is whats happening. Whenever t_Elapsed is fired it attempts to call Update() but when it does its like OMG Im not in the right thread so I cant do that sir. How do I invoke the method from the Game from the proper thread? (And yes I know the code is ugly and has magic numbers but I just kinda chugged it out without putting a lot of effort in. And yes I have zero experience programming games)
public partial class Game : Grid
{
public bool running;
public Paddle p;
public Ball b;
Timer t;
public Game()
{
Width = 500;
Height = 400;
t = new Timer(20);
p = new Paddle();
b = new Ball();
for (int i = 15; i < 300; i += 15)
{
for (int j = 15; j < 455; j += 30)
{
Brick br = new Brick();
br.Margin = new Thickness(j, i, j + 30, i + 15);
Children.Add(br);
}
}
Children.Add(p);
Children.Add(b);
p.Focus();
t.AutoReset = true;
t.Start();
t.Elapsed += new ElapsedEventHandler(t_Elapsed);
}
void t_Elapsed(object sender, ElapsedEventArgs e)
{
if (running)
{
Update();
}
}
void Update()
{
b.Update(); //Error here when Update is called from t_Elapsed event
}
void Begin()
{
running = true;
b.Initiate();
}
}
You should use the DispatcherTimer object instead, it will ensure that the timer events are published to the correct thread.
Timer elapsed events fire on a thread from the thread pool (http://www.albahari.com/threading/part3.aspx#_Timers) and not on the UI thread. Your best approach is to invoke the control's dispatcher through a call like this:
yourControl.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.Normal
, new System.Windows.Threading.DispatcherOperationCallback(delegate
{
// update your control here
return null;
}), null);
The calling thread cannot access this object because a different thread owns it
this.Dispatcher.Invoke((Action)(() =>
{
...// your code here.
}));