C#时间轴曲线图形编辑器开发1-基本功能

目录

一、前言

1、简介

2、开发过程

3、工程下载链接

二、基本功能实现

1、绘图面板创建

(1)界面布置

(2)显示面板代码

(3) 面板水平方向、竖直方向移动功能实现

(4)面板放大、缩小、恢复正常显示功能实现

(5)鼠标当前位置坐标值和界面显示

(6)面板实现效果操作演示

 2、数据曲线在面板上显示

(1)曲线数据变量定义

(2)曲线数据生成按钮事件函数

(3)曲线是否显示复选框事件函数

(4)面板上绘制曲线图形

(5)添加曲线清除按钮

(6)曲线显示操作演示

(7)代码工程下载链接

 3、鼠标在曲线上识别

(1)添加变量

(2)读取文本文件数据

(3)显示读取的两列数据曲线

(4)鼠标在两列数据曲线上识别

(5)代码下载链接

4、时间轴曲线当前所在位置指示线

 (1)添加全局变量

 (2)鼠标按键按下检测

(3)在DrawCure()中画竖直方向的指示线

(4)代码下载链接

5、鼠标拖动关键点

 (1)添加全局变量

(2) 添加几个测试的关键帧数据

(3)绘制关键帧和数据曲线

 (4)关键帧数据编辑

 (5)代码下载链接


一、前言

1、简介

通过制作简易的该曲线图形编辑器Demo,实现类似于Maya软件中的动画曲线编辑的功能。在指定的时间轴上插入关键帧,设置拖动该关键点,来编辑曲线数据。编辑好的数据导出保存在TXT文本中,同时曲线编辑器也可以读取之前导出的TXT文本数据再继续进行编辑。

 操作演示

 自制曲线编辑器Demo

 Maya软件动画编辑

2、开发过程

实现曲线编辑的功能,大致分五个过程,分包为

(1)曲线绘图面板创建、显示数据曲线

(2)鼠标经过数据曲线上识别到该曲线

(3)当前时间轴位置指示线创建

(4)鼠标拖动已设置的关键帧、改变曲线

(5)任意位置添加关键帧、对关键帧数据插值得到曲线、曲线数据导出和导入

3、工程下载链接

基本功能,打包下载链接

https://download.csdn.net/download/panjinliang066333/88112693

 全部功能测试代码

 https://download.csdn.net/download/panjinliang066333/88103699

二、基本功能实现

1、绘图面板创建

(1)界面布置

创建C# Winform工程。

①添加pictureBox1图形控件作为曲线图形显示面板,添加hHScrollBarX、VScrollBarY控件作为显示面板水平方向、竖直方向移动。

②添加3个Button按钮,用作图形面板放大、缩小、和恢复正常。

③添加绘制曲线的按钮、添加复选框用于选择是否显示曲线

④添加Label控件,用于显示当前鼠标所在曲线图形面板中的坐标位置。

(2)显示面板代码

①、窗口上添加Form1_Load,

        private void Form1_Load(object sender, EventArgs e)
        {
            if (xLineDatas.Length > dataF.Length)
            {
                hScrollBarX.Maximum = (int)xLineDatas.Max();
            }
            else
            {
                hScrollBarX.Maximum = dataF.Length;
            }
            isSpline1Show = !checkSpline1Show.Checked;

            timer1.Start();
            cureDraw = new SplineEdit(pictureBox1.Height, pictureBox1.Width);
            DrawCure();
        }

②、添加定时器,创建定时器事件函数

         private void timer1_Tick(object sender, EventArgs e)
        {
            DrawCure();
        }

③、DrawCure()封装的代码如下

        private void DrawCure()
        {
            if (pictureBox1.Height > 0 && pictureBox1.Width > 0)
            {
                cureDraw.Height = pictureBox1.Height;
                cureDraw.Width = pictureBox1.Width;
            }

            pictureBox1.Image = cureDraw.DrawImage();
        }

cureDraw为自定义封装的类SplineEdit实例对象。

④、DrawImage(),创建显示坐标系面板

        /// <summary>
        /// 生成坐标系图片面板
        /// </summary>
        /// <returns></returns>
        public Bitmap DrawImage()
        {
            ImageBoardInit();   //绘图面板
            return bitMap;
        }      

