c# dialogresult 选择文件_c#实现一例Excel自动化小工具

工作是枯燥的,重复性的工作,更是枯燥中的枯燥;不幸的是,最近有个非常枯燥而又繁重的任务,让我老眼昏花心跳加速,感叹时光补与,其实任务很简单,就是统计客户订单后,需要把订单的属性保存到excel上传存档……

在一遍又一遍地打开excel以后,我发现这些订单有一个特点:订单号是递增的,而属性,绝大部分是重复的,于是我的偷懒天赋躁动不安起来,我不禁思考:能不能让电脑自动化操作,帮我分担一些任务呢?

说到自动化脚本,最早我是想用python的,但pyQt操作excel的库不太方便,我甚至异想天开地想用win API……

不过,等等!

既然说到win,为什么不用微软全家桶呢?

通过百度大法,我知道了C#+VS+winform这样糖分巨高的组合,而对于excel的脚本,微软贴心地准备好了各种api接口给你调用,这……比python的excel库不知高到哪里去了。(虽然用起来稍微麻烦一点)

接下来花了一天时间简单浏览了一下c#的官方教程,然后又扫了一眼excel的库,不得不说,MSDN太贴心了。

好了,预热结束,准备开搞,不过,在开搞之前,我得先准备点东西:

1,下载并安装VS2019 community,全程next无压力,就是……安装包有点大。好在我的百兆水管还能凑活;
2,将浏览器搜索工具由百度切换为bing,既然是面向搜索引擎编程,那就得换个好的工具不是(主要是bing对msdn特别友好,官方文档一搜就有)

做完这些准备工作之后,那就开始编(ctrl + C)程(ctrl + V)吧!

工具由三个主要部分组成:

  • 录入界面
  • excel操作
  • 截图保存

1,录入界面

这部分主要是考虑工具的可视化,用界面把重复信息录入,方便后续批量操作。

至于界面,当然用winform了,如果不需要用界面,跳过此步骤即可。

1.1创建窗体应用程序

v2-e421c712795abf18a181eab67a51972d_b.jpg

1.2拖控件

v2-a6f5656399aa2c6243d9977a66004f33_b.jpg
左侧为控制窗口,右侧为设计窗口

v2-e57133882a65d3db4e80f7019b6618a5_b.jpg

1.3写代码

直接在控件上双击,就能写相应的代码了,比如,我这个需要录入单号、厂家、客户信息,然后自动赋值给我的excel操作类,那么可以直接 在文本框双击,写下以下代码:

private 

我需要实现点击Generate即自动生成报告,那么双击Generate控件,直接写代码:

private void button_Generate_Click(object sender, EventArgs e)
        {
            Task task = new Task(SaveFile);
            task.Start();
        }

具体的操作后面再细讲。

2,excel操作

微软针对office互操作提供了API,官方文档如下,我只需要操作excel即可。

https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.excel?view=excel-pia​docs.microsoft.com

2.1 interop.excel介绍

excel的API操作逻辑是这样的:excel应用->workbook->worksheet->cell

第一步:创建一个excel应用:

excelApp = new Microsoft.Office.Interop.Excel.Application();
excelApp.Visible = false;//这里将excel设为不可见,如果你想看到excel自动操作的界面,那么可以设为true

第二步:创建workbook和worksheet

Workbook wkb = excelApp.Workbooks.Add();//新建
//Workbook wkb = excelApp.Workbooks.Open(filename);//打开一个现有文件,文件名为filename; 
Worksheet wks = wkb.Worksheets["Sheet1"];

需要注意的是,如果本机没有安装office,是不能打开现有excel文件的,只能创建(为啥提这个,因为我电脑上只装了WPS)。

第三步:操作单元格:

Microsoft.Office.Interop.Excel.Range rng = wks.get_Range("B1");//选取单元格
//Microsoft.Office.Interop.Excel.Range rng = wks.get_Range("B1:C14");//选取一组单元格,范围从B1至C14
rng.Value = "test";//填充单元格的值

2.2 插入OLE对象

除了将界面上的文字信息填入excel之外,我还希望将本地文件作为对象插入到excel,要怎么操作呢?

interop.excel提供了OLEObjects供提供。

OLEObjects 

2.4 插入图片

interop.excel不提供直接将图像对象插入excel的方法,那么有两种办法插入图片,

一是将图像复制到剪切板,再从剪切板插入;

            //Clipboard.SetImage(this.ImgOne);//直接操作剪切板是没用的,会报错,需要将这个方法用委托来实现。
