利用PictureBox画动态曲线监护仪软件示例 功能说明:
1. 利用PictureBox控件,双缓冲,绘图无闪屏;
2. 动态绘制Sin(x)示例曲线,X范围固定为 0-4Pi;
3. 曲线的刷新可通过按钮停止或继续;
4. 实时绘制鼠标竖直标线,可通过右键固定或解除固定;
5. 实时计算鼠标标线与曲线交点的数值坐标;
6. 窗体大小可实时调整,曲线绘制不受影响;
7. 可用于绘制心律、血氧、呼吸曲线等生命体征波形的绘制;
--2024.07.03 By 云草桑
C# winfrom GDI 把曲线波形升级为曲线面积-CSDN博客
图形处理
/// <summary>
/// 生成曲线点(像素表示)
/// </summary>
/// <param name="picBox">控件</param>
/// <param name="datArray">数据数组</param>
/// <returns>曲线点数组</returns>
private PointF[] GenerateCurvePoints(PictureBox picBox, double[] datArray)
{
var curvePoints = new PointF[datArray.Length];
Rectangle pnlRect = picBox.ClientRectangle;
PointF tmpPoint;
float x, y;
float dltX = pnlRect.Width * 1f / (datArray.Length - 1);
for (int i = 0; i < datArray.Length; i++)
{
tmpPoint = new PointF(i * dltX, (float)(pnlRect.Height * (1 - datArray[i] / 2) / 2));
curvePoints[i] = tmpPoint;
}
return curvePoints;
}
/// <summary>
/// 初始化曲线数据数组
/// </summary>
/// <param name="datArray">曲线数据数组</param>
private void InitializeDatArray(double[] datArray)
{
if (datArray == null)
return;
//初始化为两个周期的正弦值(0-4Pi)
int datLen = datArray.Length;
double dltX = 4 * Math.PI / (datLen - 1);
for (int i = 0; i < datLen; i++)
{
datArray[i] = Math.Sin(i * dltX);
}
}
/// <summary>
/// 数组左移一个元素,留出最后一个元素填充新数据
/// </summary>
/// <param name="datArray">曲线数据数组</param>
private void LeftShiftOneElement(double[] datArray)
{
if (datArray != null && datArray.Length > 1)
{
//两条BlockCopy语句,实现数组左移一个元素
//int datLen = datArray.Length;
//var tmpArray = new double[datLen - 1];
//Buffer.BlockCopy(datArray, sizeof(double) * 1, tmpArray, 0, Buffer.ByteLength(tmpArray));
//Buffer.BlockCopy(tmpArray, 0, datArray, 0, Buffer.ByteLength(tmpArray));
//BlockCopy itself, only one sentence.(其实只一条BlockCopy语句即可)
Buffer.BlockCopy(src: datArray, srcOffset: sizeof(double) * 1,
dst: datArray, dstOffset: 0,
count: Buffer.ByteLength(datArray) - sizeof(double) * 1);
}
}
/// <summary>
/// 绘制位图,并赋值pictureBox.Image属性
/// </summary>
/// <param name="picBox">PictureBox</param>
private void DrawBitmap(PictureBox picBox)
{
Action<PictureBox> thisDel = DrawBitmap;
if (InvokeRequired) //跨线处理
{
Invoke(thisDel, picBox);
}
else
{
#region --- 绘图操作 ---
var rect = picBox.ClientRectangle;
Point pt1, pt2;
var bmp = new Bitmap(rect.Width, rect.Height);
var gBmp = Graphics.FromImage(bmp);
//画黑背景
gBmp.FillRectangle(Brushes.Black, rect);
//画网格
for (int i = 0; i < 3; i++) //三条横线及坐标
{
pt1 = new Point(0, (i + 1) * rect.Height / 4);
pt2 = new Point(rect.Width, (i + 1) * rect.Height / 4);
gBmp.DrawLine(Pens.LightGreen, pt1, pt2);
var yScal = 1 - i;
gBmp.DrawString(yScal.ToString("F1"), Font, Brushes.LightGreen, pt1 + new Size(5, -15));
}
for (int i = 0; i < 3; i++) //三条纵线,及坐标
{
pt1 = new Point((i + 1) * rect.Width / 4, 0);
pt2 = new Point((i + 1) * rect.Width / 4, rect.Height);
gBmp.DrawLine(Pens.LightGreen, pt1, pt2);
var xScal = i + 1;
gBmp.DrawString(xScal.ToString("F1") + "PI", Font, Brushes.LightGreen, pt2 - new Size(-5, 15));
}
//画曲线
if (curvePoints != null)
gBmp.DrawCurve(Pens.Yellow, curvePoints);
//画鼠标竖线
int msLocX; //鼠标的横坐标
if (fixedMarkLine)
msLocX = (int)(rect.Width * markLineRelPos);
else
msLocX = (int)(msRelLoc.X * rect.Width);
pt1 = new Point(msLocX, 0);
pt2 = new Point(msLocX, rect.Height);
gBmp.DrawLine(Pens.Red, pt1, pt2);
//刷新PictureBox的Image属性
picBox.Image = bmp;
//显示坐标
int xIdx = (int)((sinDat.Length - 1) * msLocX * 1d / rect.Width);
if (xIdx >= 0 && xIdx < sinDat.Length)
{
double xVal = 4 * Math.PI * xIdx / (sinDat.Length - 1);
double yVal = sinDat[xIdx];
lblCoord.Text = "交点坐标:X = " + xVal.ToString("F3") + " , Y = " + yVal.ToString("F3");
}
#endregion
}
}