Özelleştirilebilir Bir Görünüme Sahip Denetim Oluşturma

Windows Presentation Foundation (WPF), görünümü özelleştirilebilen bir denetim oluşturmanızı sağlar. Örneğin, yeni bir CheckBox oluşturarak bir ControlTemplate'nin görünümünü ayar özelliklerinin yapabileceğinden daha fazla değiştirebilirsiniz. Aşağıdaki çizimde, varsayılan bir CheckBox kullanan bir ControlTemplate ve özel bir CheckBox kullanan bir ControlTemplate gösterilmektedir.

Varsayılan denetim şablonunu içeren bir onay kutusu. Varsayılan denetim şablonunu kullanan bir Onay Kutusu

Özel denetim şablonu içeren bir onay kutusu. Özel denetim şablonu kullanan bir CheckBox

Denetim oluştururken parça ve durum modelini izlerseniz, denetiminizin görünümü özelleştirilebilir. Visual Studio için Blend gibi tasarımcı araçları parça ve durum modelini desteklediğinden, bu modeli takip ettiğinizde denetiminiz bu tür uygulamalarda özelleştirilebilir. Bu konu başlığında parçalar ve durumlar modeli ve kendi denetiminizi oluştururken nasıl izleyebileceğiniz açıklanır. Bu konuda, NumericUpDownbu modelin felsefesini göstermek için bir özel denetim örneği kullanılır. Denetim NumericUpDown , bir kullanıcının denetimin düğmelerine tıklayarak artırabileceği veya azaltabileceği sayısal bir değer görüntüler. Aşağıdaki çizim, bu konuda ele alınan NumericUpDown denetimini göstermektedir.

NumericUpDown özel denetimi. Özel bir NumericUpDown denetimi

Bu konu aşağıdaki bölümleri içerir:

Önkoşullar

Bu konu başlığında, var olan bir denetim için nasıl yeni ControlTemplate bir denetim oluşturulacağını bildiğiniz, denetim sözleşmesindeki öğelerin ne olduğu hakkında bilgi sahibi olduğunuz ve Denetim için şablon oluşturma başlığında açıklanan kavramları anladığınız varsayılır.

Uyarı

Görünümünü özelleştirebileceğiniz bir denetim oluşturmak için, Control veya onun alt sınıflarında biri hariç, UserControl sınıfının ya da alt sınıflarından birini devralan bir denetim oluşturmanız gerekir. Bir UserControl öğesinden devralınan kontrol, hızlı bir şekilde oluşturulabilir, ancak bir ControlTemplate kullanmaz ve görünümünü özelleştiremezsiniz.

Parça ve Durum Modeli

Parçalar ve durumlar modeli, bir denetimin görsel yapısını ve görsel davranışını tanımlamayı belirtir. Parça ve durum modelini izlemek için aşağıdakileri yapmanız gerekir:

  • Bir denetimin ControlTemplate içindeki görsel yapısını ve davranışını tanımlayın.

  • Denetiminizin mantığı denetim şablonunun bölümleriyle etkileşime geçtiğinde bazı en iyi yöntemleri izleyin.

  • ControlTemplate öğesine nelerin dahil edilmesi gerektiğini belirtmek için bir kontrol sözleşmesi sağlayın.

Bir denetimin görsel yapısını ve görsel davranışını ControlTemplate tanımladığınızda, uygulama yazarları kod yazmak yerine yeni ControlTemplate bir oluşturma yoluyla denetiminizin görsel yapısını ve görsel davranışını değiştirebilir. Uygulama yazarlarına hangi FrameworkElement nesnelerin ve durumların ControlTemplate içinde tanımlanması gerektiğini bildiren bir denetim sözleşmesi sağlamanız gerekir. ControlTemplate içindeki parçalarla etkileşim kurarken, denetiminizin tamamlanmamış bir ControlTemplate öğesini düzgün şekilde işlemesi için en iyi uygulamalardan bazılarını takip etmelisiniz. Bu üç ilkeyi izlerseniz, uygulama yazarları WPF ile birlikte gelen kontroller için olduğu gibi, kontrolünüz için de bir ControlTemplate kolaylıkla oluşturabilir. Aşağıdaki bölümde bu önerilerin her biri ayrıntılı olarak açıklanmaktadır.

