Özelleştirilebilir Görünümü olan Denetim Oluşturma

Windows Presentation Foundation (WPF), görünümü özelleştirilebilen bir denetim oluşturma olanağı sağlar. Örneğin, CheckBox Yeni bir oluşturarak, özelliklerinin ne şekilde yapılacağını daha fazla değiştirebilirsiniz ControlTemplate . Aşağıdaki çizimde, özel bir CheckBox ve kullanan bir varsayılan ve olan bir kullanılır ControlTemplateCheckBoxControlTemplate .

A checkbox with the default control template. Varsayılan denetim şablonunu kullanan bir onay kutusu

A checkbox with a custom control template. Özel denetim şablonu kullanan bir onay kutusu

Bir denetim oluştururken parçalar ve durumlar modelini izlerseniz, denetiminizin görünümü özelleştirilebilir olur. Visual Studio için Blend gibi tasarımcı araçları parçalar ve durumlar modelini destekler, bu nedenle bu modeli izlediğinizde denetiminiz bu tür uygulamalarda özelleştirilebilir. Bu konuda parçalar ve durumlar modeli ve kendi denetiminizi oluştururken nasıl takip edilecek anlatılmaktadır. Bu konu, NumericUpDown Bu modelin felsefesini göstermek için özel denetim örneğini kullanır. NumericUpDownDenetim, kullanıcının denetim düğmelerine tıklayarak artırabileceği veya azaltırabileceği sayısal bir değer görüntüler. Aşağıdaki çizimde, NumericUpDown Bu konuda açıklanan denetim gösterilmektedir.

NumericUpDown custom control. Özel NumericUpDown denetimi

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

Önkoşullar

Bu konu başlığı altında ControlTemplate , var olan bir denetim için yeni bir oluşturma, bir denetim sözleşmesindeki öğelerin ne olduğu hakkında bilgi sahibi olduğunuz ve ControlTemplatebölümünde ele alınan kavramların anlaşılması gerektiğini varsayar.

Not

Görünümü özelleştirilmeyen bir denetim oluşturmak için, sınıfından devralan bir denetim Control veya alt sınıflarından birini içeren bir denetim oluşturmanız gerekir UserControl . Öğesinden devralan bir denetim UserControl , hızlı bir şekilde oluşturulabilecek, ancak kullanmaz ControlTemplate ve görünümünü özelleştiremezsiniz.

Parçalar ve durumlar modeli

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

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

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

  • İçinde nelerin dahil edileceğini belirtmek için bir denetim sözleşmesi sağlayın ControlTemplate .

Bir denetimde görsel yapı ve görsel davranışı tanımladığınızda ControlTemplate , uygulama yazarları, bir kod yazmak yerine yeni bir oluşturarak denetiminizin görsel yapısını ve görsel davranışını değiştirebilir ControlTemplate . Uygulama yazarlarına hangi FrameworkElement nesne ve durum tanımlanmaları gerektiğini söyleyen bir denetim sözleşmesi sağlamanız gerekir ControlTemplate . ControlTemplateDenetiminizin tamamlanmamış bir şekilde işlemesini sağlamak için içindeki bölümlerle etkileşim kurarken bazı en iyi yöntemleri izlemeniz gerekir ControlTemplate . Bu üç ilkeden birini izlerseniz, uygulama yazarları, ControlTemplate WPF ile birlikte gelen denetimler için mümkün olduğunca kolay bir şekilde kendi denetimide oluşturabilir. Aşağıdaki bölümde Bu önerilerin her biri ayrıntılı olarak açıklanmaktadır.

Bir ControlTemplate içindeki bir denetimin görsel yapısını ve görsel davranışını tanımlama

