Aracılığıyla paylaş


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

Windows Presentation Foundation (WPF), görünümü özelleştirilebilen bir denetim oluşturmanızı sağlar. Örneğin, yeni ControlTemplatebir CheckBox oluşturarak bir ayarının görünümünü özelliklerin ne yapacağının ötesinde değiştirebilirsiniz. Aşağıdaki çizimdeCheckBox, varsayılan ControlTemplate ve özel ControlTemplatekullanan bir CheckBox gösterilmektedir.

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 CheckBox

Denetim oluştururken parça ve durum modelini izlerseniz, denetiminizin görünümü özelleştirilebilir. Visual Studio için Blend gibi Tasarım Aracı araçlar parça ve durum modelini destekler, bu nedenle 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 çizimde, bu konuda ele alınan denetim gösterilmektedir NumericUpDown .

NumericUpDown custom control. Özel bir NumericUpDown denetimi

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

Ön koş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.

Dekont

Görünümünü özelleştirebilecek bir denetim oluşturmak için sınıfından veya dışındaki UserControlalt sınıflarından Control birini devralan bir denetim oluşturmanız gerekir. öğesinden UserControl devralınan denetim, hızlı bir şekilde oluşturulabilen, ancak kullanmadığı ve görünümünü özelleştiremeyeceğiniz bir ControlTemplate denetimdir.

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 görsel yapısını ve görsel davranışını ControlTemplate tanımlayın.

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

  • öğesine nelerin dahil ControlTemplateedilmesi gerektiğini belirtmek için bir denetim 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ının içinde hangi FrameworkElement nesnelerin ve durumların tanımlanması ControlTemplategerektiğini bildiren bir denetim sözleşmesi sağlamanız gerekir. Denetiminizin tamamlanmamış ControlTemplatebir öğeyi düzgün bir şekilde işlemesi ControlTemplate için içindeki parçalarla etkileşim kurarken en iyi yöntemlerden bazılarını izlemeniz gerekir. Bu üç ilkeyi izlerseniz, uygulama yazarları WPF ile birlikte gelen denetimler için olabildiğince kolay bir şekilde denetiminiz için bir ControlTemplate 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. Denetimin görsel yapısını ve kodundaki görsel davranışını tanımlamak yerine içinde tanımlamanız ControlTemplategerekir. Ardından bir uygulama geliştiricisi düğmelerin konumunu özelleştirmek ve TextBlock değiştirilebileceği için negatif olduğunda Value hangi davranışın ControlTemplate ortaya çıktığını belirtir.

Aşağıdaki örnekte, denetimin NumericUpDown görsel yapısı gösterilmektedir. Bu, öğesini artırmak Valueiçin , RepeatButton azaltmak Valueiçin ve görüntülemek Valueiçin öğesini TextBlock içerirRepeatButton.

<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. negatif olduğunda Value kodun TextBlock değerini değiştirirsenizForeground, NumericUpDown her zaman kırmızı negatif bir değer gösterilir. öğesine nesneler ekleyerek VisualState içindeki ControlTemplate denetimin ControlTemplategörsel davranışını belirtirsiniz. Aşağıdaki örnekte ve durumlarının VisualState nesneleri gösterilmektedirPositive.Negative Positiveve Negative birbirini dışlar (denetim her zaman tam olarak ikiden birindedir), bu nedenle örnek nesneleri tek VisualStateGroupbir içine yerleştirirVisualState. Denetim duruma geçtiğinde Negative , Foreground denetimin TextBlock rengi kırmızıya dönüşür. Denetim durumundayken Positive , Foreground özgün değerine döner. içindeki nesneleri tanımlama VisualState konusu, Denetim için şablon oluşturma bölümünde daha ayrıntılı olarak ele alınmıştı.ControlTemplate

Dekont

kök FrameworkElement dizininde VisualStateManager.VisualStateGroups 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 bilerek veya yanlışlıkla nesneleri atlayabilir FrameworkElement veya VisualState atlayabilir, ancak denetiminizin mantığının bu bölümlerin düzgün çalışması gerekebilir. Parça ve durum modeli, denetiminizin eksik FrameworkElement olan veya ControlTemplateVisualState nesnelere dayanıklı olması gerektiğini belirtir. denetiminiz bir özel durum oluşturmamalı veya içinde bir , VisualStateveya VisualStateGroup eksikse ControlTemplatehata FrameworkElementbildirmemelidir. Bu bölümde, nesnelerle FrameworkElement etkileşim kurmak ve durumları yönetmek için önerilen yöntemler açıklanmaktadır.

