2009年11月6日 星期五

在WPF中自訂形狀(Shape)

在WPF的UI設計中,有時候會需要特殊形狀的控制項
可以使用ImageBrush以及透明背景的一張圖
將控制項(如Border)繪製成不規則形狀
(參考在WPF中使用影像筆刷(ImageBrush))
但是這種做法有個缺點,就是實際上的控制項並不是不規則的
所以如果要做成鑲嵌在一起的一些控制項就會因為互相覆蓋而造成問題

第二種就是用現有的基本形狀(Shape),如方形、橢圓等組合成新的控制項
可以利用Union, Xor, Intersect, Exclude兩兩組合
從而做出許多變化,例如

<Path Fill="Black">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Xor">
<CombinedGeometry.Geometry1>
<EllipseGeometry Center="50,50"
RadiusX="30"
RadiusY="30" />
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<EllipseGeometry Center="100,50"
RadiusX="30"
RadiusY="30" />
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>

上面就是兩個圓直接做Xor,顯示出來的圖會是

這樣的做法雖然已經可以做出一些很特別的控制項了
但是畢竟彈性還是有限,所以同樣使用Path,還可以用更彈性的方法來做
將上述例子中的CombinedGeometry中換成PathGeometry

<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure IsClosed="True"
StartPoint="10,10">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Point="50,50"
Size="50,50"
SweepDirection="Clockwise"/>
<LineSegment Point="50,10" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>

同樣的效果也可以用C#寫

private Path AddShape()
{
/// set path with stroke
Path p = new Path();
p.Stroke = Brushes.Black;
p.StrokeThickness = 1;

/// set path figure with start and IsClosed
PathFigure pf = new PathFigure();
pf.IsClosed = true;
pf.StartPoint = new Point(10,10);

/// add two segments
pf.Segments.Add(new ArcSegment(
new Point(50,50), new Size(50,50), 0,
false, SweepDirection.Clockwise, true));
pf.Segments.Add(new LineSegment(
new Point(50,10), true));

/// set the data as the new path figure
p.Data = new PathGeometry();
(p.Data as PathGeometry).Figures.Add(pf);

return p;
}

在這邊的路徑片段(Segment)都代表從現在的點到一個點
然後構成一整條路徑,再將其填滿,可分成
種類說明
LineSegment直線段,只需設定目的點(Point),即會從目前所在連成一條直線段
ArcSegment弧線段,除目的點(Point)外,還需要設定弧形大小(Size,橢圓的長短軸)、軸旋轉角度(RotationAngle,預設為0)、線段轉彎方向(SweepDirection),以及是否轉彎超過180度(IsLargeArc)
PolyLineSegment多線段,設定一組連續的點(Points),即會從目前所在依照順序連接起來
BezierSegment三次貝茲曲線,設定目的點(Point3)以及兩個控制點(Point1, Point2)
PolyBezierSegment多段三次貝茲曲線,設定一組連續點(Points),其中每三個為一小組,代表經過兩控制點到目的點的一段三次貝茲曲線
QuadraticBezierSegment二次貝茲曲線,設定目的點(Point2)以及一個控制點(Point1)
PolyQuadraticBezierSegment多段二次貝茲曲線,設定一組連續點(Points),其中每兩個為一小組,代表經過控制點到目的點的一段二次貝茲曲線

--
參考資料
Path 類別
HOW TO:建立複合圖案
HOW TO:使用 PathGeometry 建立圖案
PathSegment 類別

在C#中自訂應用程式圖示(Icon)

寫應用程式在開發完之後
常常會希望自己的程式有一個不一樣的圖示以示區別
在工具列中找到專案->屬性

然後設定應用程式->資源->圖示和資訊清單,選擇"圖示"右邊的開檔按鈕

然後選擇自己喜歡的圖示檔(*.ico)即可

如果喜歡的圖示並不是ico格式,也可以用繪圖軟體(如GIMP, Freeware)
轉換格式(開啟後直接另存成ico檔案)即可使用

在WPF中使用漸層筆刷(GradientBrush)

在WPF中要達到漸層效果非常簡單
只要在想要展示漸層效果的地方使用漸層筆刷上色就可以了
筆刷的分類可見在WPF中的筆刷(Brush)

而漸層筆刷分為兩個實體類別
第一類是線性漸層(LinearGradientBrush)
也就是遵照一個線性的方向去漸層

<Rectangle>
<Rectangle.Fill>
<!--gradually change according a vector-->
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,0"
>
<GradientStop Offset="0" Color="Black" />
<GradientStop Offset="1" Color="White" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>

結果就像

StartPoint和EndPoint都是以該筆刷作用範圍的左上為(0,0),右下為(1,1)
除非想要做接合效果(好幾個物件像是連在一起),否則不會超出1
另外GradientStop.Offset是0-1之間的一個小數
相對於StartPoint為0,EndPoint為1的向量相對位置
上面的例子裡只有設起始點的顏色,所以就是單純的漸進
那如果增加一個

<GradientStop Offset="0.5" Color="Red" />

那就會變成

另外一類則是放射漸層(RadialGradientBrush)
也就是從一個點放射向外的漸層筆刷

<Rectangle>
<Rectangle.Fill>
<RadialGradientBrush GradientOrigin="0.5,0.5"
Center="0.5,0.5"
RadiusX="0.5"
RadiusY="0.5"
>
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="White" Offset="1" />
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>

這裡面的座標跟線性漸層筆刷是一樣的
Center, RadiusX和RadiusY構成一個圓
然後從GradientOrigin為放射中心做出漸層

這邊的圓形會因為Brush作用的範圍而變形
另外,如果放射中心在圓的外面,那就會拉出一個椎形
比如將上面例子中的GradientOrigin改為"0,0"
那結果就會變成

--
參考資料
LinearGradientBrush 類別
RadialGradientBrush 類別

在WPF中使用影像筆刷(ImageBrush)

在WPF中可以透過筆刷的設定,將圖片直接畫在各種使用筆刷的物件上面
比如說在文字方塊設定背景

<TextBlock FontSize="50">
<TextBlock.Background>
<ImageBrush ImageSource="img.jpg" />
</TextBlock.Background>
</TextBlock>

那顯示出來的會是

這時候可以換成設定前景,也就是文字色彩

<TextBlock FontSize="50">
<TextBlock.Foreground>
<ImageBrush ImageSource="img.jpg" />
</TextBlock.Foreground>
</TextBlock>

那結果就會變成

透過這樣的動作就可以做出很多變化的介面
不過這邊要注意一個問題
就是使用影像筆刷的圖可以有透明的背景
畫出來的時候當然那些區域會是透明的,不過大小並不會改變
比如說當你將很多張透明背景的圖疊在一起的時候
下面的圖基本上是沒辦法接收到滑鼠事件的

--
參考資料
ImageBrush 類別

在WPF中的筆刷(Brush)

在WPF中的筆刷有很多種,常用的如下
筆刷說明
SolidColorBrush單色筆刷,可以用直接設定ARGB或是內建的Brushes
GradientBrush漸層筆刷,分為線性漸層(LinearGradientBrush)以及放射漸層(RadialGradientBrush)
ImageBrush影像筆刷,可以將一張圖片塗上某個物件的背景或前景
參考在WPF中使用影像筆刷(ImageBrush)
DrawingBrush可以包含圖案、影像、文字及媒體等繪圖物件
VisualBrush直接用視覺物件來繪製

筆刷可以用的地方其實很多,大部份的控制項都有背景(Background)
有些會有前景(Foreground, 如TextBlock)
還有些則是填滿(Fill, 如Retangle)
透過筆刷的交互作用,可以讓WPF的程式變得非常多變而彈性

--
參考資料
WPF 筆刷概觀

2009年10月14日 星期三

在C#中使用Mutex

在多執行緒(Multi-Thread)程式中
執行緒之間常常會有相依性,所以需要一些指標
讓搶到指標的人才能存取某個資源或是執行動作
這時候就會需要用到Mutex

Mutex的使用其實非常簡單

/// new a mutex with name
Mutex m = new Mutex(false, "m");
m.WaitOne();

如果要釋放這個資源讓別人使用的時候

m.ReleaseMutex();

不過這邊有很多需要注意的地方
首先,Mutex分為有名字及無名兩種
在自己程式中可以使用無名的Mutex
但是有命名的Mutex會在程式間也有效
所以可以做出讓程式無法重覆啟動的檢查
例如在wpf程式中增加應用程式的建構子(constructor)

/// declare mutex in class to last it until app end
Mutex m = new Mutex(false, "m");
App()
{
/// if the mutex exists, return false in 1 ms
if (!m.WaitOne(1))
{
MessageBox.Show("program is current running!");
Shutdown();
}
}

這時候這支程式就沒辦法重覆啟動了
但是這時候要注意到第二個重點了
因為C#會自動作回收(garbage collection)的動作
如果Mutex是宣告在建構子裡面,那在建構成功後就會被回收了
所以即使沒有釋放(ReleaseMutex)依舊會自動失效
也就是沒辦法阻止重覆啟動

第三個重點就是WaitOne的等待時間了
如果裡面沒有任何的參數,那就是直接等待到有空為止
這有可能會讓程式直接停住
所以最簡單的做法就是增加一個很短的等待時間
並在回傳失敗的地方先做一些處理(比如等待的警告之類的)
之後就放心大膽的等下去~~

另外,如果是有命名的Mutex
那會跟Win32的CreateMutex有相同的效果
也就是兩者使用相同命名的時候也會互相等待

--
參考資料
Threading Tutorial Example 4: Using the Mutex object
Mutex 類別

2009年10月12日 星期一

在WPF的XAML裡面使用動畫

在WPF裡面使用動畫可以參考在WPF中加入動畫
不過有時候動畫比較單純,而不需要非常精細的轉換計算動作的話
其實可以直接將動畫宣告在XAML裡面,讓他成為物件的附屬特性

<Image.Triggers>
<!--begin animation after loaded-->
<EventTrigger RoutedEvent="Image.Loaded">
<BeginStoryboard Name="bs">
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="1" To="0"
Duration="0:0:0.5"
AutoReverse="True"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<!--pause animation after leftbutton down-->
<EventTrigger
RoutedEvent="Image.MouseLeftButtonDown">
<PauseStoryboard BeginStoryboardName="bs" />
</EventTrigger>
<!--resume animation after leftbutton up-->
<EventTrigger
RoutedEvent="Image.MouseLeftButtonUp">
<ResumeStoryboard BeginStoryboardName="bs" />
</EventTrigger>
<!--stop animation after rightbutton up-->
<EventTrigger
RoutedEvent="Image.MouseRightButtonUp">
<StopStoryboard BeginStoryboardName="bs" />
</EventTrigger>
</Image.Triggers>

被設定上面屬性的影像(Image)會在載入之後就不斷的忽隱忽現