Özel denetiminizi parçalar ve durumlar modelini kullanarak oluşturduğunuzda, denetimin görsel yapısını ve görsel davranışını kendi ControlTemplate mantığı yerine içinde tanımlarsınız. Bir denetimin görsel yapısı, FrameworkElement denetimi oluşturan nesnelerin bir bileşiminin oluşur. Görsel davranış, denetimin belirli bir durumda olduğunda görünme yöntemidir. Bir ControlTemplate denetimin görsel yapısını ve görsel davranışını belirten bir oluşturma hakkında daha fazla bilgi için bkz. bir ControlTemplate.

NumericUpDownDenetim örneğinde görsel yapı iki RepeatButton Denetim ve bir içerir TextBlock . Bu denetimleri denetimin kodunda ( NumericUpDown Örneğin, oluşturucusunda) eklerseniz, bu denetimlerin konumları silme geri alınamaz. Denetimin görsel yapısını ve kendi kodunda görsel davranışını tanımlamak yerine, içinde tanımlamanız gerekir ControlTemplate . Ardından, düğmelerin konumunu özelleştirmek için bir uygulama geliştiricisi ve bu TextBlockValue , değişeceğinden, negatif olduğunda hangi davranışın gerçekleşeceğini belirtir ControlTemplate .

Aşağıdaki örnek, denetimin görsel yapısını, artış, NumericUpDownRepeatButtonValue bir RepeatButton azalma Value ve bir TextBlock görüntülenecek Value olan görünümünü gösterir.

<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 görsel davranışı NumericUpDown negatif ise, değerin kırmızı bir yazı tipinde olması olur. ForegroundTextBlock Negatif olduğunda, içindeki kodunu değiştirirseniz, ValueNumericUpDown her zaman kırmızı negatif bir değer gösterir. Üzerinde nesneleri ekleyerek denetimin görsel davranışını belirlersiniz ControlTemplateVisualStateControlTemplate . Aşağıdaki örnek, VisualState ve durumları için nesneleri gösterir PositiveNegative . Positive ve Negative birbirini dışlamalı (denetim her zaman iki ikisi de olur), bu yüzden örnek VisualState nesneleri tek bir içine koyar VisualStateGroup . Denetim duruma geçtiğinde,, NegativeForegroundTextBlock kırmızıya döner. Denetim Positive durumundayken, Foreground özgün değerine döner. VisualStateİçindeki nesneleri tanımlamak ControlTemplate , bir VisualStatebölümünde daha ayrıntılı bir şekilde ele alınmıştır.

Not

VisualStateManager.VisualStateGroupsÖğesinin kökünde iliştirilmiş özelliğini ayarladığınızdan emin olun 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>

Kodda ControlTemplate 'in parçalarını kullanma

ControlTemplateYazar veya nesneleri ya da yanlışlıkla ya da hata olarak atlayabilir FrameworkElementVisualState , ancak denetiminizin mantığı bu bölümlerin düzgün şekilde çalışmasını gerektirebilir. Parçalar ve durumlar modeli, denetiminizin ControlTemplate eksik veya nesneleri olan bir için esnek olması gerektiğini belirtir FrameworkElementVisualState . Denetiminiz bir özel durum oluşturmamalıdır veya ' de bir FrameworkElement , VisualState veya eksikse hata bildirmemelidir VisualStateGroupControlTemplate . Bu bölümde, nesnelerle etkileşime geçmek ve durumları yönetmek için önerilen uygulamalar açıklanmaktadır FrameworkElement .

Eksik FrameworkElement nesnelerini tahmin edin

FrameworkElementİçinde nesneleri tanımladığınızda ControlTemplate , denetiminizin mantığı bunlardan Bazılarınızla etkileşim kurması gerekebilir. Örneğin, denetim, NumericUpDown ' a Click ait özelliğini artırmak veya azaltmak ve ' ın ' i ' a uygulamak için düğmelerine abone olur ValueTextTextBlockValue . Özel bir ControlTemplateTextBlock veya düğmelerini atladığında, denetimin bazı işlevlerini kaybetmesi kabul edilebilir, ancak denetiminizin bir hataya neden olmadığından emin olmalısınız. Örneğin, bir, ControlTemplate Değiştirilecek düğmeleri içermiyorsa, ValueNumericUpDown Bu işlevselliği kaybeder, ancak öğesini kullanan bir uygulama çalışmaya ControlTemplate devam eder.

