2010年3月12日 星期五

[Linux Programming] 在 Ubuntu 中使用 C 語言連結 MySQL

首先.......必須安裝 MySQL server,一般來說安裝 "mysql-server" 都可以裝到目前最新版的套件

接著要安裝開發程式所需要的 library:

Ubuntu 8.04 => libmysqlclient15-dev

相關的 header 檔案都會放在 /usr/include/ 裡面,而實際運作的函式庫則會存在 /usr/lib/mysql 中

若是撰寫 mysql 相關程式時,編譯方式如下:

gcc <程式名稱> -I /usr/include/mysql -L /usr/lib/mysql -l mysqlclient -o <輸出檔案名稱>

2010年3月9日 星期二

[Git] 在 Windows 上使用 Git

Git 聽說是比 cvs, subversion 還好用的 version control system 阿!!

為何要用 Git 阿?? 因為優點很多阿! 詳細說明可以看 => Why Git is Better Than X  這一篇文章~

Git 大概有以下特色:
  1. 每個 local 端都有自己的 repository

  2. 特色為 branch + merge

  3. 適合開發人數眾多的大型專案(當然小型專案也是沒有問題滴)


大部分使用 git 的人都是在 Linux 的平台上使用,但要怎麼在 Windows 上面使用呢?

簡單說明步驟如下:
  1. 下載 Windows 用的 git 核心程式,在 git 官網上為 msysgit

  2. 在所要進行版本管理的資料夾上按滑鼠右鍵,選擇 Git Bash Here

  3. 接著會跳出一個模擬 bash 環境的視窗,接著就可以跟 Linux 一樣開始使用 Git 囉!


===== 以下說明最基本的使用方式 =====

#初始化 local repository
> git init

#將目錄中的檔案全部加入 Staging Area
> git add .

#將 Staging Area 中的檔案加入 local repository 中
> git commit -a

#移動至 master branch (正常狀況下 branch 會預設在 master)
> git checkout master

#將資料 push 至遠端的 repository (以 gitorious 為例)
> git remote add origin <git push url>
> git push origin master


【備註】
在將檔案 push 到遠端 git server 之前,需要把加密用的 key pair 準備好,可以透過以下指令產生:
> ssh-keygen -rsa
接著把兩個檔案都放到 C:\Documents and Settings\<USER NAME>\.ssh ,並將 public key 的內容加入到 gitorious 的網站上即可。


相關連結


2010年3月7日 星期日

[Linux Programming] 在 Ubuntu 中使用 dbm 資料庫

在 ubuntu 中 dbm 相關開發套件是不會預先安裝的,因此如果要使用的話,必須安裝「libgdbm-dev」套件。

而由於安裝的是 gdbm 套件,若要使用 gdbm 的相關函式當然沒問題,但若是要使用 ndbm 的相關函式,則必須進行相容性的轉換,作法如下:
  1. 在程式中 include gdbm-ndbm.h

  2. 編譯時加上 -l gdbm_compat -l gdbm 兩個參數以連結函式庫
函式庫說明可以參考以下連結:

2010年3月1日 星期一

[C#] Socket Programming

socket programming 的重點大概有幾個:
  1. 有 server & client 兩端
  2. server 只有一個,而 client 有可能會多個
  3. server 可設定監聽(listen)的 port number


以下直接用範例來說明囉!
(其中 SocketServer 跟新的 client 連線後,會交給 ClientRequestHandler 處理)

SocketServer.cs
using System;
using System.Collections.Generic;
using System.Text;

using System.Net.Sockets;
using System.Net;
using System.ComponentModel;

namespace SocketConn
{
    class SocketServer
    {
        #region private property

        ///         /// socket server TCP port number
        ///         private int _PortNumber;


        ///         /// TCP Listener
        ///         private TcpListener _Listener;


        ///         /// 用來處理 Listener 工作的執行緒
        ///         private BackgroundWorker _bgwServer = new BackgroundWorker();


        ///         /// socker client 識別編號
        ///         private int _ClientNo = 0;

        #endregion


        #region static public property

        ///         /// 工作進行中產生之訊息
        ///         public static List sMessages = new List();

        #endregion


        #region constructor

        ///         /// constructor
        ///         /// 
socket server TCP port number
        public SocketServer(int inPortNumber)
        {
            this._PortNumber = inPortNumber;

            //委派事件
            _bgwServer.DoWork += new DoWorkEventHandler(_bgwServer_DoWork);
        }

        #endregion


        #region public method

        ///         /// 開始監聽
        ///         public void Start()
        {
            if (!_bgwServer.IsBusy)
                _bgwServer.RunWorkerAsync();
        }

        #endregion


        #region BackgroundWorker

        /// 
        /// 處理 Listener 工作
        ///         /// 

        /// 

        private void _bgwServer_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                //若 TCP Listner 正在工作,則停止
                if (_Listener != null)
                    _Listener.Stop();

                //初始化 TCPListener
                _Listener = new TcpListener(IPAddress.Any, _PortNumber);

                //啟動 listener
                _Listener.Start();
                sMessages.Add(" >> " + "Server Started");

                //========== 持續接受監聽 socket client 的連線 ========== (start)
                while (true)
                {
                    //監聽到來自 socket client 的連線要求
                    TcpClient socket4Client = _Listener.AcceptTcpClient();

                    //累加 socket client 識別編號
                    _ClientNo++;
                    sMessages.Add(" >> " + "Client Request No:" + Convert.ToString(_ClientNo) + " started!");

                    //產生 BackgroundWorker 負責處理每一個 Socket Client 的要求
                    ClientRequestHandler handler = new ClientRequestHandler(_ClientNo, socket4Client);
                    handler.DoCommunicate();
                }
                //========== 持續接受監聽 socket client 的連線 ========== (end)
            }
            catch (Exception exp)
            {
                sMessages.Add(exp.ToString());
            }
        }

        #endregion
    }
}


ClientRequestHandler.cs
using System;
using System.Collections.Generic;
using System.Text;