要在XAML裡面設定動畫要先確定是對哪個控制項
並新增該控制項的觸發器(Trigger),可分為幾類
觸發器說明
EventTrigger在指定事件發生時觸發
參考HOW TO:使用腳本建立屬性的動畫
Trigger在指定屬性值變更時觸發
參考HOW TO:當屬性值變更時觸發動畫
DataTrigger在指定資料變更時觸發
參考HOW TO:資料變更時觸發動畫

設定觸發器之後,選擇要開始撥放或是對已經在撥放的動畫做動作
比較常見的有幾種
腳本操作說明
BeginStoryboard開始播放腳本,並在裡面設定動畫的細節
PauseStoryboard暫停播放腳本
ResumeStoryboard繼續播放腳本
StopStoryboard結束播放腳本

動畫也一樣可以選擇種類,可以參考在WPF中加入動畫

--
參考資料
腳本概觀
動畫概觀
HOW TO:在腳本開始後使用事件觸發程式進行控制

2009年10月5日 星期一

在WPF中使用背景工作(BackgroundWorker)

在程式中有時候會需要執行一些比較花時間的工作
比如連上網路下載或上傳東西
但在和網路溝通及傳輸的時候,畫面上的工作就會暫停
這時候會想要提示使用者程式還在工作,比如說在msn登入的時候撥放動畫

這時候就要用到背景工作了,實際上這就是一種多執行緒處理
只是.net提供了方便的包裝,讓主執行緒在背景工作執行中得知進度
BackgroundWorker主要有三個事件處理
事件說明
DoWork在背景執行的工作,為另外一個執行緒,不可以變更介面畫面(介面變更在後面兩個事件中處理)
RunWorkerCompleted背景工作完成後的處理,因為背景工作是非同步處理,所以在完成後會觸發這事件來啟動主執行緒接下來的動作,如登入完成後執行切換畫面
ProgressChanged如果動作費時太長,有時候會希望將進度反應在介面上,在這裡改變介面畫面

我們來做一個範例(參考HOW TO:使用幕後背景工作)
視窗中只有一個按鈕(Button)

<Window ...>
<Button Name="b" Click="Button_Click" />
</Window>

在按鈕按下的時候開始設定並執行背景工作

private void Button_Click(
object sender, RoutedEventArgs e)
{
/// new and allow cancel and report progress
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.WorkerReportsProgress = true;

/// add the event handler for each progress
bw.DoWork += new DoWorkEventHandler(DoWork);
bw.ProgressChanged +=
new ProgressChangedEventHandler(DuringWork);
bw.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(AfterWork);

/// start the background work
bw.RunWorkerAsync();
}

事件處理定義如下

void DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker w = sender as BackgroundWorker;

for (int i = 1; i <= 10; i++)
{
/// check if the work is cancelled
if (w.CancellationPending == true)
{
e.Cancel = true;
break;
}

/// perform a time consuming operation
Thread.Sleep(500);

/// report progress in percentage
w.ReportProgress(i * 10);
}
}

void DuringWork(
object sender, ProgressChangedEventArgs e)
{
/// reflect the change of progress in UI
b.Content =
e.ProgressPercentage.ToString() + "%";
}

void AfterWork(
object sender, RunWorkerCompletedEventArgs e)
{
/// reflect the result after background work
if (e.Cancelled == true)
{
b.Content = "Canceled!";
}
else if (!(e.Error == null))
{
b.Content = ("Error: " + e.Error.Message);
}
else
{
b.Content = "Done!";
}
}

這個程式執行後,按下按鈕的時候
就會開始執行DoWork裡面的動作(每半秒更新一下進度)
並在每次更新進度的時候更新Button上面顯示的文字

另外,有時候背景程式需要使用到介面輸入的資訊
(比如登入畫面,需要將帳號密碼傳上網路)
但是DoWork裡面不能使用介面的物件
這時候可以透過argument(object形態,可以傳任意型別)傳入
所以啟動背景工作的呼叫改寫成

private void Button_Click(
object sender, RoutedEventArgs e)
{
/// new and allow cancel and report progress
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.WorkerReportsProgress = true;

/// add the event handler for each progress
bw.DoWork += new DoWorkEventHandler(DoWork);
bw.ProgressChanged +=
new ProgressChangedEventHandler(DuringWork);
bw.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(AfterWork);

/// start the background work with argument
bw.RunWorkerAsync(b.Content);
}

而在DoWork裡面則是用e.Argument將傳入的參數讀出
如果有超過一個以上的參數,也可以利用ArrayList先打包起來再一起傳

--
參考資料
HOW TO:使用幕後背景工作
BackgroundWorker 類別

2009年9月28日 星期一

在WPF中關閉觸控指標(Disable Touch Pointer)

在Win Vista之後,微軟針對觸控做了不少改變
其中很明顯的改變就是增加了觸控指標(Touch Pointer)
還有輕拂手勢(Flicks Gesture)
但這兩個新增的東西對於程式開發人員常常造成很討厭的干擾
(也許這就是為什麼在Win7上面這個功能不再是預設屬性了)

在早期的Win32視窗以及.net Form的應用程式之中
有提供底層的API來將這些有關觸控的新增訊息給取消的機制
請參考Disable flicks and mouse pointer

但要把這個方法套用到WPF上面的時候會遇到幾個問題
首先是要使用到unmanagement的Win32 API
這會對WPF的封閉性及安全性造成一些顧慮
另外則是在WPF中已經取消handle這個概念了
所以最多只能針對一整個視窗取消這個功能
要對單一物件基本上是不可能的

幸好WPF中另外提供了簡易的方式
對於希望關掉該功能的控制項,更改對應的屬性

private void DisableTouchStylus(UIElement u)
{
/// disable flicks gesture
Stylus.SetIsFlicksEnabled(u, false);

/// disable press and hold gesture
Stylus.SetIsPressAndHoldEnabled(u, false);

/// disable tap gesture
Stylus.SetIsTapFeedbackEnabled(u, false);

/// disable touch pointer
Stylus.SetIsTouchFeedbackEnabled(u, false);
}

另外,也可以在該控制項的XAML宣告裡面直接更改
例如希望關掉Canvas的觸控效果的話

<Canvas Stylus.IsFlicksEnabled="False"
Stylus.IsPressAndHoldEnabled="False"
Stylus.IsTapFeedbackEnabled="False"
Stylus.IsTouchFeedbackEnabled="False" />

注意,在WPF裡面設定的效果都會套用到所有的子元件
所以如果需要整個視窗都關閉的話,只需要在最外層的視窗設定就好了

--
參考資料
Disable flicks and mouse pointer
Stylus.IsFlicksEnabled 附加屬性

2009年9月25日 星期五

在WPF中自訂事件(Event)

在WPF中自訂控制項很容易
但是如果自訂控制項的時候除了提供一些函式之外
有時候希望可以提供一些事件

自訂事件的概念基本上跟callback的概念是一樣的
也就是註冊一個事件處理函式給某個事件
當事件發生的時候可以觸發該事件處理函式(常見的像MouseDown, Click等)
.net2.0之後提供了很方便的EventHandler機制

參考在WPF中新增自訂控制項,我們先制定了一個新的控制項
這個控制項裡面只有一個Rectangle

<UserControl x:Class="test_wpf.NewButton"
...>
<Rectangle />
</UserControl>

因為Rectangle沒有Click事件,所以我們就幫這個控制項新增Click事件

首先,每個事件處理都具有兩個參數
sender代表是哪個物件被觸發了這個事件
而e則是代表該事件的一些特性(比如滑鼠事件都會攜帶滑鼠訊息)
所以如果不希望用已經被制定好的事件屬性的話
那就自訂一個繼承EventArgs的事件屬性

public class MsgEventArgs : EventArgs
{
/// the only string argument
private string _Msg;

/// constructor of the event argument
public MsgEventArgs(string s)
{
_Msg = s;
}

/// define a readonly string argument
public string Msg
{
get { return _Msg; }
}
}

這邊我們定義了一個只帶有一個字串的事件屬性
但如果沒有特別需要傳遞的資訊,那也可以直接用EventArgs類別
就不需要做上面這步驟

之後再新增的這個控制項中宣告一個使用這個事件屬性的事件

public partial class NewButton : UserControl
{
/// an event with our custom event argment
public event EventHandler<MsgEventArgs> Click;

/// original constructor
public NewButton()
{
InitializeComponent();
}
}

同樣的,如果不需要自訂自訂屬性的話
紅字的部分就可以省略

一般習慣來說,會為了每個事件作一個事件觸發函式
這函式可以宣告成virtual,以便之後如果有繼承的子類別可以直接修改

protected virtual void OnClick(MsgEventArgs e)
{
/// if there exists handler for the event, run it
if (Click != null)
{
Click(this, e);
}
}

接下來就是在適當的時間觸發這個事件就好了
因為是Click,所以我們在滑鼠按下又放開的時候觸發
(這邊只是示範,並不是一般嚴謹定義的Click)

/// flag to show if left button is down
private bool IsDown = false;

private void Rectangle_PreviewMouseLeftButtonDown(
object sender, MouseButtonEventArgs e)
{
/// left button is down
IsDown = true;
}

private void Rectangle_PreviewMouseLeftButtonUp(
object sender, MouseButtonEventArgs e)
{
if (IsDown)
{
/// left button is up after down, click
OnClick(new MsgEventArgs("Button Click"));
}

/// reset the flag
IsDown = false;
}

這樣就完成宣告了,接下來就是在使用這個控制項的時候
(參考在WPF的XAML裡面使用自訂的控制項)
定義相關的事件處理函式

<Window ...
xmlns:test_wpf="clr-namespace:test_wpf">
<test_wpf:NewButton Click="Btn_Click"/>
</Window>


private void Btn_Click(object sender, MsgEventArgs e)
{
Console.WriteLine(e.Msg);
}

因為我們只有一個message對應的事件處理
所以這邊的事件處理,我們只將他show到console上面
(可以參考在WPF中滑鼠事件(Event)傳遞中的設定)

