运用C#在VS2017的PictureBox控件中绘制简易二自由度机械臂,并且让机械臂实现画直线、圆、人物轮廓及写字的功能。

给大家看看效果吧



演示写字视频在下:

VID

首先放置了诸多控件

在给控件绑定事件前,先定义几个函数,需要反复调用。

绘制机械臂的函数

部分全局变量定义:
private int L1 = 100, L2 = 100; //表示机械臂的两臂长度
private double Ang1 = 0, Ang2 = 0; //表示机械臂的扭转角度,单位为弧度,取值范围为(-PI,PI)

#region 绘制机械臂
/// <summary>
/// 机械臂的底端位于坐标系原点
/// </summary>
public void DrawMechanism()
{//特别注意:我定义的坐标系为我们平常时常用的坐标系,//但电脑屏幕等运用的坐标系是:左上角为原点,水平向右为X正半轴,水平向下为Y正半轴//因此两个坐标系之间存在装换。PointF pointO = new PointF(200F, 200F);//机械臂O点位置,位于坐标原点PointF pointA = new PointF((float)(200 + L1 * Math.Cos(Ang1)), (float)(200 - L1 * Math.Sin(Ang1)));//机械臂A点位置PointF pointB = new PointF((float)(200 + L1 * Math.Cos(Ang1) + L2 * Math.Cos(Ang1 + Ang2)), (float)(200 - L1 * Math.Sin(Ang1) - L2 * Math.Sin(Ang1 + Ang2)));//机械臂B点位置Bitmap bitmap = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);//位图为PictureBox1的客户区尺寸Graphics g = Graphics.FromImage(bitmap);//定义图像,图像为抽象类,不可直接构造函数生成//绘制斜线时消除锯齿(鼠标放在那就可以看到函数功能)g.SmoothingMode = SmoothingMode.AntiAlias;g.SmoothingMode = SmoothingMode.HighQuality;Pen pen = new Pen(Color.Red, 1);//定义一只红色、1像素宽的笔g.DrawLine(pen, pointO, pointA);//绘制大臂(连接OA线段)g.DrawLine(pen, pointA, pointB);//绘制小臂(连接AB线段)//绘制转动副Og.DrawEllipse(pen, 200 - 5, 200 - 5, 10, 10);g.FillEllipse(new SolidBrush(Color.White), 200 - 5, 200 - 5, 10, 10);//绘制转动副Ag.DrawEllipse(pen, (float)(200 + L1 * Math.Cos(Ang1)) - 5, (float)(200 - L1 * Math.Sin(Ang1)) - 5, 10, 10);g.FillEllipse(new SolidBrush(Color.White), (float)(200 + L1 * Math.Cos(Ang1)) - 5, (float)(200 - L1 * Math.Sin(Ang1)) - 5, 10, 10);//绘制转动副ApictureBox1.Image = bitmap;//所以Image每次都被更新了,只显示当前机械臂位置//释放资源g.Dispose();pen.Dispose();
}
#endregion

根据机械臂末端位置求解两臂旋转角度的函数

求解说明:见下图,对于一个末端点,机械臂存在两种姿态(除了边界点),因此存在选择哪种姿态的原则。

部分全局变量定义:
private double Ang1 = 0, Ang2 = 0; //表示机械臂的扭转角度,单位为弧度,取值范围为(-PI,PI)