using System.Net.Sockets;
using System.ComponentModel;

namespace SocketConn
{
    class ClientRequestHandler
    {
        #region private property

        ///         /// socket client 識別號碼
        ///         private int _ClientNo;


        ///         /// socket client reuqest
        ///         private TcpClient _TcpClient;

        #endregion


        #region constructor

        ///         /// constructor
        ///         /// 
socket client 識別號碼
        /// 
socket client reuqest
        public ClientRequestHandler(int inClientNo, TcpClient inTcpClient)
        {
            this._ClientNo = inClientNo;
            this._TcpClient = inTcpClient;
        }

        #endregion


        #region public method

        ///         /// server & client 間相互進行通訊
        ///         public void DoCommunicate()
        {
            //產生 BackgroundWorker 負責處理每一個 socket client 的 reuqest
            BackgroundWorker bgwSocket = new BackgroundWorker();
            bgwSocket.DoWork += new DoWorkEventHandler(bgwSocket_DoWork);
            bgwSocket.RunWorkerAsync();
        }

        #endregion


        #region BackgroundWorker

        ///         /// 處理 socket client request
        ///         private void bgwSocket_DoWork(object sender, DoWorkEventArgs e)
        {
            //server & client 已經連線完成
            while (_TcpClient.Connected)
            {
                //取得網路串流物件,取得來自 socket client 的訊息
                NetworkStream netStream = _TcpClient.GetStream();
                byte[] readBuffer = new byte[1024];
                int count = 0;
                if ((count = netStream.Read(readBuffer, 0, readBuffer.Length)) != 0)
                {
                    string clientRequest = Encoding.UTF8.GetString(readBuffer, 0, count);
                    SocketServer.sMessages.Add(" >> " + "From client(" + _ClientNo + ") =>" + clientRequest);

                    //正確取得 client requst,再回傳給 client
                    string serverResponse = "Server to clinet(" + _ClientNo + ") => message: " + clientRequest;
                    byte[] sendBytes = Encoding.UTF8.GetBytes(serverResponse);
                    netStream.Write(sendBytes, 0, sendBytes.Length);
                    netStream.Flush();
                    SocketServer.sMessages.Add(" >> " + serverResponse);
                }
            }
        }

        #endregion
    }
}


SocketClient.cs
using System;
using System.Collections.Generic;
using System.Text;

using System.Net;
using System.Net.Sockets;

namespace SocketConn
{
    class SocketClient
    {
        #region private property

        ///         /// 遠端 socket server IP 位址
        ///         private string _RemoteIpAddress;

        ///         /// 遠端 socket server 所監聽的 port number
        ///         private int _RemotePortNumber;


        ///         /// socket client 物件(連接遠端 socket server 用)
        ///         private TcpClient _TcpClient;

        #endregion


        #region public static property

        public static List sMessages = new List();

        #endregion


        #region constructor

        ///         /// constructor
        ///        /// 
遠端 socket server IP 位址
        /// 
遠端 socket server 所監聽的 port number
        public SocketClient(string inRemoteIpAddr, int inRemotePortNum)
        {
            this._RemoteIpAddress = inRemoteIpAddr;
            this._RemotePortNumber = inRemotePortNum;
        }

        #endregion


        #region public method

        ///         /// 連線至 socket server
        ///         public void Connect()
        {
            //初始化 socket client
            _TcpClient = new TcpClient();
            _TcpClient.Connect(_RemoteIpAddress, _RemotePortNumber);
            sMessages.Add("Client Socket Program - Server Connected ...");
        }


        ///         /// 傳送訊息至 socker server
        ///         /// 
訊息
        /// socker server 回傳結果
        public string Send(string inMessage)
        {
            try
            {
                //取得用來傳送訊息至 socket server 的 stream 物件
                NetworkStream serverStream = _TcpClient.GetStream();

                //將資料轉為 byte[]
                byte[] outStream = System.Text.Encoding.UTF8.GetBytes(inMessage);

                //將資料寫入 stream object (表示傳送資料至 socket server)
                serverStream.Write(outStream, 0, outStream.Length);
                serverStream.Flush();

                //讀取 socket server 回傳值並轉為 string
                byte[] inStream = new byte[10025];
                serverStream.Read(inStream, 0, (int)_TcpClient.ReceiveBufferSize);
                string returndata = System.Text.Encoding.UTF8.GetString(inStream);

                sMessages.Add("Data from Server : " + returndata);
                return returndata;
            }
            catch (Exception exp)
            {
                sMessages.Add(exp.ToString());
                return exp.ToString();
            }
        }

        #endregion
    }
}

2009年10月18日 星期日

[NASM] BIOS 啟動程序

當電腦的電源開啟後,處理器會進入 reset 狀態,此時:
  1. 所有記憶體的內容會變為 0

  2. CS 的值會被預設為 0FFFF[0]H

  3. IP(Instruction Pointer) 儲存指令的 offset,預設為 0

結合 2 & 3(Code Segment + Offset),可以知道第一個要執行的指令的 memory address 為 0FFFF0H(CS:IP),而這個 memory address 即為 BIOS 的進入點。

BIOS 的常式位於 0FFF0H,他檢查各個 I/O port,並對於存在的設備進行初始化,接著 BIOS 會建立兩個資料區:
  1. Interrupt Vector Table (中斷向量表)
    長度為 1 KB,用來做為當 interrupt(中斷) 發生時,BIOS & OS 用來選擇相對應的 interrupt handler 之用,詳細資料可參考此篇文章。

  2. BIOS 資料區
    起始位址為 40[0]H,大小依據設備的多寡而不同。

接著 BIOS 會檢查系統磁碟是否存在,並從系統磁碟下載啟動程式,啟動程式接著從系統磁碟將系統檔案載入 memory,並將控制權交給 OS。

2009年10月9日 星期五

[自動化] 序列埠模擬器(Virtual Serial Port Emulator)

最近開發工作由於沒有實體的設備可以接.....因此要想辦法進行模擬...