--
參考資料
HOW TO:發行符合 .NET Framework 方針的事件 (C# 程式設計手冊)
C#的事件處理和自定義事件(轉)
C# - Delgate(委派)和Event(事件)

2009年9月23日 星期三

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

透過在WPF中跨執行緒的元件操作所說的方式
可以很容易的在不同的執行緒(Thread)中更新使用者介面(UI)
不過如果當更新的動作太過頻繁,就會發現整個反應變慢
原因就是Dispatcher.Invoke()這個函式是同步的更新
也就是當呼叫的時候會等待更新完成才會回傳
這時候可以改採用非同步的方法,最簡單的方式就是改用BeginInvoke()
假設被用到的函式是

void Func(Type Param);

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

Func(p);

直接改成(注意這邊的參數順序跟Invoke不太一樣)

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

如此一來,呼叫端的程式就不會等到更新完成才接下去做事
這時候會發現被呼叫的頻率回覆了,但可能會延遲一段時間後一次更新

另外如果是一些特別的情形,還有幾種主動的非同步方式
例如使用這個函式的地方並不是固定的
那可以先檢查是否有處理的權限再作呼叫

if (!Dispatcher.CheckAccess())
{
Dispatcher.Invoke(DispatcherPriority.Normal,
new Action<Type>(Func), p);
}
else
{
Func(p);
}

這樣一來,就不是每一次都會把更新動作放進更新的序列中
而是將可以執行的部分直接動作,只做必要的Invoke
而這時要注意不要用BeginInvoke,因為執行順序可能會不符預期

另外一種就更特別,如果其實希望做的更新可以整合在一起一次做的話
比如原本是用迴圈呼叫Invoke來做更新
那就看看可不可以將迴圈放進Invoke的函式裡面
也就是一次更新多一些,而不是放一堆更新任務到序列中

--
參考資料
Dispatcher.BeginInvoke 方法
Invoke != Dispatcher.Invoke
Dispatcher.CheckAccess 方法

2009年9月18日 星期五

在WPF中滑鼠事件(Event)傳遞 - 列表框(ListBox)篇

在WPF中滑鼠事件(Event)傳遞中我們介紹了基本的滑鼠事件傳遞
按鈕篇則是在有按鈕的視窗中事件是如何傳遞
這邊我們再舉一個例子,列表框(ListBox),例如

<Window ... Name="Win">
<Grid Name="Grd">
<ListBox Name="Lsb">
<ListBoxItem>
<Rectangle Name="Rec" />
</ListBoxItem>
</ListBox>
</Grid>
</Window>

這邊為了簡化,所以列表框只有一個物件Rec
我們一樣將所有的物件的滑鼠事件都套用相同的處理函式
依照在WPF中滑鼠事件(Event)傳遞所作的設定
出來的結果是

Win : PreviewMouseLeftButtonDown
Win : PreviewMouseDown
Grd : PreviewMouseLeftButtonDown
Grd : PreviewMouseDown
Lsb : PreviewMouseLeftButtonDown
Lsb : PreviewMouseDown
Rec : PreviewMouseLeftButtonDown
Rec : PreviewMouseDown
Rec : MouseLeftButtonDown
Rec : MouseDown
Win : PreviewMouseLeftButtonUp
Win : PreviewMouseUp
Grd : PreviewMouseLeftButtonUp
Grd : PreviewMouseUp
Lsb : PreviewMouseLeftButtonUp
Lsb : PreviewMouseUp
Rec : PreviewMouseLeftButtonUp
Rec : PreviewMouseUp
Rec : MouseLeftButtonUp
Rec : MouseUp
Lsb : MouseLeftButtonUp
Lsb : MouseUp
Grd : MouseLeftButtonUp
Grd : MouseUp
Win : MouseLeftButtonUp
Win : MouseUp

比較在按鈕篇的事件傳遞,我們可以發現,在Down的流程是一模一樣的
可是Up卻全部都回傳了(包括preview和真實的事件)

這邊可以這樣解釋,由於滑鼠按下的時候,列表框處理了按下的動作
選取了某個物件,並將該事件標示成已處理
而在滑鼠起來的時候,則因為選取物件的動作已經處理完成
所以就不再作處理,而直接回傳,所以上層物件都會收到Up的事件

同樣的,我們也可以依照按鈕篇將事件強制處理,在這邊就不贅述了

在WPF中滑鼠事件(Event)傳遞 - 按鈕(Button)篇

在WPF中滑鼠事件(Event)傳遞中我們提到基本的滑鼠事件傳遞
但是如果這時候視窗中有一個按鈕(Button),例如

<Window ... Name="Win">
<Grid Name="Grd">
<Button Name="Btn">
<Rectangle Name="Rec" />
</Button>
</Grid>
</Window>

同樣將所有的滑鼠事件都套用到相同的事件處理函式
依照在WPF中滑鼠事件(Event)傳遞所作的設定
出來的結果會是

Win : PreviewMouseLeftButtonDown
Win : PreviewMouseDown
Grd : PreviewMouseLeftButtonDown
Grd : PreviewMouseDown
Btn : PreviewMouseLeftButtonDown
Btn : PreviewMouseDown
Rec : PreviewMouseLeftButtonDown
Rec : PreviewMouseDown
Rec : MouseLeftButtonDown
Rec : MouseDown
Win : PreviewMouseLeftButtonUp
Win : PreviewMouseUp
Grd : PreviewMouseLeftButtonUp
Grd : PreviewMouseUp
Btn : PreviewMouseLeftButtonUp
Btn : PreviewMouseUp
Btn : Click

我們這時候會觀察到一個奇怪的現像
就是在Down的過程時,preview的事件都還是出來了
不過在Rec回傳給Btn之後,MouseDown事件就不見了
而Up的地方更奇怪
Btn在preview完後,竟然直接出現了Click
而Rec甚至連preview都沒有收到

這邊的解釋是,在Down的時候,preview的事件順利的傳遞到了Rec
而在回傳的時候,因為Btn發現這是左鍵的Down
所以將這個事件標示為已處理(handled)再回傳
而之後Grd和Win再收到的就是已處理過的事件,所以他們就不會再做動作

而在Up的階段,preview的事件傳到了Btn的時候
他就已經發現現在應該是Click發生的時候了
所以他就處理Click的事件,然後一樣標示成已處理並直接回傳
所以Rec就什麼都收不到,而上層的Grd和Win則是收到已處理過的事件
所以它們也不再做動作了

如果仍然希望已處理的事件可以觸發事件處理
那就要用UIElement.AddHandler()來加入事件處理,例如

Btn.AddHandler(
/// the event we want to handle
Button.MouseLeftButtonDownEvent,
/// the event handler be set
new MouseButtonEventHandler(MouseEH),
/// handle the evnet even if it is handled
true);

這裡第三個參數是handledEventsToo,是否需要處理已處理事件
在一般的加入事件處理(不論XAML或C#)會採用預設值false
而我們因為希望即使是已處理事件也要觸發事件,所以設成true

如果將所有元件的MouseLeftButtonDown, MouseDown,
MouseLeftButtonUp, MouseUp都利用這種方式來加入事件處理
那輸出結果會變成

Win : PreviewMouseLeftButtonDown
Win : PreviewMouseDown
Grd : PreviewMouseLeftButtonDown
Grd : PreviewMouseDown
Btn : PreviewMouseLeftButtonDown
Btn : PreviewMouseDown
Rec : PreviewMouseLeftButtonDown
Rec : PreviewMouseDown
Rec : MouseLeftButtonDown
Rec : MouseDown
Btn : MouseLeftButtonDown
Btn : MouseDown
Grd : MouseLeftButtonDown
Grd : MouseDown
Win : MouseLeftButtonDown
Win : MouseDown
Win : PreviewMouseLeftButtonUp
Win : PreviewMouseUp
Grd : PreviewMouseLeftButtonUp
Grd : PreviewMouseUp
Btn : PreviewMouseLeftButtonUp
Btn : PreviewMouseUp
Btn : Click
Btn : MouseLeftButtonUp
Btn : MouseUp
Grd : MouseLeftButtonUp
Grd : MouseUp
Win : MouseLeftButtonUp
Win : MouseUp

記得,如果這時候原本的事件處理仍然有寫的話
那就會變成同時加入只處理未處理事件,以及處理所有事件的兩個事件處理
所以Rec的Down就會重複兩次(因為每次加入都會視為不同的delegate)

--
參考資料
wpf button 事件的觸發順序
UIElement.AddHandler 方法 (RoutedEvent, Delegate, Boolean)

2009年9月17日 星期四

在WPF中滑鼠事件(Mouse Event)傳遞

WPF和.net form的一個很大的差異就是
大部分的控制項都已經沒有Click(除了按鈕(Button)之外)
猜測大概是因為希望未來在觸控(Touch)的世界裡面可以順利接軌吧

另外,不像.net form裡面,只有最表面(可視)的部分才能接收到事件
針對幾乎所有的事件(Event)都有Preview的機制
也就是說在上層(Parent)先收到Preview,然後傳遞到下層(Child)
等到沒有下層,或是已經引發了事件發生的話再作回傳
然後由下層先引發真正的事件,再繼續回傳

舉例來說,如果有一個架構如下

<Window ... Name="Win">
<Grid Name="Grd">
<Rectangle Name="Rec" />
</Grid>
</Window>

而我們觀察的滑鼠事件如下
事件
PreviewMouseLeftButtonDown
PreviewMouseDown
PreviewMouseLeftButtonUp
PreviewMouseUp
MouseLeftButtonDown
MouseDown
MouseLeftButtonUp
MouseUp
Click

將想要觀察的滑鼠事件都加上相同的事件處理

private void MouseEH(object sender, RoutedEventArgs e)
{
Console.WriteLine(
(sender as FrameworkElement).Name + " : " +
e.RoutedEvent.Name);
}

因為這邊用到Console,所以要特別去設定為Console的模式
在工具列中找到專案->屬性

然後設定應用程式->輸出類型,選擇"主控台應用程式"

然後在按鈕中間的方塊按下去,會得到下面的結果

Win : PreviewMouseLeftButtonDown
Win : PreviewMouseDown
Grd : PreviewMouseLeftButtonDown
Grd : PreviewMouseDown
Rec : PreviewMouseLeftButtonDown
Rec : PreviewMouseDown
Rec : MouseLeftButtonDown
Rec : MouseDown
Grd : MouseLeftButtonDown
Grd : MouseDown
Win : MouseLeftButtonDown
Win : MouseDown
Win : PreviewMouseLeftButtonUp
Win : PreviewMouseUp
Grd : PreviewMouseLeftButtonUp
Grd : PreviewMouseUp
Rec : PreviewMouseLeftButtonUp
Rec : PreviewMouseUp
Rec : MouseLeftButtonUp
Rec : MouseUp
Grd : MouseLeftButtonUp
Grd : MouseUp
Win : MouseLeftButtonUp
Win : MouseUp

從這個例子可以看出最簡單的WPF滑鼠事件傳遞順序
更複雜的例子可以參考按鈕(Button)篇以及列表框(ListBox)篇

--
參考資料
WPF的Event

在WPF中加入主畫面(Key Frame)驅動的動畫

在WPF中加入動畫有簡單的動畫例子
不過如果使用者希望作一些比較複雜的動作,比如變紅之後變黃再變綠
那就沒辦法用單純的From和To來執行
這時候可以採用主畫面(Key Frame)的方法來設定動畫
依照主畫面轉換的效果一共可以分成三類
類別說明
discrete在設定的時間直接從前一個主畫面切換成此畫面,字串類別的動畫僅有此種主畫面
linear在設定的時間之前,利用線性內插(linear interpolation),從前一個主畫面漸進切換成此畫面
spline在設定的時間之前,利用樣條插值(splined interpolation),以指定Beizer曲線作為變化軌跡,從前一個主畫面漸進切換成此畫面

下面的例子會讓按鈕的背景顏色隨著時間而有不同變化

private void ApplyAnimation(Button b)
{
/// init a color animation using key frames
ColorAnimationUsingKeyFrames cak =
new ColorAnimationUsingKeyFrames();

/// set the total duration of each animation
cak.Duration = TimeSpan.FromSeconds(6);

/// Smooth change from current color to red in the
/// first 2 sec

cak.KeyFrames.Add(
new LinearColorKeyFrame(
Brushes.Red.Color,
TimeSpan.FromSeconds(2)));

/// Sudden jump to yellow at 2.5 sec
cak.KeyFrames.Add(
new DiscreteColorKeyFrame(
Brushes.Yellow.Color,
TimeSpan.FromSeconds(2.5)));

/// By the keyspline property, smooth change from
/// yellow to green in the next 2 sec

cak.KeyFrames.Add(
new SplineColorKeyFrame(
Brushes.Green.Color,
TimeSpan.FromSeconds(4.5),
new KeySpline(0.6,0,0.9,0)));

/// set the background of input button as a solid
/// color brush and apply the animation on it

b.Background = new SolidColorBrush();
(b.Background as SolidColorBrush).BeginAnimation(
SolidColorBrush.ColorProperty, cak);
}

--
參考資料
ColorAnimationUsingKeyFrames 類別
樣條插值- 維基百科,自由的百科全書

2009年9月16日 星期三

在WPF中解除動畫綁定的屬性

在WPF中對某個屬性使用動畫的時候
原本的值會被忽略而直接由動畫管理需要呈現的數值
除了下面幾種情形,常會發生數據被綁定的現象

一種是在FillBehavior被設定為FillBehavior.Stop時
在動畫完成後,變化的屬性會自動被設定回原本的值
在此之後再對該值作動作則不會被動畫影響到

第二種則是僅使用From
則該屬性原本的值會被設定為終點
所以在此之後(甚至動畫執行期間)對該值做動作仍會發生作用

第三種是在動畫執行過後重新指定綁定的動畫(設成null即解除動畫)
不過這種方法跟第一種方法一樣
重新綁定的時候,該值會被設定成原本的值
如果需要將值保留下來,則在重新綁定之前先存起來再重設回來

private void ReleaseAnimation(Button b)
{
/// save width of button and set it back
double w = b.Width;
b.Width = w;

/// apply null animation to button
b.BeginAnimation(Button.WidthProperty, null);
}

此時需注意,重新設值必須放在重新綁定動畫之前
因為在還沒有解除綁定前,設定會更改真實的值
所以在解除綁定後,就會直接變成該值
如果先解除綁定在設定新值,則可能會出現閃爍或跳躍的影響

--
參考資料
WPF/Silverlight:解鎖被Storyboard束縛的關聯屬性

在WPF中加入動畫

WPF整合了DirectX的動態處理,內建了動畫的控制項
不過對於原本沒有在使用動畫或是flash的程式設計師
其實還是很難以想像要怎麼使用動畫的元件

基本上WPF提供的動畫依照動作的素材可以分成四類
表示在時間軸上所對應屬性的變化
素材說明
double套用在數值類型的屬性上
color套用在筆刷(顏色)屬性上
point套用在位置(座標)屬性上
string套用在字串屬性上

另外,也可以依照動畫的軌跡類型做分類
軌跡說明
僅有起點與終點將對應的屬性由起點變化到終點,字串類形(string)無此此軌跡
設定主畫面(KeyFrame)設定需要經過的主畫面,可選擇性設定每張主畫面應該出現的時間點,並可以選擇每個主畫面的切換效果
給定路徑(Path)給定屬性應該變化的路徑,僅適用於數值類型(double)以及位置類型(point)

如果希望設定一個按鈕的寬度由100到200

private void ApplyAnimation(Button b)
{
/// init a double animation
DoubleAnimation da = new DoubleAnimation();

/// set the from and to value of button width
da.From = 100;
da.To = 200;

/// set the duration of each animation
da.Duration = TimeSpan.FromSeconds(3);

/// apply the double animation to width of button
b.BeginAnimation(Button.WidthProperty, da);
}

這是最簡單的動畫了,只有設定起終點以及花費的時間
在DoubleAnimation中設定起終點的方式還有好幾種
指定欄位說明
From和To從From的數值,一直變化到To的數值
From和By從From的數值,一直變化到From+By的數值
From從From的數值,一直變化到原有的值(原本必須要有值)
To從原有的值(原本必須要有值),一直變化到To的數值
By從原有的值(原本必須要有值),一直變化到該值+By的數值

另外還有一些常用的設定可以參考下表
屬性說明
AccelerationRatio加速度,從0加速到最大的變化比率,0~1
DecelerationRatio減速度,從最大速度減速到0的變化比率,0~1, AccelerationRatio與DecelerationRatio總和亦需介於0~1之間
AutoReverse動畫結束時,再將起始點互換播放一次
RepeatBehavior重複設定,設為RepeatBehavior.Forever即會不斷重複播放(包含AutoReverse的設定)
FillBehavior動畫完成後的動作(若RepeatBehavior被設定,因為動畫沒有完成的時候,所以不會作用),若設成FillBehavior.Stop則會讓關聯的屬性回覆到原有的值(並非From的值),若不設定則預設為FillBehavior.HoldEnd,也就是保持終點的值

另外,這邊提供的是最簡易的加入動畫的方法
如果想要知道怎麼使用主畫面來設定動畫,可以參考
在WPF中加入主畫面(Key Frame)驅動的動畫
在XAML中還需要加入腳本(StoryBoard)才可以使用動畫
可以參考在WPF的XAML裡面使用動畫

--
參考資料
動畫概觀
HOW TO:不使用腳本而建立屬性的動畫
DoubleAnimation 類別
DoubleAnimation 成員

2009年9月11日 星期五

在WPF中的畫面重排(Measure & Arrange)

WPF相較於前代的Form最大的變化,就是把展示的部分做了很大的改動
會多做一些自動化(如不需要重繪)或最佳化(如版面的非同步配置)
在性能提升的同時,也會造成一些麻煩,就是比較難以自我掌控狀況

在WPF裡面的UIElement提供了Measure和Arrange
讓程式開發者可以自主的控制版面配置

Measure提供的功能是讓系統知道這個控制項需要多大的空間
預設會遞迴的向他裡面所包含的子控制項問下去,之後再回報所需要的大小
Arrange提供的功能則是將Measure出來的位置擺到選定的位置上
這個位置是相對於父物件而言的,預設中同樣也會遞迴的將子控制項排好

而在使用這兩個函式的時候主要有兩種方式
一個是覆寫掉系統中預設的函式,讓這個元件依照你的設計來排版
這個方式主要用在寫自訂的控制項時,而且只能安排到你的下一層
而再向下的子控制項就只能依照預設的方式處理了(除非底層也被覆寫)

第二種方式則是直接呼叫,這種方法並不會真正的改變控制項的屬性
只是暫時的排版而已,常用在輸出圖檔的時候
可以參考在WPF中存圖檔的問題(RenderTargetBitmap)
呼叫Measure的時候要傳入一個Size的物件
傳入的大小如果比預設需求小,那之後WPF會自動發生一次重新測量及排列
所以在畫面上完全看不出來有被更改過
(但是如果在同一個函式中有輸出圖檔,該圖會依照改動的大小排列)
預設需求會包含物件的大小,以及設定的margin或是border thinkness等

呼叫Arrange的時候要傳入一個Rect,位置是相對於父物件的原點
將前一次Measuer的大小塞入這個Arrange的大小中
之後會依照該控制項原本就設定好的Margin等屬性自動排列

需要注意的有三點
第一,除了第一次呼叫Arrange之前須要先Measure之外(系統做的也算)
之後都可以只呼叫Arrange來重排而不需要重新測量大小,比如對齊
第二,只要呼叫了Measure就必須在後面接一個Arrange
否則系統也會自動加上一個預設的Arrange來依照此大小重排
第三,此兩個函式分別有各自的佇列(Queue)
但任何的Measure都會讓尚未處理的Arrange直接失效

--
參考資料
UIElement.Measure 方法
UIElement.Arrange 方法

2009年9月10日 星期四

在WPF中存圖檔的問題(RenderTargetBitmap)

在WPF裡面要把一個視覺化物件給存成圖檔
需要使用到RenderTargetBitmap,可以參考在WPF中存讀圖檔

但是有時候會發現存出來的圖檔被位移或是根本就沒有影像
這是因為RenderTargetBitmap(RTB)在處理視覺化物件的描繪(Render)時
會依照該物件和其父物件(Parent)的相關性做處理
也就是會因為該物件的Margin或其父物件的Padding或BorderThickness
如果完全不做處理,那就會以父物件的左上角為準,多出一個位移
雖然我覺得這應該算WPF的bug
但是依照MicroSoft的解釋RenderTargetBitmap layout offset influence
似乎覺得這是應該發生的,而且不會再改的感覺

基本上目前我知道的解決方法有三種
方法說明
加一層Border簡單,但是會改變視覺物件結構樹
用VisualBrush畫出不改動到原本架構,但需要多做一些繪製的處理
用Measure和Arrange暫時改變相對位置改動後需要手動改回,而且WPF有針對這兩個函式作最佳化處理,所以不容易瞭解要怎麼用

第一個方法最單純,因為RTB只針對父物件作位移
所以就直接在你需要描繪的物件外面直接包一層類似像Border的容器
而且把需要用的margin都移到這個容器
之後就可以直接依照RTB的使用方法直接用了

如果原本的物件是這樣放

<Grid>
<Canvas Margin="20" />
</Grid>

那就改成

<Grid>
<Border Margin="20">
<Canvas />
</Border>
</Grid>

這樣就可以用之前在在WPF中存讀圖檔提到的方法
將物件送進SaveTo()存起來

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

/// new a RenderTargetBitmap with actual size of c
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();
}

第二種方法則是先將該視覺物件畫出來
直接將想要繪製的物件傳入修改過的SaveTo()
只是在render之前先將視覺物件作一個轉換

private DrawingVisual ModifyToDrawingVisual(Visual v)
{
/// new a drawing visual and get its context
DrawingVisual dv = new DrawingVisual();
DrawingContext dc = dv.RenderOpen();

/// generate a visual brush by input, and paint
VisualBrush vb = new VisualBrush(v);
dc.DrawRectangle(vb, null, b);
dc.Close();

return dv;
}

這時候要注意,使用context的時候要記得做close()才會真正做動作
如果怕忘記的話可以改用using將使用的區塊包起來
而原本在SaveTo裡面的Render就改寫成

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


第三種方法是用Measure和Arrange暫時將位置換一下
一樣在render之前將視覺物件做個轉換

private void ModifyPosition(FrameworkElement fe)
{
/// get the size of the visual with margin
Size fs = new Size(
fe.ActualWidth +
fe.Margin.Left + fe.Margin.Right,
fe.ActualHeight +
fe.Margin.Top + fe.Margin.Bottom);

/// measure the visual with new size
fe.Measure(fs);

/// arrange the visual to align parent with (0,0)
fe.Arrange(new Rect(
-fe.Margin.Left, -fe.Margin.Top,
fs.Width, fs.Height));
}

這邊要注意這種方法只能套用在UIElement上面
另外要記得要把位置arrange回來

private void ModifyPositionBack(FrameworkElement fe)
{
/// remeasure a size smaller than need, wpf will
/// rearrange it to the original position

fe.Measure(new Size());
}

因為這時候measure的大小較實際上的大小為小
所以wpf會自動重新做排版,將位置對齊回去
而原本的render則是改寫成

/// render visual
ModifyPosition(v as FrameworkElement);
r.Render(v);
ModifyPositionBack(v as FrameworkElement);

如果對measure和arrange有興趣的可以參考UIElement.Measure 方法

--
參考資料
RenderTargetBitmap layout offset influence
RenderTargetBitmap tips
RenderTargetBitmap and XamChart - Broken, Redux

2009年9月9日 星期三

在C#中排序自訂型別陣列(Sorting custom array)

在C#中要對一些基本型態的陣列作排序相當簡單

/// init an integer array
int[] a = { 10, 9, 1, 2, 6, 5, 4 };

/// sort the array
Array.Sort(a);

這樣的方法可以用在已經有實作IComparison的基本型態上
例如int, string, 或是 DateTime...等

如果要針對自訂型態的物件陣列作排序
基本概念就是要能夠比較兩個物件的順序
假設自訂類別如下

public class T1
{
public int index;
public string name;
}

實作有兩種方法,其一就是將該類別實作IComparable
所以需要將類別定義改為

public class T1 : IComparable
{
public int index;
public string name;

/// implement IComparable interface
public int CompareTo(object obj)
{
return this.index - (obj as T1).index;
}
}

實作IComparable實際上需要做的就是CompareTo這個函式
也就是自己與一個傳入的obj比較,然後回傳一個整數
回傳說明
0兩者相等,順序無所謂
正數現在的物件(this)較比較對象(obj)為大,順序在後
負數現在的物件(this)較比較對象(obj)為小,順序在前

這樣實做出來的類別,就可以直接像基本型態一樣直接排序了

private void SortT1(T1[] t)
{
Array.Sort(t);
}

上述的方法有個缺點,排序T1這個類別只能使用一種方法
也就是如果有時候想要依照index排序,而有時又希望可以用name排序的話
就無法用這個方法實現了,這時候我們就要另外實作比較的函式

private int CompareT1ByIndex(T1 t1, T1 t2)
{
return t1.index - t2.index;
}

這個函數和CompareTo的差別就是傳入是兩個T1物件
然後兩者的比較方式和CompareTo是一樣的
之後要做排序的函式就可以改寫成

private void SortT1(T1[] t)
{
Array.Sort(t, CompareT1ByIndex);
}

這樣就可以看出來,如果我們希望使用name來作排序
其實只要另外實作一個CompareT1ByName的函式就可以做到

另外,需要注意的是Array.Sort的實作排序是quick sort,屬於不穩定的排序
所以兩個物件如果相同的話,順序並不一定會依照原本的順序

--
參考資料
Array.Sort(T) 方法 (T[], Comparison(T))
Sorting Arrays [C#]
Array.Sort 泛型方法 (T[])

2009年9月4日 星期五

在WPF中計時

需要計時的時候可以使用Stopwatch這個類別來計算(精密度可以到ms)

private Stopwatch StartCount()
{
Stopwatch s = new Stopwatch();

/// reset the clock
s.Reset();

/// start to count the time
s.Start();

/// return stopwatch for closing
return s;
}

private double StopCount(Stopwatch s)
{
/// stop to count the time
s.Stop();

/// return the total time passed in ms
return s.Elapsed.TotalMilliseconds;
}

--
參考資料
想計算程式執行時間,結果卻是?

在WPF中加入提示畫面(Splash Screen)

Splash Screen算是相當常用的特效
比較常見於應用程式啟動時,會顯示一些版本或是著作權資訊
然後並沒有特別做什麼動作就自動漸出了
跟Android裡面的Toast還蠻像的
不過Splash Screen一般用來顯示圖片,而Toast則是文字訊息

如果是要在程式啟動時顯示Splash Screen
可以直接在想要顯示的圖檔的屬性視窗中選擇建置動作(Build Action)
選為SplashScreen即可

如果希望在使用者做出某個動作(如按下About按鈕)的時候顯示的話
那就將在該動作的事件處理函式中呼叫

private void ShowSplashScreen()
{
/// new a splash screen with resource name
SplashScreen s = new SplashScreen("img.png");

/// show the splash screen and close automatically
s.Show(true);
}

如果希望該畫面可以停留到另外動作(如使用者按下或移動滑鼠)發生時
那就將Show裡面的參數換成false
然後在停止動作的事件處理函式中呼叫

private SplashScreen ShowSplashScreen()
{
/// new a splash screen with resource name
SplashScreen s = new SplashScreen("img.png");

/// show and hold until close
s.Show(false);

/// return splash screen for stop
return s;
}

private void CloseSplashScreen(SplashScreen s, int t)
{
/// close with fade out time t in 100 ns
s.Close(new TimeSpan(t));
}

這裡的時間是漸出(fade out)持續的時間,以100ns為單位計算
所以如果需要讓漸出時間(從開始淡化到完全消失)為1秒的話
那t要傳入10,000,000才行

--
參考資料
Splash Screen in WPF 3.5 SP1

在C# Form中引用被封裝的資源檔(影像)

要把影像檔封裝到C# form的執行檔或函式庫裡面很容易
請參考在C#中的封裝資源檔
並請記得將建置動作(Build Action)設成內嵌資源(EmbeddedResource)
同時,依照放置位置的不同,引用方法也不同
我們將四張圖檔依照上面的方法分別放於四個位置
檔名位置
img.png直接置於該應用程式專案裡面
img_folder.png放在應用程式專案底下的folder內
img_dll.png直接置於參考的函式庫test_dll裡面
img_folder_dll.png放在參考的函式庫test_dll裡面的folder內

在C#裡面引用的話(i1-i4是PictureBox控制項物件)

/// get application assembly by file name
Assembly app = Assembly.LoadFrom("test_cs.exe");
Assembly dll = Assembly.LoadFrom("test_dll.dll");

/// image at project
i1.Image=Image.FromStream(
app.GetManifestResourceStream(
"test_cs.img.png"));

/// image in folder at project
i2.Image=Image.FromStream(
app.GetManifestResourceStream(
"test_cs.folder.img_folder.png"));

/// image at dll
i3.Image=Image.FromStream(
dll.GetManifestResourceStream(
"test_dll.img_dll.png"));

/// image in folder at dll
i4.Image=Image.FromStream(
dll.GetManifestResourceStream(
"test_dll.folder.img_folder_dll.png"));

注意,GetManifestResourceStream的參數字串
第一個部分不是檔名,而是命名空間(namespace)
命名空間.資料夾.檔名.附檔名

--
參考資料
Image Resources in WinForms and WPF
圖檔資源載入與不規則 Form 的製作

2009年9月3日 星期四

在WPF中引用被封裝的資源檔(影像)

要把影像檔封裝到WPF的執行檔或函式庫裡面很容易
請參考在C#中的封裝資源檔
並請記得將建置動作(Build Action)設成Resource
同時,依照放置位置的不同,引用方法也不同
我們將四張圖檔依照上面的方法分別放於四個位置
檔名位置
img.png直接置於該應用程式專案裡面
img_folder.png放在應用程式專案底下的folder內
img_dll.png直接置於參考的函式庫test_dll裡面
img_folder_dll.png放在參考的函式庫test_dll裡面的folder內

在XAML裡面引用的方法

<!--image at project-->
<Image Name="i1" Source=
"img.png" />

<!--image in folder at project-->
<Image Name="i2" Source=
"folder/img_folder.png" />

<!--image at dll-->
<Image Name="i3" Source=
"/test_dll;component/img_dll.png" />

<!--image in folder at dll-->
<Image Name="i4" Source=
"/test_dll;component/folder/img_folder_dll.png" />

而如果是在C#裡面引用的話(i1-i4是Image控制項物件)

/// image at project
i1.Source = new BitmapImage(new Uri(
"img.png",
UriKind.Relative));
/// image in folder at project
i2.Source = new BitmapImage(new Uri(
"folder/img_folder.png",
UriKind.Relative));
/// image at dll
i3.Source = new BitmapImage(new Uri(
"/test_dll;component/img_dll.png",
UriKind.Relative));
/// image in folder at dll
i4.Source = new BitmapImage(new Uri(
"/test_dll;component/folder/img_folder_dll.png",
UriKind.Relative));

注意使用函式庫的時候不需要加.dll
(如上面的函式庫檔名為test_dll.dll,但在使用時僅需用test_dll即可)
在這邊都採用相對路徑的寫法,如果想用絕對路徑的話

/// image at project
i1.Source = new BitmapImage(new Uri(
"pack://application:,,,/img.png"));

在這邊application的意思是本機端的路徑
因為Uri也可以使用在網路上的路徑
而且這時候就不需要特別註明是絕對路徑
預設Uri的建構會判斷為絕對路徑

另外,需要注意的地方是
即使你是在函式庫中使用被封裝在自己函式庫中的資源
還是必須採用放在函式庫中的寫法
這樣真正執行那段程式碼的應用程式才有辦法識別

還有,如果是設定ImageBrush時,可能因為ImageBrush的ImageSource會在使用到的時候才會去解析Uri
所以需要寫絕對路徑,相對路徑會解析錯誤

--
參考資料
Windows Presentation Foundation 中的 Pack URI
WPF 封裝 圖片resource的問題 c#
Resources in WPF – I (Binary Resources)
Image Resources in WinForms and WPF

在C#中的封裝資源檔

在程式裡面有時候會用到圖檔或是影片之類的資源檔
但是由於安全或是方便的考量
不希望把這些檔案放在執行檔(.exe)或程式庫(.dll)之外
C#提供了很方便的方式讓使用者可以輕易的將這些檔案加入專案

選取想要加入的檔案即可
當然,如果不喜歡一堆檔案放在一起,也是可以用資料夾整理起來的

這時候只要在方案總管(Solution Explorer)裡面選取該檔案
然後在屬性視窗(Properties Window)選擇建置動作(Build Action)

這時候就需要依照檔案的種類以及讀取的方式來決定要怎麼選擇
一般來說,如果是圖檔的話,建議在WPF專案就選擇Resource
而C# form專案則是選內嵌資源(Embedded Resource)
選項說明
無(None)檔案未包含在專案輸出群組中,而且在建置過程中不加以編譯,例如讀我檔案(.txt)
編譯(Compile)將檔案編譯到建置輸出,例如程式碼檔(.cs)
內容(Content)不編譯檔案,但會將其加入內容輸出群組,例如網頁檔案(.htm)
嵌入資源
(Embedded Resource)
將檔案以 DLL 或可執行檔嵌入主要專案建置輸出,例如影像檔案(.bmp)
ApplicationDefinition定義應用程式相關屬性所用,轉換後編譯,例如tag為Application的xaml檔案(.xaml)
Page定義視窗或控制項等頁面相關屬性所用,轉換後編譯,例如tag為Window的xaml檔案(.xaml)
SplashScreen在程式載入時先顯示的畫面,將檔案嵌入主要專案建置輸出,例如影像檔案(.bmp)
Resource將檔案以分享的方式嵌入主要專案建置輸出,例如影像檔案(.bmp)
EntityDeployEntity Framework的建置工作,可以內嵌為資源或是寫入檔案中,例如內嵌檔案(.edmx)

--
參考資料
檔案屬性
What are the various 「Build action」 settings in
VS.NET project properties and what do they do?

EntityDeploy 類別
圖檔資源載入與不規則 Form 的製作

2009年9月2日 星期三

在WPF中自訂強制回應對話框(Dialog Box)

就像在WPF中的對話框(Dialog Box)中說的
新增一個對話框其實跟新增視窗沒有差別

從視窗的設定來說,強制回應對話框至少要有可以跟使用者互動(對話)的功能,而且除了簡單的通知功能之外(MessageBox),通常會希望使用者能夠表達出他的選擇,所以一般來說,對話框至少需要正反兩個選項。


<Window x:Class="test_wpf.NewDialog"
...>
<Grid>
<!--set an event handler as button clicked-->
<Button Width="100" HorizontalAlignment="Left"
Margin="10,10,0,10" Click="Ok_Click">
OK
</Button>
<!--a button with IsCancel property is just
like the close button in title bar, so there
is no need to add a click event handler-->

<Button Width="100" HorizontalAlignment="Right"
Margin="0,10,10,10" IsCancel="True">
Cancel
</Button>
</Grid>
</Window>

注意,這時候因為第二個按鈕設成IsCancel,所以按下它相當於按下視窗預設的關閉鍵,而此時視窗關閉,並將DialogResult設成false(英文版和中文版的msdn寫的不同,但經測試回傳為false),所以不需要額外寫事件處理,而第一個按鈕的事件處理如下

private void Ok_Click(object sender, RoutedEventArgs e)
{
/// set DialogResult and the window will close
/// automatically and no need to call Close()

DialogResult = true;
}

此時因為設定DialogResult,視窗自動關閉,不需要額外呼叫Close()

使用對話框其實就是使用視窗,只是Show出來的方法不同

NewDialog d = new NewDialog();
if ((bool)d.ShowDialog())
{
/// process when DialogResult is true
MessageBox.Show("OK");
}
else
{
/// process when DialogResult is false
MessageBox.Show("Cancel");
}

--
參考資料
對話方塊概觀

2009年9月1日 星期二

在WPF中的對話框(Dialog Box)

在WPF中,對話方塊跟視窗已經合併在一起
所以自訂對話方塊實際上也是定義一個視窗

一般來說,Dialog分為兩種,強制和非強制回應
區別強制回應非強制回應
呼叫方法ShowDialog()Show()
原視窗在對話框執行期間無法操作仍然可以繼續操作
回傳時機在DialogResult被設定後直接回傳,回傳後對話框自動關閉(無需另作Close)呼叫後直接回傳,對話框繼續運作,直到對話框被關閉
範例存檔對話框尋找對話框

一般常用的是強制回應的對話框,像是內建的存讀檔對話框
所謂的強制回應就是當對話框出來之後
使用者必須先針對對話框的訊息做出回應(確定或取消)
然後才會將主控權交回給原視窗
請參考在WPF中自訂強制回應對話框(Dialog Box)

而非強制回應的對話方塊其實就是視窗
唯一的差別只是這個視窗需要跟原本呼叫他的視窗作互動
所以可能需要宣告一個事件處理來達成這個互動

--
參考資料
對話方塊概觀

2009年8月31日 星期一

在WPF的XAML裡面使用自訂的控制項

在WPF裡面自訂控制項很簡單
可以參考在WPF中新增自訂控制項
如果要使用的時候其實就直接宣告那個新增出的類別就好

public Window1()
{
InitializeComponent();

/// new a NewButton, and set it into window
NewButton b = new NewButton();
this.Content = b;
}

不過因為習慣上會希望將這些不變的介面元素宣告在XAML裡面
那就要先在XAML裡面先宣告namespace

<Window x:Class="test_wpf.Window1"
...
xmlns:test_wpf="clr-namespace:test_wpf"
...>
</Window>

xmlns:後面接的是在XAML裡面的命名空間(namespace),可以隨意取
後面clr-namespace:後面的則是在C#裡面原本使用的命名空間

之後需要加入該命名空間裡面定義的控制項時
就可以用"該命名空間:控制項"來取得

<Window x:Class="test_wpf.Window1"
...
xmlns:test_wpf="clr-namespace:test_wpf"
...>
<!--add custom user control here-->
<test_wpf:NewButton />
</Window>

在WPF中新增自訂控制項

要做自訂的控制項很簡單
比較重要的是希望做到什麼程度的自訂
在wpf裡面基本上有四類
繼承類別說明
現有元件僅須做簡單改動即可,但自訂空間較小
UserControl可輕易的做出複合控制項,自訂空間自由,推薦
Control自訂空間更大,但須自行定義的東西較多,適合想用ControlTemplate自訂控制項的外觀或製作支援不同的主題的控制項的使用者
FrameworkElement完全不以現有元件為基礎,適合想要精準控制外觀或屬性的使用者

第一種方法是最簡單的,如果你只是想要有多一個屬性的button或是想要先訂好一些預設的事件處理函式(event handler)的話,那就直接繼承該選項,然後複寫你想要改變的屬性或新建屬性進去就可以了

public class NewButton1 : Button
{
/// constructor to set default event handler
public NewButton1()
{
Click += new RoutedEventHandler(Click);
}

/// default event handler of click, switch color
void Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;

if (b.Background == Brushes.Red)
{
b.Background = Brushes.Green;
}
else
{
b.Background = Brushes.Red;
}
}
}

不過這個用法只適用於不需要做太多複雜變更的單一控制項
如果希望能夠有更多的自訂空間的話,那就繼承UserControl

public class NewButton2 : UserControl
{
/// constructor to set default event handler
public NewButton2()
{
Button b = new Button();
b.Click += new RoutedEventHandler(Click);
this.Content = b;
}

/// default event handler of click, switch color
void Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;

if (b.Background == Brushes.Red)
{
b.Background = Brushes.Green;
}
else
{
b.Background = Brushes.Red;
}
}
}