ControlTemplate'da Denetimin Görsel Yapısını ve Görsel Davranışını Tanımlama

Parçalar ve durumlar modelini kullanarak özel denetiminizi oluşturduğunuzda, denetimin görsel yapısını ve görsel davranışını ControlTemplate mantığı yerine içinde tanımlarsınız. Denetimin görsel yapısı, denetimi oluşturan nesnelerin bileşimidir FrameworkElement . Görsel davranış, denetimin belirli bir durumdayken görünme şeklidir. Bir denetimin görsel yapısını ve görsel davranışını belirten bir oluşturma ControlTemplate hakkında daha fazla bilgi için bkz. Denetim için şablon oluşturma.

Denetim örneğinde NumericUpDown görsel yapısı iki RepeatButton denetim ve bir TextBlockiçerir. Bu denetimleri denetimin NumericUpDown koduna eklerseniz (örneğin, oluşturucusunda) bu denetimlerin konumları değiştirilemez. Kontrolün ekran yapısını ve kodun içindeki görsel davranışını tanımlamak yerine, bunları ControlTemplate içinde tanımlamalısınız. Ardından bir uygulama geliştiricisi, düğmelerin konumunu özelleştirir ve TextBlock değiştirilebileceği için Value negatif olduğunda hangi davranışın ortaya çıktığını belirtir.

Aşağıdaki örnek, kontrolün NumericUpDown görsel yapısını göstermektedir. Bu yapı, RepeatButton artırmak için bir Value, RepeatButton azaltmak için bir Value ve TextBlock görüntülemek için bir Value içerir.

<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>

Denetimin NumericUpDown görsel davranışı, değerin negatifse kırmızı yazı tipinde olmasıdır. Kodda Foreground negatif olduğunda, TextBlock değerini Value ile değiştirirseniz, NumericUpDown her zaman kırmızı negatif bir değeri gösterir. ControlTemplate'ye VisualState nesneleri ekleyerek, ControlTemplate denetiminin görsel davranışını belirtirsiniz. Aşağıdaki örnek, VisualState nesnelerinin Positive ve Negative durumları için gösterildiğini belirtilmektedir. Positive ve Negative birbirini dışlayıcıdır (kontrol her zaman yalnızca ikisinden birindedir), bu nedenle örnek, VisualState nesneleri tek bir VisualStateGroup içine yerleştirir. Denetim Negative durumuna geçtiğinde, Foreground'nin TextBlock rengi kırmızıya dönüşür. Denetim Positive durumundayken, Foreground özgün değerine döner. içindeki nesneleri tanımlama VisualState konusu, ControlTemplate bölümünde daha ayrıntılı olarak ele alınmıştı.

Uyarı

kök VisualStateManager.VisualStateGroups dizininde FrameworkElement ekli özelliği ayarladığınızdan ControlTemplateemin olun.

<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>

Kodda ControlTemplate Bölümlerini Kullanma

Bir ControlTemplate yazar nesneleri bilerek veya yanlışlıkla FrameworkElement veya VisualState atlayabilir, ancak denetiminiz bu bölümlerin düzgün çalışmasına ihtiyaç duyabilir. Parça ve durum modeli, denetiminizin eksik ControlTemplate veya FrameworkElementVisualState nesneler olduğunda dayanıklı olması gerektiğini belirtir. Denetiminiz FrameworkElement öğesinde VisualState, VisualStateGroup veya ControlTemplate eksikse bir özel durum oluşturmamalı veya hata bildirmemelidir. Bu bölümde, nesnelerle FrameworkElement etkileşim kurmak ve durumları yönetmek için önerilen yöntemler açıklanmaktadır.

Eksik FrameworkElement Nesnelerini Öngör

FrameworkElement nesnelerini ControlTemplate içinde tanımladığınızda, denetiminizin mantığının bazılarıyla etkileşim kurması gerekebilir. Örneğin, NumericUpDown denetim düğmelerin Click olayına abone olarak değerini artırır veya azaltır Value ve özelliğini Text olarak TextBlockayarlarValue. Özel ControlTemplate öğesi TextBlock veya düğmeleri atladığında, denetimin bazı işlevlerini kaybetmesi kabul edilebilir bir durumdur, ancak denetiminizin hataya neden olmadığından emin olmanız gerekir. Örneğin, bir ControlTemplate değişiklik yapmak için Value düğmelerini içermiyorsa, NumericUpDown bu işlevini kaybeder, ancak ControlTemplate kullanan bir uygulama çalışmaya devam eder.