#region 求解对应坐标下两臂转动角度
/// <summary>
/// 求解两臂转动角度,(反)三角函数都是弧度单位。
/// </summary>
/// <param name="X">目标点的Y坐标(对应我定义的坐标系)</param>
/// <param name="Y">目标点的Y坐标(对应我定义的坐标系)</param>
public void GetAngel(double X, double Y)
{double x = X;double y = Y;double r = Math.Sqrt(x * x + y * y);//目标点到原点的距离double theta = Math.Atan2(y, x);//取值范围为(-PI,PI)double phi = Math.PI - Math.Acos((L1 * L1 + L2 * L2 - r * r) / (2 * L1 * L2));double theta1 = Math.Acos((r * r + L1 * L1 - L2 * L2) / (2 * L1 * r));if (r == 0){Ang2 = Math.PI;}//两个解:(theta+theta1,-phi)、(theta-theta1,phi)//选解采用最短准则:即对应的两臂角度相对于上一时刻的两臂角度需要变动的角度和更小的解else{if ((Math.Abs(theta + theta1 - Ang1) + Math.Abs(-phi - Ang2)) > (Math.Abs(theta - theta1 - Ang1) + Math.Abs(phi - Ang2))){Ang1 = theta - theta1;Ang2 = phi;}else{Ang1 = theta + theta1;Ang2 = -phi;}}textBox3.Text = (Ang1 / Math.PI * 180).ToString();//将弧度换算成角度显示textBox4.Text = (Ang2 / Math.PI * 180).ToString();//
}
#endregion

网上随便找的延时函数

忘记是哪位老哥了,真的抱歉啊!

#region 延时函数/// <summary>/// 延时函数,单位为毫秒/// </summary>/// <param name="delayTime"></param>public void DelayMs(int delayTime){DateTime now = DateTime.Now;int s;do{TimeSpan spand = DateTime.Now - now;s = spand.Milliseconds;Application.DoEvents();}while (s < delayTime);}#endregion

下面开始给控件绑定事件

部分全局变量:
string graph;//用来标志画直线、画圆、画轮廓还是写字
PointF pointLast;//机械臂末端上一时刻位置
PointF pointNow;//机械臂末端现在位置

首先给定时器绑定事件

因为机械臂从当前位置移动到直线的起点、圆的起点、轮廓的起点和字的起点的过程中,机械臂末端移动轨迹并不需要绘制,故关闭Timer1
只有机械臂末端在画线等功能时开启Timer1进行绘制轨迹

private void timer1_Tick(object sender, EventArgs e)
{//机械臂现在末端位置pointNow = new PointF((float)(200 + L1 * Math.Cos(Ang1) + L2 * Math.Cos(Ang1 + Ang2)), (float)(200 - L1 * Math.Sin(Ang1) - L2 * Math.Sin(Ang1 + Ang2)));if (pointLast == new PointF(0, 0)){//可以试试没这个if语句会发生什么pointLast = pointNow;//如果是绘制曲线的起点,便没有pointLast,令其就等与pointNow}Bitmap bt = new Bitmap(pictureBox1.BackgroundImage);//保证了现在绘图是在以前绘图的基础上,不会丢失先前轨迹Graphics g = Graphics.FromImage(bt);//绘制斜线时消除锯齿g.SmoothingMode = SmoothingMode.AntiAlias;g.SmoothingMode = SmoothingMode.HighQuality;//将机械臂上一次末端位置及现在末端位置连起来,因为两个位置及近,就是用直线不断拟合轨迹g.DrawLine(new Pen(Color.Black, (float)0.4), pointLast, pointNow);//释放资源g.Dispose();//将包含新的轨迹的位图赋给PictureBox1的BackgroundImagepictureBox1.BackgroundImage = bt;pointLast = pointNow;//更新上一次机械臂末端位置,
}

开始是画直线

“画直线”按钮的点击事件,进行一些基本设置

private void button1_Click(object sender, EventArgs e)
{//“画直线”按钮的点击事件groupBox1.Visible = true;//坐标输入功能开启,用户输入起终点groupBox2.Visible = false;//写字输入功能关闭textBox4.Visible = true;label9.Text = "起点:";label10.Text = "终点:";graph = "Line";//标志其为画直线
}

画直线的函数:将直线细分成很多个点,机械臂末端依次到达这些点