而模擬的設備又是走序列埠(serial port),因此只好去找找有沒有免費的模擬器可以用...

上 Google 搜尋「free serial port emulator」

果然給我找到一套免費的可以用 => Free Virtual Serial Ports Emulator

目前的版本是 0.936.4.687,希望這好東西可以持續的推陳出新囉!

我使用的情境如下:

  1. 建立的 Device Type 為 pair (假設為 COM2 + COM3)

  2. Modbus Slave 模擬程式連結 COM2

  3. 程式向 COM3 進行 pooling

這樣就很完美的連結起來啦! 

PS. 這套軟體無法作序列埠的相關設定,例如:Baud Rate。是比較可惜的地方,希望未來可以加入這功能囉!

2009年9月20日 星期日

[OS] Computer System Organization

Computer-System Operation

現今電腦系統中包含了一個或數個 CPU,以及多個連接在 common bus 上個多個 device controller,每種不同的 device controller 用來處理不同的設備,如下圖所示:


CPU 與其他 device controller 可以一起執行,並相互競爭取得 memory cycle;而為了確保不同設備可以依序的存取 shared memory,memory controller 必須提供不同設備同步存取記憶體的方法。

當電腦剛開機時,第一個執行的程式稱為「bootstrap」,而 bootstrap 通常存放在硬體的 firmware 中,目的是用來載入 & 啟動 OS kernel。

為了做到上述工作,bootstrap 就必須知道 OS 存放的記憶體位址,才有辦法順利載入執行,而 OS 載入後所執行的第一個 process,一般稱為「init」,並等待其他軟/硬體觸發事件以進行處理。

硬體是透過傳遞訊號給 CPU 來觸發 interrupt,而軟體則是透過呼叫 system call 來觸發 interrupt;當 CPU 收到 interrupt 訊號,會執行以下動作:
  1. 立即停止目前工作

  2. 將所要執行的工作傳到特定的位址執行(此位址即為提供執行工作服務的起始位址)

  3. 當工作執行完畢,CPU 則會恢復中斷前的運作

下圖展示出 CPU 執行與工作執行的時間軸:

Interrupt(中斷) 在電腦架構中是非常重要的一個環節,也許不同的電腦會有不同處理 interrupt 的機制,但總是會有共同的部分,當 CPU 收到 interrupt 的訊號時,就會暫停手邊工作,並將 interrupt 丟給合適的中斷處理程序進行處理。

最直接的實作方式是有一個專門的程序(routine),會針對收到的 interrupt 訊號資訊,將其丟給相對應的中斷處理程序處理。

另外一種不需要專門程序處理中斷的方式,則是有一個存放「指向 interrupt routines 的指標(pointer)的表格」,因此 interrupt routine 可以直接透過此 table 儲存的 pointer 進行呼叫,而不需要有個專門的代理程序來處理這個部分。

每個不同設備所產生的 interrupt 都會有不同的 interrupt routine 來處理,並會以唯一的設備編號作為索引,而這些資訊都會儲存在記憶體最前面的位址。


Storage Structure

主記憶體是 CPU 唯一可以直接存取的區域,而兩者之間的互動是透過 CPU 呼叫 store & load 兩個指令,將資料在 memory & register(暫存區)。

其中 load 的作用是將長度為 1 個 word 的資料從 memory 搬到 register 中;而 store 則是將 1 個 word 的資料從 register 回存到 memory 中。

然而,除了基本資料的搬移外,CPU 也是會從 memory 中載入指令以執行不同的工作,其運作步驟如下:
  1. CPU 從 memory 中取得所要執行的指令,並將其存放於 instruction register

  2. CPU 會將此指令拆解成相對應的 CPU 指令集並執行

  3. 最後將執行結果回存至 memory 中

然而,記憶體中的資料是沒辦法永久儲存的,只要停止供電後就會消失;因此需要其他儲存媒體來協助進行資料的儲存,最常見的當然就是硬碟機 or 光碟片了! 當然還有最近很夯的 USB 隨身碟(屬於 flash memory)。


I/O Structure


OS 中有一大部分是專門處理 I/O 的,原因是因為 I/O 的處理好壞會影響到整體系統的效能 & 穩定度。

I/O 動作再 OS 是按照以下步驟進行的:
  1. device driver 會先讀取在 device control 中 register 的資訊

  2. 接著 device controller 會檢視 register 中的內容,判斷所要執行的動作為何 (例如:從鍵盤讀取一個字元)

  3. device controller 開始從裝置中讀取資料至本地端的緩衝區

  4. 資料傳輸完畢,device controller 會發送 interrupt 告知 device driver

  5. 最後 device driver 會將控制權歸還給 OS

  6. 若是執行的動作為 read,就會傳取得的資料(or 指向資料所在的位置的 pointer);但若執行的是其他動作,則會回傳狀態資訊

看了上面的動作,若是還搞不懂,可以將整個動作想像成:設備要傳遞資料到記憶體中,必須一直發中斷給 CPU,並將資料傳給 CPU 後,由 CPU 把資料放入記憶體中。

顯而易見的,所有資料在傳遞時都必須依賴 CPU 的處理,若是速度很慢的裝置(例如:軟碟機)進行 I/O,就會拖垮整個系統的運作效能。

因此,以上的 I/O 動作在處理量少的資料時還可以,但若是處理量大的資料(例如進行 Disk I/O),就會產生極高的 overhead,而為了解決此問題,就有了 DMA(Direct Memory Access) 的產生,以下有張示意圖:


有了 DMA 之後,device 就可以直接將資料以 block 的方式從本身的緩衝區傳入 memory 中,且不會中斷 CPU 的工作,頂多就是當每個 block 資料傳遞完畢後,發個 interrupt 通知 device driver 動作已經完成。

有些更高階的 OS,使用的是 switch 的機制而非 bus 的架構,在這種 OS 中,不同的元件之間不需要在共用的 bus 上搶奪 cycle,且可以同時相互溝通。


參考資料




2009年8月19日 星期三

[LINQ] 製作分頁功能 - 使用 skip & take

