找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開(kāi)始

搜索
查看: 4646|回復(fù): 1
收起左側(cè)

C#導(dǎo)出數(shù)據(jù)到exceil表格中的方法和源代碼(附超級(jí)無(wú)敵詳細(xì)講解)

[復(fù)制鏈接]
ID:208071 發(fā)表于 2017-6-5 16:25 | 顯示全部樓層 |閱讀模式
如果你耐心仔細(xì)看完本文,相信以后再遇到導(dǎo)出EXCLE操作的時(shí)候你會(huì)很順手覺(jué)得SO EASY,主要給新手朋友們看的,老鳥(niǎo)可以直接飄過(guò)了,花了一晚上的時(shí)間寫(xiě)的很辛苦,如果覺(jué)得對(duì)你有幫助煩請(qǐng)留言支持一下,我會(huì)寫(xiě)更多基礎(chǔ)的原創(chuàng)內(nèi)容來(lái)回報(bào)大家。

C#導(dǎo)出數(shù)據(jù)到EXCEL表格是個(gè)老生常談的問(wèn)題了,寫(xiě)這篇文章主要是給和我一樣的新手朋友提供兩種導(dǎo)出EXCEL的方法并探討一下導(dǎo)出的效率問(wèn)題,本文中的代碼直接就可用,其中部分代碼參考其他的代碼并做了修改,拋磚引玉,希望大家一起探討,如有不對(duì)的地方還請(qǐng)大家多多包涵并指出來(lái),我也是個(gè)新手,出錯(cuò)也是難免的。

首先先總結(jié)下自己知道的導(dǎo)出EXCEL表格的方法,大致有以下幾種,有疏漏的請(qǐng)大家補(bǔ)充。
1.數(shù)據(jù)逐條逐條的寫(xiě)入EXCEL
2.通過(guò)OLEDB把EXCEL做為數(shù)據(jù)源來(lái)寫(xiě)
3.通過(guò)RANGE范圍寫(xiě)入多行多列內(nèi)存數(shù)據(jù)到EXCEL
4.利用系統(tǒng)剪貼板寫(xiě)入EXCEL

好了,我想這些方法已經(jīng)足夠完成我們要實(shí)現(xiàn)的功能了,方法不在多,在精,不是么?以上4中方法都可以實(shí)現(xiàn)導(dǎo)出EXCEL,方法1為最基礎(chǔ)的方法,意思就是效率可能不是太高,當(dāng)遇到數(shù)據(jù)量過(guò)大時(shí)所要付出的時(shí)間也是巨大的,后面3種方法都是第一種的衍生,在第一種方法效率低下的基礎(chǔ)上改進(jìn)的,這里主要就是一個(gè)效率問(wèn)題了,當(dāng)然如果你數(shù)據(jù)量都很小,我想4種方法就代碼量和復(fù)雜程度來(lái)說(shuō)第1種基本方法就可以了,或當(dāng)你的硬件非常牛逼了,那再差的方法也可以高效的完成也沒(méi)有探討的實(shí)際意義了,呵呵說(shuō)遠(yuǎn)了,本文主要是在不考慮硬件或同等硬件條件下單從軟件角度出發(fā)探討較好的解決方案。
此項(xiàng)目代碼已打包在附件中,幾乎注釋的無(wú)微不至,在配合本文中我唐僧般的娓娓道來(lái)(靠,誰(shuí)丟臭雞蛋砸俺了),相信即使剛?cè)腴T(mén)C#的朋友也能看得懂了吧,大家可以自行下載,代碼中演示了兩種方法,上述的方法1和方法3,我想足夠了,方法3的效率應(yīng)該是四種中最高的了,其他兩種有興趣的朋友自己實(shí)現(xiàn)下哈(方法2在我的程序中也有用到一些,看完你就知道了),程序如下圖所示,先加載一個(gè)EXCEL表格進(jìn)DATAGRIDVIEW作為數(shù)據(jù)源,然后兩種方法導(dǎo)出EXCEL,配上一個(gè)計(jì)數(shù)功能給大家直觀的看到導(dǎo)出的耗時(shí),大家可以準(zhǔn)備個(gè)幾萬(wàn)行和十多行的兩個(gè)EXCEL作為數(shù)據(jù)源來(lái)測(cè)試,也可以用我打包里的XLS來(lái)測(cè)試下。
0.png

首先使用前都需要加載COM組件Microsoft.Office.Interop.Excel.dll(已打包)

