0

i have this piece of code in my timer_tick method called every 10 ms It runs okay unless i add more controls to the list.

I tried running it in a thread with Thread.sleep in a while() loop, but it seemed to freeze my whole form.

How can i make this code run faster and change position of my controls? Do the type of control make a difference? Should i use Panels instead of Pictureboxes?

Here's my code:

private void notepreview_Tick(object sender, EventArgs e)
{
    
    Invoke(new Action(() =>
    {
        foreach (PictureBox picbox in activeNotes)
        {
            picbox.Location = new Point(picbox.Location.X, picbox.Location.Y + 1);
            //Console.WriteLine("x: " + picbox.Location.X);
            //Console.WriteLine("y: " + picbox.Location.Y);

            if (picbox.Location.Y > this.Height + 5)
            {
                picbox.Dispose();
                tabPage16.Controls.Remove(picbox);
            }
        }
    }));

}

and here's how it looks in my form: (each note is spawned separately, so the lag is slowly rising which causes de-syncing with the audio and piano)

imgur form lag showcase

maybe I should use gdi+ instead? Would it improve the prerformance?

I tried running the code in a separate thread from this question: Accessing a form's control from a separate thread

(heard that using a FOR loop instead of foreach is faster, but it didnt really make a difference in this case)

jebac pis
  • 39
  • 5
  • Side note: you should understand that what you are doing is pure learning/entertaining exercise. For real world usage you'd simply go with some existing platform (Unity is a good choice). So you may want to actually try out all suggestions you have in this post, measure, refactor and in general play around till you satisfied with the learning part... And then switch to more suitable platform... I don't think making someone else to do all that research would help you in any useful way. – Alexei Levenkov Jan 06 '23 at 19:44
  • @AlexeiLevenkov I wouldn't say go for Unity right away - a 2D app like this can be done just fine in C#/WinForms. – AKX Jan 06 '23 at 19:49
  • The issue with your approach is that everything is happening in the main UI thread. Anytime you create a loop, the entire UI thread is waiting for the loop to finish before it can continue to process UI events and draw on the screen, so it results in lag and stutter, and freeze your form. It is ideal to use another thread for loops, but if the loops manipulate UI objects the thread will need access to UI thread then you need cross-thread support. You should change to GDI+, its much easier than you think. – DSander Jan 06 '23 at 19:52
  • Try and add `Application.DoEvents();` inside your foreach loop, this will tell the app to process any windows events (like mouse clicks and keyboard hits) during your loop instead of waiting for the loop to finish. This will improve some lag you are getting. – DSander Jan 06 '23 at 19:54
  • Try System.Threading.Tasks.Parallel.ForEach(activeNotes...). – rubgra Jan 06 '23 at 19:56
  • No the controls are moved one pixel down each 10ms and they are destroyed when going out of the form bounds – jebac pis Jan 06 '23 at 20:00
  • What I would recommend (if this is possible), is do this in a ViewModel, and iterate on the List of elements, which are then Bound to the front end. I have had to do some fairly fast computing for some imagae analysis stuff, and have found that separating it into a ViewModel, doing it on a separate thread, and updating the UI via the Dispatcher allows you to do things at much faster rates than trying to only use the events and codebehind/WPF stuff. As some of the WPF structure has overhead for construction and manipulation, whereas simple POCOs dont. Noticed the Winforms after, mine is WPF. – Ginger Ninja Jan 06 '23 at 20:00
  • How would i manage to draw GDI+ to render beneath other controls like piano tiles or form bounds – jebac pis Jan 06 '23 at 20:04
  • With GDI you can draw directly to the form background. I would recommend also drawing the piano tiles as well though, and use a different mechanism for detecting clicks if that is what you are doing. – DSander Jan 06 '23 at 20:10
  • The piano system works using a .txt file with timing, note color etc. (each line is new note). I am using Control.Name to get the note's position and spawn the moving control above it. I'm not sure how would i get it working with this and gdi. – jebac pis Jan 06 '23 at 20:15
  • Instead of thinking of "Controls" start thinking of "Objects". You can create your own class called "Notes" and have a Name, Position, and Color fields, and have a draw function with in it. Instead of working with controls you work with those Note objects. Then you will have a separate thread dedicated to drawing them. In the end Controls are just objects, you just need to replace them with your own objects. – DSander Jan 06 '23 at 20:34
  • Is there a way to write separate code for each control and make it execute each on a separate thread, like scripts in unity? – jebac pis Jan 06 '23 at 20:51
  • Does this SO answer [Moving Notes on a Timer](https://stackoverflow.com/a/74957222/5438626) help answer your question? – IVSoftware Jan 07 '23 at 15:46

1 Answers1

2

You can try calling SuspendLayout and ResumeLayout on your form around the loop so it doesn't update itself for each control manipulated... but beyond that, if it doesn't help, there's probably no good way to make this a lot faster.

Instead of using multiple PictureBoxes on your form that you animate for each note, consider drawing the entire scene of moving note rectangles onto a single Graphics that you render onto a single PictureBox. This also has the handy property of being able to be rendered entirely deterministically, so you can time it exactly to your audio.

AKX
  • 152,115
  • 15
  • 115
  • 172