Omówienie tworzenia kontrolek

Rozszerzalność modelu sterowania Windows Presentation Foundation (WPF) znacznie zmniejsza potrzebę utworzenia nowej kontrolki. Jednak w niektórych przypadkach może być konieczne utworzenie niestandardowej kontrolki. W tym temacie omówiono funkcje, które minimalizują potrzebę utworzenia niestandardowej kontrolki i różnych modeli tworzenia kontrolek w programie Windows Presentation Foundation (WPF). W tym temacie pokazano również, jak utworzyć nową kontrolkę.

Alternatywy dla pisania nowej kontrolki

Historycznie, jeśli chcesz uzyskać dostosowane środowisko z istniejącej kontrolki, ograniczano się do zmiany standardowych właściwości kontrolki, takich jak kolor tła, szerokość obramowania i rozmiar czcionki. Jeśli chcesz rozszerzyć wygląd lub zachowanie kontrolki poza tymi wstępnie zdefiniowanymi parametrami, należy utworzyć nową kontrolkę, zwykle dziedzicząc z istniejącej kontrolki i przesłaniając metodę odpowiedzialną za rysowanie kontrolki. Mimo że jest to nadal opcja, WPF umożliwia dostosowywanie istniejących kontrolek przy użyciu rozbudowanego con tryb namiotu l, stylów, szablonów i wyzwalaczy. Poniższa lista zawiera przykłady użycia tych funkcji do tworzenia niestandardowych i spójnych środowisk bez konieczności tworzenia nowej kontrolki.

  • Zawartość wzbogacona. Wiele standardowych kontrolek WPF obsługuje bogatą zawartość. Na przykład właściwość content obiektu Button jest typu Object, więc teoretycznie wszystko może być wyświetlane na .Button Aby przycisk wyświetlał obraz i tekst, możesz dodać obraz i obiekt TextBlock do StackPanel obiektu i przypisać go StackPanel do Content właściwości . Ponieważ kontrolki mogą wyświetlać elementy wizualne WPF i dowolne dane, mniej trzeba utworzyć nową kontrolkę lub zmodyfikować istniejącą kontrolkę w celu obsługi złożonej wizualizacji. Aby uzyskać więcej informacji na temat con tryb namiotu l for Button i innych con tryb namiotu ls w WPF, zobacz Model zawartości WPF.

  • Style. A Style to kolekcja wartości reprezentujących właściwości kontrolki. Używając stylów, można utworzyć reprezentację wielokrotnego użytku żądanego wyglądu i zachowania kontrolki bez konieczności pisania nowej kontrolki. Załóżmy na przykład, że chcesz, aby wszystkie TextBlock kontrolki miały czerwoną czcionkę Arial o rozmiarze 14. Styl można utworzyć jako zasób i odpowiednio ustawić odpowiednie właściwości. Następnie każdy TextBlock dodany do aplikacji będzie miał taki sam wygląd.

  • Szablony danych. Element umożliwia DataTemplate dostosowanie sposobu wyświetlania danych w kontrolce. Można na przykład użyć elementu DataTemplate , aby określić sposób wyświetlania danych w obiekcie ListBox. Aby zapoznać się z przykładem tego, zobacz Omówienie tworzenia szablonów danych. Oprócz dostosowywania wyglądu danych element DataTemplate może zawierać elementy interfejsu użytkownika, co zapewnia dużą elastyczność w niestandardowych interfejsach użytkownika. Na przykład przy użyciu DataTemplateelementu można utworzyć obiekt ComboBox , w którym każdy element zawiera pole wyboru.

  • Szablony kontrolek. Wiele kontrolek w WPF używa elementu , ControlTemplate aby zdefiniować strukturę i wygląd kontrolki, która oddziela wygląd kontrolki od funkcjonalności kontrolki. Możesz znacząco zmienić wygląd kontrolki, definiując jej ControlTemplateelement . Załóżmy na przykład, że chcesz kontrolować, która wygląda jak stoplight. Ta kontrolka ma prosty interfejs użytkownika i funkcjonalność. Kontrolka to trzy okręgi, z których tylko jeden może być oświetlony naraz. Po pewnym odbiciu można zdać sobie sprawę, że RadioButton funkcja oferuje tylko jedną wybraną w danym momencie, ale domyślny wygląd RadioButton nie wygląda jak światła na świetle. RadioButton Ponieważ szablon kontrolki używa szablonu do definiowania jego wyglądu, łatwo jest ponownie zdefiniować ControlTemplate element zgodnie z wymaganiami kontrolki i używać przycisków radiowych w celu ustawienia światła.

    Uwaga

    Chociaż w tym przykładzie RadioButton można użyć elementu DataTemplate, element a DataTemplate nie jest wystarczający. Element DataTemplate definiuje wygląd zawartości kontrolki. W przypadku RadioButtonobiektu zawartość jest wyświetlana po prawej stronie okręgu, która wskazuje, czy jest zaznaczona RadioButton . W przykładzie reflektora przycisk radiowy musi być tylko okręgiem, który może się "rozświetlać". Ponieważ wymaganie dotyczące wyglądu światła jest tak inne niż domyślny wygląd RadioButtonobiektu , należy ponownie zdefiniować ControlTemplateelement . Ogólnie rzecz biorąc DataTemplate , element jest używany do definiowania zawartości (lub danych) kontrolki, a element ControlTemplate służy do definiowania struktury kontrolki.

  • Wyzwalaczy. Element A Trigger umożliwia dynamiczne zmienianie wyglądu i zachowania kontrolki bez tworzenia nowej kontrolki. Załóżmy na przykład, że masz wiele ListBox kontrolek w aplikacji i chcesz, aby elementy w każdej ListBox z nich były pogrubione i czerwone po wybraniu. Pierwszym instynktem może być utworzenie klasy, która dziedziczy i ListBox zastępuje OnSelectionChanged metodę w celu zmiany wyglądu wybranego elementu, ale lepszym podejściem jest dodanie wyzwalacza do stylu ListBoxItem , który zmienia wygląd wybranego elementu. Wyzwalacz umożliwia zmianę wartości właściwości lub wykonywanie akcji na podstawie wartości właściwości. Element EventTrigger umożliwia wykonywanie akcji w przypadku wystąpienia zdarzenia.

