顯示具有 C# 標籤的文章。 顯示所有文章
顯示具有 C# 標籤的文章。 顯示所有文章

2017年7月30日 星期日

【WPF】執行背景工作時,讓畫面不會看起來像卡住


只是想在WPF中使用熟悉的Process Bar,那就別花時間看這篇了
到 youtube去搜尋比較快

這篇不會提到Process Bar,雖然同樣的原理也能用在Process Bar,甚至是自訂的任何進度條。這篇還順便迴避某些書中範例無法回傳string的問題。

原因是WPF畫面的呈現和背景工作是分開的
當程式在忙碌時,畫面會乖乖的一直等背景工作跟他說OK才刷新
(圖片看起來會動,就是因為畫面不斷的刷新喔!)

想像影片前半段,希望能夠「圖片會動、文字會逐項目顯示」,那就需要用到
Async 和 Await
來寫出非同步程式,這樣畫面就不會等到海枯石爛了!
沒聽過「非同步, await, async」的也不用擔心,可以先理解差異,有興趣再找資料吧

●先來一段失敗的程式碼

        private void test_Click(object sender, RoutedEventArgs e)
        {
            //沒使用async & await
            showLog.Text = "轉檔開始(2秒/個)\r\n";
            for (int i = 0; i < 3; i++)
            {
                showLog.Text += "開始第" + (int)(i + 1) + "個轉檔...";
                Thread.Sleep(2000); //模擬轉檔時會停頓的時間
                showLog.Text += "End\r\n";
            }
            showLog.Text += "轉檔全部結束\r\n";
        }
●然後這是成功的程式碼

        private async void test_Click(object sender, RoutedEventArgs e)
        {
            //使用async & await
            showLog.Text = "轉檔開始(2秒/個)\r\n";
            for (int i = 0; i < 3; i++)
            {
                showLog.Text += "開始第" + (int)(i + 1) + "個轉檔...";
                showLog.Text += await ConvertFile("End\r\n");
            }
            showLog.Text += "轉檔全部結束\r\n";
        }

        public Task<string> ConvertFile(string str)
        {
            return Task.Factory.StartNew(() => this._ConvertFile(str));
        }

        public string _ConvertFile(string para1) {
            Thread.Sleep(2000); //模擬轉檔時會停頓的時間
            return para1;
        }

知道怎麼套用await和async,讓畫面不會卡住了嗎?
還是不懂的話,再繼續往下看吧


1. 在可能會執行到需要花時間,但前後想加上其他程式碼的方法(method)的宣告加上「async」,這樣之後要加的await才有作用。只加async卻沒加await,也不會有作用的喔!
before:
private void test_Click(object sender, RoutedEventArgs e){...}
after:
private async void test_Click(object sender, RoutedEventArgs e){...}
2. 宣告一個回傳「數值或字串」的方法 (※不見得要寫成能傳參數,只寫"public string _ConverFile()"也行) 
before:
private async void test_Click(object sender, RoutedEventArgs e){
        ...
        Thread.Sleep(2000); //模擬轉檔時會停頓的時間
        ...
}
after:
private async void test_Click(object sender, RoutedEventArgs e){
        ...
        //Thread.Sleep(2000); //將要背景執行的function搬出這個method
        ...
}
public string _ConvertFile(string para1) {
    Thread.Sleep(2000); //模擬轉檔時會停頓的時間
    return para1;
}
3. 再宣告一個Task,要利用它awaitable的特性,同時利用方便的「Factory.StartNew(...)」來快速的建立與啟動新Task (※也可以不丟參數,直接寫成 "public Task ConverFile()")  
public Task ConvertFile(string str)
{
    return Task.Factory.StartNew(() => "action");
}
4. 將「步驟2」跟「步驟3」結合
public Task ConvertFile(string str)
{
    return Task.Factory.StartNew(() => this._ConvertFile(str+"只是個丟參數的地方")); 
}

5. 將組好的task放在method中使用,記得要加上「await」
private async void test_Click(object sender, RoutedEventArgs e){
        ...
        showLog.Text += await ConvertFile("End\r\n"); //取代原本會拖時間的function
        ...
}

然後就...完成! 可以執行程式,體驗差異了!

【延伸】
有興趣的可以試試看:
1. 只加Async,卻沒用Await,是不是也能成功?
2. 只是把工作丟給Task,卻沒有用Async和Await,是不是也能成功?

