2014年12月31日 星期三

[PaaS]試用 OpenShift 平台-3(安裝 MongoDB 及其前端網頁管理工具 RockMongo)

、安裝 MongoDB 及其前端網頁管理工具 RockMongo
3.1. 安裝 MongoDB
cd myapp1
rhc cartridge add mongodb-2.4

3.2. 安裝 MongoDB 的前端網頁管理工具 RockMongo
rhc cartridge add rockmongo-1.1

若不想背太多指令,可以透過Web介面來做設定

3.3. 測試一下
3.3.1. rhc ssh

3.3.2. mongo
3.3.3. use myapp1(切換資料庫)

3.3.4. var icd10cm = {"action":"I", "code":"A", "pre":"", "content":"感染和寄生蟲疾病 Certain infectious and parasitic diseases", "quest":"Q:哪種類型?"};
3.3.5. db.icd10cm.insert(icd10cm );
3.3.6. db.icd10cm.find();

3.3.7. 安裝 mongojs
npm install mongojs

 3.3.8. 修改 server.js,增加"路由"程式碼
在 self.createRoutes 的 function 中增加以下的程式碼
self.routes['/db'] = function(req, res) {
    var mongojs = require('mongojs');
    var mydb = "myapp1";
    var mycollection = "icd10cm";
    var connectionString = process.env.OPENSHIFT_MONGODB_DB_USERNAME  
      ":"  
      process.env.OPENSHIFT_MONGODB_DB_PASSWORD  
      "@"  
      process.env.OPENSHIFT_MONGODB_DB_HOST  
      "/"  
      mydb;
    var db = mongojs(connectionString, ['icd10cm']);
    var mycollection = db.collection('icd10cm');

    db.icd10cm.find(function(err, docs) {
        res.send(docs);
    });
};

3.3.9. 提交程式碼
git add .
git status
git commit -a -m "Add mongojs to node.js"
git push

3.3.10. 輸入網址測試資料回傳
http://myapp1-bobohan.rhcloud.com/db

參考資料
[1] Installing the OpenShift Client Tools
[2] Getting Started with MongoDB on Node.js on OpenShift
[3] Running Nginx on OpenShift
[4] 《OPENSHIFT》可綁網址、無限流免費雲端主機
[5] 在 OpenShift 上部署 MongoDB 和 Node.js 应用
[6] OpenShift安裝Nginx+MYSQL+PHP5.4

2014年12月21日 星期日

健保e化抽審-4(PDF加入浮水印)

怕有日後資料外流不易追查,進而引發不必要的資安議題(申請單都消化不完了,還要搞這個),所以還是小心點好,在PDF檔裡加入浮水印好了,避免有心人傳來傳去。 必要時還可以加入當初使用者是誰,方便源頭的追蹤管理。
private void button19_Click(object sender, EventArgs e)
{
  string fontPath = Environment.GetFolderPath(Environment.SpecialFolder.System)   @"\..\Fonts\kaiu.ttf";
  BaseFont bfChinese = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
    
  string fileBefore = @"C:\temp\fileBefore.pdf";
  string fileAfter = @"C:\temp\fileAfter.pdf";

  // Creating iTextSharp.text.pdf.PdfReader object to read the Existing PDF Document
  using (PdfReader reader = new PdfReader(fileBefore))
  // Creating iTextSharp.text.pdf.PdfStamper object to write Data from iTextSharp.text.pdf.PdfReader object to FileStream object
  using (FileStream fs = new FileStream(fileAfter, FileMode.Create, FileAccess.Write, FileShare.None))
  using (PdfStamper stamper = new PdfStamper(reader, fs))
  {
    // Getting total number of pages of the Existing Document
    int pageCount = reader.NumberOfPages;

    // Create New Layer for Watermark
    PdfLayer layer = new PdfLayer("WatermarkLayer", stamper.Writer);

    // Loop through each Page
    for (int pageNum = 1; pageNum <= pageCount; pageNum  )
    {
      // Getting the Page Size
      iTextSharp.text.Rectangle rect = reader.GetPageSize(pageNum);

      // Get the ContentByte object
      PdfContentByte cb = stamper.GetUnderContent(pageNum);
   
      // Tell the cb that the next commands should be "bound" to this new layer
      cb.BeginLayer(layer);
      cb.SetFontAndSize(bfChinese, 50);
      cb.SetColorFill(BaseColor.BLACK);

      PdfGState gState = new PdfGState();
      gState.FillOpacity = 0.25f;
      cb.SetGState(gState);

      cb.BeginText();
      cb.ShowTextAligned(PdfContentByte.ALIGN_CENTER, "僅供健保e化抽審使用", rect.Width / 2, rect.Height / 2, 45f);
      cb.EndText();

      // Close the layer
      cb.EndLayer();
    }
  }
}
參考資料
[1] Adding Watermark to PDF Document using Layer
[2] Nested using statements in C#

2014年12月11日 星期四

[PaaS]試用 OpenShift 平台-2(建立可以運作 Node.js 的平台及設定 FTP)

二、建立可以運作 Node.js 的平台(PaaS)及設定 FTP
2.1. 建立可以運作 Node.js 的平台(PaaS)
2.1.1. rhc create-app myapp1 nodejs-0.10

2.1.2. 輸入"rhc ssh"連線到遠端,並測試一下「hello, world」

2.2. 測試Git
2.2.1. 建立測試檔案
echo console.log('hello, world'); > helloworld.js

2.2.2. 檢查目前 Repository 狀態
git status

2.2.3. 將測試檔案加入 Repository
git add helloworld.js

2.2.4. 提交檔案
git commit -m "Add helloworld.js to test git and node.js"
or
git commit -a -m "Add helloworld.js to test git and node.js"

2.2.5. 上傳檔案
git push
PS: 若遇到 "git push error failed to push some refs to" 的問題
請先輸入 "git pull --rebase"

2.3. 設定FTP
2.3.1. 使用 Putty Key Generator 產生公鑰及私鑰

2.3.2. 於 OpenShift 設定 Public Keys 及 Remote Access


2.3.3. 於 FileZilla 加入 SFTP 私鑰檔案設定

2.3.4. 於 FileZilla 加入主機設定(Host、Logon Type、User)

2.3.5. 連線測試一下,OK

參考資料
[1] Installing the OpenShift Client Tools
[2] Getting Started with MongoDB on Node.js on OpenShift
[3] Running Nginx on OpenShift
[4] 《OPENSHIFT》可綁網址、無限流免費雲端主機
[5] 在 OpenShift 上部署 MongoDB 和 Node.js 应用
[6] OpenShift安裝Nginx+MYSQL+PHP5.4

[PaaS]試用 OpenShift 平台-1(安裝 OpenShift Client 端工具)

一、安裝 OpenShift Client 端工具
1.1. 設定 OpenShift 的 Windows 環境
1.1.1. 安裝 rubyinstaller-1.9.3-p551.exe,記得要將設定Path勾選進來

