WinForm 曲线图动态绘制

181 阅读5分钟

前言

在winform开发过程中,有时候会需要开发曲线图相关功能,本文将分享一个自己开发的一个自定义曲线功能,如坐标轴的动态改变,以及曲线点的拖动和曲线动态绘制功能。

正文

首先准备一张精准的ps坐标图,由于我们要实现坐标轴可动态变化,所以这张ps图不含具体坐标轴的值。

然后在winform窗口的panel里添加这张图作为背景,并且在坐标轴的位置添加lable用于显示坐标轴数值。

接下来是坐标点,我用的是12个坐标点,方法是添加12个pictureBox,然后给每一个pictureBox添加红色的背景,并缩到最小大小;同时添加12个textbox用于显示每个坐标点的坐标(相对于坐标轴)效果如图。

曲线图功能核心是要实现点与点之间的连线,这个通过DrawLine方法即可实现,但是要实现坐标点的拖动,就必须是实时更新绘制,我的解决方法是:

1、每次有点在拖动的时候清空已经绘制的线条。

2、坐标点移动到新的位置以后,重新绘制界面。

3、在界面绘制事件里加入所有曲线点之间线条绘制的功能。

首先实现坐标点拖动功能,由于12个坐标的拖动功能都一样,给12个pictureBox添加相同的事件,贴上坐标点的拖动功能代码:

Point downPoint;//记录鼠标按下时的坐标(相对于panel)
Point centerPoint = new Point(5, 505);//坐标轴原点(相对于panel)
List<TextBox> textXList = new List<TextBox>();//横坐标集合
List<TextBox> textYList = new List<TextBox>();//纵坐标集合

int iMaxX=1000;//横纵最大值
int iMaxY=500;//纵轴最大值

private void PictureBox_MouseMove(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        PictureBox current = (PictureBox)sender;
        current.Location = new Point(current.Location.X + e.Location.X - downPoint.X, current.Location.Y + e.Location.Y - downPoint.Y);
    }
}

private void PictureBox_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        downPoint = e.Location;
    }
}

private void PictureBox_Move(object sender, EventArgs e)//限制每个点的移动位置
{
    PictureBox current = (PictureBox)sender;

    #region 限制位置,根据自己的panel分辨率和坐标轴分辨率来定,我的panel分辨率为1020*520,坐标轴分辨率为1000*500,坐标轴居中显示
    int index = int.Parse(current.Tag.ToString()) - 1;//每个pictureBox都有一个tag值,从1到12,用于区分对象
    int x, y;
    if (index == 0)
    {
        x = current.Location.X <= panel1.Controls[index + 1].Location.X ? current.Location.X : panel1.Controls[index + 1].Location.X;
        x = x >= 5 ? x : 5;
    }
    else if (index == 11)
    {
        x = current.Location.X >= panel1.Controls[index - 1].Location.X ? current.Location.X : panel1.Controls[index - 1].Location.X;
        x = x <= 1005 ? x : 1005;
    }
    else
    {
        x = current.Location.X;
        if (current.Location.X <= panel1.Controls[index - 1].Location.X)
        {
            x = panel1.Controls[index - 1].Location.X;
        }
        if (current.Location.X >= panel1.Controls[index + 1].Location.X)
        {
            x = panel1.Controls[index + 1].Location.X;
        }
    }
    y = current.Location.Y;
    if (current.Location.Y < 5)
    {
        y = 5;
    }
    if (current.Location.Y > 505)
    {
        y = 505;
    }
    current.Location = new Point(x, y);
    #endregion

    string x1 = (Math.Round((float)(current.Location.X- centerPoint.X)* iMaxX / 1000 ,1)).ToString();
    string y1 = (Math.Round((float)(centerPoint.Y - current.Location.Y )* iMaxY / 500 ,1)).ToString();

    for (int i = 0; i < 12; i++)//记录所有点的横纵坐标
    {
        if (current.Tag == textXList[i].Tag)
        {
            textXList[i].Text = x1;
        }
        if (current.Tag == textYList[i].Tag)
        {
            textYList[i].Text = y1;
            panel1.Invalidate();//用于重绘界面
            return;
        }
    }
}

然后就是绘制坐标点之间的连线:

