开篇!WPF自定义控件(1)——转盘菜单

注册园子账号有半年了吧,一直想写点什么,但不知道从哪写起。最近一个月在无锡一个公司实习,主要做的就是WPF相关的开发,虽然之前没接触过WPF,但好歹学过C#,上手理解起来还算容易。

最近做了个自定义圆形转盘菜单控件,效果如下:

实现过程:

  1. 圆周平分孩子节点(这里孩子节点放的都是图标)
  2. 实现点击中间图标,外围图标旋转一周动画
  3. 实现鼠标点击拖动跟随旋转动画

关键代码:

  1.圆周平分孩子节点。先遍历所有孩子节点,再减去中间图标,得到要平分圆周的图标个数,然后得到平分角度,最后根据角度、外圆半径,利用三角函数设置每个图标的TopPropertyLeftProperty属性

 1 //等分圆周 显示中间和周围图标
 2         private void DivideCircleOnShowMenu()
 3         {
 4             //获取孩子节点
 5             UIElementCollection children = this.Children;
 6             FrameworkElement f;
 7             
 8             //外圆半径
 9             double outCircleRadius = this.Width/2-IconWidthAndHeight/2;
10 
11             //平分度数
12             double divideEquallyAngle = 360 / (children.Count - 1);
13 
14             for (int i = 0; i < children.Count; i++)
15             {
16                 f = children[i] as FrameworkElement;
17                 //第一个中间图标
18                 if (i == 0)
19                 {
20                     if (ShowMenu)
21                     {
22                         f.SetValue(Canvas.TopProperty, outCircleRadius );
23                         f.SetValue(Canvas.LeftProperty, outCircleRadius );
24                         if (OperateType == OperateEnum.MouseOperate)
25                         {
26                             f.MouseDown += F_MouseDown;
27                         }
28                         else
29                         {
30                             f.TouchUp += F_TouchUp;
31                         }
32                     }
33 
34                     else
35                     {
36                         f.SetValue(Canvas.TopProperty, outCircleRadius - f.Width / 2);
37                         f.SetValue(Canvas.LeftProperty, outCircleRadius - f.Width / 2);
38                         f.Visibility = Visibility.Hidden;
39                     }
40                 }
41 
42                 else
43                 {
44                     //内角度数  角度转换为弧度
45                     double innerAngle = divideEquallyAngle * (i-1) * Math.PI / 180;
46 
47                     //TOP距离
48                     double topHeight = outCircleRadius - Math.Cos(innerAngle) * outCircleRadius;
49 
50                     //Left距离
51                     double leftWidth = Math.Sin(innerAngle) * outCircleRadius;
52 
53                     if (innerAngle <= 180)
54                     {
55                         f.SetValue(Canvas.TopProperty, topHeight );
56                         f.SetValue(Canvas.LeftProperty, outCircleRadius + leftWidth );
57                     }
58                     if (innerAngle > 180)
59                     {
60                         f.SetValue(Canvas.TopProperty, topHeight);
61                         f.SetValue(Canvas.LeftProperty, outCircleRadius - leftWidth );
62                     }
63                 }
64             }
65         }

   2.点击中间图标,外围图标旋转一周动画。这里用到的是RotateTransform动画,动画很简单,就是绕点旋转。但是还得设置每个图标的RenderTransformOrigin,这个属性是一个相对Point,以每个图标的左上角为坐标原点,向下和向右为正,比如设为(0.5,0.5)表示每个图标绕本身的中心点旋转,(1,1)表示绕本身的右下角旋转。这里我是选择让整个canvas绕着自身中心点旋转,然后再让每个图标绕自身反转,就可以实现图标方向相对不变。最后我这里rotateNumber(旋转度数)是自定义的依赖属性,可以在前台XAML应用这个控件时自行设置的。

 1  //canvans旋转
 2         private void RotateAnimation(UIElement uIelement, double rotateNumber)
 3         {
 4             RotateTransform rtf = new RotateTransform();
 5             uIelement.RenderTransform = rtf;
 6             uIelement.RenderTransformOrigin = new Point(0.5, 0.5);
 7 
 8             //定义动画路径和事件
 9             DoubleAnimation dbAnimation = new DoubleAnimation(0, rotateNumber,
10                 new Duration(TimeSpan.FromSeconds(RotateSpeed)));
11 
12             //开始动画
13             rtf.BeginAnimation(RotateTransform.AngleProperty, dbAnimation);

  3.实现鼠标拖动跟随旋转动画。这里主要就是MouseDown,MouseMove和MouseUp事件。主要是MouseMove中要获取到每次移动旋转的度数,以及转动方向的判断(顺时针转还是逆时针转)。求每次拖动旋转的度数我用到了高中的数学知识,两向量夹角的余弦公式(没错,就是它了,这也是我目前能想到的办法了= =)。根据这个公式算出旋转度数后,就要开始根据每次点击点和结束点的象限来判断旋转方向。

  

//拖动图标旋转事件
        private void Rotate_MouseMove(object sender, MouseEventArgs e)
        {
            Point mAfter = e.GetPosition(this); //获取鼠标移动过程中的坐标

            Point n1 = new Point(centerP.X - mBefore.X, centerP.Y - mBefore.Y);
            Point n2 = new Point(centerP.X - mAfter.X, centerP.Y - mAfter.Y);

            //n1*n2
            double n1n2 = n1.X * n2.X + n1.Y * n2.Y;
            //n1的模
            double n1mo = Math.Sqrt(Math.Pow(n1.X, 2) + Math.Pow(n1.Y, 2));
            //n2的模
            double n2mo = Math.Sqrt(Math.Pow(n2.X, 2) + Math.Pow(n2.Y, 2));


            //得带旋转角度
            double rotateNum = Math.Acos(n1n2 / (n1mo * n2mo));

            //相对坐标原点位置
            Point potM = new Point();
            potM.X = mAfter.X - centerP.X;
            potM.Y = centerP.Y - mAfter.Y;
            Point potD = new Point();
            potD.X = mBefore.X - centerP.X;
            potD.Y = centerP.Y - mBefore.Y;


            //当鼠标移动超出边界时停止旋转
            if (mAfter.X < 0 || mAfter.X > this.Width || mAfter.Y < 0 || mAfter.Y > this.Height)
            {
                this.MouseMove -= Rotate_MouseMove;

            }

            else
            {
                if (GetcLockwise(potD, potM))
                {
                    rotateAng += rotateNum;
                }
                else
                {
                    rotateAng -= rotateNum;
                }
            }

            //执行旋转动画

            IconRotateAnimation(-rotateAng);
            CanvansRotateAnimation(rotateAng);

        }

        /// <summary>
        /// 获取顺时针还是逆时针
        /// </summary>
        /// <param name="potD">按下坐标</param>
        /// <param name="potM">移动坐标</param>
        /// <returns>True:顺,False:逆</returns>
        private bool GetcLockwise(Point potD, Point potM)
        {
            if (potM.Y >= 0 && potD.Y >= 0) //一二象限
            {
                return potM.X >= potD.X;
            }
            if (potM.Y < 0 && potD.Y < 0)   //三四象限
            {
                return potM.X <= potD.X;
            }
            if (potM.X >= 0 && potD.X >= 0) //一四象限
            {
                return potM.Y <= potD.Y;
            }
            if (potM.X < 0 && potD.X < 0)   //二三象限
            {
                return potM.Y >= potD.Y;
            }
            else
            {
                return true;
            }
        }    

  最后,又增加了触摸滑动功能,其实大体上是就是把对应的Mouse事件换成Touch事件就OK了,但效果实现上可能需要略微的改动。本来还要做成旋转带惯性的效果的,只可惜太菜,网上资源也比较少,只能无告而终。现在觉得做WPF的控件开发也蛮有意思的,蛮锻炼人的逻辑思维能力的。开篇文到此为止了,再接再励吧!

附上Demo源码:旋转菜单控件

posted @ 2015-02-07 10:38  胡xiao波  阅读(4518)  评论(4编辑  收藏  举报