1.1.2. 測試一下,輸入ruby -v

1.1.3. 安裝 Git,順便驗證版本

1.1.4. 安裝及設定 OpenShift gem
gem install rhc

rhc setup,並輸入帳號/密碼



參考資料
[1] Installing the OpenShift Client Tools
[2] Getting Started with MongoDB on Node.js on OpenShift
[3] Running Nginx on OpenShift
[4] 《OPENSHIFT》可綁網址、無限流免費雲端主機
[5] 在 OpenShift 上部署 MongoDB 和 Node.js 应用
[6] OpenShift安裝Nginx+MYSQL+PHP5.4

2014年12月7日 星期日

[硬體/系統]Mail Server(Zimbra)送信異常問題處理(Mail Relay Attack)

最近 email server 常被攻擊,然後就會成為 mail relay 的跳板。Google了一些資料,並對我們的 email 主機作了一些設定上的變更,希望它只能 relay 我們 domain 的信件。當然,我們希望能有足夠的時間依照服務廠商的建議,來調整 email server 架構,將送信跟收信兩個功能拆成兩部伺服器來處理。
 
第一步:修改Postfix設定檔(/opt/zimbra/postfix/conf/main.cf)
修改前~
mydestination = localhost
無relay_domains

修改後~
mydestination = $myhostname, $mydomain
relay_domains = $mydomain


第二步:使用 telnet 的方式來模擬 email 的寄送
(1)建立連線,若失敗再敲一次,我們家的主機會這樣,學藝不精不知道為什麼
ehlo DomainName
(2)輸入寄件者
mail from:<user1@DomainName>
(3)輸入收件者
rcpt to:<user2@DomainName>
(4)輸入信件內容,並最後一行輸入"."表示結束
data
測試信件
.
(5)結束連線
quit
(6)檢查log,取出最後15行
tail -n 15 /var/log/maillog


第三步:安裝流量監控工具(iftop)
鑑於以上的攻擊事件,想要透過流量的分析,了解到到底壞人(IP)在那裡。若仍不行檔下來這些攻擊,那只好將這些IP設入防火前牆的黑名單內。服務廠商推薦iftop,所以就裝上它來試看看。
以下就相關的安裝過程~
(1)檢查CentOS版本
cat /etc/redhat-release
(2)因為yum指令找不到套件
yum search iftop
(3)所以只好用rpm的版本來安裝iftop
wget http://pkgs.repoforge.org/iftop/iftop-0.17-1.el5.rf.i386.rpm
rpm -ivh iftop-0.17-1.el5.rf.i386.rpm

這是我會使用的功能~
【B】顯示40秒的平均值
【3】以40秒的平均值來排序
【T】顯示總流量
【t】切換上傳下載顯示模式
【p】是否顯示來源端與目的端的port

相關操作我是參考這篇blog~
iftop流量監控使用教學,立刻抓出吃流量兇手

參考資料
[1] 鳥哥的 Linux 私房菜 - 第二十二章、郵件伺服器: Postfix
[2] iftop: display bandwidth usage on an interface
[3] How to setup an E-Mail Relay Host with Sendmail ?
[4] Send a test mail using Telnet
[5] Authenticated SMTP

2014年11月25日 星期二

[軟體/Debug]於win7環境下提示視窗帶至前景的功能(SetForegroundWindow)偶發失敗

最近前台電腦環境轉換至win7,平常在winXP運作正常將提示視窗帶到前景的功能,會偶發失敗(壓視窗)的情況發生。對於醫師來說,這非常影響門診看診的"順暢性"。所以,非常認真的找了一下資料,不然電話那頭都會傳來關切的聲音(好累喔!)...
最後參考了CodeProject的這篇文章"How to bring window to top with SetForegroundWindow()",並將相關程式碼轉換成C#的語法來使用。因為仍有電腦環境的問題要考慮,所以並沒有整合到目前的程式碼,不過還是記錄一下好了!

[DllImport("user32.dll")]
public static extern bool IsWindow(IntPtr hWnd);

[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();

[DllImport("kernel32.dll")]
public static extern uint GetCurrentThreadId();

[DllImport("user32.dll")]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);

[DllImport("user32.dll")]
public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);

public const uint SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000;
public const uint SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001;
public const uint SPIF_SENDWININICHANGE = 0x02;
public const uint SPIF_UPDATEINIFILE = 0x01;
[DllImport("user32.dll")]
public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, ref uint pvParam, uint fWinIni);

public const int ASFW_ANY = -1;
[DllImport("user32.dll")]
public static extern bool AllowSetForegroundWindow(int dwProcessId);

[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);

/// <summary>
/// How to bring window to top with SetForegroundWindow()
/// http://www.codeproject.com/Tips/76427/How-to-bring-window-to-top-with-SetForegroundWindo
/// </summary>
/// <param name="hWnd"></param>
private void SetForegroundWindowInternal(IntPtr hWnd)
{
 if(!IsWindow(hWnd)) return;

 //relation time of SetForegroundWindow lock
 uint lockTimeOut = 0;
 IntPtr hCurrWnd = GetForegroundWindow();
 uint dwThisTID = GetCurrentThreadId();
 uint dwCurrTID = GetWindowThreadProcessId(hCurrWnd, IntPtr.Zero);

 //we need to bypass some limitations from Microsoft :)
 if (dwThisTID != dwCurrTID)
 {
  AttachThreadInput(dwThisTID, dwCurrTID, true);

  SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, ref lockTimeOut, 0);
  SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, ref lockTimeOut, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);

  AllowSetForegroundWindow(ASFW_ANY);
 }

 SetForegroundWindow(hWnd);

 if (dwThisTID != dwCurrTID)
 {
  SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0,ref lockTimeOut, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
  AttachThreadInput(dwThisTID, dwCurrTID, false);
 }
}

參考資料
[1] 把視窗移到最前面
[2] How to bring window to top with SetForegroundWindow()
[3] Control in Focus in Other Processes
[4] PInvoke.net

2014年11月19日 星期三

[硬體/系統]設定單一登入(SSO)環境

1. 安裝ADAM(Active Directory Application Mode)於Windows XP

2. 程式方面~
引用:System.DirectoryServices.DirectorySearcher
string cn = "UserName";
string cnPassword = "UserPassword";

string path = "LDAP://ServerName:389/dc=DomainName,dc=com,dc=tw";
string username = "DomainName\\DomainAdmin"; 
string password = "DomainAdminPassword";

// Authentication flags.
// For non-secure connection, use LDAP port and
//  ADS_USE_SIGNING | ADS_USE_SEALING | ADS_SECURE_AUTHENTICATION
// For secure connection, use SSL port and
//  ADS_USE_SSL | ADS_SECURE_AUTHENTICATION
AuthenticationTypes authenticationTypes  = AuthenticationTypes.Signing | AuthenticationTypes.Sealing | AuthenticationTypes.Secure;

