2010年12月28日 星期二

在C#中讀取Excel的試算表名稱

在將Excel的檔案當做資料庫的存取的時候
可以先取得檔案中每個試算表的名稱,再做處理

public List LoadExcelSheetNameList(
string excelFile)
{
List sheetNameList = new List();

/// load excel file as an ole database
OleDbConnection excelDatabase =
new OleDbConnection(string.Format(
"Provider = Microsoft.Jet.OLEDB.4.0;" +
"Data Source = {0};" +
"Extended Properties='Excel 8.0;HDR=No'",
excelFile));
excelDatabase.Open();

/// load excel sheet as DataRow
DataRow[] sheetList =
excelDatabase.GetSchema("Tables").Select();
foreach (DataRow sheet in sheetList)
{
/// query each sheet name from excel file
sheetNameList.Add(
sheet["TABLE_NAME"] as string);
}

/// close and release database connection
excelDatabase.Close();

return sheetNameList;
}

在得到名稱後,就可以針對單一的試算表進行讀取

--
參考資料
[工作] 取得Excel頁籤名稱
如何取得 Excel 中的 Sheet Name

2010年12月27日 星期一

在C#中使用SQLite資料庫

自己開發程式的時候,想用資料庫又不希望花一大筆錢
常常會使用SQLite,一個輕量級的嵌入式資料庫系統(僅單一檔案)
由於.net原生並不支援SQLite
可以去System.Data.SQLite找到在公開的函式庫
注意如果是x64的程式,則需要使用x64版本的函式庫,無法混用

在每次使用資料庫的時候,都需要建立與相關資料庫的連結

private SQLiteConnection ConnectDatabase()
{
string path = "Test.db";
string password = "Password";
SQLiteConnection connection;

if (!File.Exists(_databasePath))
{
/// for non-exist database, create it
SQLiteConnection.CreateFile(path);
connection = new SQLiteConnection(
"Data Source = " + path);
connection.Open();
connection.ChangePassword(password);
}
else
{
/// for exist database, connect it
connection = new SQLiteConnection(
"Data Source = " + path);
connection.SetPassword(password);
connection.Open();
}

return connection;
}

這邊要注意的是,由於SQLite本身並沒有支援資料庫加密
有關Password的功能(紅字部分)是System.Data.SQLite實做的
所以製作出來的加密資料庫並不能被其他的SQLite應用讀取
(例如Firefox的附加元件SQLite Manager)
所以如果不打算加密,建議將紅字部分拿掉

在連結之後,就可以透過該連結,執行SQL的命令

private void RunCommand(
SQLiteConnection connection, string commandText)
{
SQLiteCommand command =
new SQLiteCommand(connection);
command.CommandText = commandText;
command.ExecuteNonQuery();
}

除了不需要回傳值的命令(例如CREATE TABLE)以外
SQL最重要的當然是Query的指令

private List RunQueryCommand(
SQLiteConnection connection, string commandText)
{
List<string> result = new List<string>();

SQLiteCommand command =
new SQLiteCommand(connection);
command.CommandText = commandText;

using (SQLiteDataReader queryResult =
command.ExecuteReader())
{
/// read one row one time
while (queryResult.Read())
{
/// change index to get different column
result.Add(queryResult.GetValue(0)
.ToString());
}
}

return result;
}

當然,在使用完畢後一定要記得將連結中止,否則資料庫檔案會被鎖住

private void DisconnectDatabase(
SQLiteConnection connection)
{
connection.Close();
}

另外,讀取資料庫的時候,如果不能肯定資料表是否存在
則需要先做一個檢查,否則會跳出例外狀況
(當然也是可以直接用try catch將Command包住,端看習慣)

private bool IsTableExist(
SQLiteConnection connection, string name)
{
return connection.GetSchema("Tables")
.Select("Table_Name = '" + name + "'")
.Length > 0;
}

--
參考資料
SQLite 簡介
System.Data.SQLite
C sharp or .Net 使用sqlite 設定
SQLite Manager
SQL語法教學
Check if table exists

Windows的顯示/隱藏桌面

在Windows XP預設的快速啟動列中會有一個捷徑,可以顯示/隱藏桌面
(隱藏/顯示所有已經開啟的視窗)
在XP的時候有時候會不小心砍掉(Win7中已經被放在視窗的右下角,反正也移除不掉,就不會有這個困擾),這時候可以自己重新做一個捷徑

先開一個純文字檔,內容如下

[Shell]
Command=2
IconFile=explorer.exe,3
[Taskbar]
Command=ToggleDesktop

並將檔案附檔名改為.scf(檔名倒是無所謂,一般習慣設成"ShowDesktop.scf"或"顯示桌面.scf")就可以了

如果想要在程式中做出同樣的功能,則可以呼叫Shell.Application去呼叫ToggleDesktop這個命令

void ShowDesktop()
{
Type shell =
Type.GetTypeFromProgID("Shell.Application");
shell.InvokeMember("ToggleDesktop",
BindingFlags.InvokeMethod, null,
Activator.CreateInstance(shell), null);
}

--
參考資料
How to re-create the Show desktop icon on the Quick Launch toolbar in Windows XP
C#使用系統的「顯示桌面」功能(Shell.Application)

2010年12月21日 星期二

在WPF中發出鍵盤事件

WPF沒有控制鍵盤的API
所以要發出鍵盤事件(例如按下"J")就必須要呼叫Win32的API了

