顯示具有 WPF 標籤的文章。 顯示所有文章
顯示具有 WPF 標籤的文章。 顯示所有文章

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 中提供了四個選取器控制項,分別是 ComboBoxListBox、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年4月30日 星期四

[WPF] 相依屬性(Dependency Property) 進階探討

實作 Dependency Property(相依屬性) 的規則與流程

1、首先必須宣告 Dependency Property

Dependency Property 欄位都是 publicstatic,並且名稱以「Property」作為結尾。

例如以下宣告:
public static readonly DependencyProperty IsDefaultProperty;

2、在 static 建構子中註冊 Dependency Property

由於 Dependency Property 必須呼叫 static 方法 DependencyProperty.Register() 來建立

而在註冊時,必須在 Register() method 中指定以下內容:
  • Dependency Property 的名稱

  • Dependency Property 的型態

  • 包含 Dependency Property 的 Class 型態

  • Metadata (型態為 FrameworkPropertyMetadata,而 Callback Function 宣告於此處)

  • 用來檢查 Property 的值是否合法的 function


3、設定 Getter 與 Setter

Dependency Property 宣告了就是用來設定或取值之用的,當然要設定 Getter 與 Setter 囉!

但比較特別的是,Dependency Property 值的設定或取得,是透過 System.Windows.DependencyObject 中的 SetValue()GetValue() 來處理的。

而比較需要注意的是,若是有任何的程式邏輯或是檢查要加入,請記得加到 Callback Function 中,別加在 Getter 與 Setter 中,因為那只是用來取值或設定值之用。
(所有 WPF 內建的屬性封裝都遵守此規則,因此別亂寫囉!)



使用 Dependency Property 的優點 & 所提供的功能

1、節省記憶體

想像一下,Button 類別有將近 100 個欄位值,每個 Label 也將近有 90 個欄位值,若是畫面中有一堆 Button 與 Label 所產生的物件,記憶體的耗用量豈不是很可觀?

沒錯,這就是 Dependency Property 所要解決的問題,透過 static 的方式先產生 Dependency Property,在將 Button 與 Label 物件的屬性「依附上去」。

當每個 Button 或是 Label 都依附到同一個 Dependency Property,如此一來,記憶體的用量就會大大地減少囉!


2、變更告知 (Change Notification)

WPF 可以根據 Dependency Property 的 Metadata 自動觸法許多動作,例如:重新整理元素、更新畫面配置、重新整理資料繫結...等等。

範例:以下程式實作滑鼠移到按鈕上後,更改文字顏色(Button 的 IsMouseOver 屬性被更改,自動觸發更改文字顏色)

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="About WPF Unleashed" SizeToContent="WidthAndHeight" Background="OrangeRed" FontSize="30" FontStyle="Italic">





Chapter 1
Chapter 2








You have seccessfully registered this product.





3、屬性值繼承 (Property Value Inheritance)

元素樹中,父節點的控制項屬性會向下套用到子系的控制項屬性。

但也是有些例外的情況,原因可能會有兩種:
  1. 在 DependencyProperty.Register() 裡,透過 FrameworkPropertyMetadata.Options.Inherits 設定為不繼承父系控制項的屬性值。

  2. 子系控制項覆寫了繼承來的屬性值。


4、支援多重提供者

WPF 中有許多屬性提供者可用不同的方式設定 Dependency Property,而屬性值的決定過程會經過五個步驟:
  1. 判斷基底值

    這個部分會由多種屬性提供者來提供值,優先順序為:

    Local Value -> Style Trigger -> Template Trigger -> Style Setter -> Theme Style Trigger -> Theme Style Setters -> Property Value Inheritance -> Default Value

  2. 驗算

    此部份進行屬性值的驗算,為運算式。

  3. 套用動畫

    若有一個以上的動畫正在執行,它們可以改變目前的屬性值,甚至完全置換掉。

  4. 轉換

    若有註冊 CoerceValueCallback 委派,則屬性值會被傳遞到此 function 進行轉換,以取得更新後的值。
    (例如 ProgressBar 就是利用 callback function 來保持屬性值不會超過最大或最小值)

  5. 驗證

    最後還會進行屬性值的驗證,決定該值是否合法。(若有設定 ValidateValueCallback 委派)