ImageBoardInit():绘制面板

        /// <summary>
        /// 创建绘图面板-显示坐标系
        /// </summary>
        private void ImageBoardInit()
        {
            int tempCountX = 0;
            int tempCountY = 0;  

            //1、绘制X、Y坐标轴

            bitMap = new Bitmap((int)width, (int)height);  //根据给定的高度和宽度创建一个位图图像
            graphics = Graphics.FromImage(bitMap);    //从指定的 objBitmap 对象创建 objGraphics 对象 (即在objBitmap对象中画图)

            //根据给定颜色(LightGray)填充图像的矩形区域 (背景)
            graphics.DrawRectangle(new Pen(boardColor, 1), 0, 0, width - 1, height - 1);    //画边框
            graphics.FillRectangle(new SolidBrush(backColor), 1, 1, width - 2, height - 2); //填充边框

            //画X轴,注意图像的原始X轴和Y轴计算是以左上角为原点,向右和向下计算的
            xAxisPoint1.X = xSpace + xSliceBegin;
            xAxisPoint1.Y = (height / 2) + ySliceBegin;    //
            xAxisPoint2.X = width;
            xAxisPoint2.Y = xAxisPoint1.Y;
            graphics.DrawLine(new Pen(new SolidBrush(axisColor), 2), xAxisPoint1, xAxisPoint2);

            //画Y轴
            yAxisPoint1.X = xSpace + xSliceBegin;
            yAxisPoint1.Y = height;
            yAxisPoint2.X = xSpace + xSliceBegin;
            yAxisPoint2.Y = 0;
            graphics.DrawLine(new Pen(new SolidBrush(axisColor), 2), yAxisPoint1, yAxisPoint2);

            //2、面板标题
            //graphics.DrawString("曲线编辑器", new Font("宋体", fontSize), new SolidBrush(Color.Blue), new PointF(width / 2, ySpace / 2));

            //3、画X轴上刻度、刻度说明            
            xSlice = (width - xSpace) / xSliceCount;
            ySlice = (height / 2) / (ySliceCount / 2);

            int tempCountX1 = (int)(-xSliceBegin / xSlice);
            int tempCountY1 = (int)Math.Abs((ySliceBegin / ySlice));
            tempCountX = tempCountX1 + xSliceCount;
            tempCountY = tempCountY1 + ySliceCount / 2;
            //画网格虚线
            Pen penDashed = new Pen(new SolidBrush(Color.Black));
            penDashed.DashStyle = DashStyle.Dash;
            for (int i = 1; i < tempCountX + 1; i++)
            {
                //X轴刻度虚线
                graphics.DrawLine(penDashed, new PointF(i * xSlice + xSpace + xSliceBegin, 0), new PointF(i * xSlice + xSpace + xSliceBegin, height));
                //X轴刻度值标识文字
                string xStr = (i * xSliceValue).ToString();
                int nStrLength = xStr.Length;
                graphics.DrawString(xStr, new Font("宋体", fontSize), new SolidBrush(Color.Black), new PointF(i * xSlice + xSpace - fontSize * nStrLength + xSliceBegin, height / 2 + fontSize + ySliceBegin));
            }
            //Y轴虚线、刻度文字-正半轴
            for (int i = 0; i < tempCountY + 1; i++)
            {
                graphics.DrawLine(penDashed, new PointF(xSpace + xSliceBegin, height / 2 - i * ySlice + ySliceBegin), new PointF(width, height / 2 - i * ySlice + ySliceBegin));
                //Y轴刻度值标识文字
                string yStr = (i * ySliceValue).ToString();
                int nStrLength = yStr.Length;
                if (i > 0)
                    graphics.DrawString(yStr, new Font("宋体", fontSize), new SolidBrush(Color.Black), new PointF(xSpace - fontSize * nStrLength + xSliceBegin, height / 2 - i * ySlice + ySliceBegin));

            }
            //Y轴虚线、刻度文字-负半轴
            for (int i = 1; i < tempCountY + 1; i++)
            {
                graphics.DrawLine(penDashed, new PointF(xSpace + xSliceBegin, height / 2 + i * ySlice + ySliceBegin), new PointF(width, height / 2 + i * ySlice + ySliceBegin));
                //Y轴刻度值标识文字
                string yStr = (-i * ySliceValue).ToString();
                int nStrLength = yStr.Length;
                graphics.DrawString(yStr, new Font("宋体", fontSize), new SolidBrush(Color.Black), new PointF(xSpace - fontSize * nStrLength + xSliceBegin, height / 2 + i * ySlice + ySliceBegin));
            }
            //4、原点刻度说明
            graphics.DrawString("0", new Font("宋体", fontSize + 2), new SolidBrush(Color.Black), new PointF(xSpace - fontSize * 2 + xSliceBegin, height / 2 + ySliceBegin));
        }

⑤、制作出的图形面板显示如下

(3) 面板水平方向、竖直方向移动功能实现

分别添加hScrollBarX_ValueChanged、vScrollBarY_ValueChanged控件事件函数

hScrollBarX_ValueChanged:

        private void hScrollBarX_ValueChanged(object sender, EventArgs e)
        {
            cureDraw.XSliceBegin = -hScrollBarX.Value * cureDraw.XSlice / cureDraw.XSliceValue;
            Invalidate();   //更新界面
        }