#region 绘制两点之间的一条直线/// <summary>/// 绘制两点之间的一条直线。/// </summary>/// <param name="startx">起点的X坐标</param>/// <param name="starty">起点的Y坐标</param>/// <param name="endx">终点的x坐标</param>/// <param name="X">终点的Y坐标</param>/// <param name="bl">是否是绘图,绘图:true;移动:false</param>public void DrawLine(double startx, double starty, double endx, double endy, Boolean bl){if (bl)//如果是绘图,就开启Timer1进行绘制轨迹{timer1.Enabled = true;}double x, y;//用来表示下一个机械臂末端位置double r = Math.Sqrt((startx - endx) * (startx - endx) + (starty - endy) * (starty - endy));//直线长度for (int i = 0; i < r / 0.05; i++)//对所画图形进行分段计数{x = startx + i / (r / 0.05) * (endx - startx);y = starty + i / (r / 0.05) * (endy - starty);GetAngel(x, y);//通过调用此函数,计算得到对应(x,y)点时的两臂角度,对全局变量Ang1和Ang2赋值DrawMechanism();//通过Ang1和Ang2的实时数据,更新机械臂位置,因为每次变化很小,肉眼上以为机械臂在转动DelayMs(4);//每个机械臂状态停留4ms,如果不停留,程序运行很快,基本基本看不到机械臂中间移动过程}timer1.Enabled = false;//无论是机械臂绘图还是单纯移动到目标点,都关闭定时器}#endregion

注:bl是用来标志是画图形还是前往图形绘制起点;false:前往图形绘制起点;true:画图形

然后是画圆

“画圆”按钮的点击事件,进行一些基本设置

private void button2_Click(object sender, EventArgs e)
{//“画圆”按钮的点击事件groupBox1.Visible = true;//坐标输入功能开启groupBox2.Visible = false;//写字输入功能关闭textBox4.Visible = false;label9.Text = "圆心:";label10.Text = "半径:";graph = "Circle";//标志其为画圆
}

画圆的函数:将圆细分成很多个点,机械臂末端依次到达这些点

#region 绘制一个圆
/// <summary>
/// 绘制给定圆心和半径的圆。
/// </summary>
/// <param name="startx">圆心的X坐标</param>
/// <param name="starty">圆心的Y坐标</param>
/// <param name="r">圆的半径</param>
public void DrawCircle(double startx, double starty, double r)
{timer1.Enabled = true;double x, y;double theta;//存储画圆时的弧度,取值范围(-PI,PI)double k = Math.PI * r / 0.05; //将圆的周长细分为2k个点for (int i = 0; i < 2 * k; i++)//从圆的最左点开始逆时针绘制,即从-PI到PI绘制{theta = (i - k) / k * Math.PI;//每个点对应的弧度//计算点的坐标x = startx + r * Math.Cos(theta);y = starty + r * Math.Sin(theta);GetAngel(x, y);DrawMechanism();DelayMs(10);}timer1.Enabled = false;
}
#endregion

接着画人物轮廓

部分全局变量:VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();//创建用于存储轮廓的VectorOfVectorOfPoint数据类型(命名空间Emgu.CV.Util)(contours里面存储的数据可以自己查,里面就是存储了很多段线段,由线段组成了轮廓,所以一定要知道contours存储的数据格式)

CvInvoke.FindContours函数的参数讲解参考了这篇老大哥:
OpenCV中的findContours函数参数讲解

“画轮廓”按钮的点击事件,提取图片轮廓