開發網頁程式時,分頁功能總是少不掉的.......

然而不使用 skip 與 take 來過濾資料,全部交給 GridView 控制項處理也是可以......

以下是使用範例:

xxx.aspx

xxx.aspx.cs


然而若是使用 skip & take,就可以僅取得部份資料,讓分頁相關的程式撰寫上更加有彈性,以下是使用方式:

xxx.aspx

xxx.aspx.cs

執行結果

2009年8月16日 星期日

[Silverlight 3.0] 如何讓消失的 ASP.NET Silverlight 控制項重新出現

昨天安裝了 Silverlight 3.0 的開發環境準備來學習一下 Silverlight

結果看書上有 ASP.NET Silverlight 控制項,但我的 VS 2010 就硬是不給我出現,解決方式有兩種:
  1. 安裝 Silverlight 2.0 SDK (沒錯......裝了 3.0 SDK 並不會有 2.0 SDK 有的東西....超怪....)

  2. 若不想安裝 2.0 SDK,則可以參考此篇文章,額外下載並加入 ASP.NET Server Controls for Silverlight Samples 即可

2009年8月13日 星期四

[LINQ] 常用語法介紹

根據特定條件取得資料 - Where

IEnumerable<T>.Where
可以協助將篩選出我們想要的資訊,共有兩種用法,首先介紹一般 where 的用法:

接著是另一種較為特別的用法,其中還將資料的 index 也考慮進來:


取得特定欄位資料 - Select

Enumerable.Select
用來取得我們所要得資料欄位或是物件,使用上除了可用 LINQ 的語法之外,亦可透過 Extension Method 的特性來使用 Select。

首先介紹透過 LINQ 語法來使用 Select:

接著是使用 Extension Method:


此外,LINQ 還提供了一個 Enumerable.SelectMany 的語法,可以將取得的集合物件直接展開使用,以下用範例說明:


去除重複 - Distinct

與 SQL 語法相同,LINQ 中也提供了 Distinct 來過濾掉重複的資料:


將 query 後的資料轉為其他集合型態

在 LINQ 中提供了 ToList()、ToArray()、ToDictionary 等方法將 query 後的資料轉為不同的集合型態來使用,但有一點需要注意,.NET 處理的方式是「複製一份資料集合」。

因此,若是 query 得到的資料很龐大,想當然而呼叫 ToList() 或是 ToArray() 這一類的方法就會很耗費系統資源,使用上就必須要注意一下。


聚合函數(Arregation Functions)

LINQ 中也提供了許多聚合函數,例如:Max、Min、Sum、Count ... 等等,以下介紹使用方式:


排序(Sorting)

根據條件取得資料後,也許還會有排序的需求,當然 LINQ 也提供了排序的語法 Enumerable.OrderBy,以下介紹其使用方式:

從上面的 orderby 使用方式可以發現,可以同時進行不同欄位資料的排序,而這一段 query,.NET Framework 是怎麼幫我們達成的呢? 

其實是將這一段語法拆成三段語法而成的:

LINQ 在語法上的使用彈性可見一班!


巢狀搜尋(Nested Query)

從上面的範例到至今,我們都是以 Book 為主的搜尋,根據不同的條件取得不同的資料。

假設我們要改以 Publisher 為主來搜尋呢? 假設要搜尋的資料是不同的 Publisher 所出版的書籍清單,這該怎麼作呢?

由於每個 Publisher 出版的書籍數量不一定是僅有一本,因此搜尋出來的資料就肯定無法用 row by row 的方式來呈現;而是必須取得 Publisher 清單後,再根據每個 Publisher 取得相對應的出版書籍。

因此我們可以將目標設定在取得類似以下資料:
  • Publisher(出版商) -> Books(出版書籍清單)

  • Publisher(出版商) -> Books(出版書籍清單)

  • ..... (etc)

可以用以下語法達成:

當然 aspx 中也要有相對應的 GridView 設定:

執行結果如下:


群組(Grouping)

以上面為例,若是要達到相同效果,也可以改用 Group 的方式,而且透過 Group 的方式取得資料,沒有出版書的 Publisher 就不會出現囉!

aspx 的部分就跟 Nested Query 中的相同!

執行結果如下:

仔細觀察程式碼,其中的 PublisherBooks 為 LINQ 處理後所會回傳的群組資料(根據 book.Publisher 分群),而 PublisherBooks 則會預設實作 IGrouping 介面。

實作 IGrouping 介面即表示這群資料集合中,都擁有相同的 key 值,也就是作為分群條件的 book.Publisher。

跟 Nested query 相比較,使用 Group 的方式取得資料至少有兩樣優點:
  1. 查詢語法更加精簡

  2. 可在查詢語法中對群組命名,以便後續使用(例如:與 aggregation function 進行搭配使用)


Joins

既然 LINQ 是針對資料集合進行處理,那怎麼會少掉 Join 這個強大的語法呢?

以下用 Join 語法來達到上面範例分群組的功能:

aspx 的部分也是跟先前的範例相同!

執行結果如下:

而從這範例結果可以看出,沒有出版書籍的 Publisher 也會一並被列出(跟 Nested Query 的結果相同),而若是要避開這種情況,可以改用其他 Join 的方式。


Inner Join

這是一般最常用的 join 方式,會過濾掉沒有符合資料的紀錄,以下是使用方式:

執行結果如下:


Left Outer Join

若是要留下 join 中沒有比對符合資料的紀錄,就必須改用 Left(Right) Outer Join 了,但 LINQ 中並沒有提供語法可以直接作到,因此要搭配 DefaultIfEmpty 方法來達到此效果:

執行結果如下:


Cross Join

這個就是有名的 Cartesian Product(卡氏積),會將 Join 兩方所有可能的組合通通列出,在 LINQ 中要達到這樣的效果,可以用下列的方式:

以下是程式執行結果:

2009年7月4日 星期六

[JavaScript] 將JavaScript 物件轉為 json 字串

