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. Szczegóły 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ś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 bardzo wrażliwy na wydajność i wymaga rezygnacji z wielu zalet CLR, aby uzyskać wydajność.

The position of WPF within the .NET Framework.

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. 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 — dyspozytora i koligacji wątków.

W fazie projektowania WPF celem było przejście do pojedynczego wątku wykonywania, ale niewątkowy model "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ą 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 stać się intensywnie obciążające pamięć. W końcu model wątkowości WPF był synchronizowany 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, wymaga wykonania koligacji pojedynczego wątku (STA).

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 DispatcherObjectklasy , utworzysz 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 jest również oparte na modelu lub oparte na danych system 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 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 DependencyObject typu. System właściwości jest naprawdę "zależności" system właściwości w, że ś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 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ż rozrzedłe przechowywanie wartości 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 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 element (na Grid przykład element układu) wymagają dodatkowych danych dotyczących elementów podrzędnych w celu kontrolowania jego 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 pobieranie pikseli narysowanych 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 (wycinkowanie, przekształcanie itp.). Visual został zaprojektowany tak, aby był bardzo lekki i elastyczny, dlatego większość funkcji nie ma publicznej ekspozycji interfejsu API i w dużym stopniu polega na chronionych funkcjach wywołania zwrotnego.

Visual to naprawdę punkt wejścia do systemu kompozycji WPF. Visual to punkt połączenia między tymi dwoma podsystemami, zarządzany interfejs API i niezarządzany 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.

The Windows Presentation Foundation Visual Tree.

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 systemach User32 i GDI system działa w systemie przycinania trybu natychmiastowego. 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 "algorytmu 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. W przypadku nowoczesnego sprzętu graficznego ten model jest stosunkowo szybki (co nie miało miejsca podczas tworzenia 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(). 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 pochodne są Drawing 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 podsystemy podstawowe, 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ł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ć oparty na wartościach właściwości, a nie logiki imperatywnej. UIElement Na poziomie wprowadza się podstawowy kontrakt układu — dwufazowy model z Measure i Arrange przechodzi.

Measure umożliwia składnikowi określenie, ile rozmiaru ma przyjąć. Jest to osobna faza, ponieważ Arrange istnieje wiele sytuacji, w których element nadrzędny będzie prosić element nadrzędny o zmierzenie kilka razy w celu określenia optymalnej pozycji i rozmiaru. Fakt, że elementy nadrzędne proszą elementy podrzędne o pomiar, pokazują kolejną kluczową filozofię WPF — rozmiar 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 położenie elementu nadrzędnego i określenie końcowego rozmiaru każdego elementu podrzędnego.

Dużo czasu jest często poświęcane na mówienie o stronie wyjściowej WPF — Visual i powiązanych obiektów. Jednak istnieje ogromna ilość innowacji po stronie danych wejściowych, jak również. Prawdopodobnie najbardziej fundamentalną zmianą w modelu wejściowym dla WPF jest consis tryb namiotu l, według którego zdarzenia wejściowe są kierowane przez system.

Dane wejściowe pochodzą jako sygnał sterownika urządzenia w trybie jądra i są kierowane do prawidłowego procesu i wątku przez skomplikowany proces obejmujący jądro systemu Windows i Użytkownika32. 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 mają pojęcie routingu przez drzewo elementów. Zdarzenia są mówione do "bąbelka", jeśli przechodzą od 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. Zdarzenia zwykłe (inne niż wersja zapoznawcza) są następnie bąbelkowe od obiektu docelowego do katalogu głównego.

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 aplikacji wywołasz funkcję TranslateAccelerator , która wącha komunikaty wejściowe w usłudze User32 i określ, czy dowolny akcelerator jest zgodny z zarejestrowanym akceleratorem. 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 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 funkcjonalności pod względem punktu końcowego polecenia — co implementuje ICommandelement . 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 kompilowania WPF czyste rozdzielenie między podstawowymi elementami (takimi jak kontrakt układu z miarą i rozmieszczeniem) i elementami struktury (takimi jak implementacja określonego układu, takiego jak Grid) była 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ę dwóm różnym sposobom. 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 "slot", 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, VerticalAlignment, MinWidthi Margin (aby wymienić kilka) dają wszystkie składniki pochodzące z 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 pośrednictwem BeginStoryboard metody . Element Storyboard zapewnia sposób tworzenia skryptów wielu animacji względem zestawu właściwości.

Dwa najważniejsze elementy, które wprowadza, FrameworkElement 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ę 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 obrócić problem i pozwolić na określenie, który ekran zostanie utworzony.

Styl jest naprawdę uproszczoną 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 trybu zachowanego, tworzenie szablonów umożliwia kontrolce opisywanie jego renderowania w sposób sparametryzowany, deklaratywny. A ControlTemplate to naprawdę nic więcej niż skrypt do utworzenia zestawu elementów podrzędnych z powiązaniami z właściwościami oferowanymi przez kontrolkę.

ControlUdostępnia zestaw właściwości akcji , Foreground, BackgroundPadding, 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 con tryb namiotu l. Jeśli przyjrzysz się kontrolce takiej jak 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.

Podsumowanie

Platforma WPF została zaprojektowana w celu umożliwienia tworzenia 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ś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.

Podczas tworzenia aplikacji opartych na WPF powinno się wydawać bardzo znane. Właściwości, obiekty i dane można ustawiać w taki sam sposób, jak w przypadku formularzy systemu 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ż