vScrollBarY_ValueChanged:

        private void vScrollBarY_ValueChanged(object sender, EventArgs e)
        {
            cureDraw.YSliceBegin = vScrollBarY.Value * cureDraw.YSlice / cureDraw.YSliceValue;
            Invalidate();   //更新界面
        }

cureDraw.XSliceBegin:X轴数值0点在屏幕起始像素位置

cureDraw.YSliceBegin:Y轴数值0点在屏幕起始像素位置

(4)面板放大、缩小、恢复正常显示功能实现

分别添加按钮事件函数btnScaleBigger_Click、btnScaleSmaller_Click、btnReset_Click

btnScaleBigger_Click:

        private void btnScaleBigger_Click(object sender, EventArgs e)
        {
            cureDraw.XSliceCount -= 1;
            cureDraw.YSliceCount -= 1;
            Invalidate();   //更新界面
        }

btnScaleSmaller_Click:

        private void btnScaleSmaller_Click(object sender, EventArgs e)
        {
            cureDraw.XSliceCount += 1;
            cureDraw.YSliceCount += 1;
            Invalidate();   //更新界面
        }

btnReset_Click:

        private void btnReset_Click(object sender, EventArgs e)
        {
            cureDraw.XSliceCount = 15;
            cureDraw.YSliceCount = 10;
            hScrollBarX.Value = 0;
            vScrollBarY.Value = 0;
        }

(5)鼠标当前位置坐标值和界面显示

①控件pictureBox1上添加pictureBox1_MouseMove事件函数

        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            int ex = 0, ey = 0;         //鼠标的坐标值
            int mouseToValueX = 0;
            float mouseToValueY = 0;    //鼠标位置对应的数值
            float fMouseToValueX = 0;

            ex = e.X;                   //鼠标在pictureBox1中X轴坐标值
            ey = e.Y;                   //鼠标在pictureBox1中Y轴坐标值
            fMouseToValueX = cureDraw.MousePosToValue_X(ex);
            mouseToValueX = (int)Math.Round(cureDraw.MousePosToValue_X(ex));
            mouseToValueY = cureDraw.MousePosToValue_Y(ey);
            if (mouseToValueX < 0)
            {
                mouseToValueX = 0;
            }
            if (mouseToValueX >= 0 && ex < pictureBox1.Width - 60)
            {
                labMousePos.Text = "X:" + mouseToValueX.ToString() + ";" + "Y:" + mouseToValueY.ToString();
                labMousePos.Location = new Point(ex + 50, ey + 40);
            }
            if (mouseToValueX >= 0 && ex > pictureBox1.Width - 60)
            {
                labMousePos.Text = "X:" + mouseToValueX.ToString() + ";" + "Y:" + mouseToValueY.ToString();
                labMousePos.Location = new Point(ex - 50, ey + 40);
            }
        }

②cureDraw.MousePosToValue_X:鼠标在pictureBox1面板上X轴的像素值转换为坐标系X轴的值

        /// <summary>
        /// 鼠标画板上横向位置转换成坐标x轴数值
        /// </summary>
        /// <param name="ex"></param>
        /// <returns></returns>
        public float MousePosToValue_X(float ex)
        {
            return (ex - xSpace - xSliceBegin) * xSliceValue / xSlice; ;
        }

③cureDraw.MousePosToValue_Y:鼠标在pictureBox1面板上Y轴的像素值转换为坐标系Y轴的值

        /// <summary>
        /// 鼠标画板上竖向位置转换成坐标y轴数值
        /// </summary>
        /// <param name="ey"></param>
        /// <returns></returns>
        public float MousePosToValue_Y(float ey)
        {
            return (ySliceBegin + height / 2 - ey) * ySliceValue / ySlice;
        }

(6)面板实现效果操作演示

 2、数据曲线在面板上显示

         曲线1为显示Sin曲线、曲线2为显示生成的随机数曲线。

         曲线1显示函数,需要分别提供X轴、Y轴的坐标位置数组,且两个数组长度必须相等


public void DrawXY(float[] xDatas, float[] yDatas, Color splineColor, float tension, bool isPointFill)

 曲线2显示函数,只需要曲线Y轴上位置数组。(数据的长度即为X轴数据)


public void DrawSpline(float[] yDatas, Color splineColor, float tension, bool isPointFill)

(1)曲线数据变量定义

        //曲线1
        bool isSpline1Show = false;
        float[] xLineDatas = new float[501];            //保存绘制的点 x、y轴位置
        float[] yLineDatas = new float[501];
        Color spline1Color = Color.Green;               //曲线1颜色  
        float tensionSpline1 = 0.5f;                    //曲线1粗细
        //曲线2
        bool isSpline2Show = false;
        float[] dataF = new float[10000];
        Color spline2Color = Color.Red;                 //曲线2颜色  
        float tensionSpline2 = 0.5f;                    //曲线2粗细
        Random rd = new Random();