一般將物件轉為 json 字串都是在編譯式的程式語言中進行的! 而這次有需要宣告 JavaScript 物件並轉為 json 字串。

Google 了一下,發現已經有人提供寫好的 JavaScript Library 可以用啦!

以下有一段語法,包含 JavaScript 物件的宣告並搭配 jQuery 一同使用此 library:



參考資料


[jQuery] 引用外部 JS 檔

在使用 jQuery 時,有時會需要額外引用外部的 js 檔案來使用裡面的功能

要怎麼作呢?

以下有一段範例語法:


參考資料

javascript include javascript(使用jQuery) - cloudio™- 點部落


2009年6月25日 星期四

發神經的 VS.NET 2008

平常用 3.0 以後提供的語法 & 功能習慣了.....例如:
public string test { get; set; }

var myVar = "text";

var newObject = new MyObject { Name = "myobject", Age = 12; }
大概就以上這些語法.......

今天用 VS.NET 2008 開啟後,重新編譯卻發現這些語法都死光光了.....例如出現以下訊息:
'xxxxxxx' 不是標記成 abstract 或 extern,因此必須宣告主體
上面就是因為用了第一段程式碼的第一行語法所出現的錯誤.....(但這是 2.0 才會出現的...)

看到這情況真是莫名其妙,於是再專案"屬性頁"中,在"建置"中的目標 Framework 中選擇 ".NET Framework 3.0" 編譯一次(當然會錯誤),再改回 ".NET Framework 3.5" 編譯一次就正常了....

2009年6月23日 星期二

[WPF] 項目控制項

簡介

由於項目控制項繼承自 System.Windows.Controls.ItemsControl 類別,因此可以容納整個集合的物件,不限定單一物件,而且每個項目都可以是任何物件。(UIElement 會呈現出來,非 UIElement 則是顯示 ToString() 的結果)

ItemsControl 有幾個屬性必須要了解:
  1. HasItems:用來檢查集合中有沒有內容

  2. IsGrouping:用來辨識集合中的項目是否有分群

  3. DisplayMemberPath:使用項目物件中的屬性來顯示(也可以是運算式喔!)

而項目控制項大致可分為以下三類:
  1. 選取器

  2. 功能表

  3. 其他項目控制項



選取器

選取器的特色在於可用 index 的方式存取,並且可以選取。

因此選取器繼承 ItemsControl 之後,額外加入了處理選取功能的屬性,例如 SelectedIndex、SelectedItem、SelectedValue .... 等等都是。

而 WPF 中提供了四個選取器控制項,分別是 ComboBox、ListBox、ListView、TabControl。


ComboBox

ComboBox 中定義了屬性 IsDropDown 可判斷目前下拉式選單的狀態是展開或是收合;另外還定義了兩個事件,分別是 OnDropDownOpened 以及 OnDropDownClosed,可在有特殊需求時填入相對應的程式碼。

以下為範例:
(IsEditable = false)

(IsEditable = true)

若是要更清晰的表達每個項目,可使用 ComBoxItem 標籤包住每一個項目內容,如此一來,不只清楚,還可以在 ComboBoxItem 標籤上加上 IsSelected 或是 IsHightlighted 等屬性。

但若使用 ComboBoxItem,TextSearch.Text 屬性就無法設定在上面範例的 StackPanel 上,而是要設定在 ComboBoxItem 上囉!


ListBox

基本上 ListBox 跟 ComboBox 是很相似的,只是 ListBox 一次顯示所有項目而已,且有三種不同的選取模式:
  1. Single (預設)

  2. Multiple

  3. Extended:搭配 Shift/Ctrl 進行多選

而 ComboBox 有 ComboBoxItem 標籤,ListBox 也有 ListBoxItem 標籤可以使用囉!


ListView

ListView 繼承自 ListBox,額外增加了 View 的屬性,可以讓開發者自訂更豐富的顯示方式,以下示範使用方式:




TabControl

TabControl 是很基本的控制項,以下為示範:





功能表

這部分有 Menu 以及 ContextMenu 兩個控制項,使用起來的效果與原本 2.0 中其實是差不多的,可以參考以下範例:




其他項目控制項

此部分包含了 TreeView、ToolBar、StatusBar 等等。

TreeView

以下是 TreeView 控制項的使用範例:

另外還有查到 TreeView 的使用範例教學,可以參考看看!

而 TreeViewItem 較為常用的屬性有 IsExpanded、IsSelected 兩個屬性,以及 Expanded、Collapsed、Selected、Unselected 這幾個事件囉!


ToolBar

可以把 ToolBar 想像是功能表的加強版! 可以將許多按鈕群組在一起。

以下找到一個簡單的使用範例教學:



StatusBar

StatusBar 也像是功能表,但其項目是以水平堆疊而成的,上網找到幾個不錯的相關教學:


2009年6月19日 星期五

[WPF] 內容控制項

簡介

內容控制項的 Content 僅能放入一個物件,皆繼承自 System.Windows.Controls.ContentControl 類別,而 Content 中的物件可以是任何型別,放入的 element tree 有多大皆可! 但僅能有一個直接的 child element。

ContentControl 中有個 HasContent 屬性,有此屬性的定義,就可以透過屬性觸發程序很輕易的在 HasContent 屬性改變時進行其他屬性的設定動作。
(上面這一招在 WPF 中使用很多.............)

內容控制項分為三大類:
  1. 按鈕

  2. 簡單容器

  3. 有標題的容器



按鈕

在 WPF 中,按鈕繼承自 System.Windows.Controls.Primitives.ButtonBase 類別,其中以下幾個控制項都繼承自 ButtonBase:
  1. Button

  2. RepeatButton

  3. ToggleButton

  4. CheckBox

  5. RadioButton
 

Button

WPF 中的 Button 類別只有在 ButtonBase 上增加了「取消按鈕」以及「預設按鈕」兩個機制,這兩個機制在對話方塊中很有用:
  1. 當 Button.IsCancel = true,此按鈕屬於取消按鈕,點選之後會關閉按鈕所屬的視窗並設定 DialogResult = false。

  2. 當 Button.IsDefault = true,此按扭屬於預設按鈕,當按鈕取得焦點時,按下 Enter 就等於按下此按鈕。