private void Panel1_Paint(object sender, PaintEventArgs e)
{
    for (int i = 1; i < 12; i++)
    {
        Point current = new Point(panel1.Controls[i].Location.X + panel1.Controls[i].Width / 2, panel1.Controls[i].Location.Y + panel1.Controls[i].Height / 2);//当前点
        Point last = new Point(panel1.Controls[i - 1].Location.X + panel1.Controls[i - 1].Width / 2, panel1.Controls[i - 1].Location.Y + panel1.Controls[i - 1].Height / 2);//上一个点
        e.Graphics.DrawLine(new Pen(Color.Red, 2), current, last);//当前点和上一个点连线
    }
}

最后显示坐标轴数值:

public void ChangXValue()//卸载窗体的load事件里
{
    label1.Text = (iMaxX / 10).ToString();
    label2.Text = (2 * iMaxX / 10).ToString();
    label3.Text = (3 * iMaxX / 10).ToString();
    label4.Text = (4 * iMaxX / 10).ToString();
    label5.Text = (5 * iMaxX / 10).ToString();
    label6.Text = (6 * iMaxX / 10).ToString();
    label7.Text = (7 * iMaxX / 10).ToString();
    label8.Text = (8 * iMaxX / 10).ToString();
    label9.Text = (9 * iMaxX / 10).ToString();
    label10.Text = (10 * formShip.curveClass.iMaxX / 10).ToString();

    label11.Text = (iMaxY / 10).ToString();
    label12.Text = (2 * iMaxY / 10).ToString();
    label13.Text = (3 * iMaxY / 10).ToString();
    label14.Text = (4 * iMaxY / 10).ToString();
    label15.Text = (5 * iMaxY / 10).ToString();
    label16.Text = (6 * iMaxY / 10).ToString();
    label17.Text = (7 * iMaxY / 10).ToString();
    label18.Text = (8 * iMaxY / 10).ToString();
    label19.Text = (9 * iMaxY / 10).ToString();
    label20.Text = (10 *iMaxY / 10).ToString();
}

此时,已经完成曲线点的绘制以及点的拖动功能,效果图如下:

最后,如果想实现通过直接配置textbox来实现点的坐标变化,可以加上如下代码:

private void X2_LostFocus(object sender, EventArgs e)//失去焦点事件
{
    TextBox tempTxt = (TextBox)sender;
    int index = int.Parse(tempTxt.Tag.ToString()) - 1;

    int x = (int)(float.Parse(tempTxt.Text) * 1000 / formShip.curveClass.iMaxX + centerPoint.X);
    panel1.Controls[index].Location = new Point(x, panel1.Controls[index].Location.Y);
}

private void Y12_LostFocus(object sender, EventArgs e)//失去焦点事件
{
    TextBox tempTxt = (TextBox)sender;
    int index = int.Parse(tempTxt.Tag.ToString()) - 1;

    int y = (int)(centerPoint.Y - float.Parse(tempTxt.Text) * 500 / formShip.curveClass.iMaxY);
    panel1.Controls[index].Location = new Point(panel1.Controls[index].Location.X, y);
}

private void X2_KeyPress(object sender, KeyPressEventArgs e)
{
    if (e.KeyChar != 8 && !Char.IsDigit(e.KeyChar))//退格键和数字
    { 
        e.Handled = true;
    }
    if (e.KeyChar == 13 )//回车键
    {
        pictureBox1.Focus();
    }
}

自此,功能全部完成。

以上仅供参考,如有更好的方案,可以提供交流一下。

总结

本方案通过组合基础控件和GDI+绘图实现了完整的动态曲线系统,关键创新点包括:

1、分离坐标计算与界面渲染逻辑

2、采用事件驱动实现实时交互

3、提供双重输入方式(拖拽/文本输入)

4、实现自适应分辨率的边界控制

实际应用中可根据需求扩展数据绑定、动画效果或导出功能,建议结合双缓冲技术优化绘制性能。

关键词

WinForm、曲线图、动态绘制、坐标轴、GDI+、控件拖拽、实时重绘、数据可视化

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:橘猫不太胖

出处:cnblogs.com/cat-not-fat/p/13717866.html

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!