(2)曲线数据生成按钮事件函数

分别添加曲线1按钮、曲线2按钮事件函数btnDrawSpline1_Click、btnDrawSpline2_Click

btnDrawSpline1_Click:

        private void btnDrawSpline1_Click(object sender, EventArgs e)
        {
            //isLineSplineShow = false;
            isSpline1Show = checkSpline1Show.Checked;

            //1、生成sin曲线,保存x、y坐标位置
            for (int i = 0; i < xLineDatas.Length; i++)
            {
                xLineDatas[i] = i;
                yLineDatas[i] = 32 * (float)Math.Sin(36 * i * Math.PI / 180);
            }

            Invalidate();   //更新界面
        }

btnDrawSpline2_Click:

        private void btnDrawSpline2_Click(object sender, EventArgs e)
        {            
            for (long i = 1; i < dataF.Length; i++)
            {
                dataF[i] = (float)rd.Next(-20, 20);
            }
            isSpline2Show = true;
        }

(3)曲线是否显示复选框事件函数

分别添加曲线1、曲线2显示复选框事件函数:heckSpline1Show_CheckedChanged、checkSpline2Show_CheckedChanged

heckSpline1Show_CheckedChanged

        private void checkSpline1Show_CheckedChanged(object sender, EventArgs e)
        {
            isSpline1Show = checkSpline1Show.Checked;
        }

checkSpline2Show_CheckedChanged

        private void checkSpline2Show_CheckedChanged(object sender, EventArgs e)
        {
            isSpline2Show = checkSpline2Show.Checked;
        }

(4)面板上绘制曲线图形

修改前面的DrawCure()函数

         private void DrawCure()
        {

            if (pictureBox1.Height > 0 && pictureBox1.Width > 0)        //若窗口最小化时候,则Height、Width都为0。DrawImage()创建图像会出错
            {
                cureDraw.Height = pictureBox1.Height;
                cureDraw.Width = pictureBox1.Width;
            }
            pictureBox1.Image = cureDraw.DrawImage();

            //曲线1显示
            if (isSpline1Show == true)
            {
                cureDraw.DrawXY(xLineDatas, yLineDatas, spline1Color, tensionSpline1, false);
            }

            //曲线2显示
            if (isSpline2Show == true)
            {
                cureDraw.DrawSpline(dataF, spline2Color, tensionSpline2, false);
            }           
        }

(5)添加曲线清除按钮

用于清除曲线数据和不再显示曲线

        private void btnDataClear_Click(object sender, EventArgs e)
        {
            for (int i = 0; i < xLineDatas.Length; i++)
            {
                xLineDatas[i] = 0;
                yLineDatas[i] = 0;
            }

            for (int i = 0; i < dataF.Length; i++)
            {
                dataF[i] = 0.0f;
            }
            isSpline2Show = false;
        }

(6)曲线显示操作演示

(7)代码工程下载链接

该链接为个人使用。

链接:https://pan.baidu.com/s/1K6HFh56YfvwvoAH5nrdJmg 
提取码:vfym 
--来自百度网盘超级会员V4的分享

 3、鼠标在曲线上识别

        读取TXT文本文件中的两列数据,将两列数据以曲线显示出来,当鼠标移动经过曲线时显示当前坐标的Label控件字体颜色变成当前识别的曲线颜色。

 曲线识别效果

(1)添加变量

        //曲线-导入数据
        bool isSplineAxis = false;
        float[] axis1Data, axis2Data;
        Color Axis1Color = Color.Red;                   //Axis1曲线颜色
        Color Axis2Color = Color.Blue;                  //Axis2曲线颜色
        bool isMouseOnAxis1, isMouseOnAxis2;            //检测鼠标是否在曲线上

(2)读取文本文件数据

     文本文件数据,附在下载链接中。

如效果图所示添加按钮控件,导入读取文本数据。添加btnImportTxtDatas_Click事件函数

        private void btnImportTxtDatas_Click(object sender, EventArgs e)
        {
            try
            {

                OpenFileDialog ofd = new OpenFileDialog();
                ofd.Filter = "All files(*.*)|*.*|文本文件(*.csv)|*.csv|文本文件(*.txt)|*.txt";
                if (ofd.ShowDialog() != DialogResult.OK)
                {
                    return;
                }
                int dataColumNums = 0;              //动作文件列表数
                int dataLineNums;                   //动作行数
                double[,] axisDataArrayRead;                


                string[] lines = File.ReadAllLines(ofd.FileName, Encoding.Default).ToArray();
                dataColumNums = CharNum(lines[0], ";");
                dataLineNums = lines.Length;

                axisDataArrayRead = new double[dataColumNums, dataLineNums];
                for (int i = 0; i < lines.Length; i++)
                {
                    string[] seg = lines[i].Split(';');    //英文逗号分隔符
                    for (int j = 0; j < dataColumNums; j++)
                    {
                        axisDataArrayRead[j, i] = Convert.ToDouble(seg[j]);
                    }
                }
                //
                axis1Data = new float[dataLineNums];
                axis2Data = new float[dataLineNums];
                for(int i=0;i<dataLineNums;i++)
                {
                    axis1Data[i] = (float)axisDataArrayRead[0, i];
                    axis2Data[i] = (float)axisDataArrayRead[1, i];
                }
                isSplineAxis = true;
                hScrollBarX.Maximum = dataLineNums + 10;
            }
            catch
            {
                isSplineAxis = false;
                MessageBox.Show("文件解析异常", "提示");
            }
        }