Aby uzyskać więcej informacji na temat stylów, szablonów i wyzwalaczy, zobacz Styling and Templating (Styling and Templating).

Ogólnie rzecz biorąc, jeśli kontrolka odzwierciedla funkcjonalność istniejącej kontrolki, ale chcesz, aby kontrolka wyglądała inaczej, należy najpierw rozważyć, czy można użyć dowolnej z metod omówionych w tej sekcji, aby zmienić wygląd istniejącej kontrolki.

Modele tworzenia kontrolek

Zaawansowane con tryb namiotu l, style, szablony i wyzwalacze minimalizują potrzebę utworzenia nowej kontrolki. Jeśli jednak musisz utworzyć nową kontrolkę, ważne jest, aby zrozumieć różne modele tworzenia kontrolek w WPF. WPF oferuje trzy ogólne modele tworzenia kontrolki, z których każdy zapewnia inny zestaw funkcji i poziom elastyczności. Klasy podstawowe dla trzech modeli to UserControl, Controli FrameworkElement.

Wyprowadzanie z elementu UserControl

Najprostszym sposobem utworzenia kontrolki w WPF jest wyprowadzenie z UserControlklasy . Podczas kompilowania kontrolki dziedziczonej z UserControlprogramu dodaje się istniejące składniki do UserControlklasy , nazwij składniki i odwołania do programów obsługi zdarzeń w języku XAML. Następnie możesz odwołać się do nazwanych elementów i zdefiniować programy obsługi zdarzeń w kodzie. Ten model programowania jest bardzo podobny do modelu używanego do tworzenia aplikacji w WPF.

W przypadku poprawnej UserControl kompilacji można korzystać z zalet rozbudowanej zawartości, stylów i wyzwalaczy. Jeśli jednak kontrolka dziedziczy z UserControlelementu , osoby korzystające z kontrolki nie będą mogły używać DataTemplate kontrolki lub ControlTemplate dostosowywać jej wyglądu. Konieczne jest utworzenie niestandardowej kontrolki obsługującej Control szablony z klasy lub jednej z jej klas pochodnych (innych niż UserControl).

Korzyści wynikające z funkcji UserControl

Rozważ wyprowadzenie z UserControl , jeśli wszystkie następujące elementy mają zastosowanie:

  • Chcesz utworzyć kontrolkę podobnie jak w przypadku tworzenia aplikacji.

  • Kontrolka składa się tylko z istniejących składników.

  • Nie musisz obsługiwać złożonego dostosowywania.

Wyprowadzanie z kontrolki

