2009年7月31日 星期五

在WPF中配置版面 - Grid

在WPF裡面有很多的版面配置元件
像是Grid, DockPanel, WrapPanel...
其中Grid是比較單純的元件
可以想像成在word裡面的表格
將表格配置好,然後把相應的內容填入表格
這樣所有的東西的位置就會對齊好了

要新增一個Grid就在XAML裡面加入

<Grid>
</Grid>

也可以在C#裡面直接加入

public void AddGrid(Window w)
{
/// new a grid in corrosponding window
Grid g = new Grid();
w.Content = g;
}

再來是要把Grid切成想要的大小
這時候就需要藉由設定Grid的
ColumnDefinitions以及RowDefinitions來改變欄位配置
假設這時候需要切成三行
那就把剛剛插入XAML裡面的語法換成

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
</Grid>

或是在C#裡面改成

public void AddGrid(Window w)
{
/// new a grid in corrosponding window
Grid g = new Grid();

/// new two rows with height as star
RowDefinition r1 = new RowDefinition();
r1.Height = new GridLength(10, GridUnitType.Star);
RowDefinition r2 = new RowDefinition();
r2.Height = new GridLength(10, GridUnitType.Star);

/// add the rows into g
g.RowDefinitions.Add(r1);
g.RowDefinitions.Add(r2);

/// set g as the content of w
w.Content = g;
}

RowDefinition的Height值跟ColumnDefinition的Width
同樣都是GridLength的物件來表示
可以直接設成Auto, *, 或是直接給值
種類說明
Auto依照內容物改變大小
*依照欄數自動均分母窗體的大小,
可加比例,如2*即代表佔兩份的意思
直接定義該攔獲該行需的長寬,
可加單位,如 50px, 2in, 1cm, 或40pt

--
參考資料
HOW TO:在方格中加入資料列和資料行
GridLength Structure
GridUnitType Enumeration

2009年7月29日 星期三

在WPF中跨執行緒的元件操作

有時候為了效能或是一些比較花時間的作業
常常會在程式中需要多開幾個執行緒(Thread)
不過如果不同執行緒中同時需要用到
同樣的物件(通常是UI的部分)
不過物件不能跨執行緒
這時候就需要用到Dispatcher

WPF在跨執行緒作業時
假設被用到的函式是

void Func(Type Param);

原本呼叫的地方(另一個執行緒中)本來應該是

Func(p);

直接改成

Dispatcher.Invoke(DispatcherPriority.Normal,
new Action<Type>(Func),p);

注意涵式不能有回傳值,參數可有多個(至少不超過四個都ok)

--
參考資料
使用 Dispatcher 建置回應性更佳的應用程式

2009年7月28日 星期二

在WPF中將視窗從Alt-Tab List中隱藏

有時候一支應用程式常常不只一個視窗
但是預設會讓所有的視窗都出現在Alt-Tab的list裡面
不僅讓使用者在使用時會混淆
甚至造成一些像是順序錯誤之類的錯誤
這時可以將視窗的屬性設成toolwindow(fixed or sizable)
然後將ShowInTaskbar設成false就可以隱藏了

public void HideInAltTab(Window w)
{
w.WindowStyle = WindowStyle.ToolWindow;
w.ShowInTaskbar = false;
}

只不過此時window的型態就變成tool window
title比較細小,而且右上角的控制只剩下關閉
最大和最小化都沒有了

不過如果是第二或第三個視窗的話
有時會希望window的邊界風格是none
那還是可以的
依照先前的方法,不過先將WindowStyle設成none
然後用user32里面提供的API,將GWL_EXSTYLE(-20)屬性
設成WS_EX_TOOLWINDOW(0x80)

public const int GWL_EXSTYLE = -20;
public const int WS_EX_TOOLWINDOW = 0x00000080;

[DllImport("user32.dll")]
public static extern int SetWindowLong(IntPtr hWnd,
int nIndex, IntPtr dwNewLong);

[DllImport("user32.dll")]
public static extern int GetWindowLong(IntPtr hWnd,
int nIndex);

public void HideInAltTab(Window w)
{
/// change the window style to none
w.WindowStyle = WindowStyle.None;
w.ShowInTaskbar = false;

/// get the handle of w
IntPtr h = GetHandle(w);

/// set the window extend style to tool window
SetWindowLong(h, GWL_EXSTYLE, new IntPtr(
GetWindowLong(h, GWL_EXSTYLE) |
WS_EX_TOOLWINDOW));
}

