Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Windows Presentation Foundation (WPF) memberi Anda kemampuan untuk membuat kontrol yang tampilannya dapat disesuaikan. Misalnya, Anda dapat mengubah tampilan CheckBox lebih dari yang dapat dilakukan dengan mengatur properti dengan membuat ControlTemplate baru. Ilustrasi berikut menunjukkan CheckBox yang menggunakan default ControlTemplate dan CheckBox yang menggunakan kustom ControlTemplate.
Kotak Centang yang menggunakan templat kontrol default
Kotak Centang yang menggunakan templat kontrol kustom
Jika Anda mengikuti model bagian dan status saat membuat kontrol, tampilan kontrol Anda akan dapat disesuaikan. Alat desainer seperti Blend for Visual Studio mendukung model bagian dan status, jadi ketika Anda mengikuti model ini, kontrol Anda akan dapat disesuaikan dalam jenis aplikasi tersebut. Topik ini membahas model bagian dan status dan cara mengikutinya saat Anda membuat kontrol Anda sendiri. Topik ini menggunakan contoh kontrol kustom, NumericUpDown
, untuk mengilustrasikan filosofi model ini.
NumericUpDown
Kontrol menampilkan nilai numerik, yang dapat ditingkatkan atau dikurangi pengguna dengan mengklik tombol kontrol. Ilustrasi berikut menunjukkan kontrol NumericUpDown
yang dibahas dalam topik ini.
Kontrol NumericUpDown kustom
Topik ini berisi bagian berikut:
Prasyarat
Topik ini mengasumsikan bahwa Anda tahu cara membuat ControlTemplate baru untuk kontrol yang sudah ada, memahami unsur-unsur dalam kontrak kontrol, dan memahami konsep yang dijelaskan di Membuat template untuk kontrol.
Nota
Untuk membuat kontrol yang dapat menyesuaikan tampilannya, Anda harus membuat kontrol yang mewarisi dari Control kelas atau salah satu subkelasnya selain UserControl. Kontrol yang mewarisi adalah UserControl kontrol yang dapat dibuat dengan cepat, tetapi tidak menggunakan ControlTemplate dan Anda tidak dapat menyesuaikan tampilannya.
Model Bagian dan Status
Model bagian dan status menentukan cara menentukan struktur visual dan perilaku visual kontrol. Untuk mengikuti model komponen dan keadaan, Anda perlu melakukan langkah-langkah berikut:
Tentukan struktur visual dan perilaku visual dalam ControlTemplate kontrol.
Ikuti praktik terbaik tertentu saat logika kontrol Anda berinteraksi dengan bagian templat kontrol.
Berikan kontrak kontrol untuk menentukan apa yang harus disertakan ControlTemplatedalam .
Saat Anda menentukan struktur visual dan perilaku visual dalam ControlTemplate kontrol, penulis aplikasi dapat mengubah struktur visual dan perilaku visual kontrol Anda dengan membuat kode baru ControlTemplate alih-alih menulis. Anda harus memberikan kontrak kontrol yang memberi tahu penulis aplikasi objek dan status mana yang FrameworkElement harus didefinisikan dalam ControlTemplate. Anda harus mengikuti beberapa praktik terbaik saat berinteraksi dengan bagian-bagian dalam ControlTemplate sehingga kontrol Anda menangani ControlTemplate yang tidak lengkap dengan benar. Jika Anda mengikuti ketiga prinsip ini, penulis aplikasi akan dapat membuat ControlTemplate untuk kontrol Anda semudah mereka membuatnya untuk kontrol yang disertakan dengan WPF. Bagian berikut menjelaskan masing-masing rekomendasi ini secara rinci.
Menentukan Struktur Visual dan Perilaku Visual Kontrol dalam ControlTemplate
Saat Anda membuat kontrol kustom dengan menggunakan model bagian dan status, Anda menentukan struktur visual kontrol dan perilaku visual di dalamnya ControlTemplate alih-alih dalam logikanya. Struktur visual kontrol adalah komposit FrameworkElement objek yang membentuk kontrol. Perilaku visual adalah cara kontrol muncul ketika berada dalam keadaan tertentu. Untuk informasi lebih lanjut tentang cara membuat ControlTemplate yang menentukan struktur visual dan perilaku visual dari kontrol, lihat Membuat templat untuk kontrol.
Dalam contoh kontrol NumericUpDown
, struktur visual mencakup dua kontrol RepeatButton dan satu TextBlock. Jika Anda menambahkan kontrol ini dalam kode NumericUpDown
kontrol--di konstruktornya, misalnya--posisi kontrol tersebut tidak akan dapat diubah. Alih-alih mendefinisikan struktur visual kontrol dan perilaku visual dalam kodenya, Anda harus menentukannya di ControlTemplate. Kemudian pengembang aplikasi untuk menyesuaikan posisi tombol dan TextBlock menentukan perilaku apa yang terjadi ketika Value
negatif karena ControlTemplate dapat diganti.
Contoh berikut menunjukkan struktur visual dari kontrol NumericUpDown
, yang mencakup RepeatButton untuk meningkatkan Value
, sebuah RepeatButton untuk mengurangi Value
, dan TextBlock untuk menampilkan 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>
Perilaku visual dari kontrol NumericUpDown
adalah nilai tersebut berwarna merah jika negatif. Jika Anda mengubah Foreground dari TextBlock dalam kode saat Value
negatif, NumericUpDown
akan selalu menampilkan nilai negatif merah. Anda menentukan perilaku visual kontrol di ControlTemplate dengan menambahkan VisualState objek ke ControlTemplate. Contoh berikut menunjukkan VisualState objek untuk status Positive
dan Negative
.
Positive
dan Negative
saling eksklusif (kontrol selalu tepat di salah satu dari keduanya), sehingga contohnya menempatkan VisualState objek ke dalam satu VisualStateGroup. Ketika sistem masuk ke Negative
status, Foreground dari TextBlock menjadi merah. Saat kontrol berada dalam status Positive
, kembali Foreground ke nilai aslinya. Menentukan VisualState objek dalam ControlTemplate lebih lanjut dibahas dalam Membuat templat untuk kontrol.
Nota
Pastikan untuk mengatur properti terlampir VisualStateManager.VisualStateGroups dari akar FrameworkElementControlTemplate.
<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>
Menggunakan Bagian Template Kontrol dalam Kode
Penulis ControlTemplate mungkin menghilangkan FrameworkElement atau VisualState objek, baik secara sengaja atau tidak sengaja, tetapi logika kontrol Anda mungkin memerlukan bagian-bagian tersebut agar berfungsi dengan baik. Model bagian dan status menentukan bahwa kontrol Anda harus tangguh terhadap ControlTemplate yang kehilangan objek FrameworkElement atau VisualState. Kontrol Anda tidak boleh melempar pengecualian atau melaporkan kesalahan jika FrameworkElement, VisualState, atau VisualStateGroup hilang dari ControlTemplate. Bagian ini menjelaskan praktik yang direkomendasikan untuk berinteraksi dengan FrameworkElement objek dan mengelola status.
Mengantisipasi Objek FrameworkElement yang Hilang
Saat Anda menentukan FrameworkElement objek dalam ControlTemplate, logika kontrol Anda mungkin perlu berinteraksi dengan beberapa di antaranya. Misalnya, kontrol NumericUpDown
mendaftar ke acara tombol Click untuk menambah atau mengurangi Value
dan menetapkan properti Text dari TextBlock ke Value
. Jika kontrol kustom ControlTemplate menghilangkan TextBlock atau tombol, dapat diterima bahwa kontrol kehilangan beberapa fungsionalitasnya, namun Anda harus memastikan bahwa kontrol Anda tidak menimbulkan kesalahan. Misalnya, jika ControlTemplate tidak berisi tombol untuk mengubah Value
, maka NumericUpDown
kehilangan fungsionalitas tersebut, tetapi aplikasi yang menggunakan ControlTemplate akan terus berjalan.
Praktik berikut akan memastikan bahwa kontrol Anda merespons objek yang hilang FrameworkElement dengan benar:
Atur
x:Name
atribut untuk setiap FrameworkElement yang perlu Anda referensikan dalam kode.Tentukan properti privat untuk masing-masing FrameworkElement yang perlu Anda berinteraksi dengan.
Berlangganan atau berhenti berlangganan dari event apa pun yang ditangani kontrol Anda dalam pengatur akses properti FrameworkElement.
Atur properti FrameworkElement yang Anda tentukan di langkah 2 dengan menggunakan metode OnApplyTemplate. Ini adalah waktu terawal FrameworkElement dalam ControlTemplate dapat dikendalikan. Gunakan
x:Name
milik FrameworkElement untuk mendapatkannya dari ControlTemplate.Pastikan bahwa FrameworkElement tidak
null
sebelum mengakses anggota-anggotanya. Jika yanull
, jangan laporkan kesalahan.
Contoh berikut menunjukkan bagaimana NumericUpDown
kontrol berinteraksi dengan FrameworkElement objek sesuai dengan rekomendasi dalam daftar sebelumnya.
Dalam contoh yang mendefinisikan struktur visual NumericUpDown
dari kontrol di ControlTemplate, RepeatButton yang meningkatkan Value
memiliki atribut x:Name
yang diatur ke UpButton
. Contoh berikut mendeklarasikan properti yang disebut UpButtonElement
yang mewakili RepeatButton yang dideklarasikan dalam ControlTemplate. Aksesori set
pertama-tama membatalkan langganan dari peristiwa tombol Click jika UpDownElement
tidak null
, lalu mengatur properti, dan kemudian berlangganan ke peristiwa Click. Ada juga properti yang ditentukan, tetapi tidak ditampilkan di sini, untuk RepeatButton lainnya, yang disebut 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
Contoh berikut menunjukkan OnApplyTemplate untuk NumericUpDown
kontrol. Contoh menggunakan GetTemplateChild metode untuk mendapatkan FrameworkElement objek dari ControlTemplate. Perhatikan bahwa contoh melindungi dari kasus di mana GetTemplateChild menemukan FrameworkElement dengan nama yang ditentukan yang bukan dari jenis yang diharapkan. Ini juga merupakan praktik terbaik untuk mengabaikan elemen yang memiliki jenis yang ditentukan x:Name
tetapi merupakan jenis yang salah.
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
Dengan mengikuti praktik yang ditampilkan dalam contoh sebelumnya, Anda memastikan bahwa kontrol Anda akan terus berjalan ketika ControlTemplate hilang .FrameworkElement
Menggunakan VisualStateManager untuk Mengelola Status
VisualStateManager melacak status kontrol dan melakukan logika yang diperlukan untuk transisi antar status. Saat Anda menambahkan VisualState objek ke ControlTemplate, Anda menambahkannya ke VisualStateGroup dan menambahkannya VisualStateGroup ke VisualStateManager.VisualStateGroups properti terlampir sehingga VisualStateManager memiliki akses ke objek tersebut.
Contoh berikut mengulangi contoh sebelumnya yang memperlihatkan objek VisualState yang sesuai dengan keadaan Positive
dan Negative
pada kontrol.
Storyboard dalam Negative
VisualState mengubah Foreground dari TextBlock menjadi merah. Ketika pengendali berada dalam keadaan NumericUpDown
, papan cerita dalam keadaan Negative
mulai berjalan. Kemudian Storyboard dalam status Negative
berhenti ketika kontrol kembali ke status Positive
.
Positive
VisualState tidak perlu berisi Storyboard karena ketika Storyboard untuk Negative
berhenti, Foreground kembali ke warna aslinya.
<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>
Perhatikan bahwa TextBlock diberikan nama, tetapi TextBlock tidak ada dalam kontrak kontrol karena NumericUpDown
logika kontrol tidak pernah mereferensikan TextBlock. Elemen yang dirujuk dalam ControlTemplate memiliki nama, tetapi tidak perlu menjadi bagian dari kontrak kontrol karena baru ControlTemplate untuk kontrol mungkin tidak perlu mereferensikan elemen tersebut. Misalnya, seseorang yang membuat satu ControlTemplate baru untuk NumericUpDown
mungkin memutuskan untuk tidak menunjukkan bahwa Value
negatif dengan mengubah Foreground. Dalam hal ini, baik kode maupun ControlTemplate tidak merujuk TextBlock berdasarkan nama.
Logika kontrol bertanggung jawab untuk mengubah status kontrol. Contoh berikut menunjukkan bahwa NumericUpDown
kontrol memanggil GoToState metode untuk masuk ke Positive
status ketika Value
0 atau lebih besar, dan Negative
status ketika Value
kurang dari 0.
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
Metode GoToState melakukan logika yang diperlukan untuk memulai dan menghentikan storyboard dengan tepat. Saat kontrol memanggil GoToState untuk mengubah statusnya, VisualStateManager lakukan hal berikut:
VisualState Jika kontrol akan memiliki Storyboard, papan cerita dimulai. Kemudian, jika VisualState kontrol berasal dari memiliki Storyboard, papan cerita berakhir.
Jika kontrol sudah dalam status yang ditentukan, GoToState tidak mengambil tindakan dan mengembalikan
true
.Jika status yang ditentukan tidak ada di ControlTemplate ,
control
GoToState tidak mengambil tindakan dan mengembalikanfalse
.
Praktik Terbaik untuk Bekerja dengan VisualStateManager
Disarankan agar Anda melakukan hal berikut untuk mempertahankan status kontrol Anda:
Gunakan properti untuk melacak statusnya.
Buat metode pembantu untuk transisi antar status.
Kontrol NumericUpDown
menggunakan propertinya Value
untuk melacak apakah properti berada dalam status Positive
atau Negative
.
NumericUpDown
Kontrol juga mendefinisikan Focused
dan status UnFocused
, yang melacak properti IsFocused. Jika Anda menggunakan status yang tidak secara alami sesuai dengan properti kontrol, Anda dapat menentukan properti privat untuk melacak status.
Satu metode yang memperbarui semua status dapat memusatkan panggilan ke VisualStateManager dan menjaga agar kode Anda tetap mudah dikelola. Contoh berikut menunjukkan metode pembantu NumericUpDown
kontrol, UpdateStates
. Ketika Value
lebih besar dari atau sama dengan 0, Control berada dalam status Positive
. Ketika Value
kurang dari 0, kontrol berada dalam status Negative
. Ketika IsFocused adalah true
, kontrol berada dalam keadaan Focused
; jika tidak, berada dalam keadaan Unfocused
. Kontrol dapat memanggil UpdateStates
setiap kali perlu mengubah statusnya, terlepas dari perubahan status apa.
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
Jika Anda meneruskan nama status ke GoToState saat kontrol sudah dalam status tersebut, GoToState tidak melakukan apa pun, sehingga Anda tidak perlu memeriksa status kontrol saat ini. Misalnya, jika Value
perubahan dari satu angka negatif ke angka negatif lainnya, papan cerita untuk Negative
status tidak terganggu dan pengguna tidak akan melihat perubahan dalam kontrol.
VisualStateManager menggunakan VisualStateGroup objek untuk menentukan status mana yang akan keluar saat Anda memanggil GoToState. Kontrol selalu berada dalam satu status untuk setiap VisualStateGroup yang didefinisikan dalam ControlTemplate dan hanya meninggalkan status ketika masuk ke status lain dari VisualStateGroup yang sama. Misalnya, ControlTemplate kontrol mendefinisikan NumericUpDown
objek danPositive
Negative
dalam satu VisualState dan VisualStateGroup objek dan Focused
Unfocused
di objek VisualState lainnya. (Anda dapat melihat Focused
danUnfocused
VisualStatedidefinisikan di bagian Contoh Lengkap dalam topik ini Ketika kontrol beralih dari Positive
status ke Negative
status, atau sebaliknya, kontrol tetap berada dalam status Focused
atau Unfocused
.
Ada tiga tempat umum di mana status kontrol mungkin berubah:
Ketika ControlTemplate diterapkan pada Control.
Saat properti berubah.
Ketika peristiwa terjadi.
Contoh berikut menunjukkan cara memperbarui status kontrol NumericUpDown
pada kasus-kasus ini.
Anda harus memperbarui status kontrol dalam OnApplyTemplate metode sehingga kontrol muncul dalam status yang benar saat ControlTemplate diterapkan. Contoh berikut memanggil UpdateStates
OnApplyTemplate untuk memastikan bahwa kontrol berada dalam status yang sesuai. Misalnya, Anda membuat NumericUpDown
kontrol, lalu mengaturnya Foreground ke hijau dan Value
ke -5. Jika Anda tidak memanggil UpdateStates
ketika ControlTemplate diterapkan ke kontrol NumericUpDown
, kontrol tidak dalam status Negative
dan nilainya hijau, bukan merah. Anda harus memanggil UpdateStates
untuk menempatkan kontrol dalam status 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
Anda sering kali perlu memperbarui status kontrol saat properti berubah. Contoh berikut menunjukkan seluruh ValueChangedCallback
metode. Karena ValueChangedCallback
dipanggil ketika Value
berubah, metode UpdateStates
dipanggil jika Value
berubah dari positif menjadi negatif atau sebaliknya. Dapat diterima untuk memanggil UpdateStates
ketika Value
berubah tetapi tetap positif atau negatif karena dalam hal ini, kontrol tidak akan mengubah keadaan.
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
Anda mungkin juga perlu memperbarui status saat peristiwa terjadi. Contoh berikut menunjukkan bahwa NumericUpDown
memanggil UpdateStates
pada Control untuk menangani kejadian 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
Kontrol VisualStateManager membantu Anda mengelola status. Dengan menggunakan VisualStateManager, Anda memastikan bahwa kontrol Anda bertransisi dengan benar antar status. Jika Anda mengikuti rekomendasi yang dijelaskan di bagian ini untuk bekerja dengan VisualStateManager, kode kontrol Anda akan tetap dapat dibaca dan dipertahankan.
Menyediakan Kontrak Pengendalian
Anda memberikan kontrak kontrol sehingga ControlTemplate penulis akan tahu apa yang harus dimasukkan ke dalam templat. Kontrak kontrol memiliki tiga elemen:
Elemen visual yang digunakan logika kontrol.
Status kontrol dan grup tempat setiap status berada.
Properti publik yang secara visual memengaruhi kontrol.
Seseorang yang menghasilkan ControlTemplate baru perlu mengetahui objek apa saja yang digunakan oleh logika kontrol FrameworkElement, jenis dari setiap objek tersebut, dan apa namanya. Penulis ControlTemplate juga perlu mengetahui nama setiap status yang mungkin dari kontrol, dan di mana VisualStateGroup status tersebut berada.
Kembali ke NumericUpDown
contoh, kontrol mengharapkan ControlTemplate untuk memiliki objek berikut FrameworkElement :
RepeatButton disebut
UpButton
.Sebuah RepeatButton yang disebut
DownButton.
Kontrol dapat berada dalam status berikut:
Dalam
ValueStates
VisualStateGroupPositive
Negative
Dalam
FocusStates
VisualStateGroupFocused
Unfocused
Untuk menentukan objek apa yang FrameworkElement diharapkan kontrol, Anda menggunakan TemplatePartAttribute, yang menentukan nama dan jenis elemen yang diharapkan. Untuk menentukan kemungkinan status kontrol, Anda menggunakan TemplateVisualStateAttribute, yang menentukan nama status dan miliknya VisualStateGroup . Letakkan TemplatePartAttribute dan TemplateVisualStateAttribute pada definisi kelas kontrol.
Properti publik apa pun yang memengaruhi tampilan kontrol Anda juga merupakan bagian dari kontrak kontrol.
Contoh berikut menentukan FrameworkElement objek dan status untuk NumericUpDown
kontrol.
[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
Contoh Lengkap
Contoh berikut adalah keseluruhan kontrol ControlTemplate untuk NumericUpDown
.
<!--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>
Contoh berikut menunjukkan logika untuk 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
Lihat juga
.NET Desktop feedback