上面兩個NewButton基本上是相同的
不過繼承自UserControl的NewButton2只要把Content改成一個Grid裡面就可以很容易的再增加一些其他原有的元件,對於需要使用複合元件的使用者來說會更方便一點

同時,繼承自UserControl的方式,多了一種新增的方法
可以在方案總管(Solution Explorer)的專案上面按右鍵
直接選擇新增使用者控制項(User Control)

這樣還會同時新增出一個xaml檔案
可以將一些屬性的設定直接寫在xaml檔案裡面

<UserControl x:Class="test_wpf.NewButton2"
...
>
<Button Click="Click" />
</UserControl>

一般狀況下使用上面的方法是最方便也最容易的
剩下的兩種狀況更適合有明確自訂需求
或是希望完全自訂而非製作複合控制項的進階使用者
可以直接參考msdn上面的詳細說明

另外,如果希望在XAML裡面宣告自訂的元件的話
可以參考在WPF的XAML裡面使用自訂的控制項

--
參考資料
控制項撰寫概觀
WPF Gadget Container Control
自訂 Windows Presentation Foundation 控制項
試試看:建立自訂的 WPF 控制項

2009年8月24日 星期一

在C#中整理程式碼

在C++中因為宣告和實作可以分開
所以將同個類別的不同實作分寫成很多檔案很容易
只要所有的檔案同時include同樣的標頭(.h)檔案就可以了

