建立外觀可自訂的控制項
Windows Presentation Foundation (WPF) 可讓您建立可以自訂其外觀的控制項。 例如,您可以藉由建立新的 ControlTemplate ,來變更 超出設定屬性將執行之動作的外觀 CheckBox 。 下圖顯示 CheckBox 使用預設值 ControlTemplate 的 ,以及 CheckBox 使用自訂 ControlTemplate 的 。
使用預設控制項範本的 CheckBox
使用自訂控制項範本的 CheckBox
如果您在建立控制項時遵循元件和狀態模型,則可以自訂控制項的外觀。 Blend for Visual Studio 之類的設計工具支援元件和狀態模型,因此當您遵循此模型時,控制項可以在這些類型的應用程式中自訂。 本主題討論群組件和狀態模型,以及如何在建立自己的控制項時遵循它。 本主題使用自訂控制項的範例, NumericUpDown
來說明此模型的哲學。 控制項 NumericUpDown
會顯示數值,使用者可以按一下控制項的按鈕來增加或減少。 下圖顯示 NumericUpDown
本主題中所討論的控制項。
自訂 NumericUpDown 控制項
本主題包含下列幾節:
必要條件
本主題假設您知道如何為現有控制項建立新的 ControlTemplate 控制項、熟悉控制項合約上的元素,以及瞭解建立控制項 範本中所 討論的概念。
注意
若要建立可以自訂其外觀的控制項,您必須建立繼承自 Control 類別的控制項或其子類別以外的 UserControl 其中一個。 繼承自 UserControl 的控制項是可快速建立的控制項,但不會使用 ControlTemplate ,而且您無法自訂其外觀。
元件和狀態模型
元件和狀態模型會指定如何定義控制項的視覺結構和視覺行為。 若要遵循元件和狀態模型,您應該執行下列動作:
在 控制項的 中 ControlTemplate 定義視覺結構和視覺行為。
當您的控制項邏輯與控制項範本的元件互動時,請遵循特定最佳做法。
提供控制項合約,以指定 中應包含 ControlTemplate 的內容。
當您在 控制項中 ControlTemplate 定義視覺結構和視覺行為時,應用程式作者可以藉由建立新的 ControlTemplate 而非撰寫程式碼來變更控制項的視覺結構和視覺行為。 您必須提供控制項合約,告知應用程式作者應在 中 ControlTemplate 定義哪些 FrameworkElement 物件和狀態。 當您與 中的 ControlTemplate 元件互動時,應該遵循一些最佳做法,讓控制項正確地處理不完整 ControlTemplate 的 。 如果您遵循這三個原則,應用程式作者就能夠像使用 WPF 一樣輕鬆地為控制項建立 ControlTemplate 。 下一節將詳細說明這些建議。
在 ControlTemplate 中定義控制項的視覺結構和視覺行為
當您使用元件和狀態模型建立自訂控制項時,您可以在其 ControlTemplate 中定義控制項的視覺結構和視覺行為,而不是在其邏輯中。 控制項的視覺結構是組成控制項的物件 FrameworkElement 複合。 視覺行為是控制項處於特定狀態時顯示的方式。 如需建立 ControlTemplate 指定控制項視覺結構和視覺行為之 的詳細資訊,請參閱 建立控制項 的範本。
在 控制項的範例中 NumericUpDown
,視覺結構包含兩 RepeatButton 個控制項和 一個 TextBlock 。 如果您在控制項的程式 NumericUpDown
代碼中新增這些控制項---在它的建構函式中,例如,這些控制項的位置是無法變動的。 您應該在 中定義控制項的視覺結構和視覺行為,而不是在其程式碼中 ControlTemplate 定義它。 然後,應用程式開發人員自訂按鈕的位置,並 TextBlock 指定當為負數時 Value
所發生的行為,因為 ControlTemplate 可以取代 。
下列範例顯示 控制項的視覺結構,其中包含 RepeatButton 要增加 Value
的 NumericUpDown
、 RepeatButton 要減少 Value
的 ,以及 TextBlock 要顯示的 Value
。
<ControlTemplate TargetType="src:NumericUpDown">
<Grid Margin="3"
Background="{TemplateBinding Background}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border BorderThickness="1" BorderBrush="Gray"
Margin="7,2,2,2" Grid.RowSpan="2"
Background="#E0FFFFFF"
VerticalAlignment="Center"
HorizontalAlignment="Stretch">
<!--Bind the TextBlock to the Value property-->
<TextBlock Name="TextBlock"
Width="60" TextAlignment="Right" Padding="5"
Text="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type src:NumericUpDown}},
Path=Value}"/>
</Border>
<RepeatButton Content="Up" Margin="2,5,5,0"
Name="UpButton"
Grid.Column="1" Grid.Row="0"/>
<RepeatButton Content="Down" Margin="2,0,5,5"
Name="DownButton"
Grid.Column="1" Grid.Row="1"/>
<Rectangle Name="FocusVisual" Grid.ColumnSpan="2" Grid.RowSpan="2"
Stroke="Black" StrokeThickness="1"
Visibility="Collapsed"/>
</Grid>
</Grid>
</ControlTemplate>
控制項的 NumericUpDown
視覺行為是,如果值為負數,則值為紅色字型。 如果您在 為負數時 Value
變更 TextBlockForeground 程式碼中的 ,則 NumericUpDown
一律會顯示紅色負值。 您可以藉由將 物件加入 VisualState 至 ControlTemplate ,以指定 控制項的 ControlTemplate 視覺行為。 下列範例顯示 VisualState 和 Negative
狀態的物件 Positive
。 Positive
和 Negative
互斥(控制項一律位於兩者中的一個),因此範例會將 VisualState 物件放入單 VisualStateGroup 一 中。 當控制項進入 Negative
狀態時, Foreground 的 TextBlock 會變成紅色。 當控制項處於 Positive
狀態時,會 Foreground 傳回其原始值。 VisualState在 中 ControlTemplate 定義 物件會進一步討論在 建立控制項 的範本中。
注意
請務必在 的根 FrameworkElementControlTemplate 目錄上設定 VisualStateManager.VisualStateGroups 附加屬性。
<ControlTemplate TargetType="local:NumericUpDown">
<Grid Margin="3"
Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ValueStates">
<!--Make the Value property red when it is negative.-->
<VisualState Name="Negative">
<Storyboard>
<ColorAnimation To="Red"
Storyboard.TargetName="TextBlock"
Storyboard.TargetProperty="(Foreground).(Color)"/>
</Storyboard>
</VisualState>
<!--Return the TextBlock's Foreground to its
original color.-->
<VisualState Name="Positive"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
在程式碼中使用 ControlTemplate 的部分
作者 ControlTemplate 可能會有目的或錯誤地省略 FrameworkElement 或 VisualState 物件,但控制項的邏輯可能需要這些元件才能正常運作。 元件和狀態模型會指定您的控制項應該能夠復原 ControlTemplate 遺失 FrameworkElement 的 或 VisualState 物件。 如果您的 FrameworkElement 控制項遺漏 、 VisualState 或 ,則不應該擲回例外狀況或 VisualStateGroupControlTemplate 回報錯誤。 本節說明與 FrameworkElement 物件互動及管理狀態的建議做法。
預期遺漏 FrameworkElement 物件
當您在 中 ControlTemplate 定義 FrameworkElement 物件時,控制項的邏輯可能需要與其中一些物件互動。 例如, NumericUpDown
控制項會訂閱按鈕 Click 的事件,以增加或減少 Value
,並將 的 TextBlock 屬性設定 Text 為 Value
。 如果自訂 ControlTemplate 省略 TextBlock 或 按鈕,控制項會遺失部分功能,但您應該確定控制項不會造成錯誤。 例如,如果 ControlTemplate 不包含要變更 Value
的按鈕,則會 NumericUpDown
遺失該功能,但使用 ControlTemplate 的應用程式會繼續執行。
下列做法可確保控制項會正確回應遺漏 FrameworkElement 的物件:
x:Name
針對您需要在程式碼中參考的每個 FrameworkElement 設定 屬性。定義每個 FrameworkElement 您需要與其互動的私人屬性。
訂閱和取消訂閱控制項在 屬性的 set 存取子中 FrameworkElement 處理的任何事件。
設定您在 FrameworkElement 方法的步驟 2 OnApplyTemplate 中定義的屬性。 這是 中 最早 FrameworkElementControlTemplate 可供 控制項使用。
x:Name
使用 的 FrameworkElement ,從 ControlTemplate 取得它。在存取其成員之前,請先確認 FrameworkElement 不是
null
。null
如果是 ,請勿報告錯誤。
下列範例顯示控制項如何 NumericUpDown
根據上述清單中的建議與 FrameworkElement 物件互動。
在 中定義 控制項視覺 NumericUpDown
結構的範例中 ControlTemplate , RepeatButton 增加 Value
的 會將其 x:Name
屬性設定為 UpButton
。 下列範例會宣告名為 UpButtonElement
的屬性,表示 RepeatButton 中宣告的 ControlTemplate 。 如果 不是 ,存取 set
子會先取消訂閱按鈕 Click 的事件 UpDownElement
,然後設定 屬性,然後訂閱 Click 事件。 null
另外還有一個屬性已定義,但此處未顯示,其他 RepeatButton 稱為 DownButtonElement
的 。
private RepeatButton upButtonElement;
private RepeatButton UpButtonElement
{
get
{
return upButtonElement;
}
set
{
if (upButtonElement != null)
{
upButtonElement.Click -=
new RoutedEventHandler(upButtonElement_Click);
}
upButtonElement = value;
if (upButtonElement != null)
{
upButtonElement.Click +=
new RoutedEventHandler(upButtonElement_Click);
}
}
}
Private m_upButtonElement As RepeatButton
Private Property UpButtonElement() As RepeatButton
Get
Return m_upButtonElement
End Get
Set(ByVal value As RepeatButton)
If m_upButtonElement IsNot Nothing Then
RemoveHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
End If
m_upButtonElement = value
If m_upButtonElement IsNot Nothing Then
AddHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
End If
End Set
End Property
下列範例顯示 OnApplyTemplate 控制項的 NumericUpDown
。 此範例會 GetTemplateChild 使用 方法從 ControlTemplate 取得 FrameworkElement 物件。 請注意,此範例會防範 GetTemplateChild 尋找 FrameworkElement 具有指定名稱且不是預期型別的 案例。 最好忽略具有指定 x:Name
但類型錯誤的元素。
public override void OnApplyTemplate()
{
UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
//TextElement = GetTemplateChild("TextBlock") as TextBlock;
UpdateStates(false);
}
Public Overloads Overrides Sub OnApplyTemplate()
UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)
UpdateStates(False)
End Sub
遵循上述範例所示的作法,您可以確定當 缺少 FrameworkElement 時 ControlTemplate ,控制項會繼續執行。
使用 VisualStateManager 管理狀態
會 VisualStateManager 追蹤控制項的狀態,並執行在狀態之間轉換所需的邏輯。 當您將 物件加入 至 ControlTemplate 時,您會將它們 VisualState 新增至 VisualStateGroup ,並將 新增 VisualStateGroup 至 VisualStateManager.VisualStateGroups 附加屬性,讓 VisualStateManager 可以存取它們。
下列範例會重複上一個範例,其中顯示 VisualState 對應至 Positive
控制項的 和 Negative
狀態的物件。 中的 Negative
VisualState 會 Storyboard 變成 Foreground 紅色的 TextBlock 。 NumericUpDown
當控制項處於 Negative
狀態時,狀態中的 Negative
分鏡腳本就會開始。 Storyboard然後,當控制項回到 Positive
狀態時,狀態中的 Negative
會停止。 Positive
VisualState 不需要包含 Storyboard ,因為 當 的 停止時 Negative
Storyboard ,會 Foreground 傳回其原始色彩。
<ControlTemplate TargetType="local:NumericUpDown">
<Grid Margin="3"
Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ValueStates">
<!--Make the Value property red when it is negative.-->
<VisualState Name="Negative">
<Storyboard>
<ColorAnimation To="Red"
Storyboard.TargetName="TextBlock"
Storyboard.TargetProperty="(Foreground).(Color)"/>
</Storyboard>
</VisualState>
<!--Return the TextBlock's Foreground to its
original color.-->
<VisualState Name="Positive"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
請注意, TextBlock 會指定 名稱,但 TextBlock 不是 在 控制項合約 NumericUpDown
中,因為控制項的邏輯永遠不會參考 TextBlock 。 中 ControlTemplate 參考的專案具有名稱,但不需要是控制項合約的一部分,因為控制項的新 ControlTemplate 專案可能不需要參考該專案。 例如,為 建立新 ControlTemplateNumericUpDown
的人員可能會決定不藉由變更 來指出 Value
是負面的 Foreground 。 在此情況下,程式碼或不會 ControlTemplate 依名稱參考 TextBlock 。
控制項的邏輯負責變更控制項的狀態。 下列範例顯示 NumericUpDown
控制項會呼叫 GoToState 方法,當 為 0 或更新時 Value
進入 Positive
狀態,以及 Negative
小於 0 時 Value
的狀態。
if (Value >= 0)
{
VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Negative", useTransitions);
}
If Value >= 0 Then
VisualStateManager.GoToState(Me, "Positive", useTransitions)
Else
VisualStateManager.GoToState(Me, "Negative", useTransitions)
End If
GoToState方法會執行適當啟動和停止分鏡腳本所需的邏輯。 當控制項呼叫 GoToState 以變更其狀態時, VisualStateManager 會執行下列動作:
VisualState如果控制項要有 Storyboard ,腳本就會開始。 然後,如果 VisualState 控制項來自 的 具有 Storyboard ,腳本就會結束。
如果控制項已經處於指定的狀態, GoToState 則不採取任何動作並傳
true
回 。如果 指定的狀態不存在於 的
control
中 ControlTemplate , GoToState 則不會採取任何動作並傳false
回 。
使用 VisualStateManager 的最佳做法
建議您執行下列動作來維護控制項的狀態:
使用屬性來追蹤其狀態。
建立協助程式方法,以在狀態之間轉換。
控制項 NumericUpDown
會使用其 Value
屬性來追蹤它是否處於 Positive
或 Negative
狀態。 控制項 NumericUpDown
也會定義 Focused
和 UnFocused
狀態,其會追蹤 IsFocused 屬性。 如果您使用不自然對應至控制項屬性的狀態,您可以定義私用屬性來追蹤狀態。
更新所有狀態的單一方法會集中呼叫 , VisualStateManager 並讓您的程式碼保持可管理。 下列範例顯示 NumericUpDown
控制項的協助程式方法 UpdateStates
。 當 大於或等於 0 時 Value
, Control 會處於 Positive
狀態。 當 小於 0 時 Value
,控制項處於 Negative
狀態。 當 為 true
時 IsFocused ,控制項處於 Focused
狀態,否則為 Unfocused
狀態。 不論狀態變更為何,控制項都可以在需要變更其狀態時呼叫 UpdateStates
。
private void UpdateStates(bool useTransitions)
{
if (Value >= 0)
{
VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Negative", useTransitions);
}
if (IsFocused)
{
VisualStateManager.GoToState(this, "Focused", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Unfocused", useTransitions);
}
}
Private Sub UpdateStates(ByVal useTransitions As Boolean)
If Value >= 0 Then
VisualStateManager.GoToState(Me, "Positive", useTransitions)
Else
VisualStateManager.GoToState(Me, "Negative", useTransitions)
End If
If IsFocused Then
VisualStateManager.GoToState(Me, "Focused", useTransitions)
Else
VisualStateManager.GoToState(Me, "Unfocused", useTransitions)
End If
End Sub
如果您在控制項已經處於該狀態時將狀態名稱 GoToState 傳遞給 , GoToState 則不會執行任何動作,因此您不需要檢查控制項的目前狀態。 例如,如果 Value
從一個負數變更為另一個負數,狀態的 Negative
分鏡腳本不會中斷,而且使用者不會看到控制項中的變更。
會 VisualStateManager 使用 VisualStateGroup 物件來判斷呼叫 GoToState 時要結束的狀態。 控制項一律處於 VisualStateGroup 一 ControlTemplate 個狀態,每個都定義在其 中,而且只有在它進入相同 VisualStateGroup 狀態時,它才會離開狀態。 例如, ControlTemplate 控制項的 NumericUpDown
定義 Positive
中的 和 Negative
VisualState 物件,以及 Focused
另一 VisualStateGroup 個 中的 和 Unfocused
VisualState 物件。 (您可以在本主題的 [完成範例 ] 區段中看到 Focused
並 VisualStateUnfocused
定義 當控制項從 Positive
狀態移至 Negative
狀態,反之亦然,控制項會維持在 Focused
或 Unfocused
狀態中。
控制項狀態可能會變更的一般位置有三個:
ControlTemplate當 套用至 Control 時。
當屬性變更時。
事件發生時。
下列範例示範在這些情況下更新 控制項的狀態 NumericUpDown
。
您應該更新 方法中的 OnApplyTemplate 控制項狀態,以便在套用 時 ControlTemplate ,控制項出現在正確的狀態。 下列範例會在 中 OnApplyTemplate 呼叫 UpdateStates
,以確保 控制項處於適當的狀態。 例如,假設您建立 NumericUpDown
控制項,然後將它 Foreground 設定為綠色,並將其 Value
設定為 -5。 如果您未在套用 至 NumericUpDown
控制項時 ControlTemplate 呼叫 UpdateStates
,則控制項不在 狀態中 Negative
,且值為綠色而非紅色。 您必須呼叫 UpdateStates
以將控制項 Negative
置於 狀態。
public override void OnApplyTemplate()
{
UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
//TextElement = GetTemplateChild("TextBlock") as TextBlock;
UpdateStates(false);
}
Public Overloads Overrides Sub OnApplyTemplate()
UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)
UpdateStates(False)
End Sub
當屬性變更時,您通常需要更新控制項的狀態。 下列範例顯示整個 ValueChangedCallback
方法。 因為在變更時 Value
呼叫 ,因此 ValueChangedCallback
方法會呼叫 UpdateStates
,以防 Value
從正值變更為負數,反之亦然。 當變更但維持正數或負值時 Value
,可以接受呼叫 UpdateStates
,因為在此情況下,控制項不會變更狀態。
private static void ValueChangedCallback(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
NumericUpDown ctl = (NumericUpDown)obj;
int newValue = (int)args.NewValue;
// Call UpdateStates because the Value might have caused the
// control to change ValueStates.
ctl.UpdateStates(true);
// Call OnValueChanged to raise the ValueChanged event.
ctl.OnValueChanged(
new ValueChangedEventArgs(NumericUpDown.ValueChangedEvent,
newValue));
}
Private Shared Sub ValueChangedCallback(ByVal obj As DependencyObject,
ByVal args As DependencyPropertyChangedEventArgs)
Dim ctl As NumericUpDown = DirectCast(obj, NumericUpDown)
Dim newValue As Integer = CInt(args.NewValue)
' Call UpdateStates because the Value might have caused the
' control to change ValueStates.
ctl.UpdateStates(True)
' Call OnValueChanged to raise the ValueChanged event.
ctl.OnValueChanged(New ValueChangedEventArgs(NumericUpDown.ValueChangedEvent, newValue))
End Sub
您可能也需要在事件發生時更新狀態。 下列範例顯示 NumericUpDown
上的 Control 呼叫 UpdateStates
來處理 GotFocus 事件。
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
UpdateStates(true);
}
Protected Overloads Overrides Sub OnGotFocus(ByVal e As RoutedEventArgs)
MyBase.OnGotFocus(e)
UpdateStates(True)
End Sub
可 VisualStateManager 協助您管理控制項的狀態。 藉由使用 VisualStateManager ,您可確保控制項在狀態之間正確轉換。 如果您遵循本節所述的使用 VisualStateManager 建議,則控制項的程式碼將會保持可讀取和維護。
提供控制項合約
您提供控制項合約,讓 ControlTemplate 作者知道要放入範本的內容。 控制項合約有三個元素:
控制項邏輯所使用的視覺元素。
控制項的狀態及每個狀態所屬的群組。
在視覺上影響控制項的公用屬性。
建立新 ControlTemplate 專案的人員必須知道控制項邏輯使用的物件 FrameworkElement 、每個物件的類型,以及其名稱為何。 作者 ControlTemplate 也必須知道控制項可能所在的每個可能狀態的名稱,以及狀態所在的狀態 VisualStateGroup 。
傳回範例 NumericUpDown
時,控制項預期 ControlTemplate 會有下列 FrameworkElement 物件:
RepeatButton名為
UpButton
的 。呼叫的 RepeatButton
DownButton.
控制項可以處於下列狀態:
在
ValueStates
VisualStateGroupPositive
Negative
在
FocusStates
VisualStateGroupFocused
Unfocused
若要指定控制項預期的物件 FrameworkElement ,您可以使用 TemplatePartAttribute ,指定預期專案的名稱和類型。 若要指定控制項的可能狀態,您可以使用 TemplateVisualStateAttribute ,指定狀態的名稱及其 VisualStateGroup 所屬。 將 和 TemplatePartAttributeTemplateVisualStateAttribute 放在 控制項的類別定義上。
影響控制面板的任何公用屬性也是控制項合約的一部分。
下列範例會 FrameworkElement 指定 控制項的物件 NumericUpDown
和狀態。
[TemplatePart(Name = "UpButtonElement", Type = typeof(RepeatButton))]
[TemplatePart(Name = "DownButtonElement", Type = typeof(RepeatButton))]
[TemplateVisualState(Name = "Positive", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Negative", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Focused", GroupName = "FocusedStates")]
[TemplateVisualState(Name = "Unfocused", GroupName = "FocusedStates")]
public class NumericUpDown : Control
{
public static readonly DependencyProperty BackgroundProperty;
public static readonly DependencyProperty BorderBrushProperty;
public static readonly DependencyProperty BorderThicknessProperty;
public static readonly DependencyProperty FontFamilyProperty;
public static readonly DependencyProperty FontSizeProperty;
public static readonly DependencyProperty FontStretchProperty;
public static readonly DependencyProperty FontStyleProperty;
public static readonly DependencyProperty FontWeightProperty;
public static readonly DependencyProperty ForegroundProperty;
public static readonly DependencyProperty HorizontalContentAlignmentProperty;
public static readonly DependencyProperty PaddingProperty;
public static readonly DependencyProperty TextAlignmentProperty;
public static readonly DependencyProperty TextDecorationsProperty;
public static readonly DependencyProperty TextWrappingProperty;
public static readonly DependencyProperty VerticalContentAlignmentProperty;
public Brush Background { get; set; }
public Brush BorderBrush { get; set; }
public Thickness BorderThickness { get; set; }
public FontFamily FontFamily { get; set; }
public double FontSize { get; set; }
public FontStretch FontStretch { get; set; }
public FontStyle FontStyle { get; set; }
public FontWeight FontWeight { get; set; }
public Brush Foreground { get; set; }
public HorizontalAlignment HorizontalContentAlignment { get; set; }
public Thickness Padding { get; set; }
public TextAlignment TextAlignment { get; set; }
public TextDecorationCollection TextDecorations { get; set; }
public TextWrapping TextWrapping { get; set; }
public VerticalAlignment VerticalContentAlignment { get; set; }
}
<TemplatePart(Name:="UpButtonElement", Type:=GetType(RepeatButton))>
<TemplatePart(Name:="DownButtonElement", Type:=GetType(RepeatButton))>
<TemplateVisualState(Name:="Positive", GroupName:="ValueStates")>
<TemplateVisualState(Name:="Negative", GroupName:="ValueStates")>
<TemplateVisualState(Name:="Focused", GroupName:="FocusedStates")>
<TemplateVisualState(Name:="Unfocused", GroupName:="FocusedStates")>
Public Class NumericUpDown
Inherits Control
Public Shared ReadOnly TextAlignmentProperty As DependencyProperty
Public Shared ReadOnly TextDecorationsProperty As DependencyProperty
Public Shared ReadOnly TextWrappingProperty As DependencyProperty
Public Property TextAlignment() As TextAlignment
Public Property TextDecorations() As TextDecorationCollection
Public Property TextWrapping() As TextWrapping
End Class
完整範例
下列範例是 控制項的 NumericUpDown
整個 ControlTemplate 。
<!--This is the contents of the themes/generic.xaml file.-->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VSMCustomControl">
<Style TargetType="{x:Type local:NumericUpDown}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:NumericUpDown">
<Grid Margin="3"
Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ValueStates">
<!--Make the Value property red when it is negative.-->
<VisualState Name="Negative">
<Storyboard>
<ColorAnimation To="Red"
Storyboard.TargetName="TextBlock"
Storyboard.TargetProperty="(Foreground).(Color)"/>
</Storyboard>
</VisualState>
<!--Return the control to its initial state by
return the TextBlock's Foreground to its
original color.-->
<VisualState Name="Positive"/>
</VisualStateGroup>
<VisualStateGroup Name="FocusStates">
<!--Add a focus rectangle to highlight the entire control
when it has focus.-->
<VisualState Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisual"
Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!--Return the control to its initial state by
hiding the focus rectangle.-->
<VisualState Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border BorderThickness="1" BorderBrush="Gray"
Margin="7,2,2,2" Grid.RowSpan="2"
Background="#E0FFFFFF"
VerticalAlignment="Center"
HorizontalAlignment="Stretch">
<!--Bind the TextBlock to the Value property-->
<TextBlock Name="TextBlock"
Width="60" TextAlignment="Right" Padding="5"
Text="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:NumericUpDown}},
Path=Value}"/>
</Border>
<RepeatButton Content="Up" Margin="2,5,5,0"
Name="UpButton"
Grid.Column="1" Grid.Row="0"/>
<RepeatButton Content="Down" Margin="2,0,5,5"
Name="DownButton"
Grid.Column="1" Grid.Row="1"/>
<Rectangle Name="FocusVisual" Grid.ColumnSpan="2" Grid.RowSpan="2"
Stroke="Black" StrokeThickness="1"
Visibility="Collapsed"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
下列範例顯示 的 NumericUpDown
邏輯。
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
namespace VSMCustomControl
{
[TemplatePart(Name = "UpButtonElement", Type = typeof(RepeatButton))]
[TemplatePart(Name = "DownButtonElement", Type = typeof(RepeatButton))]
[TemplateVisualState(Name = "Positive", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Negative", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Focused", GroupName = "FocusedStates")]
[TemplateVisualState(Name = "Unfocused", GroupName = "FocusedStates")]
public class NumericUpDown : Control
{
public NumericUpDown()
{
DefaultStyleKey = typeof(NumericUpDown);
this.IsTabStop = true;
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value", typeof(int), typeof(NumericUpDown),
new PropertyMetadata(
new PropertyChangedCallback(ValueChangedCallback)));
public int Value
{
get
{
return (int)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
private static void ValueChangedCallback(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
NumericUpDown ctl = (NumericUpDown)obj;
int newValue = (int)args.NewValue;
// Call UpdateStates because the Value might have caused the
// control to change ValueStates.
ctl.UpdateStates(true);
// Call OnValueChanged to raise the ValueChanged event.
ctl.OnValueChanged(
new ValueChangedEventArgs(NumericUpDown.ValueChangedEvent,
newValue));
}
public static readonly RoutedEvent ValueChangedEvent =
EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Direct,
typeof(ValueChangedEventHandler), typeof(NumericUpDown));
public event ValueChangedEventHandler ValueChanged
{
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }
}
protected virtual void OnValueChanged(ValueChangedEventArgs e)
{
// Raise the ValueChanged event so applications can be alerted
// when Value changes.
RaiseEvent(e);
}
private void UpdateStates(bool useTransitions)
{
if (Value >= 0)
{
VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Negative", useTransitions);
}
if (IsFocused)
{
VisualStateManager.GoToState(this, "Focused", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Unfocused", useTransitions);
}
}
public override void OnApplyTemplate()
{
UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
//TextElement = GetTemplateChild("TextBlock") as TextBlock;
UpdateStates(false);
}
private RepeatButton downButtonElement;
private RepeatButton DownButtonElement
{
get
{
return downButtonElement;
}
set
{
if (downButtonElement != null)
{
downButtonElement.Click -=
new RoutedEventHandler(downButtonElement_Click);
}
downButtonElement = value;
if (downButtonElement != null)
{
downButtonElement.Click +=
new RoutedEventHandler(downButtonElement_Click);
}
}
}
void downButtonElement_Click(object sender, RoutedEventArgs e)
{
Value--;
}
private RepeatButton upButtonElement;
private RepeatButton UpButtonElement
{
get
{
return upButtonElement;
}
set
{
if (upButtonElement != null)
{
upButtonElement.Click -=
new RoutedEventHandler(upButtonElement_Click);
}
upButtonElement = value;
if (upButtonElement != null)
{
upButtonElement.Click +=
new RoutedEventHandler(upButtonElement_Click);
}
}
}
void upButtonElement_Click(object sender, RoutedEventArgs e)
{
Value++;
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
Focus();
}
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
UpdateStates(true);
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
UpdateStates(true);
}
}
public delegate void ValueChangedEventHandler(object sender, ValueChangedEventArgs e);
public class ValueChangedEventArgs : RoutedEventArgs
{
private int _value;
public ValueChangedEventArgs(RoutedEvent id, int num)
{
_value = num;
RoutedEvent = id;
}
public int Value
{
get { return _value; }
}
}
}
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Controls.Primitives
Imports System.Windows.Input
Imports System.Windows.Media
<TemplatePart(Name:="UpButtonElement", Type:=GetType(RepeatButton))> _
<TemplatePart(Name:="DownButtonElement", Type:=GetType(RepeatButton))> _
<TemplateVisualState(Name:="Positive", GroupName:="ValueStates")> _
<TemplateVisualState(Name:="Negative", GroupName:="ValueStates")> _
<TemplateVisualState(Name:="Focused", GroupName:="FocusedStates")> _
<TemplateVisualState(Name:="Unfocused", GroupName:="FocusedStates")> _
Public Class NumericUpDown
Inherits Control
Public Sub New()
DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
Me.IsTabStop = True
End Sub
Public Shared ReadOnly ValueProperty As DependencyProperty =
DependencyProperty.Register("Value", GetType(Integer), GetType(NumericUpDown),
New PropertyMetadata(New PropertyChangedCallback(AddressOf ValueChangedCallback)))
Public Property Value() As Integer
Get
Return CInt(GetValue(ValueProperty))
End Get
Set(ByVal value As Integer)
SetValue(ValueProperty, value)
End Set
End Property
Private Shared Sub ValueChangedCallback(ByVal obj As DependencyObject,
ByVal args As DependencyPropertyChangedEventArgs)
Dim ctl As NumericUpDown = DirectCast(obj, NumericUpDown)
Dim newValue As Integer = CInt(args.NewValue)
' Call UpdateStates because the Value might have caused the
' control to change ValueStates.
ctl.UpdateStates(True)
' Call OnValueChanged to raise the ValueChanged event.
ctl.OnValueChanged(New ValueChangedEventArgs(NumericUpDown.ValueChangedEvent, newValue))
End Sub
Public Shared ReadOnly ValueChangedEvent As RoutedEvent =
EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Direct,
GetType(ValueChangedEventHandler), GetType(NumericUpDown))
Public Custom Event ValueChanged As ValueChangedEventHandler
AddHandler(ByVal value As ValueChangedEventHandler)
Me.AddHandler(ValueChangedEvent, value)
End AddHandler
RemoveHandler(ByVal value As ValueChangedEventHandler)
Me.RemoveHandler(ValueChangedEvent, value)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
Me.RaiseEvent(e)
End RaiseEvent
End Event
Protected Overridable Sub OnValueChanged(ByVal e As ValueChangedEventArgs)
' Raise the ValueChanged event so applications can be alerted
' when Value changes.
MyBase.RaiseEvent(e)
End Sub
#Region "NUDCode"
Private Sub UpdateStates(ByVal useTransitions As Boolean)
If Value >= 0 Then
VisualStateManager.GoToState(Me, "Positive", useTransitions)
Else
VisualStateManager.GoToState(Me, "Negative", useTransitions)
End If
If IsFocused Then
VisualStateManager.GoToState(Me, "Focused", useTransitions)
Else
VisualStateManager.GoToState(Me, "Unfocused", useTransitions)
End If
End Sub
Public Overloads Overrides Sub OnApplyTemplate()
UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)
UpdateStates(False)
End Sub
Private m_downButtonElement As RepeatButton
Private Property DownButtonElement() As RepeatButton
Get
Return m_downButtonElement
End Get
Set(ByVal value As RepeatButton)
If m_downButtonElement IsNot Nothing Then
RemoveHandler m_downButtonElement.Click, AddressOf downButtonElement_Click
End If
m_downButtonElement = value
If m_downButtonElement IsNot Nothing Then
AddHandler m_downButtonElement.Click, AddressOf downButtonElement_Click
End If
End Set
End Property
Private Sub downButtonElement_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
Value -= 1
End Sub
Private m_upButtonElement As RepeatButton
Private Property UpButtonElement() As RepeatButton
Get
Return m_upButtonElement
End Get
Set(ByVal value As RepeatButton)
If m_upButtonElement IsNot Nothing Then
RemoveHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
End If
m_upButtonElement = value
If m_upButtonElement IsNot Nothing Then
AddHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
End If
End Set
End Property
Private Sub upButtonElement_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
Value += 1
End Sub
Protected Overloads Overrides Sub OnMouseLeftButtonDown(ByVal e As MouseButtonEventArgs)
MyBase.OnMouseLeftButtonDown(e)
Focus()
End Sub
Protected Overloads Overrides Sub OnGotFocus(ByVal e As RoutedEventArgs)
MyBase.OnGotFocus(e)
UpdateStates(True)
End Sub
Protected Overloads Overrides Sub OnLostFocus(ByVal e As RoutedEventArgs)
MyBase.OnLostFocus(e)
UpdateStates(True)
End Sub
#End Region
End Class
Public Delegate Sub ValueChangedEventHandler(ByVal sender As Object,
ByVal e As ValueChangedEventArgs)
Public Class ValueChangedEventArgs
Inherits RoutedEventArgs
Public Sub New(ByVal id As RoutedEvent,
ByVal num As Integer)
Value = num
RoutedEvent = id
End Sub
Public ReadOnly Property Value() As Integer
End Class
另請參閱
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應