因为工作需要用到C#,所以这里简单学习了一下C#的知识:
1. C#与.NET的关系
C#本身是一门语言,它是用于生成面向.NET环境的代码,但其并不是.NET的一部分。换言之,C#编写的代码总是运行在.NET Framework中。而且,在很多时候,C#的特定功能依赖于.NET。比如,在C#中声明一个int类型,实际上是.NET中System.Int32类的一个实例。
它是专门为与Microsoft的.NET Framework一起使用而设计的。
C#就其本身而言是一种语言,尽管它是用于生成面向.NET环境的代码,但它本身不是.NET的一部分。.NET支持的一些特性,C#并不支持。而C#语言支持的另一些特性,.NET却不支持(例如运算符重载)。
————————————————
版权声明:本文为CSDN博主「陈小白…」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44543431/article/details/97231772
通俗理解:
C#编程语言,.NET是运行环境,类似于与java和jdk,不同的是C#和.net是独立存在的.
2. C#介绍:
C#是微软公司发布的一种面向对象的、运行于.NET Framework之上的高级程序设计语言。C#看起来和Java有着惊人的相似;他包括了诸如单一继承,界面,与Java几乎同样的语法,和编译成中间代码再运行的过程,但是C#与Java又有着明显的不同,它借鉴了Delphi的 一个特点,与COM(组件对象模型)是直接集成的,而它是微软公司.NET windows网络框架的主角。
C#是一种编译语言,不是脚本语言。脚本语言不会被编译,而只是逐条语句读取并进行解释(读取一条语句,翻译成机器码或者虚拟机码并立即执行,一条指令有可能翻译成多条机器指令),这对于像javascript这样的东西很有用,但是当对性能有要求时,编译语言就是可行的方法。
————————————————
版权声明:本文为CSDN博主「陈小白…」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44543431/article/details/97231772
3. C#的优点
相比Java,有更先进的语法体系。
由于C#是后生,所以其很多语法都是其他语言的改进。作为一个商业产品,C++用户是C#的首要目标,所以它充分照顾到了C++用户的习惯。从整体看,C#语法比Java语法更优雅。
C#的IDE功能非常强大,C#3的文档有包含中文的多国语言,C#所需要的运行平台在用户量极大的windows上内置。
4. C#中的构成:Program.cs、Form1.cs、Form1.Designer.cs
一、 Form.cs和Form.Designer.cs其实是一个类,Visual Studio为了让我们方便管理,用partial关键字把窗体类给拆开了。
1.
Form.Designer.cs(窗体的设计类)存放窗体的布局,你的窗体定义了哪些控件,那些控件的名字、属性等等,都是存放在Form.Designer.cs里面的----最好不要在这里写/修改代码;
2.
而Form.cs(窗体的逻辑类)则是用来存放处理方法的,比如你的按钮点击事件绑定了Button_Click这个方法,就会出现在Form.cs里;
3.
而Form.resx则是用来存放你的窗体资源的,比如你自定义了你的窗体的图标,这个图标就会出现在Form.resx里面。
其实在早期版本的Visual Studio里,是没有Form.Designer.cs的,窗体的设计类和逻辑类全在一个文件里。
用partial关键字把窗体类拆开之后:便于管理。编程的时能够更加直观。
Program.cs是程序入口,也就是Main函数。 Form1.cs是实现功能的代码,包括你的自定义方法和事件。 Form1.Designer.cs是你的画面的设计代码,一般由系统自动生成,也可以手动修改。 (1)新建一个winform,打开Form1.Designer.cs文件。 (2)从工具箱拖出一个按钮放在窗体上,打开Form1.Designer.cs文件,和之前的备份相比,发现多出来的内容如下:
分别是:
1.定义一个button1
2.实例化button1
3.给button1的属性赋值(位置、大小、名称等等)
4.将button1添加到窗体的控件之中
这样,我们可以用同样的方法,在Form1.Designer.cs里面添加相似的代码,添加一个button2,
然后打开Form1的设计界面,发现会多出来一个button2的按钮。
原文链接:C#中的构成:Program.cs、Form1.cs、Form1.Designer.cs_zxf347085420的博客-CSDN博客
5.【C#】Winform中事件触发机制学习
一、消息
在一个Form窗体中拖个按钮,双击后系统自动生成代码:
private void button1_Click( object sender ,EventArgs e) { }
同时在窗体的InitializeComponent( )方法中自动生成代码
this.button1.Click += new System.EventHandler( this.button1_Click);
当 Form 程序运行起来后点击 按钮 时,就会触发按钮的 Click 事件,并通过委托 System.EventHandler 来实现对 button1_Click( object sender , EventArgs e)
方法的调用。
但是,为什么点击按钮时,就会触发Click事件呢?要弄明白这个问题,就需要了解windows的消息机制了.
Windows 下应用程序的执行是通过消息驱动的。消息是整个应用程序的工作引擎,我们要理解掌握我们使用的编程语言是如何封装消息的原理
1、什么是消息(Message)
消息就是通知和命令。在.NET框架类库中的System.Windows.Forms命名空间中微软采用面向对象的方式重新定义了Message。新的消息(Message)结构的公共部分属性基本与早期的一样,不过它是面向对象的。
公共属性:
HWnd 获取或设定消息的处理函数
Msg 获取或设定消息的ID号
Lparam 指定消息的LParam字段
Wparam 指定消息的WParam字段
Result 指定为响应消息处理函数而向OS系统返回的值
2、消息驱动的过程
所有的外部事件,如键盘输入、鼠标移动、按动鼠标都由OS系统转换成相应的消息发送到应用程序的消息队列。每个应用程序都有一段相应的程序代码来检索、分发这些消息到对应的窗体,然后由窗体的处理函数来处理。
二、C#中消息的封装
C#对消息重新进行了面向对象的封装,在C#中消息被封装成了事件。
System.Windows.Forms.Application类具有用于启动和停止应用程序和线程以及处理Windows消息的方法。调用Run以启动当前线程上的应用程序消息循环,并可以选择使其窗体可见。调用Exit或ExitThread来停止消息循环。C#中的Application类来处理消息的接受和发送的。消息的循环是由它负责的。
对于一个可视控件来说,是不断的在接受系统发送的消息的。比如鼠标悬停在某控件上,就是一个消息,移出这个控件又是一个消息。事件里编写的代码,和方法里写的代码,最主要的不同就在前者是不知道何时触发,而后者是自己调用运行到那里就执行的。那谁来决定某个事件何时触发呢?那就是消息。
在Form类中,有一个protected override void DefWndProc( ref Message )
方法,该方法重写了Control中的DefWndProc方法,通过该方法,我们就能截获到Windows系统发送给Form的消息内容,其中m.Msg属性可以获得消息的id,用来分辨是哪个操作触发了消息。根据Message中的内容,就可以确定触发Form中哪个控件的哪个事件,从而完成整个事件的触发过程。
参考资料
C#消息机制
C#使用消息(SendMessage)进行跨进程操作
原文链接:【C#】Winform中事件触发机制学习_It_sharp的博客-CSDN博客
6. 解释一下 private void button1_Click(object sender, EventArgs e){}
( object sender , EventArgs e ) 是C#里面的事件响应的代码
总结如下:
(1) EventArgs是包含事件数据的类的基类,用于传递事件的细节。
(2) EventHandler是一个委托,声明如下(其在.Net类库中如下声明的)
通过 this.button1.Click += new System.EventHandler( this.button1_Click);
委托 public delegate void EventHandler( object sender , EventArgs e )
(3) 所以,所有形如: void 函数名(object 参数名,EventArgs 参数名); 的函数,都可以作为Control类的Click事件响应方法了。object的参数名一般用Source或Sender来表示,两个没有区别。
如下面所定义的一个事件响应方法:
private void button1_Click(object sender, System.EventArgs e)
参数 object sender 表示引发事件的对象(其实这里传递的是对象的引用,如果是 button1 的 click 事件则 sender 就是 button1),System.EventArgs e 代表事件的相应信息。
下面我们可以看下 Button 类的事件声明,以 Click 事件为例。
public event EventHandler Click;
这里定义了一个 EventHandler 类型的事件 Click
private void button1_Click(object sender, System.EventArgs e) { ... }
这是我们和 button1_click 事件所对应的方法。
那我们怎么把这个方法和事件联系起来呢,请看下面的代码。
this.button1.Click += new System.EventHandler(this.button1_Click);
把 this.button1_Click 方法绑定到 this.button1.Click 事件。
以上原理简单理解下就可以了,在实际操作中我们只需要在代码里面调用 Web 控件里面使用 button 控件,里面的属性 OnClick="button1_Click" 语句便可以起到调用方法的功能了。
在 VS。NET 中可以直接在设计页面加入 button, 而上面红色的那行代码编译器会自动实现(可在cs代码文件里面看到)。
7. C# 事件(event) 与 委托(delegate):
windows消息处理机制是非常重要的,其实 C# 事件就是基于 windows消息处理机制的,只是封装的更好,让开发者无须知道底层的消息处理机制,就可以开发出强大的基于事件的应用程序来。
参考文章:
1. https://www.cnblogs.com/qwg123/p/4641133.html
2. C#委托之个人理解 - loose_went - 博客园 (cnblogs.com)
8. C#异步委托之委托使用的时机浅析
C#异步委托之委托使用的时机1、事件调用(最常用):
事件调用(最常用)是委托用的最多的地方。
(1) 比如button1的点击事件:
通过winform生成一个窗体Form1,它包含 Form1.cs(设计),Form1.cs,Form1.Designer.cs,然后,在 Form1.cs(设计) 中拖拽生成一个按钮button1;
然后双击这个 button1,就会在 Form1.cs 中自动生成事件方法,如下代码:
private void button1_Click( object sender ,EventArgs e) { }
在 Form1.Designer.cs 中自动生成如下代码:
this.button1.Click += new System.EventHandler( this.button1_Click);
实际上,C# 自身定义了一个 点击事件相关等 的委托类,如下:
#region 程序集 mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 // C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.5.2mscorlib.dll #endregion using System.Runtime.InteropServices; namespace System { // // 摘要: // 表示将用于处理不具有事件数据的事件的方法。 // // 参数: // sender: // 事件源。 // // e: // 不包含事件数据的对象。 [ComVisible(true)] public delegate void EventHandler(object sender, EventArgs e); }
这样,当 Form 程序运行起来后点击 按钮 时,就会触发按钮的 Click 事件,并通过委托 System.EventHandler,
来实现对 button1_Click( object sender , EventArgs e)
方法的调用。(这里点击按钮,就相当于调用了 EventHandler.Invoke(this, args) 方法)。
(2) 我们为了使窗口之间的数据可以进行传递,经常会使用窗体定义事件。
最常见的情况是这样的,用户为了查询一个客户,在窗体 B 中单击“查询”按钮后,弹出一个客户查询窗体(这里定义为 A),
在 A 中查询出指定的结果后,单击“选择”按钮,A 关闭,窗体 B 上就获得了查询的客户信息。
基于这种情况,我们一般会在 A 中定义一个事件,然后会在 B 中写一个,当这个事件发生后 调用的方法(函数)。
那么怎么把 A 的事件和 B 的方法结合起来呢,这个时候就需要用到委托了。
实际上,委托就是事件和方法之间的桥梁。
模仿 click 点击事件 的调用过程,就可以定义我们自己的委托,比如:
有一个主界面MainForm.cs,主界面嵌入一个自定义窗体组件 PaxListView.cs,然后在 PaxListView.cs 界面的表格中,选中一名旅客,
然后通过快捷键 Alt+W,查看这个旅客的详细信息功能,可以触发 从 PaxListView.cs 中的事件,调用到 MainForm.cs 中的方法。
具体过程如下(委托用法的简单步骤):
举例1:两个参数的委托
1,在 PaxListView.cs 中,定义一个 委托参数 的内部类 带有一个有参的 构造方法:
public class PaxInfoShowEventArgs : EventArgs { public paxInfo PaxInfo; public PaxInfoShowEventArgs(paxInfo info) { this.PaxInfo = info; } }
2,在 PaxListView.cs 中,定义一个委托:
public delegate void PaxInfoShowHandler(object sender, PaxInfoShowEventArgs e);
3,在 PaxListView.cs中,声明一个 刚刚定义好的 PaxInfoShowHandler 委托类型的 变量 PaxDetailedInfoShow:
public event PaxInfoShowHandler PaxDetailedInfoShow; // 显示旅客详细信息事件
4,在 MainForm.Designer.cs 中,定义需要投入委托的方法 (在点击事件委托案例中,方法由 Winform 自动生成,只需添加方法逻辑即可。也可以自己手动定义。):
//这里添加要显示的详细信息 private void paxListView1_PaxDetailedInfoShow(object sender, PaxInfoShowEventArgs e) { this.switchTable.InnerPaxDetailedInfo.DataBind(e.PaxInfo); this.switchTable.InnerTabControl.SelectTab("DetailedInfo"); }
5,构建 PaxListView.cs 中的 委托事件 PaxDetailedInfoShow 与 MainForm.cs 中的 委托方法 paxListView1_PaxDetailedInfoShow 的桥梁(由 Winform 自动生成,也可以手动定义):
(1) 通过 this.paxListView1.PaxDetailedInfoShow 关联 PaxListView.cs 中的 委托变量 PaxDetailedInfoShow,
(2) 通过 ECI.Controls.PaxInfoShowHandler(...) 方法,去找到 并 调用这个 PaxInfoShowHandler 这个委托,
(3) 通过 this.paxListView1_PaxDetailedInfoShow 关联 投入到这个 PaxDetailedInfoShow 委托变量中 需要执行的 方法
this.paxListView1.PaxDetailedInfoShow += new ECI.Controls.PaxInfoShowHandler(this.paxListView1_PaxDetailedInfoShow);
6,执行委托:通过键盘快捷方式 Alt+W 调用 PaxDetailedInfoShow 的 Invoke 方法,委托给它去调用 MainForm.cs 中的方法:
switch (e.KeyCode) { ... ... case Keys.W: // 展示详细信息 if (!e.Alt) return; if (this.info.method != "A") return; paxId = this.gridPaxList.CurrentRow.Cells["PaxID"].Value.ToString(); foreach (paxInfo pax in this.info.paxList) { if (pax.paxId == paxId) { currentPaxInfo = pax; break; } } args = new PaxInfoShowEventArgs(currentPaxInfo); PaxDetailedInfoShow.Invoke(this, args); this.gridPaxList.Focus(); break; }
举例2:一个参数的委托
1,定义一个委托
public delegate void MyInvoke(TreeNode str);
2,定义需投入委托操作的方法
/// <summary> /// 1,利用this.Invoke封装添加节点 /// 2,还原部分显示信息 /// </summary> private void CreateTreeNode(TreeNode node) { this.trColorGroup.Nodes.Add(node); if (trColorGroup.Nodes[0].Nodes.Count > 0) trColorGroup.SelectedNode = trColorGroup.Nodes[0].Nodes[0]; this.Text = this.Text.Replace(Definition.SHOW_WAITING_MESSAGE, ""); this.Enabled = true; }
3,执行委托
MyInvoke mi = new MyInvoke(CreateTreeNode); this.Invoke(mi,firstLevelNode);
参考文章:
1. C#异步委托之委托使用的时机浅析 - 大明1986 - 博客园 (cnblogs.com)
2. 委托、线程的用法 - 大明1986 - 博客园 (cnblogs.com)
3. 委托 - 标签 - 大明1986 - 博客园 (cnblogs.com)
实践应用:
近期在工作中,需要对C#的前端登录功能,用Java的微服务架构 进行重构,在C#端实现远程调用Java端的一个接口,get请求url返回一张图片,Java端本以ImageIO流的形式发送图片,但因为公共的get/post请求方法中含有一些权限校验功能,不能通过其他方式进行调用,所以将java端加了返回值类型,以方便C#客户端通过公共get请求方法获取图片,但之前已经写了一些代码,通过C#的HttpWebRequet获取get请求的url返回的ImageIO,这些代码扔掉怪可惜的,因此,在这里做下记录:
功能示例:在C#端调用Java端的接口:
Java端(springboot):调用接口:IO流传输代码:
/** * 创建验证码 * * @param deviceId * @param response * @throws Exception */ @GetMapping("validateCode") public void createCode(@RequestParam String deviceId, HttpServletResponse response) throws Exception { Assert.notNull(deviceId, "机器码不能为空"); response.setHeader("Cache-Control", "no-store, no-cache"); response.setContentType("image/jpeg"); // 生成图片验证码 BufferedImage image = validateCodeUtil.createCode(deviceId); try (ServletOutputStream out = response.getOutputStream()) { ImageIO.write(image, "JPEG", out); } }
C#端(vs2019):通过HttpWebRequest接收验证码:
/// <summary> /// update-content:修改登录验证码图片加载功能; /// update-author:fhy /// update-date:2021-03-10 /// update-details:沿用旧系统ReloadValidateCode()方法,将旧系统在客户端生成验证码图片功能, /// 改为调用新系统接口,以get请求方式 获取验证码图片; /// others:在开始引入了using ECI.http;,想使用请求新系统的get请求; /// </summary> private void ReloadValidateCode() { /* * 1.下面三句 被注释的 代码是旧系统 验证码图片 的生成功能; * 2.新系统的 验证码图片 通过get请求返回ImageIO流,此处通过GetPic(string url)方法获取, * 返回值为 Image,再将Image 转为 BitMap 类型; * 3.新系统图片大小设置,按照旧系统原始图片大小,设置为 width=60, height=22. */ /*CreateValidateCode codeService = new CreateValidateCode(); ValidateCode = codeService.CreateCode(4); Bitmap image = codeService.CreateImage(ValidateCode);*/ //获取新系统验证码图片的 get请求,拼接参数deviceId后的url地址 string uuid = System.Guid.NewGuid().ToString(); deviceId = uuid; string url = "http://localhost:8002/lds-cki-shell/validateCode?deviceId="+uuid; //获取请求验证码图片 为Image类型,并将其转换为Bitmap类型; Bitmap image = new Bitmap(GetPic(url)); //设置画板,绘制新图片 Bitmap b = new Bitmap(60, 22); Graphics g = Graphics.FromImage((System.Drawing.Image)image); g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; //绘制图像 g.DrawImage(image, 0, 0, 60, 22); g.Dispose(); //Console.WriteLine(image.Width.ToString()+","+image.Height.ToString()); this.picValidateCode.Width = 60;// image.Width; this.picValidateCode.Height = 22;// image.Height; this.picValidateCode.BackgroundImage = image; } /// <summary> /// content:获取新系统 验证码图片 /// author:fhy /// date:2021-03-10 /// details:获取url请求到的图片 Image IO流,转换成 Image 类型; /// others:HTTP请求获取图片, /// 这里要使用HttpWebRequest对象,HttpWebRequest缺少Using集的引用,只要添加using System.Net; /// 这里要使用Stream对象,只要添加using System.IO;就可以了. /// </summary> public Image GetPic(string url) { /* * 1.创建HTTP请求,类型是HttpWebRequest,否则无法设置其Http参数,简单设置了Method为GET; * 2.使用GetResponse()方法获取响应,注意as HttpWebResponse不能省; * 3.使用响应的GetResponseStream获取响应流; * 4.使用Image.FromStream从响应流创建Image对象; * 5.然后关闭响应流,返回图片. */ HttpWebRequest req = WebRequest.CreateHttp(url); req.Method = "GET"; var response = req.GetResponse() as HttpWebResponse; Stream stm = response.GetResponseStream(); Image image = Image.FromStream(stm); stm.Close(); return image; }
解释:
1. C# Bitmap 与 Image 之间的转换:Bitmap是从Image类继承的一个图像类,它封装了Windows位图操作的常用功能。
1 Image img = this.pictureBox1.Image; 2 Bitmap map = new Bitmap(img); 3 Image img = Bitmap;
2. 将图像转换为Bitmap:Bitmap b =
new
Bitmap(img);
3. 创建一个调整图像大小的方法:
private static System.Drawing.Image resizeImage(System.Drawing.Image ...) { ... Bitmap b = new Bitmap(destWidth, destHeight); Graphics g = Graphics.FromImage((System.Drawing.Image)b); g.InterpolationMode = InterpolationMode.HighQualityBicubic; //绘制图像 g.DrawImage(imgToResize, 0, 0, destWidth, destHeight); g.Dispose(); return (System.Drawing.Image)b; }
C#改变图片大小:今天一女同事要做一个改变图片大小的功能,然后我就手写了几行代码,以后可能用得上
byte[] buffer = new byte[1]; //Byte转为Image对象 MemoryStream ms = new MemoryStream(buffer); Image image = System.Drawing.Image.FromStream(ms); //设置画布,绘画新图片 Bitmap b = new Bitmap(200, 200); Graphics g = Graphics.FromImage((System.Drawing.Image)b); g.InterpolationMode = InterpolationMode.HighQualityBicubic; //绘制图像 g.DrawImage(image, 0, 0, 200, 200); g.Dispose(); //保存图片 b.Save("aaaaaaaaa");
/// <summary> /// update-content:修改登录验证码图片加载功能; /// update-author:fhy /// update-date:2021-03-11 /// </summary> private void ReloadValidateCode() { /* * 1.下面三句 被注释的 代码是旧系统 验证码图片 的生成功能; * 2.新系统的 验证码图片 通过get请求返回base64字符串,此处通过GetPic(string url)方法获取, * 返回值为 Image,再将Image 转为 BitMap 类型; * 3.新系统图片大小设置,按照旧系统原始图片大小,设置为 width=60, height=22. */ //CreateValidateCode codeService = new CreateValidateCode(); //ValidateCode = codeService.CreateCode(4); //Bitmap image = codeService.CreateImage(ValidateCode); //获取新系统验证码图片的 get请求,拼接参数deviceId后的url地址 deviceId = System.Guid.NewGuid().ToString(); //string url = "http://localhost:8002/lds-cki-shell/validateCode?deviceId="+deviceId; string url = "validateCode?deviceId="+ deviceId; //发送get请求,获取验证码图片的64base串,转为图片,缩小大小为(60,22),并回显到登录页面 Results<string> results = RestTemplateUtil.getForObject<String>(url); string base64 = results.Data; byte[] byteImage = Convert.FromBase64String(base64); MemoryStream ms = new MemoryStream(byteImage); //获取请求验证码图片,并将其转换为Bitmap类型; Bitmap image = new Bitmap(Image.FromStream(ms)); //设置画板,绘制新图片 Bitmap b = new Bitmap(60, 22); Graphics g = Graphics.FromImage((System.Drawing.Image)image); g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; //绘制图像 g.DrawImage(image, 0, 0, 60, 22); g.Dispose(); this.picValidateCode.Width = 60;// image.Width; this.picValidateCode.Height = 22;// image.Height; this.picValidateCode.BackgroundImage = image; }
参考:
//保存目录 string dir = "/upload/user/head"; //站点文件目录 string fileDir = HttpContext.Current.Server.MapPath("~" + dir); //文件名称 string fileName = "headdemo" + DateTime.Now.ToString("yyyyMMddHHmmssff"); //保存文件所在站点位置 string filePath = Path.Combine(fileDir, fileName); if (!System.IO.Directory.Exists(fileDir)) System.IO.Directory.CreateDirectory(fileDir); //读图片转为Base64String System.Drawing.Bitmap bmp1 = new System.Drawing.Bitmap(Path.Combine(fileDir, "default.jpg")); using (MemoryStream ms1 = new MemoryStream()) { bmp1.Save(ms1, System.Drawing.Imaging.ImageFormat.Jpeg); byte[] arr1 = new byte[ms1.Length]; ms1.Position = 0; ms1.Read(arr1, 0, (int)ms1.Length); ms1.Close(); UserPhoto = Convert.ToBase64String(arr1); } //将Base64String转为图片并保存 byte[] arr2 = Convert.FromBase64String(UserPhoto); using (MemoryStream ms2 = new MemoryStream(arr2)) { System.Drawing.Bitmap bmp2 = new System.Drawing.Bitmap(ms2); bmp2.Save(filePath + ".jpg", System.Drawing.Imaging.ImageFormat.Jpeg); //bmp2.Save(filePath + ".bmp", System.Drawing.Imaging.ImageFormat.Bmp); //bmp2.Save(filePath + ".gif", System.Drawing.Imaging.ImageFormat.Gif); //bmp2.Save(filePath + ".png", System.Drawing.Imaging.ImageFormat.Png); }
//将Base64String转为图片并保存 byte[] arr2 = Convert.FromBase64String(UserPhoto); using (MemoryStream ms2 = new MemoryStream(arr2)) { System.Drawing.Bitmap bmp2 = new System.Drawing.Bitmap(ms2); ////只有把当前的图像复制一份,然后把旧的Dispose掉,那个文件就不被锁住了, ////这样就可以放心覆盖原始文件,否则GDI+一般性错误(A generic error occurred in GDI+) //System.Drawing.Bitmap bmpNew = new System.Drawing.Bitmap(bmp2); //bmp2.Dispose(); //bmp2 = null; bmp2.Save(filePath, System.Drawing.Imaging.ImageFormat.Jpeg); //bmp2.Save(filePath + ".jpg", System.Drawing.Imaging.ImageFormat.Jpeg); //bmp2.Save(filePath + ".bmp", System.Drawing.Imaging.ImageFormat.Bmp); //bmp2.Save(filePath + ".gif", System.Drawing.Imaging.ImageFormat.Gif); //bmp2.Save(filePath + ".png", System.Drawing.Imaging.ImageFormat.Png); bmp2.Dispose(); }
GDI+一般性错误(A generic error occurred in GDI+)
1.GDI+的前世今生
GDI+全称图形设备接口,Graphics Device Interface (GDI) ,他的爸爸叫做GDI, 用C写的。Windows XP出来以后用C++重新写了一下,变成了GDI+。从.NET Framework 1.0开始,GDI+就被正式封装在了.NET Framework里面,并被广泛地应用到了所有和图形图像相关的程序中。不幸的是,这个GDI+引入了微软有史以来最大的2个patch,造成了Microsoft IT, Support, Developer, Tester的无数麻烦。[1][2]
GDI+没有用显卡加速,所以Windows Vista推荐用Windows Display Driver Model (WDDM)了,支持渲染,3D加速。不过普通的应用程序,用GDI/GDI+其实是完全足够了,所以GDI+是在微软平台上开发图形图像程序的最好选择了。至少现在没有听说微软准备重新写GDI。
GDI+ 可以用来做图形处理,也可以做图像处理。这里只分析几个使用.NET Framework容易出错的地方。
2. GDI+一般性错误(A generic error occurred in GDI+)
这是使用GDI+的时候最滑稽的一个Exception,里面啥信息都没有。对于刚刚开始使用.NET Framework开发者来说,很难发现这个问题到底是为什么。
我们先来看看下面一段代码
string fileName = "sample.jpg"; Bitmap bmp = new Bitmap(fileName); bmp.Save(fileName, ImageFormat.Jpeg);
这段代码的目的是要打开一个Bitmap,然后保存。可惜这段代码一定会给你一个GDI+一般性错误:
System.Runtime.InteropServices.ExternalException
其中的Error Code是0x80004005, innerException是空。如果你查Windows的Error Code表,会发现这个错误原因是“Unspecified Error”,还是什么都不知道。这其实是.NET Framework封装不好的问题,我们可以调用
Marshal.GetLastWin32Error()
拿到Win32的Error, 32。这个错误代码就有点信息量了,在winerror.h里面,我们可以找到下面的定义:
// // MessageId: ERROR_SHARING_VIOLATION // // MessageText: // // The process cannot access the file because it is being used by another process. // #define ERROR_SHARING_VIOLATION 32L
原来是文件不能写。其实MSDN里面有一句话,The file remains locked until the Bitmap is disposed。所以文件读取以后是锁着的,没有办法写。那如果我想做点改动然后再保存原来的文件怎么办呢?
这里有个土办法可以搞定这个问题
Bitmap bmpTemp = new Bitmap(image); Bitmap bmp = new Bitmap(bmpTemp); bmpTemp.Dispose(); bmp.Save(image, ImageFormat.Jpeg);
只要把当前的图像复制一份,然后把旧的Dispose掉,那个文件就不被锁住了,这样就可以放心覆盖原始文件了。
想想如果你要用GDI+写一个Painter,很容易你就会遇到这个问题。
vs2019的常用操作:
为了测试一个函数的返回值,就在某个函数里加了一个局部变量,调试却不显示所添加变量的信息。
你一定设置成了release 模式。改为debug就可以了。
比较弱智的问题,mark一下。
参考文章:
1. https://blog.csdn.net/weixin_44543431/article/details/97231772
2. https://www.cnblogs.com/efreer/p/11362601.html
n. https://www.cnblogs.com/mgzy/p/3994326.html
图片放大缩小剪切
package cn.conris.sys.utils; import java.io.*; import java.awt.*; import java.awt.image.*; import java.awt.Graphics; import java.awt.color.ColorSpace; import javax.imageio.ImageIO; //图片放大缩小 public class ChangeImageSize { /** */ /** * 缩放图像 * * @param srcImageFile * 源图像文件地址 * @param result * 缩放后的图像地址 * @param scale * 缩放比例 * @param flag * 缩放选择:true 放大; false 缩小; */ public static void scale(String srcImageFile, String result, boolean flag) { try { BufferedImage src = ImageIO.read(new File(srcImageFile)); // 读入文件 int width = src.getWidth(); // 得到源图宽 int height = src.getHeight(); // 得到源图长 double scale = 1; if (width > height) { scale = height / 64; } else { scale = width / 64; } if (flag) { // 放大 width = (int) (width * scale); height = (int) (height * scale); } else { // 缩小 width = (int) (width / scale); height = (int) (height / scale); } Image image = src.getScaledInstance(width, height, Image.SCALE_DEFAULT); BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics g = tag.getGraphics(); g.drawImage(image, 0, 0, null); // 绘制缩小后的图 g.dispose(); ImageIO.write(tag, "JPEG", new File(result));// 输出到文件流 BufferedImage src2 = ImageIO.read(new File(result)); // 读入文件 //以图片中心剪切64*64的新图 int x = 0; int y = 0; if (src2.getWidth() > src2.getHeight()) { x = (src2.getWidth() - 64) / 2; // 得到剪切点x坐标 y = 0; // 得到剪切点y坐标 } else { y = (src2.getHeight() - 64) / 2; x = 0; } convert(result,result);//把图片转为jpg格式开始剪切 OperateImage opimg = new OperateImage(result, result, x, y, 64, 64);//图片剪切为64*64(64,64为剪切点开始剪切的宽高) opimg.cut(); } catch (IOException e) { e.printStackTrace(); } } // /** *//** // * 图像切割 // * @param srcImageFile 源图像地址 // * @param descDir 切片目标文件夹 // * @param destWidth 目标切片宽度 // * @param destHeight 目标切片高度 // */ // public static void cut(String srcImageFile, String descDir, int // destWidth, int destHeight) // { // try // { // Image img; // ImageFilter cropFilter; // // 读取源图像 // BufferedImage bi = ImageIO.read(new File(srcImageFile)); // int srcWidth = bi.getHeight(); // 源图宽度 // int srcHeight = bi.getWidth(); // 源图高度 // if (srcWidth > destWidth && srcHeight > destHeight) // { // Image image = bi.getScaledInstance(srcWidth, srcHeight, // Image.SCALE_DEFAULT); // destWidth = 200; // 切片宽度 // destHeight = 150; // 切片高度 // int cols = 0; // 切片横向数量 // int rows = 0; // 切片纵向数量 // // 计算切片的横向和纵向数量 // if (srcWidth % destWidth == 0) // { // cols = srcWidth / destWidth; // } // else // { // cols = (int) Math.floor(srcWidth / destWidth) + 1; // } // if (srcHeight % destHeight == 0) // { // rows = srcHeight / destHeight; // } // else // { // rows = (int) Math.floor(srcHeight / destHeight) + 1; // } // // 循环建立切片 // // 改进的想法:是否可用多线程加快切割速度 // for (int i = 0; i < rows; i++) // { // for (int j = 0; j < cols; j++) // { // // 四个参数分别为图像起点坐标和宽高 // // 即: CropImageFilter(int x,int y,int width,int height) // cropFilter = new CropImageFilter(j * 200, i * 150, destWidth, // destHeight); // img = Toolkit.getDefaultToolkit(), .createImage( // new FilteredImageSource(image.getSource(), cropFilter)); // BufferedImage tag = new BufferedImage(destWidth, destHeight, // BufferedImage.TYPE_INT_RGB); // Graphics g = tag.getGraphics(); // g.drawImage(img, 0, 0, null); // 绘制缩小后的图 // g.dispose(); // // 输出为文件 // ImageIO.write(tag, "JPEG", new File(descDir + "pre_map_" + i + "_" + j + // ".jpg")); // } // } // } // } // catch (Exception e) // { // e.printStackTrace(); // } // } /** */ /** * 图像类型转换 GIF->JPG GIF->PNG PNG->JPG PNG->GIF(X) */ public static void convert(String source, String result) { try { File f = new File(source); f.canRead(); f.canWrite(); BufferedImage src = ImageIO.read(f); ImageIO.write(src, "JPG", new File(result)); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** */ /** * 彩色转为黑白 * * @param source * @param result */ public static void gray(String source, String result) { try { BufferedImage src = ImageIO.read(new File(source)); ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); ColorConvertOp op = new ColorConvertOp(cs, null); src = op.filter(src, null); ImageIO.write(src, "JPEG", new File(result)); } catch (IOException e) { e.printStackTrace(); } } /** */ /** * @param args */ public static void main(String[] args) { scale("d://2.jpg", "d://3.jpg", false); } }
package cn.conris.sys.utils; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Iterator; import javax.imageio.ImageIO; import javax.imageio.ImageReadParam; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; public class OperateImage { // ===源图片路径名称如:c:1.jpg private String srcpath; // ===剪切图片存放路径名称.如:c:2.jpg private String subpath; // ===剪切点x坐标 private int x; private int y; // ===剪切点宽度 private int width; private int height; public OperateImage() { } public OperateImage(String srcpath, String subpath, int x, int y, int width, int height) { super(); this.srcpath = srcpath; this.subpath = subpath; this.x = x; this.y = y; this.width = width; this.height = height; } /** 对图片裁剪,并把裁剪完的新图片保存 */ public void cut() throws IOException { FileInputStream is = null; ImageInputStream iis = null; try { // 读取图片文件 is = new FileInputStream(srcpath); /* * 返回包含所有当前已注册 ImageReader 的 Iterator,这些 ImageReader 声称能够解码指定格式。 * 参数:formatName - 包含非正式格式名称 . (例如 "jpeg" 或 "tiff")等 。 */ Iterator<ImageReader> it = ImageIO.getImageReadersByFormatName("jpg"); ImageReader reader = it.next(); // 获取图片流 iis = ImageIO.createImageInputStream(is); /* * <p>iis:读取源.true:只向前搜索 </p>.将它标记为 ‘只向前搜索’。 * 此设置意味着包含在输入源中的图像将只按顺序读取,可能允许 reader 避免缓存包含与以前已经读取的图像关联的数据的那些输入部分。 */ reader.setInput(iis, true); /* * <p>描述如何对流进行解码的类<p>.用于指定如何在输入时从 Java Image I/O * 框架的上下文中的流转换一幅图像或一组图像。用于特定图像格式的插件 将从其 ImageReader 实现的 * getDefaultReadParam 方法中返回 ImageReadParam 的实例。 */ ImageReadParam param = reader.getDefaultReadParam(); /* * 图片裁剪区域。Rectangle 指定了坐标空间中的一个区域,通过 Rectangle 对象 * 的左上顶点的坐标(x,y)、宽度和高度可以定义这个区域。 */ if(width==0){ this.width=100; } if(height==0){ this.height=100; } Rectangle rect = new Rectangle(x, y, width, height); // 提供一个 BufferedImage,将其用作解码像素数据的目标。 param.setSourceRegion(rect); /* * 使用所提供的 ImageReadParam 读取通过索引 imageIndex 指定的对象,并将 它作为一个完整的 * BufferedImage 返回。 */ BufferedImage bi = reader.read(0, param); // 保存新图片 ImageIO.write(bi, "jpg", new File(subpath)); } finally { if (is != null) is.close(); if (iis != null) iis.close(); } } }