不過在C#裡面因為宣告和實作沒辦法分開做
(除非你想寫interface,不過概念又不太一樣)
但是把所有的class都寫在一起又很複雜而難以閱讀
所以在C#裡面要整理程式碼有兩個工具

第一個就是用#region,#endregion將一部分的程式碼夾住
可以把同類型的程式,如變數宣告、事件處理、公開函式
分別寫在不同的region裡面

public class c1
{
#region var declaration

int i;

#endregion

#region public function

public void f1()
{
...
}

public void f2()
{
...
}

#endregion
}

之後就可以用visual studio的功能
將一部分的程式碼直接隱藏

public class c1
{
var declaration

public function
}

這對於程式可讀性來說相當重要
之後就可以針對需要閱讀或編輯的區塊展開來編輯就好

不過即使這樣,實際的檔案還是沒有變小
如果不是使用visual studio來檢視的時候就會很痛苦

所以這時候就要用到另外一個工具 - partial class
在C#裡面可以將類別(class)宣告成部分
所以可以將不同的部分宣告在不同的partial class裡面

public partial class c2
{
public void f1()
{
...
}
}

public partial class c2
{
public void f2()
{
...
}
}

在編譯的時候這些同樣名字的類別會被合在一起編譯
所以可以把同一個類別的個別函式宣告在不同的cs檔案裡
要注意每個檔案中要用同樣的namespace
只有相同namespace下的同名的partial class會被合併一起處理

