30. november 2010 - 10:46Der er
34 kommentarer og 1 løsning
Hvorhenne skal jeg tegne?
Hej
Jeg er ved at opbygge et canvas i en winform. Canvas'et ligger i et panel, og jeg bruger graphics transform til at ændre størrelse og flytte indholdet af panelet når jeg skal hhv. zoome og pan'e.
Jeg vil så tegne noget på dette canvas, men er ikke sikker på at jeg tegner det rigtige sted. For det første bruger jeg lige nu panelets paint event. Men det giver en masse flickering højest sandsynligt pga. at nogle af tingene på mit canvas gentegnes mange gange.
Er der nogen der har en idé til, hvordan dette kan opbygges? jeg kommer til at have mange ting på canvas'et som alle skal have hit-test og som alle skal kunne flyttes. Derfor skal der også være mouseover-effekter på nogle af tingene.
Lige nu har jeg alle mine ting der skal tegnes liggende i et array og, hver gang der så sker en mouseevent som f.eks. flytning af musen, løber den hele array'et igennem for at tjekke om musen rør ved nogen ting, og gentegner derefter hele panelet, ved at invalidate formen. Det virker langsomt og flicker som sagt meget.
Det er en rigtig beslutning du har taget med at tegne i OnPaint metoden på panelet.
Når du skal gentegne, bruger du Invalidate metoden.
Hvis det kun er et lille område der skal gentegnes, kan du bruge Invalidate(Region) eller Invalidate(Rectangle). Så kan du nøjes med at tegne et lille område. Dette giver en del logik at holde styr på, men kan betyde meget på kompliserte flader.
Når du kalder Invalidate(Region) eller Invalidate(Rectangle), vil din OnPaint() metode blive kaldt som den plejer, så du bliver nød til selv at lave noget logik som avgøre hvilke elementer der skal tegnes forfra.
Jeg vil sige at generelt er mit største problem flickeringen. Altså selve problematikken med at opbygge en logik, der kan styre hvad for objekter på mit canvas, der er mouseevents på, skifte grafik lokalt på bestemte regioner osv. skal jeg nok kunne finde ud af, men jeg har ingen idé om, hvorfor den fortsætter med flickering. Jeg poster gerne min kode her, den er forholdsvis simpel, men ved ikke om det er kotume?
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Text; using System.Windows.Forms;
namespace panel2 { public partial class Form1 : Form {
PointF mouseDown;
float newX; float newY; float zoomFactor = 1F;
Region _rgn; Graphics _dc; PointF zoomPoint = new PointF(150, 150);
public Form1() { InitializeComponent();
mouseDown = new PointF(0F, 0F);
this.panel1.Paint += new PaintEventHandler(panel1_Paint); this.panel1.MouseDown += new System.Windows.Forms.MouseEventHandler(panel1_MouseDown); this.panel1.MouseMove += new System.Windows.Forms.MouseEventHandler(panel1_MouseMove);
Color gridColor = Color.FromArgb(230, 230, 230); Pen gridPen = new Pen(gridColor, 1 / zoomFactor);
for (int i = (int)newX; i < this.ClientSize.Height * (zoomFactor + 1); i = i + 30) { dc.DrawLine(gridPen, 0, i, this.ClientSize.Width * (zoomFactor + 1), i); } for (int i = (int)newY; i < this.ClientSize.Width * (zoomFactor + 1); i = i + 30) { dc.DrawLine(gridPen, i, 0, i, this.ClientSize.Height * (zoomFactor + 1)); }
I stedet for at bruge en Panel, laver du en ny klasse som nedarver fra Control klassen. I denne sætter du DoubleBuffered = true i constructoren, og overrider OnPaint metoden. Flyt alt tegne-logikken hertil.
Nu vil du kunne dragge-droppe din nye control fra toolboxen ind på din form.
Jeg har flere gange lavet noget lignende på denne måde, og det virker ganske godt. Ingen flickering.
Ok. Det vil jeg prøve. Når jeg overrider mouse metoderne vil den så reagere i hele min form? dvs. hvis jeg på et tidspunkt ønsker at den nye control jeg laver og formen ikke skal være sammenfaldende så kan jeg få problemer ifht at den reagerer på mouseevent udført udenfor mit "canvas"?
Når du overrider OnPaint skal du ikke kalde dispose, da Control klassen selv sørger for at gøre det.
Alle klasser der nedarver fra Control, har et grænsesnit imod formens message-loop. Når du kalder Invalidate, fortæller du kontrollen at den skal gentegne sig selv. Næste gang formen tager en tur igennem message-loopen, og ser at en Control er invalidated, opretter den en Graphics, kalder OnPaint, og til sidst disposer den. Den gør også andre ting, derfor er det vigtigt man ikke kalder Dispose, da disse andre ting da måske vil mislykkes.
Med hensyn til at fange events fra musen, så vil du få problemer med at det kun er den control musen er henover som fanger eventet, uanset om du abonnere på eventet fra en form eller overrider metoden. Det er lang tid siden jeg har gjort noget tilsvarende selv, så jeg kan ikke helt huske hvordan du løser det, men det kan være du bliver nød til at lave et message filter, og registrerer den ind i formens message loop. Giv en lyd hvis du får problemer med dette, så kan det være jeg kan finde et eksempel.
Har du forresten en idé om, hvorfor når jeg laver en drag funktion at objektet jeg dragger kommer haltende bagefter og først når hen til musen når den står stille? Jeg ved ikke om det kunne være fordi jeg invalidater for ofte, så prøve at lave en counter%i==0 og satte i til forskellige stigende tal for at tjekke om færre invalidates ville give bedre resultat, men uden held.
Frygtede lidt at det kunne være min oversættelse fra et koordinatsystem til et andet når der var zoomet(det er kun her problemet opstår), men når den når hen til musen på et tidspunkt kan det jo ikke rigtig være andet end noget performancebaseret?
Min mousedown ser sådan her ud:
protected override void OnMouseDown(MouseEventArgs e) { MouseEventArgs mouse = e as MouseEventArgs;
Hmm. Det var lidt mærkelig. Jeg kan ikke lige se ved din kode, hvad som er årsagen.
Du kan eventuelt prøve at sætte en Application.DoEvents() ind efter du kalder invalidate i OnMouseMove metoden.
Hvis det ikke hjælper, så kunne jeg tænke mig at se hele koden. Har du eventuelt mulighed for at uploade en zipped udgave af projektet til en eller anden server?
Ja, men jeg mistænker lidt at det er et performance problem. Jeg prøvede nemlig at lave en region udenom min grafik, og prøve at nøjes med at invalidate den og det fungerer lidt bedre, men så er problemet at afhængig af zoom-niveau kan man flytte grafikken ret langt, og den kommer nemt udenfor den region jeg invalidater, og laver så spor.
Nu har jeg set lidt på koden. Det virker rimeligt godt hos mig, den hænger lidt efter, men ikke meget.
Jeg kan ikke se nogle åbenlyse ting du kan gøre for at få bedre performance. Men noget du kan huske på, når du skal begynde at tegne flere figurer, er at aldrig gør mere end højst nødvendigt. F.eks. hvis du flytter på en figur, skal du kun regne ud GraphicsPath'en for denne ene figur, og helst kun tegne de figurer som bliver berørt igen.
Jeg ser at det kan være svært at få til at virke med zoomingen, men det kan helt sikkert lade sig gøre.
Jeg er lidt meget grøn indenfor c# så det er lidt svært at finde ud af, hvornår det jeg laver er ok, og hvornår jeg laver ting der bliver tunge beregningsmæssigt.
Men ja, jeg må jo få indstillet så den region jeg invalidater, passer nogenlunde på, hvor langt man kan nå at flytte musen, før den når at gentegne, så jeg ikke kommer udenfor.
Ifht. til det med GraphicsPath'en, så er jeg vel også nødt til at gentegne f.eks. grid'et nedenunder, det bliver jo også påvirket af at jeg trækker den henover, og evt. andre objekter på mit canvas?
Ja, du bliver nød til at gentegne grid'et under. Men du behøver jo ikke at tegne alle linjerne. I dine for-løkker som tegner gridden, kan du tjekke om linjen berørerer området som er invalidated, før du gentegner den.
Generelt så er alle tegne-operationer tunge, så længe du bruger GDI+ (Du bruger GDI+ så længe du bruger Graphics til at tegne med). Alternativt kan man bruge noget OpenGL eller Direct2D. Du kan f.eks. se på agatelib ( http://www.agatelib.org/ ), som er et c# bibliotek lavet for programmering af 2d spil. Her får du meget bedre performance.
GDI+ er fint, man skal bare være klar over at performance er rimeligt dårlig.
Indtil videre er mine muligheder desværre kun at bruge GDI+ - på sigt håber jeg at kunne skifte til en anden tegneplatform. AgateLib lyder spændende - den vil jeg kigge nærmere på.
"Ja, du bliver nød til at gentegne grid'et under. Men du behøver jo ikke at tegne alle linjerne. I dine for-løkker som tegner gridden, kan du tjekke om linjen berørerer området som er invalidated, før du gentegner den."
Men når jeg Invalidater en region, så gentegner den vel kun den grafik der ligger der indenfor ikke? eller skal jeg stadig have en logik der ved tegning også sørger for at der kun gentegnes indenfor den samme region?
Ja og nej. Den opdaterer bare den del af skærmen som er invalidated, men din logik kalder jo Graphics.DrawLine() mange gange for at tegne streger uden for området. Disse streger vil du ikke se, men logikken bliver aligevel kørt.
Ok, jeg har faktisk lavet det, men af en eller anden grund tegner den kun grid der, hvor linierne rører min path. Det er som min region ikke fungerer, den rydder hele skærmen og tegner så kun de linier der rører ved mit objekt. Nogen idéer?
Koden blev udvidet som følger:
MouseMove fik tilføjet at den laver regions omkring grafikken:
Og derefter lavede jeg følgende logik i paintEvent'en:
if (startDrag) {
RectangleF sdf = _rgn.GetBounds(_dc);
for (float y = offY; y < this.Height; y = y + 30 * zoomFactor) { if(y>sdf.Location.Y && y< sdf.Location.Y + sdf.Height) dc.DrawLine(gridPen, 0, y, this.Width, y); } for (float x = offX; x < this.Width; x = x + 30 * zoomFactor) { if (x > sdf.Location.X && x < sdf.Location.X + sdf.Width) dc.DrawLine(gridPen, x, 0, x, this.Height); }
} else { for (float y = offY; y < this.Height; y = y + 30 * zoomFactor) { dc.DrawLine(gridPen, 0, y, this.Width, y); } for (float x = offX; x < this.Width; x = x + 30 * zoomFactor) { dc.DrawLine(gridPen, x, 0, x, this.Height); } }
Og fandt selv løsningen. Jeg var nødt til at lave en ny bool variabel som jeg først sætter til true ved mouseMove, og så bruger den til at teste om kun en del eller hele grid'et skal tegnes.
Åbenbart er det ligemeget om der står:
this.Invalidate(); startDrag = true;
eller omvendt:
startDrag = true; this.Invalidate();
Den når at sætte startDrag til true før den afvikler paintEvent.
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.