private void button3_Click(object sender, EventArgs e)
{IOutputArray hierarchy = null;//与contours对应的向量,hierarchy[i][0]~hierarchy[i][3]存储后、前、子、父级轮廓链表表头OpenFileDialog ofd = new OpenFileDialog();//创建一个对话框,选择需要画轮廓的图片ofd.Filter = "JPG图片|*.jpg|BMP图片|*.bmp";//选择文件的类型(filter:过滤)//处理图像,得到图像的轮廓信息!if (ofd.ShowDialog() == DialogResult.OK){Mat _inputmat = new Mat(ofd.FileName);imageBox1.Image = _inputmat;//读入图像,在ImageBox中显示CvInvoke.GaussianBlur(_inputmat, _inputmat, new Size(3, 3), 3, 3);//对输入图像进行高斯滤波,并将滤波后的图像存至_inputmatMat dst = new Mat();//存储图片轮廓信息CvInvoke.Canny(_inputmat, dst, 120, 180);//Canny 边缘检测算子imageBox2.Image = dst;//显示轮廓图片#region  CvInvoke.FindContours方法参数讲解///<summary>///IOutputArray contours:检测到的轮廓。通常使用VectorOfVectorOfPoint类型。///IOutputArray hierarchy:可选的输出向量,包含图像的拓扑信息。不使用的时候可以用 null 填充。///每个独立的轮廓(连通域)对应 4 个 hierarchy元素 hierarchy[i][0]~hierarchy[i][4]///(i表示独立轮廓的序数)分别表示后一个轮廓、前一个轮廓、父轮廓、子轮廓的序数。///RetrType mode标识符及其解析:///External = 0 提取的最外层轮廓;///List = 1 提取所有轮廓///Ccomp = 2 检索所有轮廓并将它们组织成两级层次结构:水平是组件的外部边界,二级约束边界的洞。///Tree = 3 提取所有的轮廓和建构完整的层次结构嵌套的轮廓。///ChainApproxMethod表示轮廓的逼近方法///ChainCode = 0 Freeman链码输出轮廓。所有其他方法输出多边形(顶点序列)。///ChainApproxNone = 1 所有的点从链代码转化为点;///ChainApproxSimple = 2 压缩水平、垂直和对角线部分,也就是说, 只剩下他们的终点;///ChainApproxTc89L1 = 3 使用The - Chinl 链逼近算法的一个///ChainApproxTc89Kcos = 4 使用The - Chinl 链逼近算法的一个///LinkRuns = 5, 使用完全不同的轮廓检索算法通过链接的水平段的1s轨道。///用这种方法只能使用列表检索模式。///</summary>#endregionCvInvoke.FindContours(dst, contours, hierarchy, Emgu.CV.CvEnum.RetrType.External,Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);}graph = "Graph";//标志是画人物轮廓
}

因为存储的轮廓都是线段表示,故只要调用DrawLine函数就行

最后是写字

