230 likes | 387 Views
Programozás III. Automatikus állapotértesítés adatkötéskor (…Changed, INofityPropertyChanged, ObservableCollection<T>) Adatformázások Adatkonverziók Erőforrások. Példa. Cél: a személy neve legyen kiírva (adatkötés DataContext állítással), a gombbal lehessen változtatni
E N D
Programozás III. Automatikus állapotértesítés adatkötéskor (…Changed, INofityPropertyChanged, ObservableCollection<T>) Adatformázások Adatkonverziók Erőforrások
Példa • Cél: a személy neve legyen kiírva (adatkötés DataContext állítással), a gombbal lehessen változtatni • Adatkötés a XAML-ben: publicMainWindow() { InitializeComponent(); aktualisSzemely= newSzemely("Piros Péter"); this.DataContext= aktualisSzemely; } <Label Content="{Binding Nev}"/>
Példa folytatás • A gomb Click eseménykezelője: • A konzol kiírás szerint megtörtént a változtatás… • … az adatkötésben semmi sem látszik! privatevoidButton_Click(objectsender, RoutedEventArgse) { aktualisSzemely.Nev= "Kék Péter"; Console.WriteLine(aktualisSzemely.Nev); }
Automatikus állapotértesítés adatkötéskor • Ahhoz, hogy az adatkötések működjenek, a kötés céljának értesítést kell kapnia a forrás adott tulajdonságának a megváltozásáról • Eddigi kötéseinkben többnyire keretrendszeri osztályok tulajdonságai voltak a források… • Pl. a Slider Value tulajdonsága volt a forrás, és a Label Content-je a cél • Ezekben meg van oldva, hogy az adatkötés célját értesíteni tudják • … vagy pedig saját objektum volt a forrás, de csak az adatkötésen keresztül változtatuk benne az adatot • Pl. a lejátszási listás példaprogramban a Szám típusú objektumok Megjegyzés tulajdonsága volt a forrás, ezt változtatgattuk az adatkötésen keresztül • Saját objektumaink alapesetben nem küldenek értesítést a változásukról!
Automatikus állapotértesítés adatkötéskor • Módszerek: • A függőségi tulajdonságok képesek arra, hogy megváltozásukról automatikus értesítést küldjenek. Megoldás: a forrástulajdonság ne sima, hanem függőségi tulajdonság legyen! (nem tárgyaljuk) • …Changed esemény kiváltása a tulajdonság változásakor • INotifyPropertyChanged interfész implementálása
Állapotértesítés – …Changed esemény • A tulajdonság értékének változásakor tüzeljen egy …Changed esemény (…=tulajdonság neve) • Az eseményre való feliratkozás az adatkötéskor automatikus publiceventEventHandlerNevChanged; publicstringNev { get{ returnnev; } set { nev= value; EventHandlernevChanged= NevChanged; if (nevChanged!= null) nevChanged(this, EventArgs.Empty); } }
Állapotértesítés – INotifyPropertyChanged • A tulajdonságot deklaráló osztály implementálja az INotifyPropertyChanged interfészt (System.ComponentModel) • Ebben egyetlen esemény van • public event PropertyChangedEventHandler PropertyChanged; • Ez tüzeljen a tulajdonság értékének változtatásakor, az EventArgs paramétere a tulajdonság neve legyen • string.Empty vagy null, ha az összes tulajdonság megváltozott publicstringNev { get{ returnnev; } set { nev= value; PropertyChangedEventHandlerhandler = PropertyChanged; if (handler != null) handler(this, newPropertyChangedEventArgs("Nev")); } }
Állapotértesítés – INotifyPropertyChanged • Előny az előzőhöz képest: nem kell annyi esemény • Gyakran egynél több tulajdonságnál akarják használni: OnPropertyChanged() privatevoidOnPropertyChanged(stringpropertyName) { PropertyChangedEventHandlerhandler = PropertyChanged; if (handler!= null) handler(this, newPropertyChangedEventArgs(propertyName)); } publicstringNev { get{ returnnev; } set { nev= value; OnPropertyChanged("Nev"); } }
Állapotértesítés – Problémák gyűjteményekkel • Gyűjtemény típusú forrástulajdonságnál az adatkötött GUI-elem (pl. ListBox) a forrásgyűjtemény lecserélését detektálja csak, ha az OnPropertyChanged()-et a setterben hívjuk • Ez nem detektálja: • Ha a gyűjtemény valamelyik elemét lecseréljük • Add()/Remove()/Insert()… • Ha a gyűjtemény valamelyik elemének egy tulajdonságát módosítjuk publicList<HirlevelFeliratkozas> HirlevelFeliratkozasok { get{ returnhirlevelFeliratkozasok; } set { hirlevelFeliratkozasok= value; OnPropertyChanged("HirlevelFeliratkozasok"); //??? } }
Állapotértesítés – Problémák gyűjteményekkel List<HirlevelFeliratkozas> uj = newList<HirlevelFeliratkozas>(); uj.Add(newHirlevelFeliratkozas("gyereknevelés", true)); aktualisSzemely.HirlevelFeliratkozasok= uj; //frissít aktualisSzemely.HirlevelFeliratkozasok[0] = newHirlevelFeliratkozas("gyereknevelés", true); //nem frissít aktualisSzemely.HirlevelFeliratkozasok.Add( newHirlevelFeliratkozas("gyereknevelés", true)); //nem frissít aktualisSzemely.HirlevelFeliratkozasok[0].Aktiv = false; //nem frissít
Állapotértesítés – ObservableCollection<T> • List<T>-hez hasonlóan kell használni • System.Collections.ObjectModel • Értesítést küld: • Ha a gyűjtemény valamelyik elemét lecseréljük • Add()/Remove()/Insert() • Elem tulajdonságának módosításakor NEM! • INotifyCollectionChanged interfészt implementál • Saját osztályaink számára is implementálható, de akkor a fenti esetekre mind gondolni kell privateObservableCollection<HirlevelFeliratkozas> hirlevelFeliratkozasok; publicObservableCollection<HirlevelFeliratkozas> HirlevelFeliratkozasok { get{ returnhirlevelFeliratkozasok; } set { hirlevelFeliratkozasok= value; OnPropertyChanged("HirlevelFeliratkozasok"); } }
Állapotértesítés – ObservableCollection<T> ObservableCollection<HirlevelFeliratkozas> uj = newObservableCollection<HirlevelFeliratkozas>(); uj.Add(newHirlevelFeliratkozas("gyereknevelés", true)); aktualisSzemely.HirlevelFeliratkozasok= uj; //frissít aktualisSzemely.HirlevelFeliratkozasok[0] = newHirlevelFeliratkozas("gyereknevelés", true); //frissít aktualisSzemely.HirlevelFeliratkozasok.Add( newHirlevelFeliratkozas("gyereknevelés", true)); //frissít aktualisSzemely.HirlevelFeliratkozasok[0].Aktiv = false; //nem frissít!
Állapotértesítés – Fennmaradó problémák • ToString()-es kiírásaink sohasem frissülnek… • Lehetőleg elkerüljük a használatát! • Helyette: • Törekszünk egyenként kiírni a tulajdonságokat, adatkötésekkel • ListBoxban ehhez ún. DataTemplate kell, vagy ha egy tulajdonság kiírása elég, akkor DisplayMemberPath • Vannak más megoldások is (nem tárgyaljuk őket)
DataTemplate classSzemely : INotifyPropertyChanged { … privateObservableCollection<HirlevelFeliratkozas> hirlevelFeliratkozasok; publicObservableCollection<HirlevelFeliratkozas> HirlevelFeliratkozasok { get{ returnhirlevelFeliratkozasok; } set { hirlevelFeliratkozasok= value; OnPropertyChanged("HirlevelFeliratkozasok"); } } //+HirlevelFeliratkozas osztályban is implementálvafy- } //van az INotifyPropertyChanged <ListBox ItemsSource="{Binding HirlevelFeliratkozasok}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel> <Label Content="{Binding Tema}"/> <Label Content="{Binding Aktiv}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> Az ItemsControloknak az ItemTemplate, a ContentControloknak a ContentTemplate tulajdonságába mehet ilyen DataTemplate, Panel utódoknál CellTemplate
Állapotértesítés – konklúzió... • Minden korrekt megoldáshoz kell: • INotifyPropertyChanged implementálása az adatkötésben felhasznált összes osztályban • ObservableCollection<T> a többi gyűjtemény helyett • ToString() kerülése • Ha komplex szöveg kéne: a ma még tárgyalt StringFormat és Converter segíthet ebben... (+más megoldások)
Adatformázások, adatkonverziók • Gyakran nem szerencsés, ha az adatkötés forrástulajdonságának értéke változatlanul jelenik meg • Formázások: • Legkönnyebben: …StringFormat tulajdonságok • ContentControl-oknál: ContentStringFormat tulajdonság (nem csak adatkötésnél használható!) • ItemsControl-oknál: ItemStringFormat tulajdonság (nem csak adatkötésnél használható!) • Adatkötéseknél, CSAK HA string a céltulajdonság (pl. TextBox.Text), akkor a {Binding}-on belül StringFormat • Hasonlóan kell használni, mint a string.Format formázóstringjeit (speciális szabályok lehetnek) <ListBox.ItemTemplate> <DataTemplate> <StackPanel> <Label ContentStringFormat="Hírlevéltéma: {0}" Content="{Binding Tema}"/> <Label ContentStringFormat="Feliratkozás aktív: {0}" Content="{Binding Aktiv}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate>
Adatformázások, adatkonverziók • Adatkonverziók: • Gyakran automatikus! • Pl: ListBox kerete vegye fel a ListBox-ban kijelölt színt. A ListBox-ban lévő elemek stringek. • Egyébként az IValueConverter interfészt implementáló konverterosztállyal valósítható meg (System.Windows.Data) • Két metódusa: Convert(), ConvertBack() • Convert(): amikor az értéket átadjuk a forrásból a célba • ConvertBack(): amikor a forrás megkapja az értéket a célból (kétirányú vagy forrás felé irányuló kötésmód esetén) • A ConvertBack() után, amikor a forrásba került az érték, még egyszer hívódik a Convert(). <ListBox x:Name="listBoxSzinek" BorderBrush= "{Binding ElementName=listBoxSzinek,Path=SelectedItem}” .../>
Adatformázások, adatkonverziók • Személy osztály: • Név (string) • Kor (int) • Kötés: szemely.Kor label.Content <Window... xmlns:current="clr-namespace:WpfApplication9” ...> <Grid> <Grid.Resources> <current:KorValueConverter x:Key="KorConverter"/> </Grid.Resources> <Label Content="Személy neve:".../> <Label Content="Személy kora kb.:".../> <Label Content="{Binding Nev}” .../> <Label Content="{Binding Kor, Converter={StaticResource KorConverter}}” ... /> </Grid> </Window> Névtér behivatkozása KorConverter néven hivatkozható konvertáló példány létrehozása a Grid erőforrásszótárában Konvertáló hozzárendelése a kötéshez
Adatformázások, adatkonverziók • Konvertáló osztály • Csak egyirányú kötésre alkalmas ebben a formában, egyébként a visszafelé való konverzió implementálása is szükséges lenne classKorValueConverter : IValueConverter { publicobjectConvert(objectvalue, TypetargetType, objectparameter, System.Globalization.CultureInfoculture) { intkor = (int)value; if (kor < 18) return"gyerek"; elseif (kor < 30) return"fiatal"; elseif (kor < 50) return"középkorú"; elseif (kor < 65) return"idősebb"; else return"idős"; } publicobjectConvertBack(objectvalue, TypetargetType, objectparameter, System.Globalization.CultureInfoculture) { thrownewNotImplementedException(); } }
Erőforrások • Az erőforrás olyan objektum, amit különféle helyeken fel/újrahasználhatunk az alkalmazásunkban • Típusok: • Logikai/objektum erőforrások: tetszőlegesen (XAML-ben) létrehozott objektumok • Bináris erőforrások: képek, ikonok, sztringtáblázatok stb. • A logikai erőforrásokat az erőforrásszótár tartalmazza, mindegyik el van látva névvel (x:Key) • Resources tulajdonság (FrameworkElement utódokban) • Tartalmazott elemek megkapják a tartalmazójuk erőforrásait <Window... <Grid> <Grid.Resources> <current:KorValueConverter x:Key="KorConverter"/> </Grid.Resources> ... </Grid> </Window> KorConverter néven hivatkozható objektumerőforrás (KorValueConverter példány) létrehozása a Grid erőforrásszótárában
Erőforrások • Logikai/objektumerőforrások használata még rengeteg más helyen: • Ecsetek, színek, stílusok, Data Template-ek, konverterek, tetszőleges objektumok (pl. tömbök)… • Pl: szín (ecset) létrehozása az erőforrásszótárban: • Fajtái: • StaticResource: a XAML betöltésekor, egyszer töltődik be, ezután a változtatásokat nem veszi figyelembe • DynamicResource: futásidőben töltődik be, változtatható <Window...> <Grid> <Grid.Resources> <SolidColorBrush x:Key="kedvencSzinem" Color="Azure"/> </Grid.Resources> ... <Label Background="{StaticResource ResourceKey=kedvencSzinem}" Content="Szép színű" .../> <ListBox Background="{StaticResource ResourceKey=kedvencSzinem}" .../> </Grid> </Window>
Erőforrások • Bináris erőforrások: • A projekthez bármilyen fájl hozzáadható, amire az alkalmazásnak később szüksége lehet • A bináris erőforrások közé való felvételük a megfelelő fordítási beállítással érhető el (Solution Explorerben kijelölés, Properties ablak – Build Action) • Resource: a fordító a .NET szerelvénybe építi az adatokat – előnye, hogy így mindig az alkalmazás rendelkezésére állnak • Content: az adatok külön fájlokként állnak rendelkezésre – lecserélhetők, de figyelmetlen felhasználó pl. törölheti őket • Fájlnév szerint hivatkozunk rájuk a XAML-ben <Image Source="LockScreen___0600_0337.jpg" />