Architektura WPF
Ten temat zawiera przewodnik po hierarchii klas Windows Presentation Foundation (WPF). Obejmuje większość głównych podsystemów WPF i opisuje sposób ich interakcji. Wyszczególnia również niektóre wybory dokonane przez architektów WPF.
System.Object
Podstawowy model programowania WPF jest udostępniany za pośrednictwem 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 produktywne i niezawodne (w tym zarządzanie pamięcią, obsługa błędów, wspólny system typów itp.), ale kosztują.
Główne składniki WPF przedstawiono na poniższej ilustracji. Czerwone sekcje diagramu (PresentationFramework, PresentationCore i milcore) to główne części 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 programem DirectX. Wszystkie wyświetlacze w WPF są wykonywane za pośrednictwem aparatu DirectX, co pozwala na wydajne renderowanie sprzętu i oprogramowania. WPF wymaga również dokładnej kontroli nad pamięcią i wykonywaniem. Aparat kompozycji w milcore jest niezwykle wrażliwy na wydajność i wymaga rezygnacji z wielu zalet CLR w celu uzyskania wydajności.

Komunikacja między częściami zarządzanych i niezarządzanych WPF została 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 DispatcherObjectklasy , która zapewnia podstawowe konstrukcje do obsługi współbieżności i wątkowości. Platforma WPF jest oparta na systemie obsługi komunikatów zaimplementowanym przez dyspozytor. To działa podobnie jak znana pompa komunikatów Win32; w rzeczywistości dyspozytor WPF używa komunikatów User32 do wykonywania wywołań międzywątkowy.
Podczas omawiania współbieżności w WPF są naprawdę dwa podstawowe pojęcia — dyspozytor i koligacja wątków.
W fazie projektowania WPF celem było przejście do pojedynczego wątku wykonywania, ale niewątkowy model "affinitized". Koligacja wątku ma miejsce, gdy składnik używa tożsamości wątku wykonującego do przechowywania określonego typu stanu. Najczęstszą formą jest użycie magazynu lokalnego 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 wymagać dużej ilości pamięci. W końcu model wątkowości WPF był zsynchronizowany z istniejącym modelem wątkowości User32 z koligacją wątkowości pojedynczego wątku. Głównym powodem tego było współdziałanie systemów, takich jak OLE 2.0, schowek i program Internet Explorer, wymagają wykonania koligacji pojedynczego wątku (STA).
Biorąc pod uwagę, że masz obiekty z wątkowaniem STA, potrzebujesz sposobu komunikowania się między wątkami i sprawdzania, czy jesteś w prawidłowym wątku. W niniejszym dokumencie leży rola dyspozytora. Dyspozytor to podstawowy system wysyłania komunikatów z wieloma kolejkami priorytetowymi. Przykładami komunikatów są nieprzetworzone powiadomienia wejściowe (przeniesione myszą), funkcje struktury (układ) lub polecenia użytkownika (wykonaj tę metodę). Wyprowadzając element z DispatcherObjectklasy , należy utworzyć obiekt CLR, który ma zachowanie STA i będzie miał 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ługuje to również oparty na modelu system oparty na danych do wyświetlania zawartości interfejsu użytkownika. 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 mieć więcej systemów opartych na właściwościach, bardziej rozbudowany system właściwości niż to, co zapewnia CLR, było potrzebne. Prostym przykładem tego bogactwa są powiadomienia o zmianach. 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 powiadomić o zmianie wartości właściwości. Microsoft .NET Framework ma interfejs INotifyPropertyChange, który umożliwia obiektowi publikowanie powiadomień o zmianie, jednak jest to opcjonalne.
WPF zapewnia bogatszy system właściwości pochodzący z DependencyObject typu. System właściwości jest w pełni "zależności" system właściwości, w ramach którego śledzi zależności między wyrażeniami właściwości i automatycznie rewalidates wartości właściwości, gdy zależności się zmieniają. Jeśli na przykład masz właściwość dziedziczą (na FontSizeprzykład ), system zostanie automatycznie zaktualizowany, jeśli właściwość zmieni się na obiekcie nadrzędnym elementu, który dziedziczy wartość.
Podstawą systemu właściwości WPF jest koncepcja wyrażenia właściwości. W tej pierwszej wersji platformy WPF system wyrażeń właściwości jest zamknięty, a wszystkie wyrażenia są dostarczane w ramach struktury. Wyrażenia są przyczyną, dla których system właściwości nie ma trwale zakodowanych powiązań danych, stylów ani dziedziczenia, ale jest dostarczany przez późniejsze warstwy w ramach struktury.
System właściwości udostępnia również rozrzedliwą pamięć o wartościach właściwości. Ponieważ obiekty mogą mieć dziesiątki (jeśli nie setki) właściwości, a większość wartości jest w stanie domyślnym (dziedziczone, ustawiane według stylów itp.), nie każde wystąpienie obiektu musi mieć pełną wagę każdej zdefiniowanej właściwości.
Ostatnią nową funkcją systemu właściwości jest pojęcie dołączonych właściwości. Elementy WPF są oparte na zasadzie kompozycji i ponownego wykorzystania składników. Często zdarza się, że niektóre zawierające element (na przykład Grid element układu) wymagają dodatkowych danych dotyczących elementów podrzędnych, aby kontrolować jego zachowanie (na przykład informacje 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 pobieranie pikseli na ekranie. Klasa Visual zapewnia tworzenie drzewa obiektów wizualnych, z których każda opcjonalnie zawiera instrukcje rysowania i metadane dotyczące sposobu renderowania tych instrukcji (przycinanie, przekształcanie itp.). Visual jest przeznaczony do bardzo lekkiego i elastycznego, dlatego większość funkcji nie ma publicznej ekspozycji interfejsu API i w dużym stopniu opiera się na chronionych funkcjach wywołania zwrotnego.
Visual jest naprawdę punktem wejścia do systemu kompozycji WPF. Visual to punkt połączenia między tymi dwoma podsystemami, zarządzany interfejs API i niezarządzany rdzeń milcore.
WPF wyświetla dane przez 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 Visual się elementy i typy pochodne, które wewnętrznie komunikują się z drzewem kompozycji za pośrednictwem tego protokołu obsługi komunikatów. Każdy Visual w WPF może utworzyć jeden, żaden lub kilka węzłów kompozycji.

W tym miejscu znajduje się bardzo ważny szczegół architektoniczny — całe drzewo wizualizacji i instrukcje rysowania są buforowane. W kategoriach graficznych WPF używa zachowanego systemu renderowania. Dzięki temu system może odświeżać się z wysokimi szybkościami odświeżania bez blokowania systemu kompozycji w 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 faktycznie wykonuje kompozycję.
W systemach User32 i GDI system działa w systemie przycinania w trybie bezpośrednim. Gdy składnik musi być renderowany, system ustanawia granice przycinania, poza którymi składnik nie może dotknąć 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ę zmienia, wystarczy dotknąć objętego składnika — żaden z dwóch składników nigdy nie przyczynia się do koloru pojedynczego piksela.
WPF używa modelu malowania "algorytmu malarza". Oznacza to, że zamiast przycinania każdego składnika każdy składnik jest proszony o renderowanie z tyłu do przodu ekranu. Dzięki temu każdy składnik może malować ekran poprzedniego składnika. Zaletą tego modelu jest możliwość posiadania złożonych, częściowo przezroczystych kształtów. W przypadku nowoczesnego sprzętu graficznego ten model jest stosunkowo szybki (co nie miało miejsca w przypadku utworzenia interfejsu GDI 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(). Przeniesienie 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 pochodne 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żesz 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 podsystemy podstawowe, w tym układ, dane wejściowe i zdarzenia.
Układ jest podstawową koncepcją w WPF. W wielu systemach istnieje stały zestaw modeli układu (język HTML obsługuje trzy modele układu; przepływ, bezwzględne i tabele) lub żaden model układu (użytkownik 32 naprawdę obsługuje 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ć napędzany przez wartości właściwości, a nie logikę imperatywne. UIElement Na poziomie wprowadza się podstawowy kontrakt dla układu — dwufazowy model z Measure i Arrange przechodzi.
Measure umożliwia składnikowi określenie rozmiaru, jaki ma on przyjąć. Jest to osobna faza, ponieważ Arrange istnieje wiele sytuacji, w których element nadrzędny poprosi element nadrzędny o zmierzenie kilka razy w celu określenia jego optymalnej pozycji i rozmiaru. Fakt, że elementy nadrzędne pytają elementy podrzędne do pomiaru, pokazuje inną kluczową filozofię WPF — rozmiar do zawartości. Wszystkie kontrolki w WPF obsługują możliwość rozmiaru naturalnego rozmiaru 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 ustawienie elementu nadrzędnego 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 istnieje ogromna ilość innowacji po stronie wejściowej, jak również. 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ą jako sygnał sterownika urządzenia w trybie jądra i jest kierowany do prawidłowego procesu i wątku przez skomplikowany proces obejmujący jądro 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. WPF umożliwia konwertowanie nieprzetworzonych zdarzeń wejściowych na wiele rzeczywistych zdarzeń, dzięki czemu funkcje takie jak "MouseEnter" mają być implementowane na niskim poziomie systemu z gwarantowanym dostarczaniem.
Każde zdarzenie wejściowe jest konwertowane na co najmniej dwa zdarzenia — zdarzenie "podgląd" i rzeczywiste zdarzenie. Wszystkie zdarzenia w WPF mają pojęcie routingu przez drzewo elementów. Zdarzenia są mówi się do "bąbelka", jeśli przechodzą z celu w górę drzewa do korzenia, i mówi się do "tunelu", jeśli zaczynają się od korzenia i przechodzą do celu. Tunel zdarzeń podglądu danych wejściowych, włączając dowolny element w drzewie możliwość filtrowania lub podejmowania akcji na zdarzenie. Zwykłe (inne niż podgląd) zdarzenia, a następnie bąbelki z obiektu docelowego do katalogu głównego.
Ten podział między fazę tunelu i bąbelka sprawia, że implementacja funkcji, takich jak akceleratory klawiatury, działa w spójny sposób w świecie złożonym. W przypadku użytkownika User32 można zaimplementować akceleratory klawiatury, mając jedną tabelę globalną zawierającą wszystkie akceleratory, które mają być obsługiwane (mapowanie Ctrl+N na "Nowy"). W dyspozytorze aplikacji wywołasz funkcję TranslateAccelerator , która wącha komunikaty wejściowe w usłudze User32 i określi, czy dowolny z nich pasuje do zarejestrowanego akceleratora. W WPF to nie zadziała, ponieważ system jest w pełni "komposable" - każdy element może obsługiwać i używać dowolnego akceleratora klawiatury. Posiadanie tego dwufazowego modelu 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 zdefiniowanie 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óre mają być używane w aplikacji.
W tym momencie w temacie "podstawowe" funkcje platformy WPF — funkcje zaimplementowane w zestawie PresentationCore były głównym celem. Podczas tworzenia platformy WPF czyste rozdzielenie między elementami podstawowymi (takimi jak kontrakt dla układu z miarą i rozmieszczeniem) i elementami struktury (takimi jak implementacja określonego układu, na Gridprzykład ) była pożądanym wynikiem. Celem było zapewnienie niskiego punktu rozszerzalności w stosie, który umożliwiłby 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 program są związane z układem aplikacji. FrameworkElement Opiera się na podstawowym kontrakcie układu wprowadzonym przez UIElement program i dodaje pojęcie układu "miejsce", które ułatwia autorom układów spójny zestaw semantyki układu opartego na właściwościach. Właściwości, takie jak HorizontalAlignment, , VerticalAlignmentMinWidthi Margin (nazwa kilku) dają wszystkim składnikom pochodzącym ze FrameworkElement spójnego zachowania 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 BeginStoryboard pośrednictwem metody . Element Storyboard zapewnia sposób tworzenia skryptów wielu animacji względem zestawu właściwości.
Dwa najważniejsze kwestie, 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 elementem danych. Platforma WPF ma pełną obsługę powiązania właściwości, przekształcenia i powią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 zamiast tego odwrócić problem i pozwolić, aby dane mogły określić ekran, który zostanie utworzony.
Styl jest naprawdę 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 przez jawne odwołanie (przez ustawienie Style właściwości) lub niejawnie przez skojarzenie stylu z typem CLR elementu.
System.Windows.Controls.Control
Najważniejszą funkcją kontrolki jest tworzenie szablonów. Jeśli uważasz, że system kompozycji WPF jako system renderowania w trybie zachowanym, tworzenie szablonów pozwala kontrolce opisać jego renderowanie w sparametryzowany, deklaratywny sposób. Element to ControlTemplate 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 akcji , Foreground, Background, Paddingaby 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 w celu dostosowania modelu interakcji lub dostosowania wyświetlania (określonego 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 przyjrzysz się kontrolce takiej jak Button, zobaczysz, że ma ona właściwość o nazwie "Content" typu Object. W 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świetlania.
Podsumowanie
Platforma WPF została zaprojektowana tak, aby umożliwić tworzenie dynamicznych systemów prezentacji opartych na danych. Każda część systemu jest przeznaczona do tworzenia obiektów za pomocą zestawów właściwości, które powodują zachowanie dysku. 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świetlacza, jest generowany przez jakiś typ powiązania danych. Tekst znajdujący się wewnątrz przycisku jest wyświetlany przez utworzenie kontrolki złożonej wewnątrz przycisku i powiązanie jej wyświetlania z właściwością zawartości przycisku.
Po rozpoczęciu tworzenia aplikacji opartych na platformie WPF powinno być bardzo znane. Możesz ustawić właściwości, używać obiektów i wiązać danych w taki sam sposób, jak w Windows Forms lub ASP.NET. Dzięki dokładniejszej badaniu architektury WPF przekonasz się, że istnieje możliwość tworzenia znacznie bogatszych aplikacji, które zasadniczo traktują dane jako podstawowy czynnik aplikacji.