const long ADS_OPTION_PASSWORD_PORTNUMBER = 6;
const long ADS_OPTION_PASSWORD_METHOD = 7;
const int ADS_PASSWORD_ENCODE_REQUIRE_SSL = 0;
const int ADS_PASSWORD_ENCODE_CLEAR = 1;

/// <summary>
/// 新增 LDAP 使用者
/// </summary>
private void Add(string cn)
{
 try
 {
  // 定義 DirectoryEntry
  DirectoryEntry entry = new DirectoryEntry(path, username, password, authenticationTypes);

  // 檢查是否存在 LDAP 使用者
  DirectorySearcher searcher = new DirectorySearcher(entry);
  searcher.Filter = string.Format("(cn={0})", cn);
  SearchResult result = searcher.FindOne();

  // 新增 LDAP 使用者
  if (result == null)
  {
   DirectoryEntry user = entry.Children.Add(string.Format("cn={0},ou=Users", cn), "inetOrgPerson");
   //user.Properties["cn"].Value = "PK";
   user.Properties["displayName"].Value = &quot顯示名稱";
   user.Properties["department"].Value = "部門";
   user.Properties["departmentNumber"].Value = "部門代碼";
   user.Properties["givenname"].Value = "名";
   user.Properties["mail"].Value = "電子郵件";
   user.Properties["sn"].Value = "姓";
   user.Properties["uid"].Value = "帳號";
   user.Properties["userPrincipalName"].Value = "帳戶";
   user.CommitChanges();
   user.Close();
  }
 }
 catch (Exception ex)
 {
  throw new Exception(ex.Message);
 }
}

/// <summary>
/// 變更 LDAP 使用者密碼
/// </summary>
private void ChangePassword(string cn, string password)
{
 try
 {
  // 定義 DirectoryEntry
  DirectoryEntry entry = new DirectoryEntry(path, username, password, authenticationTypes);

  // 檢查是否存在 LDAP 使用者
  DirectorySearcher searcher = new DirectorySearcher(entry);
  searcher.Filter = string.Format("(cn={0})", cn);
  SearchResult result = searcher.FindOne();

  // 變更 LDAP 使用者密碼
  if (result != null)
  {
   DirectoryEntry user = result.GetDirectoryEntry();
   user.Invoke("SetOption", new object[] { ADS_OPTION_PASSWORD_PORTNUMBER, 389 });
   user.Invoke("SetOption", new object[] { ADS_OPTION_PASSWORD_METHOD, ADS_PASSWORD_ENCODE_CLEAR });
   user.Invoke("SetPassword", new Object[] { cnPassword });
   user.Properties["LockOutTime"].Value = 0;
   user.CommitChanges();
   user.Close();
  }
 }
 catch (Exception ex)
 {
  throw new Exception(ex.Message);
 }
}

/// <summary>
/// 刪除 LDAP 使用者
/// </summary>
private void Delete(string cn)
{
 try
 {
  // 定義 DirectoryEntry
  DirectoryEntry entry = new DirectoryEntry(path, username, password, authenticationTypes);

  // 檢查是否存在 LDAP 使用者
  DirectorySearcher searcher = new DirectorySearcher(entry);
  searcher.Filter = string.Format("(cn={0})", cn);
  SearchResult result = searcher.FindOne();

  // 刪除 LDAP 使用者
  if (result != null)
  {
   DirectoryEntry user = entry.Children.Find(string.Format("cn={0},ou=Users", cn), "inetOrgPerson");
   user.DeleteTree();
  }
 }
 catch (Exception ex)
 {
  throw new Exception(ex.Message);
 }
}

3. L7 Networks(上網認證)


 4. zimbra(電子郵件)


 5. moodle(數位學習) 1.9.12






參考資料
[1] Setting User Passwords
[2] LDAP Authentication

2014年11月11日 星期二

[硬體/系統]利用dd指令及JPerf(iperf)工具來量測XenServer的儲存及網路效能

很無奈的!我們在工作上並沒有太多的資源,所以我們的虛擬化環境是用免費的XenServer所架設,有時候遇到效能的問題,實在不容易找出可以努力改善的方向。但事情遇到了,還是要想辦法解決。在網路上看過幾份資料後,利用幾個簡單的指令及工具,建立目前環境的基準值。日後遇到效能瓶頸或是要升級硬體設備時,就有一個可以比較的基準。

儲存I/O效能工具~利用dd指令來量測
  • XenServer主機上
dd if=/dev/sda of=/dev/null bs=1M count=500 iflag=direct

  • 虛擬主機上
dd if=/dev/xvdb of=/dev/null bs=1M count=500 iflag=direct

網路I/O效能工具~利用JPerf(iperf)工具來量測

  • Server端
輸入"iperf.exe -s"執行伺服器模式。

Client端~
執行jperf.bat,並輸入Server端的ip,選擇輸出格式為MBytes,按下"Run IPerf!"

參考資料
[1] Xen and XenServer Storage Performance
[2] JPerf 2.0.2
[3] 使用Iperf測試網路效能
[4] XenServer Virtual Machine Performance Utility

2014年11月6日 星期四

健保e化抽審-3(國際條碼、Code 128)

我們原本使用的是 Code 39 的條碼,因為我們為了簡化進貨或盤點時的資料輸入(如:有效日期或批號等等...),再加上 Code 39 的資料越多則長度就越長,甚至會超出 Barcode Reader 的可掃瞄範圍,再加上很多的醫藥材都是使用 Code 128,所以我們導入了國際條碼。這個過程真的花了一番心力,現在我們將這項成果導入在文件的生成與辨識。

注意!以下這些 Code,已經忘記出處在那裡,若有人知道煩請告知,真是對不起原作者!

一、了解及準備 Code 128 字型檔
這兩篇 Blog 不錯,對於不了解什麼是 Barcode 的人,可以有初步的認識。
二、轉換 Barcode 資料及產生 Barcode 影像
Private Sub btnCode128_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCode128.Click

    Dim _data As String = "038000356216"

    ' Font Style
    Dim privateFonts As New System.Drawing.Text.PrivateFontCollection()
    privateFonts.AddFontFile("C:\WINDOWS\Fonts\3of9.ttf")
    privateFonts.AddFontFile("C:\WINDOWS\Fonts\code128.ttf")

    Dim font39 As New System.Drawing.Font(privateFonts.Families(0), 36)
    Dim font128 As New System.Drawing.Font(privateFonts.Families(1), 36)

    ' Code39:資料前後加上"*"做為啟始碼/停止碼即可
    Me.lblCode39.Font = font39
    Me.lblCode39.Text = BarcodeConverter39.StringToBarcode(_data)
    Me.lblCode39.AutoSize = True

    ' Code128:是由 啟始碼+資料碼+檢查碼+停止碼 所組合而成的條碼
    Me.lblCode128.Font = font128
    Me.lblCode128.Text = BarcodeConverter128.StringToBarcode(_data)

    ' Image Style
    Dim barcode As New BarcodeLib.Barcode()

    barcode.IncludeLabel = True
    barcode.Width = 300
    barcode.Height = 150
    barcode.Encode(BarcodeLib.TYPE.CODE128, _data)

    Dim ms As New MemoryStream()
    barcode.SaveImage(ms, BarcodeLib.SaveTypes.PNG)

    Me.PictureBox1.Image = System.Drawing.Image.FromStream(ms)