Wyprowadzanie z Control klasy jest modelem używanym przez większość istniejących kontrolek WPF. Podczas tworzenia kontrolki dziedziczonej Control po klasie można zdefiniować jej wygląd przy użyciu szablonów. W ten sposób należy oddzielić logikę operacyjną od reprezentacji wizualnej. Można również zapewnić oddzielenie interfejsu użytkownika i logiki przy użyciu poleceń i powiązań zamiast zdarzeń i uniknięcia odwoływania się do elementów w ControlTemplate miarę możliwości. Jeśli interfejs użytkownika i logika kontrolki są prawidłowo oddzielone, użytkownik kontrolki może ponownie zdefiniować kontrolkę ControlTemplate , aby dostosować jej wygląd. Chociaż tworzenie niestandardowego Control elementu nie jest tak proste, jak tworzenie obiektu UserControl, niestandardowy Control zapewnia największą elastyczność.

Korzyści wynikające z wyprowadzania z kontrolki

Rozważ wyprowadzenie z Control zamiast używać UserControl klasy , jeśli którakolwiek z następujących metod ma zastosowanie:

  • Wygląd kontrolki ma być dostosowywalny za pośrednictwem elementu ControlTemplate.

  • Chcesz, aby kontrola obsługiwała różne motywy.

Wyprowadzanie z elementu FrameworkElement

Kontrolki, które pochodzą z UserControlControl lub polegają na komponowaniu istniejących elementów. W wielu scenariuszach jest to akceptowalne rozwiązanie, ponieważ każdy obiekt dziedziczony z FrameworkElement programu może znajdować się w obiekcie ControlTemplate. Jednak czasami wygląd kontrolki wymaga więcej niż funkcjonalność prostego składu elementów. W przypadku tych scenariuszy oparcie składnika na FrameworkElement serwerze jest właściwym wyborem.

Istnieją dwie standardowe metody tworzenia FrameworkElementskładników opartych na modelu: renderowanie bezpośrednie i kompozycja elementów niestandardowych. Renderowanie bezpośrednie polega na OnRender zastąpieniu metody FrameworkElement i zapewnieniu DrawingContext operacji, które jawnie definiują wizualizacje składników. Jest to metoda używana przez Image metody i Border. Kompozycja elementów niestandardowych obejmuje używanie obiektów typu Visual do tworzenia wyglądu składnika. Przykład można znaleźć w temacie Using DrawingVisual Objects (Używanie obiektów DrawingVisual). Track jest przykładem kontrolki w WPF używającej niestandardowej kompozycji elementów. Istnieje również możliwość połączenia bezpośredniego renderowania i kompozycji elementów niestandardowych w tej samej kontrolce.

Korzyści wynikające z elementu FrameworkElement

Rozważ wyprowadzenie z FrameworkElement , jeśli którykolwiek z następujących stosowania:

  • Chcesz mieć precyzyjną kontrolę nad wyglądem kontrolki poza tym, co zapewnia prosta kompozycja elementów.

  • Chcesz zdefiniować wygląd kontrolki, definiując własną logikę renderowania.

  • Chcesz utworzyć istniejące elementy w nowatorski sposób, które wykraczają poza to, co jest możliwe z UserControl i Control.

Podstawy tworzenia kontrolek

Jak wspomniano wcześniej, jedną z najbardziej zaawansowanych funkcji WPF jest możliwość wykraczania poza ustawienie podstawowych właściwości kontrolki w celu zmiany jej wyglądu i zachowania, ale nadal nie trzeba tworzyć niestandardowej kontrolki. Funkcje stylów, powiązania danych i wyzwalacza są możliwe przez system właściwości WPF i system zdarzeń WPF. W poniższych sekcjach opisano niektóre rozwiązania, które należy wykonać, niezależnie od modelu używanego do tworzenia kontrolki niestandardowej, dzięki czemu użytkownicy kontrolki niestandardowej mogą używać tych funkcji tak samo jak w przypadku kontrolki dołączonej do platformy WPF.

Korzystanie z właściwości zależności

Jeśli właściwość jest właściwością zależności, można wykonać następujące czynności:

  • Ustaw właściwość w stylu.

  • Powiąż właściwość ze źródłem danych.

  • Użyj zasobu dynamicznego jako wartości właściwości.

  • Animowanie właściwości.