如果好奇「await和async」的原理,可以看MSDN的介紹
使用Async 和 Await 設計非同步程式 (C# 和 Visual Basic)
然後找到段落「非同步方法中執行了哪些工作」,會有詳細的執行流程
圖看一次不懂沒關係,不需要刻意去記住,用多就會理解了

2017年3月10日 星期五

【C#】跨視窗傳遞集合

我預計的期望是:
  在「視窗A」點按鈕(Button) → 彈出「視窗B」→ 將「視窗B」更改後的數值回傳

由於「視窗B」的設定不是只有一項,要回傳的話一定是陣列(Array)或集合(Collections)
好處是彼此的變數並不直接干涉,不用因為改了變數名,另一個也要跟著改。

※我用的是WPF,會跟WinForm有一點差距,但是概念差不多。偏基礎的部分,下面介紹就省略了,畢竟需要跨視窗還很在意修飾詞(public, private...)的人,看廢話也覺得煩吧。


視窗A - MainWindow
        private Dictionary<string, string> SetDic = new Dictionary<string, string>();
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            SettingWindow sw = new SettingWindow(SetDic);
            if (sw.ShowDialog() == true) //從設定中取值
                SetDic=sw.ApplySetting();
         }

視窗A的程式碼主要是
第一列:建立Dictionary的集合「SetDic」,作為接收與後續利用
第四列:在事件中用實做視窗B「SettingWindow」,視窗B的建構式看下面
第五、六列:如果在視窗B有點「確定」之類的,那就會執行「SettingWindow」中寫好的方法,將回傳的集合賦值給「SetDic」

視窗B - SettingWindow
    public partial class SettingWindow
    {
        private Dictionary<string, string> _SetDic;
        public SettingWindow(Dictionary<string, string> _SetDic)
        {
            //(不重要,中略)
            _SetDic=SetDic;
        }
        private void Apply_Click(object sender, RoutedEventArgs e)
        {
            //(不重要,中略)
            this.DialogResult = true;
            this.Close();
        }
        internal Dictionary<string, string> ApplySetting() {
            //(不重要,中略)
            return _SetDic;
        }
    }

視窗B就是重頭戲了,分成
第一列:可以看到這個視窗的類別名稱是「SettingWIndow」
第三列:建立一個用來暫存從視窗A丟過來的集合「_SetDic」
第四~八列:由於要接視窗A丟過來的「SetDic」,所以在寫建構式時,要定義參數型別。然後在將接到「SetDic」賦予給「_SetDic」
第九~十四列:這個是點「確定」時,會觸發的事件。重點在第十三列與第十四列的「this.DialogResult = true; this.Close();」,功能分別是「要讓視窗A第五列的IF判斷能成功觸發、關閉視窗B」
第十五~十八列:寫一個會回傳數值的方法,把在視窗B變更過的「_SetDic」回頭賦予給「SetDic」


2016年6月29日 星期三

[WPF]使用App.config來集中管理連線字串(ConnectionString)

因為內部的程式(VB6, winForm)要整個翻新,所以從ASP.net被拉回寫視窗程式
想說既然都要翻新,那乾脆整個框架一起翻新就用了M$支援度比較高的WPF
當然也不意外地要跟資料庫溝通,也就整個從Access換去SQL server

---------好,上面都不是重點, 只是緣由-----------------------------------

既然要跟資料庫結合,一定少不了「連線字串」
因為我都是ADO.net去寫,對於連線字串的依賴相當大
至於每次都要重寫但不易管理的方法就不提了,書本上都有
我這邊是寫個人偏好的集中管理 :D

1. 加入參考「System.Configuration.dll」
2. 在App.config中的<configuration>...</configuration>中加上

<connectionStrings>    
    <add name="DBconnStr" connectionString="要使用連線字串" />
</connectionStrings>

3. 在要設定連線字串的地方用以下進行代入
C#
string connStr = ConfigurationManager.ConnectionStrings["DBconnStr"].ConnectionString;
VB

