Grafikus C# – Windows Forms tanfolyam › 15.3 lecke

Továbbtanulás · 15.3 lecke · WPF

Adatkötés és MVVM fogalma

Windows Formsban a felület és az adat szinkronban tartása a te dolgod: az eseménykezelő beolvassa a TextBoxot, és beírja az eredményt a Labelbe. Ha az adat sok helyen látszik, minden módosítás után mindenhol frissíteni kell: ez hibaforrás. A WPF adatkötése (data binding) ezt automatizálja: a felületi elem és az adat össze van kötve, és ha az egyik változik, a másik követi. Erre épül az MVVM minta (Model–View–ViewModel), amely a WPF programok szokásos szerkezete: a View (XAML) csak megjelenít, a ViewModel az állapotot és a műveleteket tartja, a Model az adat. Ez rokona annak a rétegzésnek, amelyet a 12. fejezetben megtanultál: itt a felület és a logika szétválasztása a keretrendszer része.

  • kb. 110 perc
  • haladó szint
  • adatkötés és MVVM
Anime stílusú fiú és lány egy C# programozási feladatot beszél meg

A lecke végére

  • használod a {Binding} kifejezést (Path, Mode, DataContext);
  • érted, mikor frissül automatikusan a felület: INotifyPropertyChanged, ObservableCollection;
  • listát jelenítesz meg ItemsSource és DataTemplate segítségével;
  • megírod az ICommand egyszerű megvalósítását (RelayCommand), és gombhoz köted;
  • elmagyarázod az MVVM szerkezetét, és összeveted a 12. fejezet rétegeivel és a presenterrel (12.9);
  • a ViewModelt felület nélkül teszteled.

Mielőtt elkezded

Ehhez a leckéhez a 15.1–15.2 lecke, a 12.6–12.9 lecke (modell, szolgáltatás, rétegek, presenter) és a 6. fejezet gyűjteményei szükségesek.

15.2 Bevezetés az XAML-be →

Az adatkötés alapjai

Kötés két vezérlő között és adatobjektumhoz (XAML)XAML
<!-- 1. Elemek közötti kötés: a címke a csúszka értékét mutatja (nincs C# kód!) -->
<Slider x:Name="sldMeret" Minimum="10" Maximum="40" Value="16"/>
<TextBlock Text="Ez a szöveg a csúszkával nagyítható" FontSize="{Binding Value, ElementName=sldMeret}"/>

<!-- 2. Adatobjektumhoz kötés: a DataContext tulajdonságait érjük el -->
<StackPanel>
<TextBox Text="{Binding Nev, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="{Binding Nev, StringFormat=Szia, {0}!}"/>
<TextBlock Text="{Binding Atlag, StringFormat=Átlag: {0:0.00}}"/>
</StackPanel>
MainWindow.xaml.csC#
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();

DataContext = new Diak { Nev = "Anna", Atlag = 4.5 }; // az ablak (és minden gyereke) ebből az objektumból olvas
}
}
A DataContextA DataContext az az objektum, amelynek tulajdonságaira a {Binding Nev} hivatkozik. Az ablakon beállítva örökli minden gyerek elem. A kötés Path-ja (Nev) a DataContext tulajdonsága; a {Binding Nev} a {Binding Path=Nev} rövidítése.
ModeA OneWay az adatból a felületre (csak megjelenít); a TwoWay mindkét irányba (a TextBox módosítja az adatot); a OneTime csak egyszer olvas. A TextBox.Text alapból TwoWay, a TextBlock.Text OneWay.
UpdateSourceTriggerMikor kerül vissza az érték az adatba: alapból a mező elhagyásakor (LostFocus); a PropertyChanged minden billentyűleütésnél: élő szűréshez, ellenőrzéshez ez kell.
StringFormatA kötés formázhatja az értéket ({0:0.00}, dátum: {0:yyyy.MM.dd}): nincs ToString a kódban. Összetettebb átalakításra az IValueConverter (például bool → Visibility) való.

Jósolj!

A felület TextBlock Text="{Binding Nev}", a DataContext egy Diak objektum. A program futás közben átírja: diak.Nev = "Béla";. Megváltozik-e a felületen a név?