(3)显示读取的两列数据曲线

   修改DrawCure(),添加代码

        private void DrawCure()
        {
            if (pictureBox1.Height > 0 && pictureBox1.Width > 0)        //若窗口最小化时候,则Height、Width都为0。DrawImage()创建图像会出错
            {
                cureDraw.Height = pictureBox1.Height;
                cureDraw.Width = pictureBox1.Width;
            }            
            pictureBox1.Image = cureDraw.DrawImage();

            //曲线1显示
            if (isSpline1Show == true)
            {
                cureDraw.DrawXY(xLineDatas, yLineDatas, spline1Color, tensionSpline1, false);
            }
            //曲线2显示
            if (isSpline2Show == true && spline2DataState == 1)
            {
                cureDraw.DrawSpline(dataF, spline2Color, tensionSpline2, false);                
            }

            //读取的文本文件两列数据显示
            if(isSplineAxis)
            {
                if (checkAxis1.Checked)
                {
                    cureDraw.DrawSpline(axis1Data, Axis1Color, 0.5f, false);
                }
                if (checkAxis2.Checked)
                {
                    cureDraw.DrawSpline(axis2Data, Axis2Color, 0.5f, false);
                }                
                
            }

        }

(4)鼠标在两列数据曲线上识别

修改timer1_Tick()函数

        private void timer1_Tick(object sender, EventArgs e)
        {
            DrawCure();

            //检测鼠标是否在曲线上
            if(isSplineAxis)
            {
                int nIndex = 0;

                //第一列数据曲线识别
                if (checkAxis1.Checked)
                {
                    nIndex = currentValue_X;
                    if (nIndex >= axis1Data.Length)
                    {
                        nIndex = axis1Data.Length-1;
                    }
                    float selectValue_Y_Axis1 = axis1Data[nIndex];
                    if (Math.Abs(selectValue_Y_Axis1 - currentValue_Y) < cureDraw.YSliceValue/8)
                    {
                        isMouseOnAxis1 = true;
                        labMousePos.ForeColor = Axis1Color;
                    }
                    else
                    {
                        isMouseOnAxis1 = false;
                        //labMousePos.ForeColor = Color.Black;
                    }
                }

                //第二列数据曲线识别
                if (checkAxis2.Checked)
                {
                    nIndex = currentValue_X;
                    if (nIndex >= axis1Data.Length)
                    {
                        nIndex = axis1Data.Length-1;
                    }
                    float selectValue_Y_Axis2 = axis2Data[nIndex];
                    if (Math.Abs(selectValue_Y_Axis2 - currentValue_Y) < cureDraw.YSliceValue / 8)
                    {
                        isMouseOnAxis2 = true;
                        labMousePos.ForeColor = Axis2Color;
                    }
                    else
                    {
                        isMouseOnAxis2 = false;
                        //labMousePos.ForeColor = Color.Black;
                    }
                }


                if(isMouseOnAxis1==false && isMouseOnAxis2==false)
                {
                    labMousePos.ForeColor = Color.Black;
                }

            }

        }

(5)代码下载链接

该链接为个人使用。

链接:https://pan.baidu.com/s/1FQNVWU-PLIgaS3KmQz_BGw 
提取码:dtcj 
--来自百度网盘超级会员V4的分享

4、时间轴曲线当前所在位置指示线

     创建一条竖直方向的线,用来指示当前鼠标点击所在的X轴位置。该线的作用是在编辑数据和运行数据的时候可以很方便的看出当前所运行到的时间点。

     指示线效果如下所示

 (1)添加全局变量

        //鼠标当前在画图面板上的像素坐标,对应的坐标轴数值
        int currentValue_X;
        float currentValue_Y;
        int last_CurrentValueX = 0;

        bool isLeftButtonDowm = false, isMiddleButtonDown = false, isRightButtonDown = false;

 (2)鼠标按键按下检测

控件pictureBox1添加pictureBox1_MouseDown、pictureBox1_MouseUp事件函数,用来检测鼠标按键按下和抬起。

