Avatar billede bildsoe Nybegynder
30. november 2010 - 10:46 Der 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.

Vil sætte pris på forslag til løsninger!
Avatar billede aaberg Nybegynder
30. november 2010 - 10:53 #1
Du kan undgå flickeret ved at sætte DoubleBuffered = true på panelet.
Avatar billede aaberg Nybegynder
30. november 2010 - 10:57 #2
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.
Avatar billede aaberg Nybegynder
30. november 2010 - 10:59 #3
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.
Avatar billede janus_007 Nybegynder
30. november 2010 - 12:44 #4
Du kunne måske bruge WPF istedet?
Avatar billede bildsoe Nybegynder
30. november 2010 - 13:28 #5
Jeg har allerede sat det til double-buffered, og det har ingen forskel gjort.
Avatar billede bildsoe Nybegynder
30. november 2010 - 13:28 #6
Og desværre har jeg ikke mulighed for at skifte til WPF.
Avatar billede bildsoe Nybegynder
30. november 2010 - 13:30 #7
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?
Avatar billede bildsoe Nybegynder
30. november 2010 - 13:32 #8
Her er koden under alle omstændigheder:


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

        }

       

        private void panel1_Paint(object sender, PaintEventArgs e)
        {

            base.OnPaint(e);


            Graphics dc = e.Graphics;
            _dc = dc;

            dc.SmoothingMode = SmoothingMode.AntiAlias;

            dc.TranslateTransform(newX, newY);
            dc.ScaleTransform(zoomFactor, zoomFactor, MatrixOrder.Prepend);

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

            float XPosition = 10;
            float YPosition = 10;
            float CornerRadius = 5;
            float Width = 50;
            float Height = 50;

            Color BoxColor = Color.FromArgb(0, 0, 0);
            Pen BoxPen = new Pen(BoxColor, 2);

            GraphicsPath Path = new GraphicsPath();

            Path.AddLine(XPosition + CornerRadius, YPosition, XPosition + Width - (CornerRadius * 2), YPosition);
            Path.AddArc(XPosition + Width - (CornerRadius * 2), YPosition, CornerRadius * 2, CornerRadius * 2, 270, 90);
            Path.AddLine(XPosition + Width, YPosition + CornerRadius, XPosition + Width, YPosition + Height - (CornerRadius * 2));
            Path.AddArc(XPosition + Width - (CornerRadius * 2), YPosition + Height - (CornerRadius * 2), CornerRadius * 2, CornerRadius * 2, 0, 90);
            Path.AddLine(XPosition + Width - (CornerRadius * 2), YPosition + Height, XPosition + CornerRadius, YPosition + Height);
            Path.AddArc(XPosition, YPosition + Height - (CornerRadius * 2), CornerRadius * 2, CornerRadius * 2, 90, 90);
            Path.AddLine(XPosition, YPosition + Height - (CornerRadius * 2), XPosition, YPosition + CornerRadius);
            Path.AddArc(XPosition, YPosition, CornerRadius * 2, CornerRadius * 2, 180, 90);

            Path.CloseFigure();

           

            LinearGradientBrush lgb = new LinearGradientBrush(new PointF(XPosition+(Width/2),YPosition), new PointF(XPosition+(Width/2),YPosition + Height), Color.RosyBrown, Color.Red);

            dc.FillPath(lgb, Path);


            dc.DrawPath(BoxPen, Path);

            Matrix transformMatrix = new Matrix();
            transformMatrix.Translate(newX, newY);
            transformMatrix.Scale(zoomFactor, zoomFactor);
           
            _rgn = new Region(Path);

            _rgn.Transform(transformMatrix);

            dc.Dispose();
         

        }

        private void panel1_MouseDown(object sender, EventArgs e)
        {
            MouseEventArgs mouse = e as MouseEventArgs;

            if (mouse.Button == MouseButtons.Right)
            {

                mouseDown = mouse.Location;

                mouseDown.X = mouseDown.X - newX;
                mouseDown.Y = mouseDown.Y - newY;

            }

            else if (mouse.Button == MouseButtons.Left)
            {

                if (_rgn.IsVisible(mouse.Location, _dc))
                {
                    MessageBox.Show("tada");
                }


            }

        }

        private void panel1_MouseMove(object sender, EventArgs e)
        {
            MouseEventArgs mouse = e as MouseEventArgs;

            if (mouse.Button == MouseButtons.Right)
            {
                PointF mousePosNow = mouse.Location;

                float deltaX = mousePosNow.X - mouseDown.X;
                float deltaY = mousePosNow.Y - mouseDown.Y;

                newX = deltaX;
                newY = deltaY;

                panel1.Invalidate();
                           
            }

           
        }

        protected override void OnMouseWheel(MouseEventArgs e)
        {

            MouseEventArgs mouse = e as MouseEventArgs;

            PointF mP = mouse.Location;

            if (e.Delta > 0)
            {
                if (zoomFactor >= 1 && zoomFactor <= 10)
                {
                    zoomFactor += 1F;

                    newX = newX - ((mP.X - newX) / (zoomFactor - 1));
                    newY = newY - ((mP.Y - newY) / (zoomFactor - 1));
                }
                else if (zoomFactor == 0.5)
                {
                    zoomFactor = zoomFactor * 2;
                    newX = 2 * newX - mP.X ;
                    newY = 2 * newY - mP.Y ;
                }
                else if (zoomFactor < 0.5)
                {
                    zoomFactor = zoomFactor * 2;
                    newX = 2 * newX - mP.X;
                    newY = 2 * newY - mP.Y;
                }
            }

            else if (e.Delta < 0)
            {
                if (zoomFactor >2)
                {
                    zoomFactor -= 1F;
                    newX = newX + (((mP.X - newX)) / (zoomFactor+1 ));
                    newY = newY + (((mP.Y - newY)) / (zoomFactor+1));
                }
                else if (zoomFactor == 2) {
                    zoomFactor -= 1F;

                    newX = newX + ((mP.X - newX)/2);
                    newY = newY + ((mP.Y - newY)/2);
                }else if(zoomFactor <= 1 && zoomFactor > 0.2)
                {
                    zoomFactor = zoomFactor / 2;

                    newX = newX + ((mP.X - newX) / 2);
                    newY = newY + ((mP.Y - newY) / 2);

                }


            }
                       
            panel1.Invalidate();

        }
    }
}
Avatar billede aaberg Nybegynder
30. november 2010 - 14:00 #9
Her er et forslag.

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.
Avatar billede aaberg Nybegynder
30. november 2010 - 14:02 #10
I stedet for at lytte til events, overrider du metoder. Eksempeltvis skal du ikke lytte til MouseMove eventet, men override OnMouseMove().
Avatar billede bildsoe Nybegynder
30. november 2010 - 14:16 #11
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"?
Avatar billede bildsoe Nybegynder
30. november 2010 - 16:26 #12
Ok. Det løste faktisk problemet med flickering. Fantastisk tak. Hvordan kan det være man ikke skal bruge dispose når det er en nedarvet control?
Avatar billede aaberg Nybegynder
30. november 2010 - 18:37 #13
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.
Avatar billede bildsoe Nybegynder
01. december 2010 - 11:21 #14
Ok, super tak - indtil videre fungerer musen faktisk, så tak for det. Laver du ikke et svar så får du points'ne :)
Avatar billede aaberg Nybegynder
01. december 2010 - 12:26 #15
svar.  :)
Avatar billede bildsoe Nybegynder
01. december 2010 - 13:06 #16
Det er stadig en kommentar :D
Avatar billede aaberg Nybegynder
01. december 2010 - 14:30 #17
doh!
Avatar billede bildsoe Nybegynder
01. december 2010 - 14:46 #18
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.
Avatar billede bildsoe Nybegynder
01. december 2010 - 14:49 #19
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;

            if (mouse.Button == MouseButtons.Right)
            {

                mouseDown = mouse.Location;

                mouseDown.X = mouseDown.X - newX;
                mouseDown.Y = mouseDown.Y - newY;

            }

            else if (mouse.Button == MouseButtons.Left)
            {

                if (_rgn.IsVisible(mouse.Location, _dc))
                {

                    objColor = 2;
                    startDrag = true;
                    this.Invalidate();

                }
                else
                {
                    objColor = 1;
                    this.Invalidate();
                }


            }

           

        }

