Click here to Skip to main content
15,891,204 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more:
I'm creating a control which represents some kind of arrow (including line). I create a class called GEdge inheriting the class Label. After creating a GraphicsPath (which is arrow-shaped), I assign the Region property of the my customized Label a new Region from that GraphicsPath. It displays OK on my form, I can move it well, but when try rotating one of two heads of the edge around another, it is also well if I do that slowly, otherwise my arrow (which is in fact my Region of the customized Label) disappears sometimes (I mean it can disappear or not). If I release the mouse, I will lose my arrow, but if I try continuing to rotate the arrow, I can see it display again on my form.

I can't understand why it is.
Here is some code of mine:

This is the overridden OnMouseMove method for the customized Label (my arrow):
C#
protected override void OnMouseMove(MouseEventArgs e)
        {

            if (e.Button == MouseButtons.Left)
            {
                //Check if mouse holds down on the middle of the arrow then just move it or translate it, not rotate.
                if (downPoint == MouseDownPoint.Middle)
                {
                    Left += e.X - oldPos.X;
                    Top += e.Y - oldPos.Y;
                }
                else //if mouse holds down on one of the two heads of the arrow then start rotating.
                {
                    if (downPoint == MouseDownPoint.First)
                    {
                        if (a != ArrowType.LastToFirst)
                            TurnTailPointTo(Parent.PointToClient(PointToScreen(e.Location)));
                        else TurnHeadPointTo(Parent.PointToClient(PointToScreen(e.Location)));
                    }
                    else if (a != ArrowType.LastToFirst)
                        TurnHeadPointTo(Parent.PointToClient(PointToScreen(e.Location)));
                    else TurnTailPointTo(Parent.PointToClient(PointToScreen(e.Location)));                    
                }
                Invalidate();
                first = Parent.PointToClient(PointToScreen(localFirst));
                last = Parent.PointToClient(PointToScreen(localLast));
            }
            base.OnMouseMove(e);
        }


TurnHeadPointTo and TurnTailPointTo are used to rotate the hold-down head to the specified point (the point where the mouse pointer is being located at when moving).

Here are the code for TurnHeadPointTo and TurnTailPointTo:

C#
void TurnPointTo(Point Tip, Point p)
        {
            if (Parent != null) Parent.SuspendLayout();
            Size = new Size(2 * Math.Abs(Tip.X - p.X) + 10 + fontHeight, 2 * Math.Abs(Tip.Y - p.Y) + 10 + fontHeight);
            turning = true;
            Location = new Point(Math.Min(p.X, Tip.X) - Width / 2, Math.Min(p.Y, Tip.Y) - Height / 2);
            turning = false;
            if (Parent != null) Parent.ResumeLayout();
        }
public void TurnHeadPointTo(Point p)
        {
            if (a != ArrowType.LastToFirst)
            {
                TurnPointTo(first, p);
                localLast = new Point(p.X - Location.X, p.Y - Location.Y);
                localFirst = new Point(first.X - Location.X, first.Y - Location.Y);
                last = p;
            }
            else
            {
                TurnPointTo(last, p);
                localFirst = new Point(p.X - Location.X, p.Y - Location.Y);
                localLast = new Point(last.X - Location.X, last.Y - Location.Y);
                first = p;
            }
            Invalidate();            
        }
public void TurnTailPointTo(Point p)
        {
            if (a != ArrowType.LastToFirst)
            {
                TurnPointTo(last, p);
                localLast = new Point(last.X - Location.X, last.Y - Location.Y);
                localFirst = new Point(p.X - Location.X, p.Y - Location.Y);
                first = p;
            }
            else
            {
                TurnPointTo(first, p);
                localLast = new Point(p.X - Location.X, p.Y - Location.Y);
                localFirst = new Point(first.X - Location.X, first.Y - Location.Y);
                last = p;
            }
            Invalidate();                        
        }


My idea is each time the rotating action happens, I will resize the customized label to the new size which can contain the 2 points of 2 heads of the arrow, and relocate it, update the localLast and localFirst (the co-ordinates of two heads of the arrow calculated in the Label not in the Form), these updates will change how the arrow looks.

Here is the code for the OnPaint method of the customized Label:

C#
protected override void OnPaint(PaintEventArgs e)
        {
            GraphicsPath gp = new GraphicsPath();            
            Point tail = a == ArrowType.FirstToLast || a == ArrowType.None ? localFirst : localLast;
            Point head = a == ArrowType.FirstToLast || a == ArrowType.None ? localLast : localFirst;
            int h = Math.Max(tail.Y, head.Y) - Math.Min(tail.Y, head.Y);
            int w = Math.Max(tail.X, head.X) - Math.Min(tail.X, head.X);
            float alpha = (float)Math.Atan((float)h / w);
            int dx = (int)((thick / 2) * Math.Sin(alpha));
            int dy = (int)((thick / 2) * Math.Cos(alpha));
            int signDx = 1, signDy = 1, signHeadX = 1, signHeadY = 1;
            if (tail.X >= head.X && tail.Y >= head.Y)
            {
                signDx = -1;                
            }
            else if (tail.X <= head.X && tail.Y <= head.Y)
            {
                signDy = -1;
                signHeadX = signHeadY = -1;                
            }
            else if (tail.Y >= head.Y)
            {
                signDx = signDy = -1;
                signHeadX = -1;                
            }
            else
            {
                signHeadY = -1;                
            }
           
            Point p1 = new Point(tail.X + signDx * dx, tail.Y + signDy * dy);
            Point p2 = new Point(tail.X - signDx * dx, tail.Y - signDy * dy);
            Point p3 = new Point(head.X + signDx * dx, head.Y + signDy * dy);
            Point p4 = new Point(head.X - signDx * dx, head.Y - signDy * dy);
            if (a == ArrowType.None)//Just shape a line
            {
                gp.AddLines(new Point[] { p1, p2, p4, p3 });
            }
            else//Shape an arrow
            {
                Point k = new Point((int)(head.X + signHeadX * 10 * Math.Cos(alpha)), (int)(head.Y + signHeadY * 10 * Math.Sin(alpha)));
                p3 = new Point(k.X + signDx * dx, k.Y + signDy * dy);
                p4 = new Point(k.X - signDx * dx, k.Y - signDy * dy);
                
                k = new Point((int)Math.Round((head.X + signHeadX * 20 * Math.Cos(alpha))), (int)Math.Round((head.Y + signHeadY * 20 * Math.Sin(alpha))));
                dx = (int)(2 * thick * Math.Sin(alpha));
                dy = (int)(2 * thick * Math.Cos(alpha));
                Point p5 = new Point(k.X + signDx * dx, k.Y + signDy * dy);
                Point p6 = new Point(k.X - signDx * dx, k.Y - signDy * dy);
                gp.AddLines(new Point[] { p1, p2, p4, p6, head, p5, p3 });
            }
            gp.AddPath(val, true);
            Region = new Region(gp);            
            base.OnPaint(e);
        }


Please don't suggest me the idea using AdjustableArrowCap with Pen, I know about it but it is just for paint. I want I can move and rotate the arrow, so using Region is the best, I think so.

I also tried overriding CreateParams of the form and add the ExStyle with 0x02000000 as many other sites discussed about preventing flicker. I think my problem is some kind of flicker.

Hope you understand my idea as well as my code to help me out!
Your help would be highly appreciated! Thanks!
Posted
Updated 16-Oct-12 17:59pm
v4

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900