写字关键是字库,我选择的是CAD软件定制的SHP字体格式。网上很容易找到CAD软件中的SHX文件经过反汇编后得到SHP文件,反汇编后的SHP文件和普通的SHP文件不同,它是Shape文件,它是可以直接用记事本打开的,它是使用特殊代码法编写的文件,我们可以直接解码,不是普通的SHP文件的解码方式,那是不对的。Shape文件的代码可以通过看这位老大哥的文章学会然后解码:shx文件格式说明和形文件及其开发
我是将一个Shape文件(包含几千个字)里的每个字拿出来,单独建立一个记事本,然后根据这个字的GB2313编码给这个记事本命名。然后在根据机械臂要写的字的GB2313编码找到这个文件在读取解码出来。(主要就是利用字符串的split方法切片(split方法这位老哥讲解得很详细C#利用正则表达式实现字符串搜索),当然可能有更好的方法,我这方法较笨)

“写字”按钮的点击事件,进行基础设置

private void button4_Click(object sender, EventArgs e)
{groupBox1.Visible = false;groupBox2.Visible = true;graph = "Font";
}

最后就是开始按钮的事件绑定了

部分全局变量:
double startX; // 用来存储运动开始的位置
double startY;
double endX = 200;//用来存储运动结束的位置
double endY = 0;

“开始”按钮的点击事件:有点多,

private void button5_Click(object sender, EventArgs e)
{//读取设置的两臂长度L1 = Convert.ToInt16(textBox6.Text);L2 = Convert.ToInt16(textBox7.Text);//绘制图像为直线if (graph == "Line"){//将上一次的机械臂的末端位置(即现在机械臂末端位置)设置为起点位置startX = endX;startY = endY;//将绘制目标图像(即线段)的起点位置设置为终点位置endX = double.Parse(textBox1.Text);endY = double.Parse(textBox2.Text);//然后机械臂末端从起点沿直线运动到终点位置,并且这段移动是不需要绘制轨迹的,所以为falseDrawLine(startX, startY, endX, endY, false);//将上一次的机械臂的末端位置(即上面说的目标图像的起点位置)设置为起点位置startX = endX;startY = endY;//将绘制目标图像的终点位置设置为终点位置endX = double.Parse(textBox3.Text);endY = double.Parse(textBox4.Text);//然后机械臂末端从起点沿直线运动到终点位置,这段移动是需要绘制轨迹的,所以为trueDrawLine(startX, startY, endX, endY, true);}//绘制图像为圆if (graph == "Circle"){//将上一次的机械臂的末端位置(即现在机械臂末端位置)设置为起点位置startX = endX;startY = endY;//将绘制目标图像(即圆)的起点位置(即圆的最左点)设置为终点位置endX = double.Parse(textBox1.Text) - double.Parse(textBox3.Text);endY = double.Parse(textBox2.Text);//然后机械臂末端从起点沿直线运动到终点位置,并且这段移动是不需要绘制轨迹的,所以为falseDrawLine(startX, startY, endX, endY, false);//最后开始画圆,这段移动是需要绘制轨迹的,所以为trueDrawCircle(double.Parse(textBox1.Text), double.Parse(textBox2.Text), double.Parse(textBox3.Text));}//绘制图像为人像轮廓if (graph == "Graph"){//轮廓for (int i = 0; i < contours.Size; i++){for (int j = 0; j < contours[i].Size - 1; j++){//将上一次的机械臂的末端位置(即现在机械臂末端位置)设置为起点位置startX = endX;startY = endY;//将绘制目标图像(即轮廓的第i组的第j条线段)的起点位置(即圆的最左点)设置为终点位置endX = contours[i][j].X - 40;endY = -(contours[i][j].Y - 125);//然后机械臂末端从起点沿直线运动到终点位置,并且这段移动是不需要绘制轨迹的,所以为falseDrawLine(startX, startY, endX, endY, false);//将上一次的机械臂的末端位置(即上面说的轮廓的第i组的第j条线段的起点位置)设置为起点位置startX = endX;startY = endY;//将机械臂的目标末端位置(即上面说的轮廓的第i组的第j条线段的终点位置)设置为起点位置endX = contours[i][j + 1].X - 40;endY = -(contours[i][j + 1].Y - 125);//然后绘制轮廓的第i组第j条线段DrawLine(startX, startY, endX, endY, true);pointLast = new PointF(0, 0);//每一次图像绘制成功后,将pointLast设置(0,0)(与定时器配合使用),自行体会作用,感觉讲不清}}}//绘制图像为汉字if (graph == "Font"){for (int j = 0; j < textBox5.Text.Length; j++){char word = textBox5.Text[j];//取出第j个文字//将这个汉字转化为GB22313编码,并且加0构成文件名byte[] wordbytes = Encoding.GetEncoding("GB2312").GetBytes(new char[] { word });string filename = "0" + Convert.ToString((wordbytes[0] << 8) + wordbytes[1], 16);//从文件中读出数据StreamReader sr = new StreamReader("E:/下载软件/Visual Studio 2017/C#文件/Robotic Arm/汉字库/" + filename + ".txt");string wordtxt = sr.ReadToEnd();//将文字所有信息读入wordtxt变量string font = wordtxt.Split(new string[] { "7,-114,5,", "7,-113,0" }, StringSplitOptions.RemoveEmptyEntries)[1];//切取有用片段string[] fontarray = font.Split(new string[] { ",", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);//将字符串转化为字符数组,去掉逗号空格这些int i = 0;//调整字的初始位置,即字的左下角坐标Point point = new Point(100 * j - 50, 0);//将上一次机械臂末端位置设置为起点位置startX = endX;startY = endY;//将字的左下角位置设置为机械臂末端目标位置endX = point.X;endY = point.Y;//使机械臂从当前位置沿直线移动到字的走下角位置,此过程不需要画轨迹,故为falseDrawLine(startX, startY, endX, endY, false);//解码文字信息,结合Shape文件格式解码出想要的数据while (i < fontarray.Length){if (fontarray[i] == "2")//说明为抬笔过程,就不用画轨迹,故为false{startX = endX;startY = endY;endX = startX + Convert.ToInt16(fontarray[i + 2]);endY = startY + Convert.ToInt16(fontarray[i + 3]);DrawLine(startX, startY, endX, endY, false);i = i + 4;}if (fontarray[i++] == "1")//说明为落笔过程,需要画轨迹,故为true{for (; i < fontarray.Length && fontarray[i] != "2";){if (fontarray[i] == "8"){startX = endX;startY = endY;endX = startX + Convert.ToInt16(fontarray[i + 1]);endY = startY + Convert.ToInt16(fontarray[i + 2]);DrawLine(startX, startY, endX, endY, true);i = i + 3;}else if (fontarray[i] == "12")//因为用到圆弧的字较少,所以忽略{}}pointLast = new PointF(0, 0);//每一笔绘制成功后,将pointLast设置(0,0)(与定时器配合使用),自行体会作用}}}}pointLast = new PointF(0, 0);}

基本就是这些了

本来想把一些函数写到另一个命名空间里,但是发现使用了太多全局变量,导致不太好弄,我也很头大。

Emgu中的坑

我下载的版本是4.2的,最新的,一下载完根本找不到网上说的啥bin文件夹,是需要运行EmguCV\Solution\Windows.Desktop文件夹下的Emgu.CV.Example.sln程序后会生成bin文件夹,并且bin文件夹里也没有网上说的啥Emgu.CV.World.dll和啥这文件,应该是版本更新问题,只要把bin文件夹里的那几个(我这是4个)Emgu开头的dll文件添加到项目的引用中(那几个像ImageBox工具也是通过添加其中一个就有了,忘了哪个了,自己试试),然后将EmaguCV文件夹下的bin文件夹下的X64文件夹里的所有文件复制到自己项目里的Debug文件夹下就行(可能不需要,但是复制了全部应该是没问题的,),然后就是右击项目选择属性,在“生成”中的平台目标改为X64,应该不会还有32位的电脑吧,不会吧。当然可以试试X86,可能电脑不一样。这些可以参照这个老大哥的视频EmguCV图像处理基础教程(-)

SHP文件的坑

一开始我以为shp文件就可以通过shp文件的格式解析,网上有关这的一大堆,但是实际CAD的shx文件反汇编后得到shp文件格式不一样,是可以用记事本打开看的,
第一张CAD的SHP文件打开后的样子,第二张是普通SHP文件打开的样子


什么差别不需要多说了吧

最后

如果Emgu安装有问题或者SHP文件找不到啥的,可以留言,但是俺不一定看,也可以加我qq(2897035088),想要源代码的就别来了,代码都贴出来了,也都给注释了。那个将一个SHP文件切片函数没发,这个我一不小心删了,也懒得写了,自己看看吧,也挺简单的,就是格式别错。
并且没什么写文章的经验,有问题欢迎指出,有想法欢迎交流,特别是有改进的意见请老大哥一定要指导一下小弟,万分感激。

运用C#在VS2017的PictureBox控件中绘制简易二自由度机械臂,并且让机械臂实现画直线、圆、人物轮廓及写字的功能。相关推荐

  1. C#在控件中绘制矩形、圆、线段等(切换不同的图片,自定义翻页控件,通过委托监控某个字段)

    1.效果展示 2 自定义翻页控件 public partial class PageManagemen : UserControl{/// <summary>/// 当前页(从1开始计数) ...

  2. C#之在PictureBox控件上画点

    C#之在PictureBox控件上画点 如何在PictureBox控件上实现鼠标每左击一次就会画一个点呢?这里总结了两种方法. 首先在窗体上插入PictureBox控件和一个Button按钮,当点击按 ...

  3. 在ListView控件中绘底图

    ListView控件是Windows程序开发中的常用控件, 利用它可以把需要用户进行选择操作的多个项目在窗口中以列表的方式显示, 每一个项目可以有它的小图标和大图标, 从而可以改善程序的用户界面, 方 ...

  4. Windows10下VB6.0开发——利用PictureBox控件实现数据点实时绘图

    前言:VB中可以利用PictureBox控件实现数据点实时绘图功能. 1. 控件PictureBox   下面是PictureBox的图标:   将该控件拖到目标位置后调整它的形状大小,修改它的背景色 ...

  5. Winform中pictureBox控件SizeMode属性

    pictureBox里面的图片大小是不可调整的因为他随着pictureBox控件而变化的 有时候我们会遇到加载图片太长或者太宽了导致图片显示不全 这个时候就使用到了SizeMode里面的Zoom属性了 ...

  6. 用 C# picturebox 控件画图

    [日志] 2019/7/18 今天介绍一下 C# 用 picturebox 画坐标系 2020/6/13 这篇文章是在去年发的,没想到帮助了很多朋友.这段时间又用到 PictureBox 控件绘图了, ...

  7. C#利用Picturebox控件显示图片

    源文章:https://blog.csdn.net/liyuqian199695/article/details/54098938 C#利用Picturebox控件显示图片 1.Picturebox控 ...

  8. c# picturebox控件的使用方法介绍

    以TIM软件为例,"微信登陆"图标默认显示为灰色. 鼠标移动到图标,"预览"显示的图标变化为高亮状态. 点击登陆图标,登陆图标高亮显示.再次移动鼠标,不发生&q ...

  9. C#Winform中picturebox控件加载图片后无法释放

    问题描述   最近测试程序功能时发现存在图片资源一直被占用无法释放的问题.   程序的功能大致为打开窗口时从服务器端临时下载图片到本地,然后在窗口中显示,窗口关闭时清除下载的文件.在最后关闭窗口时会报 ...

最新文章

  1. 容灾备份技术的分类概述
  2. 统计学习笔记(3)——k近邻法与kd树
  3. 【Linux】—— /usr/bin/perl 被 MySQL-community-server-8.0.26-1.el7.x86_64 需要,解决方法
  4. python解析sql文件_如何从Python中解析sql文件?
  5. 修改docx表格_实例29_在Word表格中将上下行相同内容的单元格自动合并
  6. Youki的笔记本重装系统的方法~
  7. 查看centos硬件配置
  8. C++网络编程实例(初识多线程)
  9. c语言数字按键消息响应',c语言程序使用钩子拦截键盘消息的有关问题
  10. 浅谈Mysql底层索引原理
  11. cuda+cudnn安装(cudnn下载失败解决),环境配置以及遇到的问题记录!
  12. java取拼音首字母_java取出汉字字符串的拼音首字母
  13. java程序 扑克牌概率_java扑克牌洗牌程序,求抽可以抽出特定牌的次数
  14. 2013-2014年度总结
  15. win10系统暴雪战网连不上服务器,win10系统暴雪战网无法登陆的解决方法
  16. git之merge和rebase
  17. 字体在其他浏览器正常在IE8显示字体模糊问题解决
  18. 高并发系统设计:消息队列的三大作用:削峰填谷、异步处理、模块解耦
  19. String------字符串的字母大小写切换及获取
  20. Linux 学习笔记3 -- 常用 Linux 命令的基本使用

热门文章

  1. 【Vue2-尚硅谷四】Vue ---ajax---插槽slot
  2. 王者s19服务器维护到什么时候,王者荣耀S19赛季3月31日更新时间到什么时候?王者荣耀S19赛季更新公告更新内容汇总...
  3. 11月8-10日上海新国际博览中心 SFE第35届上海国际连锁加盟展归来
  4. 普通话水平测试考试命题自拟-(背诵版)
  5. 远翔升压FP5217。内置MOS大瓦数升压芯片
  6. Vue中的 props 属性
  7. js DOM节点操作之创建、添加、删除和克隆节点
  8. Android 挂逼 修炼之行---微信实现本地视频发布到朋友圈功能
  9. 卡巴斯基 更新数据源
  10. 一种简单可落地的分布式事务实践方案