Jóslat ellenőrzése

Nem, ha a Diak egyszerű osztály: a kötés nem tudja, hogy az érték megváltozott. A tulajdonságnak értesítenie kell a kötést: ehhez kell az INotifyPropertyChanged (következő rész).

INotifyPropertyChanged: a „szólj, ha változtam”

ObservableObject.cs és DiakViewModel.cs (using System.ComponentModel; using System.Runtime.CompilerServices;)C#
// A közös alap: minden ViewModel ebből származik
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected void Szol([CallerMemberName] string nev = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nev)); // a nev: a tulajdonság neve (automatikusan)
}

protected bool Allit<T>(ref T mezo, T ertek, [CallerMemberName] string nev = null)
{
if (Equals(mezo, ertek)) { return false; } // nem változott: nem kell értesíteni

mezo = ertek;
Szol(nev);

return true;
}
}

public class DiakViewModel : ObservableObject
{
private string nev = "";
private double atlag;

public string Nev { get { return nev; } set { Allit(ref nev, value); Szol("Udvozles"); } } // a Nev változásakor az Udvozles is új
public double Atlag { get { return atlag; } set { Allit(ref atlag, value); } }

public string Udvozles { get { return "Szia, " + Nev + "!"; } } // számított tulajdonság
}
Az értesítésAz INotifyPropertyChanged egyetlen eseményt (PropertyChanged) ír elő. A tulajdonság setterje kiváltja, a tulajdonság nevével: a WPF kötése feliratkozik rá, és frissíti a felületet. Enélkül a kötés csak az első olvasáskor látja az értéket.
AlaposztályA Szol és az Allit segéd (CallerMemberName: a fordító beírja a tulajdonság nevét) kiküszöböli az ismétlődő kódot. A CommunityToolkit.Mvvm csomag ezt még rövidebbre veszi: [ObservableProperty] attribútum generálja a tulajdonságot.
Számított tulajdonságHa az Udvozles a Nev-ből számolódik, a Nev setterje a Szol("Udvozles") hívással külön jelzi, hogy az is változott. Ezt gyakran elfelejtik.
SzálA felületet érintő értesítést a felületi szálon kell kiváltani (11.7 lecke): háttérszálból a Dispatcher.Invoke kell (a WPF megfelelője az Invoke-nak).

Jósolj!

Az Udvozles számított tulajdonság ("Szia, " + Nev), de a Nev setterje nem hívja a Szol("Udvozles")-t. A felhasználó átírja a nevet. Mi látszik az üdvözlő címkén?

Jóslat ellenőrzése

A régi üdvözlés: a kötés csak azt tudja, hogy a Nev változott, az Udvozles nem. A számított tulajdonságról is értesíteni kell (Szol("Udvozles")).

Listák: ObservableCollection és DataTemplate

MainViewModel.cs és a nézet (XAML)C# / XAML
// ViewModel: ObservableCollection értesít, ha elemet adsz hozzá vagy törölsz (a List<T> nem!)
public ObservableCollection<DiakViewModel> Diakok { get; } = new ObservableCollection<DiakViewModel>();

private DiakViewModel kivalasztott;
public DiakViewModel Kivalasztott { get { return kivalasztott; } set { Allit(ref kivalasztott, value); } }

