2010年9月16日 星期四

[C#]-利用clipboard轉換office內容物為圖片

最近接了一個case,
在office上選取物件利用ctrl+c複製物件到clipboard內,
並在picturebox控制項上利用ctrl+v將物件從clipboard取出,
並以圖片形式顯示在picturebox上,
最後將picturebox上的image存成圖片檔。

首先先記錄一下如何判斷鍵盤的組合鍵,如下:
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
    switch (e.KeyData)
    {
        case (Keys.Control | Keys.V):
            // do something 
            break;
    }
}

接下來的重點是從clipboard內取出物件時必須知道它支援那些格式,
我們可以利用下面的方式知道該物件所支援的格式有那些:
foreach (string format in Clipboard.GetDataObject().GetFormats())
{
    // do something
}
也有一些簡便的方式,例如Clipboard.ContainsText()或Clipboard.ContainsImage()等可以判斷。

再來我們準備一些method來從clipboard中取出office中大部份常見的格式。
Bitmap格式的物件,我們都能用下面的方式取得image:
public void CatchBitmap()
{
    this.pictureBox1.Image = (Bitmap)Clipboard.GetImage();
}

這裡遇到比較麻煩的困難點是EnhancedMetafile格式,
必須要用win32裡的API去取得系統的clipboard來使用,方式如下:
public const uint CF_METAFILEPICT = 3;
public const uint CF_ENHMETAFILE = 14;