Aşağıdaki uygulamalar, denetiminizin eksik FrameworkElement nesnelere düzgün yanıt vermesini sağlar:

  1. Her bir x:Name öğesini kodda referans vermeniz gerektiğinde FrameworkElement özniteliğini ayarlayın.

  2. Etkileşim kurmanız gereken her FrameworkElement biri için özel özellikler tanımlayın.

  3. Kontrolünüzün FrameworkElement özelliğinin küme erişimcisindeki işlediği olaylara abone olun ve abonelikten çıkın.

  4. 2. adımda tanımladığınız FrameworkElement özellikleri OnApplyTemplate yönteminde ayarlayın. Bu, FrameworkElement'ın ControlTemplate içindeki denetime en erken sunulma zamanıdır. x:Name öğesini FrameworkElement'den ControlTemplate almak için kullanın.

  5. Şunu kontrol edin ki FrameworkElement üyelerine erişmeden önce null değildir. Eğer null ise, hata bildirmeyin.

Aşağıdaki örnekler, önceki listedeki önerilere uygun olarak NumericUpDown denetiminin FrameworkElement nesnelerle etkileşimde nasıl bulunduğunu göstermektedir.

NumericUpDown içindeki ControlTemplate kontrolünün görsel yapısını tanımlayan örnekte, RepeatButton değerini artıran Value, x:Name özniteliği UpButton olarak ayarlanmıştır. Aşağıdaki örnek, UpButtonElement içinde bildirilen RepeatButton öğesini temsil eden, ControlTemplate adlı bir özellik bildirir. Önce setClick değilse erişimci, düğmenin UpDownElement olayının aboneliğini iptal eder, ardından özelliği ayarlar ve sonra null olayına abone olur. Diğer RepeatButton için tanımlanmış, ancak burada gösterilmeyen DownButtonElement adlı bir özellik de vardır.

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

Aşağıdaki örnek, OnApplyTemplate denetimi için NumericUpDown'yı gösterir. Örnek, GetTemplateChild içindeki FrameworkElement nesneleri almak için ControlTemplate yöntemini kullanır. Örnek, GetTemplateChild'ın beklenen türde olmayan belirtilen ada sahip bir FrameworkElement bulduğu durumlara karşı önlem alır. Belirtilen x:Name'e sahip ancak yanlış türde olan öğeleri yoksaymak da iyi bir uygulamadır.

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

Önceki örneklerde gösterilen uygulamaları takip ederek, ControlTemplate eksik bir FrameworkElement olduğunda denetiminizin çalışmaya devam edeceğinden emin olursunuz.

Durumları Yönetmek için VisualStateManager Kullanma

denetimin VisualStateManager durumlarını izler ve durumlar arasında geçiş yapmak için gereken mantığı gerçekleştirir. VisualState nesneleri ControlTemplate öğesine eklediğinizde, bunları VisualStateGroup öğesine ekler ve VisualStateGroup öğesini VisualStateManager.VisualStateGroups ekli özelliğine eklersiniz, böylece VisualStateManager bunlara erişebilir.

Aşağıdaki örnek, kontrolün VisualState ve Positive durumlarına karşılık gelen Negative nesnelerini gösteren önceki örneği yineler. Storyboard içindeki NegativeVisualState, Foreground'ün TextBlock'ünü kırmızıya çevirir. NumericUpDown denetim durumundayken, Negative durumunda olan görsel taslak Negative başlar. Storyboard Negative durumundayken, kontrol Positive durumuna döndüğünde durur. Positive VisualState öğesinin Storyboard içermesi gerekmez çünkü Storyboard için Negative durduğunda, Foreground özgün rengine döner.