<!-- A nézet: ItemsSource + DataTemplate: egy elem kinézete -->
<ListBox ItemsSource="{Binding Diakok}" SelectedItem="{Binding Kivalasztott}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- itt a DataContext az ELEM (egy DiakViewModel) -->
<Grid>
<Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions>
<TextBlock Text="{Binding Nev}" FontWeight="SemiBold"/>
<TextBlock Grid.Column="1" Text="{Binding Atlag, StringFormat={}{0:0.00}}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ItemsSourceA gyűjteménymegjelenítő vezérlők (ListBox, ComboBox, DataGrid) elemeit nem egyenként adod hozzá (Items.Add), hanem ItemsSource-ra kötöd a gyűjteményt: a vezérlő minden elemhez létrehozza a megjelenítést.
ObservableCollectionA ObservableCollection<T> értesít a hozzáadásról, törlésről: a lista a felületen magától frissül (a Windows Forms BindingList<T> (12.6 lecke) megfelelője). Egyszerű List<T> esetén a felület nem követi a változást.
DataTemplateA DataTemplate azt írja le, hogyan nézzen ki egy adatelem. Az elem DataContext-je az adott objektum, ezért a {Binding Nev} az elem Nev tulajdonságára vonatkozik. Windows Formsban ehhez saját rajzolás vagy külön vezérlő kellett.
SelectedItemA SelectedItem="{Binding Kivalasztott}" a kiválasztott elemet a ViewModel tulajdonságába köti (TwoWay): a ViewModel tudja, mi van kijelölve, a nélkül, hogy a felületet ismerné.

Jósolj!

A Diakok tulajdonság List<DiakViewModel> típusú, és a program gombnyomásra hozzáad egy elemet (Diakok.Add(...)). Megjelenik-e az új elem a ListBoxban?

Jóslat ellenőrzése

Nem (a lista nem értesít a változásról): a felület csak az első betöltéskor olvassa a listát. ObservableCollection<T> kell. Ugyanaz a probléma, mint a Windows Forms List<T> és DataSource esetében (12.6 lecke).

Parancsok és az MVVM minta

RelayCommand.cs és használat (using System.Windows.Input;)C#
public class RelayCommand : ICommand
{
private readonly Action<object> vegrehajt;
private readonly Func<object, bool> lehet;

public RelayCommand(Action<object> vegrehajt, Func<object, bool> lehet = null) { this.vegrehajt = vegrehajt; this.lehet = lehet; }

public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } }

public bool CanExecute(object p) { return lehet == null || lehet(p); }
public void Execute(object p) { vegrehajt(p); }
}

// a ViewModelben
public ICommand TorolCommand { get; }

TorolCommand = new RelayCommand(_ => Diakok.Remove(Kivalasztott), _ => Kivalasztott != null); // a gomb csak kijelöléssel aktív

<!-- a nézetben: nincs Click eseménykezelő, nincs code-behind -->
<Button Content="Törlés" Command="{Binding TorolCommand}"/>

Model

Az adat és a szabályai (Diak, FeladatLista, szolgáltatások, tárolók: 12.6–12.8 lecke). Nem ismeri a felületet.

ViewModel

A felület állapota és műveletei: a megjelenített adatok (tulajdonságok, ObservableCollection), a kijelölés, a parancsok. Nem ismer vezérlőt, ablakot, MessageBoxot.

View

A megjelenés: XAML (kötésekkel), minimális vagy semmi code-behind. Csak megjelenít és továbbít.

A kapcsolat

A View DataContext-je a ViewModel; a ViewModel a Modellt használja. A kötés és a parancs a „ragasztó”: a ViewModel és a View nem hivatkozik egymásra közvetlenül.

Hasonlóság a 12. fejezettelAz MVVM ugyanaz az elv, amelyet a 12.9 leckében tanultál: a felület, a logika és az adat szétválik. A presenter (MVP) a nézetet interface-en át hívja; az MVVM-ben a kötés végzi az összekötést: a ViewModel nem ismeri a nézetet, még interface-ként sem.
TesztelhetőségA ViewModel egyszerű C# osztály: new MainViewModel(), vm.Diakok.Add(...), vm.TorolCommand.Execute(null), és ellenőrizheted az eredményt ablak nélkül (13.8 lecke). Ez az MVVM legnagyobb gyakorlati előnye.
Parancs és CanExecuteAz ICommand.CanExecute azt is eldönti, hogy a gomb aktív-e: a Command-hoz kötött Button automatikusan tiltódik, ha nem lehet végrehajtani. Windows Formsban ezt kézzel kellett (btn.Enabled = ..., minden változásnál).
Nem kötelezőKis programnál a code-behind is rendben van; az MVVM a nagyobb, hosszabb életű, tesztelendő alkalmazásoknál térül meg. Kezdőként érdemes kipróbálni, hogy értsd az elvet, de nem „dogma”.