End Sub

三、取出 Barcode 資料
參考 GS1 General Specifications(Version 12),我們需要取得商品代號、有效日期及批號。
  • (01)商品代號~固定長度(數字形態,長度14)
  • (17)有效日期~固定長度(數字形態,長度6)
  • (10)批號~變動長度(數字形態,長度最多20)
private void btnGS1Barcode_Click(object sender, EventArgs e)
{
 string barcode;
 // 測試樣本
 //barcode = "0108888893102146";
 //barcode = "010888889310214617120315";
 barcode = "01088888931021461712031510W1040190";
 string Ai01; // (01)08888893102146
 string Ai17; // (17)120315     
 string Ai10; // (10)W1040190

 GS1BarcodeConvert(barcode, out Ai01, out Ai17, out Ai10);

 Application.DoEvents();
}

private void GS1BarcodeConvert(string pBarcode, out string pAi01, out string pAi17, out string pAi10)
{
 pAi01 = string.Empty;
 pAi17 = string.Empty;
 pAi10 = string.Empty;

 string AI;
 string DataContent = pBarcode;
 while (!string.IsNullOrEmpty(DataContent))
 {
  AI = DataContent.Substring(0, 2);

  if (AI == "01")
  {
   // Global Trade Item Number (GTIN)
   // N2 N14
   pAi01 = DataContent.Substring(2, 14);
   if (checkSum(pAi01.Remove(pAi01.Length - 1, 1), Int32.Parse(pAi01.Substring(pAi01.Length - 1, 1))))
   {
   }
   else
   {
    pAi17 = string.Empty;
   }
   DataContent = DataContent.Substring(16);
  }
  else if (AI == "17")
  {
   // Expiration Date (YYMMDD) 
   // N2 N6
   pAi17 = DataContent.Substring(2, 6);
   if (checkDate(pAi17) > DateTime.MinValue)
   {
   }
   else
   {
    pAi17 = string.Empty;
   }
   DataContent = DataContent.Substring(8);
  }
  else if (AI == "10")
  {
   // Batch or Lot Number 
   // N2 X..20
   pAi10 = DataContent.Length < 20 ? DataContent.Substring(2, DataContent.Length - 2) : DataContent.Substring(2, 20);
   DataContent = DataContent.Substring(pAi10.Length   2);
  }
 }

}

private Boolean checkSum(String pgtin, Int32 pchecksum)
{
 Boolean ret = false;
 Int32 glength = 0;
 Int32 total = 0;
 Int32 cSum = 0;
 Int32[] mutiply = { 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3 };
 glength = 17 - pgtin.Length;
 for (int i = 0; i < pgtin.Length; i  )
 {
  total = total   (Int32.Parse(pgtin[i].ToString()) * mutiply[i   glength]);
 }
 cSum = 10 - (total % 10);
 if (cSum == pchecksum)
 {
  ret = true;
 }
 return ret;

}

private DateTime checkDate(string pdate)
{
 DateTime ret = DateTime.MinValue;
 DateTime convertedDate = DateTime.MinValue;
 String dFormat = "yyMMdd";
 if (DateTime.TryParseExact(pdate, dFormat, System.Globalization.CultureInfo.CurrentCulture, System.Globalization.DateTimeStyles.None, out convertedDate))
 {
  ret = convertedDate;
 }
 return ret;
}

private void GS1DataConvert(string pBarcode, out string pAi01, out string pAi17, out string pAi10)
{
 pAi01 = string.Empty;
 pAi17 = string.Empty;
 pAi10 = string.Empty;

 String aiFull = "";
 String aiWCheckSum = "";
 String aiValue = "";
 Int32 aiCheckSum = 0;
 Int32 aiMinLength = 0;
 Int32 aiMaxLength = 0;
 int index = 0;
 if (pBarcode.Contains("01"))
 {
  index = pBarcode.IndexOf("01")   2;
  AII sai = getAiInfo("01");
  aiMinLength = sai.minLength;
  aiMaxLength = sai.maxLength;

  aiFull = pBarcode.Substring(index - 2, aiMaxLength   2);
  aiWCheckSum = pBarcode.Substring(index, aiMaxLength);
  aiValue = aiWCheckSum.Remove(aiWCheckSum.Length - 1, 1);
  aiCheckSum = Int32.Parse(aiWCheckSum.Substring(aiWCheckSum.Length - 1, 1));
  if (checkSum(aiValue, aiCheckSum))
  {
   pBarcode = pBarcode.Replace(aiFull, String.Empty);
   pAi01 = aiValue;
  }
 }
 if (pBarcode.Contains("17"))
 {
  index = pBarcode.IndexOf("17")   2;
  AII sai = getAiInfo("17");
  aiFull = pBarcode.Substring(index - 2, sai.minLength   2);
  aiValue = pBarcode.Substring(index, sai.minLength);
  if (checkDate(aiValue) > DateTime.MinValue)
  {
   pBarcode = pBarcode.Replace(aiFull, String.Empty);
   pAi17 = aiValue;
  }
 }
 if (pBarcode.Contains("10"))
 {
  index = pBarcode.IndexOf("10")   2;
  AII sai = getAiInfo("10");
  aiMinLength = sai.minLength;
  aiMaxLength = pBarcode.Length < sai.maxLength ? pBarcode.Length - 2 : sai.maxLength;
  aiFull = pBarcode.Substring(index - 2, aiMaxLength   2);
  aiValue = pBarcode.Substring(index, aiMaxLength);
  pAi10 = aiValue;
 }

}

public AII getAiInfo(String pAi)
{
 AII naii = new AII();
 if (pAi == "01")
 {
  naii.AICode = "01";
  naii.minLength = 14;
  naii.maxLength = 14;
  return naii;
 }
 if (pAi == "17")
 {
  naii.AICode = "17";
  naii.minLength = 6;
  naii.minLength = 6;
  return naii;
 }
 if (pAi == "10")
 {
  naii.AICode = "10";
  naii.minLength = 1;
  naii.maxLength = 20;
 }

 return naii;
}

public struct AII
{
 public String AICode;
 public Int32 minLength;
 public Int32 maxLength;
}
四、參考資料
[1] Free Barcode Font - Barcode String Builder
[2] Barcode Image Generation Library
[3] EAN128 or GS1-128 decode c#

2014年11月5日 星期三

健保e化抽審-2(WIA:Scan to PDF)