關(guān)鍵代碼如下:
方法1,最基本方法,用FOR循環(huán)逐條寫(xiě)入EXCELCELL中,其他的看代碼就行了,關(guān)鍵代碼如下
public void ToExcel1(DataGridViewgridView, SaveFileDialog saveFileDialog)
{
...........................
for (int i = 0; i < gridView.RowCount; i++)
{
for (int j = 0; j < gridView.ColumnCount; j++)
{
if (gridView[j,i].Value == typeof(string))
{
excel.Cells[i +2, j + 1] = "" + gridView[i,j].Value.ToString();
}
else
{
excel.Cells[i +2, j + 1] = gridView[j, i].Value.ToString();
}
}
//進(jìn)度條加1
progressBar1.Value++;
/*
* 注意此Application.DoEvents(),如果無(wú)此句,當(dāng)切換窗口后回到本程序無(wú)法重繪窗體會(huì)出現(xiàn)假死狀態(tài)
* 此處我試過(guò)用委托和線程異步調(diào)用的方法,但效果沒(méi)有這句效果好
*/                            System.Windows.Forms.Application.DoEvents();
...................
}

方法2,快速保存內(nèi)存中大量數(shù)據(jù)到ExcelWorkSheet。關(guān)鍵之處是使用Range一次存儲(chǔ)多行多列數(shù)據(jù)。
public void ToExcel2(DataGridViewgridView, SaveFileDialog saveFileDialog)
{
....................
System.Reflection.Missing miss = System.Reflection.Missing.Value;
//創(chuàng)建EXCEL對(duì)象appExcel,Workbook對(duì)象,Worksheet對(duì)象,Range對(duì)象
Microsoft.Office.Interop.Excel.Application appExcel;
appExcel = new Microsoft.Office.Interop.Excel.Application();                    
Microsoft.Office.Interop.Excel.Workbook workbookData;                    
Microsoft.Office.Interop.Excel.Worksheet worksheetData;                  
Microsoft.Office.Interop.Excel.Range rangedata;
//設(shè)置對(duì)象不可見(jiàn)
appExcel.Visible= false;
/* 在調(diào)用Excel應(yīng)用程序,或創(chuàng)建Excel工作簿之前,記著加上下面的兩行代碼
* 這是因?yàn)?/font>Excel有一個(gè)Bug,如果你的操作系統(tǒng)的環(huán)境不是英文的,而Excel就會(huì)在執(zhí)行下面的代碼時(shí),報(bào)異常。
*/
System.Globalization.CultureInfo CurrentCI = System.Threading.Thread.CurrentThread.CurrentCulture;                   System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
workbookData =appExcel.Workbooks.Add(miss);
worksheetData=(Microsoft.Office.Interop.Excel.Worksheet)workbookData.Worksheets.Add(miss,miss, miss, miss);
//給工作表賦名稱(chēng)
worksheetData.Name= "saved";
//清零計(jì)數(shù)并開(kāi)始計(jì)數(shù)
TimeP = new System.DateTime(0);
timer1.Start();
label1.Text =TimeP.ToString("HH:mm:ss");
// 保存到WorkSheet的表頭,你應(yīng)該看到,是一個(gè)Cell一個(gè)Cell的存儲(chǔ),這樣效率特別低,解決的辦法是,使用Rang,一塊一塊地存儲(chǔ)到Excel
for (int i = 0; i < gridView.ColumnCount; i++)
{
worksheetData.Cells[1,i + 1] = gridView.Columns[ i].HeaderText.ToString();
}
//先給Range對(duì)象一個(gè)范圍為A2開(kāi)始,Range對(duì)象可以給一個(gè)CELL的范圍,也可以給例如A1H10這樣的范圍
//因?yàn)榈谝恍幸呀?jīng)寫(xiě)了表頭,所以所有數(shù)據(jù)都應(yīng)該從A2開(kāi)始
rangedata =worksheetData.get_Range("A2",miss);
Microsoft.Office.Interop.Excel.Range xlRang = null;
//iRowCount為實(shí)際行數(shù),最大行
int iRowCount =gridView.RowCount;
int iParstedRow =0, iCurrSize = 0;
//iEachSize為每次寫(xiě)行的數(shù)值,可以自己設(shè)置,每次寫(xiě)1000行和每次寫(xiě)2000行大家可以自己測(cè)試下效率
int iEachSize =1000;
//iColumnAccount為實(shí)際列數(shù),最大列數(shù)
int iColumnAccount= gridView.ColumnCount;
//在內(nèi)存中聲明一個(gè)iEachSize×iColumnAccount的數(shù)組,iEachSize是每次最大存儲(chǔ)的行數(shù),iColumnAccount就是存儲(chǔ)的實(shí)際列數(shù)
object[,] objVal = new object[iEachSize,iColumnAccount];
try
{
//給進(jìn)度條賦最大值為實(shí)際行數(shù)最大值
progressBar1.Maximum= gridView.RowCount;
iCurrSize =iEachSize;

while (iParstedRow< iRowCount)
{
if ((iRowCount -iParstedRow) < iEachSize)
iCurrSize =iRowCount - iParstedRow;
//FOR循環(huán)給數(shù)組賦值
for (int i = 0; i < iCurrSize; i++)
{
for (int j = 0; j < iColumnAccount; j++)
objVal[i, j] =gridView[j, i + iParstedRow].Value.ToString();
progressBar1.Value++;                              System.Windows.Forms.Application.DoEvents();
}
/*
* 建議使用設(shè)置斷點(diǎn)研究下哈
* 例如A1H10的意思是從AH,第一行到第十行
* 下句很關(guān)鍵,要保證獲取Sheet中對(duì)應(yīng)的Range范圍
* 下句實(shí)際上是得到這樣的一個(gè)代碼語(yǔ)句xlRang= worksheetData.get_Range("A2","H100");
* 注意看實(shí)現(xiàn)的過(guò)程
*'A' + iColumnAccount - 1這兒是獲取你的最后列,A的數(shù)字碼為65,大家可以仔細(xì)看下是不是得到最后列的字母
*iParstedRow + iCurrSize + 1獲取最后行
* WHILE第一次循環(huán)的話這應(yīng)該是A2,最后列字母+最后行數(shù)字
*iParstedRow + 2要注意,每次循環(huán)這個(gè)值不一樣,他取決于你每次循環(huán)RANGE取了多大,循環(huán)了幾次,也就是iEachSize設(shè)置值的大小哦
*/
xlRang =worksheetData.get_Range("A" + ((int)(iParstedRow + 2)).ToString(), ((char)('A' +iColumnAccount - 1)).ToString() + ((int)(iParstedRow+ iCurrSize + 1)).ToString());
// 調(diào)用RangeValue2屬性,把內(nèi)存中的值賦給Excel
xlRang.Value2 =objVal;
iParstedRow =iParstedRow + iCurrSize;
}
//保存工作表
worksheetData.SaveAs(strName,miss, miss, miss, miss, miss, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlNoChange, miss, miss,miss);                                              System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRang);
xlRang = null;
progressBar1.Value= 0;
//調(diào)用方法關(guān)閉EXCEL進(jìn)程,大家可以試下不用的話如果程序不關(guān)閉在進(jìn)程里一直會(huì)有EXCEL.EXE這個(gè)進(jìn)程并鎖定你的EXCEL表格
this.KillSpecialExcel(appExcel);
timer1.Stop();
MessageBox.Show("數(shù)據(jù)已經(jīng)成功導(dǎo)出到:" +saveFileDialog.FileName.ToString(), "導(dǎo)出完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
timer1.Stop();
return;
}
// 別忘了在結(jié)束程序之前恢復(fù)你的環(huán)境!                   System.Threading.Thread.CurrentThread.CurrentCulture = CurrentCI;

最后再順便說(shuō)下本程序中的一些部分:
1.做過(guò)EXCEL導(dǎo)出的朋友應(yīng)該遇到過(guò)一個(gè)情況,當(dāng)導(dǎo)出完畢后進(jìn)程中那個(gè)討厭的EXCEL.EXE老是無(wú)法關(guān)閉,造成鎖定導(dǎo)出的XLS文件,要關(guān)閉程序后該進(jìn)程才退出,網(wǎng)上也有一種方法就是我方法ToExcel1()用到的那個(gè)方法(詳見(jiàn)下載的代碼),但我寫(xiě)ToExcel2()方法的時(shí)候發(fā)現(xiàn)這種關(guān)閉的方法突然不好使了,這我用到了另一種方法KillSpecialExcel,調(diào)用的時(shí)候這樣調(diào)用this.KillSpecialExcel(appExcel)大家可以試下把ToExcel2()改為ToExcel1()的關(guān)閉EXCEL方法看是否有效,代碼如下

#region 結(jié)束EXCEL.EXE進(jìn)程的方法
/// <summary>
/// 結(jié)束EXCEL.EXE進(jìn)程的方法
/// </summary>
/// <paramname="m_objExcel">EXCEL對(duì)象</param>
[DllImport("user32.dll",SetLastError = true)]
static extern intGetWindowThreadProcessId(IntPtr hWnd, out intlpdwProcessId);

public void KillSpecialExcel(Microsoft.Office.Interop.Excel.Application m_objExcel)
{
try
{
if (m_objExcel !=null)
{
int lpdwProcessId;
GetWindowThreadProcessId(new IntPtr(m_objExcel.Hwnd),out lpdwProcessId);                   System.Diagnostics.Process.GetProcessById(lpdwProcessId).Kill();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
2.代碼里寫(xiě)了個(gè)加載EXCELDATAGRIDVIEW的方法,就是想本來(lái)就是在寫(xiě)EXCEL操作相關(guān)嘛,干脆數(shù)據(jù)源也用EXCEL,該方法就是把EXCEL作為OLEDB數(shù)據(jù)源這樣來(lái)操作的,你是不是想到前面我所提到的四種導(dǎo)出EXCEL中的《方法2通過(guò)OLEDBEXCEL做為數(shù)據(jù)源來(lái)寫(xiě)》呢,對(duì)了,有興趣你可以嘗試修改下通過(guò)OLEDB這樣來(lái)導(dǎo)出EXCEL,當(dāng)然,新手朋友可以看下如何加載EXCEL數(shù)據(jù),你應(yīng)該希望去嘗試接著如何把這些數(shù)據(jù)儲(chǔ)存到數(shù)據(jù)庫(kù)之類(lèi)的操作
3.System.Windows.Forms.Application.DoEvents()當(dāng)運(yùn)行 Windows 窗體時(shí),它將創(chuàng)建新窗體,然后該窗體等待處理事件。該窗體在每次處理事件時(shí),均將處理與該事件關(guān)聯(lián)的所有代碼。所有其他事件在隊(duì)列中等待。在代碼處理事件時(shí),應(yīng)用程序并不響應(yīng)。例如,當(dāng)將另一窗口拖到該窗口前面時(shí),該窗口不重新繪制。如果在代碼中調(diào)用 DoEvents,則您的應(yīng)用程序可以處理其他事件。例如,如果您有向 ListBox 添加數(shù)據(jù)的窗體,并將 DoEvents 添加到代碼中,那么當(dāng)將另一窗口拖到您的窗體上時(shí),該窗體將重新繪制。如果從代碼中移除 DoEvents,那么在按鈕的單擊事件處理程序執(zhí)行結(jié)束以前,您的窗體不會(huì)重新繪制。
這里說(shuō)遠(yuǎn)一點(diǎn),關(guān)于如何防止UI假死其實(shí)還有更科學(xué)的方法,就是使用委托加線程異步執(zhí)行,把耗時(shí)的操作另起進(jìn)程執(zhí)行,從而減輕UI的壓力,使得前臺(tái)UI流暢運(yùn)行而不假死,多用this.Invoke等,但在我程序用我也測(cè)試過(guò)反而直接DoEvents效果還好的多,而且線程和委托可能對(duì)新手朋友稍微復(fù)雜了些,推薦一般應(yīng)用用DoEvents足夠了哦。

好了,東西不多廢話一大篇,手都寫(xiě)累了,基本上用到的方法都做了詳細(xì)的解釋了,呵呵,希望以后大家多多交流,本人菜鳥(niǎo)一個(gè),寫(xiě)本文就是因?yàn)樽约阂驳玫竭^(guò)很多熱心網(wǎng)友的指導(dǎo)和幫助,也希望分享一些自己的東西作為回報(bào),人人為我,我為人人,學(xué)會(huì)做個(gè)懂得感恩的人比什么都重要,歡迎大家和我一起學(xué)習(xí)套談C#

0.png

全部資料下載地址:
C#導(dǎo)出數(shù)據(jù)到EXCEL方法談(附實(shí)例源碼和超級(jí)無(wú)敵詳細(xì)講解).rar (560.85 KB, 下載次數(shù): 74)


評(píng)分

參與人數(shù) 1黑幣 +50 收起 理由
admin + 50 共享資料的黑幣獎(jiǎng)勵(lì)!

查看全部評(píng)分

回復(fù)

使用道具 舉報(bào)

ID:1 發(fā)表于 2017-6-5 17:03 | 顯示全部樓層
好資料,51黑有你更精彩!!!
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表