pictureBox1_MouseDown

        private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                isLeftButtonDowm = true;
            }
            if (e.Button == MouseButtons.Middle)
            {
                isMiddleButtonDown = true;
            }
            if (e.Button == MouseButtons.Right)
            {
                isRightButtonDown = true;
            }
        }

pictureBox1_MouseUp

        private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                isLeftButtonDowm = false;
            }
            if (e.Button == MouseButtons.Middle)
            {
                isMiddleButtonDown = false;
            }
            if (e.Button == MouseButtons.Right)
            {
                isRightButtonDown = false;
            }
        }

(3)在DrawCure()中画竖直方向的指示线

            //时间轴位置指示线
            cureDraw.DrawCurrentLine(last_CurrentValueX);
            if (isLeftButtonDowm)
            {
                last_CurrentValueX = currentValue_X;
            }

 DrawCurrentLine()函数如下封装

        /// <summary>
        /// 绘制当前鼠标所在X轴位置的数值,指示直线
        /// </summary>
        /// <param name="CurrentValue_X"></param>
        public void DrawCurrentLine(int CurrentValue_X)
        {

            Brush currentB = new SolidBrush(Color.Red);        //
            float ex = ValueToMousePoint_ex(CurrentValue_X);
            graphics.DrawLine(new Pen(currentB), ex, 0, ex, height);

        }

(4)代码下载链接

该链接为个人使用。

链接:https://pan.baidu.com/s/13c0C1dTqTgW-5G3uYWekOA 
提取码:u6n0 
--来自百度网盘超级会员V4的分享

5、鼠标拖动关键点

鼠标选中关键帧数据点,滑动鼠标实现对关键帧数据点的拖动。

 (1)添加全局变量

需要拖拽的点叫关键帧点,每个关键帧点包括两个数据:X轴中位置、Y轴中位置。创建字典集合keyListDatas 来保存关键帧数据。

        //曲线编辑
        Dictionary<int, float> keyListDatas = new Dictionary<int, float>();
        Color editSplineColor = Color.Blue;                  //曲线颜色
        bool isMouseOnKeyPoint = false;     //鼠标是否在关键点上检测
        bool isKeyEditDataMoveCan = false;
        int dataLength = 0;
        float[] myEditDatas;                //曲线数据-绘制曲线显示
        float[] keyEditDatas_X;             //关键帧-方框点绘制-X轴数据
        float[] keyEditDatas_Y;             //关键帧-方框点绘制-X轴数据

        bool isDataEdit = false;
        int keyEditFrame = 0;

(2) 添加几个测试的关键帧数据

在Form1_Load()添加下面代码,添加关键帧数据

            //曲线编辑器数据
            dataLength = int.Parse(txtDataLength.Text);
            myEditDatas = new float[dataLength];
            //字典集合中添加4个随机数据
            keyListDatas.Add(100 + dataLength / 2, 40.0f);
            keyListDatas.Add(0, 0.0f);
            keyListDatas.Add(dataLength / 2, 10.0f);
            keyListDatas.Add(dataLength - 1, 0.0f);
            keyListDatas[dataLength / 2] = 50.0f;           //修改字典集合中的值
            KeyDataTrans();

KeyDataTrans():

        /// <summary>
        /// 关键帧数据集合转数组
        /// </summary>
        private void KeyDataTrans()
        {                       
            //关键点得数据值,对应到曲线数据上
            foreach (KeyValuePair<int, float> item in keyListDatas)
            {
                int key = item.Key;
                float fValue = item.Value;
                myEditDatas[key] = fValue;
            }

            //
            List<float> fListTemp_X = new List<float> { };
            List<float> fListTemp_Y = new List<float> { };
            foreach (KeyValuePair<int, float> item in keyListDatas)
            {
                int key = item.Key;
                float fValue = item.Value;

                fListTemp_X.Add(key);
                fListTemp_Y.Add(fValue);
            }
            keyEditDatas_X = fListTemp_X.ToArray();
            keyEditDatas_Y = fListTemp_Y.ToArray();
        }

(3)绘制关键帧和数据曲线

在DrawCure()中添加代码

            //曲线编辑器
            if (keyEditDatas_Y.Length>0)
            {
                cureDraw.DrawPoint(keyEditDatas_X, keyEditDatas_Y, editSplineColor, tensionSpline1, true);
            }
            if (myEditDatas.Length > 0)
            {
                cureDraw.DrawSpline(myEditDatas, editSplineColor, 0.5f, false);
            }

 (4)关键帧数据编辑

在timer1_Tick()添加KeyPointEdit()