Eksik FrameworkElement Nesnelerini Tahmin

içinde ControlTemplatenesneleri tanımladığınızdaFrameworkElement, denetiminizin mantığının bazı nesnelerle 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 TextBlock olarak ValueayarlarText. Özel ControlTemplate bir öğe veya düğmelerini atlarsa TextBlock , denetimin bazı işlevlerini kaybetmesi kabul edilebilir bir durumdur, ancak denetiminizin hataya neden olmadığından emin olmanız gerekir. Örneğin , değiştirilecek NumericUpDownValuedüğmeleri içermiyorsaControlTemplate, işlevi kaybolur, ancak kullanan ControlTemplate bir uygulama çalışmaya devam eder.

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

  1. Kodda x:Name başvurmanız gereken her FrameworkElement birinin özniteliğini ayarlayın.

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

  3. Denetiminizin özelliğin küme erişimcisinde işlediği olaylara FrameworkElement abone olun ve aboneliği kaldırın.

  4. yönteminin FrameworkElement 2. adımında OnApplyTemplate tanımladığınız özellikleri ayarlayın. bu, içindeki ControlTemplate öğesinin FrameworkElement denetimi için en erken kullanılabilir olduğu durumdur. x:NameFrameworkElement'den ControlTemplatealmak için öğesini kullanın.

  5. öğesinin FrameworkElement üyelerine erişmeden önce olup olmadığını null denetleyin. ise nullhata bildirmeyin.

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

içindeki denetimin görsel yapısını NumericUpDown tanımlayan örnekte, artan Value öğesinin özniteliği olarak UpButtonayarlanmıştırx:Name.ControlTemplateRepeatButton Aşağıdaki örnek, içinde ControlTemplatebildirilen öğesini RepeatButton temsil eden adlı UpButtonElement bir özellik bildirir. Erişimciset, değilse UpDownElementnullönce düğmenin Click olayının aboneliğini iptal eder, ardından özelliği ayarlar ve ardından olaya abone olurClick. Burada, adlı DownButtonElementdiğer RepeatButtoniçin tanımlanmış ancak gösterilmeyen 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, denetimin öğesini OnApplyTemplateNumericUpDown gösterir. Örnek, GetTemplateChild içindeki nesneleri ControlTemplatealmak FrameworkElement için yöntemini kullanır. Örnek, beklenen türde olmayan belirtilen ada sahip bir FrameworkElement bulduğu durumlarda GetTemplateChild koruma sağlar. Belirtilen ancak yanlış türde olan öğeleri x:Name yoksaymak da en iyi yöntemdir.

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ı izleyerek, denetiminizin eksik FrameworkElementolduğunda çalışmaya devam edeceğinden ControlTemplate 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. öğesine nesneleri ControlTemplateeklediğinizdeVisualState, bunları öğesine VisualStateGroup eklersiniz ve ekli özelliğe eklersiniz VisualStateGroupVisualStateManager.VisualStateGroups, böylece VisualStateManager bunlara erişim sahibi olursunuz.

Aşağıdaki örnek, denetimin ve Negative durumlarına Positive karşılık gelen nesneleri gösteren önceki örneği yinelerVisualState. in Storyboard kırmızıyı NegativeVisualState çevirir ForegroundTextBlock . NumericUpDown Denetim durumundaykenNegative, durumdaki görsel taslak Negative başlar. StoryboardNegative Ardından, denetim duruma döndüğünde durumundaki Positive durdurulur. PositiveVisualState için durduğunda NegativeStoryboard özgün rengine döndürdüğünden öğesinin Foreground içermesi Storyboard 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>

öğesine bir ad verildiğini TextBlock , ancak TextBlock denetiminin mantığı hiçbir zaman öğesine başvurmadığı için NumericUpDown öğesinin için denetim sözleşmesinde TextBlockolmadığını unutmayın. içinde ControlTemplate başvuruda bulunan öğelerin adları vardır, ancak denetim için yeni ControlTemplate bir öğenin bu öğeye başvurması gerekmeyebileceği için denetim sözleşmesinin parçası olması gerekmez. Örneğin, için NumericUpDown yeni ControlTemplate bir oluşturan kişi, değerini değiştirerek Foregroundnegatif olduğunu Value belirtmemeye karar verebilir. Bu durumda, ne kod ne de ControlTemplate ada göre başvurur TextBlock .