上次是利用列印的方法轉換成數位化檔案(PDF),這次則是使用掃瞄的方式來進行。利用WIA(Windows Image Acquisition)的掃瞄方式,可以將傳統的醫療單張掃瞄進來。
private void btnSacnToPDF_Click(object sender, EventArgs e)
{
 iTextSharp.text.Document document = new iTextSharp.text.Document(iTextSharp.text.PageSize.A4, 0, 0, 0, 0);
 try
 {
  List<string> devices = YGH.WIAScanner.GetDevices();
  if (devices.Count == 0)
  {
   throw new Exception("devices == 0");
  }

  List<System.Drawing.Image> images = YGH.WIAScanner.Scan(devices[0].Split('|')[0]);
  if (images.Count == 0)
  {
   throw new Exception("images == 0");
  }

  string fileName;
  // 方法1: 寫死暫存檔案名
  fileName = @"C:\temp\scan.pdf";
  // 方法2: 利用 System.IO.Path.GetTempPath() 及 Guid.NewGuid() 取得暫存檔案名
  //fileName = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".pdf";
  // 方法3: 使用 System.IO.Path.GetTempFileName() 建立暫存檔案(副檔名為 .TMP)
  //fileName = System.IO.Path.GetTempFileName();

  PdfWriter.GetInstance(document, new FileStream(fileName, FileMode.Create));
  document.Open();

  foreach (System.Drawing.Image image in images)
  {
   iTextSharp.text.Image img = iTextSharp.text.Image.GetInstance(imageToByteArray(image));
   
   // 方法1: 跟 HP 內建掃瞄程式相比有明顯差異
   //img.ScaleToFit(document.PageSize.Width, document.PageSize.Height);

   // 方法2: 跟 HP 內建掃瞄程式相比差異較小
   img.SetAbsolutePosition(0, 0);
   img.ScalePercent(72.0F / img.DpiX * 100);

   // 方法3: 跟 HP 內建掃瞄程式相比有明顯差異
   //img.SetAbsolutePosition(0, 0);
   //img.ScaleAbsoluteWidth(document.PageSize.Width);
   //img.ScaleAbsoluteHeight(document.PageSize.Height);

   document.Add(img);
   document.NewPage();
  }
 }
 catch (Exception ex)
 {
  Console.WriteLine(ex.Message);
 }
 finally
 {
  document.Close();
 }
}

/// <summary>
/// C# Image to Byte Array and Byte Array to Image Converter Class
/// http://www.codeproject.com/Articles/15460/C-Image-to-Byte-Array-and-Byte-Array-to-Image-Conv
/// </summary>
/// <param name="imageIn"></param>
/// <returns></returns>
public byte[] imageToByteArray(System.Drawing.Image imageIn)
{
 MemoryStream ms = new MemoryStream();
 imageIn.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
 return ms.ToArray();
}

/// <summary>
/// C# Image to Byte Array and Byte Array to Image Converter Class
/// http://www.codeproject.com/Articles/15460/C-Image-to-Byte-Array-and-Byte-Array-to-Image-Conv
/// </summary>
/// <param name="byteArrayIn"></param>
/// <returns></returns>
public System.Drawing.Image byteArrayToImage(byte[] byteArrayIn)
{
 MemoryStream ms = new MemoryStream(byteArrayIn);
 System.Drawing.Image returnImage = System.Drawing.Image.FromStream(ms);
 return returnImage;
}
參考資料
[1] WIA Scanner in C# Windows Forms
[2] C# Image to Byte Array and Byte Array to Image Converter Class

2014年11月3日 星期一

健保e化抽審-1(PDFCreator:Print to PDF)

為了將傳統的報表轉換為數位的電子檔(PDF),我們將下戴並安裝PDFCreator,進而建立可以產生PDF檔的虛擬印表機。日後在列印報表時,我們可以使用此虛擬印表機來產生PDF檔,那我們就不用花太多的心力就可以簡單完成檔案數位的第一步。
PS~注意!若要整合在 Visual Studio 開發工具,1.3.2 及 1.4.0 版在中文檔名處理上有問題,會產生空白內容的 PDF 檔,目前測試 1.2.3 相容性較好。

1. 記得勾選"Expert setting",只安裝你所需要的軟體就好。


2. 取消安裝"PDFArchitect"


3. 記得看到這個畫面要按"cancel",不然又會幫你安裝一些軟體進來。


4. 參考資料
[1] PDFCreator
[2] 易普印之部落格 (e知識百科)~[免費] PDFCreator v1.9.5 正式版

2014年10月23日 星期四

診間報到系統-3(FileSystemWatcher)

由於診間報到系統,護士瑞及電視牆兩隻程式需要交換資料去觸發畫面更新事件。過去,常用timer設定long loop的方式來取得特定目錄的檔案更新,現在則打算用FileSystemWatcher的事件觸發來執行。一方面提昇反應時間,另一方面也是自我提昇。
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 ' 1.引用類別
 Dim watcher As New FileSystemWatcher()
 ' 2.設定監聽路徑
 watcher.Path = "C:\Temp"
 ' 3.過濾監聽條件
 watcher.Filter = "*.txt"
 ' 4.設定監看類型
 watcher.NotifyFilter = NotifyFilters.FileName
 ' 5.設定監聽項目
 AddHandler watcher.Created, AddressOf watcher_Created
 ' 6.開啟監聽事件
 watcher.EnableRaisingEvents = True
End Sub

Private Shared locker As Object = New [Object]()
Private Sub watcher_Created(ByVal sender As Object, ByVal e As FileSystemEventArgs)
 SyncLock locker
  Dim file As New FileInfo(e.FullPath)
            Dim intCounter As Integer = 0
            Do While file.IsLocked()
                System.Threading.Thread.Sleep(100)
                intCounter += 1
                If intCounter >= 100 Then Exit Sub
            Loop
   
   string readText = file.ReadAllText();
 End SyncLock
End Sub

Module clsExtension

    <Extension()> _
    Public Function IsLocked(ByVal file As FileInfo) As Boolean
        Dim stream As FileStream = Nothing

        Try
            stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None)
        Catch ex_IsLocked As IOException
            'the file is unavailable because it is:
            'still being written to
            'or being processed by another thread
            'or does not exist (has already been processed)
            Return True
        Finally
            If stream IsNot Nothing Then
                stream.Close()
            End If
        End Try

        'file is not locked
        Return False
    End Function

    <Extension()> _
    Public Function ReadAllText(ByVal file As FileInfo) As String
        Try
            Using reader As StreamReader = file.OpenText()
                Return reader.ReadToEnd()
            End Using
        Catch ex_ReadAllText As Exception
            Return Nothing
        End Try
    End Function

End Module

