zoukankan      html  css  js  c++  java
  • C#的初步接触与学习

    因为工作需要用到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关键字把窗体类拆开之后:便于管理。编程的时能够更加直观。

    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

    C#事件(event)解析

    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");

     C#中图片与BASE64码互相转换

            /// <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的常用操作:

    1. vs调试时,不显示局部变量

     

    为了测试一个函数的返回值,就在某个函数里加了一个局部变量,调试却不显示所添加变量的信息。

    你一定设置成了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();  
            }  
        }  
    }  

     底部

  • 相关阅读:
    table中tr间距的设定table合并单元格 colspan(跨列)和rowspan(跨行)
    使用jquery触发a标签跳转
    真正的让iframe自适应高度 兼容多种浏览器随着窗口大小改变
    html5 data属性的使用
    jQuery取得select选择的文本与值
    jqueryui教程
    密码复杂度
    zabbix配置微信报警
    tomcat配置域名访问
    阿里云ecs禁止ping,禁止telnet
  • 原文地址:https://www.cnblogs.com/HarryVan/p/14503939.html
Copyright © 2011-2022 走看看