Jeśli chcesz, aby właściwość kontrolki obsługiwała dowolną z tych funkcji, należy zaimplementować ją jako właściwość zależności. W poniższym przykładzie zdefiniowano właściwość zależności o nazwie Value , wykonując następujące czynności:

  • Zdefiniuj DependencyProperty identyfikator o nazwie ValueProperty jako publicstaticreadonly pole.

  • Zarejestruj nazwę właściwości w systemie właściwości, wywołując DependencyProperty.Registermetodę , aby określić następujące elementy:

    • Nazwa właściwości.

    • Typ właściwości.

    • Typ, który jest właścicielem właściwości.

    • Metadane właściwości. Metadane zawierają wartość domyślną właściwości a i CoerceValueCallbackPropertyChangedCallback.

  • Zdefiniuj właściwość otoki CLR o nazwie Value, która jest taką samą nazwą, która jest używana do rejestrowania właściwości zależności, implementując właściwości get i set metody dostępu. Należy pamiętać, że get metody i set są wywoływane GetValue tylko i SetValue odpowiednio. Zaleca się, aby metody dostępu właściwości zależności nie zawierały dodatkowej logiki, ponieważ klienci i WPF mogą pominąć metody dostępu i wywołania GetValue i SetValue bezpośrednio. Na przykład gdy właściwość jest powiązana ze źródłem danych, akcesorium właściwości set nie jest wywoływane. Zamiast dodawać dodatkową logikę do metody uzyskiwania i ustawiania metod dostępu, użyj ValidateValueCallbackdelegatów , CoerceValueCallbacki PropertyChangedCallback , aby reagować na zmiany lub sprawdzać wartość. Aby uzyskać więcej informacji na temat tych wywołań zwrotnych, zobacz Wywołania zwrotne właściwości zależności i walidacja.

  • Zdefiniuj CoerceValueCallback metodę dla nazwy CoerceValue. CoerceValue zapewnia, że Value wartość jest większa lub równa MinValue i mniejsza niż lub równa MaxValue.

  • Zdefiniuj PropertyChangedCallbackmetodę dla klasy o nazwie OnValueChanged. OnValueChanged Tworzy RoutedPropertyChangedEventArgs<T> obiekt i przygotowuje się do wywołania zdarzenia kierowanego ValueChanged . Zdarzenia kierowane zostały omówione w następnej sekcji.

/// <summary>
/// Identifies the Value dependency property.
/// </summary>
public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register(
        "Value", typeof(decimal), typeof(NumericUpDown),
        new FrameworkPropertyMetadata(MinValue, new PropertyChangedCallback(OnValueChanged),
                                      new CoerceValueCallback(CoerceValue)));

/// <summary>
/// Gets or sets the value assigned to the control.
/// </summary>
public decimal Value
{
    get { return (decimal)GetValue(ValueProperty); }
    set { SetValue(ValueProperty, value); }
}

private static object CoerceValue(DependencyObject element, object value)
{
    decimal newValue = (decimal)value;
    NumericUpDown control = (NumericUpDown)element;

    newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue));

    return newValue;
}

private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    NumericUpDown control = (NumericUpDown)obj;			

    RoutedPropertyChangedEventArgs<decimal> e = new RoutedPropertyChangedEventArgs<decimal>(
        (decimal)args.OldValue, (decimal)args.NewValue, ValueChangedEvent);
    control.OnValueChanged(e);
}
''' <summary>
''' Identifies the Value dependency property.
''' </summary>
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Decimal), GetType(NumericUpDown), New FrameworkPropertyMetadata(MinValue, New PropertyChangedCallback(AddressOf OnValueChanged), New CoerceValueCallback(AddressOf CoerceValue)))

''' <summary>
''' Gets or sets the value assigned to the control.
''' </summary>
Public Property Value() As Decimal
    Get
        Return CDec(GetValue(ValueProperty))
    End Get
    Set(ByVal value As Decimal)
        SetValue(ValueProperty, value)
    End Set
End Property

Private Shared Overloads Function CoerceValue(ByVal element As DependencyObject, ByVal value As Object) As Object
    Dim newValue As Decimal = CDec(value)
    Dim control As NumericUpDown = CType(element, NumericUpDown)

    newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue))

    Return newValue
End Function