參考資料
[1] [VB.NET][C#.NET] 使用 FileSystemWatcher 監看資料夾 是否被變更
[2] Wait Until File Is Completely Written

2014年10月21日 星期二

健保e化抽審-1(PDF Merge & Bookmark)

private void button17_Click(object sender, EventArgs e)
{
  Dictionary<PrintDocument, byte[]> myFiles = new Dictionary<PrintDocument, byte[]>();

  myFiles.Add(new PrintDocument { Title = "title1", ChapterName = "chapterName1", ChapterNumber = 1 }, File.ReadAllBytes(@"C:\temp\title1.pdf"));
  myFiles.Add(new PrintDocument { Title = "title2", ChapterName = "chapterName1", ChapterNumber = 1 }, File.ReadAllBytes(@"C:\temp\title2.pdf"));
  myFiles.Add(new PrintDocument { Title = "title3", ChapterName = "chapterName1", ChapterNumber = 1 }, File.ReadAllBytes(@"C:\temp\title3.pdf"));

  File.WriteAllBytes(@"C:\temp\test.pdf", MergeFilesAndAddBookmarks(myFiles));
}

public static byte[] MergeFilesAndAddBookmarks(Dictionary<PrintDocument, byte[]> sourceFiles)
{
  using (var ms = new MemoryStream())
  {
    using (var document = new iTextSharp.text.Document())
    {
      using (var copy = new iTextSharp.text.pdf.PdfCopy(document, ms))
      {
        //Order the files by chapternumber
        var files = sourceFiles.GroupBy(f => f.Key.ChapterNumber);

        document.Open();
        
        var outlines = new List<Dictionary<string, object>>();

        var pageIndex = 1;

        foreach (var chapterGroup in files)
        {
          var map = new Dictionary<string, object>();
          outlines.Add(map);
          map.Add("Title", chapterGroup.First().Key.ChapterName);
          var kids = new List<Dictionary<string, object>>();
          map.Add("Kids", kids);

          foreach (var sourceFile in chapterGroup)
          {
            using (var reader = new iTextSharp.text.pdf.PdfReader(sourceFile.Value))
            {
              //Add the pages
              var n = reader.NumberOfPages;

              for (var page = 0; page < n;)
              {
                if (page == 0)
                {
                  var kid = new Dictionary<string, object>();
                  kids.Add(kid);
                  kid["Title"] = sourceFile.Key.Title;
                  kid["Action"] = "GoTo";
                  kid["Page"] = String.Format("{0} Fit", pageIndex);
                }
                copy.AddPage(copy.GetImportedPage(reader, ++page));
              }

              pageIndex += n;
              reader.Close();
            }
          }
        }

        copy.Outlines = outlines;
        document.Close();
        copy.Close();
        ms.Close();
      }
    }
    return ms.ToArray();
  }
}

public class PrintDocument
{
  public string Title { get; set; }
  public string ChapterName { get; set; }
  public int ChapterNumber { get; set; }
}
參考資料
[1] 使用ASP .NET (C#) 產生PDF檔的好幫手—iTextSharp library (上)
[2] 使用ASP .NET (C#) 產生PDF檔的好幫手—iTextSharp library (下)

2014年10月19日 星期日

[MobileWeb]網路掛號Prototype-2(Listview Autodivider搭配JSON)

HTML更新如下
回上頁

查詢時間

JavaScript
$(document).ready(function () {
    var jsonData = [
        {"醫師": "柯X哲", "科別": "外科", "時段": "上午(08:30~11:30)"},
        {"醫師": "邱X逹", "科別": "外科", "時段": "下午(13:30~17:00)"},
        {"醫師": "沈X雄", "科別": "內科", "時段": "上午(08:30~11:30)"},
        {"醫師": "賴X德", "科別": "內科", "時段": "上午(08:30~11:30)"},
        {"醫師": "柯X銘", "科別": "牙科", "時段": "上午(08:30~11:30)"},
        {"醫師": "涂X哲", "科別": "內科", "時段": "夜診(08:30~11:30)"}
    ];
    
    var targetList = $("#查詢時間List");
    
    if (jsonData) {
        var dataLength = jsonData.length;
        for (var i = 0; i < dataLength; i++) {
            targetList.append("
  • " + jsonData[i].醫師 + "

    " + jsonData[i].科別 + "

  • "); } } targetList.listview({ autodividers: true, autodividersSelector: function ( li ) { return li.data("時段"); } }).listview("refresh"); });

    ※ 原始碼及執行預覽(jsFiddle): http://jsfiddle.net/ez0brabf/11/

    2014年10月15日 星期三

    [MobileWeb]網路掛號Prototype-1(jQuery Mobile 框架)

    我們運用jQuery Mobile,開發以行動裝置所使用的MobileWeb,不但可以簡化及加速UI的設計與開發,專注於程式功能及流程上的開發及整合,也可以減少技能重新學習的成本。

    基本的原型的框架如下~
    HTML
    
    
    
    回上頁

    查詢科別

    回上頁

    查詢時間

    Page content goes here.

    Page Footer

    回上頁

    查詣 / 取消掛號

    Page content goes here.

    Page Footer

    回上頁

    語音掛號

    Page content goes here.

    Page Footer


    CSS
    .noshadow * {
      -webkit-box-shadow: none !important;
      -moz-box-shadow: none !important;
      box-shadow: none !important;
    }
    

    ※ 原始碼及執行預覽(jsFiddle): http://jsfiddle.net/ez0brabf/8/

    2014年10月10日 星期五

    設定SyntaxHighlighter讓Blogger可以貼上程式碼

    最近開始在Blogger上貼程式碼,但又希望它呈現的方式跟一般IDE介面一樣,Google了許久,最後選定了SyntaxHighlighter做為解決方案。參考官網上的安裝方式,開啟Blogger的範本,按下編輯HTML~

    將以下程式碼加入在"</head>"之前
    將以下程式碼加入在"</body>"之前
    
    
    

    建立新文章時以HTML頁面鍵入"<pre></pre>",中間貼入置你的程式碼,以下是我常用的類型~
    類型1:
    <pre class="brush: csharp">
    ...your code
    </pre>

    類型2:
    <pre class="brush: vbnet">
    ...your code
    </pre>

    類型3:
    <script type="syntaxhighlighter" class="brush: javascript; html-script: true"><![CDATA[
    ...your code
    ]]></script>

    類型4:
    <pre class="brush: javascript; html-script: true">
    ...your code
    </pre>

    2014年10月9日 星期四

    [Security Alert: Apache Cordova vulnerabilities in your Google Play app]在PhoneGap找不到cordova-3.5.1.jar

    我的第一個Android APP上架後就再沒有更新它,沒想到Google寄了因為安全問題必須升級Apache Cordova的一封信給我。快二年沒有研究它了,沒想到已經不太一樣!只是需要3.5.1的Apache Cordova就搞了好幾天,還是記錄一下免得忘光光。

    一、作業系統環境需求
    1.1. JDK 6(設定 Java 環境)
    1.1.1. 安裝jdk-6u45-windows-i586.exe
    1.1.2. 設定Path(加入C:\Program Files\Java\jdk1.6.0_45\bin)




     1.1.3. 設定JAVA_HOME(C:\Program Files\Java\jdk1.6.0_45)


    1.2 Apache Ant 1.8 or later
    (Eclipse ADT Bundle已內建,待會將路徑加入Path)

    1.3 解壓Eclipse ADT Bundle
    (D:\My Apps\adt-bundle-windows-x86-20140702)

    1.4. 設定Path,加入以下路徑
    D:\My Apps\adt-bundle-windows-x86-20140702\sdk\tools
    D:\My Apps\adt-bundle-windows-x86-20140702\sdk\platform-tools
    D:\My Apps\adt-bundle-windows-x86-20140702\eclipse\plugins\org.apache.ant_1.8.3.v201301120609\bin


    1.5. 設定Android SDK Manager


    1.6 設定Android Virtual Device (AVD) Manager


    二、安裝Apache Cordova
    2.1. 安裝Node.js(http://nodejs.org/)

    2.2. 安裝Git(http://git-scm.com/)


    2.3. 用npm(Node.js Package Manager)下載Apache Cordova
    npm install -g cordova

    三、制作cordova.jar及更新cordova.js
    3.1. 建立新專案
    cd D:\workspace
    cordova create YGHApp tw.com.ygh.yghapp YGHApp

    3.2 加入作業系統平台
    cd D:\workspace\YGHApp
    cordova platform add android
    cordova platform ls

    3.3制作jar檔
    cd C:\Users\HPC\.cordova\lib\android\cordova\3.5.1\framework
    android update project -p .
    ant jar

    四、參考資料
    [1] Upgrading Cordova Android
    [2] Upgrading from 2.9 to 3.x

    2014年9月29日 星期一

    健保雲端藥歷系統-3(WatiN、webBrowser)

    改寫了一版內嵌 webBrower 的版本。由於在同一個 process 上去取得 webBrower 的控制項會有問題,所以開另外一個 thread 搭配匿名函式(Anonymous Functions)去執行 ~

    public partial class Form1 : Form
    {
        ...
    
        delegate void UpdateDataGridViewDataSourceDelegate(DataGridView dgv, DataTable dt);
        private void UpdateDataGridViewDataSource(DataGridView dgv, DataTable dt)
        {
            ...
        }
    
        private void btnEmbed健保雲端藥歷系統_Click(object sender, EventArgs e)
        {
            try
            {
                btnEmbed健保雲端藥歷系統.Enabled = false;
    
                var thread = new Thread(() =>
                {
                    baseTime = DateTime.Now;
    
                    if (WatiN.Core.Settings.AutoStartDialogWatcher)
                    {
                        WatiN.Core.Settings.AutoStartDialogWatcher = false;
                    }
    
                    WatiN.Core.IE __windowIE = new WatiN.Core.IE(webBrowser1.ActiveXInstance);
                    if (webBrowser1.Url == null)
                    {
                        __windowIE.GoTo("https://10.253.253.245/imme0000/IMME0002S01.aspx");
                    }
                    else
                    {
                        WatiN.Core.Button ContentPlaceHolder1_btnReSrc = __windowIE.Button("ContentPlaceHolder1_btnReSrc");
                        if (ContentPlaceHolder1_btnReSrc.Exists)
                        {
                            ContentPlaceHolder1_btnReSrc.Click();
                        }
                    }
    
                    #region busy and timeout control
                    while (((SHDocVw.InternetExplorer)(__windowIE.InternetExplorer)).Busy)
                    {
                        Application.DoEvents();
                        System.Threading.Thread.Sleep(3000);
    
                        runTime = DateTime.Now - baseTime;
                        if (runTime > timeOut) break;
                    }
                    #endregion
    
                    while (true)
                    {
                        #region https://10.253.253.245/imme0000/IMME0002S01.aspx
                        if (__windowIE.Url == entryURL)
                        {
                            WatiN.Core.Span ContentPlaceHolder1_lblmsg = __windowIE.Span("ContentPlaceHolder1_lblmsg");
                            if (ContentPlaceHolder1_lblmsg.Exists)
                            {
                                MessageBox.Show(ContentPlaceHolder1_lblmsg.OuterText);
                                break;
                            }
                        }
                        #endregion
    
                        #region https://10.253.253.245/imme0000/IMME0002S02.aspx
                        if (__windowIE.Url == targetURL)
                        {
                            WatiN.Core.Table ContentPlaceHolder1_gvList = __windowIE.Table("ContentPlaceHolder1_gvList");
                            if (ContentPlaceHolder1_gvList.Exists)
                            {
                                DataTable dt = gvListHtml2DT(ContentPlaceHolder1_gvList);
    
                                if (dataGridView1.InvokeRequired)
                                {
                                    dataGridView1.Invoke(new UpdateDataGridViewDataSourceDelegate(UpdateDataGridViewDataSource), new object[] { dataGridView1, dt });
                                }
                                else
                                {
                                    UpdateDataGridViewDataSource(dataGridView1, dt);
                                }
    
                                break;
                            }
    
                            WatiN.Core.Span ContentPlaceHolder1_lblmsg = __windowIE.Span("ContentPlaceHolder1_lblmsg");
                            if (ContentPlaceHolder1_lblmsg.Exists && ContentPlaceHolder1_lblmsg.OuterText.Contains("查無資料"))
                            {
                                if (dataGridView1.InvokeRequired)
                                {
                                    dataGridView1.Invoke(new UpdateDataGridViewDataSourceDelegate(UpdateDataGridViewDataSource), new object[] { dataGridView1, new DataTable() });
                                }
                                else
                                {
                                    UpdateDataGridViewDataSource(dataGridView1, new DataTable());
                                }
    
                                MessageBox.Show(ContentPlaceHolder1_lblmsg.OuterText);
                                break;
                            }
                        }
                        #endregion
    
                        Application.DoEvents();
                        System.Threading.Thread.Sleep(1000);
    
                        runTime = DateTime.Now - baseTime;
                        if (runTime > timeOut) break;
                    }
                });
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
              
            }
            catch (Exception ex)
            {
                Debug.Print(ex.Message);
            }
            finally
            {
                btnEmbed健保雲端藥歷系統.Enabled = true;
            }
        }
    
        private DataTable gvListHtml2DT(WatiN.Core.Table table)
        {
            ...
        }
    
    } 
    

    健保雲端藥歷系統-2(WatiN)

    如果你不希望看到IE,那就呼叫前先設定 WatiN.Core.Settings.MakeNewIeInstanceVisible 為 false。

    if (WatiN.Core.Settings.MakeNewIeInstanceVisible)
        WatiN.Core.Settings.MakeNewIeInstanceVisible = false;
    

    健保雲端藥歷系統-1(WatiN)

    衛生福利部的送的"大禮" ~ 健保雲端藥歷系統
    經過各同仁一番努力之後,看來不得不將這套系統整合到自己的HIS內,所以參考了WatiN這套網頁測試工具,效果不錯但仍有些非同步的網頁的狀態判斷仍不夠好(只能拉長等待秒數,避免判斷錯誤)!

    public partial class Form1 : Form
    {
        private WatiN.Core.IE windowIE = null;
        private string entryURL = "https://10.253.253.245/imme0000/IMME0002S01.aspx";
        private string targetURL = "https://10.253.253.245/imme0000/IMME0002S02.aspx";
        private DateTime baseTime;
        private TimeSpan runTime;
        private TimeSpan timeOut = TimeSpan.FromSeconds(90);
    
        private void btnShow健保雲端藥歷系統_Click(object sender, EventArgs e)
        {
            try
            {
                StartCounting();
    
                baseTime = DateTime.Now;
    
                if (windowIE != null)
                {
                    WatiN.Core.Button ContentPlaceHolder1_btnReSrc = windowIE.Button("ContentPlaceHolder1_btnReSrc");
                    if (ContentPlaceHolder1_btnReSrc.Exists)
                    {
                        ContentPlaceHolder1_btnReSrc.Click();
                    }
                }
                else
                {
                    windowIE = new WatiN.Core.IE(entryURL);
                    windowIE.WaitForComplete((int)timeOut.TotalSeconds);
                }
    
                #region busy and timeout control
                while (((SHDocVw.InternetExplorer)(windowIE.InternetExplorer)).Busy)
                {
                    Application.DoEvents();
                    System.Threading.Thread.Sleep(3000);
    
                    runTime = DateTime.Now - baseTime;
                    if (runTime > timeOut) break;
                }
                #endregion
    
                while (true)
                {
                    #region https://10.253.253.245/imme0000/IMME0002S01.aspx
                    if (windowIE.Url == entryURL)
                    {
                        WatiN.Core.Span ContentPlaceHolder1_lblmsg = windowIE.Span("ContentPlaceHolder1_lblmsg");
                        if (ContentPlaceHolder1_lblmsg.Exists)
                        {
                            MessageBox.Show(ContentPlaceHolder1_lblmsg.OuterText);
                            break;
                        }
                    }
                    #endregion
    
                    #region https://10.253.253.245/imme0000/IMME0002S02.aspx
                    if (windowIE.Url == targetURL)
                    {
                        WatiN.Core.Table ContentPlaceHolder1_gvList = windowIE.Table("ContentPlaceHolder1_gvList");
                        if (ContentPlaceHolder1_gvList.Exists)
                        {
                            if (dataGridView1.DataSource != null)
                            {
                                dataGridView1.DataSource = null;
                            }
                            else
                            {
                                dataGridView1.Rows.Clear();
                            }
                            dataGridView1.Refresh();
                            dataGridView1.DataSource = gvListHtml2DT(ContentPlaceHolder1_gvList);
                            break;
                        }
    
                        WatiN.Core.Span ContentPlaceHolder1_lblmsg = windowIE.Span("ContentPlaceHolder1_lblmsg");
                        if (ContentPlaceHolder1_lblmsg.Exists && ContentPlaceHolder1_lblmsg.OuterText.Contains("查無資料"))
                        {
                            if (dataGridView1.DataSource != null)
                            {
                                dataGridView1.DataSource = null;
                            }
                            else
                            {
                                dataGridView1.Rows.Clear();
                            }
                            dataGridView1.Refresh();
                            MessageBox.Show(ContentPlaceHolder1_lblmsg.OuterText);
                            break;
                        }
                    }
                    #endregion
    
                    Application.DoEvents();
                    System.Threading.Thread.Sleep(1000);
    
                    runTime = DateTime.Now - baseTime;
                    if (runTime > timeOut) break;
                }
            }
            catch (Exception ex)
            {
                Debug.Print(ex.Message);
                if (windowIE != null)
                {
                    windowIE.Dispose();
                }
            }
            finally
            {
                StopCounting();
            }
        }
    
        private DataTable gvListHtml2DT(WatiN.Core.Table table)
        {
            DataTable dt = new DataTable();
    
            WatiN.Core.TableRowCollection rows = table.TableRows;
            WatiN.Core.TableRow row = rows[0];
            WatiN.Core.ElementCollection cols = row.Elements;
    
            StringCollection RowValues = new StringCollection();
            foreach (WatiN.Core.Element col in cols)
            {
                if (col.TagName == "TH") RowValues.Add(col.OuterText);
            }
    
            for (int j = 0; j < RowValues.Count; j++)
            {
                dt.Columns.Add(RowValues[j], Type.GetType("System.String"));
            }
    
            if (rows.Count > 1)
            {
                for (int i = 1; i < rows.Count; i++)
                {
                    row = rows[i];
                    cols = row.Elements;
    
                    dt.Rows.Add();
                    for (int j = 0; j < RowValues.Count; j++)
                    {
                        dt.Rows[i - 1][j] = cols[j].OuterText;
                    }
                }
            }
    
            return dt;
        }
    
        private void StartCounting()
        {
            ...
        }
    
        private void StopCounting()
        {
            ...
        }
    
    }
    

    2014年9月28日 星期日

    診間報到系統-2(BackgroundWorker)

    由於我們報到系統的USB延長線有10米長,很容易就需要重新插拔USB接頭,間接daemon的程式就很容易死掉。所以,打算改寫daemon的程式,希望能多一些錯誤控制但又不想用timer去寫程式(內心覺得有點low ~ 哈)。最後,用BackgroundWorker模擬像是daemon的運作方式 ~

    public class SmartCardLite
    {
        // 讀取健保卡基本資料
        public static void ManualSmartCardDetection()
        {
            ...
        }
    
        private static BackgroundWorker _worker;
    
        public static void StartAutoSmartCardDetection()
        {
            _worker = new BackgroundWorker();
            _worker.WorkerSupportsCancellation = true;
            _worker.DoWork += WaitChangeStatus;
            _worker.RunWorkerAsync();
        }
    
        public static void StopAutoSmartCardDetection()
        {
            _worker.CancelAsync();
            _worker.Dispose();
        }
    
        private static void WaitChangeStatus(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bwAsync = sender as BackgroundWorker;
    
            while (!bwAsync.CancellationPending)
            {
                ManualSmartCardDetection();
    
                Thread.Sleep(5000);
            }
        }
    
    }
    

    診間報到系統-1(DoubleBuffered)

    叫號時閃爍功能不夠順暢!開啟控制項的DoubleBuffered

    Public Sub New()
    
        ' 此為 Windows Form 設計工具所需的呼叫。
        InitializeComponent()
    
        ' 在 InitializeComponent() 呼叫之後加入任何初始設定。
        Me.DoubleBuffered = True
        Me.SetStyle(ControlStyles.UserPaint Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.OptimizedDoubleBuffer, True)
        Me._PropertyInfo = Me.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)
        SearchControl(Me)
    
    End Sub
    
    Sub SearchControl(ByVal Ctrl As System.Windows.Forms.Control)
    
        For Each rootCtrl As System.Windows.Forms.Control In Ctrl.Controls
            Me._PropertyInfo.SetValue(rootCtrl, True, Nothing)
            If (rootCtrl.HasChildren) Then
                SearchControl(rootCtrl)
            End If
        Next
    
    End Sub