查看完整版本: 分享一些程式寫作的小技巧
頁: [1]

rr09192084 發表於 2015-9-11 10:56 AM

分享一些程式寫作的小技巧

這是一個將 DataGridView 相關的一些常用到的功能,作一些小小分享,其中就包括轉成EXCEL檔案、列印等。
其中也有一些平常設計程式會用到的小技巧,諸如資料模組的設計,擴充法、資料型別的轉換等等,給大家參考。
這是Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Excel = Microsoft.Office.Interop.Excel;

namespace ListToDataGridView
{
    public partial class Form1 : Form
    {
        //通常定義欄位都是以private為主
        private List<PersonModel> person;
        private string toExcelString = "";
        private Timer timer;
        //定義常數
        private const int WM_CLOSE = 0x10;
        public Form1()
        {
            InitializeComponent();
        }
        //API
        
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
        
        public static extern bool PostMessage(IntPtr hWnd, uint Msg, UIntPtr wParam, IntPtr lParam);

        private void Form1_Load(object sender, EventArgs e)
        {
            //建構時一次加入3筆資料
            person = new List<PersonModel>()
            {
                new PersonModel
                {
                    Name = "王大維",
                    ID = "B122111111",
                    Birthday = "19680705",
                    TEL = "07-23593512",
                    CellPhone = "0982-333-333",
                    Address = "高雄市苓雅區中華西路二段451號" ,
                    EMail = "daviwang@msa.hinet.net"
                },
                new PersonModel
                {
                    Name = "姜中華",
                    ID = "C122221221",
                    Birthday = "19771025",
                    TEL = "04-23257066",
                    CellPhone = "0952-666-666",
                    Address = "台中市北屯區中西三路1026號",
                    EMail = "hwa661025888@yahoo.com.tw"
                },
                new PersonModel
                {
                    Name = "張小明",
                    ID = "A122331331",
                    Birthday = "19780630",
                    TEL = "02-25805632",
                    CellPhone = "0988-999-999",
                    Address = "台北市大安區復興南路一段203號10樓之16",
                    EMail = "ming53621107@gmail.com"
                }
            };

            //或是後來再加入1筆資料
            person.Add(new PersonModel
                {
                    Name = "李維勳",
                    ID = "T122551551",
                    Birthday = "19750630",
                    TEL = "02-23231122",
                    CellPhone = "0938-777-777",
                    Address = "台北市松山區信義路二段300號11樓之1",
                    EMail = "waishin@dodogogo.com"
                });
            
            //實際應用時也許會像這樣
            /*
            person.Add(new PersonModel
            {
                Name = textBox1.Text,
                ID = textBox2.Text,
                Birthday = textBox3.Text,
                TEL = textBox4.Text,
                CellPhone = textBox5.Text,
                Address = textBox6.Text,
                EMail = textBox7.Text
            });  */
            //=============================================================
            //這段程式碼是將 List 放到 dataGridView1 裡
            //這裡我在 ExtensionUtility.cs 這個類別裡寫的一個擴充方法
            dataGridView1.DataSource = person.ToBindingSource<PersonModel>();
            //=============================================================

            //=============================================================
            //設定欄位寬度,這段也可以放到dataGridView1_DataBindingComplete裡面
            for (int i = 0; i < dataGridView1.ColumnCount; i++)
            {
                dataGridView1.Columns.AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
            }
            //=============================================================
        }