Private Shared Sub OnValueChanged(ByVal obj As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
    Dim control As NumericUpDown = CType(obj, NumericUpDown)

    Dim e As New RoutedPropertyChangedEventArgs(Of Decimal)(CDec(args.OldValue), CDec(args.NewValue), ValueChangedEvent)
    control.OnValueChanged(e)
End Sub

Aby uzyskać więcej informacji, zobacz Właściwości zależności niestandardowych.

Używanie zdarzeń trasowanych

Podobnie jak właściwości zależności rozszerzają pojęcie właściwości CLR z dodatkowymi funkcjami, zdarzenia kierowane rozszerzają pojęcie standardowych zdarzeń CLR. Podczas tworzenia nowej kontrolki WPF warto również zaimplementować zdarzenie jako zdarzenie kierowane, ponieważ zdarzenie kierowane obsługuje następujące zachowanie:

  • Zdarzenia mogą być obsługiwane w obiekcie nadrzędnym wielu kontrolek. Jeśli zdarzenie jest zdarzeniem bubbling, pojedynczy element nadrzędny w drzewie elementów może subskrybować zdarzenie. Następnie autorzy aplikacji mogą użyć jednej procedury obsługi w celu reagowania na zdarzenie wielu kontrolek. Jeśli na przykład kontrolka jest częścią każdego elementu w elemencie ListBox (ponieważ znajduje się w elemencie DataTemplate), deweloper aplikacji może zdefiniować program obsługi zdarzeń dla zdarzenia kontrolki w obiekcie ListBox. Za każdym razem, gdy zdarzenie występuje w dowolnej z kontrolek, wywoływana jest procedura obsługi zdarzeń.

  • Zdarzenia kierowane mogą być używane w obiekcie EventSetter, co umożliwia deweloperom aplikacji określenie procedury obsługi zdarzenia w stylu.

  • Zdarzenia trasowane mogą być używane w elemecie EventTrigger, co jest przydatne do animowania właściwości przy użyciu języka XAML. Aby uzyskać więcej informacji, zobacz Omówienie animacji.

W poniższym przykładzie zdefiniowano zdarzenie kierowane, wykonując następujące czynności:

  • Zdefiniuj RoutedEvent identyfikator o nazwie ValueChangedEvent jako publicstaticreadonly pole.

  • Zarejestruj zdarzenie kierowane, wywołując metodę EventManager.RegisterRoutedEvent . Przykład określa następujące informacje, gdy wywołuje RegisterRoutedEventmetodę :

    • Nazwa zdarzenia to ValueChanged.

    • Strategia routingu to Bubble, co oznacza, że program obsługi zdarzeń w źródle (obiekt, który zgłasza zdarzenie) jest wywoływany najpierw, a następnie programy obsługi zdarzeń w elementach nadrzędnych źródła są wywoływane z rzędu, począwszy od programu obsługi zdarzeń w najbliższym elemercie nadrzędnym.

    • Typ programu obsługi zdarzeń to RoutedPropertyChangedEventHandler<T>, skonstruowany z typem Decimal .

    • Typ posiadania zdarzenia to NumericUpDown.

  • Zadeklaruj zdarzenie publiczne o nazwie ValueChanged i zawiera deklaracje akcesorów zdarzeń. Przykład wywołuje AddHandler deklarację add metody dostępu i RemoveHandler w remove deklaracji dostępu do korzystania z usług zdarzeń WPF.

  • Utwórz chronioną metodę wirtualną o nazwie OnValueChanged , która zgłasza ValueChanged zdarzenie.

/// <summary>
/// Identifies the ValueChanged routed event.
/// </summary>
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
    "ValueChanged", RoutingStrategy.Bubble,
    typeof(RoutedPropertyChangedEventHandler<decimal>), typeof(NumericUpDown));

/// <summary>
/// Occurs when the Value property changes.
/// </summary>
public event RoutedPropertyChangedEventHandler<decimal> ValueChanged
{
    add { AddHandler(ValueChangedEvent, value); }
    remove { RemoveHandler(ValueChangedEvent, value); }
}

/// <summary>
/// Raises the ValueChanged event.
/// </summary>
/// <param name="args">Arguments associated with the ValueChanged event.</param>
protected virtual void OnValueChanged(RoutedPropertyChangedEventArgs<decimal> args)
{
    RaiseEvent(args);
}
''' <summary>
''' Identifies the ValueChanged routed event.
''' </summary>
Public Shared ReadOnly ValueChangedEvent As RoutedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, GetType(RoutedPropertyChangedEventHandler(Of Decimal)), GetType(NumericUpDown))

''' <summary>
''' Occurs when the Value property changes.
''' </summary>
Public Custom Event ValueChanged As RoutedPropertyChangedEventHandler(Of Decimal)
    AddHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
        MyBase.AddHandler(ValueChangedEvent, value)
    End AddHandler
    RemoveHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
        MyBase.RemoveHandler(ValueChangedEvent, value)
    End RemoveHandler
    RaiseEvent(ByVal sender As System.Object, ByVal e As RoutedPropertyChangedEventArgs(Of Decimal))
    End RaiseEvent
End Event

''' <summary>
''' Raises the ValueChanged event.
''' </summary>
''' <param name="args">Arguments associated with the ValueChanged event.</param>
Protected Overridable Sub OnValueChanged(ByVal args As RoutedPropertyChangedEventArgs(Of Decimal))
    MyBase.RaiseEvent(args)
End Sub

Aby uzyskać więcej informacji, zobacz Routed Events Overview (Przegląd zdarzeń trasowanych) i Create a Custom Routed Event (Tworzenie niestandardowego zdarzenia trasowanego).

