2008年12月30日 星期二

[SQLite]新增資料至Boolean欄位發生錯誤!

今天新增一筆資料時出現錯誤
SQLite error
no such column: True

更正來源︰http://chriscraig.net/blog/sqlite-boolean-true-or-false/
INSERT INTO myTable (boolField) VALUES(true); (Failed)

INSERT INTO myTable (boolField) VALUES('true'); (Successed)

[SQLite]字串未被辨認為有效的 DateTime?

明明只是從 資料庫 Select 資料出來而已,尚未作任何的資料處理。
為何會出現 Exception 呢?
public void SelectTable()
{
 string strCommand = "SELECT * FROM Table1";
 SQLiteConnection myConnection = new SQLiteConnection(strConnection);
 SQLiteCommand myCommand = new SQLiteCommand(strCommand, myConnection);
 
 DataTable table = new DataTable();
 try
 {
  myConnecion.Open();
  table.Load(myCommand.ExecuteReader()); //在這行跳出 Exception
 }
 catch(Exception ex){ throw new Exception(ex.Message); }
 finally{ myConnection.Close();}
}
這沒可能啊!! command如此的單純,怎麼還會出錯呢?
trace code 後發現原因出在 table裡面的內容錯誤!!
不會吧 怎麼會有 Insert 成功 Select 失敗的事呢?
我也不知道 實在是苦惱啊!

以下是原來 Insert 一筆資料的方法︰
public void InsertData()
{
 string strCommand = "INSERT Table1(Start_Time) VALUES('2008/12/30 23:59:59')";
 SQLiteConnection myConnection = new SQLiteConnection(strConnection);
 SQLiteCommand myCommand = new SQLiteCommand(strCommand, myConnection);
 
 try
 {
  myConnecion.Open();
  int count = myCommand.ExecuteNonQuery(); //count 為1表示資料新增成功
 }
 catch(Exception ex) { throw new Exception(ex.Message); }
 finally { myConnection.Close();}
}
可是用上面的寫法,雖然可以新增成功,但資料會讀不出來!!
(原本使用於MySQL上面讀寫皆正常)