Dim connStr As Stringction(Configuration.ConnfigurationManager.ConnectionStrings("DBconnStr").ConnectionString

----------------------------
如果直接 new SqlConnection, 可參考下列寫法

SqlConnection connstr = new SqlConnection(ConfigurationManager.ConnectionStrings["AL_connstr"].ConnectionString)

2016年5月17日 星期二

[LINQ]在LINQPad中使用LinqToExcel(如何引入參考與Namespaces)

LINQPad是一個可以快速檢視LINQ查詢結果的工具(而且是麻雀雖小但五臟俱全)
不像在VS中還需要特別寫顯示的畫面,對於Demo來說相當方便
這篇文章只是從零到在LINQPad使用LinqToExcel的教學文
重點在於「如何引入參考和命名空間」
LINQPad的詳細使用可以找google小姐

LinqToExcel對針對只「讀」的資料而言,比NPOI與OpenXML更佳的選擇

LINQPad版本(官網下載的FREE版):v4.50.00
LinqToExcel版本(NuGet抓取):v1.10.1 

Step1: 取得LINQPad


不喜歡安裝的,可以點紅框處。至於選LINQPad 5 還是LINQPad 4 則取決於用的.NET Framework版本

下載後,解壓縮在尬意的位置就可以了,然後點「LINQPad.exe」執行
打開LINQPad後,畫面長這個樣子

Step2: LINQPad環境設定

要使用LinqToExcel前,需要將DLL讓LINQPad認識。(加入參考與命名空間)
點工具列的「Query」→「Query Peoperties」
在「Additional Reference」的頁籤中,利用「Browse...」將已經抓好的LinqToExcel相關DLL如圖所需添加(Name欄位才有參考價值)

再換到「Additional Namespace Imports」頁籤中添加命名空間
如果不知道要打什麼或是擔心打錯的,點「Pick from assemblies」
右側就會自動產生相應的命名空間,需要的再點「< Add Selected Namespaces」添加
最後,當然就是點「OK」

Step 3:Query它

將Language切換到「C# Program 」後 (Language詳細請參閱:LINQPad: The Ultimate .NET Scratchpad)
在輸入區打上下方語法後,Excute(F5)
----------------------------------------------------------------------------------------
void Main()
{
    var fileName=System.IO.Path.Combine(@"E:\備份\2016年度","SOP文件總攬.xlsx");
    var excelFile=new ExcelQueryFactory(fileName);
   
    var excel=excelFile.Worksheet(1); //亦可輸入工作表名 excelFile.Worksheet(“工作表1”)
    foreach (var element in excel)
    {
        element.Dump();
        "".Dump();
    }
}
----------------------------------------------------------------------------------------

然後你就得到它了

2016年1月17日 星期日

[NuGet]用OpenXml在docx中套版列印 - 基本型

使用版本:
  • Visual Studio 2012
  • DocumentFormat.OpenXml v2.5

參考資料:


NPOI在excel中用起來還不錯, 但是Word中就... 不那麼直觀了(也許只是個人問題)

「基本型」就是有現成的Template, 僅是將[$...$]包夾的文字取代後Export
([$...$]是在黑暗執行緒中發現的, 覺得不錯就學起來了 :D)


步驟一:設定aspx中的控制項, 且產生Button1的Click事件
輸入的控制項使用TextBox, 按鈕的控制項使用Button

步驟二:撰寫Button1的Click事件
此段與黑大鎖寫近乎無異, 僅有綠色區域因需求而微調
綠框視控制項的設定而變, 綠線則是函數名, 結果與黑大的MakeDocx()差不多


 步驟三:撰寫editWordDoc函數
此處是參考「我如何...」編寫

到此就大功告成了

如果Export後,發現[$...$]沒有被取代

將*.docx改為*.zip去看「word→doucment.xml」(用可編輯的工具為佳, 個人是用記事本)
看是不是[$...$]不連續, 將多餘的地方刪除, 再存回docx就可以了


與OpenXml類似用途的NPOI介紹

2015年12月11日 星期五

[ASP.NET]從後台透用C#呼叫前台寫好的JavaScript function()

此文章不是如何在從*.cs中編寫一段js並到前台執行
而是在*.cs中, 使用一段前台已經寫好的js功能
此篇介紹的是作用於page內, 而非updatepanel中的js, updatepanel的請參見頁尾的msdn

假設前台(*.aspx)中有這麼一段<script>
    <script type="text/javascript">
      var optionSubjectBar = {
            'title': 'Nya~',
            'width': 200,
            'height': 300,
            vAxis: { minValue: 0 }
        };
        ...................
        function drawAxisTickColors() {
            var dataAudit = google.visualization.arrayToDataTable([
            chart.draw(dataAudit, optionAuditStackedBar);        }

        function clicktest() {
            var data = new google.visualization.DataTable();
            ...................
            chart.draw(data, optionAuditStackedBar);
        };
    </script>

在要執行該段js的C#語法中視需求插入即可
例如我想要使用「clicktest()」這個功能

(*.cs)後台
建構式為
public static void RegisterStartupScript( Page page, Type type, string key, string script, bool addScriptTags )
  • page:一般用「Page」就對了
  • type:一般用「GetType()」就對了
  • key:給後面的指令碼有個識別值, 像是ID般的存在, 不一定要跟function相同名稱
  • script: 當然就是要執行的function了,以這個例子就是輸入「clicktest()」,要直接寫一段程式碼也是可以
  • addScriptTage:true或false, 只是會不會加上<script>...<script>而已


ScriptManager.RegisterStartupScript(Page,GetType(),"clicktest","clicktest()",true)
如果在addScriptTags用false, 那就要寫成「<script>clicktest()<script>」
通常為了避免重複一直註冊key, 會多加上判斷式
-----------------------------------------------------------------------------------------------------------------------------------------------

if (!Page.ClientScript.IsStartupScriptRegistered("clicktest"))
        {
            ScriptManager.RegisterStartupScript(Page,GetType(),"clicktest","<script>clicktest()</script>",false)
        }
-----------------------------------------------------------------------------------------------------------------------------------------------

對於單純的page也許不太需要, 但是會不斷刷新的ajax就可以節省頻寬喔

使用UpdatePanel的話,請參考這MSDN的設定:(點我前往)
另外這篇文章也不錯,說明三種註冊方法的差異(本文只使用其中一種):動態註冊JavaScript至Client端


參考資料:



2015年10月22日 星期四

[NuGet]NPOI初探

從googlesite中搬過來的, 與其說介紹文, 比較像是個人「筆記」之類的存在
公司excel檔案都改成xlsx後, XLS相關就不再使用了
並沒有全面的介紹NPOI, 如果打算深入NPOI, 可以直接參考最底下的外部來源 

另一個與NPOI類似用途的工具:OpenXml 介紹

簡介 / 安裝方法
【簡介】
可以操作excel檔案的
Microsoft Developer Network > 學習園地 > CodePlex 教學

【安裝NPOI】
於VS中→右鍵網站或專案→管理NuGet套件→搜尋NOPI→安裝→完成
(沒錯, 就是這麼簡單)



XLS範例

使用的NPOI版本 v2.1.3.1 (供參考, 舊版本可能有部分程式碼不相容)

引入的參考

  1. using System.IO;  
  2. using NPOI.HSSF.UserModel;  
  3. using NPOI.HSSF.Util;  
  4. using NPOI.SS.UserModel;  

檔案IO

建立一個新活頁簿

  1. //建立物件  
  2. HSSFWorkbook workbook = new HSSFWorkbook();  
  3. workbook.CreateSheet("試算表A");  
  4.   
  5. //將物件寫到記憶體並產生另存  
  6. MemoryStream ms = new MemoryStream();  
  7. workbook.Write(ms);  
  8. Response.AddHeader("Content-Disposition", string.Format("attchment; filename=EmptyWorkbook.xls"));   
  9. Response.BinaryWrite(ms.ToArray());  
  10.   
  11. //清空  
  12. workbook = null;  
  13. ms.Close();  
  14. ms.Dispose();  

取出Server中已存在的活頁簿

  1. string strFilePath = string.Format(Server.MapPath(".") + "\\format_eCRF\\EmptyWorkbook.xls"); //抓取ASP.NET網頁的程式位址    
  2. HSSFWorkbook workbook;    
  3. using (FileStream fs = new FileStream(strFilePath, FileMode.Open, FileAccess.ReadWrite))    
  4. {    
  5.     workbook = new HSSFWorkbook(fs);    
  6. }  
  7.   
  8. /*--IT'S SHOW TIME!--*/  
  9.     
  10. //定番    
  11. MemoryStream ms = new MemoryStream();    
  12. workbook.Write(ms);    
  13. Response.AddHeader("Content-Disposition", string.Format("attchment; filename=EmptyWorkbook.xls"));    
  14. Response.BinaryWrite(ms.ToArray());    
  15.     
  16. workbook = null;    
  17. ms.Close();    
  18. ms.Dispose();  

cell操作 

建立cell

可以用CreateSheet / CreateRow / CreateCell串燒
(範例待續, 請先參考下方)

找尋目標cell

可以用GetSheet / GetSheetAt / GetRow / GetCell串燒到目標cell
(範例待續, 請先參考下方)


填入值

再用SetCellValue(value),可容許相當多種type

  1. //插入資料。  
  2. HSSFSheet sheet = (HSSFSheet)workbook.CreateSheet("My Sheet"); //新建資料表同時插入  
  3. sheet.CreateRow(0).CreateCell(0).SetCellValue(0);  
  4. HSSFSheet at_sheet = (HSSFSheet)workbook.GetSheetAt(0);//用索引取得資料表並插入  
  5. at_sheet.CreateRow(0).CreateCell(0).SetCellValue("at sheet");  
  6. HSSFSheet _sheet = (HSSFSheet)workbook.GetSheet("試算表 A");   //用資料表名取得資料表並插入  
  7. _sheet.GetRow(0).GetCell(0).SetCellValue("get sheet by name"); 

欄位寬度
利用SetColumnWidth(第n攔, 寬度多少)
  1. workbook.GetSheetAt(0).SetColumnWidth(3, 2048);  

幫cell上色

物件HSSFCellStyle中的各項方法
  1. //建立儲存格樣式  
  2. HSSFSheet teststyle=(HSSFSheet)workbook.CreateSheet("sheet1");  
  3. HSSFCellStyle style1 = (HSSFCellStyle)workbook.CreateCellStyle();  
  4. style1.FillForegroundColor = HSSFColor.Pink.Index;  
  5. style1.FillPattern = FillPattern.SolidForeground; //版本2.0的操作方法  
  6.   
  7. HSSFCell cell = (HSSFCell)teststyle.GetRow(0).GetCell(0);  
  8. cell.CellStyle = style1;  

函數應用

在cell中產生與欄位相呼應的值

利用LastRowNum和LastCellNum取得陣列範圍
  1. for (int i = 0; i < workbook.GetSheetAt(0).LastRowNum ; i++)  
  2. {  
  3.     int n = 65;  
  4.     for (int j = 0; j < workbook.GetSheetAt(0).GetRow(1).LastCellNum; j++)  
  5.     {  
  6.         workbook.GetSheetAt(0).GetRow(i).GetCell(j).SetCellValue(Convert.ToChar(n + j).ToString() + (i+1));  
  7.     }  
  8. }  

Q&A

有些程式碼無法起作用

如果範例是以2.0以前的版本去實作,可能會遇到這些問題
【需要強制轉型】HSSFSheet sheet = (HSSFSheet)workbook.CreateSheet("My Sheet");

【變更調用方法】style1.FillPattern = NPOI.SS.UserModel.FillPattern.SolidForeground;
建議下載官方範本去看該如何在C#中使用

CreateRow 和 GetRow 有何差異

相CreateRow想像成初始化Row會更容易理解
同:Sheet層的設定維持不變, 會在同一列(不會產生插入列的效果)
異:前者移除style後者保留, 前者Cell需要重新create cell後者維持




XLSX範例

基本上跟XLS很像, 遇到特別的再說

讀取樣板, 並另存新檔

只是能用而不太講究的話, 將HSSF全部改成XSSF就可以了
缺點是開啟檔案時, 會彈出警告視窗
參考官方範例並加以修改後(改成檔案是在記憶體中產生, 不留檔), 只要將下面這段
  Response.AddHeader("Content-Disposition", string.Format("attchment; filename=EmptyWorkbook.xlsx"));
  Response.BinaryWrite(ms.ToArray());

  workbook = null;
  ms.Close();
  ms.Dispose();
替換成這段就ok了(前半仍需完成HSSF替換成XSSF)
  Response.Clear();
  Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
  Response.AddHeader("Content-Disposition", string.Format("attachment;filename={0}", "editedWorkbook.xlsx"));
  Response.BinaryWrite(ms.ToArray());
  Response.Flush();
  Response.End();

將DataSet的資料用NPOI倒入Excel

如果有要特定格式, 可善用「string.format()」
例如日期:
  • 將「SetCellValue(column.ColumnName);」
  • 替換成「SetCellValue(string.Format("{0:yyyy/MM/dd HH:mm:ss}", row[column]));」
DataSet相關請參考:DataSet.Tables 屬性

/*--此處開始編輯檔案*/
  XSSFSheet sheet = (XSSFSheet)workbook.GetSheetAt(2);

  for (int i = 0; i <= 2;i++ )
  {
      int rowIndex = i*3;
      
      dt = ds.Tables.Count > 0 ? ds.Tables[i] : new DataTable();
      XSSFRow headerRow = (XSSFRow)sheet.CreateRow(rowIndex);
      foreach (DataColumn column in dt.Columns)
    headerRow.CreateCell(column.Ordinal).SetCellValue(column.ColumnName);

      foreach (DataRow row in dt.Rows)
      {
    XSSFRow dataRow = (XSSFRow)sheet.CreateRow(rowIndex+1);
    foreach (DataColumn column in dt.Columns)
    {
        dataRow.CreateCell(column.Ordinal).SetCellValue(row[column].ToString());
    }
    rowIndex++;
      }
  }