5、附加屬性 (Attached Property)


附加屬性(attached property)是種特殊的 Dependency Property,可以附加到任何物件上,以下直接用範例說明:

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="About WPF Unleashed" Background="OrangeRed">





Chapter 1
Chapter 2








You have seccessfully registered this product




從程式碼可以看出,透過附加屬性的使用,StackPanel 中的控制項就不用一一加上 FontSize & FontStyle 兩個屬性了。


參考資料

Anita 的.NET 世界: 什麼是Dependency Property

design studio: Dependency Property

WPF Tutorial | Introduction to DependencyProperties

2009年4月7日 星期二

[WPF] Routed 輸入事件

Routed Event

假設 element tree 如下:

Window
|
Grid
|
---------------------------
| | |
Button TextBlock ScrollViewer
| |
TextBlock StackPanel
|
TextBlock


而 Routed Event Handler 大概長以下這樣:
void functionName(object sender, RoutedEventArgs args)
{ ....(事件處理)... }
其中參數的部份有幾個重點:
  • sender:此物件將 event 送到 application 中

  • args.Source:真正導致 event 發生的 element


若是在 element tree 中左下角的 TextBlock 按一下滑鼠右鍵,分別會產生出以下的 event:
PreviewMouseDown -> MouseDown
其中 sender 的部份,觸發 event 順序如下:
  • PreviewMouseDown:Window -> Grid -> Button -> TextBlock

  • MouseDown:TextBlock -> Button -> Grid -> Window

因此從上面可以知道,PreviewMouseDown 是屬於 tunneling eventMouseDown 屬於 bubbling event

而透過 Event Routing 的特性,若是要讓較上層的 element 先處理 mouse down 的動作,可以撰寫在 PreviewMouseDown 中。



Mouse Event

MouseDown 與 MouseUp 不一定會同時出現

例如:在視窗中按下按鈕,會發生 MouseDown event,但如果持續按著不放,再離開視窗的範圍後再放開滑鼠,不會出現 MouseUp event。

因此,若要解決此問題,可以透過在 UIElementContentElement 中所定義的 CaptureMouse 方法來解決。

當呼叫了 CaptureMouse 方法後,即使滑鼠離開視窗,視窗也會持續的接收 MouseMove 與 MouseUp 事件。

最後當 MouseUp 事件處理完成後,可以呼叫 ReleaseMouseCapture 或是設定 Mouse.Capture 為 null 來釋放所捕捉的滑鼠。



Keyboard Event

在 WPF 中,keyboard event 都會下放到 TextBox、RichTextBox、ScrollViewer 這一類的控制項中作處理。

需要注意的是,只有當 element 或是控制項具有鍵盤焦點時,才會是 keyboard event 的來源。

此外,keyboard event 亦屬於 routed event,因此具有焦點的 element 的所有上層 element,都可以參與 keyboard event,且其 IsKeyboardFocusWithin 屬性都會是 true。

當 element 獲得 or 失去焦點時,會觸發 GotKeyboardFocusLostKeyboardfocus 事件,也都是 routed event;此外,由於兩個事件屬於 tunneling event,因此在 element tree 中上方的 element 可以先察覺到焦點的改變。

而 Keyboard 的輸入包含了三種 event,發生順序如下:
  1. KeyDown (參數型態為 KeyEventArgs)

  2. TextInput (參數型態為 TextCompositionEventArgs)

  3. KeyUp (參數型態為 KeyEventArgs)

但比較需要注意的是,若按下的是「Shift」、方向鍵、或是其他功能鍵,並不會產生 TextInput 事件。舉例來說,或先按下 Shift 再按 A,一共會觸發兩次 KeyDown 事件、一次 TextInput 事件、兩次 KeyUp 事件。

2009年4月1日 星期三

[WPF] 相依屬性(Dependency Property) 初探

在傳統的 Window Form 中的設計中,每個控制項都各自擁有自己的 property,而這些 property 大多都是相同的(例如:FontSize),因此不僅浪費記憶體來儲存這些 property 的值,有時在程式中還需要撰寫多餘程式來設定這些 property 的值。

因此,在 WPF 中提出了Dependency Property,用來解決各控制項中重複的 property 過多因此造成浪費記憶體的現象。

