Ik werk momenteel aan een programmaatje dat een audiospectrum weergeeft. Daarbij wordt meerdere malen per seconde een control fullscreen gerepaint. Dat heb ik zo efficient mogelijk geprobeerd te maken met de volgende code:
Als er bijvoorbeeld nieuwe data getekend moet worden gebeurt er dit:
Dat loopt als een zonnetje (~1% CPU belasting bij fullscreen 8x per seconde tekenen) en werkt nagenoeg perfect. Als windows een repaint triggert (bijvoorbeeld omdat ik een ander venster, dat de grafiek overlapte, minimaliseer) wordt de boel netjes opnieuw getekend.
Het probleem van deze constructie wordt duidelijk als ik bijvoorbeeld de Task Manager boven mijn grafiek zet en dan op kruisje druk (in de Task Manager) terwijl de spectrum analyzer draait:

Wat gebeurt er? Windows triggert een Paint event omdat de Task Manager verdwijnt. Normaal gesproken zou de OnPaint handler vrijwel onmiddelijk zijn uitgevoerd, waarbij een full redraw wordt gedaan omdat alle newXxxx vlaggen false zijn. Het systeem is echter even druk met het afsluiten van de Task Manager, waardoor het uitvoeren van deze OnPaint even op zich laat wachten. Tijdens deze periode komt er nieuwe data binnen, waardoor de newData flag op true wordt gezet. Hierdoor ziet mijn OnPaint handler niet meer dat hij eerst door Windows was getriggered, en in plaats van een full redraw wordt alleen de data opnieuw getekend.
Na deze veel te lange introductie de vraag: hoe kan ik nog het onderscheid maken tussen een door Windows getriggerde OnPaint en een door mijzelf middels this.Invalidate() getriggerde OnPaint()?
De logische oplossing zou zijn om de OnInvalidate handler een vlaggetje te laten zetten (als in userTriggered = true) maar dat maakt geen verschil want de OnPaint wordt per definitie pas na de OnInvalidate gedaan, dus in het probleemgeval zou userTriggered gewoon true zijn.
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| protected override void OnPaint(PaintEventArgs e) { // If Windows requested this repaint, do a full redraw if (!(newData || newAxes || newSize)) newAxes = true; if (newSize) { // Graph size changed. // Create a new offscreen bitmap to draw on drawCanvas(); } if (newAxes) { // Axes dimensions changed. // Draw new axes onto offScreen bitmap currentAxes = futureAxes; drawAxes(); e.Graphics.DrawImage(offScreenBitmap, 0, 0); // Draw current data in new axes newData = true; } if (newData) { // Channel data changed // Draw white over old lines foreach (Point[] line in lines) { e.Graphics.DrawLines(this.eraseLinePen, line); } // Compute new lines drawData(); // Draw new lines for (int i = 0; i < lines.Count; i++) { this.drawLinePen.Color = channelColors[i]; e.Graphics.DrawLines(this.drawLinePen, lines[i]); } } newSize = newAxes = newData = false; } protected override void OnPaintBackground(PaintEventArgs e) { // Ignore } |
Als er bijvoorbeeld nieuwe data getekend moet worden gebeurt er dit:
C#:
1
2
3
4
5
6
7
8
9
| public void requestDataDraw() { // New data is ready to be drawn newData = true; // Redraw data area (defined in Rectangle graphRect) else this.Invalidate(graphRect); } |
Dat loopt als een zonnetje (~1% CPU belasting bij fullscreen 8x per seconde tekenen) en werkt nagenoeg perfect. Als windows een repaint triggert (bijvoorbeeld omdat ik een ander venster, dat de grafiek overlapte, minimaliseer) wordt de boel netjes opnieuw getekend.
Het probleem van deze constructie wordt duidelijk als ik bijvoorbeeld de Task Manager boven mijn grafiek zet en dan op kruisje druk (in de Task Manager) terwijl de spectrum analyzer draait:

Wat gebeurt er? Windows triggert een Paint event omdat de Task Manager verdwijnt. Normaal gesproken zou de OnPaint handler vrijwel onmiddelijk zijn uitgevoerd, waarbij een full redraw wordt gedaan omdat alle newXxxx vlaggen false zijn. Het systeem is echter even druk met het afsluiten van de Task Manager, waardoor het uitvoeren van deze OnPaint even op zich laat wachten. Tijdens deze periode komt er nieuwe data binnen, waardoor de newData flag op true wordt gezet. Hierdoor ziet mijn OnPaint handler niet meer dat hij eerst door Windows was getriggered, en in plaats van een full redraw wordt alleen de data opnieuw getekend.
Na deze veel te lange introductie de vraag: hoe kan ik nog het onderscheid maken tussen een door Windows getriggerde OnPaint en een door mijzelf middels this.Invalidate() getriggerde OnPaint()?
De logische oplossing zou zijn om de OnInvalidate handler een vlaggetje te laten zetten (als in userTriggered = true) maar dat maakt geen verschil want de OnPaint wordt per definitie pas na de OnInvalidate gedaan, dus in het probleemgeval zou userTriggered gewoon true zijn.