<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 öğesine bir ad verildiğine dikkat edin, ancak TextBlock kontrolü için NumericUpDown kontrol sözleşmesinde TextBlock yoktur çünkü kontrolün mantığı NumericUpDown öğesine hiçbir zaman başvurmaz. ControlTemplate içinde atıfta bulunulan öğelerin adları vardır, ancak yeni bir ControlTemplate için denetim sözleşmesinin parçası olmak zorunda değillerdir çünkü bu öğeye atıfta bulunulmasına gerek kalmayabilir. Örneğin, ControlTemplate için yeni bir NumericUpDown oluşturan kişi, Value değiştirerek Foreground'nin negatif olduğunu göstermemeye karar verebilir. Bu durumda, ne kod ne de ControlTemplateTextBlock adına atıfta bulunur.

Denetimin mantığı, denetimin durumunu değiştirmekle sorumludur. Aşağıdaki örnek, NumericUpDown denetiminin, GoToState 0 veya daha büyük olduğunda Positive yöntemini çağırarak Value durumuna geçtiğini ve GoToState 0'dan küçük olduğunda Value durumuna geçtiğini gösterir.

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 yöntemi, görsel taslakları uygun şekilde başlatmak ve durdurmak için gereken işlemi gerçekleştirir. Bir denetim durumunu değiştirmek için GoToState’yi çağırdığında, VisualStateManager aşağıdakileri yapar:

  • Denetimin VisualState bir Storyboard'i olacaksa, görsel taslak başlar. Ardından, denetimin VisualState geldiği öğede bir Storyboardvarsa görsel taslak sona erer.

  • Denetim zaten belirtilen durumdaysa hiçbir GoToState işlem gerçekleştirmez ve döndürür true.

  • Belirtilen durum ControlTemplate içindeki control bulunmadığında, GoToState hiçbir eylem gerçekleştirmez ve false döndürür.

VisualStateManager ile Çalışmak için En İyi Yöntemler

Denetiminizin durumlarını korumak için aşağıdakileri yapmanız önerilir:

  • Durumunu izlemek için özellikleri kullanın.

  • Durumlar arasında geçiş yapmak için bir yardımcı yöntem oluşturun.

NumericUpDown denetimi, Value özelliğini kullanarak Positive veya Negative durumunda olup olmadığını izler. Denetim ayrıca NumericUpDown ve Focused durumlarını, UnFocused özelliğini izleyen IsFocused tanımlar. Denetimin bir özelliğine doğal olarak karşılık olmayan durumlar kullanırsanız, durumu izlemek için özel bir özellik tanımlayabilirsiniz.

Tüm durumları güncelleştiren tek bir yöntem, çağrıları VisualStateManager merkezileştirir ve kodunuzu yönetilebilir durumda tutar. Aşağıdaki örnek, denetimin NumericUpDown yardımcı yöntemi olan UpdateStatesöğesini gösterir. 0'dan büyük veya 0'a Value eşit olduğundaControl, Positive durumundadır. Value 0'dan küçük olduğunda, denetim Negative durumundadır. IsFocused true olduğunda, denetim Focused durumundadır; aksi takdirde, Unfocused durumundadır. Denetim, hangi durumun değiştiğinden bağımsız olarak durumunu değiştirmesi gerektiğinde çağırabilir 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

Denetim zaten bu durumdayken bir durum adı GoToState geçirirseniz hiçbir GoToState şey yapmaz, bu nedenle denetimin geçerli durumunu denetlemeniz gerekmez. Örneğin, Value, bir negatif sayıdan başka bir negatif sayıya değişirse, Negative durumu için taslak kesintiye uğramaz ve kullanıcı denetimde bir değişiklik görmez.

VisualStateManager, VisualStateGroup nesnelerini, GoToState çağırdığınızda hangi durumdan çıkılacağını belirlemek için kullanır. Kontrol, içinde VisualStateGroup tanımlanan her ControlTemplate için her zaman bir durumdadır ve yalnızca aynı VisualStateGroup içinde başka bir duruma geçtiğinde bir durumdan ayrılır. Örneğin, ControlTemplate denetiminin NumericUpDown öğesi, bir Positive içinde Negative ile VisualStateVisualStateGroup nesnelerini ve diğerinde Focused ile UnfocusedVisualState nesnelerini tanımlar. (tr-TR: Focused ve UnfocusedVisualState değerlerini bu konudaki Tam Örnek bölümünde görebilirsiniz. Denetim, Positive durumundan Negative durumuna geçerken veya tam tersi durumda, denetim ya Focused ya da Unfocused durumunda kalır.)