以下用範例程式來說明:(節錄自 Application = Code + Markup 一書)

SpaceButton.cs

using System;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace CH08.SetSpaceProperty
{
public class SpaceButton : Button
{
//傳統 .NET 做法:私有欄位搭配公開 property
string _txt;

//透過 get & set 設定 property 的處理方式
public string Text
{
set
{
_txt = value;
this.Content = SpaceOutText(_txt);
}
get { return _txt; }
}

//Dependecy Property 的宣告
public static readonly DependencyProperty SpaceProperty;
public int Space
{
//不同與以往 property 的設定方式
//Dependency Property 必須透過 DependencyObject 中的 SetValue() 與 GetValue() 進行 value 的處理
//*** SetValue() 與 GetValue() 皆為 static method ***
set { SetValue(SpaceProperty, value); }
get { return (int)GetValue(SpaceProperty); }
}

//處理 Dependency Property 必須使用 static constructor
static SpaceButton()
{
//設定 metadata (可以想像成是 Dependency Property 的相關特性)
FrameworkPropertyMetadata meta = new FrameworkPropertyMetadata();
meta.DefaultValue = 1;
meta.AffectsMeasure = true;
meta.Inherits = true;
meta.PropertyChangedCallback += OnSpacePropertyChanged;

//註冊 DependecyProperty
SpaceProperty = DependencyProperty.Register("Space", typeof(int), typeof(SpaceButton), meta, ValidateSpaceValue);
}

//value 檢驗時呼叫方法
static bool ValidateSpaceValue(object obj)
{
int i = (int)obj;
return i >= 0;
}


//當 property 改變時,呼叫此方法
static void OnSpacePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
SpaceButton btn = obj as SpaceButton;
btn.Content = btn.SpaceOutText(btn._txt);
}

//在 Button Content 中每個字元間塞指定數量(Space)的空白
string SpaceOutText(string str)
{
if (str == null)
return null;

StringBuilder build = new StringBuilder();
foreach (char ch in str)
build.Append(ch + new string(' ', Space));

return build.ToString();
}
}
}



SpaceWindow.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace CH08.SetSpaceProperty
{
public class SpaceWindow : Window
{
//設定 Dependency Property
public static readonly DependencyProperty SpaceProperty;

public int Space
{
//同樣的,這邊也要透過 DependencyObject 中的 SetValue() 與 GetValue() 進行 value 的處理
set { SetValue(SpaceProperty, value); }
get { return (int)GetValue(SpaceProperty); }
}

//處理 Dependency Property 必須使用 static constructor
static SpaceWindow()
{
//設定 metadata
FrameworkPropertyMetadata meta = new FrameworkPropertyMetadata();
meta.Inherits = true;

//由於 SpaceProperty 在 SpaceButton 中已經宣告過
//因此這邊直接將 SpaceProperty 指到 SpaceButton 中的 SpaceProperty
//只要透過 AddOwner() 將自己也設定為該 Dependency Property 的擁有者即可
SpaceProperty = SpaceButton.SpaceProperty.AddOwner(typeof(SpaceWindow));

//唯一需要注意的是 metadata 並不會一同被採用
//因此需要另外的宣告與設定,並 override
SpaceProperty.OverrideMetadata(typeof(SpaceWindow), meta);
}
}
}