private enum KeyEventFlag
{
KEYEVENTF_KEYDOWN = 0x0000,
KEYEVENTF_EXTENDEDKEY = 0x0001,
KEYEVENTF_KEYUP = 0x0002,
}

[DllImport("user32.dll", SetLastError = true)]
private static extern void keybd_event(
byte bVk, byte bScan, KeyEventFlag dwFlags,
IntPtr dwExtraInfo);

這時發生一個問題,bVk是以Win32中定義的Virtual-Key Codes做為對照
但是在WPF中所採用的是Key 列舉型別
需要使用KeyInterop做轉換,所以實際上發出按下鍵盤的函式如下

private void SendKeyDown(Key key)
{
keybd_event(
(byte)KeyInterop.VirtualKeyFromKey(key), 0,
KeyEventFlag.KEYEVENTF_KEYDOWN, IntPtr.Zero);
}

另外,如果要送出放開鍵盤的事件,那就把KEYEVENTF_KEYDOWN換成KEYEVENTF_KEYUP,而連續送出一個按下加上一個放開,就變成Click了
同理,要發出復合鍵則是再放開前按下所有鍵,再放開即可

--
參考資料
.NET - How can I convert 'System.Windows.Input.Key' to 'System.Windows.Forms.Keys'?

2010年9月3日 星期五

在C#中確認視窗是否是全螢幕

要確認一個視窗是否是全螢幕
可以直接比對視窗的大小是否與螢幕大小相同
因為如果不是全螢幕的視窗,那至少會有一部分被工具列佔走

public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}

[DllImport("user32.dll")]
private static extern int GetWindowRect(
IntPtr hWnd, out RECT lpRect);

bool IsFullScreen(IntPtr h)
{
/// get window bound from handle
RECT b;
GetWindowRect(h, out b);

/// get screen bound from handle
Rectangle r = Screen.FromHandle(h).Bounds;

/// check if the two size is the same
if ((b.bottom - b.top) == r.Height &&
(b.right - b.left) == r.Width)
{
return true;
}

return false;
}

注意Screen這個類別是宣告在System.Windows.Forms裡面
還有Rectangle是宣告在System.Drawing裡面
所以如果是WPF的程式,則需要另外新增參考

--
參考資料
GetWindowRect Function
RECT Structure
Screen.FromHandle 方法
Detect whether active window is full screen

在C#中取得前景視窗(ForegroundWindow)的應用程式名稱

如果在寫工具類的程式,有時會需要知道目前前景的視窗
(Foreground,也就是持有Focus的視窗)
這時候可以透過Win32的API來取得相關的資訊

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

[DllImport("user32.dll")]
public static extern int GetWindowThreadProcessId(
IntPtr hWnd, out int lpdwProcessId);

string GetForegroundApplicationName()
{
/// get foreground window handle
IntPtr h = GetForegroundWindow();

/// get foreground process from handle
int pId;
GetWindowThreadProcessId(h, out pId);
Process p = Process.GetProcessById(pId);

return p.ProcessName;
}

--
參考資料
GetForegroundWindow Function
GetWindowThreadProcessId Function
Process.GetProcessById 方法 (Int32)

2010年7月16日 星期五

查找註冊表的變動(WinDiff)

在寫Windows程式時,如果遇到比較冷門的功能
常常會遇到怎麼找都找不到API的狀況(甚至上msdn也沒人回應)
如果這時候你發現其實有手動的方式可以達到同樣的目的
(例如之前提過的DPI Virtualization)
那就可以考慮一下去比對註冊表的變動了

在Windows系統中幾乎所有的變動都會改變註冊表
在執行列打入regedit就可以看到一堆機碼
基本上真的無從找起

這時候就要用到WinDiff工具了
可以參考如何使用 Windiff.exe 公用程式
這個工具可以讓你比對兩個文字檔案,找到並瀏覽差異

1. 在做想要達到目的的操作前,先將註冊表匯出成文字檔
2. 做手動的操作
3. 重開註冊表,並匯出成另一個文字檔
4. 開啟WinDiff,從表單中選取File->CompareFiles...
並分別選取兩個文字檔
5. 如果兩個檔案相同,會顯示identical的訊息
6. 如果不同,會顯示紅字,雙擊紅字就會展開整份檔案
7. 首先會到第一筆差異處,利用下一筆(F8)或上一筆(F7)瀏覽

接下來就是用眼睛比對了
通常你會發現一些關鍵字出現,就是你要找的了
這裡有幾點需要注意的
1. 整份註冊表還蠻大的(100MB+是正常的)
可以考慮針對有關的註冊表查找,或是分成很多個表匯出
這樣可以節省比對的時間
2. 使用WinDiff比對完一對檔案後,最好是重開後再比對下一份
因為似乎還是會占用記憶體,所以會變慢很多
3. 一個動作可能會更動到註冊表的很多地方
就算已經找到,還是建議繼續搜索完其他的部分

在找到差異後,就可以透過編輯註冊表的API來做同樣的調整了
例如DPI Virtualization中的方法
不過這邊要強調,這種做法其實有一定的風險
(當然,如果只是檢查倒是無所謂,但是有修改就很難說了)
你有可能有漏掉沒有改到的地方(造成註冊表的關聯錯誤)
建議是先在測試的機器(可隨時重新安裝的電腦)上,手動做幾次
如果確定穩定而且達到目的的話再用程式去改

--
參考資料
如何使用 Windiff.exe 公用程式