Architektura WPF
Ten temat zawiera przewodnik po hierarchii klas programu Windows Presentation Foundation (WPF). Obejmuje większość głównych podsystemów WPF i opisuje sposób ich interakcji. Zawiera także szczegóły dotyczące niektórych wyborów dokonanych przez architektów WPF.
System.Object
Podstawowy model programowania WPF jest udostępniany za pomocą kodu zarządzanego. Na początku fazy projektowania WPF było wiele debat na temat tego, gdzie należy wyciągnąć linię między zarządzanymi składnikami systemu a niezarządzanych. ClR udostępnia wiele funkcji, które sprawiają, że programowanie jest bardziej wydajne i niezawodne (w tym zarządzanie pamięcią, obsługa błędów, wspólny system typów itp.), ale są one kosztowne.
Główne składniki WPF przedstawiono na poniższym rysunku. Czerwone sekcje diagramu (PresentationFramework, PresentationCore i milcore) są głównymi fragmentami kodu WPF. Z nich tylko jeden jest składnikiem niezarządzanym — milcore. Milcore jest napisany w kodzie niezarządzanym, aby umożliwić ścisłą integrację z directX. Wszystkie wyświetlenia w WPF są realizowane za pośrednictwem silnika DirectX, co pozwala na wydajne renderowanie sprzętowe i programowe. WPF wymaga również dokładnej kontroli nad pamięcią i wykonywaniem. Aparat kompozycji w milcore jest bardzo wrażliwy na wydajność i wymaga rezygnacji z wielu zalet CLR, aby uzyskać wydajność.
Komunikacja między zarządzanymi i niezarządzanymi częściami WPF jest omówiona w dalszej części tego tematu. Pozostała część zarządzanego modelu programowania została opisana poniżej.
System.Threading.DispatcherObject
Większość obiektów w WPF pochodzi z DispatcherObject, która zapewnia podstawowe konstrukcje do obsługi współbieżności i wątkowości. WPF jest oparty na systemie obsługi komunikatów zaimplementowanym przez dyspozytora. Działa to podobnie jak znana pompa komunikatów Win32; w rzeczywistości dyspozytor WPF używa komunikatów User32 do wykonywania wywołań między wątkami.
Istnieją naprawdę dwa podstawowe pojęcia, które należy zrozumieć podczas omawiania współbieżności w WPF — dysponent i powiązanie wątków.
W fazie projektowania WPF celem było przejście do modelu jednowątkowego, ale bez przypisania wątkowego ("affinitized"). Koligacja wątków występuje, gdy składnik używa tożsamości wykonywanego wątku do przechowywania pewnego typu stanu. Najczęstszą formą tego jest użycie lokalnego schowka wątku (TLS) do przechowywania stanu. Koligacja wątków wymaga, aby każdy logiczny wątek wykonywania był własnością tylko jednego wątku fizycznego w systemie operacyjnym, co może stać się wymagające dla pamięci. Ostatecznie model wątkowości WPF został zsynchronizowany z istniejącym modelem wątkowym User32, który charakteryzuje się wykonywaniem pojedynczych wątków z przypisaniem wątku. Głównym powodem tego była interoperacyjność – systemy takie jak OLE 2.0, schowek i Internet Explorer wymagają jednowątkowej afinitetu (STA) do wykonania.
Biorąc pod uwagę, że masz obiekty z wątkami STA, potrzebujesz sposobu komunikowania się między wątkami i sprawdzania, czy jesteś w prawidłowym wątku. Tutaj leży rola dyspozytora. Dyspozytor to podstawowy system wysyłania komunikatów z wieloma kolejkami priorytetowymi. Przykłady komunikatów obejmują nieprzetworzone powiadomienia wejściowe (przeniesione myszą), funkcje struktury (układ) lub polecenia użytkownika (wykonaj tę metodę). Wyprowadzając z DispatcherObject, należy utworzyć obiekt CLR, który ma zachowanie STA, i otrzyma wskaźnik do dyspozytora w czasie tworzenia.
System.Windows.DependencyObject
Jedną z podstawowych filozofii architektonicznych używanych w tworzeniu WPF była preferencja właściwości nad metodami lub zdarzeniami. Właściwości są deklaratywne i umożliwiają łatwiejsze określanie intencji zamiast akcji. Obsługiwane są również systemy wyświetlania zawartości interfejsu użytkownika oparte na modelu lub danych. Ta filozofia miała zamierzony wpływ na tworzenie większej liczby właściwości, z którymi można powiązać, aby lepiej kontrolować zachowanie aplikacji.
Aby system był bardziej oparty na właściwościach, potrzebny był bogatszy system właściwości niż ten, który zapewnia CLR. Prostym przykładem tego bogactwa są powiadomienia o zmianie. Aby włączyć powiązanie dwukierunkowe, potrzebne są obie strony powiązania w celu obsługi powiadomienia o zmianie. Aby zachowanie było powiązane z wartościami właściwości, należy otrzymywać powiadomienia o zmianie wartości właściwości. Program Microsoft .NET Framework ma interfejs INotifyPropertyChange, który umożliwia obiektowi publikowanie powiadomień o zmianach, jednak jest to opcjonalne.
WPF zapewnia bogatszy system właściwości pochodzący z typu DependencyObject. System właściwości jest naprawdę systemem właściwości zależności, który śledzi zależności między wyrażeniami właściwości i automatycznie ponownie weryfikuje wartości właściwości, gdy zależności się zmieniają. Jeśli na przykład masz właściwość dziedziczącą (na przykład FontSize), system jest automatycznie aktualizowany, gdy właściwość zmienia się na obiekcie nadrzędnym elementu, który dziedziczy wartość.
Podstawą systemu właściwości WPF jest pojęcie wyrażenia właściwości. W tej pierwszej wersji platformy WPF system wyrażeń właściwości jest zamknięty, a wyrażenia są dostarczane w ramach struktury. Wyrażenia są przyczyną, dla których system właściwości nie ma powiązania danych, stylów ani dziedziczenia zakodowanego w kodzie, ale raczej zapewnianych przez późniejsze warstwy w ramach platformy.
System właściwości zapewnia również oszczędne przechowywanie wartości. Ponieważ obiekty mogą mieć dziesiątki (jeśli nie setki) właściwości, a większość wartości jest w ich domyślnym stanie (dziedziczone, ustawiane według stylów itp.), nie każde wystąpienie obiektu musi mieć pełną wagę każdej właściwości zdefiniowanej na nim.
Ostatnia nowa funkcja systemu właściwości jest pojęcie dołączonych właściwości. Elementy WPF są zbudowane na zasadzie kompozycji i ponownego wykorzystania składników. Często zdarza się, że niektóre zawierające elementy (na przykład element układu Grid) wymagają dodatkowych danych o elementach podrzędnych w celu kontrolowania ich zachowania, na przykład informacji o wierszach/kolumnach. Zamiast kojarzyć wszystkie te właściwości z każdym elementem, każdy obiekt może udostępniać definicje właściwości dla dowolnego innego obiektu. Jest to podobne do funkcji "expando" języka JavaScript.
System.Windows.Media.Visual
Po zdefiniowaniu systemu następnym krokiem jest rysowanie pikseli na ekranie. Klasa Visual służy do tworzenia drzewa obiektów wizualnych, z których każda opcjonalnie zawiera instrukcje rysowania i metadane dotyczące renderowania tych instrukcji (wycinanie, przekształcanie itp.). Visual jest zaprojektowana tak, aby była bardzo uproszczona i elastyczna, dlatego większość funkcji nie ma publicznej ekspozycji interfejsu API i intensywnie korzysta z chronionych funkcji wywołania zwrotnego.
Visual jest naprawdę punktem wejścia do systemu kompozycji WPF. Visual to punkt połączenia między tymi dwoma podsystemami, zarządzanymi interfejsami API i niezarządzanym milcore.
WPF wyświetla dane poprzez przechodzenie niezarządzanych struktur danych zarządzanych przez milcore. Te struktury, nazywane węzłami kompozycji, reprezentują hierarchiczne drzewo wyświetlania z instrukcjami renderowania w każdym węźle. To drzewo, przedstawione po prawej stronie rysunku poniżej, jest dostępne tylko za pośrednictwem protokołu obsługi komunikatów.
Podczas programowania WPF tworzy się elementy Visual i typy pochodne, które wewnętrznie komunikują się z drzewem kompozycji za pośrednictwem tego protokołu obsługi komunikatów. Każda Visual w WPF może utworzyć jeden, żaden lub kilka węzłów kompozycji.
W tym miejscu znajduje się bardzo ważny szczegół architektury — całe drzewo wizualizacji i instrukcje rysowania są buforowane. W kategoriach graficznych WPF używa zachowanego systemu renderowania. Umożliwia to systemowi przemalowanie przy wysokich szybkościach odświeżania bez blokowania systemu kompozycji na wywołaniach zwrotnych do kodu użytkownika. Pomaga to zapobiec pojawieniu się aplikacji, która nie odpowiada.
Innym ważnym szczegółem, który nie jest naprawdę zauważalny na diagramie, jest sposób, w jaki system rzeczywiście wykonuje kompozycję.
W User32 i GDI system działa w trybie natychmiastowego przycinania. Gdy składnik musi być renderowany, system ustanawia granice wycinków poza którymi składnik nie może dotykać pikseli, a następnie składnik jest proszony o malowanie pikseli w tym polu. Ten system działa bardzo dobrze w systemach ograniczonych pamięci, ponieważ gdy coś się zmieni, trzeba dotknąć tylko składnika, którego dotyczy problem, nie ma żadnych dwóch składników, które nigdy nie przyczyniają się do koloru pojedynczego piksela.
WPF używa modelu malowania nazwanego „algorytmem malarza”. Oznacza to, że zamiast przycinać każdy składnik, każdy składnik jest proszony o renderowanie z tyłu do przodu ekranu. Umożliwia to każdemu składnikowi malowanie na ekranie poprzedniego składnika. Zaletą tego modelu jest to, że można mieć złożone, częściowo przezroczyste kształty. Dzięki dzisiejszemu nowoczesnemu sprzętowi graficznemu ten model działa stosunkowo szybko (co nie miało miejsca w czasach tworzenia User32/GDI).
Jak wspomniano wcześniej, podstawową filozofią WPF jest przejście do bardziej deklaratywnego, "skoncentrowanego na właściwości" modelu programowania. W systemie wizualnym jest to wyświetlane w kilku interesujących miejscach.
Po pierwsze, jeśli myślisz o systemie graficznym w trybie zachowanym, jest to naprawdę odejście od imperatywnego modelu typu DrawLine/DrawLine do modelu zorientowanego na dane — nowy Line()/new Line(). Przejście do renderowania opartego na danych umożliwia wykonywanie złożonych operacji na instrukcjach rysowania, które mają być wyrażane przy użyciu właściwości. Typy pochodzące z Drawing są skutecznie modelem obiektów do renderowania.
Po drugie, jeśli ocenisz system animacji, zobaczysz, że jest prawie całkowicie deklaratywny. Zamiast wymagać od dewelopera obliczenia następnej lokalizacji lub następnego koloru, można wyrazić animacje jako zestaw właściwości w obiekcie animacji. Te animacje mogą następnie wyrazić intencję dewelopera lub projektanta (przenieść ten przycisk z tego miejsca do miejsca w ciągu 5 sekund), a system może określić najbardziej wydajny sposób, aby to osiągnąć.
System.Windows.UIElement
UIElement definiuje podstawowe podsystemy, w tym układ, dane wejściowe i zdarzenia.
Układ to podstawowa koncepcja w WPF. W wielu systemach istnieje stały zestaw modeli układu (język HTML obsługuje trzy modele układu: przepływowy, bezwzględny i tabelaryczny) lub żaden model układu (User32 w rzeczywistości obsługuje tylko pozycjonowanie bezwzględne). WPF zaczęło się od założenia, że deweloperzy i projektanci chcieli elastycznego, rozszerzalnego modelu układu, który może być oparty na wartościach właściwości, a nie logiki imperatywnej. Na poziomie UIElement zostaje wprowadzony podstawowy kontrakt układu — dwufazowy model z fazami Measure i Arrange.
Measure umożliwia składnikowi określenie, jak dużo miejsca chciałby zająć. Jest to oddzielna faza od Arrange, ponieważ istnieje wiele sytuacji, w których element rodzicielski będzie prosił dziecko o dokonanie pomiaru kilka razy w celu określenia optymalnej pozycji i rozmiaru. Fakt, że elementy nadrzędne proszą elementy podrzędne o dokonanie pomiaru, pokazuje kolejną kluczową filozofię WPF — dopasowanie rozmiaru do zawartości. Wszystkie kontrolki w WPF obsługują możliwość dopasowania rozmiaru do naturalnej wielkości ich zawartości. Dzięki temu lokalizacja jest znacznie łatwiejsza i umożliwia dynamiczny układ elementów w miarę zmiany rozmiaru elementów. Faza Arrange umożliwia elementowi nadrzędnemu pozycjonowanie i określenie końcowego rozmiaru każdego elementu podrzędnego.
Dużo czasu często poświęca się na mówienie o stronie wyjściowej WPF — Visual i powiązanych obiektów. Jednakże istnieje ogromna ilość innowacji również w zakresie danych wejściowych. Prawdopodobnie najbardziej fundamentalną zmianą w modelu wejściowym dla WPF jest spójny model, za pomocą którego zdarzenia wejściowe są kierowane przez system.
Dane wejściowe pochodzą z sygnału sterownika urządzenia w trybie jądra i są kierowane do prawidłowego procesu i wątku poprzez skomplikowany proces obejmujący jądro systemu Windows i User32. Gdy komunikat User32 odpowiadający danych wejściowych jest kierowany do WPF, jest konwertowany na nieprzetworzony komunikat wejściowy WPF i wysyłany do dyspozytora. Platforma WPF umożliwia konwertowanie nieprzetworzonych zdarzeń wejściowych na wiele rzeczywistych zdarzeń, umożliwiając zaimplementowanie takich funkcji jak "MouseEnter" na niskim poziomie systemu z gwarantowanym dostarczaniem.
Każde zdarzenie wejściowe jest konwertowane na co najmniej dwa zdarzenia — zdarzenie "wersja zapoznawcza" i rzeczywiste zdarzenie. Wszystkie zdarzenia w WPF mogą być kierowane przez drzewo elementów. Zdarzenia nazywane są "bąbelkowaniem", jeśli przemieszczają się od celu w górę drzewa do korzenia, i nazywane są "tunelem", jeśli zaczynają się od korzenia i przemieszczają w dół do celu. Tunel zdarzeń podglądowych danych wejściowych, umożliwiając dowolnemu elementowi w drzewie filtrowanie lub podjęcie działań na zdarzenie. Następnie zdarzenia zwykłe (inne niż wersja zapoznawcza) propagują się jako bąbelki od obiektu docelowego do korzenia.
Ten podział między fazą tunelu i bąbelka sprawia, że implementacja funkcji, takich jak akceleratory klawiatury, działają w spójny sposób w świecie złożonym. W usłudze User32 należy zaimplementować akceleratory klawiatury, mając jedną globalną tabelę zawierającą wszystkie akceleratory, które chcesz obsługiwać (mapowanie Ctrl+N na "Nowy"). W dyspozytorze dla swojej aplikacji wywołasz TranslateAccelerator, który przechwytuje komunikaty wejściowe w usłudze User32 i określi, czy którykolwiek z nich odpowiada zarejestrowanemu akceleratorowi. W WPF nie zadziałałoby to, ponieważ system jest w pełni "komponowalny" — każdy element może obsługiwać i używać dowolnego akceleratora klawiatury. Posiadanie tego dwufazowego modelu dla danych wejściowych umożliwia składnikom zaimplementowanie własnego "TranslateAccelerator".
Aby wykonać ten krok dalej, UIElement wprowadza również pojęcie CommandBindings. System poleceń WPF umożliwia deweloperom definiowanie funkcji pod względem punktu końcowego polecenia — coś, co implementuje ICommand. Powiązania poleceń umożliwiają elementowi definiowanie mapowania między gestem wejściowym (Ctrl+N) i poleceniem (Nowy). Zarówno gesty wejściowe, jak i definicje poleceń są rozszerzalne i mogą być połączone razem w czasie użycia. Dzięki temu użytkownik końcowy może na przykład dostosować powiązania kluczy, których chcą używać w aplikacji.
Do tego momentu w temacie "podstawowe" funkcje WPF — funkcje zaimplementowane w zestawie PresentationCore były głównym celem. Podczas tworzenia WPF czyste rozdzielenie między elementami podstawowymi (takimi jak umowa dotycząca układu z Measure i Arrange) i elementami struktury (takimi jak implementacja określonego układu, takiego jak Grid), było pożądanym wynikiem. Celem było zapewnienie niskiego poziomu rozszerzalności w stosie, który umożliwi zewnętrznym deweloperom tworzenie własnych struktur w razie potrzeby.
System.Windows.FrameworkElement
FrameworkElement można przyjrzeć się na dwa różne sposoby. Wprowadza zestaw zasad i dostosowań w podsystemach wprowadzonych w niższych warstwach WPF. Wprowadza również zestaw nowych podsystemów.
Podstawowe zasady wprowadzone przez FrameworkElement są związane z układem aplikacji. FrameworkElement opiera się na podstawowej umowie dotyczącej układu wprowadzonej przez UIElement i dodaje pojęcie gniazda układu, które ułatwia autorom układów zapewnienie spójności semantycznej opartej na właściwościach. Właściwości takie jak HorizontalAlignment, VerticalAlignment, MinWidthi Margin (aby wymienić kilka) dają wszystkim składnikom pochodzącym z FrameworkElement spójne zachowanie wewnątrz kontenerów układu.
FrameworkElement zapewnia również łatwiejsze narażenie interfejsu API na wiele funkcji znajdujących się w podstawowych warstwach WPF. Na przykład FrameworkElement zapewnia bezpośredni dostęp do animacji za pośrednictwem metody BeginStoryboard. Storyboard umożliwia pisanie skryptów wielu animacji dla zestawu właściwości.
Dwa najważniejsze elementy, które FrameworkElement wprowadzono, to powiązanie danych i style.
Podsystem powiązania danych w WPF powinien być stosunkowo znany każdemu, kto używał windows Forms lub ASP.NET do tworzenia interfejsu użytkownika aplikacji. W każdym z tych systemów istnieje prosty sposób wyrażenia, że chcesz, aby co najmniej jedna właściwości z danego elementu była powiązana z fragmentem danych. WPF ma pełną obsługę wiązania właściwości, przekształcenia i wiązania listy.
Jedną z najbardziej interesujących funkcji powiązania danych w WPF jest wprowadzenie szablonów danych. Szablony danych umożliwiają deklaratywne określenie sposobu wizualizacji elementu danych. Zamiast tworzyć niestandardowy interfejs użytkownika, który może być powiązany z danymi, możesz odwrócić ten proces i pozwolić, aby to dane decydowały o prezentacji, która zostanie utworzona.
Stylizacja jest rzeczywiście lekką formą powiązania danych. Za pomocą stylów można powiązać zestaw właściwości z definicji udostępnionej z co najmniej jednym wystąpieniem elementu. Style są stosowane do elementu za pomocą jawnego odwołania (przez ustawienie właściwości Style) lub niejawnie przez skojarzenie stylu z typem CLR elementu.
System.Windows.Controls.Control
Najważniejszą funkcją kontrolki jest tworzenie szablonów. Jeśli zastanowisz się nad systemem kompozycji WPF jako systemem renderowania w trybie zachowanym, tworzenie szablonów umożliwia kontrolce opisanie jej renderowania w sposób sparametryzowany, deklaratywny. ControlTemplate to naprawdę nic więcej niż skrypt umożliwiający utworzenie zestawu elementów podrzędnych z powiązaniami z właściwościami oferowanymi przez kontrolkę.
Control udostępnia zestaw właściwości zapasów, Foreground, Background, Padding, aby wymienić kilka, których autorzy szablonów mogą następnie użyć do dostosowania wyświetlania kontrolki. Implementacja kontrolki zapewnia model danych i model interakcji. Model interakcji definiuje zestaw poleceń (na przykład Zamknij dla okna) i powiązania z gestami wejściowymi (na przykład kliknięcie czerwonego X w górnym rogu okna). Model danych udostępnia zestaw właściwości, aby dostosować model interakcji lub dostosować ekran (określony przez szablon).
Ten podział między model danych (właściwości), model interakcji (polecenia i zdarzenia) oraz model wyświetlania (szablony) umożliwia pełne dostosowanie wyglądu i zachowania kontrolki.
Typowym aspektem modelu danych kontrolek jest model zawartości. Jeśli spojrzysz na kontrolkę podobną do Button, zobaczysz, że ma ona właściwość o nazwie "Content" typu Object. W formularzach Windows Forms i ASP.NET ta właściwość zazwyczaj jest ciągiem — jednak ogranicza typ zawartości, którą można umieścić w przycisku. Zawartość przycisku może być prostym ciągiem, obiektem danych złożonym lub całym drzewem elementów. W przypadku obiektu danych szablon danych jest używany do konstruowania wyświetlacza.
Streszczenie
Platforma WPF została zaprojektowana w celu umożliwienia tworzenia dynamicznych systemów prezentacji opartych na danych. Każda część systemu została zaprojektowana do tworzenia obiektów za pomocą zestawów właściwości, które odpowiadają za zachowanie. Powiązanie danych jest podstawową częścią systemu i jest zintegrowane w każdej warstwie.
Tradycyjne aplikacje tworzą ekran, a następnie wiążą się z niektórymi danymi. W WPF wszystko o kontrolce, każdy aspekt wyświetlania, jest generowany przez jakiś typ powiązania danych. Tekst znaleziony wewnątrz przycisku jest wyświetlany przez utworzenie złożonej kontrolki wewnątrz przycisku i powiązanie jej wyświetlania z właściwością zawartości przycisku.
Kiedy zaczynasz tworzyć aplikacje oparte na WPF, powinno to być bardzo znajome. Można ustawiać właściwości, używać obiektów i wiązać dane w sposób bardzo podobny do tego, jak to robisz za pomocą formularzy Windows lub ASP.NET. Dzięki dokładniejszej badaniu architektury platformy WPF przekonasz się, że istnieje możliwość tworzenia znacznie bogatszych aplikacji, które zasadniczo traktują dane jako podstawowy sterownik aplikacji.
Zobacz też
- Visual
- UIElement
- ICommand
- FrameworkElement
- DispatcherObject
- CommandBinding
- Control
- omówienie powiązania danych
- Układ
- Animacja — omówienie
.NET Desktop feedback