        //匯出EXCEL檔案功能按鈕
        private void toExcelButton_Click(object sender, EventArgs e)
        {
            if (toExcelString == "") return;
            //為了避免相互干擾,會強制關閉執行中EXCEL
            initailExcel();
            //所以要確認其它開啟的EXCEL檔案已經存檔並關閉。
            Excel.Application xlApplication = new Excel.Application();
            Excel.Workbook workBook = null;
            Excel.Worksheet first_sheet = null;
            Excel.Range tmprng = null;
            string xlfilename = "";
            FolderBrowserDialog fd = new FolderBrowserDialog();
            if (fd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                if (fd.SelectedPath.Substring(fd.SelectedPath.Length-1, 1) == @"\")
                    //如果是選根目錄
                    xlfilename = fd.SelectedPath + @"(" + convertDate(DateTime.Now) + ")-個人資料(明細表).xlsx";
                else
                    //選子目錄
                    xlfilename = fd.SelectedPath + @"\(" + convertDate(DateTime.Now) + ")-個人資料(明細表).xlsx";
            }
            else
            {
                return;
            }
            try
            {
                //EXCEL檔案如果已經存在就直接刪除
                if (File.Exists(xlfilename)) { File.Delete(xlfilename); }
                //建立新的EXCEL檔案
                workBook = xlApplication.Workbooks.Add();
                first_sheet = workBook.Sheets as Excel.Worksheet;
                //將文字複製到剪貼簿
                Clipboard.SetText(toExcelString, TextDataFormat.UnicodeText);
                //將C到E欄和G欄設定為文字格式並靠右對齊
                tmprng = first_sheet.Range["C:E"];
                tmprng.NumberFormat = "@";
                tmprng.HorizontalAlignment = Microsoft.Office.Interop.Excel.XlHAlign.xlHAlignRight;
                //在A1貼上剪貼簿上的內容
                first_sheet.Paste(first_sheet.Range["A1", Type.Missing], false);
                first_sheet.Name = "服務事項明細表";
                //設定自動欄寬
                first_sheet.Cells.EntireColumn.AutoFit();
                //設定自動篩選
                tmprng = first_sheet.get_Range("A1", "A1");
                tmprng.AutoFilter(1, Type.Missing, Excel.XlAutoFilterOperator.xlAnd, Type.Missing, true);
                //不詢問直接覆蓋原有檔案
                xlApplication.DisplayAlerts = false;
                //存檔為 EXCEL 2007 格式
                workBook.SaveAs(xlfilename, Excel.XlFileFormat.xlWorkbookDefault);
            }
            catch (Exception ex)
            {
                //這一段是秀出錯誤訊息並且5秒鐘後自動關閉
                StartTimerKillMessageBox(5);
                MessageBox.Show(ex.Message + ex.ToString(), "MessageBox");
            }
            finally
            {
                //釋放掉一些資源的程式碼
                NAR(tmprng);
                NAR(first_sheet);
                NAR(workBook);
                xlApplication.Quit();
                NAR(xlApplication);
                GC.Collect();
                if (File.Exists(xlfilename)) MessageBox.Show("匯出成功!!");

            }
        }

        //列印功能按鈕
        private void toPrinter_Click(object sender, EventArgs e)
        {
            if (toExcelString == "") return;
            string tmpstr = "";
            string[] ss = System.Text.RegularExpressions.Regex.Split(toExcelString, "\r\n");
            foreach (string s in ss)
            {   //以下這段程式是將文字稍作排版
                if (s.Length < 5) continue;
                string[] sv = System.Text.RegularExpressions.Regex.Split(s, "\t");
                if (sv.Length == 7)
                {
                    tmpstr += sv + new String(' ', 12 - ChtStr_Lenght(sv));
                    tmpstr += sv + new String(' ', 11 - ChtStr_Lenght(sv));
                    tmpstr += sv + new String(' ', 11 - ChtStr_Lenght(sv));
                    tmpstr += sv + new String(' ', 16 - ChtStr_Lenght(sv));
                    tmpstr += sv + new String(' ', 16 - ChtStr_Lenght(sv));
                    tmpstr += sv + new String(' ', 40 - ChtStr_Lenght(sv));
                    tmpstr += sv + new String(' ', 28 - ChtStr_Lenght(sv)) + "\r\n";
                }
            }
            PrintDocument p = new PrintDocument();
            //把版面設定成橫印
            p.DefaultPageSettings.Landscape = true;
            p.PrintPage += delegate(object sender1, PrintPageEventArgs e1)
            {   //這個列印程式只能印出第一頁,如果要印出多頁,這段程式要做修改
                e1.Graphics.DrawString(tmpstr, new Font("標楷體", 11),
                                       new SolidBrush(Color.Black),
                                       new RectangleF(25, 50, p.DefaultPageSettings.PrintableArea.Height,
                                       p.DefaultPageSettings.PrintableArea.Width));

            };
            try
            {
                p.Print();
            }
            catch (Exception ex)
            {
                throw new Exception("列印出錯:", ex);
            }
        }

        private void dataGridView1_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
        {
            //每次DataBinding完成時都將person這個List轉成PersonExcelModel這個模組型態(LINQ語法)
            //並且輸出成以TAB相隔的字串,方便貼到EXCEL工作表上
            //其實這一整段基本上是可以放在Button_Click裡面的
            toExcelString = "姓名\t證號\t生日\t電話\t手機\t地址\t電郵\r\n";

            //這個是LINQ語法,我個人覺得是很好用東東
            //=============================================================
            var personexcel = from p in person
                            group p by new {
                                p.Name,
                                p.ID,
                                p.Birthday,
                                p.TEL,
                                p.CellPhone,
                                p.Address,
                                p.EMail
                            } into rs
                            select new PersonExcelModel()
                            {
                                姓名 = rs.Key.Name,
                                證號 = rs.Key.ID,
                                生日 = rs.Key.Birthday,
                                電話 = rs.Key.TEL,
                                手機 = rs.Key.CellPhone,
                                地址 = rs.Key.Address,
                                電郵 = rs.Key.EMail
                            };
            //=============================================================

            //所以personexcel現在已經是PersonExcelModel這種形態了
            foreach (var item in personexcel)
            {
                toExcelString += item.ToString(); //這裡應用的就是之前複寫的ToString()方法
            }
        }

        //啟動關閉訊息框
        public void StartTimerKillMessageBox(int sec, string msg = "MessageBox")
        {
            timer = new Timer();
            timer.Interval = sec * 1000; //等sec秒
            timer.Tag = msg;
            timer.Tick += new EventHandler(Timer_CloseMessageBox);
            timer.Start();
        }

        public void Timer_CloseMessageBox(object sender, EventArgs e)
        {
            KillMessageBox(Convert.ToString(((Timer)sender).Tag));
            //停止Timer
            ((Timer)sender).Stop();
        }

        public void KillMessageBox(string msg)
        {
            //依MessageBox的標題,找出MessageBox的視窗
            IntPtr ptr = FindWindow(null, msg);
            if (ptr != IntPtr.Zero)
            {
                //找到則關閉MessageBox視窗
                PostMessage(ptr, WM_CLOSE, UIntPtr.Zero, IntPtr.Zero);
            }
        }

        //傳回自訂格式的日期
        public string convertDate(DateTime dt)
        {
            return dt.Year.ToString("0000") + dt.Month.ToString("00") + dt.Day.ToString("00");
        }

        //傳回有中文的字串的真實長度
        public int ChtStr_Lenght(string a_SrcStr)
        {
            byte[] l_byte = System.Text.Encoding.Default.GetBytes(a_SrcStr);
            return l_byte.Length;
        }

        //釋放占用的資源
        public void NAR(object sender)
        {
            try
            {
                if (sender != null)
                {
                    while (Marshal.ReleaseComObject(sender) > 0) ;
                }
            }
            finally
            { sender = null; }
        }
        public void initailExcel()
        {
            //檢查PC有無Excel在執行,有的話強制關閉。
            foreach (var item in System.Diagnostics.Process.GetProcesses())
            {
                if (item.ProcessName.ToUpper() == "EXCEL")
                {
                    item.Kill();
                    item.WaitForExit();
                }
            }
        }
    }
}
這是DataModel.cs,用來設計資料的模組
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ListToDataGridView
{
    class PersonModel : IEquatable<PersonModel>
    {
        public string Name { get; set; }
        public string ID { get; set; }
        public string Birthday { get; set; }
        public string TEL { get; set; }
        public string CellPhone { get; set; }
        public string Address { get; set; }
        public string EMail { get; set; }

        //IEquatable這個介面需要實作Equals這個方法
        public bool Equals(PersonModel other)
        {
            return this.ID.Equals(other.ID);
        }
    }

    class PersonExcelModel : IEquatable<PersonExcelModel>
    {
        public string 姓名 { get; set; }
        public string 證號 { get; set; }
        public string 生日 { get; set; }
        public string 電話 { get; set; }
        public string 手機 { get; set; }
        public string 地址 { get; set; }
        public string 電郵 { get; set; }

        //IEquatable這個介面需要實作Equals這個方法
        public bool Equals(PersonExcelModel other)
        {   
            return this.證號.Equals(other.證號);
        }

        //再寫一個多載方法可以和PersonModel做比對
        public bool Equals(PersonModel other)
        {
            return this.證號.Equals(other.ID);
        }

        //這裡我們複寫ToString()這個方法,以便讓它可以符合輸出到EXCEL的需求
        public override string ToString()
        {
            return this.姓名 + "\t" + this.證號 + "\t" + this.生日
                        + "\t" + this.電話 + "\t" + this.手機
                        + "\t" + this.地址 + "\t" + this.電郵 + "\r\n";
        }
    }
}
這是ExtensionUtility.cs,裡面裝了一個把List轉成BindingSource的擴充方法
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ListToDataGridView
{
    public static class ExtensionUtility
    {
        //擴充方法,有興趣自己找資料研究,可以先看看MSDN的說明
        //https://msdn.microsoft.com/zh-tw/library/bb383977.aspx
        public static BindingSource ToBindingSource<TSource>(this IList<TSource> data)
        {
            var bindingList = new BindingList<TSource>(data);
            return new BindingSource(bindingList, null);

        }
    }
}
請大家不吝指教...<div class='locked'><em>瀏覽完整內容,請先 <a href='member.php?mod=register'>註冊</a> 或 <a href='javascript:;' onclick="lsSubmit()">登入會員</a></em></div><div></div>

rr09192084 發表於 2015-9-13 01:59 PM

列印與匯出成EXCEL檔案部分有做一些修正與加強,列印部分改成可以列出多頁。匯出EXCEL部分增加了會出後直接開啟EXCEL檔案的功能。
要改的地方蠻多的
1.在欄位的地方增加
private PrintDocument pd = new PrintDocument();
private string stringToPrint = "";
2.在Form_Load裏加上
pd.PrintPage += new PrintPageEventHandler(printDocument_PrintPage);
3.把 或是後來再加入1筆資料 這一段改成下面這一段程式,直接增加70筆資料,列印時會分成2頁。
            //或是後來再加入70筆資料
            for (int i = 0; i < 70; i++)
            {
                int idnum = 122551551;
                person.Add(new PersonModel
                {
                    Name = "李維勳",
                    ID = "T" + (idnum + i).ToString(),  //ID不同才會被視為不同一筆資料
                    Birthday = "19750630",
                    TEL = "02-23231122",
                    CellPhone = "0938-777-777",
                    Address = "台北市松山區信義路二段300號11樓之1",
                    EMail = "waishin64@extenalmail.dodogogo.com.tw"
                });
            }
4.在toExcelButton_Click裏面finally這一段改成
                //釋放掉一些資源的程式碼
                NAR(tmprng);
                NAR(first_sheet);
                NAR(workBook);
                xlApplication.Quit();
                NAR(xlApplication);
                GC.Collect();
                if (File.Exists(xlfilename))
                {
                    StartTimerKillMessageBox(3);
                    MessageBox.Show("匯出成功!!3秒後將會開啟檔案...", "MessageBox");
                    System.Diagnostics.Process ps = new System.Diagnostics.Process();
                    //開啟EXCEL檔案
                    ps.StartInfo = new System.Diagnostics.ProcessStartInfo(xlfilename);
                    //或是開啟EXCEL檔案所在的目錄
                    //ps.StartInfo = new System.Diagnostics.ProcessStartInfo(fd.SelectedPath);
                    ps.Start();
                }
5.把toPrinter_Click裡面所有程式碼改成
            if (toExcelString == "") return;
            string tmpstr = "";
            string[] ss = System.Text.RegularExpressions.Regex.Split(toExcelString, "\r\n");
            foreach (string s in ss)
            {   //以下這段程式是將文字稍作排版
                if (s.Length < 5) continue;
                string[] sv = System.Text.RegularExpressions.Regex.Split(s, "\t");
                if (sv.Length == 7)
                {   
                    //姓名是中文資料,要另外處理,所以又加了另一個擴充方法ToChtSubstring
                    tmpstr += (ChtStr_Length(sv) <= 12) ? sv + new String(' ', 12 - ChtStr_Length(sv)) : sv.ToChtSubstring(0, 12);
                    tmpstr += (sv.Length <= 11) ? sv + new String(' ', 11 - ChtStr_Length(sv)) : sv.Substring(0, 11);
                    tmpstr += (sv.Length <= 11) ? sv + new String(' ', 11 - ChtStr_Length(sv)) : sv.Substring(0, 11);
                    tmpstr += (sv.Length <= 16) ? sv + new String(' ', 16 - ChtStr_Length(sv)) : sv.Substring(0, 16);
                    tmpstr += (sv.Length <= 16) ? sv + new String(' ', 16 - ChtStr_Length(sv)) : sv.Substring(0, 16);
                    //地址也是中文資料,同樣另外處理
                    tmpstr += (ChtStr_Length(sv) <= 42) ? sv + new String(' ', 42 - ChtStr_Length(sv)) : sv.ToChtSubstring(0, 42);
                    tmpstr += (sv.Length <= 40) ? sv + new String(' ', 40 - ChtStr_Length(sv)) : sv.Substring(0, 40);
                    tmpstr += "\r\n";
                }
            }
            stringToPrint = tmpstr;
            try
            {
                //設定成橫印
                pd.DefaultPageSettings.Landscape = true;
                //列印輸出
                //叫出列印對話框
                PrintDialog pdialog = new PrintDialog();
                pdialog.Document = pd;
                if (pdialog.ShowDialog() == DialogResult.OK)
                {
                    pd.Print();
                }
            }
            catch (Exception ex)
            {
                throw new Exception("列印出錯:", ex);
            }
6.增加一個PrintDocument處理PrintPage事件的函式
        private void printDocument_PrintPage(object sender, PrintPageEventArgs e)
        {
            //每頁字數
            int charactersOnPage = 0;
            //每頁的列數
            int linesPerPage = 0;
            //目前預設印表機可印範圍的高度
            float h = pd.DefaultPageSettings.PrintableArea.Height - 40;
            //目前預設印表機可印範圍的寬度(這裡將兩個參數減掉一些數據,是因為通常都不會滿版列印)
            float w = pd.DefaultPageSettings.PrintableArea.Width - 80;
            //如果是設定為橫印時,要將寬與高對調
            if (pd.DefaultPageSettings.Landscape)
            {
                float tmp = h;
                h = w;
                w = tmp;
            }
            //計算出每頁字數,每頁列數
            e.Graphics.MeasureString(stringToPrint,
                new Font("標楷體", 11),
                new SizeF(w, h),
                StringFormat.GenericTypographic,
                out charactersOnPage,  //輸出給每頁字數的變數
                out linesPerPage);     //輸出給每頁列數的變數
            //畫出列印頁面
            e.Graphics.DrawString(stringToPrint,
                new Font("標楷體", 11),
                Brushes.Black,
                new RectangleF(20, 50, w, h), //這是跟邊界有關的4個參數
                StringFormat.GenericTypographic);

            //取出每一頁字數的剩餘部分
            stringToPrint = stringToPrint.Substring(charactersOnPage);
            //一直到沒有頁面可以列印為止
            e.HasMorePages = (stringToPrint.Length > 0);
        }
7.在ExtensionUtility裏增加一個ToChtSubstring()的擴充方法
        public static String ToChtSubstring(this String s, int starindex, int lenght)
        {
            byte[] linestr = System.Text.Encoding.Default.GetBytes(s);
            return System.Text.Encoding.Default.GetString(linestr, starindex, lenght);
        }
嫌麻煩的話可以直接下載整個專案檔案來參考看看喔
http://1drv.ms/1NwKu9w...<div class='locked'><em>瀏覽完整內容,請先 <a href='member.php?mod=register'>註冊</a> 或 <a href='javascript:;' onclick="lsSubmit()">登入會員</a></em></div>

smallanan 發表於 2015-9-24 12:04 AM

個人會比較建議如果依照datagridview一模一樣的方式匯出excel檔案的話

使用odbc效率會比較好(而且不用安裝office)

如果有特殊需求(如:要匯出exce的報表)那這種才使用這種方式寫入excel

rr09192084 發表於 2015-9-24 12:42 AM

smallanan 發表於 2015-9-24 12:04 AM static/image/common/back.gif
個人會比較建議如果依照datagridview一模一樣的方式匯出excel檔案的話

使用odbc效率會比較好(而且不用安 ...

哈哈,我說怎麼都沒人回應呢?感謝您的指教!!
我本來後續要貼一些EXCEL排版相關的功能,還有套表列印信封、賀卡等等...
跟資料庫的連接、利用LINQ做一些樞紐分析等等...
看到沒人回應,想就樣做罷了說
...<div class='locked'><em>瀏覽完整內容,請先 <a href='member.php?mod=register'>註冊</a> 或 <a href='javascript:;' onclick="lsSubmit()">登入會員</a></em></div>

孤單小呆呆 發表於 2015-10-1 01:22 PM

最近剛好從C++跳過來
很多都還看不懂

感謝大大的分享
讓我有學習的地方<br><br><br><br><br><div></div>

superwaterdog 發表於 2015-11-19 09:00 PM

還不錯的介紹

程式的基礎架構都有
當然純看CODE 會有一些基礎會比較好
如有用到INTERFACE 等技巧

Enjoyeyny51 發表於 2015-12-25 12:29 AM

這範例解說真不錯
剛好最近有需要用到類似的功能
範例先收下當參考了

謝謝大大分享^^

superwaterdog 發表於 2016-2-24 10:35 PM

不錯的範例
謝謝分享
不過要有一點工夫才看得懂

licious0421 發表於 2016-2-26 04:13 PM

很棒呀!!!
我也算是C#新手八= =
剛好目前有遇到~DATAGREADVIEW TO EXCEL~
完全就是我的需要阿!!

c15722145 發表於 2016-3-4 08:21 AM

不建議這樣操作Excel
樓上ODBC方式較佳
不然也可找open source有可以直接生成excel檔案的方法<br><br><br><br><br><div></div>

bigpig200126 發表於 2016-3-13 09:08 PM

小弟也是新手,最近在研究Kendo UI ,發現他蠻好用的,可以直接幫你把Girid的資料產出成Excel,完全不用寫程式,你可以試試看唷

min721107 發表於 2020-4-15 02:18 PM

謝謝大大分享,收下使用

jasonme168 發表於 2020-8-27 10:55 PM

神人!
感謝您的分享,造福後學。

SIHEOT 發表於 2021-1-2 03:59 AM

太神啦
感謝大大的分享
頁: [1]