KeyPointEdit():

        private void KeyPointEdit()
        {
            //
            KeyDataTrans();

            //
            PointF[] pf=new PointF[keyEditDatas_X.Length];
            for(int i=0;i<keyEditDatas_X.Length;i++)
            {
                pf[i].X = cureDraw.ValueToMousePoint_ex((int)keyEditDatas_X[i]);
                pf[i].Y = cureDraw.ValueToMousePoint_ey(keyEditDatas_Y[i]);
            }

            //
            label7.BackColor = Color.Blue ;
            foreach (PointF pp  in pf)
            {
                //检测鼠标是否在圆点上
                GraphicsPath vGraphicsPath = new GraphicsPath();
                vGraphicsPath.AddEllipse(pp.X - 5, pp.Y - 5, 10, 10);       // 添加需要检测识别的点
                Region vRegion = new Region(vGraphicsPath);
                isMouseOnKeyPoint = vRegion.IsVisible(ex, ey);           // 判断点是否在圆中

                if (isMouseOnKeyPoint)
                {
                    Cursor.Current = Cursors.SizeNS;                    //设置鼠标为手指形
                    label7.BackColor = Color.Lime;
                    if(isLeftButtonDowm)
                    {
                        //isKeyEditDataMoveCan = true;
                    }
                    isKeyEditDataMoveCan = true;
                }
            }
            

            //
            if (isKeyEditDataMoveCan)
            {
                //
                for (int i = 0; i < keyEditDatas_X.Length; i++)
                {
                    if (Math.Abs(currentValue_X - keyEditDatas_X[i]) < 2)
                    {
                        isDataEdit = true;
                        keyEditFrame = (int)keyEditDatas_X[i];
                    }

                }

            }
            if (isLeftButtonDowm == false)
            {
                isDataEdit = false;
                isKeyEditDataMoveCan = false;
            }


            //
            if(isDataEdit)
            {
                keyListDatas[keyEditFrame] = currentValue_Y;
            }
        }

 (5)代码下载链接

该链接为个人使用。

链接:https://pan.baidu.com/s/1HVAWpqR-68OY98KmgUlJbw 
提取码:hxvw 
--来自百度网盘超级会员V4的分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/50012.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024届IC秋招兆易创新数字IC后端笔试面试题

数字IC后端实现PR阶段设计导入需要哪些文件&#xff1f; 设计导入需要的文件如下图所示。这个必须熟练掌握。只要做过后端训练营项目的&#xff0c;对这个肯定是比较熟悉的。大家还要知道每个input文件的作用是什么。 在吾爱IC后端训练营Cortexa7core项目中&#xff0c;你认为…

华为刷题:HJ3明明随机数