下面用Parameter的方式改寫︰
public void InsertData()
{
 string strCommand = "INSERT Table1(Start_Time) VALUES(@parStart_Time)";
 SQLiteConnection myConnection = new SQLiteConnection(strConnection);
 SQLiteCommand myCommand = new SQLiteCommand(strCommand, myConnection);
 DateTime sTime = new DateTime(2008, 12, 30, 23, 59, 59);
 myCommand.Parameter.Add(new Parameter("@parStart_Time", sTime);
 try
 {
  myConnecion.Open();
  int count = myCommand.ExecuteNonQuery(); //count 為1表示資料新增成功
 }
 catch(Exception ex) { throw new Exception(ex.Message); }
 finally { myConnection.Close();}
}
ok 現在讀寫都正常了(使用MySQL時必須將符號換成符號)

結論︰沒事還是使用Parameter帶值吧! 既方便又安全,別再用 + 號了

2008年12月24日 星期三

[SQLite]列出資料庫中的所有資料表

DataTable ListAllTables(string filePath, string userID, string psw)
{
 string strConnection = string.Format("Data source={0}", filePath);
 SQLiteConnection myConnection = new SQLiteConnection(strConnection);
 SQLiteCommand myCommand = new SQLiteCommand(myConnection);
 myCommand.CommandText = "SELECT * FROM sqlite_master WHERE type='table'";
 try
 {
  myConnection.Open();
  DataTable table = new DataTable();
  table.Load(myCommand.ExecuteReader());
  return table;
 }
 catch { return null; }
 finally { myConnection.Close(); }
}

2008年12月11日 星期四

控制項跨執行緒無效

//只要加上這一行,就能不檢查執行緒衝突
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;

2008年11月28日 星期五

使用 MySQL command line 刪除資料庫

mysql>drop database yourDbName;

ps.記得加上"分號"命令才會送出

2008年11月21日 星期五

搜尋並轉換欄位值

假設有某個欄位,名稱為"MediaType"。是Integer型態,存放的資料範圍是1-4,對應如下:
1:文字
2:圖片
3:影片
4:PowerPoint

若要SELECT出MediaType欄位則,一般的SQL語法如下:
"SELECT MediaType FROM TempTable"



若要將裡面的值轉換成其他文字,則轉換的SQL語法如下:
"SELECT CASE MediaType When '1' Then '文字' "When '2' Then '圖片' When '3' Then '影片' When '4' Then 'PowerPoint' End AS MediaType FROM TempTable"

新增額外的判斷欄位至資料表

public class ConditionColumn
{
 private DataTable table = new DataTable();
 public void Main()
 {
  table.Columns.Add(new DataColumn("MediaType"));
  DataRow dr1 = table.NewRow();
  DataRow dr2 = table.NewRow();
  DataRow dr3 = table.NewRow();
  DataRow dr4 = table.NewRow();
  dr1["MediaType"] = 1;
  dr2["MediaType"] = 2;
  dr3["MediaType"] = 2;
  dr4["MediaType"] = 4;
  table.Rows.Add(dr1);
  table.Rows.Add(dr2);
  table.Rows.Add(dr3);
  table.Rows.Add(dr4);
 }

 public void AddColumn()
 {
  table.Columns.Add("Comment", System.Type.GetType("System.String"), "IIF(MediaType=1,'文字',IIF(MediaType=2,'圖片','未知'))");
 }
}

呼叫 AddColumn 前的資料表

呼叫 AddColumn 後的資料表

2008年11月12日 星期三

簡單的 .NET Remoting 使用範例 (2)

當Server的服務完成之後,接下來就是Client的部分
Client也分成兩個檔案,為:Remote.cs 及 Client.cs

Remote.cs

namespace Remote
{
  interface IRemoteObject
  {
    bool Login(string account, string password);
   }
}

這裡Client端的Remote.cs 與 Server端的Remote.cs 最大的差別就是
,Client端的Remote.cs 只需要 IRemoteObject 而不需要實作,
這也表示Client端不會知道Server端是如何實作IRemoteObject 所宣告的功能
,而我們要的安全性也就達成了。

有一個必須要注意的地方,就是Client端與Server端的IRemoteObjectnamespace命名必須相同

Client.cs

using Remote;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

namespace Client
{
 public class CClient
 {
  private string serverIP = "192.168.1.100"; //Server的IP

  public void Test()
  {
   TcpChannel channel = new TcpChannel(); //建立通道但不指定port

   try
   {
    //註冊通道,系統會自動找未使用的port
    ChannelServices.RegisterChannel(channel, false);
    //經由"tcp://192.168.1.100:7181/ServiceMessage"
    //從Server端實做一個IRemoteObject型態的物件
    IRemoteObject remoteObj = (IRemoteObject)Activator.GetObject(typeof(IRemoteObject), string.Format("tcp://{0}:7181/ServiceMessage", serverIP));

    //物件建立完成後,就可像一般物件一樣使用
    if(remoteObj.Login("root","1234")
     MessageBox.Show("登入成功");
    else
     MessageBox.Show("登入失敗");
   }
   catch
   {
    MessageBox.Show("無法與伺服器建立連線");
   }
   finally
   {
    ChannelServices.UnregisterChannel(channel);
   }
  }  
 }
}

當4個檔案都建立完成後,我們可以經由
Server 的 StartService() 方法來啟動服務
而使用 Client 的 Test() 方法來測試

簡單的 .NET Remoting 使用範例 (1)

在寫應用程式時,常常需要實作一些存取Server資料庫的行為
有時候我們會從ClientAP直接連接到Server的資料庫去作存取的動作

可是這麼作似乎有些安全性的考量,因此我們希望能夠透過一支中介的程式才幫我們做資料庫的存取,這時候使用 .Net Remoting 可以是一個簡單的解決方式。

程式的原始檔,總共分為4個。其中,Client端 與 Server端各占兩個。

在Server端的部分需要如下的檔案:

Remote.cs

using System;
using System.Data;
using MySql.Data.MySqlClient;

namespace Remote
{
 interface IRemoteObject
 {
  bool Login(string account, string password);
 }

 public class RemoteObject : MarshalByRefObject, IRemoteObject
 {
  #region IRemoteObject 成員

  bool IRemoteObject.Login(string account, string password)
  {
   //方法的實作
   //return true or false
   .
   .
   .   
  }
  
  #endregion
 }
}

這檔案中實作Login方法的地方並不是重點,
重點是我們宣告了一個 IRemoteObject 並且實作它。
實做出來的類別必須繼承 MarshalByRefObject

Server端的第2個檔案
Server.cs

using Remote;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

namespace Server
{
 public class CServer
 {
  private TcpChannel channel = null;

  public void StartService()
  {
   try
   {
    //先建立一個通道。7181是port 可以隨便打
    channel = new TcpChannel(7181);
    //跟系統註冊此通道
    ChannelServices.RegisterChannel(channel);
    //跟系統註冊RemoteObject,及RemoteObject的路徑是ServiceMessage
    RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObject), "ServiceMessage", WellKnownObjectMode.SingleCall);
   }
   catch
   {
    MessageBox.Show("無法啟動服務");
    ChannelServices.UnregisterChannel(channel);
   }
  }
 }
}