Aşağıdaki uygulamalar, denetiminizin eksik nesneler için düzgün şekilde yanıt vermesini sağlar FrameworkElement :

  1. x:NameKodda başvuru yapmanız gereken her biri için özniteliğini ayarlayın FrameworkElement .

  2. Etkileşimde bulunmak için ihtiyacınız olan her biri için özel özellikler tanımlayın FrameworkElement .

  3. Denetimin, FrameworkElement özelliğin set erişimcisinde işletiğine yönelik olaylara abone olma ve aboneliği kaldırma.

  4. FrameworkElementYönteminde adım 2 ' de tanımladığınız özellikleri ayarlayın OnApplyTemplate . Bu, FrameworkElement içindeki içindeki denetimin kullanılabildiği en erken zamandır ControlTemplate . ' x:NameFrameworkElement Den almak için öğesini kullanın ControlTemplate .

  5. FrameworkElementnull Üyelerinin üyelerine erişmeden önce olmadığını kontrol edin. Varsa null , bir hata bildirmeyin.

Aşağıdaki örneklerde, NumericUpDown denetimin, FrameworkElement önceki listedeki önerilere uygun olarak nesnelerle nasıl etkileşim kurduğu gösterilmektedir.

İçindeki denetimin görsel yapısını tanımlayan örnekte, NumericUpDownControlTemplateRepeatButton Bu artın Valuex:Name özniteliği olarak ayarlanır UpButton . Aşağıdaki örnek UpButtonElement , içinde bildirildiği öğesini temsil eden adlı bir özellik bildirir RepeatButtonControlTemplate . setErişimci ilk olarak düğmenin olayına abone olur, yoksa, ClickUpDownElementnull özelliği ayarlar ve sonra olaya abone olur Click . Ayrıca, adında tanımlanmış bir özellik de vardır, ancak burada gösterilmez RepeatButtonDownButtonElement .

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 gösterir NumericUpDown . Örnek, GetTemplateChild öğesinden nesneleri almak için yöntemini kullanır FrameworkElementControlTemplate . Örnek, GetTemplateChildFrameworkElement beklenen türde olmayan, belirtilen ada sahip bir olarak bulduğu servis taleplerine karşı koruduğuna dikkat edin. Ayrıca, belirtilen ancak yanlış türde olan öğeleri yoksaymak için de en iyi uygulamadır 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

Önceki örneklerde gösterilen uygulamalardan sonra, denetimin eksik olduğu durumlarda denetiminizin çalışmaya devam edecek şekilde emin olursunuz ControlTemplateFrameworkElement .

VisualStateManager 'ı kullanarak durumları yönetme

, VisualStateManager Bir denetimin durumlarını izler ve durumlar arasında geçiş yapmak için gereken mantığı gerçekleştirir. İçine nesneler eklediğinizde VisualStateControlTemplate , bunları bir öğesine ekler VisualStateGroup ve VisualStateGroupVisualStateManager.VisualStateGroups bunlara erişimi olması için ekli özelliğe eklersiniz VisualStateManager .

Aşağıdaki örnek, VisualStatePositive denetimin ve durumlarına karşılık gelen nesneleri gösteren önceki örneği yineler Negative . Storyboardİçinde, kırmızı ' i NegativeVisualState döndürür ForegroundTextBlock . NumericUpDownDenetim Negative durumundayken, durum içindeki film şeridi Negative başlar. Sonra, StoryboardNegative Denetim duruma döndüğünde durum durumu duraklar Positive . İçin bir,,, PositiveVisualState için, StoryboardStoryboardNegativeForeground özgün rengine geri döndüğünde bir içermesi gerekmez.