import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main {public static void main(String[] args) {Scanner scan new Scanner(System.in);int N scan.nextInt();int[] arr new int[N];for (int i 0; i < N; i) {int n sca…

数据库压力测试方法小结

一、前言 在前面的压力测试过程中&#xff0c;主要关注的是对接口以及服务器硬件性能进行压力测试&#xff0c;评估请求接口和硬件性能对服务的影响。但是对于多数Web应用来说&#xff0c;整个系统的瓶颈在于数据库。 原因很简单&#xff1a;Web应用中的其他因素&#xff0c;…

基于ChatGPT聊天的零样本信息提取7.25

基于ChatGPT聊天的零样本信息提取 摘要介绍ChatIE用于零样本IE的多轮 QA 实验总结 摘要 零样本信息提取&#xff08;IE&#xff09;旨在从未注释的文本中构建IE系统。由于很少涉及人类干预&#xff0c;因此具有挑战性。 零样本IE减少了数据标记所需的时间和工作量。最近对大型…

【Git】Git的概念安装工作区-暂存区-版本库

文章目录 Git概念-版本控制器Git安装Centos平台&ubuntu Git基本操作创建Git本地仓库配置Git 认识⼯作区、暂存区、版本库添加文件查看.git文件总结添加文件场景2 Git概念-版本控制器 引入 我们在编写各种⽂档时&#xff0c;为了防⽌⽂档丢失&#xff0c;更改失误&#xff0…

sql server表值函数

一、创建测试表 Employees 二、创建表值函数 -- DROP FUNCTION TableIntSplit;CREATE FUNCTION TableIntSplit(Text NVARCHAR(4000),Sign NVARCHAR(4000)) RETURNS tempTable TABLE(Id INT ) AS BEGIN DECLARE StartIndex INT DECLARE FindIndex INT DECLARE Content VARCHAR(…

Spring之IoC源码分析及设计思想(一)——BeanFactory

关于Spring的IOC Spring 是一个开源的 Java 平台&#xff0c;它提供了一种简化应用程序开发的框架。它是一个分层的框架&#xff0c;包括两个主要的内核&#xff1a;控制反转&#xff08;IOC&#xff09;和面向切面编程&#xff08;AOP&#xff09;。IOC 允许应用程序将组件之…

STM32MP157驱动开发——按键驱动(POLL 机制)

文章目录 “POLL ”机制&#xff1a;APP执行过程驱动使用的函数应用使用的函数pollfd结构体poll函数事件类型实现原理 poll方式的按键驱动程序(stm32mp157)gpio_key_drv.cbutton_test.cMakefile修改设备树文件编译测试 “POLL ”机制&#xff1a; 使用休眠-唤醒的方式等待某个…

BOB_1.0.1靶机详解

BOB_1.0.1靶机详解 靶机下载地址&#xff1a;https://download.vulnhub.com/bob/Bob_v1.0.1.ova 这个靶机是一个相对简单的靶机&#xff0c;很快就打完了。 找到ip地址后对IP进行一个单独的扫描&#xff0c;发现ssh端口被改到25468了&#xff0c;等会儿登陆时候需要用到。 目…

SQL注入原理分析

前言 order by的作用及含义 order by 用于判断显示位&#xff0c;order by 原有的作用是对字段进行一个排序&#xff0c;在sql注入中用order by 来判断排序&#xff0c;order by 1就是对一个字段进行排序&#xff0c;如果一共四个字段&#xff0c;你order by 5 数据库不知道怎么…

物联网场景中的边缘计算解决方案有哪些?

在物联网场景中&#xff0c;边缘计算是一种重要的解决方案&#xff0c;用于在物联网设备和云端之间进行实时数据处理、分析和决策。HiWoo Box作为工业边缘网关设备&#xff0c;具备边缘计算能力&#xff0c;包括单点公式计算、Python脚本编程以及规则引擎&#xff0c;它为物联网…

使用Kmeans算法完成聚类任务

聚类任务 聚类任务是一种无监督学习任务&#xff0c;其目的是将一组数据点划分成若干个类别或簇&#xff0c;使得同一个簇内的数据点之间的相似度尽可能高&#xff0c;而不同簇之间的相似度尽可能低。聚类算法可以帮助我们发现数据中的内在结构和模式&#xff0c;发现异常点和离…

原生求生记:揭秘UniApp的原生能力限制

文章目录 1. 样式适配问题2. 性能问题3. 原生能力限制4. 插件兼容性问题5. 第三方组件库兼容性问题6. 全局变量污染7. 调试和定位问题8. 版本兼容性问题9. 前端生态限制10. 文档和支持附录&#xff1a;「简历必备」前后端实战项目&#xff08;推荐&#xff1a;⭐️⭐️⭐️⭐️…

服务网格技术对比:深入比较Istio、Linkerd和Envoy等服务网格解决方案的优缺点

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

解决mysqld服务启动失败

1、进程 首先查看下mysql进程: ps -aux | grep mysql有进程号 2、所有者和所属组为mysql 查看/usr/local/MySQL/data/mysqld.pid所有者和所属组是否为mysql 原来是权限有问题&#xff0c;那么更改权限&#xff08;还需要加权限&#xff09;3、 重新启动服务

Kubernetes.Service—使用源 IP

使用源 IP 运行在 Kubernetes 集群中的应用程序通过 Service 抽象发现彼此并相互通信&#xff0c;它们也用 Service 与外部世界通信。 本文解释了发送到不同类型 Service 的数据包的源 IP 会发生什么情况&#xff0c;以及如何根据需要切换此行为。 准备开始 术语表 本文使用…

阿里云盘自动每日签到无需部署无需服务器(仅限学习交流使用)

一、前言 阿里云盘自动每日签到&#xff0c;无需部署&#xff0c;无需服务器 执行思路&#xff1a;使用金山文档的每日定时任务&#xff0c;执行阿里云盘签到接口。 二、效果展示&#xff1a; 三、步骤&#xff1a; 1、进入金山文档网页版 金山文档官网&#xff1a;https:…

EXCEL数据自动web网页查询----高效工作,做个监工

目的 自动将excel将数据填充到web网页&#xff0c;将反馈的数据粘贴到excel表 准备 24KB的鼠标连点器软件&#xff08;文末附链接&#xff09;、Excel 宏模块 优势 不需要编程、web验证、爬虫等风险提示。轻量、稳定、安全。 缺点 效率没那么快 演示 宏环境 ht…

QT第四讲

思维导图 基于QT的网络聊天室 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QTcpServer> //服务器类 #include<QTcpSocket> //客户端类 #include<QMessageBox> //对话框类 #include<QList…

【Terraform学习】Terraform-AWS部署快速入门(快速入门)

Terraform-AWS部署快速入门 实验步骤 连接到 Terraform 环境 SSH 连接到Terraform 环境(名为MyEC2Instance的实例) 在 Amazon Web Services &#xff08;AWS&#xff09; 上预置 EC2 实例 用于描述 Terraform 中基础结构的文件集称为 Terraform 配置。您将编写一个配置来定义…