2008年10月16日 星期四

在Windows Service 讀不到 Register 的值

今天使用 Microsoft.Win32.Registry 類別
HKEY_CURRENT_USER\Software\下建立一個名稱為 "TMM" 的機碼
HKEY_CURRENT_USER\Software\TMM 下建立了一個,名稱為 "Path" 資料為 "C:\" 的字串值

當我在Windows Form 下做資料取回測試的時候,
string keyName = "HKEY_CURRENT_USER\Software\TMM";
string path = (string)Registry.GetValue(keyName, "Path", null);
一切都很正常

當我以同樣的方式把code加到我的 Windows Service後,卻怎麼樣也取不到值
看了http://topic.csdn.net/t/20040728/16/3219207.html此討論串之後
得知 Windows Service 讀取 HKEY_CURRENT_USER 下的資料是有些限制的

最後也因為怕麻煩,所以將原本寫在 HKEY_CURRENT_USER 的資料
全部改寫在 HKEY_LOCAL_MACHINE,問題就解決了。

2008年10月8日 星期三

C# 提供的Timer不準啦!

今天將Timer的Interval設為1000以下的時候發現被觸發的間隔時間並不是那麼準
Interval 設為 1 的時候 15ms 或 16ms 才會觸發一次
Interval 設為31的時候 31ms 或 32ms 才會觸發一次
Interval 設為32的時候 32ms、46ms 或 48ms 才會觸發一次
Interval 設為33的時候 45~48ms 才會觸發一次
這結果還滿怪的,不過看得出來有些規律性,每次約以15ms或16ms遞增。
ps. 搭配Stopwatch去看

看來只能直接使用win32的API了,不過高興的是在CodeProject有人將它包起來了。

http://www.codeproject.com/KB/miscctrl/lescsmultimediatimer.aspx

2008年10月5日 星期日

C# 建立多參數的方法

有使用過 string.Format() 的都知道
在第一個參數之後可以接N個參數,如:
string.Format("{0}{1}{2}...{N}",v1,v2,...,vN);
這不可能用多載來建構N個方法吧
=.=

C# 中可利用 params 關鍵字來建立此方法

在使用上有幾點限制:
1. 在使用params之後不可以在有其他的參數
 void Test(string arg1, params object[ ] args, object count){...} //錯誤
 void Test(string arg1, params
object[ ] args){...} //正確
2. 一個方法中只能有一個 params 參數
 void Test(string arg1, params object[ ] args1, params object [] args2){...} //錯誤
3. 使用 params 的參數必須是一維陣列
 void Test(string arg1, params object arg){...} //錯誤

