Omówienie tworzenia kontrolek
Rozszerzalność modelu Windows Presentation Foundation (WPF) znacznie zmniejsza potrzebę tworzenia nowej kontrolki. Jednak w niektórych przypadkach nadal może być konieczne utworzenie kontrolki niestandardowej. W tym temacie omówiono funkcje, które minimalizują potrzebę tworzenia kontrolki niestandardowej oraz różne modele tworzenia kontrolek w Windows Presentation Foundation (WPF). W tym temacie pokazano również sposób tworzenia nowej kontrolki.
Alternatywy dla pisania nowej kontrolki
W przeszłości, 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 te wstępnie zdefiniowane parametry, musisz utworzyć nową kontrolkę, zazwyczaj dziedzicząc z istniejącej kontrolki i zastępując metodę odpowiedzialną za rysowanie kontrolki. Mimo że nadal jest to opcja, WPF umożliwia dostosowywanie istniejących kontrolek przy użyciu jego modelu zawartości sformatowanych, stylów, szablonów i wyzwalaczy. Na poniższej liście przedstawiono przykłady użycia tych funkcji do tworzenia niestandardowych i spójnych funkcji bez konieczności tworzenia nowej kontrolki.
Zawartość bogata. Wiele standardowych kontrolek WPF obsługuje zawartość bogatą. Na przykład właściwość zawartości obiektu jest ButtonObjecttypu , więc teoretycznie wszystko może być wyświetlane w obiekcie Button. Aby przycisk wyświetlał obraz i tekst, możesz dodać obraz i element TextBlock do obiektu i StackPanel przypisać element StackPanel do właściwości Content . Ponieważ kontrolki mogą wyświetlać elementy wizualne WPF i dowolne dane, nie ma potrzeby tworzenia nowej kontrolki ani modyfikowania istniejącej kontrolki w celu obsługi złożonej wizualizacji. Aby uzyskać więcej informacji na temat modelu zawartości i innych Button modeli zawartości 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 TextBlock , aby wszystkie kontrolki mają mieć 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 , co dodajesz do aplikacji, będzie miał taki sam wygląd.
Szablony danych. Kontrolka DataTemplate umożliwia dostosowanie sposobu wyświetlania danych w kontrolce. Na przykład można DataTemplate użyć do określenia sposobu wyświetlania danych w .ListBox Aby uzyskać przykład tego, zobacz Data Templating Overview (Omówienie szablonów danych). Oprócz dostosowywania wyglądu danych DataTemplate element może zawierać elementy interfejsu użytkownika, co zapewnia dużą elastyczność w niestandardowych interfejsach użytkownika. Na przykład przy użyciu elementu DataTemplatemożna utworzyć element , w ComboBox którym każdy element zawiera pole wyboru.
Szablony kontrolek. Wiele kontrolek w WPF ControlTemplate używa obiektu do definiowania struktury i wyglądu kontrolki, która oddziela wygląd kontrolki od jej funkcjonalności. Możesz znacząco zmienić wygląd kontrolki, ponownie definiując jej .ControlTemplate Załóżmy na przykład, że potrzebujesz kontrolki, która wygląda jak stoplight. Ta kontrolka ma prosty interfejs użytkownika i funkcje. Kontrolka to trzy okręgi, z których tylko jeden może być oświetlony na raz. Po pewnym odbicia można RadioButton zdać sobie sprawę, że element oferuje funkcjonalność tylko jednego wybranego na raz, RadioButton ale domyślny wygląd kontrolki nie wygląda jak światła na stope. Ponieważ szablon RadioButton kontrolki jest używany do definiowania jego wyglądu, ControlTemplate można łatwo ponownie zdefiniować kontrolkę, aby dopasować ją do wymagań kontrolki, i użyć przycisków radiowych, aby zatrzymać.
Uwaga
Mimo że w RadioButton tym przykładzie można użyć DataTemplate, a DataTemplate nie jest wystarczająca. Definiuje DataTemplate wygląd zawartości kontrolki. W przypadku , zawartość jest RadioButtonwyświetlana po prawej stronie okręgu, co wskazuje, czy element RadioButton jest wybrany. W tym przykładzie przycisk radiowy musi być okręgiem, który może "osłaniać". Ponieważ wymaganie wyglądu dla stoplight jest tak różne niż domyślny wygląd RadioButton, należy ponownie zdefiniować .ControlTemplate Ogólnie rzecz biorąc, DataTemplate kontrolka służy do definiowania zawartości (lub danych) kontrolki, ControlTemplate a kontrolka służy do definiowania struktury kontrolki.
Wyzwalaczy. Kontrolka Trigger umożliwia dynamiczną zmianę wyglądu i zachowania kontrolki bez tworzenia nowej kontrolki. Załóżmy na przykład, że masz ListBoxListBox wiele kontrolek w aplikacji i chcesz, aby elementy w każdej z nich były pogrubione i czerwone po wybraniu. Pierwszym instynkcją ListBoxOnSelectionChanged może być utworzenie klasy, która dziedziczy z metody i przesłania ją w celu zmiany wyglądu wybranego elementu, ListBoxItem ale lepszym rozwiązaniem jest dodanie wyzwalacza do stylu elementu, który zmienia wygląd wybranego elementu. Wyzwalacz umożliwia zmianę wartości właściwości lub podjęcia akcji na podstawie wartości właściwości. Element EventTrigger umożliwia akcje w przypadku wystąpienia zdarzenia.
Aby uzyskać więcej informacji na temat stylów, szablonów i wyzwalaczy, zobacz Styleing and Templating (Style i szablony).
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 metody omówionej w tej sekcji, aby zmienić wygląd istniejącej kontrolki.
Modele tworzenia kontrolek
Bogaty model zawartości, style, szablony i wyzwalacze minimalizują potrzebę tworzenia nowej kontrolki. Jeśli jednak musisz utworzyć nową kontrolkę, ważne jest, aby zrozumieć różne modele tworzenia kontrolek w WPF. WPF udostępnia trzy ogólne modele tworzenia kontrolki, z których każdy zapewnia inny zestaw funkcji i poziom elastyczności. Klasy bazowe dla trzech modeli to UserControl, Controli FrameworkElement.
Wyprowadzanie z UserControl
Najprostszym sposobem utworzenia kontrolki w WPF jest wyprowadzenia z .UserControl Podczas tworzenia kontrolki dziedziczcy UserControlUserControlz programu dodaje się istniejące składniki do pliku , nazwij składniki i procedury obsługi zdarzeń odwoływać się w WPF.
Jeśli jest poprawnie sbudowaną, obiekt UserControl może korzystać z zalet rozbudowanych zawartości, stylów i wyzwalaczy. Jeśli jednak kontrolka dziedziczy z kontrolki UserControl, DataTemplate osoby, które jej używają, nie będą mogły użyć kontrolki lub ControlTemplate w celu dostosowania jej wyglądu. Aby utworzyć niestandardową kontrolkę Control , która obsługuje szablony, konieczne jest wyprowadzenie z klasy lub jednej z jej klas pochodnych ( UserControlinnych niż ).
Korzyści wynikające z kontroli użytkownika
Rozważ wyprowadzenie z , UserControl jeśli mają zastosowanie wszystkie następujące elementy:
Chcesz utworzyć kontrolę w sposób podobny do sposobu tworzenia aplikacji.
Kontrolka składa się tylko z istniejących składników.
Nie trzeba obsługiwać złożonych dostosowań.
Wyprowadzanie z kontrolki
Wyprowadzanie z klasy Control jest modelem używanym przez większość istniejących kontrolek WPF. Tworząc kontrolkę dziedziczącą z klasy Control , definiujesz jej wygląd przy użyciu szablonów. W ten sposób oddzielisz logikę operacyjną od reprezentacji wizualnej. Możesz również zapewnić rozdzielenie interfejsu użytkownika i logiki, używając poleceń i powiązań zamiast zdarzeń i unikając odwoływać się do elementów w , jeśli ControlTemplate to możliwe. Jeśli interfejs użytkownika i logika kontrolki są prawidłowo oddzielone, ControlTemplate użytkownik kontrolki może ponownie zdefiniować kontrolki, aby dostosować jej wygląd. Chociaż tworzenie niestandardowego obiektu Control nie jest tak proste, jak tworzenie UserControlobiektu , niestandardowe Control zapewnia większą elastyczność.
Zalety wyprowadzania z kontroli
Rozważ wyprowadzenie z Control klasy zamiast używania klasy UserControl , jeśli ma zastosowanie dowolne z następujących:
Chcesz, aby wygląd kontrolki można było dostosowywać za pośrednictwem .ControlTemplate
Chcesz, aby kontrolka obsługiła różne motywy.
Wyprowadzanie z elementu FrameworkElement
Kontrolki, które pochodzą z UserControlControl istniejących elementów lub polegają na nich. W wielu scenariuszach jest to dopuszczalne rozwiązanie, ponieważ każdy obiekt FrameworkElement dziedziczący z obiektu może być w obiekcie ControlTemplate. Jednak czasami wygląd kontrolki wymaga więcej niż funkcjonalność prostej kompozycji elementów. W przypadku tych scenariuszy właściwe jest bazowanie składnika FrameworkElement na.
Istnieją dwie standardowe metody tworzenia składników FrameworkElementopartych na elemencie: renderowanie bezpośrednie i niestandardowa kompozycja elementów. Renderowanie bezpośrednie polega na zastępowaniu metody OnRenderFrameworkElement i dostarczaniu operacji DrawingContext , które jawnie definiują wizualizacje składników. Jest to metoda używana przez metody i ImageBorder. Kompozycja elementów niestandardowych polega na używaniu obiektów typu Visual do redagowania wyglądu składnika. Aby uzyskać przykład, zobacz Using DrawingVisual Objects (Używanie obiektów DrawingVisual). Track jest przykładem kontrolki w WPF, która używa niestandardowej kompozycji elementów. W tej samej kontrolce można również mieszać renderowanie bezpośrednie i kompozycję elementów niestandardowych.
Zalety wyprowadzania z elementu FrameworkElement
Rozważ wyprowadzenie z , FrameworkElement jeśli ma zastosowanie dowolne z następujących:
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 redagować istniejące elementy w nowatorski sposób, który wykracza poza to, co jest możliwe, za pomocą i UserControlControl.
Podstawy tworzenia kontrolek
Jak wspomniano wcześniej, jedną z najbardziej zaawansowanych funkcji platformy WPF jest możliwość wykraczania poza ustawianie podstawowych właściwości kontrolki w celu zmiany jej wyglądu i zachowania, ale nadal nie trzeba tworzyć niestandardowej kontrolki. Funkcje stylu, 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 stosować, niezależnie od modelu służącego do tworzenia kontrolki niestandardowej, dzięki czemu użytkownicy kontrolki niestandardowej mogą używać tych funkcji w taki sam sposób, jak w przypadku kontrolki dołączonej do WPF.
Korzystanie z właściwości zależności
Gdy 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ługił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
ValuePropertynazwie jakopublicstaticreadonlypole.Zarejestruj nazwę właściwości w systemie właściwości, wywołując DependencyProperty.Register, 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 dla właściwości . Metadane zawierają wartość domyślną właściwości, a CoerceValueCallback i PropertyChangedCallback.
Zdefiniuj właściwość otoki
ValueCLR o nazwie , która jest taka sama jak nazwa używana do rejestrowania właściwości zależności przez zaimplementowaniegetwłaściwości isetaccessors. Należy pamiętać, żegetaccessors isetwywołują tylko GetValue odpowiednio SetValue i . Zaleca się, aby elementy dostępu właściwości zależności nie zawierały dodatkowej logiki, ponieważ klienci i WPF GetValue mogą pominąć te elementy, wywołując i SetValue bezpośrednio. Na przykład gdy właściwość jest powiązana ze źródłem danych,setnie jest wywoływana accessor właściwości. Zamiast dodawać dodatkową logikę do funkcji dostępu get i set, ValidateValueCallbackużyj delegatów , CoerceValueCallbacki , aby PropertyChangedCallback odpowiedzieć na zmiany lub sprawdzić wartość po jej zmianie. Aby uzyskać więcej informacji na temat tych wywołań zwrotnych, zobacz Dependency Property Callbacks and Validation (Wywołania zwrotne właściwości zależności i walidacja).Zdefiniuj metodę dla metody o CoerceValueCallback nazwie
CoerceValue.CoerceValueZapewnia, że wartośćValuejest większa lub równa iMinValuemniejsza niż lub równa .MaxValueZdefiniuj metodę dla metody PropertyChangedCallbacko nazwie
OnValueChanged.OnValueChangedTworzy obiekt RoutedPropertyChangedEventArgs<T> i przygotowuje się do podniesienia trasowaneValueChangedzdarzenie. Zdarzenia trasowane 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.
Korzystanie ze zdarzeń trasowych
Podobnie jak właściwości zależności rozszerzają zależność właściwości CLR o dodatkowe funkcje, zdarzenia trasowane rozszerzają pojmowanie standardowych zdarzeń CLR. Podczas tworzenia nowej kontrolki WPF dobrym rozwiązaniem jest również zaimplementowanie zdarzenia jako zdarzenia trasowane, ponieważ zdarzenie trasowane obsługuje następujące zachowanie:
Zdarzenia mogą być obsługiwane na elementach nadrzędnych wielu kontrolek. Jeśli zdarzenie jest zdarzeniem propagacji, jeden element nadrzędny w drzewie elementów może subskrybować zdarzenie. Następnie autorzy aplikacji mogą reagować na zdarzenia wielu kontrolek za pomocą jednej procedury obsługi. Jeśli na ListBox przykład kontrolka jest częścią każdego elementu w kontrolce ( DataTemplateponieważ jest uwzględniona w kontrolce ), deweloper aplikacji może zdefiniować program obsługi zdarzeń dla zdarzenia kontrolki w kontrolce ListBox. Za każdym razem, gdy zdarzenie wystąpi na dowolnej kontrolce, wywoływana jest procedura obsługi zdarzeń.
Zdarzenia trasowane mogą być używane w EventSetter, co umożliwia deweloperom aplikacji określenie procedury obsługi zdarzenia w stylu.
Zdarzenia trasowane mogą być używane w EventTrigger, co jest przydatne do animowania właściwości przy użyciu języka XAML. Aby uzyskać więcej informacji, zobacz Przegląd animacji.
W poniższym przykładzie zdefiniowano zdarzenie trasowane, wykonując następujące czynności:
Zdefiniuj RoutedEvent identyfikator o
ValueChangedEventnazwie jakopublicstaticreadonlypole.Zarejestruj trasowane zdarzenie, wywołując EventManager.RegisterRoutedEvent metodę . W przykładzie określono następujące informacje podczas wywołania :RegisterRoutedEvent
Nazwa zdarzenia to
ValueChanged.Strategia routingu Bubbleto , co oznacza, że program obsługi zdarzeń w źródle (obiekcie, który wywołuje zdarzenie) jest wywoływany jako pierwszy, a następnie procedury obsługi zdarzeń w elementach nadrzędnych źródła są wywoływane kolejno, począwszy od procedury obsługi zdarzeń na najbliższym elemencie nadrzędnym.
Typ procedury obsługi zdarzeń to RoutedPropertyChangedEventHandler<T>, skonstruowany przy użyciu Decimal typu .
Typ zdarzenia, który jest właścicielem, to
NumericUpDown.
Zadeklaruj zdarzenie publiczne o nazwie
ValueChangedi zawiera deklaracje dostępu do zdarzeń. Przykład wywołuje w AddHandler deklaracjiaddaccessor i RemoveHandler w deklaracjiremoveaccessor użycie usług zdarzeń WPF.Utwórz chronioną, wirtualną metodę o
OnValueChangednazwie , która wywołujeValueChangedzdarzenie.
/// <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ń tras) i Create a Custom Routed Event (Tworzenie niestandardowego zdarzenia trasowego).
Używanie powiązania
Aby oddzielić interfejs użytkownika kontrolki od jej logiki, rozważ użycie powiązania danych. Jest to szczególnie ważne, jeśli definiujesz wygląd kontrolki przy użyciu .ControlTemplate Jeśli używasz powiązania danych, możesz wyeliminować konieczność odwołania się do określonych części interfejsu użytkownika z kodu. Dobrym pomysłem ControlTemplateControlTemplateControlTemplate jest unikanie odwoływania się do elementów, które znajdują się w elemencie , ponieważ gdy kod odwołuje się do elementów, które znajdują się w elemencie , a element jest zmieniany, przywołyowany element musi zostać uwzględniony w nowym elemencie .ControlTemplate
Poniższy przykład aktualizuje kontrolkę TextBlockNumericUpDown , przypisując jej 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 w celu wykonania tego samego zadania.
<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 powiązania danych, zobacz Omówienie powiązań danych.
Projektowanie dla projektantów
Aby uzyskać obsługę niestandardowych kontrolek WPF w projektancie WPF dla platformy Visual Studio (na przykład edytowanie właściwości za pomocą okno Właściwości), postępuj zgodnie z tymi wytycznymi. Aby uzyskać więcej informacji na temat tworzenia aplikacji dla projektanta WPF, zobacz Design XAML in Visual Studio (Projektowanie XAML w programie Visual Studio).
Właściwości zależności
Pamiętaj, aby zaimplementować clr get i set accessors zgodnie z wcześniejszym opisem w "Use Dependency Properties" (Używanie właściwości zależności). Projektanci mogą używać otoki do wykrywania obecności właściwości zależności, ale tak jak WPF i klienci kontrolki, nie są zobowiązani do wywołania dostępu podczas uzyskiwania lub ustawiania właściwości.
Właściwości dołączone
Dołączone właściwości kontrolek niestandardowych należy zaimplementować przy użyciu następujących wytycznych:
Ma formularz
publicDependencyPropertyreadonlystaticPropertyNameProperty, który został utworzone przy użyciu RegisterAttached metody . Nazwa właściwości, która jest przekazywana do, musi RegisterAttached odpowiadać właściwości PropertyName.Zaim implementuj parę metod
publicstaticCLR o nazwachSetPropertyName iGetPropertyName. Obie metody powinny akceptować klasę pochodną klasy DependencyProperty jako swój pierwszy argument. MetodaSetPropertyName akceptuje również argument, którego typ odpowiada zarejestrowanemu typowi danych dla właściwości. MetodaGetPropertyName powinna zwrócić wartość tego samego typu. Jeśli brakujeSetmetody PropertyName, właściwość zostanie oznaczona jako tylko do odczytu.SetWłaściwości PropertyName iGetPropertyName muszą być kierowane bezpośrednio GetValue do metod i SetValue odpowiednio w docelowym obiekcie zależności. Projektanci mogą uzyskać dostęp do dołączonej właściwości, wywołując metodę za pośrednictwem otoki metody lub wywołując bezpośrednio docelowy obiekt zależności.
Aby uzyskać więcej informacji na temat dołączonych właściwości, zobacz Attached Properties Overview (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ć ją 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 stosowanej metody. Istnieje jednak jedna różnica, na która warto zwrócić uwagę. Jeśli umieścisz kontrolkę w tym samym zestawie co aplikacja, możesz dodać zasoby globalne do pliku App.xaml. Jednak zestaw, który zawiera tylko kontrolki, nie ma skojarzonego Application z nim obiektu, więc plik App.xaml jest niedostępny.
Gdy aplikacja szuka zasobu, szuka trzech poziomów w następującej kolejności:
Poziom elementu.
System rozpoczyna się od elementu , który odwołuje się do zasobu, a następnie wyszukuje zasoby elementu nadrzędnego logicznego itd., aż do momentu, gdy zostanie osiągnięty element główny.
Poziom aplikacji.
Zasoby zdefiniowane przez Application obiekt .
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ć takie elementy jak Nacją.NormalColor.xaml, Bydgoska.NormalColor.xaml i tak dalej. Możesz również mieć plik o nazwie generic.xaml. Gdy system szuka zasobu na poziomie motywów, najpierw szuka go w pliku specyficznym dla motywu, a następnie szuka go w pliku generic.xaml.
Gdy kontrolka znajduje się w zestawie oddzielonym od aplikacji, należy 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 definiować na poziomie elementu, tworząc niestandardowy słownik zasobów i scalając go ze słownikiem zasobów kontrolki. Korzystając z tej metody, możesz nazwać plik zasobów w imieniu wszystkich elementów i może on być 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 plik zasobów LinearGradientBrush 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 zdefiniowanym słowniku należy scalić go ze słownikiem zasobów kontrolki. Można to zrobić przy użyciu kodu LUB XAML.
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 udostępnione słowniki zasobów 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 wynikową klasę ResourceDictionary.
Poniższy przykład tworzy klasę, która zwraca udostępniony klasę ResourceDictionary.
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;
}
W poniższym przykładzie zasób udostępniony jest scalony z zasobami kontrolki niestandardowej w konstruktorze kontrolki, zanim wywoła .InitializeComponent Ponieważ obiekt SharedDictionaryManager.SharedDictionary jest właściwością statyczną, obiekt ResourceDictionary jest tworzony tylko raz. Ponieważ słownik zasobów został scalony przed wywołaniu InitializeComponent , zasoby są dostępne dla kontrolki w 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 Windows motywów. Jako autor kontrolki możesz zdefiniować zasób dla określonego motywu, aby zmienić wygląd kontrolki w zależności od tego, który motyw jest w użyciu. Button Na przykład wygląd a w motywie klasycznym usługi Windows (motyw domyślny dla programu Windows 2000) Button różni się od wyglądu w motywie Windows Pomiń (motyw domyślny dla Windows XP), ButtonControlTemplate ponieważ dla każdego motywu jest używany inny motyw.
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 | Windows motywu |
|---|---|
Classic.xaml |
Klasyczne Windows 9x/2000 na Windows XP |
Luna.NormalColor.xaml |
Domyślny motyw niebieski na Windows XP |
Luna.Homestead.xaml |
Motyw na Windows XP |
Luna.Metallic.xaml |
Motyw Silver na Windows XP |
Royale.NormalColor.xaml |
Motyw domyślny w 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 , czy zasób. Jeśli zasób nie jest zdefiniowany Classic.xamlw pliku odpowiadającym bieżącemu motywowi lub w pliku , 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. Mimo generic.xaml że nie odpowiada on konkretnemu Windows tematu, nadal jest to słownik na poziomie motywu.
Przykładowa kontrolka niestandardowa NumericUpDown NumericUpDown w języku C# lub Visual Basic z obsługą automatyzacji motywu i interfejsu użytkownika zawiera dwa słowniki zasobów dla tej kontrolki: jeden znajduje się w pliku generic.xaml, a drugi to w pliku Xaml.NormalColor.
Jeśli umieścisz w ControlTemplate dowolnym pliku słownika zasobów specyficzny dla motywu, OverrideMetadata(Type, PropertyMetadata)DefaultStyleKeymusisz utworzyć konstruktor statyczny dla kontrolki i wywołać metodę w pliku , 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ć jako ComponentResourceKey klucza . W poniższym przykładzie zdefiniowano 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 jako ComponentResourceKey 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 hostingu musi wiedzieć, że zestaw zawiera zasoby specyficzne dla kontroli. Można to zrobić, dodając do ThemeInfoAttribute zestawu zawierającego kontrolkę . Obiekt ThemeInfoAttribute ma właściwość GenericDictionaryLocation , która określa lokalizację zasobów ogólnych, oraz ThemeDictionaryLocation właściwość określającą lokalizację zasobów specyficznych dla motywu.
Poniższy przykład ustawia właściwości GenericDictionaryLocation i ThemeDictionaryLocationSourceAssemblyna , 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)>