Toon posts:

(C#.NET 2005) UserControl Painting - ClippingRect Issues

Pagina: 1
Acties:

Verwijderd

Topicstarter
Hoi allemaal,

ik heb momenteel een probleem waar ik niet uitkom. Ik ben bezig om een custom UserControl te maken. Ik zit echter met een painting probleem. Ik wil effectief painten, wat inhoud dat alles wat niet op het scherm dient niet getekend te worden. Om dit te kunnen realiseren dien ik gebruik te maken van de ClippingRectangle die meegeleverd wordt met de PaintEventArgs. So far so good :).

Nu ben ik een tijd bezig geweest met mijn control en vroeg me zwaar af waarom de boel zo lagde. Ik had dubbelbuffering aan maar nog steeds leek alles gerepaint te worden bij een simple resize naar rechts (dus het scherm breeder maken). Normaal gesproken zou alleen het rechter gedeelte na het slepen gerepaint dienen te worden omdat de ClippingRectangle correct gezet wordt. Het probleem is echter wanneer ik een docking TabControl gebruik! De ClippingRectangle altijd het gehele scherm bevat!! Dus van x = 0, y = 0, width = window width, height = window height. Hier kan ik dus helemaal niets mee :( omdat dit de reden is dat het ding zo hard lagged.

Nu heb ik lopen testen waarom het komt dat de TabControl in full docking mode alles probeerd te repainten maar ik kom er niet uit. De grap is namelijk dat als ik de TabControl niet dock ik wel X = 250 en dergelijke krijg en dus kan zien dat de ClippingRectangle werkt. Als ik bijvoorbeeld een panel gebruik (en deze full dock) en daarin mijn UserControl full dock, dan werkt de ClippingRectangle ook gewoon! Ik denk haast bijna dat dit een bug is in de TabControl?

Ik hoop dat iemand mij verder op weg kan helpen! Hopelijk zie ik wat over het hoofd!

  • pjvandesande
  • Registratie: Maart 2004
  • Laatst online: 14:06

pjvandesande

GC.Collect(head);

Het kan zijn dat de ControlStyles.ResizeRedraw flag is geset voor je control. Deze flag zorgt ervoor dat je dat je Control invalidated wordt zodra hij resized.

Maar, changed de graphics vaak van je control? Want anders is het beter om zelf een buffer bij te houden en deze alleen aan te passen als dit ook echt nodig is en bij een paint request gewoon de buffer uitspugen.

Verwijderd

Topicstarter
questa schreef op donderdag 29 juni 2006 @ 09:05:
Het kan zijn dat de ControlStyles.ResizeRedraw flag is geset voor je control. Deze flag zorgt ervoor dat je dat je Control invalidated wordt zodra hij resized.

Maar, changed de graphics vaak van je control? Want anders is het beter om zelf een buffer bij te houden en deze alleen aan te passen als dit ook echt nodig is en bij een paint request gewoon de buffer uitspugen.
Ik zal vanavond testen of die flag aan of uit staat. Ik denk niet dat mijn control die flag heeft maar eerder de TabControl omdat met een normale Panel ik geen problemen heb met de ClippingRectangle.

De graphics hebben geen animaties dus ik denk niet dat ze vaak changen. Alleen als ze echt gerepaint dienen te worden maar dat is ook wel logisch denk ik.

De buffer bijhouden is een goed idee, ik vraag me alleen af hoe je gedeelten van het scherm wilt gaan repainten? Je moet toch weten wat geinvalidate dient te worden. Als je PaintEventArgs altijd het gehele scherm willen invalidaten dan schiet je nog niets op zelfs niet als je een buffer gebruikt denk ik? Het lijkt me dat je veel vertraging blijft houden (ondanks het feit dat de vertraging er vloeiender uitziet?).

  • pjvandesande
  • Registratie: Maart 2004
  • Laatst online: 14:06

pjvandesande

GC.Collect(head);

Verwijderd schreef op donderdag 29 juni 2006 @ 11:39:
[...]

De buffer bijhouden is een goed idee, ik vraag me alleen af hoe je gedeelten van het scherm wilt gaan repainten? Je moet toch weten wat geinvalidate dient te worden. Als je PaintEventArgs altijd het gehele scherm willen invalidaten dan schiet je nog niets op zelfs niet als je een buffer gebruikt denk ik? Het lijkt me dat je veel vertraging blijft houden (ondanks het feit dat de vertraging er vloeiender uitziet?).
Je kunt gewoon een gedeelte van je Buffer 'verversen' als dit nodig is en alleen het gedeelte wat op je scherm een repaint nodig heeft uit de buffer trekken en tekeken.

Ik gebruik deze simpelen techniek voor een ala grid control voor ons product en met 100.000 cellen heeft hij nog geen problemen. Er zit ondersteuning in voor plaatjes, text, sub text, sup text, schaduw, border, glowing... etc en dit bij een repaint gaat dit in zonder merkbare vertraging. Dus ik gok dat er in je code ook nog wel wat te optimalizeren valt.

Je kunt altijd een profiler, NProf is gratis, pakken en kijken waar de bottleneck ligt en dat dan even hier op posten.

Verwijderd

Topicstarter
questa schreef op donderdag 29 juni 2006 @ 14:21:
Je kunt gewoon een gedeelte van je Buffer 'verversen' als dit nodig is en alleen het gedeelte wat op je scherm een repaint nodig heeft uit de buffer trekken en tekeken.
ik denk dat je me een beetje verkeerd begrijpt. Ik begrijp het nut van een buffer zeer goed. Mijn vraag was of je ZONDER de ClipRectangle uberhaupt wat aan een buffer er tussen hebt omdat je ALTIJD het scherm zal repainten omdat je anders nooit weet wat je moet repainten. Daarom was mijn vraag ook is dit zo? En zo niet hoe kom je dan zonder de ClipRectangle er achter wat gerepaint dient te worden?
questa schreef op donderdag 29 juni 2006 @ 14:21:
Je kunt altijd een profiler, NProf is gratis, pakken en kijken waar de bottleneck ligt en dat dan even hier op posten.
Ah thanks! Zal vanavond eens kijken of dit het probleem kan bepalen :).