RepeatButton

RepeatButton 按著不放會持續觸發 Click 事件,但由於直接繼承 ButtonBase 類別,因此沒有取消以及預設兩種機制存在。

而 RepeatButton 持續觸發 Click 事件的頻率取決於 Delay 以及 Interval 兩個屬性,預設即為 SystemParameters.KeyboardDelay 以及 SystemParameters.KeyboardSpeed。

看到以上描述有想起什麼嗎? 沒錯! 就是之前提過的 DependencyProperty(相依屬性) 啊~


ToggleButton

ToggleButton 可視為「會粘住」的按鈕,因此點選以後會保持狀態,第一次點選 IsChecked = true,再一次點選 IsChecked = false。

若 IsThreeState 屬性為 true,則 IsChecked 的值的變化為 true -> null -> false,對應到事件的觸發則是:
  • IsChecked = true 觸發 Checked 事件

  • IsChecked = false 觸發 Unchecked 事件

  • IsChecked = null 觸發 Indeterminate 事件


CheckBox

CheckBox 的特性相當類似於 ToggleButton,因此他是繼承自 ToggleButton 而來的,只是外觀有點不同罷了!


RadioButton

RadioButton 也是繼承自 ToggleButton,另外還支援了單選功能(同一群組內)。



簡單容器

Label

在 WPF 中的容器控制項,幾乎都可以擺入任意型態的物件,但唯讀 Label 僅能放入文字內容。

但雖然如此,Label 控制項卻可以搭配 Alt 進行快捷鍵的設定喔!

以下程式會在按下 Alt + U 時,焦點會自動跳到 TextBox 上:



ToolTip

ToolTip 是用來顯示提示訊息用,原本都是顯示文字訊息而已,但在 WPF 中,已經可以顯示任何的內容了! 以下有個簡單範例:



另外若是要讓 ToolTip 有更多不同的方式進行呈現時(例如:顯示時間長短),可搭配 ToolTipService 靜態類別來達成!


Frame

Frame 也可以放入任意內容,且可以同時支援 HTML & WPF,透過設定 Source 屬性就可以在 Frame 中顯示連結的內容。



有標題的容器

此部分介紹的控制項(GroupBox、Expander),由於繼承自 System.Windows.Controls.HeaderedContentControl 類別,因此多了一個型態為 Object 的 Header 屬性。而這代表什麼? 代表 Header 可以放進任何你想要放的東西.......


GroupBox

我們通常將 GroupBox 用將多個控制項歸納為相同群組時使用,但由於它是內容控制項,因此只能有一個 child element,因此我們要用其他的容器來當中介:




Expander

Expander 類似 GroupBox,但有個按鈕可以讓使用者開啟 & 收合所包含的內容:



此外,Expander 還定義了 IsExpanded 屬性以及 OnExpanded / OnCollapsed 兩個事件,還可以透過 ExpandDirection 來控制內容展開的方向。

2009年6月17日 星期三

[LINQ] 初探 LINQ to Objects

哪些類別支援 LINQ 相關功能 ?

LINQ to Objects 表示要透過 LINQ 與集合型態的物件進行互動,這些資料存在於 memory 中,但是 .NET Framework 提供的類別如此之多,支援 LINQ 存取的是哪些呢?

答案就是有實作 System.Collections.IEnumerable 介面的類別所產生的物件都可以。

何以見得? 由於 .NET Framework 3.0 以後加入了 Extension Method(擴充方法) 這個機制,且又在 System.Linq.Enumerable 類別中針對實作 System.Collections.IEnumerable 介面的類別加入了許多 Extension Method,而這些類別(Array、List、Dictionary .... 等等)則可說是「自動擴充」成擁有 LINQ 的相關功能。



使用範例

首先是 LINQ to Object Array:

using System;
using System.Linq;

class Program
{
static void Main(string[] args)
{
//定義各種不同型態的資料
Object[] array = { "String", 12, true, 'a' };

var types = array.Select(item => item.GetType().Name) //選取集合中資料的型態
.OrderBy(type => type); //根據型態進行排序

/* 執行結果:
Boolean
Char
Int32
String */
ObjectDumper.Write(types);
Console.ReadLine();
}
}



接著是 LINQ to 自行定義的物件(Book) Array:

using System;
using System.Linq;
using LinqInAction.LinqBooks.Common;

class Program
{
static void Main(string[] args)
{
Book[] books = { new Book { Title = "LINQ In Action" },
new Book { Title = "LINQ For Fun" },
new Book { Title = "Extreme LINQ" } };

var titles = books.Where(book => book.Title.Contains("Action")) //取得 Title 欄位包含 "Action" 關鍵字的書籍資料
.Select(book => book.Title); //取得 Title 欄位

/* 執行結果:
LINQ In Action */
ObjectDumper.Write(titles);
Console.ReadLine();
}
}



其實,只要是 Array,存放於 Array 中可以是任何型態的物件(因為 Array 有實作 System.Collections.IEnumerable 介面)


接著改用 List,除了型態不同以外,query 的方法可說是完全相同:

using System;
using System.Collections.Generic;
using System.Linq;
using LinqInAction.LinqBooks.Common;

class Program
{
static void Main(string[] args)
{
//即使資料換成是 List,LINQ 的語法也是相同的!
List books = new List() { new Book { Title = "LINQ In Action" },
new Book { Title = "LINQ For Fun" },
new Book { Title = "Extreme LINQ" } };

var titles = books.Where(book => book.Title.Contains("Action")) //取得 Title 欄位包含 "Action" 關鍵字的書籍資料
.Select(book => book.Title); //取得 Title 欄位

/* 執行結果:
LINQ In Action */
ObjectDumper.Write(titles);
Console.ReadLine();
}
}



這就是 LINQ 想要達成的目標 => 讓 query 的語言一致!
目前嘗試 Array 與 List 的確有這種感覺~