<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' A bir ad verildiğini, ancak TextBlock denetimin mantığı hiçbir şekilde başvurmadığı için denetim sözleşmesinde olmadığından, NumericUpDown öğesine göz atalım TextBlock . İçinde başvurulan öğelerin ControlTemplate adları vardır ancak denetim sözleşmesinin bir parçası olması gerekmez, çünkü denetimin yeni bir ControlTemplate öğesi bu öğeye başvurmak zorunda kalabilir. Örneğin, yeni bir için yeni oluşturan birisi ControlTemplateNumericUpDown , değiştirilerek negatif olduğunu Belirtmemeye karar verebilir ValueForeground . Bu durumda, ne kod ne de ne de ControlTemplate adıyla başvuru yapılır TextBlock .

Denetimin mantığı, denetimin durumunu değiştirmekten sorumludur. Aşağıdaki örnek, NumericUpDown denetimin, GoToStatePositiveValue 0 veya daha büyük olduğunda ve Negative durumu 0 ' dan küçük olduğunda, durum ' a geçmek için yöntemini çağırıyorsa gösterir 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

GoToStateYöntemi, görsel taslakları uygun şekilde başlatmak ve durdurmak için gereken mantığı gerçekleştirir. Bir denetim GoToState durumunu değiştirmek için çağırdığında, VisualStateManager şunları yapar:

VisualStateManager ile çalışmaya yönelik en iyi uygulamalar

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.

NumericUpDownDenetim Value , veya durumunda olup olmadığını izlemek için özelliğini kullanır PositiveNegative . NumericUpDownDenetim Ayrıca, FocusedUnFocused özelliğini izleyen ve durumlarını tanımlar IsFocused . Doğal olarak denetimin bir özelliğine karşılık gelen durumlar kullanırsanız, durumu izlemek için özel bir özellik tanımlayabilirsiniz.

Tüm durumları güncelleştiren tek bir yöntem, öğesine yapılan çağrıları merkezileştirir VisualStateManager ve kodunuzu yönetilebilir halde tutar. Aşağıdaki örnekte NumericUpDown , denetimin yardımcı yöntemi gösterilmektedir UpdateStates . Value0 ' dan büyük veya buna eşit olduğunda, ControlPositive durumunda olur. Value0 ' dan küçükse denetim Negative durumundadır. Ne zaman olduğunda IsFocusedtrue , Denetim Focused durumundadır; Aksi takdirde, Unfocused durumundadır. Durum UpdateStates ne olursa olsun, denetimin durumunu değiştirmesi gerektiğinde bu denetim çağralabilir.

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

GoToStateDenetimin zaten o durumda olduğu durumlarda bir durum adı geçirirseniz, GoToState hiçbir şey yapmaz, böylece denetimin geçerli durumunu denetlemeniz gerekmez. Örneğin, Value bir negatif sayıdan başka bir negatif sayıya değişiklik olursa, durum için görsel taslak Negative kesintiye uğramaz ve Kullanıcı denetimde bir değişiklik görmez.