MouseMove:



        protected override void OnMouseMove(MouseEventArgs e)
        {
            MouseEventArgs mouse = e as MouseEventArgs;

            if (mouse.Button == MouseButtons.Right)
            {
                PointF mousePosNow = mouse.Location;

                float deltaX = mousePosNow.X - mouseDown.X;
                float deltaY = mousePosNow.Y - mouseDown.Y;

                newX = deltaX;
                newY = deltaY;

                this.Invalidate();

            }


           
            if (startDrag)
            {
               
                XPosition = ((mouse.X - newX)*(1/zoomFactor))-objWidth/2;
                YPosition = ((mouse.Y - newY)*(1/zoomFactor))-objHeight/2;


                this.Invalidate();

               
            }
           

        }

Og mouseUp

protected override void OnMouseUp(MouseEventArgs e)
        {

            if (startDrag)
            {
                startDrag = false;
            }
        }
Avatar billede aaberg Nybegynder
01. december 2010 - 15:00 #20
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?
Avatar billede bildsoe Nybegynder
01. december 2010 - 15:19 #21
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.

Projektet ligger her:
http://www.bildsoe.dk/c-sharp/panel2.zip
Avatar billede aaberg Nybegynder
01. december 2010 - 15:21 #22
jeg skal prøve at se på det lidt senere i dag :)
Avatar billede bildsoe Nybegynder
01. december 2010 - 15:23 #23
Cool... du kan godt få flere point, så kan jeg bare oprette et nyt spørgsmål med det i?
Avatar billede aaberg Nybegynder
01. december 2010 - 15:34 #24
Nej, det er ikke nødvendigt. Hvis der bliver flere ting senere, kan du altid oprette nye spørgsmål til dem, men du behøver ikke at gøre det nu.
Avatar billede aaberg Nybegynder
01. december 2010 - 22:57 #25
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.
Avatar billede bildsoe Nybegynder
02. december 2010 - 09:39 #26
Ok, super - tak for det.

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?
Avatar billede bildsoe Nybegynder
02. december 2010 - 09:52 #27
Fandt iøvrigt lige denne artikel. Kan være den kunne være interessant for andre i samme situation som mig.