最後是 LINQ to Dictionary,並用兩種不同的方式(query operator & query expression)進行查詢:

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
static void Main(string[] args)
{
Dictionary myNumbers = new Dictionary();
myNumbers.Add(0, "zero");
myNumbers.Add(1, "One");
myNumbers.Add(2, "Two");
myNumbers.Add(3, "Three");
myNumbers.Add(4, "Four");

//透過 query operator 的方式取得偶數值
var evenNumbers01 = myNumbers.Where(number => number.Key % 2 == 0)
.Select(number => number.Value);
ObjectDumper.Write(evenNumbers01);

//也可以透過 query expression 的方式取得偶數值
var evenNumbers02 = from number in myNumbers
where (number.Key % 2) == 0
select number.Value;
ObjectDumper.Write(evenNumbers02);

/*
以上兩個執行結果都會是:
Zero
Two
Four
*/
Console.ReadLine();
}
}


由此可見,由於 Extend Method 機制的出現,幾乎讓所有集合型態的資料都可以透過 LINQ 的方式進行存取了,只要有實作 System.Collections.IEnumerable 介面即可。

除此之外,查詢的方式也不僅限於一種,除了 query operator 外也還有 query expression 可以使用,甚至兩種同時搭配使用亦可喔!

[C#] Deferred Query Execution

什麼是 Deferred Query(延遲查詢) Execution 呢?

這是在看 LINQ In Action 的時候發現的,查詢了一下,原來這是 C# 2.0 就已經提供的功能.....

用途在於可以用更少的資源(Resource)使用集合(collection)物件!

為何這麼說呢? 以下用一段程式碼來說明:


using System;
using System.Linq;

namespace DeferredQueryExecution
{
class Program
{
static double Square(double n)
{
//每呼叫 Square 就會印出下列訊息
Console.WriteLine("Computing Square(" + n + ")....");
return Math.Pow(n, 2);
}

static void Main(string[] args)
{
int[] numbers = { 1, 2, 3 };

//取得 numbers 陣列中所有元素的平方值並存入 query 集合物件中
var query =
from n in numbers
select Square(n);

//印出 query 集合物件的內容
foreach (var n in query)
Console.WriteLine(n);

Console.ReadLine();
}
}
}



這一段程式碼執行完的結果會是如何呢? 一般來說,應該是以下這樣....
Computing Square(1)....
Computing Square(2)....
Computing Square(3)....
1
4
9

但實際跑出來的結果並非這樣,而是:
Computing Square(1)....
1
Computing Square(2)....
4
Computing Square(3)....
9

結果令人出乎意料吧!

從結果可以看出,.NET compile 在處理集合物件時,並非一次給定完整結果,而是「真的有需要才一個一個慢慢給出」,因此當集合物件的數量龐大時,可以有效降低系統資源的浪費,提升在操作集合物件時的效能。

當然,這跟 LINQ 好像沒啥關係......因此只要了解一下 .NET 有這種機制存在即可囉!

2009年6月12日 星期五

[C#] delegate(委派) -> Lambda Expression -> LINQ

觀念解說

關於 delegate & Lambda Expression & LINQ 入門,參考以下文章可以得到很清楚的觀念喔!

Huan-Lin 學習筆記: C# 筆記:重訪委派-從 C# 1.0 到 2.0 到 3.0

Huan-Lin 學習筆記: C# 筆記:從 Lambda 表示式到 LINQ

C# 3.0 極簡風 - Lambda Expression - 黑暗執行緒


接著以下只記錄一些瑣碎的筆記........


LINQ 從何而來?

Lambada Expression 大量了使用了之前所提到的 extension method,而 .NET 中也提供了不少的 extension method 以提昇 Lambada Expression 的易用性,藉此讓 LINQ 的語法更加的直覺!

舉例來說,在 System.Linq.Enumerable class 中,就定義了許多 extension method,例如:OrderByDescending、Last、Max、Min、Contains、Count.... 等等。


除了 delegate & Lambda Expression 之外,與 LINQ 有關的特殊功能還有 Implicit typed local variables、Object and collection initializers、Anonymous types 等等。

而除了 delegate 之外,都是在 C# 3.0 中才有提供的(當然 VB.NET 也有),因此看得出來,LINQ 可說是集合所有新功能於一身的查詢技術。


範例說明

以上範例將上述的新功能簡單做了一次完整的結合測試:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace CompleteCode
{
//為了將 Extension Method 定義在此類別, 要加上 static 關鍵字
//否則就要另外定義一個 static class 來放 Extension Method
static class Program
{
static void Main(string[] args)
{
//在呼叫時指定比對用的 function (使用 Lambda Expression)
DisplayProcesses(process => process.WorkingSet64 >= 20 * 1024 * 1024);

Console.ReadLine();
}

//定義 inner class
class ProcessData
{
public Int32 Id { get; set; }
public Int64 Memory { get; set; }
public string Name { get; set; }
}

//此處以委派(delegate)的方式將 match 函式交由呼叫端決定
//傳入的參數型態為 System.Diagnostics.Process
//回傳型態為 Boolean
static void DisplayProcesses(Func match)
{
//此處變數型態會由 compiler 在編譯時決定
var processes = new List();

foreach (var process in Process.GetProcesses())
{
//使用 Object Initializer 的方式宣告物件並給定值
if (match(process))
processes.Add(new ProcessData { Id = process.Id, Name = process.ProcessName, Memory = process.WorkingSet64 });
}

Console.WriteLine("Total memory: {0} MB", processes.TotalMemory() / 1024 / 1024);

//透過 .NET 內建的 Extension Method,取得耗用記憶體最大的兩個 process 所耗費的記憶體總計
var top2Memory = processes.OrderByDescending(process => process.Memory)
.Take(2)
.Sum(process => process.Memory) / 1024 / 1024;
Console.WriteLine("Memory consumed by the two most processes: {0} MB", top2Memory);

//使用 Anonymous Type 宣告物件並顯示
var results = new { TotalMomery = processes.TotalMemory() / 1024 / 1024,
Top2Memory = top2Memory,
Processes = processes };
ObjectDumper.Write(results, 1);
}

//定義 Extension Method(計算記憶體總使用量)
//針對 IEnumerable 型別進行擴充
static Int64 TotalMemory(this IEnumerable processes)
{
Int64 result = 0;

foreach (var process in processes)
result += process.Memory;

return result;
}
}
}

2009年6月9日 星期二

[C#] 3.0 中的新功能 - 擴充方法(Extension Method)

簡介

擴充方法能將方法「加入」至現有型別,但不需要建立新的衍生型別 (Derived Type)、重新編譯,或是修改原始型別。

神奇的是,不僅是自訂的型別可以增加 extension method,連既有的型別都可以增加 extension method !

隨便舉個例,假設要檢查字串是否為數字,以前都是另外寫個 function 來檢查,例如:
public bool IsNumeric(string strVal);

現在若是透過 extension method,只要透過以下方式即可:
strVal.IsNumeric();

如何? 使用起來是不是更加直覺了呢?

[LINQ ] 初探

簡介

LINQ 是 Microsoft 開發出來專門用來處理集合資訊用的技術,什麼叫做集合資訊呢?

舉凡 Database、XML、Object、DataSet、Array、Collection .... 等都可以算是集合資訊

而我們再開發軟體時,資料來源也就差不多是上面這些,但每一種資料來源都有很多額外的知識必須要學..... Database 是一門學問、XML 又是一門學問 ...

想像一下若是可以用同一套語法來存取以上這些資料來源,不是很迷人的事情嗎?

而 LINQ 就是為了這個目的而產生的!

瞭解一下 LINQ 的目標就可以知道他有多酷了:(from LINQ In Action)
  1. Integrated objects, relational data, and XML (這當然是首要目的)

  2. SQL and XQuery-like power in C# and VB (整合進 C# & VB.NET 囉)

  3. Extensibility model for languages (提供其他語言的擴充性)

  4. Extensibility model for multiple data sources (當然也提供了不同資料來源的擴充性)

  5. Type safety (透過 compile-time 檢查)

  6. Extensive IntelliSense support (enabled by strong-typing) (這對開發者肯定是一大福音啊!)

  7. Debug support (這功能沒有怎麼可以呢?)



使用範例

首先來個使用範例,LINQ to Object !



using System;
using System.Linq;

namespace HelloLinq
{
class Program
{
static void Main(string[] args)
{
//string array
string[] words = { "hello", "wonderful", "linq", "beautiful", "world" };

//取得陣列中字串長度小於等於 5 的字串
var shortwords = from word in words
where word.Length <= 5
select word;
/* hello
linq
world */
foreach (var word in shortwords)
Console.WriteLine(word);


//依照字串長度分群組
var groups = from word in words
orderby word ascending
group word by word.Length into lengthGroups
orderby lengthGroups.Key descending
select new { Length = lengthGroups.Key, Words = lengthGroups };
/*
words of length 9
beautiful
wonderful
words of length 5
hello
world
words of length 4
linq
*/
foreach (var group in groups)
{
Console.WriteLine("words of length " + group.Length);
foreach (string word in group.Words)
Console.WriteLine(" " + word);
}

Console.ReadLine();
}
}
}




看到範例的第一個部分,也許有人會覺得沒有 LINQ 也是很好寫吧!

但第二部份,不是用 LINQ 的話,可就很費工囉!

然而,範例雖然簡單,還是可以看出 LINQ 的語法真的是相當直覺......加上 VS.NET 的 IntelliSense....(難怪 MS 的開發工具這麼多人用.....)


接著以下的範例,可以看出 LINQ 如何以幾乎無縫的方式整合進 C#(VB.NET 也行啦) 中,搭配 XML API 來產生 XML 文件:


using System;
using System.Linq;
using System.Xml.Linq;

namespace HelloLinqToXml
{
class Book
{
public string Publisher;
public string Title;
public int Year;

public Book(string title, string publisher, int year)
{
Title = title;
Publisher = publisher;
Year = year;
}
}

class Program
{
static void Main(string[] args)
{
//定義 object collection
Book[] books = new Book[] {
new Book("Ajax in Action", "Manning", 2005),
new Book("Windows Forms in Action", "Manning", 2006),
new Book("RSS and Atom in Action", "Manning", 2006)
};

//從 object collection 中取得資料後,並搭配 XML API 產生 XML 文件
XElement xml = new XElement("books",
from book in books
where book.Year == 2006
select new XElement("book",
new XAttribute("title", book.Title),
new XElement("publisher", book.Publisher)));

Console.WriteLine(xml);
Console.ReadLine();
}
}
}




最後一個部分則是 LINQ to SQL,此處以 NorthWind 為範例資料庫:


using System;
using System.Linq;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Data.SqlClient;

namespace HelloLinqToSql
{
class Program
{
static void Main(string[] args)
{
//設定資料來源
DataContext db = new DataContext(new SqlConnection("Server=CIC-GODLEON\\SQLEXPRESS;Database=northwind;Trusted_Connection=True;"));

//透過 LINQ 來取得資料
var contacts =
from contact in db.GetTable()
where contact.City == "Paris"
select contact;

foreach (var contact in contacts)
Console.WriteLine("Bonjour " + contact.Name);

Console.ReadLine();

}
}


///
/// 必須透過設定 attribute 的方式指定 class 和 DB table 的 mapping
/// 此處指定對應到 Customers 資料表
///

[Table(Name="Customers")]
class Contact
{
///
/// 同樣內部的設定也必須要透過 attribute 的方式設定 mapping
///


//未指定則對應到同名(CustomerID)欄位
[Column(IsPrimaryKey=true)]
public string CustomerID { get; set; }

//指定對應到 ContactName 欄位
[Column(Name="ContactName")]
public string Name { get; set; }

//對應 City 欄位
[Column]
public string City { get; set; }
}
}





參考資料