Legarea și extensiile de markup xaml folosind localizarea ca exemplu. Configurarea afișajului de date cu legarea datelor și WPF

  • Programare
  • Unul dintre punctele cheie în dezvoltare xaml aplicațiile orientate este să folosească legături ( Legături). Legare- Acest mediator(intermediar), cu ajutorul căruia valorile proprietăților sunt sincronizate între obiectele înrudite.

    Este de remarcat faptul că nu este evident, dar nuanță importantă: Deși legarea face referire cumva la obiectele care interacționează, nu le împiedică să fie colectate de gunoi!

    Moștenirea dintr-o clasă Legare permis, dar din motive de siguranță a codului, anulează metoda FurnizațiValoare, care este legat de logica principală de funcționare, nu este permisă. Acest lucru determină cumva dezvoltatorii să folosească modelul Convertor, care este strâns împletită cu tema legărilor.

    Legăturile sunt un instrument foarte puternic, dar în unele cazuri declarația lor devine pronunțată și incomod pentru utilizarea obișnuită, de exemplu, pentru localizare. În acest articol ne vom uita la o metodă simplă și elegantă care face codul mult mai curat și mai frumos.


    Declara legături în xaml permis în două moduri:



    Evident, prima metodă nu pare foarte concisă, dar a doua, bazată pe utilizare extensii de markup, este folosit cel mai des. Pe platformă WPF există posibilitatea de a crea extensii de markup personalizate. De exemplu, sunt convenabile de utilizat pentru localizare.


    În cel mai simplu caz, trebuie să moșteniți din clasă MarkupExtensionși implementați metoda FurnizațiValoare, în care să obțineți valoarea dorită folosind tasta.

    Dar această implementare nu acceptă schimb cald limbaj în timpul execuției programului. Pentru a face această îmbunătățire, este necesar, în primul rând, să stocați o referință la elementul de interfață localizat și, în al doilea rând, ceea ce este mai puțin evident, să aveți cumva în aplicație o referință la instanța clasei în sine. Localizarea, pentru a-l proteja de colectarea gunoiului și, în al treilea rând, este necesar să implementați corect abonamentul și dezabonarea de la evenimentul de schimbare a limbii.

    Dacă faceți aceste lucruri greșit, aveți garanția că veți obține scurgeri de memorie dacă vizualizările sunt create și distruse dinamic în timp ce aplicația rulează, ceea ce este cazul în multe cazuri. Adică, adăugarea s-ar părea că nu este cea mai mare functie complexa, va trebui să vă ocupați de subiecte non-triviale verigi slabeȘi abonamente slabe la evenimente. Și codul nu va fi foarte simplu.

    Mai mult, pe xaml-platforme Windows Phone, Magazin WindowsȘi Xamarin.Forms nu există nicio modalitate de a crea extensii de markup personalizate, ceea ce aduce în discuție ideea utilizării legăturilor ca extensii de markup

    Să nu ne batem prin tufiș, iată de ce avem nevoie:

    Clasa abstractă publică BindingExtension: Binding, IValueConverter ( protejat BindingExtension() ( Sursă = Convertor = aceasta; ) BindingExtension (sursă obiect) protejat (sursă obiect) // setează Sursa la null pentru utilizarea DataContext ( Sursă = sursă; Convertor = aceasta; ) BindingExtension (Sursă Relativă) protejată relativeSource) ( RelativeSource = relativeSource; Converter = this; ) obiect abstract Conversie(valoare obiect, Tip targetType, parametru obiect, cultură CultureInfo, obiect virtual public ConvertBack(valoare obiect, Tip targetType, parametru obiect, cultură CultureInfo) (aruncă nou); NotImplementedException();
    Este de remarcat faptul că legarea este un convertor în sine. Ca rezultat, obținem un comportament foarte asemănător cu cel al moștenirii de la o clasă MarkupExtension, dar, în plus, Rămâne posibil să se utilizeze mecanisme standard de control al colectării gunoiului!

    Acum, logica pentru localizarea arată nu ar putea fi mai simplă:

    Clasă parțială publică Localizare: Base.BindingExtension ( public static readonly Manager ActiveManager = nou Manager(); public Localizing() ( Sursă = ActiveManager; Path = new PropertyPath("Source"); ) public Localizing (cheie șir) ( Cheie = cheie ; Sursă = ActiveManager Cale = new PropertyPath("Source" șir public Key ( get; set; ) public override object Convert(valoare obiect, Type target parameter, CultureInfo culture) ( var key = Key; var resourceManager = valoare ca ResourceManager; var localizedValue = resourceManager == null || string.IsNullOrEmpty(key) ? ":" + key + ":" : (resourceManager.GetString(key) ?? ":" + key + ":"); returnează localizedValue;
    Clasa parțială publică Localizare ( Manager de clasă publică: INotifyPropertyChanged ( private ResourceManager _source; public ResourceManager Source ( get ( return _source; ) set ( _source = valoare; PropertyChanged(this, new PropertyChangedEventArgs("Source"))); ) ) șir public Get( cheie șir, șirFormat șir = nul) ( if (_source == null || string.IsNullOrWhiteSpace(key)) cheie return; var localizedValue = _source.GetString(key) ?? ":" + key + ":"; return șir .IsNullOrEmpty(stringFormat) ? localizedValue: string.Format(stringFormat, localizedValue eveniment public PropertyChangedEventHandler PropertyChanged = (sender, args) => ( ) );
    Este ușor să adăugați posibilitatea de a schimba majusculele literelor:

    Clasă parțială publică Localizare: Base.BindingExtension ( enumerare publică Case ( Implicit, Inferioară, Superioară ) public static readonly Manager ActiveManager = nou Manager(); public Localizing() ( Sursă = ActiveManager; Cale = nou PropertyPath("Sursa"); ) public Localizing(string key) ( Cheie = cheie; Sursă = ActiveManager; Path = new PropertyPath("Source"); ) public șir Cheie ( get; set; ) public Cases Case ( get; set; ) public override string ToString() ( returnează Convert(ActiveManager.Source, null, Key, Thread.CurrentThread.CurrentCulture) ca șir ?? șir.Empty; ) public override object Convert (valoare obiect, Tip targetType, parametru obiect, cultură CultureInfo) ( var key = Key; var resourceManager = valoare ca ResourceManager var localizedValue = resourceManager == null || string.IsNullOrEmpty(key) ? "); comutator (Case) (case Cases.Lower: return localizedValue.ToLower(); case Cases.Upper: return localizedValue.ToUpper(); implicit: return localizedValue; ) ) )
    ÎN xamlÎnregistrarea pare convenabilă și frumoasă, dar există câteva limitări ale parserelor de marcare pe diferite platforme:


    Pentru a scăpa de WPF din prefixul cerut m: trebuie să plasați extensia de marcare într-un ansamblu separat și în Proprietăți/AssemblyInfo.cs specifica urmatoarele directive:


    Pentru a ajusta numele prefixului la Windows Phone sau Magazin:


    Folosind extensii de legare ( Extensii de legare) pe WPF nu exclude extensiile obișnuite de markup, dar în unele cazuri este chiar mai sigur și varianta simpla. De asemenea, toate acestea nu se limitează doar la localizare, ci sunt potrivite pentru multe alte scopuri...

    Abordarea demonstrată este utilizată pe scară largă în bibliotecă

    Ultima actualizare: 02/07/2016

    În WPF, legarea este Unealtă puternică programare, fără de care nici o singură aplicație serioasă nu poate face.

    Legarea implică interacțiunea a două obiecte: o sursă și o destinație. Obiectul destinație creează o legătură cu o anumită proprietate a obiectului sursă. Dacă obiectul sursă este modificat, obiectul destinație va fi și el modificat. De exemplu, cea mai simplă formă folosind legarea:

    Pentru a defini o legătură, o expresie ca:

    (Binding ElementName=Sursă nume_obiect, Cale=Sursă obiect_proprietate)

    Adică, în acest caz, elementul TextBox este sursa, iar TextBlock este destinația legării. Proprietate Element Text TextBlock este legat de proprietatea Text a elementului TextBox. Ca urmare, atunci când introduceți un câmp de text, modificările în blocul de text vor avea loc sincron.

    Lucrul cu Binding în C#

    Obiectul cheie la crearea unei legături este obiectul System.Windows.Data.Binding. Folosind acest obiect putem obține legarea deja existentă pentru element:

    Binding binding = BindingOperations.GetBinding(myTextBlock, TextBlock.TextProperty);

    În acest caz, obținem o legare pentru proprietatea de dependență TextProperty a elementului myTextBlock.

    De asemenea, puteți seta legarea în întregime în codul C#:

    Public MainWindow() ( InitializeComponent(); Binding binding = new Binding(); binding.ElementName = "myTextBox"; // element sursă binding.Path = new PropertyPath("Text"); // proprietate element sursă myTextBlock. SetBinding( TextBlock.TextProperty, binding); // setarea legării pentru elementul receptor)

    Dacă în viitor nu mai avem nevoie de o legare, putem folosi clasa BindingOperations și metodele sale ClearBinding() (elimină o legare) și ClearAllBindings() (elimină toate legăturile pentru un anumit element).

    BindingOperations.ClearBinding(myTextBlock, TextBlock.TextProperty);

    BindingOperations.ClearAllBindings(myTextBlock);

    Câteva proprietăți ale clasei Binding:

      ElementName : numele elementului la care este creată legătura

      IsAsync: dacă este setat la True, folosește modul asincron pentru a primi date de la obiect. Setarea implicită este False

      Mod: Modul Legare

      TargetNullValue : Setează valoarea implicită dacă proprietatea legată a sursei de legare este nulă

      RelativeSource: creează o legătură relativă la obiectul curent

      Sursă: indică obiectul sursă dacă nu este un control.

      XPath : utilizat în locul proprietății cale pentru a specifica calea către datele XML

    Moduri Snap

    Proprietatea Mode a unui obiect Binding, care reprezintă modul de legare, poate lua următoarele valori:

      OneWay: Proprietatea obiectului destinație este modificată după ce proprietatea obiectului sursă este modificată.

      OneTime: proprietatea obiectului destinație este setată la proprietatea obiectului sursă o singură dată. Modificările ulterioare ale sursei nu au niciun efect asupra obiectului destinație.

      TwoWay: Atât obiectele aplicație, cât și obiectele sursă pot schimba proprietățile legate reciproc.

      OneWayToSource: obiectul destinație în care este declarată legarea modifică obiectul sursă.

      Implicit : implicit (dacă proprietatea TextBox.Text se modifică, aceasta are valoarea TwoWay, în caz contrar OneWay).

    Aplicarea modului snap:

    Actualizare obligatorie. UpdateSourceTrigger

    Legarea unidirecțională de la sursă la receptor schimbă proprietatea receptorului aproape instantaneu. Dar dacă folosim legarea bidirecțională în cazul câmpurilor de text (ca în exemplul de mai sus), atunci când destinația se schimbă, proprietatea sursă nu se schimbă instantaneu. Deci, în exemplul de mai sus, pentru ca câmpul de text sursă să se schimbe, trebuie să mutăm focalizarea din câmpul de text de destinație. Și în acest caz, intră în joc proprietatea UpdateSourceTrigger a clasei Binding, care specifică modul în care va avea loc actualizarea. Această proprietate ia una dintre valorile de enumerare UpdateSourceTrigger:

      PropertyChanged: sursa de legare este actualizată imediat după actualizarea proprietății din receptor

      LostFocus: sursa de ancorare este actualizată numai după ce receptorul își pierde focalizarea

      Explicit: sursa nu este actualizată până când metoda BindingExpression.UpdateSource() nu este apelată

      Implicit: Valoare implicită. Pentru majoritatea proprietăților, aceasta este PropertyChanged . Și pentru proprietatea Text a elementului TextBox, această valoare este LostFocus

    În acest caz, vorbim despre actualizarea sursei de legare după schimbarea destinației în modurile OneWayToSource sau TwoWay. Adică, pentru ca ambele câmpuri de text care sunt conectate prin modul TwoWay să fie actualizate instantaneu după modificarea unuia dintre ele, trebuie să folosim valoarea UpdateSourceTrigger.PropertyChanged:

    Proprietatea sursă

    Proprietatea Sursă vă permite să setați o legare chiar și pentru obiecte care nu sunt controale WPF. De exemplu, să definim clasa Telefon:

    Class Phone ( șir public Titlu ( get; set; ) public șir Companie ( get; set; ) public int Preț ( get; set; ) )

    Acum să creăm un obiect din această clasă și să definim o legătură cu acesta:

    Proprietatea TargetNullValue

    În cazul în care o proprietate din sursa de legare are brusc valoarea nulă, adică nu este setată, putem seta o valoare implicită. De exemplu:

    În acest caz, resursa nexusPhone nu are setată o proprietate Titlu, astfel încât blocul de text va scoate valoarea implicită specificată în parametrul TargetNullValue.

    Proprietatea RelativeSource

    Proprietatea RelativeSource vă permite să setați o legătură relativă la un element sursă care are un fel de relație cu elementul destinație. De exemplu, elementul sursă poate fi unul dintre containerele exterioare pentru elementul destinație. Sau sursa și destinația pot fi același element.

    Pentru a seta această proprietate, utilizați obiectul RelativeSource cu același nume. Acest obiect are o proprietate Mode, care specifică metoda de legare. Este nevoie de una dintre valorile de enumerare RelativeSourceMode:

      Self: legarea se realizează la o proprietate a aceluiași element. Adică, elementul sursă al legării este în același timp și receptorul legării.

      FindAncestor: legarea este efectuată la proprietatea elementului container.

    De exemplu, combinați legarea sursă și destinație în elementul în sine:

    Aici textul și culoarea de fundal a casetei de text sunt legate printr-o legare bidirecțională. Drept urmare, putem vedea pe teren valoare numerica culoare, schimbați-l și fundalul câmpului se va schimba odată cu acesta.

    Legarea la proprietățile containerului:

    Când utilizați modul FindAncestor, adică legarea la un container, trebuie să specificați și parametrul AncestorType și să îi transmiteți tipul de container sub forma expresiei AncestorType=(x:Type Container_element_type) . În acest caz, am putea selecta orice container din arborele elementelor ca container în special, în acest caz, pe lângă Grid, un astfel de container este și elementul Window;

    Proprietatea DataContext

    Obiectul FrameworkElement de la care moștenesc controalele are o proprietate DataContext interesantă. Vă permite să setați un context de date pentru un element și elementele sale imbricate. Elementele imbricate pot folosi apoi obiectul Binding pentru a se lega de proprietăți specifice ale contextului respectiv. De exemplu, să folosim clasa Phone definită anterior și să creăm un context de date dintr-un obiect din această clasă:

    În acest fel, setăm proprietatea DataContext la o resursă dinamică sau statică. Apoi ne legăm de această resursă.

    Legarea datelor în XAML

    Partajarea obiectelor într-un fișier XAML se poate face și prin legarea datelor. De fapt legarea datelor conectează două proprietăți ale obiectelor diferite. După cum vom arăta mai târziu, legăturile de date sunt cel mai adesea folosite pentru a asocia elementele vizuale ale paginii cu sursele de date; în plus, ele sunt o componentă importantă a implementării popularului model arhitectural MVVM (Model-View-ViewModel). Legăturile joacă, de asemenea, un rol important în definirea tiparelor de afișare a obiectelor de date.

    Legăturile de date pot fi folosite pentru a asocia proprietățile a două elemente. Element de legare, precum StaticResource, este de obicei exprimat ca o extensie de markup, adică închisă între acolade. Totuși, elementul Binding permite și o expresie alternativă sub forma sintaxei elementului de proprietate.

    Utilizați următorul dicționar de resurse în proiectul nostru de testare:

    Setările pensulei

    Stilul implicit TextBlock nu conține o proprietate Foreground. Un LinearGradientBrush este definit pe primul dintre cele patru elemente TextBlock care folosesc acea perie, iar elementele TextBlock ulterioare fac referire la aceeași perie printr-o legare:

    Legarea datelor are sursăȘi receptor (țintă). Destinația este întotdeauna proprietatea la care este stabilită legarea, iar sursa este proprietatea la care este legată. Sursa legăturilor afișate este un element TextBlock numit topTxb; receptorii sunt trei elemente TextBlock care partajează proprietatea Foreground. Două receptoare reprezintă o modalitate mai standard de a exprima un obiect Binding ca extensie de marcare XAML:

    (Binding ElementName=topTxb, Path=Foreground)

    Extensiile de markup XAML sunt întotdeauna plasate între acolade. Într-o extensie de markup pentru Binding, de obicei trebuie să setați o pereche de proprietăți, separate prin virgule. Proprietatea ElementName denotă numele elementului pentru care este setată proprietatea dorită; Proprietatea căii furnizează denumirea proprietății.

    Când scriu extensii de markup Binding, vreau întotdeauna să încadrez valorile proprietăților între ghilimele, dar acest lucru este incorect. Citatele nu sunt necesare în expresiile obligatorii.

    Ultimul element TextBlock demonstrează expresia Binding în sintaxa elementului de proprietate mai puțin comună:

    În această sintaxă, ghilimelele în jurul valorilor proprietăților sunt necesare deoarece astea sunt obisnuite Atributele XML. De asemenea, puteți crea un obiect Binding în cod și îl puteți atribui proprietății de primire folosind metoda SetBinding() definit în FrameworkElement. Acest lucru arată că destinația de legare trebuie să fie întotdeauna o proprietate de dependență.

    Proprietatea Path a clasei Binding este numită astfel deoarece poate conține mai multe nume de proprietăți separate prin puncte. De exemplu, încercați să înlocuiți una dintre valorile Text din proiectul dvs. cu următoarea valoare:

    Text="(Binding ElementName=topTxb, Path=FontFamily.Source)"

    Prima parte a Căii indică faptul că avem nevoie de datele din proprietatea FontFamily. Proprietatea este setată la un obiect de tip FontFamily care conține numele Sursă, care denotă numele familiei de fonturi. Prin urmare, TextBlock va afișa textul „Arial”.

    Încercați să aplicați următorul construct oricărui element TextBlock din proiectul nostru:

    Text="(Binding RelativeSource=(RelativeSource Self), Path=FontSize)"

    Iată extensia de markup Sursă relativă se găsește în interiorul extensiei de markup Binding și este folosit pentru a face referire la o proprietate a elementului pentru care este setat legarea.

    Acum ați creat pagini cu drepturi depline pentru aplicația dvs. Acum probabil că veți dori să le completați cu diverse date.

    În această parte veți învăța:

    • Cum să legați datele la interfața cu utilizatorul.
    • Cum Studio vizual vă poate ajuta să creați legături de date.
    • Cum să afișați datele într-o listă.
    • Cum să faceți față unor scenarii obligatorii mai complexe.

    Legarea datelor la interfața cu utilizatorul

    Aplicația Fuel Tracker are trei pagini de date. Datele sunt stocate în principal în trei clase. Următoarea imagine arată paginile și clasele asociate acestora.

    Legarea datelor este de obicei folosită pentru afișarea datelor. Legarea datelor oferă posibilitatea de a conecta o interfață de utilizator la o sursă de date. Când sunt create legături și sursa de date se modifică, elementele UI care sunt legate la sursa de date reflectă modificările automat. De asemenea, modificările făcute de utilizator la un element UI sunt reflectate în sursa de date. De exemplu, dacă utilizatorul modifică o valoare într-un TextBox, sursa de date corespunzătoare se va actualiza automat pentru a reflecta acele modificări.

    Următorul fragment de cod XAML ilustrează sintaxa care este utilizată pentru a lega proprietatea Text a unui control TextBox la proprietatea Name a obiectului sursă.

    1. < TextBox x:Name ="NameTextBox" Text ="(Binding Name, Mode=TwoWay)" />

    Următoarea imagine prezintă un exemplu de astfel de legare.

    Fiecare legare are o proprietate Mode, care determină cum și când datele sunt actualizate. Legarea OneWay înseamnă că elementul UI țintă este actualizat dacă sursa se modifică. Legarea în două sensuri înseamnă că atât ținta, cât și sursa sunt actualizate dacă oricare dintre ele se modifică. Dacă utilizați legături OneWay sau TwoWay, atunci pentru ca legarea să fie notificată cu privire la modificările aduse obiectului sursă, trebuie să implementați interfața INotifyPropertyChanged. Această interfață va fi discutată mai detaliat în partea următoare, „Crearea claselor de date”.

    Specificați un obiect sursă setând proprietatea DataContext. Dacă utilizați un control ListBox, trebuie să specificați obiectul sursă setând proprietatea ItemsSource. Următorul exemplu arată cum să setați proprietatea DataContext a unui panou CarHeader la un obiect sursă preluat dintr-o proprietate statică.

    1. CarHeader.DataContext = CarDataStore.Car;
    * Acest cod sursă a fost evidențiat cu Sursa de evidențiere a codului.

    Proprietatea DataContext vă permite să setați o legare standard pentru un întreg element UI, inclusiv toate elementele sale secundare. În unele cazuri, veți găsi mai convenabil să setați proprietatea DataContext pentru întreaga pagină, iar în altele, veți găsi mai convenabil să o setați individual pentru fiecare element din pagină. Setarea DataContext la fiecare nivel XAML anulează orice setări la un nivel superior. În plus, puteți suprascrie orice setare DataContext pentru legături individuale, setând proprietatea sursă.

    De exemplu, în aplicația Fuel Tracker, fiecare pagină setează DataContext la o valoare diferită. Totuși, pe pagină FillupPage DataContext la nivel de pagină este înlocuit pentru panoul care afișează numele și fotografia mașinii. Următoarea imagine arată setările de context de date pentru aplicația Fuel Tracker.

    Utilizarea generatorului de legături de date

    Visual Studio include un generator de legături de date pentru a vă ajuta să creați legături de date în XAML. Deși generatorul de legături de date poate oferi îmbunătățiri de performanță, nu acceptă toate scenariile posibile. De exemplu, nu acceptă legarea la elementele indexate și nu recunoaște legăturile create în cod. Prin urmare, în unele cazuri va trebui să specificați manual legăturile de date.

    Afișarea datelor într-o listă

    Afișarea unei colecții de elemente dintr-o listă este una dintre sarcinile principale ale telefonului dvs. Pentru a afișa o colecție de articole dintr-o listă folosind legarea de date, trebuie să faceți următoarele:
    1. Adăugați ListBox la aplicația dvs.
    2. Specificați sursa de date pentru ListBox legând colecția la proprietatea ItemsSource.
    3. Pentru a personaliza aspectul fiecărui element dintr-un ListBox, adăugați un șablon de date pentru ListBox.
    4. În șablonul de date, legați elementele ListBox la proprietățile Item Collection.
    Următoarea imagine arată legăturile pentru ListBox pe pagina de rezumat al aplicației Fuel Tracker.

    Următorul XAML arată cum au fost specificate legăturile pentru ListBox.

    1. < ListBox ItemContainerStyle ="(StaticResource ListBoxStyle)"
    2. ItemsSource="(Istoric de completare de legare)"
    3. Height="380" HorizontalAlignment="Left" Margin="5.25,0.0"
    4. VerticalAlignment="Top" Width="444" >
    5. < ListBox.ItemTemplate >
    6. < DataTemplate >
    7. < StackPanel Orientation ="Horizontal" >
    8. < TextBlock Style
    9. Text ="(Data de legare, Convertor=(StaticResource StringFormatter), ConverterParameter=\(0:d\) )"
    10. Width="105" TextWrapping="Wrap" />
    11. < TextBlock Style ="(Stil de rezumat al resurselor statice)"
    12. Text ="(Binding FuelQuantity)" TextWrapping ="Wrap" />
    13. < TextBlock Style ="(Stil de rezumat al resurselor statice)"
    14. Text ="(Binding DistanceDriven)" TextWrapping ="Wrap" />
    15. < TextBlock Style ="(Stil de rezumat al resurselor statice)"
    16. Text ="(Binding PricePerFuelUnit, Converter=(StaticResource StringFormatter), ConverterParameter=\(0:c\), ConverterCulture=en-US)" />
    17. < TextBlock Style ="(Stil de rezumat al resurselor statice)"
    18. Text ="(Binding FuelEfficiency, Converter=(StaticResource StringFormatter), ConverterParameter=\(0:F\))" TextWrapping ="Wrap" />
    * Acest cod sursă a fost evidențiat cu Sursa de evidențiere a codului.

    În XAML anterior, proprietatea ListBox.ItemsSource este legată de Car.FillupHistory astfel încât fiecare obiect A completaîn colecția de istorie va apărea ca un element separat în ListBox. Elementul DataTemplate definește aspectul fiecărui element și conține mai multe elemente TextBlock, fiecare dintre acestea fiind legat de o proprietate de clasă A completa.

    Acest XAML va funcționa numai atunci când obiectul Mașină este mai întâi asociat cu pagina, așa cum se arată în următorul cod din SummaryPage.xaml.cs.

    1. acest .DataContext = CarDataStore.Car;
    * Acest cod sursă a fost evidențiat cu Sursa de evidențiere a codului.

    Sfat pentru îmbunătățirea performanței:
    Dacă derularea în ListBox nu pare lină și receptivă, încercați aceste sfaturi:
    • Simplificați elementele din ListBox.
    • Încărcați imagini în fundal.
    • Utilizați virtualizarea datelor.
    • Rețineți utilizarea DeferredLoadListBox sau LazyListBox.
    • Nu utilizați liste imbricate.

    Căi de legare complexe

    Pe lângă flexibilitatea setării proprietății DataContext la orice nivel (permițându-vă să suprascrieți setările la un nivel superior), puteți specifica și căi de legare complexe pentru a explora proprietățile de referință, cum ar fi Car.FillupHistory. De exemplu, următorul XAML din SummaryPage.xaml demonstrează legarea proprietăților Umplere.Eficiența combustibilului primul articol din colecția de istorie a benzinăriilor.
    1. < TextBlock Text ="(Legarea FillupHistory.FuelEfficiency, Converter=(StaticResource StringFormatter), ConverterParameter=\(0:F\))" />
    * Acest cod sursă a fost evidențiat cu Sursa de evidențiere a codului.

    Următoarea imagine arată legăturile din SummaryPage.xaml și arată cum legături complexe iar șabloanele de date vă permit să legați controale la diferite proprietăți ale diferitelor obiecte, chiar dacă toate aparțin aceluiași DataContext.

    Dreptunghiurile verzi din ecranul Pivot din stânga arată controalele care sunt legate folosind drumuri dificile. Aceste căi încep de la primul element (index 0) din colecție Car.FillupHistoryși se încheie cu diverse proprietăți de clasă A completa. De exemplu, câmpul Current MPG folosește calea de legare FillupHistory.FuelEfficiency. Dacă includeți setarea DataContext a paginii în această cale, întreaga cale de legare va arăta astfel: CarDataStore.Car.FillupHistory.FuelEfficiency.

    Etichete:

    • windows phone 7
    • crearea unei aplicații
    • de la început până la sfârșit
    Adaugă etichete

    Când Windows Presentation Foundation (WPF) a intrat pentru prima dată pe radarul .NET, majoritatea articolelor și demonstrațiilor și-au prezentat motorul de randare de top și capabilitățile de grafică 3D. Deși sunt distractive de citit și de jucat, aceste exemple nu reflectă capacitățile largi ale WPF din lumea reală. Majoritatea dintre noi nu trebuie să creeze aplicații cu cuburi video care se rotesc, care explodează în artificii atunci când se dă clic. Cei mai mulți dintre noi își câștigă existența creând software pentru afișarea și editarea unor volume mari de date științifice sau de afaceri complexe.

    Vestea bună este că WPF oferă suport excelent pentru gestionarea afișajului și editarea datelor complexe. În numărul din decembrie 2007 al revistei MSDN®, John Papa a scris un articol, „Legarea datelor în WPF” (msdn.microsoft.com/magazine/cc163299), care face o treabă excelentă de a explica conceptele cheie ale legăturii de date în WPF. Aici voi analiza cazuri mai complexe de legare a datelor, bazându-mă pe ceea ce a prezentat John în seria de puncte de date menționată mai sus. După citire, cititorii vor fi conștienți de diferite modalități de implementare a cerințelor comune de legare a datelor observate în majoritatea aplicațiilor de afaceri.

    Legarea în cod

    Una dintre cele mai mari schimbări pe care WPF le introduce dezvoltatorilor de aplicații desktop este utilizarea pe scară largă și suportul pentru programarea declarativă. Interfețele și resursele utilizator WPF pot fi declarate folosind Extensible Application Markup Language (XAML), un limbaj de marcare standard bazat pe XML. Majoritatea explicațiilor legate de legare date WPF Ele vă arată doar cum să lucrați cu legături în XAML. Deoarece tot ceea ce se poate face în XAML poate fi realizat și în cod, este important ca dezvoltatori profesionisti sub WPF, au știut să lucreze cu legarea de date nu numai declarativ, ci și programatic.

    În multe cazuri, este mai ușor și mai convenabil să declarați legături în XAML. Pe măsură ce sistemele devin mai complexe și mai dinamice, uneori are sens să lucrezi cu legături în cod. Înainte de a merge mai departe, să ne uităm mai întâi la câteva clase și metode comune implicate în legarea de date programatică.

    Elementele WPF moștenesc metodele SetBinding și GetBindingExpression fie de la FrameworkElement, fie de la FrameworkContentElement. Acestea sunt pur și simplu metode comode care apelează metode cu aceleași nume în clasa de utilitate BindingOperations. Următorul cod arată cum să utilizați clasa BindingOperations pentru a lega proprietatea Text a unei casete de text la o proprietate de pe alt obiect:

    Static void BindText(TextBox textBox, proprietate șir)
    {
    dacă (!BindingOperations.IsDataBound(textBox, textProp))
    {
    Binding b = new Binding(proprietate);
    BindingOperations.SetBinding(textBox, textProp, b);
    }
    }

    Este ușor să dezlegați o proprietate folosind următorul cod:

    Static void UnbindText(TextBox textBox)
    {
    DependencyProperty textProp = TextBox.TextProperty;
    dacă (BindingOperations.IsDataBound(textBox, textProp))
    {
    BindingOperations.ClearBinding(textBox, textProp);
    }
    }

    Când ștergeți o legare, valoarea legată este, de asemenea, eliminată din proprietatea țintă.

    Declararea legăturii de date în XAML ascunde unele dintre detaliile de bază. Când începeți să lucrați cu legături în cod, aceste detalii încep să iasă la suprafață. Una este că relația dintre sursa unei legături și ținta acesteia este de fapt menținută de o instanță a clasei BindingExpression, mai degrabă decât de clasa Binding în sine. Clasa Binding conține informații de nivel înalt care pot fi partajate de mai multe clase BindingExpression, dar expresia de bază oferă conexiunea între două proprietăți legate. Următorul cod arată cum poate fi utilizată un BindingExpression pentru a asigura programatic că proprietatea Text a unei casete de text este bifată:

    bool static IsTextValidated(TextBox textBox)
    {
    DependencyProperty textProp = TextBox.TextProperty;

    var expr = textBox.GetBindingExpression(textProp);
    dacă (expr == nul)
    returnează fals;

    Legarea b = expr.ParentBinding;
    returnează b.ValidationRules.Any();
    }

    Deoarece clasa BindingExpression nu știe că este în curs de validare, trebuie pusă întrebarea legată de legarea părinte. Voi discuta mai jos diferite tehnici de validare a intrărilor.

    Lucrul cu șabloane

    O interfață de utilizator eficientă prezintă datele brute într-un mod care permite utilizatorului să extragă intuitiv informații semnificative din acestea. Aceasta este esența vizualizării datelor. Legarea datelor este doar o piesă a puzzle-ului de vizualizare a datelor. Toate programele WPF, cu excepția celor mai banale, necesită o modalitate de a reprezenta datele care este mai puternică decât simpla legare a unei proprietăți de pe control la o proprietate a obiectului de date. Obiectele de date reale au multe proprietăți care se referă la ele, iar aceste proprietăți diferite trebuie să convergă într-o reprezentare vizuală coerentă. Acesta este motivul pentru care WPF are șabloane de date.

    Clasa System.Windows.DataTemplate este doar o formă de șablon în WPF. Practic, un șablon este un cookie cutter pe care cadrul WPF îl folosește pentru a crea elemente vizuale care ajută la redarea obiectelor care nu au propria lor reprezentare vizuală. Când un element încearcă să redea un obiect care nu are o astfel de reprezentare, să zicem un obiect de afaceri non-standard, puteți spune elementului cum să randeze obiectul dându-i un DataTemplate.
    Un DataTemplate poate crea atâtea imagini vizuale cât este necesar pentru a afișa un obiect de date. Aceste elemente folosesc legături de date pentru a afișa valoarea proprietăților unui obiect de date. Dacă un element nu știe cum să redea obiectul căruia i s-a spus să îl redea, pur și simplu apelează metoda ToString pe el și afișează rezultatele într-un TextBlock.

    Să presupunem că avem o clasă simplă numită FullName care stochează numele unei persoane. Doriți să afișați o listă de nume care face ca numele fiecărei persoane să iasă în evidență de restul. Pentru a face acest lucru, puteți crea un DataTemplate care descrie cum să randați obiectul FullName. Codul prezentat în Fig. 1, afișează clasa FullName și codul de fundal pentru o fereastră care va afișa o listă de nume.

    Orez. 1. Afișarea obiectelor FullName folosind DataTemplate

    clasă publică FullName
    {
    șir public Prenume ( obține; setează; )
    public char MiddleInitial ( obține; setează; )
    șir public Nume (get; set; )
    }

    clasă parțială publică WorkingWithTemplates: Window
    {
    // Acesta este constructorul ferestrei.
    Public WorkingWithTemplates()
    {
    InitializeComponent();

    base.DataContext = nou FullName
    {
    Nume complet nou
    {
    Prenume = „Johann”,
    MiddleInitial = „S”,
    Nume = „Bach”
    },
    Nume complet nou
    {
    Prenume = „Gustav”,
    MiddleInitial = " ",
    Nume = „Mahler”
    },
    Nume complet nou
    {
    Prenume = „Alfred”,
    MiddleInitial = „G”,
    Nume = „Schnittke”
    }
    };
    }
    }

    După cum se poate observa în Fig. 2, Există un control ItemsControl în fișierul XAML din fereastră. Acesta creează o listă simplă de elemente pe care utilizatorul nu le poate selecta sau șterge. ItemsControl are un șablon DataTemplate alocat proprietății sale ItemTemplate, cu care redă fiecare instanță FullName creată în constructorul ferestrei. Veți observa că majoritatea elementelor TextBlock din DataTemplate au proprietatea Text legată de proprietățile obiectului FullName pe care îl reprezintă.

    Orez. 2. Afișarea obiectelor FullName folosind DataTemplate














    Când rulați această aplicație demonstrativă, arată ca în fig. 3. Utilizarea unui DataTemplate pentru a reda un nume facilitează evidențierea numelui de familie al fiecărei persoane, deoarece setarea FontWeight a TextBlock corespunzător este aldine. Acest exemplu simplu demonstrează relația dintre legarea de date WPF și șabloane. Pe măsură ce aprofundez subiectul, voi combina aceste caracteristici pentru a crea modalități de a vizualiza obiecte complexe cu capacități din ce în ce mai mari.

    Orez. 3. Obiecte FullName redate de DataTemplate

    Lucrul cu Inherited DataContext

    Dacă nu se specifică altfel, toate legăturile se leagă implicit la proprietatea DataContext a elementului. DataContextul unui element se referă la sursa de date, ca să spunem așa. Există câteva lucruri speciale pe care trebuie să le știți despre cum funcționează DataContext. Înțelegerea acestui aspect implicit al DataContext face mult mai ușoară proiectarea interfețelor utilizator complexe legate de date.

    Pentru a face referire la un obiect sursă de date, nu este necesar să setați proprietatea DataContext. Dacă proprietății DataContext a unui element strămoș dintr-un arbore de elemente (tehnic vorbind, un arbore logic) primește o valoare pentru DataContext, atunci valoarea va fi moștenită automat de fiecare element derivat din interfața cu utilizatorul. Cu alte cuvinte, dacă DataContext este setat să se refere la un obiect Foo, atunci, implicit, DataContext al fiecărui element din fereastră se va referi la același obiect Foo. Orice element dintr-o fereastră poate primi cu ușurință propria sa valoare DataContext, ceea ce va face ca toate elementele care derivă din acel element să moștenească noua valoare DataContext. Aceasta este similară cu o proprietate externă în Windows Forms.

    În secțiunea anterioară, m-am uitat la utilizarea DataTemplates pentru a crea vizualizări ale obiectelor de date. Proprietățile elementelor create de șablonul din Fig. 2 sunt legate de proprietățile obiectului FullName. Aceste elemente sunt implicit legate de proprietatea lor DataContext. Proprietatea DataContext a elementelor create de șablonul DataTemplate se referă la obiectul de date pentru care este utilizat șablonul, cum ar fi un obiect FullName.

    Nu există nicio magie în moștenirea valorii proprietății DataContext. Acest lucru folosește pur și simplu suportul încorporat al WPF pentru proprietățile de dependență moștenite. Orice proprietate de dependență poate fi o proprietate moștenită dacă i se dă pur și simplu un steag în metadatele furnizate la înregistrarea acelei proprietăți cu sistemul de proprietăți de dependență WPF.

    Un alt exemplu de proprietate de dependență moștenită este proprietatea Font-Size pe care o au toate elementele. Dacă setați proprietatea de dependență FontSize pe o fereastră, atunci în mod implicit toate elementele din această fereastră vor fi afișate ca text cu dimensiunea fontului specificată de aceasta. Infrastructura care este folosită pentru a propaga valoarea FontSize în jos în arborele de elemente propagă și DataContext.

    Aici termenul „moștenire” este folosit într-un sens diferit de sensul său orientat pe obiecte, unde o subclasă moștenește membrii clasei părinte. Moștenirea valorilor proprietăților se referă doar la propagarea valorilor în jos în arborele de elemente în timpul execuției. Desigur, o clasă poate moșteni o proprietate de dependență pentru a sprijini moștenirea valorii într-un sens orientat pe obiect.

    Lucrul cu vizualizările colecției

    Când controalele WPF se leagă de o colecție de date, acestea nu se leagă direct de colecția în sine. În schimb, se leagă implicit la o vedere, care devine automat învelișul acelei colecții. Vederea implementează interfața ICollectionViews și poate fi una dintre câteva implementări concrete, cum ar fi ListCollectionView.

    Prezentarea unei colecții are mai multe sarcini. Acesta ține evidența elementului curent din colecție, care este de obicei elementul selectat/activ într-un control cu ​​listă. Vizualizările colecțiilor oferă și modalități generale de a comanda, filtra și grupa articolele într-o listă. Mai multe controale se pot lega la aceeași vizualizare în jurul unei colecții, asigurând coordonarea lor între ele. Codul de mai jos arată câteva dintre capabilitățile ICollectionView:

    // Obține vizualizarea implicită în jurul listei de clienți.
    ICollectionView view = CollectionViewSource.GetDefaultView(allCustomers);

    // Selectați clientul în UI.
    Client selectatCustomer = view.CurrentItem ca Client;

    // Setați Clientul selectat în UI.
    view.MoveCurrentTo(someOtherCustomer);

    Toate controalele de tip listă, inclusiv casetele de listă, casetele combinate și vizualizările de listă, trebuie să aibă proprietatea IsSynchronizedWithCurrentItem setată la true pentru a rămâne sincronizate cu proprietatea de vizualizare a colecției CurrentItem. Această proprietate definește clasa abstractă Selector. Dacă nu este setat la adevărat, atunci selectarea unui element din controlul casetă listă nu va actualiza elementul curent al vizualizării colecției, iar setarea CurrentItem la o nouă valoare nu se va reflecta în controlul casetă listă.

    Lucrul cu date ierarhice

    Lumea reală este plină de date ierarhice. Un client plasează mai multe comenzi, o moleculă este formată din mulți atomi, un departament este format din mulți angajați și sistem solar conţine un grup de corpuri cereşti. Cititorii sunt, fără îndoială, familiarizați cu acest model de rezumat/detaliu.
    WPF oferă moduri diferite de a lucra cu structuri de date ierarhice, fiecare potrivită pentru diferite situații. În esență, alternativa este fie de a folosi mai multe controale pentru a afișa date, fie de a afișa mai multe niveluri de ierarhie a datelor într-un singur control. Aici voi analiza ambele abordări.

    Utilizarea mai multor controale pentru a afișa date XML

    O modalitate foarte comună de a lucra cu date ierarhice este de a afișa fiecare nivel al ierarhiei ca un control separat. De exemplu, să presupunem că avem un sistem care reprezintă clienții, comenzile și detaliile comenzii. Într-o astfel de situație, o casetă combo poate fi folosită pentru a afișa clienții, o casetă listă pentru a afișa toate comenzile clienților selectați și, în final, un ItemsControl pentru a afișa detaliile comenzii selectate. Acest metodă grozavă afișarea datelor ierarhice și implementarea lor în WPF este destul de ușoară.

    În fig. Figura 4, bazată pe situația descrisă mai sus, arată un exemplu simplificat al datelor cu care poate lucra o aplicație, înfășurate într-o componentă WPF XmlDataProvider. Aceste date pot fi afișate într-o interfață de utilizator similară cu cea prezentată în Fig. 5. Vă rugăm să rețineți că clienții și comenzile pot fi selectate, dar detaliile comenzii există în formă de listă doar pentru citire. Există un motiv pentru aceasta - capacitatea de a selecta un obiect vizual ar trebui să fie furnizată numai atunci când afectează starea aplicației sau este mutabilă.

    Orez. 4. Ierarhizarea comenzilor clienților și a informațiilor despre comandă în format XML





















    Orez. 5 O modalitate de a afișa date XML

    În codul XAML din Fig. Figura 6 descrie cum să utilizați aceste diferite controale pentru a afișa datele ierarhice tocmai afișate. Această fereastră nu necesită cod; acesta există în întregime în codul XAML.

    Orez. 6. Cod XAML pentru legarea datelor XML ierarhice la interfața cu utilizatorul

    „(Sursa de legare=(StaticResource xmlData),
    XPath=clienți/client)"
    Margin="4"
    >







    ItemsSource="(Legatură)"
    >









    x:Name="orderSelector"
    DataContext="(Binding Path=CurrentItem)"
    IsSynchronizedWithCurrentItem="True"
    ItemsSource="(Binding XPath=order)"
    >








    Text="Detalii comandă" />
    DataContext=
    „(Binding ElementName=orderSelector, Path=SelectedItem)”
    ItemsSource="(Binding XPath=orderDetail)">



    Produs:

    (

    )





    Rețineți că utilizarea pe scară largă a interogărilor XPath scurte pentru a indica WPF unde să preia valorile legate. Clasa Binding oferă o proprietate XPath care poate fi atribuită oricărei interogări XPath acceptată de metoda XmlNode.SelectNodes. Internul WPF utilizează această metodă pentru a executa interogări XPath. Din păcate, aceasta înseamnă că, deoarece XmlNode.SelectNodes nu acceptă în prezent utilizarea funcțiilor XPath, legarea de date WPF nu le acceptă.

    Caseta cu listă de clienți și lista de comenzi sunt legate la setul rezultat de noduri de interogare XPath executate de interogarea DataContext a elementului Grid rădăcină. DataContextul listei va returna automat CurrentItem al vizualizării colecției, care este un wrapper pentru colecția XmlNodes creată pentru DataContextul tabelului. Cu alte cuvinte, DataContext al listei este cel selectat în în prezent client. Deoarece ItemsSource a listei este legată implicit la propriul DataContext (deoarece nu a fost specificată nicio altă sursă) și legarea ItemsSource realizează o interogare XPath pentru a prelua elementele din DataContext, apoi ItemsSource este de fapt legat de lista de comenzi ale clientului selectat.

    Amintiți-vă că atunci când vă legați de date XML, legarea reală este de obiectele create de apelul la XmlNode.SelectNodes. Dacă nu ești atent, poți ajunge cu mai multe controale care se leagă la seturi de XmlNodes echivalente din punct de vedere logic, dar diferite din punct de vedere fizic. Acest lucru se datorează faptului că de fiecare dată când apelați XmlNode.SelectNodes, a set nou XmlNode, chiar dacă trimiteți aceeași interogare XPath la același XmlNode de fiecare dată. Acest problema speciala legarea la date XML, astfel încât să le puteți ignora în siguranță atunci când vă legați de obiecte de afaceri.

    Utilizarea controalelor multiple pentru a afișa obiecte de afaceri

    Acum să presupunem că doriți să vă legați de datele din exemplul anterior, dar datele există mai degrabă ca obiecte de afaceri decât în ​​cod XML. Cum va schimba acest lucru modul în care ne legăm la diferite niveluri ale ierarhiei datelor? Cât de asemănătoare sau diferită va fi recepția?

    În codul din Fig. Figura 7 prezintă clasele simple utilizate pentru a crea obiecte de afaceri care stochează datele la care vor fi legate. Aceste clase formează la fel circuit logic, la fel ca datele XML utilizate în secțiunea anterioară.

    Orez. 7. Clase pentru crearea unei ierarhii de obiecte de afaceri

    Client de clasa publica
    {
    șir public Nume (get; set; )
    Lista publică Comenzi ( primiți; setați; )


    {
    returnează aceasta.Nume;
    }
    }

    Ordine de clasă publică
    {
    șir public Desc ( obține; setează; )
    Lista publică Detalii comandă ( obțineți; setați; )

    șir de suprascriere public ToString()
    {
    returneaza aceasta.Desc;
    }
    }

    clasa publică OrderDetail
    {
    șir public Produs ( obține; setează; )
    public int Cantitate ( obține; setează; )
    }

    Codul XAML pentru fereastra care afișează aceste obiecte este prezentat în Fig. 8. Este foarte asemănător cu codul XAML din Fig. 6, dar există între diferențe importante, cărora merită să le acordați atenție. Ceea ce codul XAML nu vede este un constructor de fereastră care creează obiecte de date și setează DataContext, mai degrabă decât codul XAML care setează o referință la acesta ca resursă. Rețineți că niciunul dintre controale nu are proprietatea DataContext setată direct. Toate moștenesc aceeași proprietate DataContext, care este o instanță Listă .

    Orez. 8. Cod XAML pentru legarea obiectelor de afaceri ierarhice la interfața cu utilizatorul








    />
    IsSynchronizedWithCurrentItem="True"
    ItemsSource="(Binding Path=.)"
    />




    IsSynchronizedWithCurrentItem="True"
    ItemsSource="(Binding Path=CurrentItem.Orders)"
    />



    Text="Detalii comandă" />
    ItemsSource="(Binding Path=CurrentItem.Orders.CurrentItem.
    Comanda Detalii)"
    >



    Produs:

    (

    )





    O altă diferență semnificativă la legarea la obiecte de afaceri în loc de XML este că ItemsControl care deține informațiile despre comandă nu trebuie să se lege la SelectedItem din lista de comenzi. Această abordare a fost necesară în cazul legării XML din cauza lipsei unui mod universal de a se referi la elementul curent al unei liste ale cărei elemente provin dintr-o interogare locală XPath.

    Atunci când legarea la obiecte de afaceri în loc de XML, legarea la nivelurile imbricate ale elementelor selectate este o sarcină trivială. Legarea ItemsSource din ItemsControl folosește acest lucru funcție convenabilă, specificând CurrentItem de două ori în calea obligatorie: o dată pentru clientul selectat, o dată pentru comanda selectată. Proprietatea CurrentItem este un membru al ICollectionView subiacent, care include sursa de date, așa cum sa discutat mai sus.

    Mai este unul punct interesant, referindu-se la diferența în modul în care funcționează XML și un obiect de afaceri. Deoarece exemplul XML se leagă de XmlElements, trebuie să furnizați DataTemplates pentru a explica cum să vizualizați clienții și comenzile. Când vă legați de obiecte de afaceri personalizate, puteți evita această muncă suplimentară prin simpla suprascriere a metodei ToString a claselor Customer și Order și lăsând WPF să afișeze rezultatul acelei metode pentru acele obiecte. Acest truc este suficient doar pentru obiectele care pot avea reprezentări simple de text. Atunci când lucrați cu obiecte de date complexe, utilizarea acestei tehnici convenabile poate să nu aibă sens.

    Un control pentru a afișa întreaga ierarhie

    Inainte de în acest moment A arătat doar modalități de afișare a datelor ierarhice prin afișarea fiecărui nivel al ierarhiei în controale separate. Este adesea util și necesar să se demonstreze toate nivelurile unei structuri de date ierarhice într-un singur control. Exemplul canonic al acestei abordări este controlul TreeView, care acceptă afișarea și iterația printr-un număr arbitrar de niveluri de date imbricate.

    Puteți popula un TreeView în WPF cu elemente într-unul din două moduri. Prima modalitate este să adăugați elemente manual în cod sau în XAML, iar a doua este să le creați prin legarea de date.

    Următorul XAML arată cum puteți adăuga TreeViewItems la un TreeView în XAML:







    Tehnica de a crea manual elemente într-un TreeView are sens în situațiile în care controlul va afișa întotdeauna un set mic și static de elemente. Atunci când este nevoie să afișați cantități mari de date care se pot schimba în timp, devine necesară o abordare mai dinamică. În această etapă există două opțiuni. Puteți scrie cod care traversează structura de date, creează TreeViewItems pe baza obiectelor de date pe care le găsește și adaugă acele elemente la TreeView. O alternativă este să folosiți șabloane de date ierarhice și să lăsați toată munca în seama WPF.

    Utilizarea șabloanelor de date ierarhice

    Modul în care WPF ar trebui să vizualizeze datele ierarhice prin șabloane de date ierarhice poate fi exprimat declarativ. Clasa HierarchicalDataTemplate este un instrument care face o punte între o structură de date complexă și o reprezentare vizuală a acestor date. Este foarte asemănător cu un DataTemplate normal, dar vă permite, de asemenea, să specificați de unde provin copiii obiectului de date. De asemenea, puteți furniza clasei HierarchicalDataTemplate un șablon pentru a reda acești copii.

    Să presupunem că acum dorim să afișăm datele prezentate în Fig. 7 în interiorul unui control TreeView. TreeView rezultat poate arăta ceva ca cel prezentat în Fig. 9. Implementarea acestui lucru implică utilizarea a două HierarchicalDataTemplates și a unui DataTemplate.

    Orez. 9. Afișarea unei întregi ierarhii de date într-un TreeView

    Două șabloane ierarhice afișează obiectele Client și Comandă. Deoarece obiectele OrderDetail nu au copii, acestea pot fi redate folosind un DataTemplate non-ierarhic. Proprietatea ItemTemplate a TreeView folosește un șablon pentru obiectele Client, deoarece obiectele Client și obiectele de date sunt conținute la nivelul rădăcină al TreeView. În codul XAML prezentat în Fig. Figura 10 arată cum se reunesc toate piesele acestui puzzle.

    Orez. 10. XAML în spatele TreeView




    xmlns:local="clr-namespace:VariousBindingExamples"
    ObjectType="(x:Type local:Customer)"
    MethodName="CreateCustomers"
    />





    Produs:

    (

    )


    x:Key="OrderTemplate"
    ItemsSource="(Binding Path=OrderDetails)"
    ItemTemplate="(StaticResource OrderDetailTemplate)"
    >


    x:Key="CustomerTemplate"
    ItemsSource="(Binding Path=Comenzi)"
    ItemTemplate="(StaticResource OrderTemplate)"
    >


    ItemsSource="(Binding Path=.)"
    ItemTemplate="(StaticResource CustomerTemplate)"
    />

    Aloca o colecție de obiecte Customer la DataContext al unui tabel (Grid) care conține un TreeView. În codul XAML, acest lucru se poate face folosind ObjectDataProvider, adică într-un mod convenabil apelarea unei metode din XAML. Deoarece DataContext este moștenit în arborele de elemente, DataContextul TreeView oferă o referință la acest set de obiecte Client. Din acest motiv, putem da proprietății sale ItemsSource o legare de „(Binding Path=.)”, care este o modalitate de a indica faptul că proprietatea ItemsSource este legată de DataContext al TreeView.

    Dacă proprietatea ItemTemplate a TreeView nu a fost setată, TreeView va afișa numai obiectele Customer de nivel superior. Deoarece WPF nu știe cum să redea un Client, va apela ToString pe fiecare Client și va afișa acel text pentru fiecare element. Nu va avea nicio modalitate de a afla că fiecare Client are o listă de obiecte Comanda asociată cu acesta, iar fiecare obiect Comanda are o listă de obiecte OrderDetail. Deoarece WPF nu poate înțelege prin magie o schemă de date existentă, este necesar să se explice schema lui WPF, astfel încât să poată vizualiza structura datelor corect.

    HierarchicalDataTemplates intră în joc atunci când trebuie să explicați structura și aspectul datelor către WPF. Șabloanele folosite în această demonstrație conțin arbori vizuali foarte simpli, practic doar câmpuri TextBlock cu o cantitate mică de text în ele. În aplicațiile mai sofisticate, șabloanele pot avea modele 3D interactive rotative, imagini, desene grafica vectoriala, UserControls complexe sau orice alt conținut WPF conceput pentru a reda un obiect de date subiacent.

    Este important să acordați atenție ordinii în care sunt declarate șabloanele. Un șablon trebuie declarat înainte de a putea fi referit printr-o expresie StaticResource. Aceasta este o cerință impusă de cititorul XAML și se aplică tuturor resurselor, nu doar șabloanelor.

    În schimb, șabloanele pot fi referite folosind o expresie DynamicResource în acest caz, ordinea lexicală a declarațiilor șablonului nu este importantă. Cu toate acestea, utilizarea legăturilor DynamicResource, spre deosebire de legăturile StaticResource, vine cu o anumită suprasarcină de execuție, deoarece urmăresc modificările aduse sistemului de resurse. Deoarece nu înlocuim șabloanele în timpul execuției, această suprasarcină este inutilă, așa că cel mai bine este să folosiți referințe StaticResource și să puneți declarațiile șabloane în ordinea corectă.

    Lucrul cu intrarea utilizatorului

    Pentru majoritatea programelor, afișarea datelor este doar jumătate din luptă. O altă provocare majoră este analizarea, acceptarea și respingerea intrărilor utilizatorului. Într-o lume ideală în care toți utilizatorii introduc întotdeauna date logice și precise, aceasta ar fi o sarcină simplă. Dar în lumea reală nu este cazul. Utilizatorii reali fac greșeli de tipar, uită să introducă valorile necesare, introduc valori în locuri greșite, șterg intrările care nu ar trebui să fie șterse, adaugă intrări care nu ar trebui adăugate și, în general, urmează Legea lui Murphy ori de câte ori este posibil.

    Sarcina noastră ca dezvoltatori și arhitecți este să combatem ceea ce utilizatorii vor intra inevitabil din greșeală sau intenție rău intenționată. Infrastructură Legături WPF acceptă validarea intrărilor. În următoarele câteva secțiuni ale acestui articol, voi explica cum să utilizați suportul de validare WPF, precum și cum să afișați utilizatorului erorile detectate prin validare.

    Validarea intrării prin ValidationRules

    Prima versiune a WPF, care făcea parte din Microsoft® .NET Framework 3.0, avea doar suport limitat pentru validarea intrărilor. Clasa Binding avea o proprietate ValidationRules care putea stoca orice număr de clase derivate din ValidationRule. Fiecare dintre aceste reguli poate conține o logică care verifică dacă valoarea legată este validă.

    La acel moment, WPF avea doar o subclasă ValidationRule numită ExceptionValidationRule. Dezvoltatorii ar putea adăuga această regulă la proprietatea ValidationRules a unei legături, care ar prinde apoi excepțiile aruncate în timpul actualizărilor la sursa de date, permițând interfeței cu utilizatorul să afișeze un mesaj de eroare de excepție. Utilitatea acestei abordări pentru validarea intrărilor este discutabilă, având în vedere că fundamentul unei bune experiențe de utilizator este evitarea expunerii acestora la detalii tehnice inutile. Mesajele de eroare din excepțiile de analiză a datelor sunt de obicei astfel de detalii pentru majoritatea utilizatorilor, dar înapoi la subiectul nostru.

    Să presupunem că avem o clasă care reprezintă o epocă de timp, cum ar fi clasa Era simplă prezentată aici:

    Clasa publică Era
    {

    }

    Dacă doriți să permiteți utilizatorului să schimbe data de început și durata unei epoci, puteți utiliza două controale casete de text și puteți lega proprietățile text ale acestora la proprietățile instanței Era. Deoarece utilizatorul poate introduce orice dorește într-un câmp de text, nu există nicio modalitate de a fi sigur că textul pe care îl introduce va fi convertibil într-o instanță DateTime sau TimeSpan. În acest caz, puteți utiliza ExceptionValidationRule pentru a raporta erori în transformarea datelor și apoi afișați acele erori în interfața cu utilizatorul. În codul XAML prezentat în Fig. Figura 11 arată cum poate fi îndeplinită această sarcină.

    Orez. 11. O clasă simplă reprezentând o epocă de timp


    Data de început:









    Durată:
    Grid.Row="3"
    Text="(Legatoare
    Cale=Durata

    />

    Aceste două câmpuri de text demonstrează două moduri prin care o ExceptionValidationRule poate fi adăugată la proprietatea ValidationRules a unei legături în XAML. Caseta text Data de începere utilizează sintaxa elementului de proprietate extinsă pentru a adăuga direct o regulă. Caseta de text Duration folosește o sintaxă scurtă care pur și simplu setează proprietatea de legare ValidatesOnExceptions la true. Ambele legături au proprietatea UpdateSourceTrigger setată la PropertyChanged, astfel încât intrarea să fie verificată de fiecare dată când proprietății Text a câmpului text i se dă o nouă valoare, în loc să aștepte până când controlul își pierde focalizarea. O captură de ecran a programului este prezentată în Fig. 12.

    Orez. 12. ExceptionValidationRule afișează erori de validare

    Afișarea erorilor de validare

    După cum se arată în Fig. 13, caseta de text Durată conține o valoare incorectă. Șirul pe care îl conține nu poate fi convertit într-o instanță TimeSpan. Mesajul de tip toast al casetei de text afișează un mesaj de eroare și o pictogramă roșie mică de eroare apare în partea dreaptă a comenzii. Acest comportament nu este automat, dar este ușor de implementat și adaptat la un caz specific.

    Orez. 13. Vizualizarea erorilor detectate în timpul verificării intrării pentru utilizator




    DockPanel.Dock="Dreapta"
    Marja="2.0"
    ToolTip="Conține date nevalide"
    Lățime="10" Înălțime="10"
    >











    Clasa de validare statică formează relația dintre control și orice erori de validare pe care le conține prin utilizarea proprietăților atașate și a metodelor statice. Aceste proprietăți atașate pot fi făcute referință în XAML pentru a crea descrieri unice de markup despre modul în care interfața cu utilizatorul ar trebui să prezinte erorile întâlnite în timpul validării intrării către utilizator. Codul XAML din fig. 13 este responsabil pentru explicarea modului de redare a mesajelor de eroare de intrare pentru cele două controale casete de text din exemplul anterior.

    Stil („Stil”) din Fig. 13 vizează toate instanțele unui câmp de text din interfața cu utilizatorul. Aplică trei parametri câmpului de text. Primul, Setter, afectează proprietatea Margin a câmpului de text. Proprietatea Margin este setată la o valoare care oferă suficient spațiu pentru a afișa pictograma de eroare în partea dreaptă.

    Următoarea proprietate Setter din Style atribuie ControlTemplate folosit pentru a reda câmpul de text atunci când acesta conține date nevalide. Setează proprietatea atașată Validation.ErrorTemplate la ControlTemplate declarat deasupra Style. Când clasa de validare raportează că validarea a identificat una sau mai multe erori într-un câmp de text, câmpul de text redă mesajul folosind acest șablon. Acesta este ceea ce creează pictograma roșie de eroare prezentată în figură. 12.

    Style conține, de asemenea, un Trigger care monitorizează proprietatea Validation.HasError atașată câmpului de text. Când clasa de validare setează proprietatea atașată HasError la true pentru un anumit câmp de text, declanșatorul din stilul se declanșează și atribuie un mesaj toast câmpului de text. Conținutul mesajului toast este legat de mesajul de eroare al excepției aruncate atunci când încercați să convertiți text dintr-o casetă de text într-o instanță a tipului de date al proprietății sursei de date.

    Validarea intrării prin IDataErrorInfo

    Odată cu lansarea Microsoft .NET Framework 3.5, suportul pentru validarea intrărilor în WPF s-a îmbunătățit dramatic. Abordarea ValidationRule este utilă pentru aplicații simple, dar aplicațiile din lumea reală se ocupă de complexitatea datelor din lumea reală și a regulilor de afaceri. Codarea regulilor de afaceri în obiecte ValidationRule nu numai că leagă acel cod de cadrul WPF, ci și împiedică logica de afaceri să fie acolo unde îi aparține: în obiectele de afaceri!

    Multe aplicații au un nivel de afaceri, în care toată complexitatea procesării regulilor de afaceri este conținută într-un set de obiecte de afaceri. Când este compilat în Microsoft .NET Framework 3.5, puteți utiliza interfața IDataErrorInfo pentru a forța WPF să interogheze obiectele de afaceri indiferent dacă acestea sunt într-o stare validă sau nu. Acest lucru elimină necesitatea de a plasa logica de afaceri în obiecte separate de stratul de afaceri și vă permite să creați obiecte de afaceri care sunt independente de platforma interfeței cu utilizatorul. Deoarece interfața IDataErrorInfo a fost utilizată de câțiva ani, facilitează, de asemenea, reutilizarea obiectelor de afaceri din Windows Forms sau aplicații ASP.NET mai vechi.

    Să presupunem că doriți să oferiți validare pentru o epocă care depășește simpla asigurare că textul furnizat de utilizator este convertibil în tipul de date al proprietății sursei de date. Ar putea avea sens să nu plasăm data de început a unei ere în viitor, deoarece nu știm despre epocile care urmează să vină. Cererea ca epoca să dureze cel puțin o milisecundă poate avea, de asemenea, sens.

    Aceste tipuri de reguli sunt similare cu ideea generală a logicii de afaceri, prin aceea că ambele sunt cazuri de reguli de domeniu. Regulile de domeniul de aplicare sunt create cel mai bine în obiecte care își stochează starea: obiecte de domeniu. În codul prezentat în Fig. 14 prezintă clasa SmartEra, care furnizează mesaje despre erorile detectate de verificare prin interfața IDataErrorInfo.

    Orez. 14. IDataErrorInfo furnizează mesaje despre erorile detectate de verificare

    clasă publică SmartEra
    : System.ComponentModel.IDataErrorInfo
    {
    public DateTime StartDate ( obțineți; setați; )
    public TimeSpan Duration ( obțineți; setați; )

    #region IDataErrorInfo Membrii

    șir public Eroare
    {
    obține (întoarce nul; )
    }

    string public asta
    {
    obține
    {
    șir msg = nul;
    comutator (proprietate)
    {
    cazul „StartDate”:
    dacă (DateTime.Now< this.StartDate)
    msg = "Data de începere trebuie să fie din trecut.";
    pauză;

    cazul „Durata”:
    dacă (acest.Durata.Ticks == 0)
    msg = "O epocă trebuie să aibă o durată.";
    pauză;

    Mod implicit:
    aruncați o nouă excepție ArgumentException(
    „Proprietate nerecunoscută:” + proprietate);
    }
    mesaj returnat;
    }
    }

    #endregion // Membri IDataErrorInfo
    }

    Utilizarea suportului de validare a clasei SmartEra din interfața de utilizator WPF este foarte ușoară. Trebuie doar să spuneți legăturilor că ar trebui să accepte interfața IDataErrorInfo pe obiectul la care sunt legate. Acest lucru se poate face într-unul din două moduri, așa cum se arată în Fig. 15.

    Orez. 15. Utilizarea logicii de validare


    Data de început:










    Durată:
    Grid.Row="3"
    Text="(Legatoare
    Cale=Durata
    UpdateSourceTrigger=PropertyChanged,
    ValidatesOnDataErrors=Adevărat,
    ValidatesOnExceptions=True)"
    />

    Așa cum o ExceptionValidationRule poate fi adăugată la colecția ValidationRules a unei legături, fie explicit, fie implicit, o DataErrorValidationRule poate fi adăugată direct la ValidationRules a unei legături sau proprietatea ValidatesOnDataErrors poate fi setată la true. Ambele abordări au același efect final - sistemul de legare interogează interfața IDataErrorInfo a sursei de date pentru erori detectate de verificare.

    Rezumând

    Există un motiv pentru care mulți dezvoltatori spun că caracteristica lor preferată WPF este suportul extins pentru legarea datelor. Capacitățile de legare ale WPF sunt atât de puternice și omniprezente încât necesită multor dezvoltatori de software să ajusteze modul în care gândesc despre relația dintre date și interfața cu utilizatorul. Multe componente de bază ale WPF lucrează împreună pentru a sprijini cazuri complexe de legare de date, cum ar fi șabloanele, stilurile și proprietățile atașate.

    Cu relativ puține linii de cod XAML, vă puteți exprima intențiile de a afișa o structură de date ierarhică și de a valida intrarea utilizatorului. În situații mai complexe, puteți profita de toate capacitățile sistemului de legare accesând-l în mod programatic. Având la dispoziție o infrastructură atât de puternică, dezvoltatorii care construiesc aplicații moderne de afaceri se pot apropia în sfârșit de a-și atinge obiectivul vechi de a oferi experiențe excelente pentru utilizatori și vizualizări de date convingătoare.