2016年12月12日 星期一

[C#] 使用EventHandler<T>簡化事件的撰寫

說明:
傳統自行撰寫事件,需先定義委派,使用EventHanler<T>本身就是委派,又加入泛型(傳入事件資料的型別),使得定義方法簽章更為方便。


實作:








2016年11月16日 星期三

[.NET/Tool] Common.Logging與Log4Net的差異與使用

問題:
專案中會看到同時參考Common.Logging與Log4Net,Log4Net就可以輸出Log了,為何還需要Common.Logging?


說明:
專案是可以互相參考的,專案A使用Log4Net輸出log,專案B卻使用NLog,造成管理的不便,
此時使用Common.Logging可提供抽象,由Log4net等dll來實作這些方法,
可以整合Log4net或NLog等.NET常見的Log函式庫,讓專案間可以使用Common.Logging來當Interface。

使用NuGet加入Common.Logging後,using與建立log物件,如下圖:
在app.config加入Logging Configuration後(使用ConsoleOutLoggerFactoryAdapter),
即可輸出Console。



搭配Log4Net,使用NuGet加入Common.Logging.Log4Net1215(目前最新),NuGet就會自動加入對應的Log4Net版本1.2.15(package version為2.0.5),
一樣需設定Logging Configuration,即可輸出Console。




Logging Configuration需注意的是:
● factoryAdapter內的type屬性,最後的Common.Logging.Log4Net1215版本號需正確。
● factoryAdapter下的ConfigType屬性設為INLINE表示讀取專案app.config內設定,若沒設定該屬性,則須有外部設定檔。

輸出外部log,可使用RollingFileAppender

附上Log4Net版本號與NuGet package version對應,如1.2.15對應NuGet 2.0.5。


參考資料:
http://netcommon.sourceforge.net/docs/2.1.0/reference/html/ch01.html
http://logging.apache.org/log4net/release/config-examples.html
https://cmatskas.com/extend-the-common-logging-api-with-log4net/
https://github.com/net-commons/common-logging/wiki/Common.Logging-Packaging-and-Versions
http://stackoverflow.com/questions/37171988/nuget-package-version-not-matching-reference-version

2016年10月23日 星期日

[C#] 使用Microsoft.Interop.Excel將Excel文件列印在同一頁(PageSetup)

說明:
將欄位較多的Excel文件自動列印在同一頁(將所有欄放入單一頁面)。


作法:
using Excel = Microsoft.Office.Interop.Excel;

Excel.Application excel = new Excel.Application();
Excel.Workbook workbook = excel.Workbooks.Open(excelPath);
Excel.Worksheet worksheet = (Excel.Worksheet)workbook.ActiveSheet;

//Fit all columns on one page
excel.PrintCommunication = false;
Excel.PageSetup pageSetup = worksheet.PageSetup;
//FitToPagesTall property設為false,則以FitToPagesWide來縮放Worksheet
pageSetup.FitToPagesTall = false;
pageSetup.FitToPagesWide = 1;
excel.PrintCommunication = true;

workbook.Save();
worksheet.PrintOutEx();
workbook.Close();
excel.Quit();


參考來源:
https://msdn.microsoft.com/en-us/library/microsoft.office.interop.excel.pagesetup.fittopagestall(v=office.15).aspx
http://stackoverflow.com/questions/25741049/how-to-set-fit-all-columns-on-one-page-in-print-tab

2016年9月28日 星期三

[.NET] 使用HtmlAgilityPack與HAPXPathFinder擷取網頁上的資料

說明:
想擷取網頁上的資料,該怎麼做?

作法:
寫.NET的可以使用HtmlAgilityPack這個套件,可透過NuGet或自行下載後加入參考,
這裡以擷取統一發票中獎號碼為例(網址:http://invoice.etax.nat.gov.tw/)。

假設想擷取特別獎號碼,需要知道它的XPath,何謂XPath呢?
XPath(XML Path Languange),用來尋找XML檔案中的節點,詳細語法可參閱[1][2]。
一般而言,網頁的XPath大多像這樣:/html/body/div[1]/table[1]/tr[1]/td[1]。

找尋XPath主要有兩種方法:
● 檢視網頁原始碼來找尋XPath,或按F12開發者工具,再點選「選取元素」自行剖析XPath,Chrome可右鍵->Copy->Copy XPath。

● 使用HAPXPathFinder[3],曾自行剖析某網頁XPath,程式卻一直找不到該節點,使用HAPXPathFinder發現,該XPath沒有html tag...。因各瀏覽器產生的DOM有所差異,
使用HAPXPathFinder可以準確找出HtmlAgilityPack DOM的XPath。
打開HAPXPathFinder,在「Browser」tab輸入URL後,點選「DOM Tree」tab,
在TextBox輸入尋找內容->點選「Find」->點選右方Text區塊的搜尋結果
(->點選左方區塊的標示節點)->中間區塊即有節點的XPath(/#text[1]不需要)。如:


有了URL與XPath,就可以使用HtmlDocument.DocumentNode.SelectSingleNode方法的InnerText屬性來取得號碼了。 若找不到資料,可能是網頁編碼的問題,該網頁編碼使用UTF8。







參考資料:
[1] http://www.w3schools.com/xsl/xpath_syntax.asp
[2] https://msdn.microsoft.com/zh-tw/library/ms256086(v=vs.120).aspx
[3] https://hapxpathfinder.codeplex.com/

2016年8月21日 星期日

[.NET] 使用Sandcastle製作軟體help file的sample code

說明:
軟體開發完成後,需要有說明文件方便使用者查詢使用,
微軟有提供CHM(Compiled HTML)的說明檔格式,
使用Sandcastle與GhostDoc(professional版以上)都可產出CHM help file,
但如果還想要加入sample code的頁面,該如何做呢?


作法:
可使用Sandcastle Help File Builder(SHFB)這套免費工具,又是獨立的編譯器,使用上也較方便。

● 先下載SHFB(http://shfb.codeplex.com/)並安裝,安裝後執行Sandcastle Help File Builder GUI。

● 上方選單列->File->New Project->輸入專案名稱並存檔。

● 在右方Project Explorer中的Documentation Sources上右鍵,選擇Add Documentation Sources...,加入軟體專案編譯後的dll與XML,(專案XML需在專案屬性->建置->輸出區塊,勾選XML文件檔案)。

● 接著在專案名稱上右鍵->Add->New Item,加入Code Snippets與Content Layout,可更改檔名後存檔。

● 點開Code Snippets,將item的id改成需要的名稱,例:SampleCode#NewInstance,sampleCode tag中則是要顯示的程式碼,預設有C#與VB,<可用&lt;、>用&gt;取代。

● 點開Content Layout,點選主畫面左上角的Add a new topic as a sibling of the selected topic,選擇Standard Templates的Sample,填入檔名後Add。

● 點開上一步加入的Sample.aml,在section中的content tag中加上<codeReference>item id</codeReference>,例:

<section address="Section1">
      <title>Create Instance</title>
      <content>
        <para>建立實體物件</para>
        <codeReference>SampleCode#NewInstance</codeReference>
      </content>
</section>

● 調整想要的Project Properties後,上方選單列->Documentation->Build Project,建置結束後,選單列->Documentation->View Help File即可。


參考資料:
http://www.codeproject.com/Articles/141766/Creating-documentation-for-a-NET-component-with-Sa
http://community.submain.com/blogs/tutorials/archive/2014/03/05/conceptual-content-adding-custom-content-to-api-documentation-created-with-ghostdoc.aspx
http://huan-lin.blogspot.com/2009/02/use-sandcastle-file-builder-to-create.html
http://www.codeproject.com/KB/XML/csharpcodedocumentation.aspx?msg=1679454

2016年5月12日 星期四

[Programming/Modbus] little-endian與big-endian

endian在CS裡指的是記憶體位址的排列,不同的CPU有不同的endian,電腦常用的Intel x86是little-endian設計,而big-endian設計的有Modbus PLC,與HP、IBM、MOTOROLA 68K系列CPU,究竟差在哪?


65534以1個word表示為0xFFFE,而記憶體是由左而右存放,
放到little-endian的記憶體會是
FEFF
最低byte存放記憶體最前面,反序排列。



而放到big-endian的記憶體會是
FFFE
最高byte存放記憶體最前面,依序排列,較適合人讀。




參考資料:
https://en.wikipedia.org/wiki/Endianness

2016年5月11日 星期三

[WebDAV] 如何設定WebDAV的基本驗證(Basic Auth)功能(Server與Client)

說明:
WebDAV (Web-Based Distributed Authoring and Versioning)可透過網路磁碟機將Server上檔案整合進本機磁碟,若要設定WebDAV的基本驗證(Basic Authentication),Server該如何設定? Client又該如何連接呢?


作法:
● Server(以Windows Server 2012 R2為例):
1. 安裝WebDAV與基本驗證:
開啟[伺服器管理員] -> 點選新增角色及功能 -> 直到[伺服器角色]tab -> 點開[網頁伺服器] ->
◎ 點開[一般HTTP功能] -> 勾選[WebDAV發行] ->
◎ 再點開[安全性] -> 勾選[基本驗證] -> 安裝。

2. 建立使用者帳戶:
開啟 [電腦管理] -> 點開[系統工具] -> 點開[本機使用者和群組] -> [使用者]上右鍵 -> 新使用者 -> 輸入使用者名稱與密碼。

3. IIS站台新增應用程式與設定:
開啟 [IIS管理員] -> 在站台下新增[應用程式] -> 輸入[別名]與對應實體資料夾 -> 點選剛新增的應用程式 ->
◎ 右側雙擊[WebDAV編寫規則] -> 右鍵[新增編寫規則] -> 在[指定的使用者]輸入新增的使用者名稱,其餘依需求設定 ->
◎ 右側雙擊[驗證] -> 啟用[基本驗證] -> 停用其他驗證方式。

至此,Server已設定完成。


● Client(以Windows 7為例):
1. 打開[服務] (控制台\所有控制台項目\系統管理工具) -> 啟動[WebClient]。

2. 打開登錄檔 (開始 -> 搜尋視窗打regedit) -> [BasicAuthLevel]改為2 (HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters\BasicAuthLevel),表啟用SSL與非SSL的基本驗證 -> reboot。

3. 右鍵[電腦] -> 點選[連線網路磁碟機] -> 在資料夾輸入http://IP或domain name/應用程式名稱
-> 彈出登入視窗 -> 輸入使用者名稱與密碼 -> 送出即可。



參考資料:
https://advancedhomeserver.com/making-a-pc-a-secure-webdav-server/
https://technet.microsoft.com/en-us/library/cc772009(v=ws.10).aspx
https://technet.microsoft.com/zh-tw/library/cc770642.aspx
http://blog.pulipuli.info/2014/07/windows-7owncloudwebdav-map-owncloud.html

2016年5月1日 星期日

[.NET] WPF使用BackgroundWorker實現非同步作業

說明:BackgroundWorker是.NET framewrok 2.0時就有的控制項,方便用於UI thread外,另開thread背景執行耗時的作業,如下載網路上的資料等。


用法:
using System.ComponentModel;

//declare BackgroundWorker object in Field
private readonly BackgroundWorker worker = new BackgroundWorker();

//subscribe events & assign property
public MainWindow()
{
    worker.DoWork += worker_DoWork;
    worker.RunWorkerCompleted += worker_RunWorkerCompleted;
    worker.ProgressChanged += worker_ProgressChanged;
    worker.WorkerReportsProgress = true;
    worker.WorkerSupportsCancellation = true;
}

//下載button
private void btnDownload_Click(object sender, RoutedEventArgs e)
{
    if (!worker.IsBusy)
    {
        //trigger DoWork event
        worker.RunWorkerAsync();
    }
    else
    {
         MessageBox.Show("背景下載作業執行中,請稍候!");
         return;
    }
}

//取消下載button
private void btnCancelDownload_Click(object sender, RoutedEventArgs e)
{
    worker.CancelAsync();
}

#region event handler method

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
     //背景耗時作業
    Download(sender as BackgroundWorker);
         
    if (worker.CancellationPending)
    {
        e.Cancel = true;
        return;
    }
    else
    {
        e.Result = "Download Completed";
    }
}

//DoWork結束後觸發 屬UI Thread
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //從DoWorkEventArgs object取得傳遞引數e
    //label binding
    this.lblDownloadProgress.Content = (e.Cancelled) ? "Canceled" : e.Result.ToString();
}

//屬UI Thread
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    //label binding
    this.lblDownloadProgress.Content = e.UserState.ToString();
    //progressbar binding
    this.pbDownload.Value = e.ProgressPercentage;
}

#endregion

private void Download(BackgroundWorker worker)
{
    for (int i = 0; i < data.Length; i++)
    {
        if (worker.CancellationPending)
        {
            return;
        }
        else
        {
            //觸發ProgressChanged event
            worker.ReportProgress(i * 100 / data.Length, string.Format("{0} files download", i));
        }
    Thread.Sleep(100);
    }
}


參考資料:
http://stackoverflow.com/questions/5483565/how-to-use-wpf-background-worker
https://msdn.microsoft.com/zh-tw/library/cc221403(VS.95).aspx
https://dotblogs.com.tw/billchung/2009/05/29/8592

2016年4月19日 星期二

[Visual Studio] 使用Visual Studio 2015從程式碼逆向工程產出類別圖(class diagram)

說明:
從Visual Studio 2010後,可從架構總管(architecture explorer),提供從程式碼逆向產出類別圖,但VS 2015拿掉了架構總管,那該怎麼產出類別圖?


作法:
使用類別檢視(VS -> 檢視 -> 類別檢視)。
切換至類別檢視,點開你的專案,會列出專案全部namespce,
再點開namespace,會列出包含的class,在cs檔上右鍵,選擇類別圖檢視,
VS就會新增ClassDiagram1.cd,上面就有該class的圖,
再將其他class拖曳至ClassDiagram1.cd,即可產出想要的類別圖。


參考資料:
https://blogs.msdn.microsoft.com/visualstudioalm/2014/10/24/upcoming-changes-in-visual-studio-architecture-and-design-tools/

2016年2月22日 星期一

[.NET/WPF] 屬性'Content'設定了一次以上。

說明:
編輯WPF的xaml,使用GroupBox編輯控制項出現該錯誤。


原因:
GroupBox內只能有一個element,因此無論Panel或Grid都只能放一個,可以在Panel或Grid內放入子element即可。例:
<GroupBox>
   <StackPanel>
      <UniformGrid></UniformGrid>
      <Grid>
         <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
         </Grid.RowDefinitions>
      </Grid>
   </StackPanel>
</GroupBox>


參考資料:
http://stackoverflow.com/questions/1356036/groupbox-in-wpf-can-only-contain-one-element

2016年2月16日 星期二

[Visual Studio] 如何自動同步程式發行版本與最小必要版本? (強制更新發行程式) / minimum required version auto-increment in the clickonce deployment

說明:
使用clickonce發行程式安裝檔,想強制更新client程式,需指定最小必要版本,因發行版本可自動遞增,如何讓最小必要版本同步發行版本? 讓client程式可自動更新。


做法:
1. 方案總管中的專案圖示 -> 右鍵 -> 卸載專案
2. 一樣專案圖示  -> 右鍵 -> 編輯xxx.csproj
3. 在<Project>與</Project>的tag中插入以下設定:
<Target Name="AutoSetMinimumRequiredVersion" BeforeTargets="GenerateDeploymentManifest">
  <FormatVersion Version="$(ApplicationVersion)" Revision="$(ApplicationRevision)">
    <Output PropertyName="MinimumRequiredVersion" TaskParameter="OutputVersion"  />
  </FormatVersion>
  <FormatVersion Version="$(ApplicationVersion)" Revision="$(ApplicationRevision)">
    <Output PropertyName="_DeploymentBuiltMinimumRequiredVersion" TaskParameter="OutputVersion"  />
  </FormatVersion>
</Target>
設定是將ApplicationVersion(如: 1.0.0.%2a)與ApplicationRevision(修訂號,如: 2)合併成最終發行版號,寫入最小必要版本,再寫入部署資訊清單內的最小必要版本屬性。

4. 儲存並關閉csproj -> 發行後檢視部署資訊清單(xxx.application) -> 檢視minimum required version是否有值且正確。


參考資料:
http://stackoverflow.com/questions/202491/automatically-increment-minimum-required-version-in-a-clickonce-deployment
http://stackoverflow.com/questions/202491/automatically-increment-minimum-required-version-in-a-clickonce-deployment

2016年2月4日 星期四

[.NET] DateTimeOffset與DateTime的差異/ What's the difference between DateTimeOffset and DateTime

說明:在.NET framework中用來處理日期與時間的class有DateTimeOffset跟DateTime,究竟差在哪?


說明:

DateTime:好像最直覺用來處理時間的class,但有下列缺點:
● 轉UTC或LocalTime時,可能會與預期不符,如:
DateTime dt = new DateTime(2016,2,4,22,0,0);
dt.ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss");   //顯示為2016/2/5 06:00:00

這是因為DateTime內只儲存Ticks(從0001/1/1起的時間單位)跟Kind,此時Kind為Unspecified,呼叫ToLocalTime()將dt視為UTC時間,依據本地電腦時區+8,因此出現該情形。若改為:
DateTime dt = DateTime.Now;   //Kind為Local,代表本地時間,ToLocalTime()就ok。

● DateTime.CompareTo(),不會考慮時區,單純比較Ticks。


DateTimeOffset:特定時間需指定時區offset,如:
DateTimeOffset dt = new DateTimeOffset(2016,2,4, 22, 0, 0, new TimeSpan(8,0,0));

若有跨時區轉換,用DateTimeOffset是最保險的做法。


參考資料:
http://blogs.msdn.com/b/davidrickard/archive/2012/04/07/system-datetime-good-practices-and-common-pitfalls.aspx