Korzystanie z powiązania

Aby rozdzielić interfejs użytkownika kontrolki z jej logiki, rozważ użycie powiązania danych. Jest to szczególnie ważne w przypadku definiowania wyglądu kontrolki przy użyciu elementu ControlTemplate. Jeśli używasz powiązania danych, możesz wyeliminować konieczność odwołowania się do określonych części interfejsu użytkownika z kodu. Warto unikać odwoływania się do elementów, które znajdują się w obiekcie ControlTemplate , ponieważ gdy kod odwołuje się do elementów znajdujących się w elemencie ControlTemplateControlTemplate i jest zmieniany, element, do którego się odwołuje, musi zostać uwzględniony w nowym ControlTemplateelemencie .

Poniższy przykład aktualizuje TextBlock kontrolkę NumericUpDown , przypisując do niej nazwę i odwołując się do pola tekstowego według nazwy w kodzie.

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">
  <TextBlock Name="valueText" Width="60" TextAlignment="Right" Padding="5"/>
</Border>
private void UpdateTextBlock()
{
    valueText.Text = Value.ToString();
}
Private Sub UpdateTextBlock()
    valueText.Text = Value.ToString()
End Sub

W poniższym przykładzie użyto powiązania, aby wykonać to samo.

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">

    <!--Bind the TextBlock to the Value property-->
    <TextBlock 
        Width="60" TextAlignment="Right" Padding="5"
        Text="{Binding RelativeSource={RelativeSource FindAncestor, 
                       AncestorType={x:Type local:NumericUpDown}}, 
                       Path=Value}"/>

</Border>

Aby uzyskać więcej informacji na temat tworzenia powiązań danych, zobacz Omówienie powiązań danych.

Projektowanie pod kątem Projektant

Aby uzyskać obsługę niestandardowych kontrolek WPF w Projektant WPF dla programu Visual Studio (na przykład edytowanie właściwości przy użyciu okno Właściwości), postępuj zgodnie z tymi wytycznymi. Aby uzyskać więcej informacji na temat programowania dla Projektant WPF, zobacz Design XAML in Visual Studio (Projektowanie kodu XAML w programie Visual Studio).

Właściwości zależności

Pamiętaj, aby zaimplementować clR get i set metody dostępu zgodnie z wcześniejszym opisem w temacie "Use Dependency Properties" (Używanie właściwości zależności). Projektant mogą używać otoki do wykrywania obecności właściwości zależności, ale, takich jak WPF i klienci kontrolki, nie są wymagane do wywoływania metod dostępu podczas pobierania lub ustawiania właściwości.

Dołączone właściwości

Należy zaimplementować dołączone właściwości kontrolek niestandardowych, korzystając z następujących wskazówek:

  • Ma formę publicreadonlystaticDependencyProperty PropertyNameProperty, która została utworzona RegisterAttached przy użyciu metody . Przekazana nazwa RegisterAttached właściwości musi być zgodna z właściwością PropertyName.

  • Zaimplementuj parę metod CLR o nazwach SetPropertyName i GetPropertyName.publicstatic Obie metody powinny akceptować klasę pochodzącą z DependencyProperty pierwszego argumentu. Metoda SetPropertyName akceptuje również argument, którego typ pasuje do zarejestrowanego typu danych dla właściwości. GetMetoda PropertyName powinna zwrócić wartość tego samego typu. SetJeśli brakuje metody PropertyName, właściwość jest oznaczona jako tylko do odczytu.

  • SetWłaściwości PropertyName i GetPropertyName muszą być kierowane bezpośrednio do GetValue metod i SetValue odpowiednio do obiektu zależności docelowej. Projektant może uzyskać dostęp do dołączonej właściwości przez wywołanie otoki metody lub bezpośrednie wywołanie obiektu zależności docelowej.

Aby uzyskać więcej informacji na temat dołączonych właściwości, zobacz Omówienie dołączonych właściwości.

Definiowanie zasobów udostępnionych i korzystanie z nich

Kontrolkę można dołączyć do tego samego zestawu co aplikacja lub spakować kontrolkę w osobnym zestawie, który może być używany w wielu aplikacjach. W większości przypadków informacje omówione w tym temacie mają zastosowanie niezależnie od używanej metody. Istnieje jednak jedna różnica, której warto zauważyć. Gdy umieścisz kontrolkę w tym samym zestawie co aplikacja, możesz dodać zasoby globalne do pliku App.xaml. Jednak zestaw zawierający tylko kontrolki nie ma skojarzonego Application z nim obiektu, więc plik App.xaml nie jest dostępny.