Denetimin mantığı, denetimin durumunu değiştirmekle sorumludur. Aşağıdaki örnek, denetimin NumericUpDown 0 veya daha büyük olduğunda Value duruma gitmek Positive için yöntemini ve Negative 0'dan küçük olduğunda durumunu çağırdığını GoToStateValue 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

yöntemi, GoToState görsel taslakları uygun şekilde başlatmak ve durdurmak için gereken mantığı gerçekleştirir. Bir denetim durumunu değiştirmek için çağırdığında GoToState aşağıdakileri VisualStateManager 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 içinde ControlTemplatecontrolGoToState yoksa hiçbir eylem gerçekleştirmez ve döndürür.false

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.

Denetim, NumericUpDown veya Negative durumunda olup olmadığını Positive izlemek için özelliğini kullanırValue. Denetim ayrıca NumericUpDown özelliğini izleyen ve UnFocused durumlarını IsFocused da tanımlarFocused. 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 Positive eşit olduğundaValue, Control durumundadır. 0'dan küçük olduğunda Value denetim durumundadır Negative . olduğundaIsFocused, denetim durumundadırFocused; aksi takdirde, durumundadırUnfocused.true 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, bir negatif sayıdan başka bir negatif sayıya değişirse Value , durum için görsel taslak Negative kesintiye uğramaz ve kullanıcı denetimde bir değişiklik görmez.

, VisualStateManager çağırdığınızda GoToStatehangi durumdan çıkabileceğinizi belirlemek için nesneleri kullanırVisualStateGroup. Denetimi, içinde ControlTemplate tanımlanan her VisualStateGroup biri için her zaman bir durumdadır ve yalnızca aynı VisualStateGroupdurumdan başka bir duruma geçtiğinde bir durumdan ayrılır. Örneğin, denetimin öğesi bir içindeki VisualStateGroup ve NegativeVisualState nesnelerini ve diğerindeki Focused ve UnfocusedVisualState nesnelerini tanımlarPositive.NumericUpDownControlTemplate (ve değerini bu konudaki Tam Örnek bölümünde görebilirsiniz UnfocusedVisualStateFocused. Denetim durumundan Positive duruma Negative geçtiğinde veya tam tersi durumda kaldığında, denetim ya da UnfocusedFocused 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.

uygulandığında OnApplyTemplate denetimin doğru durumda görünmesi için yöntemindeki denetimin ControlTemplate durumunu güncelleştirmeniz gerekir. Aşağıdaki örnek, denetimin uygun durumlarda olduğundan emin olmak için öğesini çağırır UpdateStatesOnApplyTemplate . Örneğin, bir NumericUpDown denetim oluşturduğunuzu ve ardından bunu Foreground yeşil ve Value -5 olarak ayarladığınızı varsayalım. denetimine uygulandığında NumericUpDownControlTemplate çağrı UpdateStates yapmazsanız, denetim durumunda değildir Negative ve değer kırmızı yerine yeşil olur. Denetimi Negative duruma getirmek için aramanız UpdateStates 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 durumlarını güncelleştirmeniz gerekir. Aşağıdaki örnekte yöntemin tamamı ValueChangedCallback gösterilmektedir. Değişiklik ValueChangedCallback yapıldığında Value çağrıldığından, yöntem pozitiften negatife veya tersine değişmesi durumunda Value çağrı yaparUpdateStates. Değişiklikler olduğunda Value çağrı UpdateStates yapmak kabul edilebilir ancak pozitif veya negatif kalır, çünkü 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

Bir olay gerçekleştiğinde durumları da güncelleştirmeniz gerekebilir. Aşağıdaki örnekte, olayı işlemek için üzerindeki çağrılar UpdateStates gösterilmektedir.GotFocusNumericUpDownControl

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

denetiminizin VisualStateManager 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.

Denetim 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 ortak özellikler.

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.

Örneğine döndüğünüzdeNumericUpDown, denetim öğesinin aşağıdaki FrameworkElement nesnelere sahip olmasını beklerControlTemplate:

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, durumunun adını ve ait olduğu VisualStateGroup adı belirten öğesini kullanırsınızTemplateVisualStateAttribute. ve TemplateVisualStateAttribute öğesini TemplatePartAttribute denetimin sınıf tanımına yerleştirin.

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

Aşağıdaki örnek, denetimin FrameworkElement nesnesini ve durumlarını NumericUpDown 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, denetimin tamamıdır 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 ö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 bkz.