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

news2024/9/27 12:28:25

目录

一、前言

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的分享

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

kotlin 编写一个简单的天气预报app(三)

使用eventbus替换broadcast 将从Broadcast切换到EventBus有以下几个好处&#xff1a; 解耦性&#xff1a;通过使用EventBus&#xff0c;您可以实现组件之间的解耦。传统的Broadcast机制需要发送方和接收方明确知道对方的存在&#xff0c;并且需要在代码中设置Intent过滤器和广…

Mnist分类与气温预测任务

目录 传统机器学习与深度学习的特征工程特征向量pytorch实现minist代码解析归一化损失函数计算图Mnist分类获取Mnist数据集&#xff0c;预处理&#xff0c;输出一张图像面向工具包编程使用TensorDataset和DataLoader来简化数据预处理计算验证集准确率 气温预测回归构建神经网络…

网页版五子棋项目演示

项目源码&#xff1a;五子棋游戏 演示使用的用户名&#xff1a;zyz 密码:123 注册页面&#xff1a; 登录页面&#xff1a; 游戏大厅页面&#xff1a; 未匹配&#xff1a; 匹配中&#xff1a; 游戏房间页面&#xff1a; 对方落子&#xff1a; 己方落子&#xff1a; 对…

5.6 Java递归讲解

5.6 Java递归讲解 A方法调用B方法&#xff0c;我们很容易理解递归就是&#xff1a;A方法调用A方法&#xff01;就是自己调用自己利用递归可以实现通过简单的程序来解决一些复杂的问题。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解&#xff0…

Redis的五大数据类型介绍

、简介 Redis的五大数据类型也称五大数据对象&#xff1b;前面介绍过6大数据结构&#xff0c;Redis并没有直接使用这些结构来实现键值对数据库&#xff0c;而是使用这些结构构建了一个对象系统redisObject&#xff1b;这个对象系统包含了五大数据对象&#xff0c;字符串对象&am…

MFC第二十四天 使用GDI对象画笔和画刷来开发控件(分页控件选择态的算法分析、使用CToolTipCtrl开发动静态提示)

文章目录 GDI对象画笔和画刷来开发控件梯形边框的按钮控件CMainDlg.hCMainDlg.cppCLadderCtrl.hCLadderCtrl.cpp 矩形边框的三态按钮控件 CToolTipCtrl开发动静态提示CMainDlg.hCMainDlg.cppCLadderCtrl.hCLadderCtrl.cpp: 实现文件 矩形边框的三态按钮控件 CToolTipCtrl开发动…

linux服务器安装redis

一、安装下载 下载安装参考文章 下载安装包地址&#xff1a;https://download.redis.io/releases/ 亲测有效&#xff0c;但是启动的步骤有一些问题 安装完成&#xff01;&#xff01;&#xff01; 二、启动 有三种启动方式 默认启动指定配置启动开机自启 说明&#xff1a…

CentOS下 Docker、Docker Compose 的安装教程

Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互之间不会有任何接口。 Docker Compose是用于定义…

【Lua学习笔记】Lua进阶——Table(4)继承,封装,多态

文章目录 封装继承多态 封装 // 定义基类 Object {}//由于表的特性&#xff0c;该句就相当于定义基类变量 Object.id 1//该句相当于定义方法&#xff0c;Object可以视为定义的对象&#xff0c;Test可以视为方法名 //我们知道Object是一个表&#xff0c;但是抽象地看&#xff…

为什么要有虚拟内存?

操作系统是通过内存分段和内存分页的方式管理虚拟内存地址和物理内存地址之间的关系 内存分段 程序是由若干个逻辑分段组成的&#xff0c;代码分段、数据分段、栈段、堆段组成&#xff0c;不同的段有不同的属性&#xff0c;所以就用分段的形式分离开。 分段机制下的虚拟内存…

【业务功能篇58】Springboot + Spring Security 权限管理 【下篇】

4.2.2.3 SpringSecurity工作流程分析 SpringSecurity的原理其实就是一个过滤器链&#xff0c;内部包含了提供各种功能的过滤器。这里我们可以看看入门案例中的过滤器。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KjoRRost-1690534711077)(http…

使用Django自带的后台管理系统进行数据库管理的实例

Django自带的后台管理系统主要用来对数据库进行操作和管理。它是Django框架的一个强大功能&#xff0c;可以让你快速创建一个管理界面&#xff0c;用于管理你的应用程序的数据模型。 使用Django后台管理系统&#xff0c;你可以轻松地进行以下操作&#xff1a; 数据库管理&…

详解机器学习中的熵、条件熵、相对熵和交叉熵

这个是讲的不错的链接 https://www.cnblogs.com/kyrieng/p/8694705.html 这个是交叉熵 https://blog.csdn.net/m0_57236802/article/details/129554878

《焊接点云处理》-角焊焊缝处理

角焊缝点云处理 前言一、代码二、实现步骤3、验证前言 针对T型板,识别效果如下所示 一、代码 主函数 #include "CGALRECONSTRUCT.h" #include "CGALREGIONPLANE.h" #include

设计利器,掌握CAD辅助命令的必备指南

CAD设计中的辅助命令是提高效率和确度的关键工具。掌握并正确运用CAD中的各种辅助命令对于设计师们来说至关重要。本文将为你详细介绍如何使用CAD中的辅助命令&#xff0c;从而帮助你在设计过程中更加高效地实现你的创意。、 大家有没有发现&#xff0c;当我们的直线命令移动到…

Rethinking the Image Fusion(PMGI)

1.摘要 本文提出了一种基于梯度和强度比例维护&#xff08;PMGI&#xff09;的快速统一图像融合网络&#xff0c;可以端到端实现各种图像融合任务&#xff0c;包括红外和可见图像融合、多曝光图像融合、医学图像融合、多焦点图像融合和全色增强。我们将图像融合问题统一为源图…

C++信号量与共享内存实现进程间通信

关于信号量和共享内存的相关知识可参考下面链接&#xff1a; 进程间通信方式介绍_夜雨听萧瑟的博客-CSDN博客 C 创建共享内存_c共享内存_夜雨听萧瑟的博客-CSDN博客 信号量SytemV与Posix信号量的介绍与用法_夜雨听萧瑟的博客-CSDN博客 直接上代码&#xff0c;代码如下&#…

蓝桥杯单片机第十二届国赛 真题+代码

iic.c /* # I2C代码片段说明1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。2. 参赛选手可以自行编写相关代码或以该代码为基础&#xff0c;根据所选单片机类型、运行速度和试题中对单片机时钟频率的要求&#xff0c;进行代码调试和修改。 */ #include <STC1…

golang文件锁,目录锁,syscall包的使用

先说结论 1. golang提供了syscall包来实现文件/目录的加锁&#xff0c;解锁 2. syscall包属于文件锁&#xff0c;是比较底层的技术&#xff0c;并不能在所有操作系统上完全实现&#xff0c;linux上实现了&#xff0c;windows下面就没有 3. 加锁时调用syscall.Flock(fd&#…