Gdy aplikacja wyszukuje zasób, wygląda na trzy poziomy w następującej kolejności:

  1. Poziom elementu.

    System zaczyna się od elementu, który odwołuje się do zasobu, a następnie wyszukuje zasoby elementu nadrzędnego logicznego i tak dalej, aż do osiągnięcia elementu głównego.

  2. Poziom aplikacji.

    Zasoby zdefiniowane przez Application obiekt.

  3. Poziom motywu.

    Słowniki na poziomie motywu są przechowywane w podfolderze o nazwie Motywy. Pliki w folderze Motywy odpowiadają motywom. Na przykład możesz mieć plik Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml itd. Możesz również mieć plik o nazwie generic.xaml. Gdy system wyszukuje zasób na poziomie motywów, najpierw wyszukuje go w pliku specyficznym dla motywu, a następnie wyszukuje go w pliku generic.xaml.

Gdy kontrolka znajduje się w zestawie, który jest oddzielony od aplikacji, musisz umieścić zasoby globalne na poziomie elementu lub na poziomie motywu. Obie metody mają swoje zalety.

Definiowanie zasobów na poziomie elementu

Zasoby udostępnione można zdefiniować na poziomie elementu, tworząc słownik zasobów niestandardowych i scalając go ze słownikiem zasobów kontrolki. Jeśli używasz tej metody, możesz nazwać plik zasobu dowolnych elementów i może znajdować się w tym samym folderze co kontrolki. Zasoby na poziomie elementu mogą również używać prostych ciągów jako kluczy. Poniższy przykład tworzy LinearGradientBrush plik zasobu o nazwie Dictionary1.xaml.

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <LinearGradientBrush 
    x:Key="myBrush"  
    StartPoint="0,0" EndPoint="1,1">
    <GradientStop Color="Red" Offset="0.25" />
    <GradientStop Color="Blue" Offset="0.75" />
  </LinearGradientBrush>
  
</ResourceDictionary>

Po zdefiniowaniu słownika należy scalić go ze słownikiem zasobów kontrolki. Można to zrobić przy użyciu języka XAML lub kodu.

Poniższy przykład scala słownik zasobów przy użyciu języka XAML.

<UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Dictionary1.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</UserControl.Resources>

Wadą tego podejścia jest to, że ResourceDictionary obiekt jest tworzony za każdym razem, gdy się do niego odwołujesz. Jeśli na przykład w bibliotece masz 10 kontrolek niestandardowych i scalisz słowniki zasobów udostępnionych dla każdej kontrolki przy użyciu języka XAML, utworzysz 10 identycznych ResourceDictionary obiektów. Można tego uniknąć, tworząc klasę statyczną, która scala zasoby w kodzie i zwraca wynikowy ResourceDictionaryelement .

W poniższym przykładzie zostanie utworzona klasa zwracająca współużytkowany ResourceDictionaryelement .

internal static class SharedDictionaryManager
{
    internal static ResourceDictionary SharedDictionary
    {
        get
        {
            if (_sharedDictionary == null)
            {
                System.Uri resourceLocater =
                    new System.Uri("/ElementResourcesCustomControlLibrary;component/Dictionary1.xaml",
                                    System.UriKind.Relative);

                _sharedDictionary =
                    (ResourceDictionary)Application.LoadComponent(resourceLocater);
            }

            return _sharedDictionary;
        }
    }

    private static ResourceDictionary _sharedDictionary;
}

Poniższy przykład scala udostępniony zasób z zasobami niestandardowej kontrolki w konstruktorze kontrolki przed wywołaniami InitializeComponentpolecenia . SharedDictionaryManager.SharedDictionary Ponieważ obiekt jest właściwością statyczną, ResourceDictionary jest tworzony tylko raz. Ponieważ słownik zasobów został wcześniej scalony InitializeComponent , zasoby są dostępne dla kontrolki w swoim pliku XAML.

public NumericUpDown()
{
    this.Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary);
    InitializeComponent();
}

Definiowanie zasobów na poziomie motywu

WPF umożliwia tworzenie zasobów dla różnych motywów systemu Windows. Jako autor kontrolki możesz zdefiniować zasób dla określonego motywu, aby zmienić wygląd kontrolki w zależności od używanego motywu. Na przykład wygląd Button motywu Windows Classic (motyw domyślny dla systemu Windows 2000) różni się od Button motywu Windows Luna (motyw domyślny dla systemu Windows XP), ponieważ Button używa innego ControlTemplate motywu dla każdego motywu.