Jósolj!

A TorolCommand CanExecute feltétele Kivalasztott != null. A felhasználó törli az utolsó kijelölt elemet, és a kijelölés megszűnik. Mi történik a Törlés gombbal?

Jóslat ellenőrzése

A gomb automatikusan letiltódik (a CommandManager újraértékeli a CanExecute-t), kézi Enabled beállítás nélkül. Ezért olyan kényelmes a parancs a kézi btn.Enabled kezeléshez képest.

📷 Képernyőkép helye

Mit tegyél ide: a diáklista MVVM-mel: a lista, a mezők, a letiltott/aktív Törlés gomb.Javasolt fájlnév: winforms-15-3-mvvm.webp · Alt szöveg: A diáklista MVVM mintával

Hibakeresés: te vagy a hibadetektív

Mi a baj, és hogyan javítod?

Gondold végig az okot, és csak utána nyisd le a megoldást.

1. A felület nem frissül

A tulajdonság értéke változik, a felület mégsem.

Ok és javítás

A setter nem váltja ki a PropertyChanged eseményt (nincs INotifyPropertyChanged, vagy elfelejtetted a Szol() hívást). A számított tulajdonságokról is értesíts.

2. Az új elem nem jelenik meg a listában

A Diakok.Add(...) után a ListBox változatlan.

Ok és javítás

List<T> helyett ObservableCollection<T>. Ha a teljes gyűjteményt cseréled (Diakok = new ...), a tulajdonságnak értesítenie kell.

3. A kötés semmit nem mutat

A TextBlock üres, kivétel nincs.

Ok és javítás

Nincs beállítva a DataContext, vagy elírás van a Path-ban (a kötés csendben elhallgat). Nézd a Visual Studio Output ablakát: „BindingExpression path error: 'Nv' property not found”. A tulajdonság legyen public (nem mező).

4. A gomb mindig aktív/inaktív

A Command CanExecute értéke nem frissül.

Ok és javítás

A CanExecuteChanged nincs bekötve (a CommandManager.RequerySuggested a felületi eseményekre frissít). Egyedi állapotváltozásnál hívd a CommandManager.InvalidateRequerySuggested() metódust.

5. A TextBox csak a mező elhagyásakor ad adatot

Az élő szűrés késik.

Ok és javítás

Az UpdateSourceTrigger alapból LostFocus: UpdateSourceTrigger=PropertyChanged kell a kötésben.

6. A ViewModel MessageBox-ot hív

public void Mentes() { MessageBox.Show("Mentve!"); }   // a ViewModelben
Hiba és javítás

A ViewModel nem ismerheti a felületet (nem tesztelhető). Állítson be egy Uzenet tulajdonságot (kötött TextBlock), vagy egy szolgáltatás-interface-en (IParbeszedSzolgaltatas) át kérjen megjelenítést (12.8 lecke elve).

Mini projekt: diáklista MVVM-mel

Építsd újra a diáknyilvántartó egy kis részét WPF-ben, MVVM szerkezettel:

  1. Projekt: WPF Application (DiakMvvm); mappák: Models, ViewModels, Commands; a Views a MainWindow.xaml.
  2. Model: Diak osztály (név, jegyek listája, átlag számítása): felülettől független, ellenőrzéssel (13.5 lecke).
  3. Alapok: ObservableObject (INotifyPropertyChanged) és RelayCommand a leckéből.
  4. ViewModelek: DiakViewModel (Nev, Atlag, Megjelenites) és MainViewModel: ObservableCollection<DiakViewModel> Diakok, Kivalasztott, UjNev, UjJegy, Szuro, Allapot, parancsok: HozzaadCommand, JegyCommand, TorolCommand (CanExecute: van kijelölés).
  5. Nézet: Grid: bal oldalon ListBox (ItemsSource, ItemTemplate), jobb oldalon a mezők és gombok (mind kötéssel); alul állapotsor a Allapot-ra kötve; code-behind üres (csak DataContext).
  6. Szűrés: a Szuro TextBox (UpdateSourceTrigger=PropertyChanged) a lista nevekre szűr (CollectionViewSource/ICollectionView.Filter vagy külön szűrt gyűjtemény).
  7. Ellenőrzés: az üres név, a hibás jegy (1–5 kívül) az Allapot üzenetben jelenik meg (nem MessageBox).
  8. Teszt: a MainViewModel egységtesztje ablak nélkül (hozzáadás, törlés, jegy, átlag, parancsok CanExecute értéke).
  9. Összehasonlítás: írd le (5–6 mondat): mi lett egyszerűbb, mint a Windows Forms változatban (lista és gomb állapot frissítés), és mi lett nehezebb vagy szokatlan?