下面是一個將所有參數列出來的簡單例子:
public static void WriteAllParameters(params string[ ] list)
{
 for ( int i = 0 ; i <>
  Console.WriteLine( list[i] );
}

2008年9月30日 星期二

MySQL中使用DataTable.Load() 讀取BIGINT型態的欄位時發生( "數值對 Int32 而言太大或太小" )的錯誤!

原始碼如下:
string strConnection = "dataSource=localhost;database=test;user=root;password=1234";
MySqlConnection myConnection = new MySqlConnection(strConnection);
string strCommand = "SELECT AdID FROM Advertisement";
MySqlCommand myCommand = new MySqlCommand(strCommand, myConnection);
DataTable table = new DataTable();
try
{
 myConnection.Open();
 table.Load(myCommand.ExecuteReader()); //此行發生錯誤
}
catch { MessageBox.Show("讀取失敗"); }
finally { myConnection.Close(); }

真是奇怪?
我記得TableLoad的時候會自動判斷資料庫中Column的型態,然後自動對Table新增相對應型態的Column啊,明明Table一開始也沒有指定任何型態,怎麼會發生這樣的事情?

找了很久終於找到原因了。
原來AdID這個Column在資料庫中是"自動遞增(Auto Increment)",結果將此ColumnAuto INC取消,然後將Default Value設為0 錯誤就消失了^^'''

到現在我還是不知道到底是為什麼,兩者看起來似乎沒有什麼關係啊! 有人知道嗎?

2008年9月28日 星期日

正則表達式

下面是自己嘗試寫出來的不保證一定正確
for C#

using
System.Text.RegularExpressions;

Regex 類別

[資料夾路徑驗證 ]
適用格式如下:
c: , c:\ , c:\abc
, c:\abc\

正則表達式如下:
"^[a-zA-Z]:(\\\\|(\\\\[^\\\\/:*?\"<>|]+)*|\\\\([^\\\\/:*?\"<>|]+\\\\)*)$"

[取得副檔名 ]
適用格式如下:
.a , .ab , .
abc , .abcd

正則表達式如下:
@"\.[a-zA-Z0-9]{1,4}$"

Match match = Regex.Match("要驗證的文字","正則表達式") ;
if (match.Success)
 string extName = match.Groups[0].Value;
else
 MessageBox.Show("無任何匹配");

[IPv4驗證 ]
適用格式如下:
0-255.0-255.0-255.0-255
ps. 開頭不得為0 ( ex. 192.168.00.001 )

正則表達式如下:
@"^(\d|[1-9]\d|1\d{1,2}|2[0-4]\d|25[0-5])(\.(\d|[1-9]\d|1\d{1,2}|2[0-4]\d|25[0-5])){3}$"

2008年9月23日 星期二

兩程式之間的訊息傳遞(使用SendMessage)

想要從一個程式傳送消息到另一個程式可以使用下面兩個Win32 API
1. SendMessage( ):用來傳遞消息代號
2. FindWindows( ):用來找出目標視窗的Handler

下面例子為由Client端發送訊息至Server端

Client端程式

using System.Runtime.InteropServices;

public class Client
{
 [DllImport("User32.dll", EntryPoint = "FindWindow")]
 private static extern int FindWindows(string lpClassName, string lpWindowName);

 [DllImport("User32.dll", EntryPoint = "SendMessage")]

 private static extern int SendMessage(int hWnd, int Msg, int wParam, int lParam);
 
 
public Cliect()
 {
  //傳送代號為1982,且帶兩個參數為8與1的訊息到另一支程式
  SendMsgToServer(1982, 8, 1);    
 }

 //傳送訊息至Server端  
 public void SendMsgToServer(int msgCode, int wp, int lp)
 {
  //先取得另一支程式的視窗Handler,
  //這裡使用視窗標題名稱 "Form1" 來尋找
  int windowHandler = FindWindows(null, @"Form1");
  if (windowHandler > 0) 
  {
   //傳送訊息     
   int hr = SendMessage(windowHandler, msgCode, wp, lp);
  }
 }
}

Server端程式 (主要為覆寫 DefWndProc 事件處理函式)

publc class Server
{
 protected override void DefWndProc(ref Message m)
 {
  if (m.Msg == 1982)
  {
   MessageBox.Show(string.Format( "My birthday is {0}-{1}-{2}", m.Msg, m.WParam, m.LParam));
  }
  else
   base.DefWndProc(ref m);
 }
}

PS1. 如果電腦中同時有數個相同標題名稱的視窗,只有最上層的視窗收的到訊號,也就是說FindWindows( )只會回傳最上層視窗的Handler。

PS2.無法發送訊號時,請檢查msgCode是否太小。

2008年9月22日 星期一

Serialize(序列化) 與 Deserialize(還原序列化)

程式是在記憶體中執行的,一旦結束程式資料也就消失了
有時候我們希望將某個物件的內容(資料)保存起來
所以必須將資料寫進檔案中,要使用的時候再重新讀進來
至於寫入的格式該怎麼訂,讀出的時候該怎麼分析這就依照個人喜好了
有時候這些步驟是很麻煩的

現在有一個叫 IFormatter 的東西可以簡單的完成這項任務
下面的例子僅將物件讀進資料流,再由資料流寫回物件

假設我們有一個自定類別,定義如下:
[Serializable]
public class MyObject
{
  public int age = 0;
  public string name = string.empty;
}
//這裡需注意的是,要使該類別的物件能被序列化,該類別必須加入[Serializable]屬性

using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.IO;

public class Main
{
 public Main()
 {
 
 //處理序列化的物件
 
 IFormatter formatter = new BinaryFormatter();

 
 //要被序列化的物件
 
 MyObject obj = new MyObject();

 
 MemoryStream ms = new MemoryStream();
 
 //將物件序列化
  formatter .Serialize(ms, obj);

 
 //還原序列化資料
 
 ms.Position = 0;
 
 MyObject obj2 = (MyObject)formatter.Deserialize(ms);
 }
}

2008年9月5日 星期五

'CoInitializeEx' : undeclared identifier 錯誤

從MSDN中知道了要include objbase.h標頭檔

 #include <objbase.h>
 void main()
 {
  int hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); 
 }

奇怪怎麼發生錯誤? Google了一下原來在 ColnitializeEx在objbase.h中的定義如下:

 #if (_WIN32_WINNT >= 0x0400 ) || defined(_WIN32_DCOM) // DCOM
 WINOLEAPI  CoInitializeEx(LPVOID pvReserved, DWORD dwCoInit);
 #endif // DCOM

這表示使用前必須先加入:#define   _WIN32_DCOM
重點是此行必須加在 #include <objbase.h> 之前,改成如下即可執行。

 #define   _WIN32_DCOM
 #include <objbase.h>
 void main()
 {
  int hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); 
 }


2008年9月4日 星期四

wchar_t 輸出中文


原本執行下列程式碼
wchar_t str[] = L"Hello";
wcout  << str << endl;
可順利輸出Hello

但是當字串改成中文後

wchar_t str[] = L"哈囉";
wcout  << str << endl;
卻沒有輸出任何字元

google 後的結果是因為輸出語系的關係

將程式碼更正後如下:

wchar_t str[] = L"哈囉";
wcout.imbue(locale("cht")); //更改輸出語系
wcout  << str << endl;
即可順利輸出 哈囉

IDL的型態 和C++、Visual Basic及JAVA 型態之間的轉換




IDL Type

C++

Visual Basic

Java

signed char

signed char


byte

unsigned char

unsigned char

Byte


wchar_t

wchar_t

Integer

char

signed short

short

Integer

short

unsigned short

unsigned short



signed int

int

Long

int

unsigned int

unsigned int



signed hyper

__int64


long

float

float

Single

float

double

double

Double

double

BSTR

BSTR

String

java.lang.String

boolean

bool

Boolean

boolean

VARIANT

VARIANT

Variant

com.ms.com.Variant

DATE

DATE

Date

double

CY

CY

Currency

long

SAFEARRAY

SAFEARRAY

[] (a standard Visual Basic array)

com.ms.com.SafeArray

IUnknown*

IUnknown*

IUnknown

com.ms.com.IUnknown

IDispatch*

IDispatch*

Object

java.lang.Objec

2008年9月3日 星期三

位址空間

在程式中出現的指標 ( ex. char *p),指向的並不是實體記憶體中的位址,而是應用程式載入記憶體時CPU指定給程式的邏輯位址。因為如果CPU指定的是實體位址的話,那麼當程式在記憶體中SWAP時就需要不停的修改程式。因此,一個指標指向的邏輯位址,對於另一個應用程式來說沒有任何意義,在應用程式之間傳遞資料時,應該直接傳遞資料,而不是傳遞指標

建立一個COM物件時,需要2個ID

1. 類別ID ( CLSID ):用來表示COM元件 ( DDL 或 EXE 檔案 ) 的ID

2. 介面ID ( IID ):用來表示要建立的物件的類別ID

2008年8月29日 星期五

虛擬函式 與 純虛擬函式

虛擬函式跟一般函式一樣,唯一的差別是在於使用再"多型"上,當一個子類別想要使用和父類別相同名稱的函式時,也就是做覆寫的動作時,必須在父類別中將該函式前面加上 virtual 用以辨識,表示該函式是可以被覆寫的,而此函式稱為虛擬函式。
class A
{
 public:
  //一般函式
 int Test()
 {
  return 0;
 }
}

class A
{
 public:
  //虛擬函式
 virtual int Test()
 {
  return 0;
 }
}

另外有另一種不提供函式內容的虛擬函式稱為"純"虛擬函式,只要類別中有一個以上的純虛擬函式,該類別就稱為抽象類別,抽象類別只能被拿來繼承,無法直接產生物件(實例),而繼承該類別的類別,必須實做那些函式。純虛擬函式與虛擬函式的宣告方式差異,除了純虛擬含是不定義函式內容以外,必須在函式後面加上=0
class A
{
 public:
 
//純虛擬函式
 virtual int Test()=0;
}

2008年8月22日 星期五

事件(Event) 與 回呼( Callback ) 的不同

事件(Event) 與 回呼( Callback ) 看起來很像, 運作的方式也差不多,都是透過 deleget 來進行委派, 但使用的方式有一點點不同. 其實應該說是角度上的不同.

事件是由物件本身觸發, 而向外界發佈, 所以外界可以使用 += 識別符, 將方法(method)的委派掛上去, 而事件一般來說都是public的.

回呼並不向外界發佈, 而是透過其他方式取得要處理的方法的委派, 通常一個回呼指向一個方法

以下是"事件"的使用方式:
public delegate void TestHandler(string msg);
class ClassA
{
  public event TestHandler TestEvent;  
  public void Invoke()
  {
    TestEvent("Hello Event");
  }
}

class MainClass
{
  public ClassA cA = new ClassA();
  public MainClass()
  {
    this.cA += new TestHandler(cA_TestEvent);
  }
  private void cA_TestEvent(string msg)
  {
    Message.Show(msg);
  }
}

以下是"回呼"的使用方式
public delegate void TestHandler(string msg);
class ClassA
{
  private TestHandler TestCallback;
  public void RegistCallback(TestHandler callback)
  {
   this.TestCallback = callback;
  }
  public void Invoke()
  {
    TestCallback("Hello Callback");
  }

}

class MainClass
{
  public ClassA cA = new ClassA();
  public MainClass()
  {
    TestHandler myCallback = new TestHandler(cA_Callback);
    this.cA.RegistCallback(myCallback );
  }
  private void cA_Callback(string msg)
  {
    Message.Show(msg);
  }
}

2008年8月20日 星期三

自訂類別的陣列排序

我們都知道用intlong等基本形態所建立的一維陣列可以使用Array.Sort()來做排序的動作
今天我們建立了一個如下的類別:
public class People
{
  private int age = 0;
  private string name = string.Empty;
  public int Age
  {
   get { return age; }
   set
   {
    if ( age >= 0 )
     age = value;
   }
  }
  public string Name
  {
   get { return name; }
   set { name = value; }
  }
}
如果我們希望能夠使用Array.Sort()來排序此資料形態的陣列
ex:
People[] peoples = new People[5];
Array.Sort(peoples);

此時會出現 "無法比較陣列中的兩個元素。" 的例外
因為我們並沒有指定要排序的條件
例如是照age或是name來排序
因此我們必須讓People類別實做 System.IComparable 介面
實作後的類別如下:
public class People:System.IComparablePeople
{
  //內容完全與前面相同,在此省略

  #region IComparable<People> 成員

  
//多實做了此方法
  int IComparable
People.CompareTo(People other)
  {
   return this.
Age.CompareTo(other.Age); //這裡我們選擇以Age做為排序為基準
  }

  #endregion
}