2010年4月2日 星期五

在WPF中捕捉螢幕畫面(ScreenShot)

WPF中沒有直接可以捕捉螢幕畫面的物件或函式
不過可以使用.net Form的方法做到,先加入參考System.Drawing

Bitmap CaptureScreen()
{
/// new a bitmap with screen width and height
Bitmap b = new Bitmap(
(int)SystemParameters.PrimaryScreenWidth,
(int)SystemParameters.PrimaryScreenHeight);

/// copy screen through .net form api
using (Graphics g = Graphics.FromImage(b))
{
g.CopyFromScreen(0, 0, 0, 0,
b.Size, CopyPixelOperation.SourceCopy);
}

return b;
}

這時候得到的是Form使用的Bitmap物件
如果要存成圖檔的話就可以直接使用Bitmap物件的Save

void SaveBitmap(Bitmap b, string f)
{
b.Save(f);
}

或是再把它轉換成WPF使用的BitmapSource

[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DeleteObject(IntPtr hObject);

private static BitmapSource ToBitmapSource(Bitmap b)
{
/// transform to hbitmap image
IntPtr hb = b.GetHbitmap();

/// convert hbitmap to bitmap source
BitmapSource bs =
Imaging.CreateBitmapSourceFromHBitmap(
hb, IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());

/// release temp hbitmap image
DeleteObject(hb);

return bs;
}

不過由於透明背景的視窗會被放在不同層的顯示記憶體中
這也造成了使用上面的捕捉畫面方法會抓不到透明的視窗
如果希望抓到的畫面包括這樣的視窗,那就要直接用Win32的GDI+函式了

const uint SRCCOPY = 0x00CC0020;
const uint CAPTUREBLT = 0x40000000;

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

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

[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool BitBlt(IntPtr hdcDest, int nXDest,
int nYDest, int nWidth, int nHeight, IntPtr hdcSrc,
int nXSrc, int nYSrc, uint dwRop);

[DllImport("user32.dll")]
static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);

void CaptureScreen()
{
/// new a bitmap with screen width and height
Bitmap b = new Bitmap(
(int)SystemParameters.PrimaryScreenWidth,
(int)SystemParameters.PrimaryScreenHeight);

/// copy screen through gdi+ api
using (Graphics g = Graphics.FromImage(b))
{
IntPtr hdc = g.GetHdc();
IntPtr dh = GetDesktopWindow();
IntPtr dhdc = GetDC(dh);
BitBlt(hdc, 0, 0,
(int)SystemParameters.PrimaryScreenWidth,
(int)SystemParameters.PrimaryScreenHeight,
dhdc, 0, 0, SRCCOPY | CAPTUREBLT);

/// release and dispose unmanaged resources
ReleaseDC(dh, dhdc);
g.ReleaseHdc();
hdc = IntPtr.Zero;
dh = IntPtr.Zero;
}
}

這樣就可以抓到透明背景的視窗
但是如果仔細看的話會發現在抓圖的時候,滑鼠游標會有一個閃爍
(需要連續一直抓比較明顯,直接抓是看不太出來的)
這是因為透明圖層跟滑鼠游標是在同一層
但是BitBlt的原則是不要抓到滑鼠游標
所以會再抓取透明圖層的時候會先將滑鼠變不見

--
參考資料
ScreenShot Application in WPF using GD+
BitBlt (gdi32)
The Case of the Disappearing Cursor

2010年4月1日 星期四

在WPF中的形狀(Shape)增加虛線框

在Shape中要加上框線很簡單
只要設定框線的顏色和粗細就好

<Rectangle Stroke="Black" StrokeThickness="2" />

再設定框線變成虛線

<Rectangle Stroke="Black" StrokeThickness="2"
StrokeDashArray="3 5" />

這裡需要兩個數字,一個是虛線中的線段長度
另一個是虛線中的空間長度,單位都是框線的粗度
所以上面例子是粗度為2像素,而每6像素就會空白10像素的長方形外框

--
參考資料
Shape.StrokeDashArray 屬性