Megoldás megnyitása (próbáld ki előbb önállóan!)
Diak.cs, ViewModelek, MainWindow.xaml és tesztC# / XAML
// ---- Models/Diak.cs ----
public class Diak
{
public string Nev { get; private set; }
public List<int> Jegyek { get; } = new List<int>();

public Diak(string nev)
{
if (string.IsNullOrWhiteSpace(nev)) { throw new ArgumentException("A név kötelező."); }

Nev = nev.Trim();
}

public void JegyetAd(int jegy)
{
if (jegy < 1 || jegy > 5) { throw new ArgumentOutOfRangeException("jegy", "A jegy 1 és 5 között lehet."); }

Jegyek.Add(jegy);
}

public double? Atlag { get { return Jegyek.Count == 0 ? (double?)null : Jegyek.Average(); } }
}

// ---- ViewModels/DiakViewModel.cs ----
public class DiakViewModel : ObservableObject
{
public Diak Model { get; }

public DiakViewModel(Diak model) { Model = model; }

public string Nev { get { return Model.Nev; } }
public string Megjelenites { get { return Model.Nev + (Model.Atlag.HasValue ? " (" + Model.Atlag.Value.ToString("0.00") + ")" : " (nincs jegy)"); } }

public void Frissul() { Szol("Megjelenites"); }
}

// ---- ViewModels/MainViewModel.cs ----
public class MainViewModel : ObservableObject
{
private DiakViewModel kivalasztott;
private string ujNev = "", ujJegy = "", allapot = "Kész.", szuro = "";

public ObservableCollection<DiakViewModel> Diakok { get; } = new ObservableCollection<DiakViewModel>();
public ICollectionView Lista { get; }

public DiakViewModel Kivalasztott { get { return kivalasztott; } set { Allit(ref kivalasztott, value); } }
public string UjNev { get { return ujNev; } set { Allit(ref ujNev, value); } }
public string UjJegy { get { return ujJegy; } set { Allit(ref ujJegy, value); } }
public string Allapot { get { return allapot; } set { Allit(ref allapot, value); } }

public string Szuro { get { return szuro; } set { if (Allit(ref szuro, value)) { Lista.Refresh(); } } }

public ICommand HozzaadCommand { get; }
public ICommand JegyCommand { get; }
public ICommand TorolCommand { get; }

public MainViewModel()
{
Lista = CollectionViewSource.GetDefaultView(Diakok);
Lista.Filter = o => ((DiakViewModel)o).Nev.IndexOf(Szuro ?? "", StringComparison.CurrentCultureIgnoreCase) >= 0;

HozzaadCommand = new RelayCommand(_ => Hozzaad());
JegyCommand = new RelayCommand(_ => JegyetAd(), _ => Kivalasztott != null);
TorolCommand = new RelayCommand(_ => { Diakok.Remove(Kivalasztott); Allapot = "Törölve."; }, _ => Kivalasztott != null);
}

private void Hozzaad()
{
try
{
Diakok.Add(new DiakViewModel(new Diak(UjNev)));
UjNev = "";
Allapot = "Diák felvéve (összesen: " + Diakok.Count + ").";
}
catch (ArgumentException ex) { Allapot = ex.Message; } // üzenet kötéssel, nincs MessageBox
}

private void JegyetAd()
{
int jegy;

if (!int.TryParse(UjJegy, out jegy)) { Allapot = "A jegy egész szám kell legyen."; return; }

try { Kivalasztott.Model.JegyetAd(jegy); Kivalasztott.Frissul(); UjJegy = ""; Allapot = "Jegy rögzítve."; }
catch (ArgumentOutOfRangeException ex) { Allapot = ex.Message; }
}
}