http://inchoatethoughts.com/custom-drawing-controls-in-c-manual-double-buffering
Avatar billede aaberg Nybegynder
02. december 2010 - 11:01 #28
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.
Avatar billede bildsoe Nybegynder
02. december 2010 - 11:33 #29
Ok, det kunne jeg selvfølgelig gøre ja.

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å.

Tak for alt hjælpen.
Avatar billede bildsoe Nybegynder
02. december 2010 - 12:57 #30
Hov, men lige et spørgsmål:

"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?
Avatar billede aaberg Nybegynder
02. december 2010 - 13:05 #31
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.
Avatar billede aaberg Nybegynder
02. december 2010 - 13:06 #32
Og DrawLine metoden er tung, uanset om du tegner inden for det synlige område eller ej.
Avatar billede bildsoe Nybegynder
02. december 2010 - 14:34 #33
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:

if (startDrag)
            {
               
                XPosition = ((mouse.X - newX)*(1/zoomFactor))-(objWidth/2);
                YPosition = ((mouse.Y - newY)*(1/zoomFactor))-(objHeight/2);

                RectangleF rct;

                if (zoomFactor > 2)
                {
                  rct = new Rectangle(0, 0, Math.Max(this.Width, 1), Math.Max(this.Height, 1));
                }
                else if (zoomFactor < 1)
                {
                    rct = new RectangleF(newX + (XPosition * zoomFactor) - 200 , newY + (YPosition * zoomFactor) - 200 , (objWidth + 400 ), (objHeight + 400 ));

                }else
                {
                    rct = new RectangleF(newX + (XPosition * zoomFactor) - 100 * zoomFactor, newY + (YPosition * zoomFactor) - 100 * zoomFactor, (objWidth + 200 * zoomFactor), (objHeight + 200 * zoomFactor));
                }

                    rg = new Region(rct);
                   
                    this.Invalidate(rg);

            }

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);
                }
            }
Avatar billede bildsoe Nybegynder
02. december 2010 - 14:37 #34
Hov og jeg har rettet _rgn i paint-event til rg, men det burde ikke gøre nogen forskel...
Avatar billede bildsoe Nybegynder
02. december 2010 - 14:54 #35
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.
Avatar billede Ny bruger Nybegynder

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.

Loading billede Opret Preview
Kategori
IT-kurser om Microsoft 365, sikkerhed, personlig vækst, udvikling, digital markedsføring, grafisk design, SAP og forretningsanalyse.

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester



IT-JOB

Cognizant Technology Solutions Denmark ApS

Senior Delivery Manager

Udviklings- og Forenklingsstyrelsen

Erfaren teamleder til årsopgørelsen

Queue-it ApS

Team Lead Engineering