public delegate void setClipBoard(Image img);//这个委托需要放到函数外; 
setClipBoard setClip = new setClipBoard(setCB);//这是自定义的将图像设置到系统剪切板的函数
setClip.Invoke(this.ImgOne);//将截图复制到剪切板

            int shapeCount;
            rng = wks.get_Range("B30");
            wks.Paste(rng);
            shapeCount = wks.Shapes.Count;//图像插入以后,就是一个shape,记录下shape的数量,方便操作。
            Console.WriteLine("shapeCount=" + shapeCount);
            Shape tempShape = wks.Shapes.Item(shapeCount);
//设置shape也就是刚刚插入的图片的缩放
            if(tempShape.Height > 100)
            {
                Console.WriteLine("resize pic");
//按高度缩放
                tempShape.ScaleHeight(100/tempShape.Height,Microsoft.Office.Core.MsoTriState.msoCTrue);
            }

setCB定义如下

   public void setCB(Image img)         {             Clipboard.SetImage(img);         }

二是从文件插入:

 rng = wks.get_Range("B30");
 pic = range.Worksheet.Shapes.AddPicture(filePath, Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoCTrue, rng.Left, rng.Top, 300, 300)  

我采用的是第一种,因为我需要截图,如果把截图保存到文件再插入,那就太麻烦了。

到这里,自动填充excel基本功能都实现了。

下面的截图保存是个额外功能,和excel没关系,可以不看。

3,截图保存

3.1屏幕截图的基本原理

原理呢,就是新建一个Form,放到最前面,然后把当前屏幕的图像设为Form的背景图,其实是有点取巧的意见。

3.2截图操作

新建一个空白 Form,注意要将事件绑定为自己写的截图函数

v2-ec298921a21531079ce96a8246072ab1_b.jpg

双击窗体,写下初始化代码设置

private void CutPic_Load(object sender, EventArgs e)
        {
            this.KeyPreview = true;//保持窗体先接收键盘消息,用来绑定ESC退出事件
            this.Image = this.BackgroundImage;//将屏幕图像设置为Form背景
            CatchFinished = false;//用来判断是否完成截图
            CatchStart = false;//用来判断是否开始截图
            isDowned = false;//用来判断是否鼠标按下
        }

如果按下ESC,退出截图

 private void CutPic_KeyDown(object sender,KeyEventArgs e)
        {
            if(e.KeyCode == Keys.Escape)
            {
                this.Close();
            }
        }

如果按下右键,也退出; 如果是左键,则准备画画;