// ---- MainWindow.xaml ----
<Window x:Class="DiakMvvm.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Diáklista" Height="380" Width="620">
<DockPanel>
<StatusBar DockPanel.Dock="Bottom"><TextBlock Text="{Binding Allapot}"/></StatusBar>
<Grid Margin="12">
<Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="240"/></Grid.ColumnDefinitions>
<DockPanel Margin="0,0,12,0">
<TextBox DockPanel.Dock="Top" Text="{Binding Szuro, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,8"/>
<ListBox ItemsSource="{Binding Lista}" SelectedItem="{Binding Kivalasztott}" DisplayMemberPath="Megjelenites"/>
</DockPanel>
<StackPanel Grid.Column="1">
<TextBlock Text="Új diák neve:"/>
<TextBox Text="{Binding UjNev, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,6"/>
<Button Content="Hozzáad" Command="{Binding HozzaadCommand}" Margin="0,0,0,16"/>
<TextBlock Text="Jegy a kijelölt diáknak (1–5):"/>
<TextBox Text="{Binding UjJegy, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,6"/>
<Button Content="Jegyet rögzít" Command="{Binding JegyCommand}" Margin="0,0,0,6"/>
<Button Content="Törlés" Command="{Binding TorolCommand}"/>
</StackPanel>
</Grid>
</DockPanel>
</Window>

// ---- MainWindow.xaml.cs: ennyi a teljes code-behind ----
public MainWindow() { InitializeComponent(); DataContext = new MainViewModel(); }

// ---- Teszt (ablak nélkül) ----
MainViewModel vm = new MainViewModel();
vm.UjNev = "Anna"; vm.HozzaadCommand.Execute(null);
Debug.Assert(vm.Diakok.Count == 1);
Debug.Assert(!vm.TorolCommand.CanExecute(null)); // nincs kijelölés
vm.Kivalasztott = vm.Diakok[0];
Debug.Assert(vm.TorolCommand.CanExecute(null));
vm.UjJegy = "5"; vm.JegyCommand.Execute(null);
Debug.Assert(vm.Kivalasztott.Model.Atlag == 5.0);
vm.UjJegy = "7"; vm.JegyCommand.Execute(null);
Debug.Assert(vm.Allapot.Contains("1 és 5"));

Megjegyzés: a using System.ComponentModel;, System.Windows.Data; (CollectionViewSource), System.Collections.ObjectModel; és System.Windows.Input; névterek kellenek. A DiakViewModel.Megjelenites a jegy rögzítése után a Frissul() hívással értesít, így a lista sora magától frissül. A Debug.Assert teszt csak szemléltet: éles projektben MSTest/xUnit teszt kell (13.8 lecke). A kinézet és a pontos viselkedés WPF-verziófüggően kissé eltérhet; futtasd és ellenőrizd.

📷 Képernyőkép helye

Mit tegyél ide: a kész diáklista (szűrés, jegy rögzítése, letiltott Törlés gomb kijelölés nélkül).Javasolt fájlnév: winforms-15-3-kesz.webp · Alt szöveg: A kész MVVM diáklista

Három önálló gyakorlófeladat

Egyszerre csak egyet csinálj: tervezd meg, írd meg, futtasd, ellenőrizd, és csak utána nyisd ki a megoldást.

1. Converter

Írj IValueConvertert, amely egy bool értéket Visibility-re alakít (igaz: látszik, hamis: eltűnik).

Egy lehetséges megoldás

A WPF-ben már van beépített BooleanToVisibilityConverter: erőforrásként (<BooleanToVisibilityConverter x:Key="BoolVis"/>) és Visibility="{Binding VanKivalasztas, Converter={StaticResource BoolVis}}". Saját változat: Convert(...) (bool)value ? Visibility.Visible : Visibility.Collapsed, a ConvertBack throw new NotSupportedException().

2. DataTrigger