, VisualStateManagerVisualStateGroup Çağırdığınızda hangi durumun çıkış olacağını belirleyen nesneleri kullanır GoToState . Denetim her zaman için her zaman bir durum durumundadır VisualStateGroupControlTemplate ve aynı durum, yalnızca aynı diğer bir duruma geçtiğinde bir durumu bırakır VisualStateGroup . Örneğin, ControlTemplateNumericUpDown denetimin Positive ve NegativeVisualState nesnelerini VisualStateGroupFocusedUnfocusedVisualState bir ve içindeki nesneleri bir ve üzerinde tanımlar. ( FocusedUnfocusedVisualState Denetim durumdan duruma geçtiğinde ve bunun tersi durumda Focused , PositiveNegative Denetim Focused veya durumunda kalır, bu konunun tam örnek bölümünde tanımlanan ve tanımlanan ' u görebilirsiniz Unfocused .

Bir denetimin durumunun değiştirebildiği üç tipik konum vardır:

  • ControlTemplateÖğesine uygulandığında Control .

  • Bir özellik değiştiğinde.

  • Bir olay gerçekleştiğinde.

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

Yönteminde denetimin durumunu güncelleştirmeniz gerekir, OnApplyTemplate böylece denetim uygulandığında denetimin doğru durumda görünmesini sağlayabilirsiniz ControlTemplate . Aşağıdaki örnek, UpdateStatesOnApplyTemplate denetiminin uygun durumlarda olduğundan emin olmak için ' de çağırır. Örneğin, bir NumericUpDown Denetim oluşturduğunuzu ve sonra da Foreground yeşil ve-5 ' i ayarlayadığınızı varsayalım Value . UpdateStatesControlTemplate Denetime uygulandığında, NumericUpDown Denetim Negative durumunda değildir ve değeri kırmızı yerine yeşil olur... UpdateStatesDenetimi duruma koymak için öğesini çağırmanız gerekir 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

Genellikle bir özellik değiştiğinde bir denetimin durumlarını güncelleştirmeniz gerekir. Aşağıdaki örnek, tüm yöntemi gösterir ValueChangedCallback . ValueChangedCallbackDeğişiklik sırasında çağrıldığı Value için, yöntem çağrısı UpdateStatesValue pozitif iken negatif veya tam tersi olarak değişir. Değişiklikler sırasında çağrı kabul edilebilir UpdateStatesValue ancak pozitif veya negatif kalır, bu durumda denetim durumları değiştirmez.

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

Ayrıca bir olay gerçekleştiğinde durumları güncelleştirmeniz gerekebilir. Aşağıdaki örnek NumericUpDownUpdateStates , Control olayını işlemek için üzerinde yapılan çağrıların gösterir 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 Denetiminizin durumlarını yönetmenize yardımcı olur. Kullanarak VisualStateManager , denetiminizin durumlar arasında doğru şekilde geçiş yaparak emin olursunuz. İle birlikte çalışmak için bu bölümde açıklanan önerileri izlerseniz VisualStateManager , denetiminizin kodu okunabilir ve sürdürülebilir kalır.

Denetim sözleşmesini sağlama

ControlTemplateYazarların şablona ne yerleştirileceğini bilmesi için bir denetim sözleşmesi sağlarsınız. Bir 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 ortak özellikler.

ControlTemplateFrameworkElement Denetim mantığının hangi nesneleri kullandığını, her nesnenin ne tür olduğunu ve adının ne olduğunu bilmemiz için yeni bir ihtiyacı olan birisi. Bir ControlTemplate yazarın Ayrıca, denetimin içinde yer aldığı ve durumun bulunduğu her olası durum adını bilmeleri gerekir VisualStateGroup .

NumericUpDownÖrneğe dönerek denetim, ' ın ControlTemplate Aşağıdaki nesnelere sahip olmasını bekler FrameworkElement :

Denetim aşağıdaki durumlarda olabilir:

FrameworkElementDenetimin beklediği nesneleri belirtmek için, TemplatePartAttribute beklenen öğelerin adını ve türünü belirten öğesini kullanırsınız. Bir denetimin olası durumlarını belirtmek için, ' ı kullanırsınız, bu, TemplateVisualStateAttribute durumunun adını ve ait olduğunu belirtir VisualStateGroup . Öğesini TemplatePartAttribute ve TemplateVisualStateAttribute denetimin sınıf tanımına koyun.

Denetiminizin görünümünü etkileyen tüm ortak özellikler ayrıca denetim sözleşmesinin bir parçasıdır.

Aşağıdaki örnek, FrameworkElement denetimin nesne ve durumlarını belirtir 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

Tam Örnek

Aşağıdaki örnek, denetimin tamamından sorumludur ControlTemplateNumericUpDown .

<!--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 örnek, için mantığını gösterir 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 bkz.