Trouwens ik had nog een vraag naar aanleiding van mijn UserControl. Ik zit nog te emmeren met de scrollbars binnen de applicatie maar ik denk dat ik hier wel uit kom. Wat ik momenteel alleen check is of een gehele regel (een listachtige control) gepaint dient te worden. Wat mooier en efficienter is om natuurlijk ook de breedte mee te nemen.

Stel dat de helft van het scherm gerepaint dient te worden, in mijn voorbeeld zal hij dus alle regels repainten. Zou je bijvoorbeeld iets als het volgende doen om horizontaal te clippen?

code:
1
2
3
4
5
6
7
8
9
10
11
for (int i = 0; i < stringToDraw.Length; i++)
{
   // als de tekst tussen de x van de cliprectangle en de x + width van de rectangle ligt,
   // repaint dan het teken.
   int posCharWidth = textPosX + e.Graphics.MeasureString(stringToDraw[i])
   int posClipWidth = e.ClipRectangle.X + e.ClipRectangle.Width;
   if ((e.ClipRectangle.X <= textPosX) || (posClipWidth <= textPosX + posCharWidth))
   {        
      e.Graphics.DrawString(stringToDraw[i]);
   }
}


In bovenstaand voorbeeld zal ik dus de gehele tekst per regel door lopen om te kijken of er een paar tekens zijn die opnieuw getekent dienen te worden. Is dit effecient of kan je beter alle chars tussen de ClipRectangle.X en (ClipRectangle.X + ClipRectangle.Width) nemen? Er zit geen verschil in het aantal loops, omdat je blijft bepalen hoeveel chars het zijn maar in het laatste geval hoef je maar 1 keer DrawString aan te roepen.

Nog een korte vraag. Om je scrollbars te laten werken gebruik je daarvoor TranslateTransform?

  • pjvandesande
  • Registratie: Maart 2004
  • Laatst online: 14:06

pjvandesande

GC.Collect(head);

Verwijderd schreef op donderdag 29 juni 2006 @ 14:43:
[...]