Színezd pirosra a lista elemét, ha az átlag 2 alatti.

Egy lehetséges megoldás

A DataTemplate.Triggers alatt <DataTrigger Binding="{Binding Gyenge}" Value="True"><Setter TargetName="szoveg" Property="Foreground" Value="Red"/></DataTrigger> (a TextBlock x:Name="szoveg"); a ViewModelben bool Gyenge számított tulajdonság. A szín nem csak jelzés: a szöveg is mutassa (akadálymentesség).

3. Mentés a ViewModelből

A ViewModel mentsen fájlba a 12.8 lecke tárolójával. Hogyan adod át?

Egy lehetséges megoldás

A MainViewModel konstruktora egy IDiakTarolo-t kap (függőség átadása); a MentCommand a tárolón hív; az App.xaml.cs OnStartup metódusa (composition root, 12.9 lecke) állítja össze: new MainWindow { DataContext = new MainViewModel(new JsonDiakTarolo(ut)) }.

Gyakorlótár: 6 további feladat

Három szinten gyakorolhatsz: alap, közepes és kihívás. Csak a próbálkozás után nyisd le a megoldást.

Alap szint

1. Mi az adatkötés?

Fogalmazd meg egy mondatban.

Megoldás megnyitása

A felületi elem és az adat összekapcsolása, hogy az egyik változását a másik automatikusan kövesse.

2. DataContext

Mi a DataContext?

Megoldás megnyitása

Az az objektum, amelynek tulajdonságaira a {Binding ...} hivatkozik; a gyerek elemek öröklik.

Közepes szint

3. Lista és értesítés

Miért ObservableCollection és nem List?

Megoldás megnyitása

Az ObservableCollection értesít az elemek hozzáadásáról/törléséről, a felület magától frissül.

4. Mode

Mikor TwoWay, mikor OneWay?

Megoldás megnyitása

TwoWay, ha a felhasználó módosíthatja az adatot (TextBox); OneWay, ha csak megjelenítés (TextBlock).

Kihívás szint

5. MVVM és MVP

Miben különbözik a ViewModel a presentertől (12.9 lecke)?

Megoldás megnyitása

A presenter a nézetet interface-en át hívja és állítja; a ViewModel nem ismeri a nézetet: a kötés és a parancs kapcsolja össze.

6. Parancs

Mi a CanExecute haszna?

Megoldás megnyitása

Megadja, hogy a parancs végrehajtható-e: a hozzákötött gomb automatikusan tiltódik/engedélyeződik.

Gyors önellenőrzés

Válaszolj fejben, és csak utána nyisd le a választ.

1. Mit tartalmaz az MVVM három része?

Model (adat és szabály), View (XAML megjelenítés), ViewModel (felület-állapot és parancsok).

2. Mi az INotifyPropertyChanged?

Az interface, amellyel a tulajdonság jelzi a kötésnek, hogy változott.

3. Mi a DataTemplate?

Egy adatelem megjelenésének leírása (listaelemhez, kártyához).

4. Mi az ICommand?

A felületi művelet absztrakciója (Execute, CanExecute); gombhoz kötve.

5. Miért tesztelhető a ViewModel?

Nem ismer felületet: sima osztályként példányosítható és ellenőrizhető.

6. Hol látod a kötési hibákat?

A Visual Studio Output ablakában (BindingExpression path error).

7. Mikor nem kell MVVM?

Kis, rövid életű programnál: a code-behind elég; a minta a nagyobb, tesztelendő alkalmazásoknál térül meg.

Mit tudsz már?

  • Adatkötést használsz (Binding, DataContext, Mode, StringFormat).
  • ViewModelt írsz INotifyPropertyChanged, ObservableCollection és RelayCommand segítségével.
  • Listát jelenítesz meg ItemsSource és DataTemplate segítségével.
  • Érted az MVVM szerkezetét, és összekötöd a 12. fejezet rétegeivel; a ViewModelt felület nélkül teszteled.

Következő lecke: .NET MAUI és többplatformos alkalmazások

Hogyan készíthetsz egyetlen programot, amely Windowson, Androidon, iOS-en és macOS-en is fut.