UI Performance - Xamarin - c#

Hey I have an rather specific issue.
I am building an metronom, and this metronom also shows boxes, which will change their color in the given tact in bpm. the highest possible bpm is 400bpm with 4/4 beats. that means the fastest change of color should happen every: 37,5 ms.
The change of color will last some milliseconds.
Until now i tried to realize that with a timer:
timer = new System.Timers.Timer() { AutoReset = true, SynchronizingObject = null };
private async void timer1_Elapsed(object sender, ElapsedEventArgs e)
{
boxViewLast = (BoxView)beatDisplay.Children[(int)lastI];
boxView = (BoxView)beatDisplay.Children[(int)i];
boxViewLast.Color = boxColor;
boxView.Color = transColor;
await Task.Delay((int)BeatMilliseconds);
boxView.Color = boxColor;
lastI = i;
i++;
if (i >= numOfChildren)
{
i = 0;
}
timer.AutoReset = Tempo == 1 && Play;
}
But in the UI I see a flickering some times if the color is changing fast and sometimes it is not even changing. I guess that resources are getting blocked and so the change can not be fullfilled in the given time. Is there a way to have UI change async and very performant? But I also need a very pricise timer

No reason to await Task.Delay inside the callback. You can use the timer to define the interval between callbacks:
var lastTime = DateTime.Now;
var times = new List<TimeSpan>(1000);
var timer = new System.Timers.Timer() { Enabled = true, Interval = 32, AutoReset = true};
timer.Elapsed += TimerHandler;
Console.WriteLine("Starting timer");
timer.Start();
await Task.Delay(TimeSpan.FromSeconds(3));
timer.Stop();
foreach (var time in times)
{
Console.WriteLine($"{time.TotalMilliseconds}ms passed");
}
void TimerHandler(object? sender, System.Timers.ElapsedEventArgs e)
{
TimeSpan timePassed = e.SignalTime - lastTime;
times.Add(timePassed);
lastTime = DateTime.Now;
}
It's going to be somewhat accurate:
30.5771ms passed
31.0725ms passed
30.8268ms passed
31.0783ms passed
30.5758ms passed
31.4693ms passed
31.0038ms passed
32.1051ms passed
31.0289ms passed
31.1313ms passed
30.9087ms passed
31.0877ms passed
31.9015ms passed
30.7389ms passed
46.8875ms passed
31.4987ms passed
31.7549ms passed

Related

wpf Multiple dispatch timer for timer application : Run multiple timer simulteniously

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.

How to return control of flow to a for loop?

I'm trying to code a loop to re execute a block of code 3 times. At the moment the code starts and executes once but doesn't repeat as intended with using the for loop.
I've set a break point on the for loop and it only goes through the loop once.
private async void startBtn_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
int i;
int roundMax = 3;
for (i = 1; i <= roundMax; i++)
{
//delay stop watch start to allow time to get ready.
TimeSpan time = TimeSpan.FromSeconds(1);
await System.Threading.Tasks.Task.Delay(time);
//set text box editing to false to prevent illegal input.
wrkTbx.IsEnabled = false;
restTbx.IsEnabled = false;
roundSlider.IsEnabled = false;
roundsTbx.IsEnabled = false;
StopGoCvs.Background = new SolidColorBrush(Colors.Green);
//startSoundElmt.Play();
// set up the timer
myTimer = new DispatcherTimer();
myTimer.Interval = new TimeSpan(0, 0, 0, 0, 1);
myTimer.Tick += myTimer_Tick;
// start both timers
myTimer.Start();
myStopwatch.Start();
}
}
this is a logical issue, and I suspect it exits the for loop at some point, Assumming you're using Visual Studio, I would use Breakpoints at every steps and run the program to see where it fails, you may be missing a return or else somewhere

WPF Dispatchertimer is not stopping, nor restarting

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);
}

Thread.Sleep() in C#

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();
}

C# Set duration and play sounds stored in an arraylist consecutively

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.

Categories