private void CutPic_MouseDown(object sender, MouseEventArgs e)
        {
//左键准备画,设置判断属性
            if(e.Button == MouseButtons.Left)
            {
                if (!CatchStart)
                {
                    CatchStart = true;
                    // 保存此时鼠标按下坐标
                    DownPoint = new Point(e.X, e.Y);
                }
            }
//右键退出
            if (MouseButtons.Right == e.Button)
            {
                this.DialogResult = DialogResult.OK;
                this.Close();
            }


如果移动鼠标,同时左键按下,则画矩形框:

如果没有按下,只是移动鼠标,则根据鼠标位置画十字线(比较笨的实现)

        private void CutPic_MouseMove(object sender, MouseEventArgs e)
        {
//如果按下了,则开始画
            if (CatchStart)
            {
//新建一个图像
                Bitmap copyMap =  new Bitmap(Screen.AllScreens[0].Bounds.Width, Screen.AllScreens[0].Bounds.Height);
//新建起始点
                Point newPoint = new Point(DownPoint.X, DownPoint.Y);
//将图像复制一下
                copyMap = (Bitmap)this.BackgroundImage.Clone();

//新建一个画面
                Graphics g = Graphics.FromImage(copyMap);
//新建画笔
                Pen pen = new Pen(Color.Red, 1);

                int width = Math.Abs(e.X - DownPoint.X);
                int height = Math.Abs(e.Y - DownPoint.Y);
//如果是反向移动鼠标,则把矩形的起点调换下
                if(e.X < DownPoint.X)
                {
                    newPoint.X = e.X;
                }
                if(e.Y < DownPoint.Y)
                {
                    newPoint.Y = e.Y;
                }
//画矩形
                CatchRectangle = new Rectangle(newPoint, new Size(width, height));
                g.DrawRectangle(pen, CatchRectangle);
//抛弃对象,省点内存
                g.Dispose();
                pen.Dispose();
                //画完之后,重新新建画布,再把画面刷新一下,否则,你会看到屏幕上画满了矩形,
                Graphics g1 = this.CreateGraphics();
                g1.DrawImage(copyMap, new Point(0, 0));
                g1.Dispose();
                copyMap.Dispose();
            } 
//如果鼠标没按下,则根据鼠标位置画十字线:
            else
            {
//先刷新一下画面,否则上一次画的十字线会停留在屏幕上,这是个很笨的办法就是了。
                this.Refresh();
        //新建画布        
                Graphics g = this.CreateGraphics();
//新建画笔
                Pen pen = new Pen(Color.Green, 1);
//画线
                //draw vertical line
                g.DrawLine(pen, new Point(e.X, 0), new Point(e.X, this.Height));
                //draw horizontal line
                g.DrawLine(pen, new Point(0, e.Y), new Point(this.Width, e.Y));
//抛弃对象,省点内存
                g.Dispose();
                pen.Dispose();
            }

        }

画图完成,鼠标按起,则把矩形里的图像截图:

        private void CutPic_MouseUp(object sender, MouseEventArgs e)
        {
            if(CatchRectangle.Width == 0 | CatchRectangle.Height == 0)
            {
                CatchStart = false;
                return;
            }
            else if(e.Button == MouseButtons.Left)
            {
                if(CatchStart == true)
                {
                    CatchStart = false;
                    CatchFinished = true;
//将图像复制一下
                    Bitmap tempMap = (Bitmap)this.BackgroundImage.Clone();
                    toDrawMap = new Bitmap(CatchRectangle.Width, CatchRectangle.Height);
//新建画布
                    Graphics g = Graphics.FromImage(toDrawMap);
//画图
                    g.DrawImage(tempMap, new Rectangle(0,0,CatchRectangle.Width,CatchRectangle.Height), CatchRectangle, GraphicsUnit.Pixel);
//将截图保存到剪切板,这里可以直接操作,如果是在非GUI线程,是不能直接操作ClipBoard的,比如刚刚的excelApp
                    Clipboard.SetImage(toDrawMap);
                    sendMapToFrom1(toDrawMap);//事件,通知主程序,我画完了,同时把截图传过去,显示在主程序界面
                    g.Dispose();
                    tempMap.Dispose();
                    tempMap.Dispose();
                    this.Close();//画图完成,退出
                }
            }
        }

对于工具的截图操作,有个不方便的地方,点一下截图,会把当前的程序界面也截图了,那么采用的办法有一个,截图开始前,将主程序最小化并隐藏:

this.WindowState = FormWindowState.Minimized;
this.Hide();
Thread.sleep(100);//为啥要加一个100ms等待,因为这个程序在另外一台win7上跑的时候发现最小化的时间有点长,还是被捕捉到截图里了。只想到这个笨办法来避免。

这里重点提供一下事件绑定,在主界面里新建截图界面的时候,要把这个对象的事件消息绑定一下,如下:

CutPic cutter = new CutPic();
            cutter.WindowState = FormWindowState.Maximized;
            cutter.BackgroundImage = img;
            cutter.BackgroundImageLayout = ImageLayout.Zoom;

            cutter.sendMapToFrom1 += new CutPic.SendMap(sendmaptwo);//绑定事件消息
            cutter.Show();

那么这个事件消息在哪里设置的呢?在截图界面里定义

public partial class CutPic : Form
    {
//设置委托
        public delegate void SendMap(Bitmap bm);
//定义事件
        public event SendMap sendMapToFrom;

        private Point DownPoint;

        protected bool m_bMoving;
        protected bool m_bChangedWidth;
        protected bool m_bChangeHeight;
        protected bool m_bMouseHover;
        private bool _IsDrawed;
        private bool isDowned;
        private Image _Image;
        private bool CatchStart;
        private Rectangle CatchRectangle;
        private bool CatchFinished;
        private Bitmap fullmaskMap;
        private Rectangle maskRectangle;
        private Pen maskPen;
        private SolidBrush maskBrush;

        Bitmap toDrawMap;
}

主界面针对收到的消息,进行处理,如下:

 public void sendmaptwo(Bitmap bm)
        {
            if (bm != null)
            {
//将excelApp中的图像设置为刚刚的截图
                newReport.ImgTwo = bm;
//将截图同步显示在主界面图片框里
                this.pictureBox2.Image = bm;
                this.pictureBox2.SizeMode = PictureBoxSizeMode.Zoom;
            }
//已知截图完成,将主程序最大化并显示
            this.Show();
            this.WindowState = FormWindowState.Normal;
            isDrawing = false;
        }

4,热键绑定

即使有了程序,当然也想用热键来提升效率,怎么办呢。

以下是从网上抄来的办法,具体的程序定义还没弄太明白,将就着用

        //定义了辅助键的名称(将数字转变为字符以便于记忆,也可去除此枚举而直接使用数值)
        [Flags()]
        public enum KeyModifiers
        {
            None = 0,
            Alt = 1,
            Ctrl = 2,
            Shift = 4,
            WindowsKey = 8
        }
//引入user32
        [DllImport("user32.dll")]
        public static extern bool RegisterHotKey(
            IntPtr hWnd,                //要定义热键的窗口的句柄
            int id,                     //定义热键ID(不能与其它ID重复)          
            KeyModifiers fsModifiers,   //标识热键是否在按Alt、Ctrl、Shift、Windows等键时才会生效
            Keys vk                     //定义热键的内容
            );

        [DllImport("user32.dll", SetLastError = true)]
        public static extern bool UnregisterHotKey(
            IntPtr hWnd,                //要取消热键的窗口的句柄
            int id                      //要取消热键的ID
            );

        /// <summary>
        /// 注册热键
        /// </summary>
        /// <param name="hwnd">窗口句柄</param>
        /// <param name="hotKey_id">热键ID</param>
        /// <param name="keyModifiers">组合键</param>
        /// <param name="key">热键</param>
        public static void RegKey(IntPtr hwnd, int hotKey_id, KeyModifiers keyModifiers, Keys key)
        {
            try
            {
                if (!RegisterHotKey(hwnd, hotKey_id, keyModifiers, key))
                {
                    if (Marshal.GetLastWin32Error() == 1409) { MessageBox.Show("热键被占用 !"); }
                    else
                    {
                        MessageBox.Show("注册热键失败!");
                    }
                }
            }
            catch (Exception e) {
                MessageBox.Show("注册热键失败:" + e.ToString());
            }
        }
        /// <summary>
        /// 注销热键
        /// </summary>
        /// <param name="hwnd">窗口句柄</param>
        /// <param name="hotKey_id">热键ID</param>
        public static void UnRegKey(IntPtr hwnd, int hotKey_id)
        {
            //注销Id号为hotKey_id的热键设定
            UnregisterHotKey(hwnd, hotKey_id);
        }

绑定热键

        private bool isDrawing;

        private const int WM_HOTKEY = 0x312; //窗口消息-热键
        private const int WM_CREATE = 0x1; //窗口消息-创建
        private const int WM_DESTROY = 0x2; //窗口消息-销毁
        private const int KeyOneId = 0x34567; //热键ID
        private const int KeyTwoId = 0x34568; //热键ID
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
            switch (m.Msg)
            {
                case WM_HOTKEY: //窗口消息-热键ID
                    switch (m.WParam.ToInt32())
                    {
                        case KeyOneId: //热键ID
                            if(isDrawing == false)
                            {
                                this.WindowState = FormWindowState.Minimized;
                                this.Hide();
                                ShowCutPic();
                            }                            
                            break;
                        case KeyTwoId:
                            if (isDrawing == false)
                            {
                                this.WindowState = FormWindowState.Minimized;
                                this.Hide();
                                ShowCutPicTwo();
                            }
                            break;
                        default:
                            break;
                    }
                    break;

                    //    D1  35                The 1(one) key.               
                    //    D2  36                        The 2 key.                
                    //      D3  37                The 3 key.    
                case WM_CREATE: //窗口消息-创建
                    RegKey(this.Handle, KeyOneId, KeyModifiers.Ctrl, Keys.D1);//Ctrl + 1 
                    RegKey(this.Handle, KeyTwoId, KeyModifiers.Ctrl, Keys.D2);//Ctrl + 2
                    break;
                case WM_DESTROY: //窗口消息-销毁
                    UnRegKey(this.Handle, KeyOneId); //销毁热键
                    UnRegKey(this.Handle, KeyTwoId);
                    break;
                default:
                    break;
            }
        }

完结

后续要做的,是打算开发小爬虫读取网页上的客户信息,再传到这个APP里自动保存。不过C#里怎么实现,还要研究下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值