其中取得Handle的函式請參考 在WPF中找出window的handle

在WPF中找出window的handle

WPF的window裡面沒有handle這個變數
可是如果用到一些API裡面需要使用的時候
還是可以透過WindowInteropHelper取得

public IntPtr GetHandle(Window w)
{
WindowInteropHelper h = new WindowInteropHelper(w);
return h.Handle;
}

其中WindowInteropHelper需要加入

using System.Windows.Interop;

--
參考資料
Get the WPF Window handle

在WPF中輸入密碼

在.net form以及之前的時候
基本上還是先拉出一個textbox
然後把Password屬性設成true
但是WPF中增加了一個控制項PasswordBox
直接在XAML中加入

<PasswordBox
Margin="20,120,20,120"
Name="p"
PasswordChar="#"
/>

就可以加入一個輸入密碼的文字控制項
改變PasswordChar則可以改變顯示的字元

2009年7月24日 星期五

在WPF中存讀圖檔

要把WPF的物件輸出成圖檔
首先將視覺化物件先繪製出來
透過一個encoder將繪製出的圖檔編碼
最後再透過filestream將圖存進檔案

private void SaveTo(Visual v, string f)
{
/// get bound of the visual
Rect b = VisualTreeHelper.GetDescendantBounds(v);

/// new a RenderTargetBitmap with actual size
RenderTargetBitmap r = new RenderTargetBitmap(
(int)b.Width, (int)b.Height,
96, 96, PixelFormats.Pbgra32);

/// render visual
r.Render(v);

/// new a JpegBitmapEncoder and add r into it
JpegBitmapEncoder e = new JpegBitmapEncoder();
e.Frames.Add(BitmapFrame.Create(r));

/// new a FileStream to write the image file
FileStream s = new FileStream(f,
FileMode.OpenOrCreate, FileAccess.Write);
e.Save(s);
s.Close();
}

記得這時候想要輸出的控制項和其父控制項的左上角必須對齊
可參考在WPF中存圖檔的問題(RenderTargetBitmap)

另外,如果要從檔案中讀出圖檔並放進WPF的畫布(Canvas)上面的話
則是先從一個filestream將檔案打開
並透過decoder將圖檔解碼

private void LoadFrom(Canvas c, string f)
{
/// new a FileStream to write the image file
FileStream s = new FileStream(f,
FileMode.OpenOrCreate, FileAccess.Read);

/// new a decoder to decode image
BitmapDecoder d = BitmapDecoder.Create(s,
BitmapCreateOptions.None,
BitmapCacheOption.None);

/// new an image with decoded image
Image i = new Image();
i.Source = d.Frames[0];

/// add the image into canvas
c.Children.Add(i);
}

注意,此時如果將FileStream關掉(Close)的話
會丟出影響無法解碼的例外情形
所以要等到該圖已經不在需要顯示的時候再做Close的動作

在WPF應用程式中繪圖

WPF的繪圖和.net form的概念不同
並不是透過一個Graphics的物件去處理繪圖
而是做出所需的圖形(線段(Line)、橢圓(Ellipse)、方塊(Rectangle)...)放進容器(Canvas)
步驟如下:

1. 做出Canvas的容器

在XAML裡面加入(可以放在grid的標籤裡面)

<Canvas Margin="0,0,0,0" Name="c"/>

或是在程式碼加入

Canvas c = new Canvas();
this.Content = c;

2. 製作出想要的繪製的圖形物件並將物件放進容器

private void DrawLine(Canvas c, Point s, Point e)
{
/// new a line geometry
LineGeometry l = new LineGeometry(s, e);

/// new a path and set its data as the geometry
System.Windows.Shapes.Path p =
new System.Windows.Shapes.Path();
p.Data = l;

/// add the line as a child of canvas
c.Children.Add(p);
}

其中的LineGeometry也可以換成EllipseGeometry或是RectangleGeometry
就可以畫出橢圓或是長方形
另外,由於WPF會自動作重繪的動作,所以將物件加入就可以了
而且加入的物件依然可以再做改動

--
參考資料
WPF幾何繪圖
WPF中,如何使用圖像API進行繪製