[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool OpenClipboard(IntPtr hWndNewOwner);

[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool CloseClipboard();

[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GetClipboardData(uint format);

[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool IsClipboardFormatAvailable(uint format);

public void CatchEMF()
{
    if (OpenClipboard(this.Handle)) {
        if (IsClipboardFormatAvailable(CF_ENHMETAFILE))
        {
            IntPtr ptr = GetClipboardData(CF_ENHMETAFILE);
            if (!ptr.Equals(new IntPtr(0)))
            {
                Metafile metafile = new Metafile(ptr, true);
                //Set the Image Property of PictureBox 
                this.pictureBox1.Image = metafile;
            }
        }
        CloseClipboard();
    }
}

再來如果copy放進clipboard內的是純文字物件的話,
就必須要用Graphics來drawing string才能產生圖檔,如下:
public void DrawString()
{
    String sImageText = Clipboard.GetText();
    Bitmap objBmpImage = new Bitmap(1, 1);

    int intWidth = 0;
    int intHeight = 0;

    // Create the Font object for the image text drawing.
    Font objFont = 
        new Font("Arial", 20, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Pixel);

    // Create a graphics object to measure the text's width and height.
    Graphics objGraphics = Graphics.FromImage(objBmpImage);

    // This is where the bitmap size is determined.
    intWidth = (int)objGraphics.MeasureString(sImageText, objFont).Width;
    intHeight = (int)objGraphics.MeasureString(sImageText, objFont).Height;

    // Create the bmpImage again with the correct size for the text and font.
    objBmpImage = new Bitmap(objBmpImage, new Size(intWidth, intHeight));

    // Add the colors to the new bitmap.
    objGraphics = Graphics.FromImage(objBmpImage);

    // Set Background color
    objGraphics.Clear(Color.White);
    objGraphics.SmoothingMode = SmoothingMode.AntiAlias;
    objGraphics.TextRenderingHint = TextRenderingHint.AntiAlias;
    objGraphics.DrawString(sImageText, objFont, 
        new SolidBrush(Color.FromArgb(102, 102, 102)), 0, 0);
    objGraphics.Flush();
    this.pictureBox1.Image = objBmpImage;
}

接下來下面要講解的功能是如何把在excel內選取的cells自動轉換成對應的chart圖,
基本上複製的cells放進clipboard本身就支援Bitmap格式,
但直接取用會直接像剪貼那樣,如果要轉換成對應的chart圖則必須再行判斷,
這裡是利用Microsoft.Office.Interop.Excel來轉換,範例碼如下:
public void Convert()
{
    IDataObject iData = Clipboard.GetDataObject();
    String cellArea = (String)iData.GetData(DataFormats.Text);
    String[] rows = cellArea.Split(new Char[] { '\n' });

    Excel.Application xlApp;
    Excel.Workbook xlWorkBook;
    Excel.Worksheet xlWorkSheet;
    object misValue = System.Reflection.Missing.Value;

    xlApp = new Excel.ApplicationClass();
    xlWorkBook = xlApp.Workbooks.Add(misValue);
    xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);

    int rowLength = rows.Length;
    int rowCount = 1;
    int cellCount = 1;
    int maxCellCount = 1;
    
    foreach (String line in rows)
    {
        if (rowCount == rowLength)
        {
            break;
        }
        String[] cells = line.Split(null);
        int cellLength = cells.Length;
        foreach (String cell in cells)
        {
            if (cellCount == cellLength)
            {
                break;
            }
            if (cellCount > maxCellCount)
            {
                maxCellCount++;
            }
            xlWorkSheet.Cells[rowCount, cellCount] = cell;
            cellCount++;
        }
        rowCount++;
        cellCount = 1;
    }

    Excel.Range chartRange;

    Excel.ChartObjects xlCharts = (Excel.ChartObjects)xlWorkSheet.ChartObjects(Type.Missing);
    Excel.ChartObject myChart = (Excel.ChartObject)xlCharts.Add(10, 80, 300, 250);
    Excel.Chart chartPage = myChart.Chart;

    rowCount--;
    maxCellCount--;
    String endCorner = map.ElementAt(maxCellCount).Value;
    endCorner = endCorner + rowCount.ToString();
    
    // endCorner的格式例如:c7,就是cells右下角的範圍邊界點
    chartRange = xlWorkSheet.get_Range("A1", endCorner);
    chartPage.SetSourceData(chartRange, misValue);
    // 設定繪製的chart圖型式
    chartPage.ChartType = Excel.XlChartType.xlColumnClustered;
    chartPage.CopyPicture(Excel.XlPictureAppearance.xlScreen,
                          Excel.XlCopyPictureFormat.xlBitmap,
                          Excel.XlPictureAppearance.xlScreen);
    xlWorkBook.Close(false, false, misValue);
}
產生的chart圖會利用copy放進clipboard內。

使用畫面如下,紅色框部份就是被選取的cells,當在picturebox上按下ctrl+v時,
就會利用office元件繪製chart圖:

最後就是整理一些條件判斷式,來處理當遇到什樣格式時要執行那一個method比較恰當:
// 一般的圖檔
if (this.formatList.Contains("Bitmap") && !this.formatList.Contains("Text"))
{
    CatchBitmap();
}
// excel的cells欄位集合
else if (this.formatList.Contains("Bitmap") && this.formatList.Contains("Text"))
{
    // if want to create chart
    Convert();
                
    // offic 2003
    CatchBitmap();
                   
    // offic 2007
    CatchEMF();
}
// 純粹選取文字
else if (!this.formatList.Contains("Bitmap") && this.formatList.Contains("Text"))
{
    DrawString();
}
// excel2003的chart,只有emf格式,最後再處理
else if (this.formatList.Contains("EnhancedMetafile"))
{
    CatchEMF();
}

2010年9月3日 星期五

[C#]-利用Microsoft.Office.Interop.Outlook讀取outlook msg檔

若要開發 Microsoft Office Outlook 的增益集,您可以與 Outlook 物件模型提供的物件進行互動。此 Outlook 物件模型會提供表示使用者介面中各種項目的類別。例如,Microsoft.Office.Interop.Outlook.Application 類別是表示整個應用程式、Microsoft.Office.Interop.Outlook.MAPIFolder 類別是表示含有電子郵件訊息或其他項目的資料夾,而 Microsoft.Office.Interop.Outlook.MailItem 類別則是表示電子郵件訊息。

Outlook 會提供許多您可以與之互動的類別。下列各節將簡述某些最上層類別,並描述它們彼此的互動關係。這些類別包括:

  • Microsoft.Office.Interop.Outlook.Application
  • Microsoft.Office.Interop.Outlook.Explorer
  • Microsoft.Office.Interop.Outlook.Inspector
  • Microsoft.Office.Interop.Outlook.MAPIFolder
  • Microsoft.Office.Interop.Outlook.MailItem
  • Microsoft.Office.Interop.Outlook.AppointmentItem
  • Microsoft.Office.Interop.Outlook.TaskItem
  • Microsoft.Office.Interop.Outlook.ContactItem

下面是範例程式:
首先在專案參考內先匯入Microsoft.Office.Interop.Outlook
接著是取出msg檔內各欄位的方式
Microsoft.Office.Interop.Outlook.Application app = null;
Microsoft.Office.Interop.Outlook.MailItem item = null;

DialogResult msgFileSelectResult = this.openFileDialog.ShowDialog();
if (msgFileSelectResult == DialogResult.OK)
{
     string msgfile = this.openFileDialog.FileName
     app = new Microsoft.Office.Interop.Outlook.Application();
     item = app.Session.OpenSharedItem(msgfile) as Microsoft.Office.Interop.Outlook.MailItem;
     this.label1.Text = item.Subject;
     this.label2.Text = item.SenderName;
     this.label3.Text = item.To;
     this.label4.Text = item.Body;           
}

2010年9月2日 星期四

[PHP]-使用PDO操作sqlite

PHP5以後除了使用mysql_connect這種舊的方法連接資料庫之外,
加入了PDO的新功能可以用來連接資料庫,好處在於在效能上改善很多,
而且未來在將系統移植道不同的資料庫時也比較方便。

因為在PHP 5以前的php4/php3都是一堆的數據庫擴展來跟各個數據庫的連接和處理,
什麼 php_mysql.dll、php_pgsql.dll、php_mssql.dll、php_sqlite.dll等等擴展
來連接MySQL、 PostgreSQL、MS SQL Server、SQLite,同樣的,
我們必須借助 ADOdb、PEAR::DB、PHPlib::DB之類的數據庫抽象類來幫助我們,
無比煩瑣和低效。

以下是各種不同資料庫中的dsn(數據源)設定方法:

MySQL:
mysql:host={IP HOST}; dbname={DB NAME}

MSSQL︰
mssql:host={IP HOST};dbname={DB NAME}

Orcale:
oci:host={IP HOST};dbname={DB NAME}

SQLite:
sqlite:{DB PATH + DB NAME}

若是要設定預設的編碼,例如要設為使用UTF8,可在dsn中設定charset=UTF8即可。

簡介SQLite
1. ACID事務
2. 零配置 – 無需安裝和管理配置
3. 儲存在單一磁盤文件中的一個完整的數據庫
4. 數據庫文件可以在不同字節順序的機器間自由的共享
5. 支持數據庫大小至2TB
6. 足夠小, 大致3萬行C代碼, 250K
7. 比一些流行的數據庫在大部分普通數據庫操作要快
8. 簡單, 輕鬆的API
9. 包含TCL綁定, 同時通過Wrapper支持其他語言的綁定
10. 良好註釋的源代碼, 並且有著90%以上的測試覆蓋率
11. 獨立: 沒有額外依賴
12. Source完全的Open, 你可以用於任何用途, 包括出售它
要注意的是,如果在linux上使用sqlite,資料檔和資料檔所在目錄的權限一定要開,
這裡特別要強調的是,本身所在目錄的權限也要。

雖然SQLite允許忽略數據類型,下面仍列出常見的數據類型:
CREATE TABLE ex(
a VARCHAR(10),
b NVARCHAR(15),
c TEXT,
d INTEGER,
e FLOAT,
f BOOLEAN,
g CLOB,
h BLOB,
i TIMESTAMP,
j NUMERIC(10,5),
k VARYING CHARACTER (24),
l NATIONAL VARYING CHARACTER(16)
);

下面列舉一些利用PDO操作SQLite的範例:
create table
$dbh = new PDO('sqlite:example.db');
if (!$dbh) die ($error);

$stm = "CREATE TABLE User(id INTEGER PRIMARY KEY, name, birthday1, birthday2, ".
            "birthday0, gender, marry, education, job, tel1, tel2, ext, pw, ".
            "city, district, address, email)";
$dbh->exec($stm);
可以在欄位後面直接加INTEGER PRIMARY KEY指定為primary key,這個欄位值
同時也具有auto increment特性。
也可以利用下列語法指定索引鍵:
CREATE INDEX id_index on table_name(id)

select data from table
$dbh = new PDO('sqlite:example.db');
$sth = $dbh->prepare("SELECT * FROM User");
$sth->execute();
$result_set = $sth->fetchAll();
result_set的格式會如下:
Array
([0] => Array(
[id] => 1
[0] => 1
[name] => heiyeluren
[1] => heiyeluren
[gender] => 男
[2] => 男
[time] => 2006-10-28 23:14:23
[3] => 2006-10-28 23:14:23
)

prepare statement 型式
$dbh = new PDO('sqlite:example.db');
$sth = $dbh->prepare("SELECT * FROM User WHERE email = ?");
$sth->execute(array($_SESSION['user']));
$result = $sth->fetch();

攔截錯誤訊息
$dbh = new PDO('sqlite:example.db');
$rs = $dbh->query("SELECT * FROM User");
if ($dbh->errorCode() != 00000){
 echo $dbh->errorInfo();
 exit;
}
PDO和PDOStatement對象有errorCode() 和 errorInfo() 方法,
如果沒有任何錯誤, errorCode() 返回的是: 00000 ,
否則就會返回一些錯誤代碼。errorInfo() 返回的一個數組,
包括PHP定義的錯誤代碼和MySQL的錯誤代碼和錯誤信息,數組結構如下:
Array
(
[0] => 42S22
[1] => 1054
[2] => Unknown column aaa in field list
)

PDO的一些method參數說明
$dbh = new PDO('sqlite:example.db');
$dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
$rs = $dbh->query("SELECT * FROM User");
$rs->setFetchMode(PDO::FETCH_ASSOC);
$result_set = $rs->fetchAll();
setAttribute() 方法是設置部分屬性
PDO::CASE_LOWER -- 強制列名是小寫
PDO::CASE_NATURAL -- 列名按照原始的方式
PDO::CASE_UPPER -- 強制列名為大寫

setFetchMode方法來設置獲取結果集的返回值的類型
PDO::FETCH_ASSOC -- 關聯數組形式
PDO::FETCH_NUM -- 數字索引數組形式
PDO::FETCH_BOTH -- 兩者數組形式都有,這是缺省的
PDO::FETCH_OBJ -- 按照對象的形式,類似於以前的 mysql_fetch_object()