另外補充一點
在WPF裡面,如果有使用XAML來配置class的一些內容
比如window或是application之類的
那class也必須宣告成partial
因為XAML也會被轉換成另外一個partial class
裡面包含像是InitializeComponent之類預設的function

--
參考資料
Partial Class Definitions (C# Programming Guide)
.NET 2.0四大金剛Partial Class

2009年8月19日 星期三

關閉視窗與關閉應用程式

在WPF的預設中,應用程式的關閉是在所有的視窗被關閉之後
但是對於習慣.net form上面的應用程式開發者來說
還是習慣使用單一視窗為主視窗,應用程式會隨之關閉
這時候可以將WPF的Application的XAML加入

<Application
...
MainWindow="Window1"
ShutdownMode="OnMainWindowClose">
<Application.Resources>
</Application.Resources>
</Application>

這時就算之後新增了很多視窗,但是Window1仍舊會導致程式關閉
如果到執行的一半需要更換主視窗(比如登入之後)
可以在Application的程式碼中(不是Window的~)

private void ChangeMainWindow(Window w)
{
MainWindow = w;
}

取得視窗關閉的理由 - CloseReason

在.net form預設的事件處理裡面有FormClosing

private void Form1_FormClosing(object sender,
FormClosingEventArgs e){...}

在視窗被關閉的時候會被啟動
e.CloseReason就是這個event發生的原因,常用如下
CloseReason說明
UserClosing使用者按下此form右上角的關閉按鈕
TaskManagerClosing用工作管理員強制關閉
WindowsShutDown關機的時候所觸發
UserClApplicationExitCallosing所從屬的應用程式被關閉時呼叫

此時可以針對不同的需求來做不同的處理
例如讓使用者做再一次的確認
並用e.Cancel抑制關閉事件的發生

private void Form1_FormClosing(object sender,
FormClosingEventArgs e)
{
/// if the close button in the title bar is clicked
if (e.CloseReason == CloseReason.UserClosing)
{
/// ask the user whether close or not
if (MessageBox.Show("Leave?", "warning",
MessageBoxButtons.YesNo) ==
DialogResult.No)
{
/// cancel the process of closing
e.Cancel = true;
}
}
}

但是到了WPF之後
Window的Closing事件和應用程式的關閉已經切開了
在Window的Closing事件中仍然可以被制止

private void Window_Closing(object sender,
System.ComponentModel.CancelEventArgs e)
{
/// ask the user whether close or not
if (MessageBox.Show("Leave?", "warning",
MessageBoxButton.YesNo) ==
MessageBoxResult.No)
{
/// cancel the process of closing
e.Cancel = true;
}
}

但這時候已經沒有CloseReason可以選用了
因為單一視窗並不影響Application是否關閉
Application預設的關閉條件是最後的視窗被關閉
所以原先所提供的選項已經不再有意義

這時候如果仍然希望知道Application被關閉的原因
Application有提供SessionEnding事件
在Application的XAML裡面加上

<Application
...
SessionEnding="Application_SessionEnding">
<Application.Resources>
</Application.Resources>
</Application>

在程式碼中會看到

private void Application_SessionEnding(object sender,
SessionEndingCancelEventArgs e){...}

這時候可以從e.ReasonSessionEnding得到簡單的關閉原因

--
參考資料
WPF - Closing Application
表單右上角之關閉功能

2009年8月17日 星期一

在C#中可為null的型別,bool? int?

在C#中有些型別可以在原有的值域在加上null
而成為System.Nullable結構的執行個體,例如

Nullable<bool> b = null;

語法T?是Nullable<T>的簡略表示法,所以上面也可以表示成

bool? b = null;

把一個變數宣告成Nullable對於使用容許不宣告的變數很方便
不過在使用此型別的變數的時候需要特別小心
因為有時候並沒有被宣告(或是設成了null),所以在轉換成原有形別之前
需要先檢查是否有被設定

public bool ChangeToBool(bool? b)
{
/// check if b is null
if (b.HasValue)
{
return (bool)b;
}
else
{
return false;
}
}

另外,C#裡面也定義了更簡單的null聯合運算子??
所以上面的函式也可以直接寫成

public bool ChangeToBool(bool? b)
{
/// if b is null, return false, otherwise, return b
return b ?? false;
}

注意,透過null聯合運算子的結果就不需要再做型別轉換了
--
參考資料
可為 Null 的型別 (C# 程式設計手冊)
HOW TO:從 bool? 安全轉型至 bool (C# 程式設計手冊)
?? 運算子 (C# 參考)

2009年8月14日 星期五

UML - 活動圖 (Activity Diagram)

在循序圖裡面不容易表達狀況的分支
同一個時點可能會有多種選擇
而不同的選擇應該要接下去發展的情況就有所不同
這時候就要用活動圖來表示這些分支的狀況

一個循序圖有下面幾個元素
元素說明
初始狀態
(Initial state)
活動的起始點,全圖只會有一個,實心圓
最後狀態
(Final state)
活動的終止點,全圖至少一個,實心圓外包一個圓
動作狀態
(Action state)
代表一個動作,左右為弧線,上下為水平線,登入...
決策
(Decision)
代表情況的分歧,菱形
行動流
(Action flow)
動作之間的連續,實線箭頭
可加條件,以中括弧夾住
物件流
(Object flow)
物件之間的傳遞,虛線箭頭,此例圖中無
分叉(Fork)該狀態可以有多種行動,粗黑線段,一進多出
加入(Join)多種狀態都可以有該行動,粗黑線段,多進一出
此例圖中無

活動圖其實和流程圖很像
透過活動圖可以將整個系統的流程表達出來
也可以找出一些在循序圖中無法發現的分歧,避免遺漏

Blogger的插入圖片

在Blogger中使用預設的工具插入圖片
有時候會覺得照片看起來糊糊的
那是因為Blogger在上傳圖片的時候幫你做了兩件事

首先,如果圖檔的長或寬有一邊超過1600px
那他就會先幫你重新壓縮成1600為最大

第二,在顯示圖檔的時候,他會是以長邊為400的縮圖來顯示的
因為在選定的時候大小就只有200,320,400三種大小而已
這樣可以讓圖的載入速度變快,但是有時就會覺得畫質不好

在加入圖案的時候,選擇修改Html,可以看到原始碼

<a
onblur="
try {parent.deselectBloggerImageGracefully();}
catch(e) {}"
href="http://4.bp.blogspot.com/_7YTvBjsH78k/
SoT-IhmThfI/AAAAAAAAApM/Ax3wDbQ4uBw/s1600-h/
use+case+1.png">
<img
style="display:block; margin:0px auto 10px;
text-align:center;cursor:pointer;
cursor:hand; width: 400px; height: 320px;"
src="http://4.bp.blogspot.com/_7YTvBjsH78k/
SoT-IhmThfI/AAAAAAAAApM/Ax3wDbQ4uBw/s400/
use+case+1.png"
border="0"
alt=""
id="BLOGGER_PHOTO_ID_5369696078143915506"
/>
</a>

上面紅字的地方,就代表著使用400px的縮圖為來源
所以我們把它修改成s1600
就會是以最高解析度的原圖顯示了

--
參考資料
Blogger圖片大小的控制
Blogger上傳圖片的原始碼解析

UML - 循序圖 (Sequence Diagram)

在使用案例圖做完後,會發現我們要提供的系統該提供哪些功能
可是系統到底應該做些什麼來達到這些功能呢
這時候就需要循序圖(Sequence Diagram)來幫忙啦

循序圖最大的好處就是可以沿著時間軸
一步一步的將某個使用案例中,系統元件的訊息交換表現出來

一個循序圖至少有下面幾個元素
元素說明
角色
(Class role)
每個物件在系統中所扮演的角色,觀眾、訂票網頁、資料庫
生命線
(Lifeline)
角色存活的時間,角色下延伸的虛線
活動
(Activity)
角色處理一項操作需要的時間,覆蓋虛線的方框
訊息
(Message)
物件之間溝通的訊息,方框間的箭頭符號
* 一般傳輸、呼叫、回傳分別用不同的箭頭表示
* 使用者對介面的溝通常會在訊息前加上"/"以示區別

透過循序圖,開發者可以先構思每個物件該如何交換訊息
而且因為有時間軸的概念,循序圖可以表示一些事件應該發生的先後順序
這是靜態圖和使用案例圖無法達到的

另外,雖然循序圖基本上是對單一使用案例的單一情況
(使用者下的決定或環境狀況)來做描述
不過如果希望將多種可能同時畫在一張循序圖上
也可以在傳遞的訊息前加上[狀況]來表達某種情況下的處理方式
不同狀況的處理方式就可以放進同一張循序圖中了
有興趣的話可以參考Interaction Diagrams
不過基本上這種做法常會讓循序圖的時間軸亂掉
如果沒有把握的話不建議使用

2009年8月13日 星期四

在C#的結構(struct)中使用陣列(array)

C#是物件導向的語言,所以並不建議在其中使用結構
需要使用的時候最好儘量使用類別(class)來取代

不過有時候還是會需要定義結構,比如使用前人定義出的結構時
為了要和非.net(unmanagement)的程式庫(dll)溝通
還是需要定義出符合原本定義的結構,如果都只用到一些共通的形態的話
那在C#和C++中的定義基本上是一樣的

struct t1
{
char c;
int i;
};

不過這時候如果結構裡面有定義陣列的話

struct t2
{
char c;
int i[10];
};

因為C#裡面並沒有單純陣列的概念
所以基本上都要宣告成陣列物件

struct t2cs
{
char c;
int[] i;
};

這時候會發現沒辦法在這結構裡面直接宣告陣列的大小
因為物件不能在宣告的時候給值,必須要在new出物件的時候才能給定
記憶體的大小會跟非net的結構不同,所以傳遞過去也沒辦法取用

這時候可以使用unsafe和fixed來處理,
將這個程式區段設定為unmanagement的形態

unsafe struct t2cs
{
char c;
fixed int i[10];
};

雖然這種宣告方法很直覺
不過這樣的宣告需要去專案屬性裡面
將編譯選項裡面的容許unsafe選項打開
而且這個方法僅適用於一般形態的陣列(int, char...)
如果是結構的陣列就會出現問題

struct t3
{
char c;
t1 t[10];
};

那就沒辦法使用fixed來定義了
這時候可以用MarshalAs來預留一塊記憶體位置

struct t3cs
{
char c;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=10)]
t1[] t;
};

這時候如果發現兩邊的記憶體大小值不一樣的話
可以參考結構(struct)的大小問題來限定記憶體的配置方式
之後就可以直接接收物件來使用或是傳遞迴去了

--
參考資料
Fixed Size Buffers (C# Programming Guide)
[C#] Array in struct
Marshal Unmanaged struct to managed code using c#
UnmanagedType 列舉型別

2009年8月12日 星期三

在C#中傳回參數(out & ref)

因為C#裡面並不常使用指標的概念
所以在呼叫函式的時候,如果需要回傳多個值,會覺得無從下手
實際上C#裡面有定義out來幫助程式開發者傳回物件
用法很簡單,在宣告和呼叫的參數前都加上out就可以了

public void GetZero(out int i)
{
i = 0;
}

public void Call()
{
int i;
GetZero(out i);
}

注意,在這時候,由於宣告了參數是out的形態
所以在呼叫前可以不給值(給了值也會在呼叫的時候自動清掉)
但是在函數中一定要給值,否則會有錯誤(如同宣告了回傳值,卻沒有return)

另外,有時候會需要用到一個值,然後再將該值更新
那就需要用到ref了,一樣是在宣告和呼叫的時候都在參數前加上ref

public void AddOne(ref int i)
{
i++;
}

public void Call()
{
int i = 0;
AddOne(ref i);
}

注意,ref的參數必須在呼叫前給值(因為該值在函式中可能被使用到)
而且也可以在呼叫過程中更新(不更新也不會有錯誤)
--
參考資料
out (C# 參考)
ref (C# 參考)
C#中ref和out的使用小結

2009年8月11日 星期二

.net的日期時間物件

在System裡面就有DateTime類別可以用

/// current local time
DateTime now = DateTime.Now;

/// current time in Coordinated Universal Time (UTC)
DateTime unow = DateTime.UtcNow;

如果要自訂一個時間,也可以指定一個世界標準時間

DateTime d = new DateTime(
2000, /// year
8, /// month
12, /// day
17, /// hour
30, /// minute
0, /// second
DateTimeKind.Utc);

再用下面的方法與當地時間互相轉換

DateTime ld = d.ToLocalTime();
DateTime ud = ld.ToUniversalTime();

--
參考資料
DateTime 結構

2009年8月10日 星期一

UML的使用流程

因為UML就是一個工具箱,看什麼場合用什麼樣的工具
所以其實並沒有所謂的使用流程
但是對於初學者,還是可以遵照一個簡單的流程
步驟說明
使用者觀點利用使用個案圖,將使用者會直接用到的功能列出
行為觀點利用循序圖,表達每個使用個案的特定流程
利用活動圖,將循序圖泛化成一般活動流程
結構觀點利用類別圖,表達活動及循序圖中所用到的物件類別,包括變數、函式及其間相關性

根據UML的使用時機,UML是在設計階段之後
也就是已經將需求分析等事前準備工作都完成後
要將概念傳達給實作者的步驟
所以這整個塑模的流程,是在已經清楚的知道想要做出的成品時
將成品的概念利用塑模的工具一點點捏塑出來
而當類別圖建好之後,就已經可以開始實作了

UML的使用時機

初學者在使用UML的時候很容易迷失了方向
因為UML提供了太多的觀點而不知從何下手
這時候可以先從系統的開發角度來看
階段說明
設計分析需求,將需求分類,並針對有效的需求做出相應設計
實作針對設計出的模型,將需求實際的功能填入模型
評估根據實作出的成品,審視需求的滿足狀態

而UML的模型套用的地方,就是從設計階段到實作階段時
透過設計者(非工程師)與實作者(工程師)共同瞭解的表示法
有效的將設計的理念傳達出來
在評估階段,也可以透過當初設計者所制定的UML
確認實作者是否將設計者所想的都做了出來

另外,由於上面列的這段流程會是一個循環
從設計出發,實作、分析、修正設計、添加實作、重新分析...
透過螺旋式(Spiral model)的放大,一步步的進化成完成品
所以UML在使用上也會是這樣的進化
並不需要在一開始的時候就訂的非常完整
而是經過不同階段的實作及分析修改設計,漸漸的完善出來的

2009年8月6日 星期四

結構(struct)的大小問題

在記憶體配置的時候,因為效率考量,預設會依照元素大小整數倍的對齊
並且會以其中最大的元素為單位,配置整數倍的記憶體
例如同樣是char, short, int三個元素

struct t1
{
char c; /// 0000000x
short s; /// 0000xx00
int i; /// xxxx0000
};

struct t2
{
int i; /// 0000xxxx
short s; /// 00xx0000
char c; /// 0x000000
};

struct t3
{
short s; /// 0000000000xx
int i; /// 0000xxxx0000
char c; /// 000x00000000
};


這時候宣告出來的結構物件,大小會是8,8,12
在t1中的short,因為需要從偶數位置開始,所以會跳過1直接由2開始
而t3中的int則是因為需要從四的倍數開始,所以跳過2,3直接從4開始
三個結構的最大單元都是int,所以預留記憶體大小的時候都會是四的倍數

從上面的例子可以看出記憶體不連續的狀態,進而造成大小判斷錯誤
所以在宣告的時候最好依照大小作配置,才比較不會出現記憶體的空缺

不過,如果是對記憶體非常侷限的設備,為了避免記憶體的浪費
或是一些可讀性的考量,所以配置的方式並不能依照大小排列
這時候會需要將多出的記憶體空缺給填滿(改變對齊方式)
將struct宣告改寫成

#pragma pack(1) /// force alignment to 1 byte
struct t1
{
char c; /// 000000x
short s; /// 0000xx0
int i; /// xxxx000
};
#pragma pack() /// set alignment back to default

強制要求程式以1byte為單位對齊,大小就會變成7
並在宣告後,將預設的單位設回來(否則會出現warning)

如果是在C#裡面,則改成

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct t1
{
char c; /// 000000x
short s; /// 0000xx0
int i; /// xxxx000
};


--
參考資料
如何控制.NET Framework 2.0 中的資料欄位的實體配置
#pragma pack和數據對齊問題
C#-struct實例字段的內存佈局(Layout)和大小(Size)

2009年8月5日 星期三

UML - 使用個案圖 (Use Case Diagram)

在開始對一個系統塑模的時候,通常會先從使用個案圖開始
由非工程角度出發,確定該系統的設計範圍以及使用者會使用到的功能
也就是利用使用者的觀點取得一個視角,例如

一個使用案例圖至少有下面幾個元素
元素說明
動作者(actor)實際操作系統的人或外部系統,觀眾
使用案例(use case)系統提供的功能,動作越明確越好,訂票、取消
系統(system)實際上要開發的系統,訂票系統

由上面的例子可以看出,我們開發的訂票系統,使用對象是觀眾
可以操作的功能則有訂票和取消兩項
使用案例圖有時候還會用一些關聯性來模組化使用案例

關聯性說明
使用(uses)在過程中會使用到其它的使用案例,也稱做包含(include)
擴充(extend)在某條件成立下,會使用到其它的使用案例

如果系統是由多個人共同開發,透過使用案例圖
可以在制定細節前,預先讓開發者對於使用者可以有一致的瞭解
之後在開發的時候才有所依據

--
參考資料
從鳥瞰的觀點看 Use Case Diagram
使用案例圖的UML風格指南
Wikipedia - Use case diagram

2009年8月4日 星期二

UML的圖形種類

在UML2.0里面定義了13種圖(Diagrams)
結構性圖形(Structure Diagrams),系統式的建模
類別圖
(Class Diagram)
將系統中的類別列出,並描述出每個類別包含的屬性方法及類別間的關聯性
元件圖
(Component Diagram)
系統的實作觀點,系統中元件的互動、關連性及介面
複合結構圖(Composite Structure Diagram)系統中元件、類別或是使用案例的細節
部署圖
(Deployment Diagram)
系統的環境觀點,資源元素的組態及其與元件間的互動
物件圖
(Object Diagram)
類別的實作,通常是在某個時間點或某特定情況下的物件及其之間的交互關係
包圖
(Package Diagram)
顯示模型元素是如何包裝起來,以及這些包裝間的相依性
行為式圖形(Behavior Diagrams),事件觸髮式的建模
活動圖
(Activity Diagram)
單一類別對於內部處理作的反應,包含行動流及物件流
狀態機圖
(State Machine diagram)
單一物件的生命週期,顯示其與外界互動所造成的狀態改變
使用個案圖
(Use Case Diagram)
系統的使用者觀點,表示系統可以提供給每類互動者哪些功能
溝通性圖形(Interaction Diagrams),資料流程式的建模
行為圖形的子集合
通信圖
(Communication Diagram)
表示物件的相關性,以及其間的訊息流,也叫做合作圖(Collaboration Diagram)
交互概述圖(Interaction Overview Diagram)概觀系統的控制流,每個活動都可以再被表示成一個交互圖,2.0新增
循序圖
(Sequence Diagram)
顯示在時間序列中,類別訊息交換的順序
時間圖
(Timing Diagram)
物件受互動影響而造成的狀態改變過程(隨時間改變的狀態),2.0新增

在規畫塑模的時候,不一定全部都需要建立出來
依照系統的屬性以及需求,採用不同的面向才有意義

--
參考資料
維基百科 - 統一塑模語言(UML)
Introduction to the Diagrams of UML 2.0

UML概觀

UML可以用各種不同的面向來呈現或觀察一個系統
舉例來說,一台螢幕可以用這些面向來呈現
或許因為面向太多,所以很容易混淆而弄不清楚UML到底在幹嘛
面向呈現
功能呈現影像訊號、轉動改變視角、按鍵調整設定、觸控(選用)
結構背光模組、框架、接頭、按鍵、觸控模組(選用)
狀態開、關、調整畫面

我覺得應該不要把UML當成一種語言,而是一種溝通的工具
用一些大家約定好的記號來表示剛剛所提的這些面向
比如說一個橢圓、或是棒子人、一個方框...都代表特定的含意
在呈現與觀看的雙方都建立好同樣的背景知識後
才有辦法針對系統內可能會發生或已經發生的問題討論

對於大型系統開發尤其重要,越是耗費人力與時間的系統
越容易在過程中有共同開發甚至交接的狀況發生
大型系統耗費在溝通上的時間可能還超過開發的時間
這時候一個共通的溝通工具和已經制定好的模型就異常重要了

2009年8月3日 星期一

在Blog中自訂CSS標籤

如果在寫blog的時候需要換文字的顏色
又不希望每次還要切換成撰寫模式去填顏色
或是怕每次填的顏色可能會不一樣
那就自訂一個標籤吧

在css中設定標籤的地方加入

n1 {
color: rgb(51, 204, 0);
}

之後再需要的內容使用這個標籤夾住

<n1>/// notes 1<n1>

就可以將該內容改成你希望的格式

/// notes 1

在Blog中加入表格

在blog中如果將表格從別的軟體貼過來
常常格式會亂掉
如果變成圖片再貼過來又覺得解析度不太好
這時候就直接用html來製作表格吧

在設定css的地方,以Blogger為例
自訂->版面配置->修改HTML 找到

body {
...
}

然後加入

table {
border-collapse: separate;
border: 1px solid #999999;
width: 100%;
margin: 10px 0px 10px 0px;
}

th {
border: 1px solid #999999;
background-color: #eeeeee;
text-align: center;
}

td {
border: 1px solid #999999;
}

然後新增文章裡面直接修改html,加入

<table><th>標題1</th><th>標題2</th></tr><tr><td>內容1-1</td><td>內容2-1</td></tr><tr><td>內容1-2</td><td>內容2-2</td></tr></table>

就會變成
標題1標題2
內容1-1內容2-1
內容1-2內容2-2

注意,這裡不能換行,因為blogger會自動將換行符號
轉換成<br>,讓表格上方多出一堆空白

語法遵照CSS的標準

標籤參數說明
tableborder-collapse內框線與外框線分離
border框線的型態,設成細灰線
width寬度,設成填滿所在框架的寬度
margin外邊界,分別是上左下右邊界的寬度
thborder框線的型態,設成細灰線
background-color填滿色彩,設成淺灰色
text-align文字對齊方式,置中
tdborder框線的型態,設成細灰線

在Blog中加入程式碼方塊(code block)

在網頁或是blog中加入程式碼方塊
除非一開始選的範本裡面就有定義好
否則只能自己修改範本裡的css

以Blogger為例,在自訂->版面配置->修改HTML找到

code {
...
}

把整個換成

code {
white-space: pre;
display: block;
background: #eeeeee;
padding: 5px 5px 20px 5px;
margin: 5px;
}

這樣在發表文章的時候,直接點選修改html
將想要放在程式碼方塊中的內容,用code這個標籤夾住,像是

<code>
...
</code>

發佈之後就會看到

...

裡面的用法都是css的標準用法
參數說明
white-space縮排方式,可參考CSS white-space 屬性
display將標籤夾起的部分圍成一個區塊
background設定背景顏色,淺灰色
padding內邊界(上左下右),預設上方會多一行,所以下方設多一點
margin外邊界,讓區塊跟文字稍為隔開

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