ik denk dat je me een beetje verkeerd begrijpt. Ik begrijp het nut van een buffer zeer goed. Mijn vraag was of je ZONDER de ClipRectangle uberhaupt wat aan een buffer er tussen hebt omdat je ALTIJD het scherm zal repainten omdat je anders nooit weet wat je moet repainten. Daarom was mijn vraag ook is dit zo? En zo niet hoe kom je dan zonder de ClipRectangle er achter wat gerepaint dient te worden?
Ja en nee. Je hebt de ClipRectangle in prencipe alleen nodig om je buffer up te daten, de buffer in je graphics object painten kost je niks. Maar als je de ClipRectangle toch hebt is het altijd mooi meegenomen om die even mee te nemen.
Verwijderd schreef op donderdag 29 juni 2006 @ 14:43:
[...]

Stel dat de helft van het scherm gerepaint dient te worden, in mijn voorbeeld zal hij dus alle regels repainten. Zou je bijvoorbeeld iets als het volgende doen om horizontaal te clippen?

code:
1
2
3
4
5
6
7
8
9
10
11
for (int i = 0; i < stringToDraw.Length; i++)
{
   // als de tekst tussen de x van de cliprectangle en de x + width van de rectangle ligt,
   // repaint dan het teken.
   int posCharWidth = textPosX + e.Graphics.MeasureString(stringToDraw[i])
   int posClipWidth = e.ClipRectangle.X + e.ClipRectangle.Width;
   if ((e.ClipRectangle.X <= textPosX) || (posClipWidth <= textPosX + posCharWidth))
   {        
      e.Graphics.DrawString(stringToDraw[i]);
   }
}
In prencipe is dit soort optimalizatie niet nodig. Het berekenen kost vaak meer tijd dan het painten. Normaal doe ik alles gewoon in een normaal geoptimalizeerde manier en draaf ik niet tever door.
Bij het profilen ontdek je bottlenecks, deze ga ik dan wel uitmelken.

Verwijderd

Topicstarter
MMmmmmm :(. Ik heb net voor de grap even een test applicatie geschreven die alleen een gradient tekent over het gehele scherm. Als ik resize heeft dit totaal geen vertraging. Ik denk dan dat het komt doordat ik alles weer doorloop voor ik het op het scherm zet, dus ik loop alle lines door om deze vervolgens weer op het scherm te zetten als er een repaint nodig is.
questa schreef op donderdag 29 juni 2006 @ 15:41:
[...]
Bij het profilen ontdek je bottlenecks, deze ga ik dan wel uitmelken.
Ik heb geen idee hoe je af kan lezen wat je bottleneck is met NProf. Zou je me dat misschien kunnen uitleggen :*). Ik zie een heleboel getallen maar niet echt een idee wat ik er uit moet afleiden. De website heeft geen documentation / faq gedeelte :x


Trouwens hoe paint je normaal? Paint je direct ALLES om vervolgens deze buffer anders te positioneren op het scherm? Stel je buffer bevat je gehele paint scherm. Als je naar beneden scrollt zou je dan b.v. DrawImage(buffer, scrolledRect) doen om het scrollen te simuleren? Het lijkt me namelijk niet efficient (hoe ik het nu doe) om alles elke keer maar weer door te loopen.

Een andere mogelijkheid zou zijn om je buffer alles te laten bevatten maar wanneer je het op je scherm zet alleen een snapshot (je huidige viewport) eruit trekt van het geen je wilt tekenen. Dit lijkt me efficienter dan het bovenstaande. Je houdt net als hierboven nog steeds het feit dat het initializeren gigantisch lang kan duren mocht je veel dienen te painten (veel dienen te painten in de zin van de mogelijkheid hebt tot scrollen, net als bijvoorbeeld een word document van 200 pagina's).

Hoe zou je het bijvoorbeeld doen met animaties? Stel ik wil een line selecteren dan zou ik in het bovenstaande geval alleen een stuk uit mijn buffer hoeven te vervangen en verder wordt alles automatisch gedaan? Als ik het in mijn algoritme zou zetten dan moet ik elke keer kijken of een line IsSelected is om vervolgens deze line geselecteerd over te laten doen komen, wat inhoud dat er nog een check bij is gekomen.

Ik vind het lastig spul dat GDI+. Niet zozeer het painten zelf want dat is niet lastig, maar meer de principes en gebruiken met GDI+. Mocht iemand meer info hebben over GDI+ (heb al veel gelezen maar nog weinig over buffers en hoe om te gaan met een "echte" control). Het liefst een tutorial of info over hoe je vanuit niets een control schrijft.
Pagina: 1