SetSpaceProperty.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace CH08.SetSpaceProperty
{
public class SetSpaceProperty : SpaceWindow
{
[STAThread]
public static void Main()
{
Application app = new Application();
app.Run(new SetSpaceProperty());
}

public SetSpaceProperty()
{
this.Title = "Set Space Property";
this.SizeToContent = SizeToContent.WidthAndHeight;
this.ResizeMode = ResizeMode.CanMinimize;

int[] iSpaces = { 0, 1, 2 };
Grid grid = new Grid();
this.Content = grid;

//設定 Grid 的 RowDefinition
for (int i = 0; i < 2; i++)
{
RowDefinition row = new RowDefinition();
row.Height = GridLength.Auto;
grid.RowDefinitions.Add(row);
}

//設定 Grid 的 ColumnDefinition
for (int i = 0; i < iSpaces.Length; i++)
{
ColumnDefinition col = new ColumnDefinition();
col.Width = GridLength.Auto;
grid.ColumnDefinitions.Add(col);
}

for (int i = 0; i < iSpaces.Length; i++)
{
SpaceButton btn = new SpaceButton();
btn.Text = "Set window Space to " + iSpaces[i];
btn.Tag = iSpaces[i];
btn.HorizontalAlignment = HorizontalAlignment.Center;
btn.VerticalAlignment = VerticalAlignment.Center;
btn.Click += WindowPropertyOnClick;
grid.Children.Add(btn);
Grid.SetRow(btn, 0);
Grid.SetColumn(btn, i);

btn = new SpaceButton();
btn.Text = "Set button Space to " + iSpaces[i];
btn.Tag = iSpaces[i];
btn.HorizontalAlignment = HorizontalAlignment.Center;
btn.VerticalAlignment = VerticalAlignment.Center;
btn.Click += ButtonPropertyOnClick;
grid.Children.Add(btn);
Grid.SetRow(btn, 1);
Grid.SetColumn(btn, i);
}
}

//更改 Window 中的 Dependency Property
void WindowPropertyOnClick(object sender, RoutedEventArgs args)
{
SpaceButton btn = args.Source as SpaceButton;

//修改 Window 的 Space Dependency Property
//而此修改會影響所有在 SpaceWindow 中且擁有 SpaceProperty 屬性的控制項
//唯獨已經設定 Space 值的 SpaceButton 控制項例外
Space = (int)btn.Tag;
}

//更改 Button 中的 Dependency Property
void ButtonPropertyOnClick(object sender, RoutedEventArgs args)
{
SpaceButton btn = args.Source as SpaceButton;

//僅單獨修改 SpaceButton 中的 Space 值
//不會影響到 SpaceWindow 中其他擁有 SpaceProperty 的控制項
btn.Space = (int)btn.Tag;
}
}
}



參考資料

[WPF] Layout

Panel

ContentControl 中衍生出來的 class 都有 Content 屬性,幾乎可以被設定為任何物件,但問題在於「Content 只能設定一個物件」。

然而一個視窗中怎麼可能只放一個物件呢? 因此在 WPF 中使用了 Panel 作為解決此問題的方式,而 Panel 的階層與衍生類別如下:

UIElement

每種不同的 panel 有其自訂的 layout model,例如:

StackPanel 用水平或垂直 stack 的方式安排子元素

WrapPanel 類似 StackPanel,但可以將自動將元素擺到下一個 columm 或是 row 的位置

DockPanel 則會根據 parent container 的內部邊界來進行自動擺設

Grid 則是把所有子元素以格子狀的方式擺設

UniformGrid 則是所有的行都等寬,列都等高

Canvas 則是最貼近原本的做法,每個元素都有自己的座標位置


Dock

重點就在於 dock 的位置了,分別是 Top、Bottom、Left、Right 四種,如果將控制項的 HorizontalAlignment 設定為 Center,整個版面會變得很奇怪,因此在撰寫上要注意一下。

element 的定位必須使用 DockPanel.SetDock() 來處理。


Grid

蠻龜毛的一個 layout panel,必須要先定義 RowDefinition 以及 ColumnDefinition 並加入到 Grid 中,才能將控制項加入到 Grid 中。

而控制項加入時還要透過 Grid.SetRow() 以及 Grid.SetColumn() 來設定控制項在 Grid 中的位置。

element 的定位必須使用 Grid.SetRow()Grid.SetColumn()Grid.SetRowSpan()Grid.SetColumnSpan() 來處理。


Canvas


element 的定位必須使用 Canvas.SetLeft()Canvas.SetTop()Canvas.SetRight()Canvas.SetBottom() 等方法來處理。


事件處理

若將 event 處理指定給特定的控制項,例如:

Button.Click += ButtonOnClick;

void ButtonOnClick(object sender, RoutedEventArgs args)
{
MessageBox.Show("Hello");
}


當然,也可以把 event 透過 this.AddHandler 註冊給 Window 物件(包含 Button),但是就要在 function 中透過 args.Source 來判斷控制項的型態了,設定方式如下:

this.AddHandler(Button.ClickEvent, new RoutedEventHandler(ButtonOnClick));


既然可以將 event 註冊給 Window 物件,當然也可以註冊給 Panel 物件囉!