Zasoby specyficzne dla motywu są przechowywane w słowniku zasobów o określonej nazwie pliku. Te pliki muszą znajdować się w folderze o nazwie Themes , który jest podfolderem folderu zawierającego kontrolkę. W poniższej tabeli wymieniono pliki słownika zasobów i motyw skojarzony z każdym plikiem:

Nazwa pliku słownika zasobów Motyw systemu Windows
Classic.xaml Klasyczny wygląd systemu Windows 9x/2000 w systemie Windows XP
Luna.NormalColor.xaml Domyślny niebieski motyw w systemie Windows XP
Luna.Homestead.xaml Motyw Olive w systemie Windows XP
Luna.Metallic.xaml Motyw Silver w systemie Windows XP
Royale.NormalColor.xaml Motyw domyślny w systemie Windows XP Media Center Edition
Aero.NormalColor.xaml Motyw domyślny w systemie Windows Vista

Nie trzeba definiować zasobu dla każdego motywu. Jeśli zasób nie jest zdefiniowany dla określonego motywu, kontrolka sprawdza Classic.xaml zasób. Jeśli zasób nie jest zdefiniowany w pliku, który odpowiada bieżącemu motywowi lub w Classic.xamlelemencie , kontrolka używa zasobu ogólnego, który znajduje się w pliku słownika zasobów o nazwie generic.xaml. Plik generic.xaml znajduje się w tym samym folderze co pliki słownika zasobów specyficzne dla motywu. Chociaż generic.xaml nie odpowiada określonemu motywowi systemu Windows, nadal jest to słownik na poziomie motywu.

Niestandardowa kontrolka języka C# lub Visual Basic NumericUpDown z motywem i obsługą automatyzacji interfejsu użytkownika zawiera dwa słowniki zasobów dla NumericUpDown kontrolki: jeden znajduje się w pliku generic.xaml, a drugi znajduje się w pliku Luna.NormalColor.xaml.

W przypadku umieszczenia ControlTemplate elementu w dowolnym z plików słownika zasobów specyficznych dla motywu należy utworzyć konstruktor statyczny dla kontrolki i wywołać OverrideMetadata(Type, PropertyMetadata) metodę w DefaultStyleKeyobiekcie , jak pokazano w poniższym przykładzie.

static NumericUpDown()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
               new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}
Shared Sub New()
    DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
End Sub
Definiowanie i odwoływanie się do kluczy dla zasobów motywu

Podczas definiowania zasobu na poziomie elementu można przypisać ciąg jako jego klucz i uzyskać dostęp do zasobu za pośrednictwem ciągu. Podczas definiowania zasobu na poziomie motywu należy użyć ComponentResourceKey elementu jako klucza. Poniższy przykład definiuje zasób w pliku generic.xaml.

<LinearGradientBrush 
     x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:Painter}, 
                                  ResourceId=MyEllipseBrush}"  
                                  StartPoint="0,0" EndPoint="1,0">
    <GradientStop Color="Blue" Offset="0" />
    <GradientStop Color="Red" Offset="0.5" />
    <GradientStop Color="Green" Offset="1"/>
</LinearGradientBrush>

Poniższy przykład odwołuje się do zasobu, określając ComponentResourceKey jako klucz.

<RepeatButton 
    Grid.Column="1" Grid.Row="0"
    Background="{StaticResource {ComponentResourceKey 
                        TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                        ResourceId=ButtonBrush}}">
    Up
</RepeatButton>
<RepeatButton 
    Grid.Column="1" Grid.Row="1"
    Background="{StaticResource {ComponentResourceKey 
                    TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                    ResourceId=ButtonBrush}}">
    Down
 </RepeatButton>
Określanie lokalizacji zasobów motywu

Aby znaleźć zasoby dla kontrolki, aplikacja hostująca musi wiedzieć, że zestaw zawiera zasoby specyficzne dla kontroli. Można to zrobić, dodając element ThemeInfoAttribute do zestawu zawierającego kontrolkę. Właściwość ThemeInfoAttribute ma właściwość określającą GenericDictionaryLocation lokalizację zasobów ogólnych oraz właściwość określającą ThemeDictionaryLocation lokalizację zasobów specyficznych dla motywu.

Poniższy przykład ustawia GenericDictionaryLocation właściwości i ThemeDictionaryLocation na SourceAssembly, aby określić, że zasoby ogólne i specyficzne dla motywu znajdują się w tym samym zestawie co kontrolka.

[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly,
           ResourceDictionaryLocation.SourceAssembly)]
<Assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, ResourceDictionaryLocation.SourceAssembly)>

Zobacz też