Bir denetimin durumunun değişebileceği üç tipik yer vardır:

Aşağıdaki örneklerde, bu durumlarda denetimin durumunun NumericUpDown güncelleştirilmesi gösterilmektedir.

OnApplyTemplate yönteminde denetimi güncelleyerek ControlTemplate uygulandığında denetimin doğru durumda görünmesini sağlamalısınız. Aşağıdaki örnek, denetimin uygun durumda olduğunu sağlamak için UpdateStates öğesini OnApplyTemplate içinde çağırır. Örneğin, bir NumericUpDown denetim oluşturduğunuzu ve ardından bunu Foreground yeşil ve Value -5 olarak ayarladığınızı varsayalım. Eğer UpdateStatesControlTemplate kontrolüne uygulandığında NumericUpDown çağrısı yapmazsanız, kontrol Negative durumunda değildir ve değer kırmızı yerine yeşil olur. Denetimi UpdateStates duruma getirmek için aramanız Negative gerekir.

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

Genellikle bir özellik değiştiğinde denetimin durumunu güncelleştirmeniz gerekir. Aşağıdaki örnek, ValueChangedCallback yönteminin tamamını göstermektedir. ValueChangedCallback Value değiştiğinde çağrıldığından, yöntem UpdateStates pozitiften negatife veya tersine geçtiğinde Value çağırır. UpdateStates değiştiğinde ancak pozitif veya negatif kalmaya devam ettiğinde, Value çağrısı yapmak kabul edilebilir çünkü bu durumda kontrol durumu değişmez.

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

Bir olay gerçekleştiğinde durumları da güncelleştirmeniz gerekebilir. Aşağıdaki örnek, NumericUpDown'nün UpdateStates üzerindeki Control'i GotFocus olayını ele almak için çağırdığını göstermektedir.

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, denetiminizin durumlarını yönetmenize yardımcı olur. kullanarak VisualStateManager, denetiminizin durumlar arasında doğru şekilde geçişini sağlarsınız. ile VisualStateManagerçalışmak için bu bölümde açıklanan önerileri izlerseniz, denetiminizin kodu okunabilir ve sürdürülebilir kalır.

Kontrol Sözleşmesini Sağlama

Yazarların ControlTemplate şablona ne ekleyeceklerini bilmesi için bir denetim sözleşmesi sağlarsınız. Denetim sözleşmesinin üç öğesi vardır:

  • Denetimin mantığının kullandığı görsel öğeler.

  • Denetimin durumları ve her durumun ait olduğu grup.

  • Denetimi görsel olarak etkileyen kamu özellikleri.

Yeni ControlTemplate bir öğe oluşturan birinin denetimin mantığının hangi FrameworkElement nesneleri kullandığını, her nesnenin ne tür olduğunu ve adının ne olduğunu bilmesi gerekir. Bir ControlTemplate yazarın denetimin içinde bulunabileceği her olası durumun adını ve VisualStateGroup durumunu da bilmesi gerekir.

NumericUpDown örneğine dönersek, denetim öğesinin ControlTemplate'nin aşağıdaki FrameworkElement nesnelere sahip olmasını beklediğini göreceğiz.

Denetim aşağıdaki durumlarda olabilir:

Denetimin beklediği FrameworkElement nesneleri belirtmek için, beklenen öğelerin adını ve türünü belirten öğesini kullanırsınız TemplatePartAttribute. Bir denetimin olası durumlarını belirtmek için, durumun adını belirten TemplateVisualStateAttribute öğesini ve ait olduğu VisualStateGroup öğesini kullanırsınız. TemplatePartAttribute ve TemplateVisualStateAttribute öğelerini kontrolün sınıf tanımına yerleştirin.

Denetiminizin görünümünü etkileyen tüm kamu malları da denetim sözleşmesinin bir parçasıdır.

Aşağıdaki örnek, FrameworkElement nesnesini ve NumericUpDown denetimi için durumları belirtir.

[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

Tam Örnek

Aşağıdaki örnek, ControlTemplate kontrolü için tüm NumericUpDown'yu göstermektedir.

<!--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>

Aşağıdaki örnekte için mantığı gösterilmektedir 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

Ayrıca bakınız