diff --git a/2024/hazi/6-tervezesi-mintak/index.html b/2024/hazi/6-tervezesi-mintak/index.html index ce6c523..c1131d0 100644 --- a/2024/hazi/6-tervezesi-mintak/index.html +++ b/2024/hazi/6-tervezesi-mintak/index.html @@ -1916,12 +1916,14 @@

6. Opcionális feladat Tipp: egy fájl tartalmát pl. a File.ReadAllBytes statikus művelettel egy lépésben be lehet olvasni. -
  • Ellenőrizd, hogy az integrációs teszt hiba nélkül lefut.
  • +
  • Ellenőrizd, hogy az integrációs teszt hiba nélkül lefut.
    1. Buildeld meg a projektet
    2. Nyisd meg a Test Explorert (Test/Test Explorer menü)
    3. A teszt futtatására a Test Explorer nézet tetején található eszközsávon levő gombokkal van lehetőség. De a teszt debuggolására is van lehetőség, jobb gombbal a tesztre kattintva és a Debug menü kiválasztásával: ez nagyon hasznos tud lenni, ha a tesztünk hibásan fut, és szeretnénk töréspontok segítségével a kódon lépkedni, illetve a változók értékét megnézni.
    4. Ha a teszt hiba nélkül fut le, a teszthez tartozó ikon zöld lesz. Ha hibával, akkor piros, és a hibaüzenetről a tesztet kiválasztva Test Explorer nézet alján kapunk bővebb információt.
    +
  • +

    7. Opcionális feladat - Unit teszt készítése

    A feladat megoldásával +2 IMSc pont szerezhető.

    A korábbi, 3. feladat során ismertetésre került az egységteszt fogalma. Jelen opcionális feladat célja ennek gyakorlása, jobb megértése egy feladaton keresztül.

    @@ -1992,8 +1994,8 @@

    Kiterjesztési technikák áttekint - - 2024-05-07 + + 2024-05-11 diff --git a/2024/search/search_index.json b/2024/search/search_index.json index 1759032..f06869d 100644 --- a/2024/search/search_index.json +++ b/2024/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["hu"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Szoftvertechnik\u00e1k (2024-t\u0151l)","text":"

    Gyakorlati anyagok \u00e9s h\u00e1zi feladatok a BMEVIAUAB00 Szoftvertechnik\u00e1k c. t\u00e1rgyhoz, 2024 \u00e9vt\u0151l kezd\u0151d\u0151en. A kor\u00e1bbi \u00e9vek anyag\u00e1nak megtekint\u00e9s\u00e9hez az oldal fejl\u00e9c\u00e9ben tal\u00e1lhat\u00f3 leny\u00edl\u00f3 mez\u0151ben a megfelel\u0151 \u00e9vet kell kiv\u00e1lasztani (pl. \"2023-ig\").

    Jav\u00edt\u00e1s az anyagban

    A t\u00e1rgy hallgat\u00f3inak a jegyzet anyag\u00e1ban t\u00f6rt\u00e9n\u0151 jav\u00edt\u00e1s\u00e9rt, kieg\u00e9sz\u00edt\u00e9s\u00e9rt plusz pontot adunk! Ha hib\u00e1t tal\u00e1lsz a jegyzet b\u00e1rmely r\u00e9sz\u00e9ben, vagy kieg\u00e9sz\u00edten\u00e9d azt, nyiss egy pull request-et! A repository linkj\u00e9t a jobb fels\u0151 sarokban tal\u00e1lod.

    Felhaszn\u00e1l\u00e1si felt\u00e9telek

    Az itt tal\u00e1lhat\u00f3 oktat\u00e1si seg\u00e9danyagok a BMEVIAUAB00 t\u00e1rgy hallgat\u00f3inak k\u00e9sz\u00fcltek. Az anyagok oly m\u00f3d\u00fa felhaszn\u00e1l\u00e1sa, amely a t\u00e1rgy oktat\u00e1s\u00e1hoz nem szorosan kapcsol\u00f3dik, csak a szerz\u0151(k) enged\u00e9ly\u00e9vel \u00e9s a forr\u00e1s megjel\u00f6l\u00e9s\u00e9vel t\u00f6rt\u00e9nhet.

    Az anyagok a t\u00e1rgy keret\u00e9ben oktatott kontextusban \u00e9rtelmezhet\u0151ek. Az anyagok\u00e9rt egy\u00e9b felhaszn\u00e1l\u00e1s eset\u00e9n a szerz\u0151(k) felel\u0151ss\u00e9get nem v\u00e1llalnak.

    "},{"location":"egyeb/interfesz-es-absztrakt-os/","title":"Interf\u00e9sz \u00e9s absztrakt (\u0151s)oszt\u00e1ly","text":"

    Utols\u00f3 m\u00f3dos\u00edt\u00e1s ideje: 2022.10.15 Kidolgozta: Benedek Zolt\u00e1n

    A fejezet nem tartalmaz feladatot, a hallgat\u00f3k sz\u00e1m\u00e1ra ismerteti a kapcsol\u00f3d\u00f3 elm\u00e9letet.

    "},{"location":"egyeb/interfesz-es-absztrakt-os/#absztrakt-osztaly","title":"Absztrakt oszt\u00e1ly","text":"

    A fogalmak kor\u00e1bbi t\u00e1rgyak keret\u00e9ben m\u00e1r ismertet\u00e9sre ker\u00fcltek, \u00edgy most csak a legfontosabbakat foglaljuk \u00f6ssze, illetve a C# vonatkoz\u00e1s\u00e1ra t\u00e9r\u00fcnk ki. Absztrakt oszt\u00e1ly Olyan oszt\u00e1ly, mely nem p\u00e9ld\u00e1nyos\u00edthat\u00f3. C# nyelven az oszt\u00e1lydefin\u00edci\u00f3ban az abstract kulcssz\u00f3t kell ki\u00edrni, pl.:

    abstract class Shape { \u2026 }\n

    Absztrakt oszt\u00e1lyoknak lehetnek absztrakt met\u00f3dusaik, melyeknek nem adjuk meg a t\u00f6rzs\u00e9t, ezekn\u00e9l is az abstract kulcssz\u00f3t kell haszn\u00e1lni:

    \u2026\nabstract void Draw();\n\u2026\n

    Absztrakt oszt\u00e1lyok haszn\u00e1lat\u00e1nak k\u00e9t c\u00e9lja lehet:

    .NET k\u00f6rnyezetben, csak\u00fagy, mint Java nyelven, egy oszt\u00e1lynak csak egy \u0151soszt\u00e1lya lehet.

    "},{"location":"egyeb/interfesz-es-absztrakt-os/#interfesz","title":"Interf\u00e9sz","text":"

    Az interf\u00e9sz nem m\u00e1s, mint egy m\u0171velethalmaz. Tulajdonk\u00e9ppen egy olyan absztrakt oszt\u00e1lynak felel meg, melynek minden m\u0171velete absztrakt.

    C# nyelven az interface kulcssz\u00f3val tudunk interf\u00e9szt defini\u00e1lni:

    public interface ISerializable \n{\n   void WriteToStream(Stream s);\n   void LoadFromStream(Stream s);\n}\n\npublic interface IComparable \n{\n   int CompareTo(Object obj);\n}\n

    M\u00edg egy oszt\u00e1lynak csak egy \u0151se lehet, ak\u00e1rh\u00e1ny interf\u00e9szt implement\u00e1lhat:

    public class Rect : Shape, ISerializable, IComparable\n{\n    \u2026\n}\n

    Ebben a p\u00e9ld\u00e1ban Rect oszt\u00e1ly a Shape oszt\u00e1lyb\u00f3l sz\u00e1rmazik, valamint az ISerializable \u00e9s IComparable interf\u00e9szeket implement\u00e1lja (k\u00f6telez\u0151en az \u0151soszt\u00e1lyt kell el\u0151sz\u00f6r megadni). Az interf\u00e9szt implement\u00e1l\u00f3 oszt\u00e1lyban annak valamennyi m\u0171velet\u00e9t meg kell val\u00f3s\u00edtani, vagyis meg kell \u00edrni a t\u00f6rzs\u00e9t (kiv\u00e9ve azt a ritka esetet, amikor absztrakt m\u0171velettel val\u00f3s\u00edtjuk meg). Interf\u00e9szek haszn\u00e1lat\u00e1nak egy f\u0151 c\u00e9lja van. Interf\u00e9szk\u00e9nt hivatkozva egys\u00e9gesen tudjuk az interf\u00e9szt implement\u00e1l\u00f3 valamennyi oszt\u00e1lyt kezelni (pl. heterog\u00e9n kollekci\u00f3). Ennek egy k\u00f6vetkezm\u00e9nye: az interf\u00e9szek lehet\u0151v\u00e9 teszik sz\u00e9les k\u00f6rben haszn\u00e1lhat\u00f3 oszt\u00e1lyok \u00e9s f\u00fcggv\u00e9nyek meg\u00edr\u00e1s\u00e1t. Pl. tudunk \u00edrni egy univerz\u00e1lis Sort sorrendez\u0151 f\u00fcggv\u00e9nyt, mely b\u00e1rmilyen oszt\u00e1llyal haszn\u00e1lhat\u00f3, mely implement\u00e1lja az IComparable interf\u00e9szt.

    Az interf\u00e9sz alkalmaz\u00e1s\u00e1nak el\u0151nyei m\u00e9g:

    "},{"location":"egyeb/interfesz-es-absztrakt-os/#absztrakt-os-es-interfesz-osszehasonlitasa","title":"Absztrakt \u0151s \u00e9s interf\u00e9sz \u00f6sszehasonl\u00edt\u00e1sa","text":"

    Az absztrakt \u0151s el\u0151nye az interf\u00e9sszel szemben, hogy adhatunk meg a m\u0171veletekre vonatkoz\u00f3an alap\u00e9rtelmezett implement\u00e1ci\u00f3t, illetve vehet\u00fcnk fel tagv\u00e1ltoz\u00f3kat.

    Az interf\u00e9szek el\u0151nye az absztrakt \u0151ssel szemben, hogy egy oszt\u00e1ly ak\u00e1rh\u00e1ny interf\u00e9szt implement\u00e1lhat, m\u00edg \u0151se maximum egy lehet.

    Az interf\u00e9szek haszn\u00e1lat\u00e1nak van m\u00e9g egy k\u00f6vetkezm\u00e9nye, ami bizonyos esetben kellemetlens\u00e9geket okozhat. Amikor az interf\u00e9szbe \u00faj m\u0171veletet vesz\u00fcnk fel, akkor valamennyi implement\u00e1l\u00f3 oszt\u00e1lyt szint\u00e9n b\u0151v\u00edteni kell, k\u00fcl\u00f6nben a k\u00f3d nem fordul. Absztrakt \u0151s b\u0151v\u00edt\u00e9se eset\u00e9n ez nincs \u00edgy: amennyiben \u00faj m\u0171veletet vesz\u00fcnk fel, lehet\u0151s\u00e9g\u00fcnk van azt virtu\u00e1lis f\u00fcggv\u00e9nyk\u00e9nt felvenni, \u00e9s \u00edgy az \u0151sben alap\u00e9rtelmezett implement\u00e1ci\u00f3t adni r\u00e1. Ez esetben az lesz\u00e1rmazottak ig\u00e9ny szerint tudj\u00e1k ezt fel\u00fcldefini\u00e1lni, erre nincsenek r\u00e1k\u00e9nyszer\u00edtve. Az interf\u00e9szek ezen tulajdons\u00e1ga k\u00fcl\u00f6n\u00f6sen oszt\u00e1lyk\u00f6nyvt\u00e1rak/keretrendszerek eset\u00e9n lehet kellemetlen. Tegy\u00fck fel, hogy a .NET \u00faj verzi\u00f3j\u00e1nak kiad\u00e1skor a keretrendszer egyik interf\u00e9sz\u00e9be \u00faj m\u0171veletet vesznek fel. Ekkor valamennyi alkalmaz\u00e1sban valamennyi implement\u00e1l\u00f3 oszt\u00e1lyt m\u00f3dos\u00edtani kell, k\u00fcl\u00f6nben nem fordul a k\u00f3d. Ezt k\u00e9tf\u00e9lek\u00e9ppen lehet elker\u00fclni. Vagy \u0151soszt\u00e1ly haszn\u00e1lat\u00e1val, vagy ha m\u00e9gis interf\u00e9szt kellene b\u0151v\u00edteni, akkor ink\u00e1bb \u00faj interf\u00e9szt bevezet\u00e9s\u00e9vel, amely m\u00e1r az \u00faj m\u0171veletet is tartalmazza. B\u00e1r itt az els\u0151 megk\u00f6zel\u00edt\u00e9s (\u0151soszt\u00e1ly alkalmaz\u00e1sa) t\u0171nik els\u0151 \u00e9rz\u00e9sre vonz\u00f3bbnak, ennek is van h\u00e1tr\u00e1nya: ha az alkalmaz\u00e1s fejleszt\u00e9sekor egy keretrendszerbeli \u0151sb\u0151l sz\u00e1rmaztatunk, akkor oszt\u00e1lyunknak m\u00e1r nem lehet m\u00e1s \u0151se, \u00e9s ez bizony sok esetben f\u00e1jdalmas megk\u00f6t\u00e9st jelent.

    \u00c9rdemes tudni, hogy C# 8-t\u00f3l (illetve .NET vagy .NET Core runtime is kell hozz\u00e1, .NET Framework alatt nem t\u00e1mogatott) kezdve interf\u00e9sz m\u0171veleteknek is lehet alap\u00e9rtelmezett implement\u00e1ci\u00f3t adni (default interface methods), \u00edgy a fenti probl\u00e9ma megold\u00e1s\u00e1hoz nincs sz\u00fcks\u00e9g absztrakt oszt\u00e1lyra, de interf\u00e9sznek tov\u00e1bbiakban sem lehet tagv\u00e1ltoz\u00f3ja. B\u0151vebben inform\u00e1ci\u00f3 itt: default interface methods.

    Mivel mind az interf\u00e9szek, mind az absztrakt \u0151soszt\u00e1lyok alkalmaz\u00e1sa j\u00e1rhat negat\u00edv k\u00f6vetkezm\u00e9nyekkel is, sz\u00e1mos esetben a kett\u0151 egy\u00fcttes haszn\u00e1lat\u00e1val tudjuk kihozni megold\u00e1sunkb\u00f3l a maximumot (vagyis lesz a k\u00f3dunk k\u00f6nnyen b\u0151v\u00edthet\u0151 \u00fagy, hogy nem, vagy csak minim\u00e1lis m\u00e9rt\u00e9kben tartalmaz k\u00f3dduplik\u00e1ci\u00f3t).

    "},{"location":"egyeb/interfesz-es-absztrakt-os/index_eng/","title":"Interface and abstract (base) class","text":"

    Last modified date: 2022.10.15 Edited by Zolt\u00e1n Benedek

    The chapter does not contain an exercise, it introduces the related theory to students.

    "},{"location":"egyeb/interfesz-es-absztrakt-os/index_eng/#abstract-class","title":"Abstract class","text":"

    The concepts have been covered in previous topics, so for now we will just summarize the most important ones and focus on the C# aspect. Abstract class A class that cannot be instantiated. In C#, in the class definition, the abstract keyword must be written out, e.g.:

    abstract class Shape { ... }\n

    Abstract classes may have abstract methods that do not have a root, and for these abstract methods the abstract keyword should be used:

    ...\nabstract void Draw();\n...\n

    There are two purposes for using abstract classes:

    in .NET, as in Java, a class can have only one base class class.

    "},{"location":"egyeb/interfesz-es-absztrakt-os/index_eng/#interface","title":"Interface","text":"

    An interface is nothing more than a set of operations. In fact, it corresponds to an abstract class whose all operations are abstract.

    In C# you can define an interface with the interface keyword:

    public interface ISerializable \n{\n   void WriteToStream(Stream s);\n   void LoadFromStream(Stream s);\n}\n\npublic interface IComparable \n{\n   int CompareTo(Object obj);\n}\n

    While a class can have only one base class, it can implement any number of interfaces:

    public class Rect : Shape, ISerializable, IComparable\n{\n    ...\n}\n

    In this example, the Rect class is derived from the Shape class and implements the ISerializable and IComparable interfaces (mandatory to specify the base class first). In a class implementing an interface, all its operations must be implemented, i.e., its trunk must be written (except in the rare case where it is implemented by an abstract operation). There is one main purpose for using interfaces. Referenced as an interface, we can uniformly manage all the classes that implement the interface (e.g., a heterogeneous collection). One consequence of this is that interfaces allow us to write classes and functions that can be used in a wide variety of ways. For example, we can write a universal Sort ordering function that can be used with any class that implements the IComparable interface.

    Other benefits of using the interface include:

    "},{"location":"egyeb/interfesz-es-absztrakt-os/index_eng/#comparison-of-abstract-base-class-and-interface","title":"Comparison of abstract base class and interface","text":"

    The advantage of the abstract base class over the interface is that you can specify a default implementation for the operations and include member variables.

    The advantage of interfaces over abstract ancestors is that a class can implement any number of interfaces, while its base class can implement at most one.

    There is another consequence of using interfaces, which can cause inconvenience in some cases. When a new operation is added to the interface, all implementing classes must also be extended, otherwise the code will not compile. This is not the case when extending an abstract base class: if you add a new operation, you have the option to add it as a virtual function, and thus give it a default implementation in the ancestor. In this case, the descendants can redefine this as they wish, they are not forced to do so. This feature of interfaces can be particularly inconvenient for class libraries/framework systems. Suppose a new version of .NET is released and a new operation is added to one of the interfaces of the framework. All implementing classes in all applications must then be modified, otherwise the code will not compile. There are two ways to avoid this. Either by using a legacy class, or, if an interface should be extended, by introducing a new interface that already contains the new operation. Although the first approach (using an base class class) seems more attractive at first sight, it also has a drawback: if you derive from an ancestor in the framework when developing your application, your class can have no other ancestor, and this is a painful constraint in many cases.

    It's worth knowing that starting from C# 8 (or .NET or .NET Core runtime, not supported under .NET Framework), interface operations can be given a default implementation (default interface methods), so no abstract class is needed to solve the above problem, but an interface can no longer have a member variable. More information here: default interface methods.

    Since using both interfaces and abstract classes can have negative consequences, in many cases we can get the most out of our solution by using both (i.e., our code can be easily extended with no or minimal code duplication).

    "},{"location":"egyeb/interfesz-es-absztrakt-os/index_ger/","title":"Schnittstelle und abstrakte (angestammte) Klasse","text":"

    Letztes \u00c4nderungsdatum: 2022.10.15 Er hat trainiert: Zolt\u00e1n Benedek

    Das Kapitel enth\u00e4lt keine \u00dcbung, sondern bietet den Studierenden eine Einf\u00fchrung in die entsprechende Theorie.

    "},{"location":"egyeb/interfesz-es-absztrakt-os/index_ger/#abstrakte-klasse","title":"Abstrakte Klasse","text":"

    Die Konzepte wurden bereits in fr\u00fcheren Themen behandelt, so dass wir jetzt nur die wichtigsten zusammenfassen und uns auf den C#-Aspekt konzentrieren werden. Abstrakte Klasse Eine Klasse, die nicht instanziiert werden kann. In C# sollte in der Klassendefinition das abstrakte Schl\u00fcsselwort geschrieben werden, z.B.:

    abstract class Shape { ... }\n

    Abstrakte Klassen k\u00f6nnen abstrakte Methoden haben, die keine Wurzel haben, und f\u00fcr diese abstrakten Methoden sollte das Schl\u00fcsselwort abstract verwendet werden:

    ..\nabstract void Draw();\n..\n

    Es gibt zwei Gr\u00fcnde f\u00fcr die Verwendung abstrakter Klassen:

    in .NET, wie auch in Java, kann eine Klasse nur eine Vorg\u00e4ngerklasse haben.

    "},{"location":"egyeb/interfesz-es-absztrakt-os/index_ger/#schnittstelle","title":"Schnittstelle","text":"

    Eine Schnittstelle ist nichts anderes als eine Reihe von Operationen. Sie entspricht in der Tat einer abstrakten Klasse, deren s\u00e4mtliche Operationen abstrakt sind.

    In C# k\u00f6nnen Sie eine Schnittstelle mit dem Schl\u00fcsselwort interface definieren:

    public interface ISerializable \n{\n   void WriteToStream(Stream s);\n   void LoadFromStream(Stream s);\n}\n\npublic interface IComparable \n{\n   int CompareTo(Object obj);\n}\n

    W\u00e4hrend eine Klasse nur einen Vorfahren haben kann, kann sie eine beliebige Anzahl von Schnittstellen implementieren:

    public class Rect : Shape, ISerializable, IComparable\n{\n    ..\n}\n

    In diesem Beispiel ist die Klasse Rect von der Klasse Shape abgeleitet und implementiert die Schnittstellen ISerializable und IComparable (die Vorg\u00e4ngerklasse muss zuerst angegeben werden). In der Klasse, die die Schnittstelle implementiert, m\u00fcssen alle ihre Operationen implementiert werden, d. h. ihr Stamm muss geschrieben werden (au\u00dfer in dem seltenen Fall, dass sie durch eine abstrakte Operation implementiert wird). Die Verwendung von Schnittstellen hat vor allem einen Zweck. Als Schnittstelle referenziert, k\u00f6nnen wir alle Klassen, die die Schnittstelle implementieren, einheitlich verwalten (z. B. heterogene Sammlung). Eine Folge davon ist, dass Schnittstellen es Ihnen erm\u00f6glichen, Klassen und Funktionen zu schreiben, die auf vielf\u00e4ltige Weise verwendet werden k\u00f6nnen. Wir k\u00f6nnen zum Beispiel eine universelle Sortierfunktion schreiben, die mit jeder Klasse verwendet werden kann, die die Schnittstelle IComparable implementiert.

    Weitere Vorteile der Nutzung der Schnittstelle sind:

    "},{"location":"egyeb/interfesz-es-absztrakt-os/index_ger/#vergleich-von-abstraktem-vorfahren-und-schnittstelle","title":"Vergleich von abstraktem Vorfahren und Schnittstelle","text":"

    Der Vorteil des abstrakten Vorg\u00e4ngers gegen\u00fcber der Schnittstelle besteht darin, dass Sie eine Standardimplementierung f\u00fcr die Operationen angeben und Membervariablen einschlie\u00dfen k\u00f6nnen.

    Der Vorteil von Schnittstellen gegen\u00fcber abstrakten Vorfahren besteht darin, dass eine Klasse eine beliebige Anzahl von Schnittstellen implementieren kann, w\u00e4hrend ihr Vorfahre h\u00f6chstens eine implementieren kann.

    Die Verwendung von Schnittstellen hat noch eine weitere Konsequenz, die in einigen F\u00e4llen zu Unannehmlichkeiten f\u00fchren kann. Wenn eine neue Operation zur Schnittstelle hinzugef\u00fcgt wird, m\u00fcssen alle implementierenden Klassen ebenfalls erweitert werden, sonst l\u00e4sst sich der Code nicht kompilieren. Dies ist bei der Erweiterung eines abstrakten Vorg\u00e4ngers nicht der Fall: Wenn Sie eine neue Operation hinzuf\u00fcgen, haben Sie die M\u00f6glichkeit, sie als virtuelle Funktion hinzuzuf\u00fcgen und ihr somit eine Standardimplementierung im Vorg\u00e4nger zu geben. In diesem Fall k\u00f6nnen die Nachkommen dies nach Belieben umdefinieren, sie sind nicht dazu gezwungen. Diese Eigenschaft von Schnittstellen kann f\u00fcr Klassenbibliotheken/Framework-Systeme besonders unangenehm sein. Angenommen, eine neue Version von .NET wird ver\u00f6ffentlicht und eine neue Operation wird zu einer der Schnittstellen des Frameworks hinzugef\u00fcgt. Alle implementierenden Klassen in allen Anwendungen m\u00fcssen dann ge\u00e4ndert werden, da der Code sonst nicht kompiliert werden kann. Es gibt zwei M\u00f6glichkeiten, dies zu vermeiden. Entweder durch Verwendung einer Legacy-Klasse oder, wenn eine Schnittstelle erweitert werden soll, durch Einf\u00fchrung einer neuen Schnittstelle, die die neue Operation bereits enth\u00e4lt. Obwohl der erste Ansatz (Verwendung einer Vorg\u00e4ngerklasse) auf den ersten Blick attraktiver erscheint, hat er auch einen Nachteil: Wenn Sie bei der Entwicklung Ihrer Anwendung von einem Vorg\u00e4nger im Framework ableiten, kann Ihre Klasse keinen weiteren Vorg\u00e4nger haben, und das ist in vielen F\u00e4llen eine schmerzhafte Einschr\u00e4nkung.

    Es ist wichtig zu wissen, dass ab C# 8 (oder .NET oder .NET Core Runtime, nicht unterst\u00fctzt unter .NET Framework), Schnittstellenoperationen eine Standardimplementierung (Standardschnittstellenmethoden) gegeben werden kann, so dass keine abstrakte Klasse ben\u00f6tigt wird, um das obige Problem zu l\u00f6sen, aber eine Schnittstelle kann nicht mehr eine Mitgliedsvariable haben. Weitere Informationen finden Sie hier: Standardschnittstellenmethoden.

    Da sowohl die Verwendung von Schnittstellen als auch von abstrakten Klassen negative Folgen haben kann, k\u00f6nnen wir in vielen F\u00e4llen das meiste aus unserer L\u00f6sung herausholen, wenn wir beides verwenden (d. h. unser Code kann ohne oder mit nur minimaler Code-Duplizierung leicht erweitert werden).

    "},{"location":"egyeb/uml-kod-kapcsolata/","title":"Az UML oszt\u00e1lydiagram \u00e9s a k\u00f3d kapcsolat\u00e1nak elm\u00e9lete","text":"

    Utols\u00f3 m\u00f3dos\u00edt\u00e1s ideje: 2022.10.15 Kidolgozta: Benedek Zolt\u00e1n

    A fejezet nem tartalmaz feladatot, a hallgat\u00f3k sz\u00e1m\u00e1ra ismerteti a kapcsol\u00f3d\u00f3 elm\u00e9letet.

    "},{"location":"egyeb/uml-kod-kapcsolata/#bevezeto","title":"Bevezet\u0151","text":"

    A fejezet egy r\u00f6vid, v\u00e1zlatos \u00e1ttekint\u00e9st ad az UML oszt\u00e1lydiagram \u00e9s a forr\u00e1sk\u00f3d k\u00f6z\u00f6tti lek\u00e9pez\u00e9s alapjair\u00f3l, a megel\u0151z\u0151 f\u00e9l\u00e9vben Szoftvertechnol\u00f3gia t\u00e1rgyb\u00f3l m\u00e1r tanultak ism\u00e9tl\u00e9sek\u00e9nt.

    Napjainkban sz\u00e1mos szoftverfejleszt\u00e9si m\u00f3dszertan l\u00e9tezik. Ezek k\u00fcl\u00f6nb\u00f6z\u0151 m\u00e9rt\u00e9kben \u00e9p\u00edtenek arra, illetve k\u00f6vetelik meg, hogy a szoftver elk\u00e9sz\u00edt\u00e9se sor\u00e1n modellez\u00e9st alkalmazzunk. Az azonban k\u00e9ts\u00e9gtelen, hogy m\u00e9g a legagilisabb, legink\u00e1bb \u201ek\u00f3dcentrikus\u201d szeml\u00e9letm\u00f3dok k\u00f6vet\u0151i is hasznosnak \u00edt\u00e9lik a szoftver fontosabb/komplexebb komponenseinek \u00e9s szerkezeti elemeinek vizu\u00e1lis modellez\u00e9s\u00e9t annak grafikus volt\u00e1b\u00f3l ad\u00f3d\u00f3 nagyobb kifejez\u0151 ereje miatt.

    Tegy\u00fck fel, hogy feladatunk egy alkalmaz\u00e1s, vagy annak adott modulj\u00e1nak elk\u00e9sz\u00edt\u00e9se. A v\u00e1lasztott m\u00f3dszertanunkat k\u00f6vetve \u2013 j\u00f3 es\u00e9llyel t\u00f6bb iter\u00e1ci\u00f3ban \u2013 a k\u00f6vetelm\u00e9ny elemz\u00e9s, anal\u00edzis, tervez\u00e9s, implement\u00e1ci\u00f3 \u00e9s tesztel\u00e9s l\u00e9p\u00e9seit fogjuk \u00e9rinteni. Koncentr\u00e1ljunk most a tervez\u00e9si f\u00e1zisra. Ennek sor\u00e1n elk\u00e9sz\u00fcl a rendszer (legal\u00e1bbis bizonyos r\u00e9szeinek) r\u00e9szletes terve, mely kimenete a r\u00e9szletes/ implement\u00e1ci\u00f3s terv, illetve modell. Ezen a szinten a modellben szerepl\u0151 bizonyos elemek (pl. oszt\u00e1lyok) egy\u00e9rtelm\u0171en lek\u00e9pezhet\u0151k az adott alrendszer implement\u00e1ci\u00f3j\u00e1ul v\u00e1lasztott programoz\u00e1si nyelv elemeire. Ha j\u00f3 a fejleszt\u0151/modellez\u0151 eszk\u00f6z\u00fcnk, akkor az le tudja gener\u00e1lni az oszt\u00e1lyok v\u00e1z\u00e1t (pl. C++, Java, C# oszt\u00e1lyok). A feladatunk ezt k\u00f6vet\u0151en a gener\u00e1lt k\u00f3dban szerepl\u0151 a met\u00f3dusok t\u00f6rzs\u00e9nek kit\u00f6lt\u00e9se.

    "},{"location":"egyeb/uml-kod-kapcsolata/#fogalmak","title":"Fogalmak","text":"

    Ahhoz, hogy a k\u00f3dgener\u00e1l\u00e1s el\u0151nyeivel \u00e9lni tudjunk, a k\u00f6vetkez\u0151kkel kell tiszt\u00e1ban legy\u00fcnk: ismern\u00fcnk kell, hogy az adott modellez\u0151 eszk\u00f6z az egyes modell elemeket hogyan k\u00e9pezi le az adott programoz\u00e1si nyelv elemeire. A lek\u00e9pez\u00e9s f\u00fcgg a nyelvt\u0151l \u00e9s a modellez\u0151 eszk\u00f6zt\u0151l is, nincs r\u00e1 univerz\u00e1lis szabv\u00e1ny. A lek\u00e9pez\u00e9sek \u00e1ltal\u00e1ban magukt\u00f3l \u00e9rtet\u0151d\u0151ek, t\u00fal nagy elt\u00e9r\u00e9s nem szokott lenni.

    A k\u00f6vetkez\u0151kben azt tekintj\u00fck \u00e1t, hogy az UML oszt\u00e1lydiagram egyes modellelemei hogyan k\u00e9pz\u0151dnek le forr\u00e1sk\u00f3dra, \u00e9s viszont.

    "},{"location":"egyeb/uml-kod-kapcsolata/#osztalyok-lekepezese","title":"Oszt\u00e1lyok lek\u00e9pez\u00e9se","text":"

    Mondhatni trivi\u00e1lisan egyszer\u0171:

    Egy p\u00e9lda:

    , mely a k\u00f6vetkez\u0151 k\u00f3dnak felel meg C# nyelven:

    public abstract class Shape\n{\n    private int x;\n    private int y;\n    public Shape(int x, int y) { this.x = x; this.y = y; }\n    public abstract void Draw(Graphics gr);\n}\n

    A l\u00e1that\u00f3s\u00e1g kapcs\u00e1n a lek\u00e9pez\u00e9s:

    Enn\u00e9l izgalmasabb k\u00e9rd\u00e9sk\u00f6r, hogy milyen m\u00f3don t\u00f6rt\u00e9nik az oszt\u00e1lyok k\u00f6z\u00f6tti kapcsolatok lek\u00e9pez\u00e9se, ezt a k\u00f6vetkez\u0151 fejezetek ismertetik.

    "},{"location":"egyeb/uml-kod-kapcsolata/#i-altalanositas-specializacio-kapcsolat","title":"I. \u00c1ltal\u00e1nos\u00edt\u00e1s, specializ\u00e1ci\u00f3 kapcsolat","text":"

    C# lek\u00e9pez\u00e9s:

    public class Base\n{ };\npublic class Derived : Base\n{ };\n
    "},{"location":"egyeb/uml-kod-kapcsolata/#ii-asszociacio","title":"II. Asszoci\u00e1ci\u00f3","text":"

    Ez a kapcsolatt\u00edpus mindig kommunik\u00e1ci\u00f3t jelent az oszt\u00e1lyok objektumai k\u00f6z\u00f6tt. Egy adott oszt\u00e1ly ig\u00e9nybe veszi egy m\u00e1sik oszt\u00e1ly szolg\u00e1ltat\u00e1sait.

    "},{"location":"egyeb/uml-kod-kapcsolata/#a-lekepezes-01-multiplicitasu-asszociacios-kapcsolat-eseten","title":"A) Lek\u00e9pez\u00e9s 0..1 multiplicit\u00e1s\u00fa asszoci\u00e1ci\u00f3s kapcsolat eset\u00e9n","text":"

    Ebben az esetben egy pointert vagy referenci\u00e1t tartalmaz a kliens oszt\u00e1ly, melyen kereszt\u00fcl ig\u00e9nybe tudja venni a c\u00e9loszt\u00e1ly szolg\u00e1ltat\u00e1sait (meg tudja h\u00edvni annak m\u0171veleteit). P\u00e9lda:

    C++ lek\u00e9pez\u00e9s:

    class Application\n{\n   WindowManager* windowManager;\n};\n\nclass WindowManager\n{\n};\n

    C# lek\u00e9pez\u00e9s (nincsenek pointerek, csak referenci\u00e1k):

    class Application\n{\n   WindowManager windowManager;\n};\n\nclass WindowManager\n{\n};\n

    Mink\u00e9t esetben azt l\u00e1tjuk, hogy a kliens oszt\u00e1lyba felvesz\u00fcnk egy pointer vagy referencia tagv\u00e1ltoz\u00f3t, melynek t\u00edpusa megegyezik az asszoci\u00e1ci\u00f3ban hivatkozott c\u00e9loszt\u00e1ly t\u00edpus\u00e1val, illetve a tagv\u00e1ltoz\u00f3 neve az asszoci\u00e1ci\u00f3s kapcsolatra a c\u00e9loszt\u00e1lyra megadott szereppel (role), mely a p\u00e9ld\u00e1ban a windowManager. A lek\u00e9pez\u00e9s logikus, hiszen a kliens ezen pointeren/referenci\u00e1n kereszt\u00fcl tudja a c\u00e9lobjektumot b\u00e1rmely m\u0171velet\u00e9b\u0151l el\u00e9rni \u00e9s met\u00f3dusait megh\u00edvni.

    Megjegyz\u00e9s. El\u0151fordulhat, hogy az asszoci\u00e1ci\u00f3 k\u00e9tir\u00e1ny\u00fa, mindk\u00e9t oszt\u00e1ly ig\u00e9nybe veszi a m\u00e1sik szolg\u00e1ltat\u00e1sait. Ilyenkor sokszor nem tessz\u00fck ki az asszoci\u00e1ci\u00f3 mindk\u00e9t v\u00e9g\u00e9re a nyilat, hanem mindk\u00e9t v\u00e9g\u00e9r\u0151l elhagyjuk azt. Ilyen k\u00e9tir\u00e1ny\u00fa kapcsolat eset\u00e9n a szerepet (role) a kapcsolat mindk\u00e9t v\u00e9g\u00e9n meg kell adni. A lek\u00e9pez\u00e9s sor\u00e1n mindk\u00e9t oszt\u00e1lyba felvesz\u00fcnk egy pointert/referenci\u00e1t a m\u00e1sikra.

    "},{"location":"egyeb/uml-kod-kapcsolata/#b-lekepezes-0n-multiplicitasu-asszociacios-kapcsolat-eseten","title":"B) Lek\u00e9pez\u00e9s 0..n multiplicit\u00e1s\u00fa asszoci\u00e1ci\u00f3s kapcsolat eset\u00e9n","text":"

    Ebben az esetben egy kliensoldali objektum t\u00f6bb c\u00e9loldali objektummal van kapcsolatban. P\u00e9lda:

    Egy WindowManager objektum t\u00f6bb Window objektumot menedzsel. A lek\u00e9pez\u00e9s sor\u00e1n a kliens oszt\u00e1lyba a c\u00e9loszt\u00e1lybeli objektumok valamilyen gy\u0171jtem\u00e9ny\u00e9t vessz\u00fck fel. Ez lehet t\u00f6mb, lista stb., ami a c\u00e9lunknak az adott helyzetben legink\u00e1bb megfelel.

    Egy lek\u00e9pz\u00e9si lehet\u0151s\u00e9g a fenti p\u00e9ld\u00e1ra C++ nyelven:

    class WindowManager\n{\n  vector<Window*> windows;\n};\n

    Illetve C# nyelven:

    class WindowManager\n{\n  List<Window> windows; \n};\n
    "},{"location":"egyeb/uml-kod-kapcsolata/#iii-aggregacio-tartalmazas-resz-egesz-viszony","title":"III. Aggreg\u00e1ci\u00f3 (tartalmaz\u00e1s, r\u00e9sz-eg\u00e9sz viszony)","text":"

    \u00c1ltal\u00e1ban a lek\u00e9pez\u00e9se pontosan \u00fagy t\u00f6rt\u00e9nik, mint az asszoci\u00e1ci\u00f3 eset\u00e9ben.

    "},{"location":"egyeb/uml-kod-kapcsolata/#iv-fuggoseg-dependency","title":"IV. F\u00fcgg\u0151s\u00e9g (dependency)","text":"

    A leglaz\u00e1bb kapcsolatot jelenti oszt\u00e1lyok k\u00f6z\u00f6tt. P\u00e9lda:

    A jelent\u00e9se: a Window oszt\u00e1ly f\u00fcgg a Graphics oszt\u00e1lyt\u00f3l. Vagyis, ha a Graphics oszt\u00e1ly megv\u00e1ltozik, akkor lehet, hogy a Window oszt\u00e1lyt is meg kell v\u00e1ltoztatni. Ezt a kapcsolatt\u00edpust akkor szoktuk haszn\u00e1lni, ha a f\u00fcgg\u0151s\u00e9gi kapcsolat elej\u00e9n lev\u0151 oszt\u00e1ly met\u00f3dusai param\u00e9terlist\u00e1j\u00e1ban/visszat\u00e9r\u00e9si \u00e9rt\u00e9k\u00e9ben szerepel a kapcsolat v\u00e9g\u00e9n lev\u0151 oszt\u00e1ly. A p\u00e9ld\u00e1ban a Window oszt\u00e1ly onDraw m\u0171velete param\u00e9terk\u00e9nt megkapja a Graphics oszt\u00e1ly egy objektum\u00e1t, \u00edgy f\u00fcgg t\u0151le, hiszen a met\u00f3dus t\u00f6rzs\u00e9ben \u00edgy meg tudja h\u00edvni a Graphics oszt\u00e1ly met\u00f3dusait. Ha pl. a Graphics oszt\u00e1ly FillRect met\u00f3dus\u00e1nak nev\u00e9t megv\u00e1ltoztatjuk, akkor ezt a v\u00e1ltoz\u00e1st \u00e1t kell vezetni a h\u00edv\u00e1sok hely\u00e9n, vagyis a Window oszt\u00e1ly onDraw met\u00f3dus\u00e1nak t\u00f6rzs\u00e9ben is.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/","title":"Theory of the relationship between the UML class diagram and code","text":"

    Last modified date: 2022.10.15 Edited by Zolt\u00e1n Benedek

    The chapter does not contain an exercise, it introduces the related theory to students.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#introduction","title":"Introduction","text":"

    The chapter gives a brief, sketchy overview of the basics of mapping between the UML class diagram and the source code, as a review of what has already been learned in Software Engineering in the previous semester.

    Today, there are many software development methodologies. They rely on, or require, modelling to varying degrees in the construction of the software. However, there is no doubt that even the most agile, \"code-centric\" followers of the most \"code-centric\" approaches find it useful to visually model the more important/complex components and structural elements of software, because of the greater expressive power of the graphical nature of the software.

    Let's say you have to build an application or a specific module of an application. Following our chosen methodology, we will cover the steps of requirements analysis, analysis, design, implementation and testing, probably in several iterations. Let's now focus on the design phase. This will result in a detailed design of the system (at least parts of it), resulting in a detailed/implementation plan or model. At this level, certain elements of the model (e.g. classes) can be explicitly mapped to elements of the programming language chosen to implement the subsystem. If you have a good development/modeling tool, it can generate the class skeleton (e.g. C++, Java, C# classes). Our task is then to fill in the root of the methods in the generated code.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#concepts","title":"Concepts","text":"

    In order to take advantage of code generation, you need to be aware of the following: you need to know how a given modelling tool maps each model element to elements of a given programming language. The mapping depends on the language and the modelling tool, there is no universal standard. The mappings are usually self-explanatory, there is not usually too much variation.

    In the following we will look at how each model element of the UML class diagram is mapped to source code, and vice versa.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#mapping-of-classes","title":"Mapping of classes","text":"

    It's trivially simple:

    An example:

    Shape class

    , which corresponds to the following code in C#:

    public abstract class Shape\n{\n    private int x;\n    private int y;\n    public Shape(int x, int y) { this.x = x; this.y = y; }\n    public abstract void Draw(Graphics gr);\n}\n

    In the context of visibility, mapping:

    A more exciting question is how the relationships between classes are mapped, and this is discussed in the following chapters.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#i-generalisation-specialisation-link","title":"I. Generalisation, specialisation link","text":"

    Generalisation, specialisation

    C# mapping:

    public class Base\n{ };\npublic class Derived : Base\n{ };\n
    "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#ii-association","title":"II. Association","text":"

    This relationship type always implies communication between objects of classes. A department uses the services of another department.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#a-building-a-01-multiplicity-association-relation","title":"A) Building a 0..1 multiplicity association relation","text":"

    In this case, the client class contains a pointer or reference through which it can use the services of the target class (call its operations). Example:

    Generalisation, specialisation, single contact

    C++ mapping:

    class Application\n{\n   WindowManager* windowManager;\n};\n\nclass WindowManager\n{\n};\n

    C# mapping (no pointers, only references):

    class Application\n{\n   WindowManager windowManager;\n};\n\nclass WindowManager\n{\n};\n

    In both cases, we see that a pointer or reference member variable is added to the client class, whose type is the same as the type of the target class referenced in the association, and the name of the member variable is the role given to the target class for the association relationship, which in the example is . The mapping is logical, since the client can access the target object from any of its operations and call its methods through this pointer/reference.

    Comment. Sometimes the association is two-way, with each class using the services of the other. Often, instead of putting an arrow at both ends of the association, we leave it at both ends. In such a two-way relationship, the role must be specified at both ends of the relationship. During the mapping, we add a pointer/reference to each class to the other.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#b-derivation-for-an-association-relation-with-multiplicity-0n","title":"B) Derivation for an association relation with multiplicity 0..n","text":"

    In this case, a client-side object is related to several target-side objects. Example:

    Generalisation, specialisation, multiple links

    One WindowManager object manages several Window objects. The mapping takes some collection of objects in the target class into the client class. This can be an array, list, etc., whichever best suits our purpose in the situation.

    A mapping to the above example in C++:

    class WindowManager\n{\n  vector<Window*> windows;\n};\n

    Or in C#:

    class WindowManager\n{\n  List<Window> windows; \n};\n
    "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#iii-aggregation-inclusion-part-part-relationship","title":"III. Aggregation (inclusion, part-part relationship)","text":"

    In general, the mapping is exactly the same as for association.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#iv-dependency-dependency","title":"IV. Dependency (dependency)","text":"

    It represents the loosest link between departments. Example:

    Dependency

    Meaning: the Window class depends on the Graphics class. That is, if the Graphics class is changed, the Window class may also need to be changed. This connection type is used when the parameter list/return value of the methods of the class at the beginning of the dependency connection contains the class at the end of the connection. In the example, the onDraw operation of the Window class receives an object of the Graphics class as a parameter, and thus depends on it, since it can call the methods of the Graphics class in the method's trunk. If, for example, the name of the FillRect method of the Graphics class is changed, this change must be reflected in the call location, i.e., in the trunk of the onDraw method of the Window class.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/","title":"Theorie der Beziehung zwischen dem UML-Klassendiagramm und dem Code","text":"

    Letztes \u00c4nderungsdatum: 2022.10.15 Ausgearbeitet von: Zolt\u00e1n Benedek

    Das Kapitel enth\u00e4lt keine \u00dcbung, sondern bietet den Studierenden eine Einf\u00fchrung in die entsprechende Theorie.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

    Das Kapitel gibt einen kurzen \u00dcberblick \u00fcber die Grundlagen des Mappings zwischen dem UML-Klassendiagramm und dem Quellcode, als Wiederholung dessen, was bereits im vorherigen Semester in Softwarechnologien gelernt wurde.

    Heutzutage gibt es viele Softwareentwicklungsmethoden. Sie st\u00fctzen sich bei der Erstellung der Software in unterschiedlichem Ma\u00dfe auf die Modellierung bzw. erfordern diese. Es besteht jedoch kein Zweifel daran, dass selbst die Anh\u00e4nger der agilsten, \"code-zentrierten\" Ans\u00e4tze es f\u00fcr n\u00fctzlich halten, die wichtigeren/komplexeren Komponenten und Strukturelemente der Software visuell zu modellieren, da deren grafische Natur eine gr\u00f6\u00dfere Ausdruckskraft hat.

    Nehmen wir an, man muss eine Anwendung oder ein bestimmtes Modul einer Anwendung erstellen. Nach der von sich gew\u00e4hlten Methodik wird man die Schritte Anforderungsanalyse, Analyse, Entwurf, Implementierung und Test durchf\u00fchren, wahrscheinlich in mehreren Iterationen. Konzentrieren wir uns nun auf die Entwurfsphase. Das Ergebnis ist ein detaillierter Entwurf des Systems (zumindest von Teilen davon), der in einen detaillierten Plan oder ein Modell f\u00fcr die Umsetzung m\u00fcndet. Auf dieser Ebene k\u00f6nnen bestimmte Elemente des Modells (z. B. Klassen) explizit auf Elemente der f\u00fcr die Implementierung des Teilsystems gew\u00e4hlten Programmiersprache abgebildet werden. Wenn man \u00fcber ein gutes Entwicklungs-/Modellierungswerkzeug verf\u00fcgt, kann dieses das Klassenskelett (z. B. C++, Java-, C#-Klassen) generieren. Unsere Aufgabe besteht nun darin, die Wurzel der Methoden in den generierten Code einzutragen.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#konzepte","title":"Konzepte","text":"

    Um die Vorteile der Codegenerierung nutzen zu k\u00f6nnen, muss man Folgendes wissen: man muss wissen, wie ein bestimmtes Modellierungswerkzeug jedes Modellelement auf Elemente einer bestimmten Programmiersprache abbildet. Das Mapping h\u00e4ngt von der Sprache und dem Modellierungswerkzeug ab, es gibt keinen universellen Standard. Die Zuordnungen sind in der Regel selbsterkl\u00e4rend, es gibt in der Regel nicht allzu viele Variationen.

    Im Folgenden werden wir uns ansehen, wie jedes Modellelement des UML-Klassendiagramms auf den Quellcode abgebildet wird und umgekehrt.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#zuordnung-von-klassen","title":"Zuordnung von Klassen","text":"

    Es ist trivial einfach:

    Ein Beispiel:

    , was folgendem Code in C# entspricht:

    public abstract class Shape\n{\n    private int x;\n    private int y;\n    public Shape(int x, int y) { this.x = x; this.y = y; }\n    public abstract void Draw(Graphics gr);\n}\n

    Im Zusammenhang mit der Sichtbarkeit, Kartierung:

    Eine spannendere Frage ist, wie die Beziehungen zwischen den Klassen abgebildet werden, und dies wird in den folgenden Kapiteln diskutiert.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#i-generalisierung-und-spezialisierung","title":"I. Generalisierung und Spezialisierung","text":"

    C#-Zuordnung:

    public class Base\n{ };\npublic class Derived : Base\n{ };\n
    "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#ii-assoziation","title":"II. Assoziation","text":"

    Dieser Beziehungstyp impliziert immer eine Kommunikation zwischen Objekten von Klassen. Eine Abteilung nimmt die Dienste einer anderen Abteilung in Anspruch.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#a-aufbau-einer-01-multiplizitats-assoziationsbeziehung","title":"A) Aufbau einer 0..1-Multiplizit\u00e4ts-Assoziationsbeziehung","text":"

    In diesem Fall enth\u00e4lt die Client-Klasse einen Zeiger oder Verweis, \u00fcber den sie die Dienste der Zielklasse nutzen (ihre Operationen aufrufen) kann. Beispiel:

    C++-Zuordnung:

    klasse Bewerbung\n{\n   WindowManager* windowManager;\n};\n\nclass WindowManager\n{\n};\n

    C#-Zuordnung (keine Zeiger, nur Referenzen):

    class Application\n{\n   WindowManager windowManager;\n};\n\nclass WindowManager\n{\n};\n

    In beiden F\u00e4llen sehen wir, dass wir der Client-Klasse eine Zeiger- oder Referenz-Member-Variable hinzuf\u00fcgen, die vom gleichen Typ ist wie die Zielklasse, auf die in der Assoziation verwiesen wird, und der Name der Member-Variable ist die Rolle, die der Zielklasse f\u00fcr die Assoziationsbeziehung gegeben wurde, die in diesem Beispiel windowManagerist. Die Zuordnung ist logisch, da der Client auf das Zielobjekt aus jeder seiner Operationen zugreifen und seine Methoden \u00fcber diesen Zeiger/Verweis aufrufen kann.

    Kommentar. Manchmal ist die Assoziation in beide Richtungen, wobei jede Klasse die Dienste der anderen nutzt. Anstatt einen Pfeil an beiden Enden der Assoziation anzubringen, lassen wir ihn oft an beiden Enden stehen. In einer solchen wechselseitigen Beziehung muss die Rolle an beiden Enden der Beziehung angegeben werden. W\u00e4hrend des Mappings f\u00fcgen wir einen Zeiger/Referenz auf jede Klasse der anderen hinzu.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#b-ableitung-fur-eine-assoziationsbeziehung-mit-der-multiplizitat-0n","title":"B) Ableitung f\u00fcr eine Assoziationsbeziehung mit der Multiplizit\u00e4t 0..n","text":"

    In diesem Fall ist ein Objekt auf der Client-Seite mit mehreren Objekten auf der Zielseite verbunden. Beispiel:

    Ein WindowManager Objekt verwaltet mehrere Window Objekte. Das Mapping \u00fcbernimmt eine Sammlung von Objekten der Zielklasse in die Client-Klasse. Dabei kann es sich um ein Array, eine Liste usw. handeln, je nachdem, was f\u00fcr unsere Zwecke in der jeweiligen Situation am besten geeignet ist.

    Eine Abbildung des obigen Beispiels in C++:

    class WindowManager\n{\n  vector<Window*> windows;\n};\n

    Oder in C#:

    class WindowManager\n{\n  List<Window> windows; \n};\n
    "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#iii-aggregation-einbeziehung-teil-ganzes-beziehung","title":"III. Aggregation (Einbeziehung, Teil-Ganzes-Beziehung)","text":"

    Im Allgemeinen ist die Zuordnung genau die gleiche wie bei der Assoziation.

    "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#iv-abhangigkeit-dependenz","title":"IV. Abh\u00e4ngigkeit (Dependenz)","text":"

    Sie stellt die lockerste Verbindung zwischen den Abteilungen dar. Beispiel:

    Das bedeutet: Die Klasse Window h\u00e4ngt von der Klasse Graphics ab. Das hei\u00dft, wenn die Klasse Graphics ge\u00e4ndert wird, muss m\u00f6glicherweise auch die Klasse Window ge\u00e4ndert werden. Diese Art der Beziehung wird verwendet, wenn die Parameterliste/R\u00fcckgabewerte der Methoden der Klasse am Anfang der Abh\u00e4ngigkeitsbeziehung die Klasse am Ende der Beziehung enth\u00e4lt. Im Beispiel erh\u00e4lt die Operation onDraw der Klasse Window ein Objekt der Klasse Graphics als Parameter und ist somit von dieser abh\u00e4ngig, da sie die Methoden der Klasse Graphics im Stamm der Methode aufrufen kann. Wird z.B. der Name der Methode FillRect der Klasse Graphics ge\u00e4ndert, muss sich diese \u00c4nderung in der Aufrufstelle, d.h. im Stamm der Methode onDraw der Klasse Window widerspiegeln.

    "},{"location":"hazi/","title":"\u00d6n\u00e1ll\u00f3/h\u00e1zi feladatok","text":"

    Valamennyi h\u00e1zi feladat elk\u00e9sz\u00edt\u00e9se k\u00f6telez\u0151. A megold\u00e1sok bead\u00e1sa GitHub Classroom seg\u00edts\u00e9g\u00e9vel t\u00f6rt\u00e9nik (b\u0151vebben itt). Az \u00f6n\u00e1ll\u00f3/h\u00e1zi feladatokra vonatkoz\u00f3 pontos k\u00f6vetelm\u00e9nyek Moodle-ben, a T\u00e1rgyk\u00f6vetelm\u00e9nyek alatt olvashat\u00f3k (\"\u00d6n\u00e1ll\u00f3/h\u00e1zi feladatok\" fejezet).

    "},{"location":"hazi/#a-feladatok","title":"A feladatok","text":""},{"location":"hazi/#a-feladatok-beadasa","title":"A feladatok bead\u00e1sa","text":"

    Minden h\u00e1zi feladat megold\u00e1s\u00e1t egy szem\u00e9lyre sz\u00f3l\u00f3 git repository-ban kell beadni. Ennek pontos folyamat\u00e1t l\u00e1sd itt. K\u00e9r\u00fcnk, alaposan olvasd v\u00e9gig a le\u00edr\u00e1st!

    FONTOS

    A h\u00e1zik elk\u00e9sz\u00edt\u00e9se \u00e9s bead\u00e1s sor\u00e1n az itt le\u00edrtak szerint kell elj\u00e1rnod. A nem ilyen form\u00e1ban beadott h\u00e1zi feladatokat nem \u00e9rt\u00e9kelj\u00fck.

    Bizonyos h\u00e1zi feladatokhoz automata el\u0151ellen\u0151rz\u0151 is tartozik, err\u0151l itt olvashatsz b\u0151vebben.

    "},{"location":"hazi/#kepernyokepek","title":"K\u00e9perny\u0151k\u00e9pek","text":"

    Bizonyos h\u00e1zi feladatok k\u00e9rik, hogy k\u00e9sz\u00edts k\u00e9perny\u0151k\u00e9pet a megold\u00e1s egy-egy r\u00e9sz\u00e9r\u0151l, mert ezzel bizony\u00edtod, hogy a megold\u00e1sod saj\u00e1t magad k\u00e9sz\u00edtetted. A k\u00e9perny\u0151k\u00e9pek elv\u00e1rt tartalm\u00e1t a feladat minden esetben pontosan megnevezi.

    A k\u00e9perny\u0151k\u00e9peket a megold\u00e1s r\u00e9szek\u00e9nt kell beadni, \u00edgy felker\u00fclnek a git repository tartalm\u00e1val egy\u00fctt. Mivel a repository priv\u00e1t, azt az oktat\u00f3kon k\u00edv\u00fcl m\u00e1s nem l\u00e1tja. Amennyiben olyan tartalom ker\u00fcl a k\u00e9perny\u0151k\u00e9pre, amit nem szeretn\u00e9l felt\u00f6lteni, kitakarhatod a k\u00e9pr\u0151l.

    "},{"location":"hazi/#szukseges-eszkozok","title":"Sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k","text":"

    A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezet alapvet\u0151en a Visual Studio 2022, err\u0151l itt tal\u00e1lhat\u00f3 b\u0151vebb le\u00edr\u00e1s.

    "},{"location":"hazi/VisualStudio/","title":"Visual Studio & .NET SDK telep\u00edt\u00e9se","text":"

    COMING SOON

    "},{"location":"hazi/meghirdetes-elott/","title":"H\u00e1zi feladat","text":"

    Ez a h\u00e1zi feladat ebben a f\u00e9l\u00e9vben m\u00e9g nem ker\u00fclt meghirdet\u00e9sre, \u00edgy a le\u00edr\u00e1sa k\u00e9s\u0151bb lesz el\u00e9rhet\u0151 a f\u00e9l\u00e9v folyam\u00e1n.

    "},{"location":"hazi/meghirdetes-elott_ger/","title":"Hausaufgaben","text":"

    Diese Hausarbeit wurde in diesem Semester noch nicht angek\u00fcndigt, so dass die Beschreibung erst im Laufe des Semesters verf\u00fcgbar sein wird.

    "},{"location":"hazi/1-model-es-kod-kapcsolata/","title":"1. HF - A modell \u00e9s a k\u00f3d kapcsolata","text":""},{"location":"hazi/1-model-es-kod-kapcsolata/#bevezetes","title":"Bevezet\u00e9s","text":"

    A feladathoz nem kapcsol\u00f3dik el\u0151ad\u00e1s. A feladatok elm\u00e9leti \u00e9s gyakorlati h\u00e1tter\u00e9\u00fcl az \"1. A modell \u00e9s a k\u00f3d kapcsolata\" vezetett laborgyakorlat szolg\u00e1l:

    Erre \u00e9p\u00edtve jelen \u00f6n\u00e1ll\u00f3 gyakorlat feladatai a feladatle\u00edr\u00e1st k\u00f6vet\u0151 r\u00f6videbb ir\u00e1nymutat\u00e1s seg\u00edts\u00e9g\u00e9vel elv\u00e9gezhet\u0151k.

    Az \u00f6n\u00e1ll\u00f3 gyakorlat c\u00e9lja:

    A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s.

    C# 12-es (\u00e9s \u00fajabb) nyelvi elemek haszn\u00e1lata

    A h\u00e1zi feladat megold\u00e1sa sor\u00e1n C# 12-es, \u00e9s ann\u00e1l \u00fajabb nyelvi elemek, (pl. primary constructor) nem haszn\u00e1lhat\u00f3k, ugyanis a GitHub-on fut\u00f3 ellen\u0151rz\u0151 ezeket m\u00e9g nem t\u00e1mogatja.

    "},{"location":"hazi/1-model-es-kod-kapcsolata/#a-kiindulasi-keret-letoltese-az-elkeszult-megoldas-feltoltese","title":"A kiindul\u00e1si keret let\u00f6lt\u00e9se, az elk\u00e9sz\u00fclt megold\u00e1s felt\u00f6lt\u00e9se","text":"

    A h\u00e1zi feladat kiindul\u00e1si k\u00f6rnyezet\u00e9nek publik\u00e1l\u00e1sa, valamint a megold\u00e1s bead\u00e1sa Git, GitHub \u00e9s GitHub Classroom seg\u00edts\u00e9g\u00e9vel t\u00f6rt\u00e9nik. F\u0151bb l\u00e9p\u00e9sek:

    1. GitHub Classroom seg\u00edts\u00e9g\u00e9vel hozz l\u00e9tre magadnak egy repository-t. A megh\u00edv\u00f3 URL-t Moodle-ben tal\u00e1lod (a t\u00e1rgy nyit\u00f3oldal\u00e1n a \"GitHub classroom hivatkoz\u00e1sok a h\u00e1zi feladatokhoz\" hivatkoz\u00e1sra kattintva megjelen\u0151 oldalon l\u00e1that\u00f3).
    2. Kl\u00f3nozd le az \u00edgy elk\u00e9sz\u00fclt repository-t. Ez tartalmazni fogja a megold\u00e1s elv\u00e1rt szerkezet\u00e9t.
    3. A feladatok elk\u00e9sz\u00edt\u00e9se ut\u00e1n commit-old \u00e9s push-old a megold\u00e1sod.

    Ezekhez itt tal\u00e1lhat\u00f3 r\u00e9szletesebb le\u00edr\u00e1s:

    "},{"location":"hazi/1-model-es-kod-kapcsolata/#a-hazi-feladat-eloellenorzese-es-hivatalos-ertekelese","title":"A h\u00e1zi feladat el\u0151ellen\u0151rz\u00e9se \u00e9s hivatalos \u00e9rt\u00e9kel\u00e9se","text":"

    Minden egyes alkalommal, miut\u00e1n a GitHub-ra push-olt\u00e1l k\u00f3dot, a GitHub-on automatikusan lefut a felt\u00f6lt\u00f6tt k\u00f3d (el\u0151)ellen\u0151rz\u00e9se, \u00e9s meg lehet n\u00e9zni a kimenet\u00e9t! Err\u0151l b\u0151vebb inform\u00e1ci\u00f3 itt tal\u00e1lhat\u00f3 (mindenk\u00e9ppen olvasd el): A h\u00e1zi feladat el\u0151ellen\u0151rz\u00e9se \u00e9s hivatalos \u00e9rt\u00e9kel\u00e9se.

    "},{"location":"hazi/1-model-es-kod-kapcsolata/#feladat-1-egy-egyszeru-net-konzol-alkalmazas-elkeszitese","title":"Feladat 1 \u2013 Egy egyszer\u0171 .NET konzol alkalmaz\u00e1s elk\u00e9sz\u00edt\u00e9se","text":""},{"location":"hazi/1-model-es-kod-kapcsolata/#kiindulo-projekt","title":"Kiindul\u00f3 projekt","text":"

    A kiindul\u00e1si k\u00f6rnyezet a Feladat1 mapp\u00e1ban tal\u00e1lhat\u00f3, az ebben lev\u0151 MusicApp.sln f\u00e1jlt nyissuk meg Visual Studioban \u00e9s ebben a solutionben dolgozzunk.

    Figyelem!

    \u00daj solution \u00e9s/vagy projektf\u00e1jl l\u00e9trehoz\u00e1sa, vagy a projekt m\u00e1s/\u00fajabb .NET verzi\u00f3kra targetel\u00e9se tilos.

    A Feladat1\\Input mapp\u00e1ban tal\u00e1lhat\u00f3 egy music.txt f\u00e1jl, mely a feladat bemenetek\u00e9nt haszn\u00e1land\u00f3.

    "},{"location":"hazi/1-model-es-kod-kapcsolata/#feladat","title":"Feladat","text":"

    Egy sz\u00f6vegf\u00e1ljban zeneszerz\u0151k/el\u0151ad\u00f3k/egy\u00fcttesek sz\u00e1mainak c\u00edmeit t\u00e1roljuk a k\u00f6vetkez\u0151 form\u00e1tumban.

    A mell\u00e9kelt music.txt f\u00e1jl tartalma a k\u00f6vetkez\u0151h\u00f6z hasonl\u00f3:

    Adele; Hello; Rolling in the Deep; Skyfall\nEnnio Morricone;    A Fistful Of Dollars; Man with a Harmonica\nAC/DC; Thunderstruck; T.N.T\n

    Olvassuk be a f\u00e1jlt Song oszt\u00e1lybeli objektumok list\u00e1j\u00e1ba. Egy Song objektum egy dal adatait t\u00e1rolja (szerz\u0151 \u00e9s c\u00edm). A beolvas\u00e1st k\u00f6vet\u0151en \u00edrjuk ki form\u00e1zott m\u00f3don az objektumok adatait a szabv\u00e1nyos kimenetre az al\u00e1bbi form\u00e1ban:

    szerz\u01511: szerz\u01511_dalc\u00edm1\nszerz\u01511: szerz\u01511_dalc\u00edm2\n...\nszerz\u01512: szerz\u01512_dalc\u00edm1\n...\nstb.\n

    A mintaf\u00e1jlunk eset\u00e9ben a k\u00f6vetkez\u0151 (a f\u00e1jl tartalm\u00e1nak f\u00fcggv\u00e9ny\u00e9ben lehet elt\u00e9r\u00e9s) kimenetet szeretn\u00e9nk l\u00e1tni:

    "},{"location":"hazi/1-model-es-kod-kapcsolata/#a-megvalositas-lepesei","title":"A megval\u00f3s\u00edt\u00e1s l\u00e9p\u00e9sei","text":"

    Vegy\u00fcnk fel egy Song nev\u0171 oszt\u00e1lyt a projektbe (jobb katt a Solution Explorerben a projekten, a men\u00fcben Add / Class).

    Vegy\u00fck fel a sz\u00fcks\u00e9ges tagokat \u00e9s egy ezekhez passzol\u00f3 konstruktort:

    public class Song\n{\n    public readonly string Artist;\n    public readonly string Title;\n\n    public Song(string artist, string title)\n    {\n        Artist = artist;\n        Title = title;\n    }\n}\n

    Property

    A tagv\u00e1ltoz\u00f3kat readonly-k\u00e9nt vett\u00fck fel, mert nem akartuk, hogy ezek ut\u00f3lag, a konstruktor lefut\u00e1s\u00e1t k\u00f6vet\u0151en megv\u00e1ltoztathat\u00f3k legyenek. Alternat\u00edva lehetne a csak olvashat\u00f3 tulajdons\u00e1g (property) alkalmaz\u00e1sa a readonly tagv\u00e1ltoz\u00f3k helyett (ez k\u00e9s\u0151bbi tanagyag).

    A k\u00f6vetkez\u0151kben a Song oszt\u00e1lyunkban defini\u00e1ljuk fel\u00fcl az implicit System.Object \u0151sb\u0151l \u00f6r\u00f6k\u00f6lt ToString m\u0171veletet, hogy az az el\u0151\u00edrt form\u00e1ban adja vissza objektum adatait. A megold\u00e1sban sztring interpol\u00e1ci\u00f3t haszn\u00e1ljunk (ezt m\u00e1r alkalmaztuk az els\u0151 labor keret\u00e9ben):

    public override string ToString()\n{\n    return $\"{Artist}: {Title}\";\n}\n

    Sz\u00f6vegf\u00e1jl feldolgoz\u00e1s\u00e1ra legk\u00e9nyelmesebben a System.IO n\u00e9vt\u00e9rben lev\u0151 StreamReader oszt\u00e1lyt tudjuk haszn\u00e1lni.

    A Main f\u00fcggv\u00e9ny\u00fcnkben olvassuk fel soronk\u00e9nt a f\u00e1jlt, hozzuk l\u00e9tre a Song objektumokat, \u00e9s tegy\u00fck be egy List<Song> dinamikusan ny\u00fajt\u00f3zkod\u00f3 t\u00f6mbbe. Figyelj\u00fcnk arra, hogy a f\u00e1jlban a ;-vel elv\u00e1lasztott elemek el\u0151tt/ut\u00e1n whitespace karakterek (space, tab) lehetnek, ezekt\u0151l szabaduljunk meg!

    A k\u00f6vetkez\u0151 k\u00f3d egy lehets\u00e9ges megold\u00e1st mutat, a megold\u00e1s r\u00e9szleteit a k\u00f3dkommentek magyar\u00e1zz\u00e1k. A f\u00e9l\u00e9v sor\u00e1n ez az els\u0151 \u00f6n\u00e1ll\u00f3 feladat, valamint a hallgat\u00f3k t\u00f6bbs\u00e9g\u00e9nek ez els\u0151 .NET/C# alkalmaz\u00e1sa, \u00edgy itt m\u00e9g adunk mintamegold\u00e1st, de a rutinosabb hallgat\u00f3k \u00f6n\u00e1ll\u00f3an is pr\u00f3b\u00e1lkozhatnak.

    Megold\u00e1s
    namespace MusicApp;\n\npublic class Program\n{\n    // A Main f\u00fcggv\u00e9ny a Program oszt\u00e1lyon bel\u00fcl tal\u00e1lhat\u00f3, ezt itt nem jel\u00fclj\u00fck\n    public static void Main(string[] args)\n    {\n        // Ebben t\u00e1roljuk a dal objektumokat\n        List<Song> songs = new List<Song>();\n\n        // F\u00e1jl beolvas\u00e1sa soronk\u00e9nt, songs lista felt\u00f6lt\u00e9se\n        StreamReader sr = null;\n        try\n        {\n            // A @ jelent\u00e9se a string konstans el\u0151tt:\n            // kikapcsolja a string escape-el\u00e9st,\n            // \u00edgy nem kell a '\\' helyett '\\\\'-t \u00edrni.\n            sr = new StreamReader(@\"C:\\temp\\music.txt\");\n            string line;\n            while ((line = sr.ReadLine()) != null)\n            {\n                // Ha \u00fcres volt a sor\n                if (string.IsNullOrWhiteSpace(line))\n                    continue;\n\n                // A line v\u00e1ltoz\u00f3ban benne van az eg\u00e9sz sor,\n                // a Split-tel a ;-k ment\u00e9n feldaraboljuk\n                string[] lineItems = line.Split(';');\n\n                // Els\u0151 elem, amiben az szerz\u0151 nev\u00e9t v\u00e1rjuk\n                // A Trim elt\u00e1vol\u00edtja a vezet\u0151 \u00e9s z\u00e1r\u00f3 whitespace karaktereket\n                string artist = lineItems[0].Trim();\n\n                // Menj\u00fcnk v\u00e9gig a dalokon, \u00e9s vegy\u00fck fel a list\u00e1ba\n                for (int i = 1; i < lineItems.Length; i++)\n                {\n                    Song song = new Song(artist, lineItems[i].Trim());\n                    songs.Add(song);\n                }\n            }\n        }\n        catch (Exception e)\n        {\n            Console.WriteLine(\"A f\u00e1jl feldolgoz\u00e1sa sikertelen.\");\n            // Az e.Message csak a kiv\u00e9tel sz\u00f6veg\u00e9t tartalmazza. \n            // Ha minden kiv\u00e9tel inform\u00e1ci\u00f3t ki szeretn\u00e9nk \u00edrni (pl. stack trace), \n            // akkor az e.ToString()-et \u00edrjuk ki.\n            Console.WriteLine(e.Message);\n        }\n        finally\n        {\n            // L\u00e9nyeges, hogy finally blokkban z\u00e1rjuk le a f\u00e1jlt, \n            // hogy egy esetleges kiv\u00e9tel eset\u00e9n se maradjon m\u00f6g\u00f6tt\u00fcnk lez\u00e1ratlan \u00e1llom\u00e1ny.\n            // try-finally helyett haszn\u00e1lhattunk volna using blokkot,\n            // azt egyel\u0151re nem kell tudni (a f\u00e9l\u00e9v derek\u00e1n tanuljuk).\n            if (sr != null)\n                sr.Close();\n        }\n\n        // A songs lista elemeinek ki\u00edr\u00e1sa a konzolra\n        foreach (Song song in songs)\n            Console.WriteLine(song.ToString());\n    }\n}\n

    A c:\\temp mapp\u00e1ba m\u00e1soljuk ki a music.txt f\u00e1jlt, \u00e9s futtassuk az alkalmaz\u00e1st. A megval\u00f3s\u00edt\u00e1s sor\u00e1n az egyszer\u0171s\u00e9gre t\u00f6rekedve mindent bele\u00f6nt\u00f6tt\u00fcnk a main f\u00fcggv\u00e9nybe, \u201e\u00e9les\u201d k\u00f6rnyezetben mindenk\u00e9pp c\u00e9lszer\u0171 a k\u00f3dot egy k\u00fcl\u00f6n feldolgoz\u00f3 oszt\u00e1lyba kiszervezni.

    A fenti p\u00e9ld\u00e1ban j\u00f3p\u00e1r .NET/C# alaptechnika bemutat\u00e1sra ker\u00fcl, mindenk\u00e9pen \u00e9rdemes a fenti k\u00f3dba sz\u00fart megjegyz\u00e9sek alapj\u00e1n ezeket \u00e9rtelmezni \u00e9s megtanulni, a f\u00e9l\u00e9v sor\u00e1n ezekre \u00e9p\u00edteni fogunk.

    "},{"location":"hazi/1-model-es-kod-kapcsolata/#feladat-2-az-uml-es-a-kod-kapcsolata-interfesz-es-absztrakt-os-alkalmazastechnikaja","title":"Feladat 2 - Az UML \u00e9s a k\u00f3d kapcsolata, interf\u00e9sz \u00e9s absztrakt \u0151s alkalmaz\u00e1stechnik\u00e1ja","text":""},{"location":"hazi/1-model-es-kod-kapcsolata/#kiindulo-kornyezet","title":"Kiindul\u00f3 k\u00f6rnyezet","text":"

    A kiindul\u00e1si k\u00f6rnyezet a Feladat2 mapp\u00e1ban tal\u00e1lhat\u00f3, az ebben lev\u0151 Shapes.sln f\u00e1jlt nyissuk meg Visual Studioban, \u00e9s ebben a solutionben dolgozzunk.

    Figyelem!

    \u00daj solution \u00e9s/vagy projektf\u00e1jl l\u00e9trehoz\u00e1sa, vagy a projekt m\u00e1s/\u00fajabb .NET verzi\u00f3kra targetel\u00e9se tilos.

    A Feladat2\\Shapes mapp\u00e1ban tal\u00e1lhat\u00f3 egy Controls.dll f\u00e1jl, ezt a feladat megold\u00e1sa sor\u00e1n kell majd felhaszn\u00e1lni.

    "},{"location":"hazi/1-model-es-kod-kapcsolata/#beadando-a-forraskodon-tulmenoen","title":"Beadand\u00f3 (a forr\u00e1sk\u00f3don t\u00falmen\u0151en)","text":"

    K\u00e9t-h\u00e1rom bekezd\u00e9sben a Feladat 2 megold\u00e1sa sor\u00e1n hozott tervez\u0151i d\u00f6nt\u00e9sek, a megold\u00e1s legfontosabb alapelveinek r\u00f6vid sz\u00f6veges \u00f6sszefoglal\u00e1sa, indokl\u00e1sa. Ezt a kiindul\u00f3 keret Feladat2 mapp\u00e1j\u00e1ban m\u00e1r megtal\u00e1lhat\u00f3 readme.md sz\u00f6vegf\u00e1jlba kell bele\u00edrni tetsz\u0151leges markdown form\u00e1tumban, vagy egyszer\u0171 nyers sz\u00f6vegk\u00e9nt. Fontos, hogy a Feladat2 mapp\u00e1ban lev\u0151 f\u00e1jlba dolgozz (akkor is, ha esetleg a gy\u00f6k\u00e9rmapp\u00e1ban is van egy azonos nev\u0171 f\u00e1jl).

    "},{"location":"hazi/1-model-es-kod-kapcsolata/#feladat_1","title":"Feladat","text":"

    Egy s\u00edkbeli vektorgrafikus alakzatokat kezelni k\u00e9pes CAD tervez\u0151alkalmaz\u00e1s els\u0151 v\u00e1ltozat\u00e1nak kifejleszt\u00e9s\u00e9vel b\u00edznak meg benn\u00fcnket. B\u0151vebben:

    A megval\u00f3s\u00edt\u00e1s sor\u00e1n jelent\u0151s egyszer\u0171s\u00edt\u00e9ssel \u00e9l\u00fcnk:

    "},{"location":"hazi/1-model-es-kod-kapcsolata/#osztalykonyvtarak-hasznalata","title":"Oszt\u00e1lyk\u00f6nyvt\u00e1rak haszn\u00e1lata","text":"

    A megold\u00e1s az 1. A modell \u00e9s a k\u00f3d kapcsolata laborgyakorlat mint\u00e1j\u00e1ra kidolgozhat\u00f3. Jelen feladat egy l\u00e9nyeges r\u00e9szlet\u00e9ben k\u00fcl\u00f6nb\u00f6zik t\u0151le: m\u00edg abban csak sz\u00f3ban k\u00f6t\u00f6tt\u00fck ki, hogy a DisplayBase \u0151soszt\u00e1ly forr\u00e1sk\u00f3dja nem megv\u00e1ltoztat\u00f3, jelen esetben a Textbox \u0151soszt\u00e1lyunk eset\u00e9ben ez adott, hiszen csak egy leford\u00edtott dll form\u00e1j\u00e1ban \u00e1ll rendelkez\u00e9sre.

    Note

    T\u00f6bbkomponens\u0171 alkalmaz\u00e1sok fejleszt\u00e9s\u00e9r\u0151l, szerelv\u00e9ny \u00e9s projekt referencia alkalmaz\u00e1s\u00e1r\u00f3l az els\u0151 el\u0151ad\u00e1son volt sz\u00f3, ha nem eml\u00e9kszel erre a t\u00e9mak\u00f6rre, c\u00e9lszer\u0171 \u00e1tism\u00e9telni.

    A k\u00f6vetkez\u0151kben n\u00e9zz\u00fck meg, milyen l\u00e9p\u00e9sekben lehet egy ilyen dll-ben lev\u0151 oszt\u00e1lyokat a k\u00f3dunkban felhaszn\u00e1lni:

    1. A Visual Studio Solution Explorer ablak\u00e1ban jobb gombbal kattintsunk a Dependencies elemen, \u00e9s v\u00e1lasszuk az Add Reference-t vagy Add Project Reference-t (amelyik l\u00e9tezik).
    2. A megjelen\u0151 ablak bal oldal\u00e1n v\u00e1lasszuk ki a Browse elemet,
    3. Ha az ablak k\u00f6zep\u00e9n a list\u00e1ban megjelenik a Controls.dll, pip\u00e1ljuk ki az elemet.
    4. Ha nem jelenik meg, akkor kattintsunk az ablakunk jobb als\u00f3 r\u00e9sz\u00e9ben lev\u0151 Browse... gombon. 1. A megjelen\u0151 f\u00e1jlb\u00f6ng\u00e9sz\u0151 ablakban navig\u00e1ljunk el a Controls.dll f\u00e1jlhoz, \u00e9s kattintsunk rajta dupl\u00e1n, ami bez\u00e1rja az ablakot. 2. A Reference Manager ablakunk k\u00f6z\u00e9ps\u0151 r\u00e9sz\u00e9n a Controls.dll l\u00e1that\u00f3 kipip\u00e1lva, az OK gombbal z\u00e1rjuk be az ablakot.
    5. Az OK gombbal z\u00e1rjuk be az ablakot.
    Ha esetleg 'Reference is invalid or unsupported' hiba\u00fczenetet kapsz

    Nagyon ritk\u00e1n, de el\u0151fordulhat, hogy a fenti l\u00e9p\u00e9sek sor\u00e1n a Visual Studio a \"Reference is invalid or unsupported\" hiba\u00fczenetet jelzi. Ilyenkor az esetek t\u00f6bbs\u00e9g\u00e9ben a Visual Studio \u00fajratelep\u00edt\u00e9se seg\u00edt.

    Ezzel a projekt\u00fcnkben felvett\u00fcnk egy referenci\u00e1t a Controls.dll-re, \u00edgy a benne lev\u0151 oszt\u00e1lyok haszn\u00e1lhat\u00f3k (pl. lehet p\u00e9ld\u00e1nyos\u00edtani \u0151ket, vagy lehet bel\u0151l\u00fck sz\u00e1rmaztatni). A Solution Explorer-ben a Dependencies majd Assemblies csom\u00f3pontot lenyitva a Controls megjelenik:

    A Textbox oszt\u00e1ly, melyb\u0151l a TextArea oszt\u00e1lyunkat sz\u00e1rmaztatni kell, a Controls n\u00e9vt\u00e9rben tal\u00e1lhat\u00f3. A TextBox oszt\u00e1lynak egy konstruktora van, melynek n\u00e9gy param\u00e9tere van, az x \u00e9s y koordin\u00e1t\u00e1k, valamint a sz\u00e9less\u00e9g \u00e9s a magass\u00e1g. Amennyiben sz\u00fcks\u00e9g lenne r\u00e1, a t\u00f6bbi m\u0171velet felder\u00edt\u00e9s\u00e9ben az Object Browser seg\u00edt. Az Object Browser a View men\u00fcb\u0151l az Object Browser men\u00fc kiv\u00e1laszt\u00e1s\u00e1val nyithat\u00f3 meg. Az Object Browser egy \u00faj tabf\u00fcl\u00f6n jelenik meg.

    Ha \u00fcres az Object Browser n\u00e9zet

    A Visual Studio 2022 hajlamos arra, hogy mindaddig, am\u00edg nincs egy forr\u00e1sf\u00e1jl megnyitva, az Object Browserben nem jelen\u00edt meg semmit (csak egy \"No information\" kezdet\u0171 sz\u00f6veg l\u00e1tszik). Ha azt tapasztaljuk, hogy \u00fcres az Object Browser n\u00e9zet, csak nyissuk meg a Program.cs f\u00e1jl a Solution Explorerben, majd v\u00e1ltsunk vissza az Object Browser tabf\u00fclre, ahol \u00edgy m\u00e1r megjelennek a komponensek.

    Az Object Browserben a Controls komponenst lenyitogatva az egyes csom\u00f3pontokat kiv\u00e1lasztva (n\u00e9vt\u00e9r, oszt\u00e1ly) az adott csom\u00f3pont jellemz\u0151i jelennek meg: pl. az oszt\u00e1ly nev\u00e9n \u00e1llva az oszt\u00e1ly tagjait l\u00e1tjuk.

    Most m\u00e1r minden inform\u00e1ci\u00f3 rendelkez\u00e9s\u00fcnkre \u00e1ll a feladat megval\u00f3s\u00edt\u00e1s\u00e1hoz.

    "},{"location":"hazi/1-model-es-kod-kapcsolata/#beadas","title":"Bead\u00e1s","text":"

    Ellen\u0151rz\u0151lista ism\u00e9tl\u00e9sk\u00e9ppen:

    "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/","title":"1. HF - Beziehung zwischen Modell und Code","text":""},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

    Die \u00dcbung ist nicht mit einer Pr\u00e4sentation verbunden. Den theoretischen und praktischen Hintergrund f\u00fcr die \u00dcbungen liefert das Kapitel \"1. Die Beziehung zwischen Modell und Code\" wird als angeleitete Labor\u00fcbung dienen:

    Darauf aufbauend k\u00f6nnen die Aufgaben dieser Selbst\u00fcbung mit Hilfe der k\u00fcrzeren Leitf\u00e4den, die der Aufgabenbeschreibung folgen, durchgef\u00fchrt werden.

    Das Ziel der unabh\u00e4ngigen \u00dcbung:

    Die erforderliche Entwicklungsumgebung wird hier beschrieben.

    "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#laden-sie-den-ausgangsrahmen-herunter-laden-sie-die-fertige-losung-hoch","title":"Laden Sie den Ausgangsrahmen herunter, laden Sie die fertige L\u00f6sung hoch","text":"

    Die urspr\u00fcngliche Hausaufgabenumgebung wird ver\u00f6ffentlicht und die L\u00f6sung wird \u00fcber Git, GitHub und GitHub Classroom eingereicht. Die wichtigsten Schritte:

    1. Erstellen Sie mit GitHub Classroom ein Repository f\u00fcr sich selbst. Sie finden die Einladungs-URL in Moodle (Sie k\u00f6nnen sie sehen, indem Sie auf den Link*\"GitHub classroom links for homework*\" auf der Startseite des Fachs klicken).
    2. Klonen Sie das resultierende Repository. Dazu geh\u00f6rt auch die erwartete Struktur der L\u00f6sung.
    3. Nachdem Sie die Aufgaben erledigt haben, \u00fcbergeben Sie Ihre L\u00f6sung alt und dr\u00fccken Sie sie alt.

    Diese werden hier ausf\u00fchrlicher beschrieben:

    "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#vorabkontrolle-und-formale-bewertung-der-hausaufgaben","title":"Vorabkontrolle und formale Bewertung der Hausaufgaben","text":"

    Jedes Mal, wenn Sie Code auf GitHub hochladen, f\u00fchrt GitHub automatisch eine (Vor-)Pr\u00fcfung des hochgeladenen Codes durch und Sie k\u00f6nnen das Ergebnis sehen! Weitere Informationen dazu finden Sie hier (lesen Sie sie unbedingt): Vorabkontrolle und formale Bewertung der Hausaufgaben.

    "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#aufgabe-1-erstellen-einer-einfachen-net-konsolenanwendung","title":"Aufgabe 1 - Erstellen einer einfachen .NET-Konsolenanwendung","text":""},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#ursprungliches-projekt","title":"Urspr\u00fcngliches Projekt","text":"

    Die anf\u00e4ngliche Umgebung befindet sich im Ordner Feladat1, \u00f6ffnen Sie die Datei MusicApp.sln in Visual Studio und arbeiten Sie in dieser L\u00f6sung.

    Achtung!

    Das Erstellen einer neuen Projektmappe und/oder Projektdatei oder die Ausrichtung des Projekts auf andere/neuere .NET-Versionen ist verboten.

    Im Ordner Feladat1\\Input befindet sich eine Datei music.txt, die als Eingabe f\u00fcr die Aufgabe verwendet werden soll.

    "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#verfasst-am","title":"Verfasst am","text":"

    In einem Textstring speichern wir die Titel der Lieder von Komponisten/Interpreten/Ensembles im folgenden Format.

    Der Inhalt der beigef\u00fcgten Datei music.txt ist \u00e4hnlich wie der folgende:

    Adele; Hello; Rolling in the Deep; Skyfall\nEnnio Morricone;    A Fistful Of Dollars; Mann mit der Mundharmonika\nAC/DC; Thunderstruck; T.N.T\n

    Lesen Sie die Datei in die Liste der Klassenobjekte Song. Ein Objekt Song speichert die Daten (Autor und Titel) eines Liedes. Nach dem Scannen schreiben Sie die formatierten Daten der Objekte in folgendem Format auf die Standardausgabe:

    autor1: Autor1_Titel1\nautor1: Autor1_Titel2\n...\nautor2: Autor2_Songtitel1\n...\nusw.\n

    F\u00fcr unsere Beispieldatei m\u00f6chten wir die folgende Ausgabe sehen (die je nach Inhalt der Datei variieren kann):

    "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#schritte-der-umsetzung","title":"Schritte der Umsetzung","text":"

    F\u00fcgen Sie dem Projekt eine Klasse mit dem Namen Song hinzu (Rechtsklick auf das Projekt im Solution Explorer, Men\u00fc Hinzuf\u00fcgen / Klasse).

    F\u00fcgen Sie die erforderlichen Mitglieder und einen passenden Konstruktor ein:

    public class Song\n{\n    public readonly string Artist;\n    public readonly string Title;\n\n    public Song(string artist, string title)\n    {\n        Artist = artist;\n        Title = title;\n    }\n}\n

    Property

    Die Mitgliedsvariablen wurden als readonlyeingef\u00fcgt, weil wir nicht wollten, dass sie nach Ausf\u00fchrung des Konstruktors ge\u00e4ndert werden k\u00f6nnen. Eine Alternative w\u00e4re die Verwendung von schreibgesch\u00fctzten Eigenschaften anstelle von schreibgesch\u00fctzten Mitgliedsvariablen (dies ist ein sp\u00e4terer Kern).

    Im Folgenden werden wir die Operation ToString, die vom impliziten Vorfahren System.Object geerbt wurde, in unserer Klasse Song umdefinieren, um Objektdaten in der gew\u00fcnschten Form zur\u00fcckzugeben. Verwenden Sie die String-Interpolation in der L\u00f6sung (wir haben dies bereits in der ersten \u00dcbung verwendet):

    public override string ToString()\n{\n    return $\"{Artist}: {Title}\";\n}\n

    Die geeignetste Klasse zur Verarbeitung einer Textdatei ist StreamReader im Namensraum System.IO.

    In unserer Funktion Main lesen wir die Datei Zeile f\u00fcr Zeile ein, erstellen die Song Objekte und legen sie in ein List<Song> dynamisch dehnbares Array. Bitte beachten Sie, dass in der Datei vor/nach den durch ;getrennten Elementen Leerzeichen (Space, Tab) stehen k\u00f6nnen, entfernen Sie diese!

    Der folgende Code zeigt eine m\u00f6gliche L\u00f6sung, deren Einzelheiten in den Codekommentaren erl\u00e4utert werden. Dies ist die erste eigenst\u00e4ndige Aufgabe des Semesters und f\u00fcr die meisten Studenten die erste Anwendung von .NET/C#, daher geben wir Ihnen eine Musterl\u00f6sung, aber erfahrenere Studenten k\u00f6nnen es auch selbst versuchen.

    L\u00f6sung
    namespace MusicApp;\n\npublic class Program\n{\n    // Die Funktion Main befindet sich innerhalb der Klasse Program, die hier nicht gezeigt wird\n    public static void Main(string[] args)\n    {\n        // Hier werden die Liedobjekte gespeichert\n        Liste<Song> songs = new List<Song>();\n\n        // Datei zeilenweise durchsuchen, Liederliste hochladen\n        StreamReader sr = null;\n        try\n        {\n            // @ steht f\u00fcr @ vor der Zeichenkettenkonstante:\n            // Deaktiviert String Escape,\n            // damit Sie nicht '\\\\' statt '\\\\' schreiben m\u00fcssen.\n            sr = new StreamReader(@\"C:\\temp\\music.txt\");\n            string line;\n            while ((line = sr.ReadLine()) != null)\n            {\n                // Wenn die Warteschlange leer war\n                if (string.IsNullOrWhiteSpace(line))\n                    continue;\n\n                // Die Zeilenvariable enth\u00e4lt die gesamte Zeile,\n                // geteilt entlang der ;- mit Split\n                string[] lineItems = line.Split(';');\n\n                // Erstes Element, in dem wir den Namen des Autors erwarten\n                // Trim entfernt f\u00fchrende und nachfolgende Wei\u00dfraumzeichen\n                string artist = lineItems[0].Trim();\n\n                // Gehen Sie die Lieder durch und f\u00fcgen Sie sie der Liste hinzu\n                for (int i = 1; i < lineItems.Length; i++)\n                {\n                    Song song = new Song(artist, lineItems[i].Trim());\n                    songs.Add(song);\n                }\n            }\n        }\n        catch (Exception e)\n        {\n            Console.WriteLine(\"Die Datei konnte nicht verarbeitet werden.\");\n            // e.Message enth\u00e4lt nur den Text der Ausnahme. \n            // Wenn Sie alle Ausnahmeinformationen (z.B. Stacktrace) ausgeben m\u00f6chten, \n            // dann wird e.ToString() gedruckt.\n            Console.WriteLine(e.Message);\n        }\n        finally\n        {\n            // Es ist wichtig, dass die Datei abschlie\u00dfend in einem Block geschlossen wird, \n            // um sicherzustellen, dass wir im Falle einer Ausnahme keine offene Datei haben.\n            // Wir h\u00e4tten einen using-Block anstelle von try-finally verwenden k\u00f6nnen,\n            // Das brauchen Sie noch nicht zu wissen (wir werden es in der Mitte des Semesters lernen).\n            if (sr != null)\n                sr.Close();\n        }\n\n        // Ausgabe der Lieder in der Liste auf der Konsole\n        foreach (Song song in songs)\n            Console.WriteLine(song.ToString());\n    }\n}\n

    Kopieren Sie die Datei \"music.txt\" in den Ordner \"c:\\temp\" und starten Sie die Anwendung. Der Einfachheit halber haben wir alles in die Funktion main aufgenommen, aber in einer \"Live\"-Umgebung ist es ratsam, den Code in eine separate Verarbeitungsklasse auszulagern.

    Im obigen Beispiel werden eine Reihe grundlegender .NET/C#-Techniken vorgestellt. Es lohnt sich auf jeden Fall, sie zu interpretieren und aus den Notizen im obigen Code zu lernen, und wir werden im Laufe des Semesters auf ihnen aufbauen.

    "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#aufgabe-2-beziehung-zwischen-uml-und-code-schnittstellen-und-abstrakten-anwendungstechniken","title":"Aufgabe 2 - Beziehung zwischen UML und Code, Schnittstellen und abstrakten Anwendungstechniken","text":""},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#ursprungliche-umgebung","title":"Urspr\u00fcngliche Umgebung","text":"

    Die anf\u00e4ngliche Umgebung befindet sich im Ordner Feladat2, \u00f6ffnen Sie die Datei Shapes.sln in Visual Studio und arbeiten Sie in dieser L\u00f6sung.

    Achtung!

    Das Erstellen einer neuen Projektmappe und/oder Projektdatei oder die Ausrichtung des Projekts auf andere/neuere .NET-Versionen ist verboten.

    Es gibt eine Datei Controls.dll im Ordner Feladat2\\Shapes, die Sie zur L\u00f6sung des Problems verwenden m\u00fcssen.

    "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#einzureichen-zusatzlich-zum-quellcode","title":"Einzureichen (zus\u00e4tzlich zum Quellcode)","text":"

    In zwei bis drei Abs\u00e4tzen eine kurze textliche Zusammenfassung der bei der L\u00f6sung von Aufgabe 2 getroffenen Entwurfsentscheidungen, der wichtigsten Grunds\u00e4tze der L\u00f6sung und der Begr\u00fcndung daf\u00fcr. Dies sollte in die Textdatei readme.md geschrieben werden, die sich bereits im Ordner Feladat2 des urspr\u00fcnglichen Frames befindet, in einem beliebigen Markdown-Format oder als einfacher Text. Es ist wichtig, in der Datei im Ordner Feladat2 zu arbeiten (auch wenn es eine Datei mit demselben Namen im Stammordner gibt).

    "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#verfasst-am_1","title":"Verfasst am","text":"

    Wir haben die Aufgabe, die erste Version einer CAD-Anwendung zu entwickeln, die fl\u00e4chige Vektorgrafiken verarbeiten kann. Lesen Sie mehr:

    Wir nehmen erhebliche Vereinfachungen bei der Umsetzung vor:

    "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#verwendung-von-klassenbibliotheken","title":"Verwendung von Klassenbibliotheken","text":"

    Die L\u00f6sung ist 1. Die Beziehung zwischen dem Modell und dem Code kann auf der Grundlage einer Labor\u00fcbung entwickelt werden. Die vorliegende Aufgabe unterscheidet sich in einem wichtigen Detail: W\u00e4hrend wir nur verbal feststellten, dass der Quellcode der Vorfahrenklasse DisplayBase nicht ver\u00e4nderbar ist, ist dies im Fall unserer Vorfahrenklasse Textbox eine Selbstverst\u00e4ndlichkeit, da sie nur als kompilierte DLL verf\u00fcgbar ist.

    Note

    Die Entwicklung von Mehrkomponentenanwendungen, die Zusammenstellung und die Projektreferenz wurden in der ersten Vorlesung behandelt; wenn Sie sich nicht an dieses Thema erinnern, lohnt es sich, es zu wiederholen.

    Im Folgenden werden wir uns die Schritte zur Verwendung der Klassen in einer solchen DLL in unserem Code ansehen:

    1. Klicken Sie im Fenster Visual Studio Solution Explorer mit der rechten Maustaste auf Abh\u00e4ngigkeiten und w\u00e4hlen Sie Verweis hinzuf\u00fcgen oder Projektverweis hinzuf\u00fcgen(je nachdem, was vorhanden ist).
    2. W\u00e4hlen Sie auf der linken Seite des angezeigten Fensters Browse,
    3. Wenn Controls.dll in der Liste in der Mitte des Fensters erscheint, deaktivieren Sie das Kontrollk\u00e4stchen.
    4. Wenn sie nicht angezeigt wird, klicken Sie auf die Schaltfl\u00e4che Durchsuchen... unten rechts im Fenster. 1. Navigieren Sie im angezeigten Dateibrowser-Fenster zur Datei Controls.dll und doppelklicken Sie darauf, um das Fenster zu schlie\u00dfen. 2. In der Mitte des Referenzmanager-Fensters sehen Sie das H\u00e4kchen bei Controls.dll. Klicken Sie auf OK, um das Fenster zu schlie\u00dfen.
    5. Klicken Sie auf OK, um das Fenster zu schlie\u00dfen.
    Sehr selten, aber es kann vorkommen, dass Visual Studio eine Fehlermeldung

    Referenz ist ung\u00fcltig oder wird nicht unterst\u00fctzt\" anzeigt, wenn Sie die oben genannten Schritte ausf\u00fchren. In den meisten F\u00e4llen hilft eine Neuinstallation von Visual Studio.

    Damit haben wir in unserem Projekt einen Verweis auf Controls.dllhinzugef\u00fcgt, so dass die darin enthaltenen Klassen verwendet werden k\u00f6nnen (z. B. k\u00f6nnen sie instanziiert oder von ihnen abgeleitet werden). Wenn Sie im Projektmappen-Explorer auf Abh\u00e4ngigkeiten und dann auf Baugruppen klicken, werden Steuerelemente angezeigt:

    Die Klasse Textbox, von der unsere Klasse TextArea abgeleitet werden soll, befindet sich im Namespace Controls. Die Klasse TextBox hat einen Konstruktor mit vier Parametern, den x- und y-Koordinaten sowie der Breite und H\u00f6he. Bei Bedarf kann der Object Browser *Ihnen helfen, andere Operationen zu entdecken. Der *Object Browser *kann durch Auswahl des Men\u00fcs *Object Browser *aus dem Men\u00fc *Ansicht ge\u00f6ffnet werden. Der *Object Browser *wird in einer neuen Registerkarte angezeigt.

    Wenn die Objektbrowser-Ansicht leer ist

    Visual Studio 2022 zeigt im Objektbrowser nichts an (nur den Text \"Keine Informationen\"), solange keine Quelldatei ge\u00f6ffnet ist. Wenn Sie feststellen, dass die Object Browser-Ansicht leer ist, \u00f6ffnen Sie einfach die Datei Program.cs im Projektmappen-Explorer und wechseln Sie zur\u00fcck zur Registerkarte Object Browser, wo die Komponenten nun angezeigt werden.

    Wenn Sie im *Object Browser *auf die Komponente Controls klicken und jeden Knoten (Namensraum, Klasse) ausw\u00e4hlen, werden die Attribute dieses Knotens angezeigt: Wenn Sie z. B. auf den Klassennamen klicken, werden die Mitglieder der Klasse angezeigt.

    Wir haben nun alle Informationen, die wir zur Erf\u00fcllung der Aufgabe ben\u00f6tigen.

    "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#vorlegen-bei","title":"Vorlegen bei","text":"

    Checkliste f\u00fcr Wiederholungen:

    "},{"location":"hazi/2-nyelvi-eszkozok/","title":"2. HF - Nyelvi eszk\u00f6z\u00f6k","text":""},{"location":"hazi/2-nyelvi-eszkozok/#bevezetes","title":"Bevezet\u00e9s","text":"

    Az \u00f6n\u00e1ll\u00f3 feladat a 2. el\u0151ad\u00e1son \u00e9s a 3. el\u0151ad\u00e1s els\u0151 fel\u00e9ben elhangzottakra \u00e9p\u00edt (ezek a \"El\u0151ad\u00e1s 02 - Nyelvi eszk\u00f6z\u00f6k\" el\u0151ad\u00e1sanyagban szerepelnek). Gyakorlati h\u00e1tter\u00e9\u00fcl a 2. labor - Nyelvi eszk\u00f6z\u00f6k laborgyakorlat szolg\u00e1l.

    A fentiekre \u00e9p\u00edtve, jelen \u00f6n\u00e1ll\u00f3 gyakorlat feladatai a feladatle\u00edr\u00e1st k\u00f6vet\u0151 r\u00f6videbb ir\u00e1nymutat\u00e1s seg\u00edts\u00e9g\u00e9vel elv\u00e9gezhet\u0151k.

    Az \u00f6n\u00e1ll\u00f3 gyakorlat c\u00e9lja:

    A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s.

    C# 12-es (\u00e9s \u00fajabb) nyelvi elemek haszn\u00e1lata

    A h\u00e1zi feladat megold\u00e1sa sor\u00e1n C# 12-es, \u00e9s ann\u00e1l \u00fajabb nyelvi elemek, (pl. primary constructor) nem haszn\u00e1lhat\u00f3k, ugyanis a GitHub-on fut\u00f3 ellen\u0151rz\u0151 ezeket m\u00e9g nem t\u00e1mogatja.

    "},{"location":"hazi/2-nyelvi-eszkozok/#beadas-menete-eloellenorzo","title":"Bead\u00e1s menete, el\u0151ellen\u0151rz\u0151","text":"

    A bead\u00e1s menete megegyezik az els\u0151 h\u00e1zi feladat\u00e9val (r\u00e9szletes le\u00edr\u00e1s a szok\u00e1sos helyen, l\u00e1sd H\u00e1zi feladat munkafolyamat \u00e9s a Git/GitHub haszn\u00e1lata):

    1. GitHub Classroom seg\u00edts\u00e9g\u00e9vel hozz l\u00e9tre magadnak egy repository-t. A megh\u00edv\u00f3 URL-t Moodle-ben tal\u00e1lod (a t\u00e1rgy nyit\u00f3oldal\u00e1n a \"GitHub classroom hivatkoz\u00e1sok a h\u00e1zi feladatokhoz\" hivatkoz\u00e1sra kattintva megjelen\u0151 oldalon l\u00e1that\u00f3). Fontos, hogy a megfelel\u0151, ezen h\u00e1zi feladathoz tartoz\u00f3 megh\u00edv\u00f3 URL-t haszn\u00e1ld (minden h\u00e1zi feladathoz m\u00e1s URL tartozik).
    2. Kl\u00f3nozd le az \u00edgy elk\u00e9sz\u00fclt repository-t. Ez tartalmazni fogja a megold\u00e1s elv\u00e1rt szerkezet\u00e9t.
    3. A feladatok elk\u00e9sz\u00edt\u00e9se ut\u00e1n commit-old \u00e9s push-old a megold\u00e1sod.

    Az el\u0151ellen\u0151rz\u0151 is a szok\u00e1sos m\u00f3don m\u0171k\u00f6dik. R\u00e9szletes le\u00edr\u00e1s: A h\u00e1zi feladat el\u0151ellen\u0151rz\u00e9se \u00e9s hivatalos \u00e9rt\u00e9kel\u00e9se.

    "},{"location":"hazi/2-nyelvi-eszkozok/#feladat-1-baljos-arnyak","title":"Feladat 1 \u2013 Balj\u00f3s \u00e1rnyak","text":""},{"location":"hazi/2-nyelvi-eszkozok/#feladat","title":"Feladat","text":"

    Amint az k\u00f6zismert, a jedi lovagok erej\u00e9t a sejtjeikben \u00e9l\u0151 kis \u00e9letform\u00e1k, a midi-chlorianok adj\u00e1k. Az eddigi legmagasabb midi-chlorian szintet (20.000 f\u00f6l\u00f6tti \u00e9rt\u00e9ket) Anakin Skywalkern\u00e9l m\u00e9rt\u00e9k.

    K\u00e9sz\u00edts egy oszt\u00e1lyt Jedi n\u00e9ven mely egy string t\u00edpus\u00fa Name \u00e9s egy int t\u00edpus\u00fa MidiChlorianCount tulajdons\u00e1ggal rendelkezik. Ut\u00f3bbi eset\u00e9ben figyelj r\u00e1, hogy a MidiChlorianCount \u00e9rt\u00e9k\u00e9t ne lehessen 35-re, vagy ann\u00e1l kisebb \u00e9rt\u00e9kre \u00e1ll\u00edtani, ha ezzel pr\u00f3b\u00e1lkozik valaki, az oszt\u00e1lynak kiv\u00e9telt kell dobnia. A valid\u00e1ci\u00f3 sor\u00e1n a lehet\u0151 legegyszer\u0171bb, legletisztultabb megold\u00e1st v\u00e1laszd: a property setterben egyszer\u0171 if-et haszn\u00e1lj \u00e9s dobj kiv\u00e9telt, ne legyen az if-nek else \u00e1ga, valamint nincs sz\u00fcks\u00e9g a return haszn\u00e1lat\u00e1ra sem.

    "},{"location":"hazi/2-nyelvi-eszkozok/#megoldas","title":"Megold\u00e1s","text":"

    A feladat megold\u00e1sa a 2. labor 1. feladat\u00e1val anal\u00f3g m\u00f3don k\u00e9sz\u00edthet\u0151 el. A MidiChlorianCount tulajdons\u00e1g setter\u00e9ben \u00e9rv\u00e9nytelen \u00e9rt\u00e9k eset\u00e9n dobj kiv\u00e9telt. Ezt p\u00e9ld\u00e1ul a k\u00f6vetkez\u0151 utas\u00edt\u00e1ssal tehet\u0151 meg:

    throw new ArgumentException(\"You are not a true jedi!\");\n
    "},{"location":"hazi/2-nyelvi-eszkozok/#feladat-2-a-klonok-tamadasa","title":"Feladat 2 \u2013 A kl\u00f3nok t\u00e1mad\u00e1sa","text":""},{"location":"hazi/2-nyelvi-eszkozok/#feladat_1","title":"Feladat","text":"

    Eg\u00e9sz\u00edtsd ki az 1. feladatban elk\u00e9sz\u00edtett oszt\u00e1lyt attrib\u00fatumokkal \u00fagy, hogy amennyiben az XmlSerializer oszt\u00e1ly seg\u00edts\u00e9g\u00e9vel, XML form\u00e1tum\u00fa adatf\u00e1jlba \u00edrunk/soros\u00edtunk ki egy Jedi objektumot, a tulajdons\u00e1gai egy-egy XML attrib\u00fatum form\u00e1j\u00e1ban, magyarul jelenjenek meg! Ezt k\u00f6vet\u0151en \u00edrj egy f\u00fcggv\u00e9nyt, mely a Jedi oszt\u00e1ly egy p\u00e9ld\u00e1ny\u00e1t egy sz\u00f6vegf\u00e1jlba soros\u00edtja, majd onnan visszaolvassa egy \u00faj objektumba (ezzel tulajdonk\u00e9ppen kl\u00f3nozva az eredeti objektumot).

    XML soros\u00edt\u00f3 attrib\u00fatumai

    Az XML soros\u00edt\u00e1st szab\u00e1lyoz\u00f3 attrib\u00fatumokat ne tagv\u00e1ltoz\u00f3k, hanem a property-k felett helyezd el!

    A Jedi oszt\u00e1ly legyen publikus

    Az XML soros\u00edt\u00f3 csak publikus oszt\u00e1lyokon tud dolgozni, ennek megfelel\u0151en a Jedi oszt\u00e1ly legyen publikus:

    public class Jedi { ...}\n

    Fontos

    A ment\u00e9st \u00e9s bet\u00f6lt\u00e9st v\u00e9gz\u0151/demonstr\u00e1l\u00f3 k\u00f3dot \u00edrd egy k\u00f6z\u00f6s, erre dedik\u00e1lt f\u00fcggv\u00e9nybe, a f\u00fcggv\u00e9nyt pedig l\u00e1sd el a [Description(\"Feladat2\")] C# attrib\u00fatummal (a f\u00fcggv\u00e9ny el\u0151tti sorba kell be\u00edrni). A mentett/bet\u00f6lt\u00f6tt objektum lok\u00e1lis v\u00e1ltoz\u00f3k\u00e9nt legyen ebben a f\u00fcggv\u00e9nyben megval\u00f3s\u00edtva. Az oszt\u00e1ly/f\u00fcggv\u00e9ny neve b\u00e1rmi lehet (pl. ker\u00fclhet a Program oszt\u00e1lyba is). A f\u00fcggv\u00e9ny nem szorosan a feladathoz tartoz\u00f3 k\u00f3dot ne tartalmazzon, \u00edgy m\u00e1s (r\u00e9sz)feladathoz tartoz\u00f3t sem. A f\u00fcggv\u00e9nyt h\u00edvd meg a Program oszt\u00e1ly Main f\u00fcggv\u00e9ny\u00e9b\u0151l. A fenti attrib\u00fatum haszn\u00e1lat\u00e1hoz using-olni kell a System.ComponentModel n\u00e9vteret.

    L\u00e9nyeges, hogy

    "},{"location":"hazi/2-nyelvi-eszkozok/#megoldas_1","title":"Megold\u00e1s","text":"

    A feladat megold\u00e1sa a 2. labor 4. feladat\u00e1val anal\u00f3g m\u00f3don k\u00e9sz\u00edthet\u0151 el. A megold\u00e1shoz az al\u00e1bbi seg\u00edts\u00e9geket adjuk:

    "},{"location":"hazi/2-nyelvi-eszkozok/#feladat-3-a-sith-ek-bosszuja","title":"Feladat 3 \u2013 A Sith-ek bossz\u00faja","text":""},{"location":"hazi/2-nyelvi-eszkozok/#feladat_2","title":"Feladat","text":"

    A Jeditan\u00e1csban az ut\u00f3bbi id\u0151ben nagy a fluktu\u00e1ci\u00f3. Hogy a v\u00e1ltoz\u00e1sokat k\u00f6nnyebben nyomon k\u00f6vethess\u00fck, k\u00e9sz\u00edts egy oszt\u00e1lyt, mely k\u00e9pes nyilv\u00e1ntartani a tan\u00e1cs tagjait \u00e9s minden v\u00e1ltoz\u00e1sr\u00f3l egy esem\u00e9ny form\u00e1j\u00e1ban sz\u00f6veges \u00e9rtes\u00edt\u00e9st k\u00fcldeni! A lista manipul\u00e1ci\u00f3j\u00e1t k\u00e9t f\u00fcggv\u00e9nnyel lehessen v\u00e9gezni. Az Add f\u00fcggv\u00e9ny egy \u00faj jedi lovagot regisztr\u00e1ljon a tan\u00e1csba, m\u00edg a Remove f\u00fcggv\u00e9ny t\u00e1vol\u00edtsa el a legutolj\u00e1ra felvett tan\u00e1cstagot. K\u00fcl\u00f6n \u00e9rtes\u00edt\u00e9s jelezze, ha a tan\u00e1cs teljesen ki\u00fcr\u00fcl (ehhez ugyanazt az esem\u00e9nyt haszn\u00e1ld, mint a t\u00f6bbi v\u00e1ltoz\u00e1s eset\u00e9n, csak m\u00e1s sz\u00f6veggel jelezze).

    A tan\u00e1cstagok (members) nyilv\u00e1ntart\u00e1s\u00e1t egy List<Jedi> t\u00edpus\u00fa tagv\u00e1ltoz\u00f3ban t\u00e1roljuk, az Add f\u00fcggv\u00e9ny ehhez a list\u00e1hoz f\u0171zze hozz\u00e1 az \u00faj elemeket, m\u00edg a Remove f\u00fcggv\u00e9ny generikus lista RemoveAt utas\u00edt\u00e1s\u00e1val mindig a legutolj\u00e1ra felvett tagot t\u00e1vol\u00edtsa el (az utols\u00f3 elem index\u00e9t a lista hossza alapj\u00e1n tudjuk meghat\u00e1rozni, melyet a Count property ad vissza).

    Az \u00e9rtes\u00edt\u00e9s egy C# esem\u00e9nyen (C# event) kereszt\u00fcl t\u00f6rt\u00e9njen. Az esem\u00e9nyhez tartoz\u00f3 delegate t\u00edpus param\u00e9terk\u00e9nt egy egyszer\u0171 string-et kapjon. Az \u00faj tag hozz\u00e1ad\u00e1s\u00e1t, az egyes tagok elt\u00e1vol\u00edt\u00e1s\u00e1t, illetve az utols\u00f3 tag elt\u00e1vol\u00edt\u00e1s\u00e1t m\u00e1s-m\u00e1s sz\u00f6veg\u0171 \u00fczenet jelezze. Az esem\u00e9ny els\u00fct\u00e9s\u00e9t k\u00f6zvetlen\u00fcl az Add \u00e9s a Remove m\u0171veletekben v\u00e9gezd el (ne vezess be erre seg\u00e9df\u00fcggv\u00e9nyt).

    Az esem\u00e9ny t\u00edpus\u00e1nak ne haszn\u00e1lj be\u00e9p\u00edtett delegate t\u00edpust, hanem vezess be egy saj\u00e1tot.

    Fontos

    A Jeditan\u00e1cs objektumot l\u00e9trehoz\u00f3 \u00e9s azt tesztel\u0151 (C# esem\u00e9ny\u00e9re val\u00f3 feliratkoz\u00e1s, Add \u00e9s Remove h\u00edv\u00e1sa) k\u00f3d ker\u00fclj\u00f6n egy k\u00f6z\u00f6s, \u00f6n\u00e1ll\u00f3 f\u00fcggv\u00e9nybe, ezt a f\u00fcggv\u00e9nyt pedig l\u00e1sd el a [Description(\"Feladat3\")] C# attrib\u00fatummal. Az oszt\u00e1ly/f\u00fcggv\u00e9ny neve b\u00e1rmi lehet. A f\u00fcggv\u00e9ny nem szorosan a feladathoz tartoz\u00f3 k\u00f3dot ne tartalmazzon, \u00edgy m\u00e1s (r\u00e9sz)feladathoz tartoz\u00f3t sem. A f\u00fcggv\u00e9nyt h\u00edvd meg a Program oszt\u00e1ly Main f\u00fcggv\u00e9ny\u00e9b\u0151l.

    L\u00e9nyeges, hogy

    "},{"location":"hazi/2-nyelvi-eszkozok/#megoldas_2","title":"Megold\u00e1s","text":"

    A feladat megold\u00e1sa a 2. labor t\u00f6bb r\u00e9szlet\u00e9re is \u00e9p\u00edt. Az \u00faj esem\u00e9ny bevezet\u00e9s\u00e9t a 2. \u00e9s a 3. feladatban le\u00edrt m\u00f3don tudjuk elv\u00e9gezni, m\u00edg a tan\u00e1cs tagjait egy list\u00e1ban tudjuk nyilv\u00e1ntartani.

    A fenti inform\u00e1ci\u00f3k alapj\u00e1n pr\u00f3b\u00e1ld meg \u00f6n\u00e1ll\u00f3an megoldani a feladatot, majd ha k\u00e9szen vagy, a k\u00f6vetkez\u0151 kinyithat\u00f3 blokkban folytasd az \u00fatmutat\u00f3 olvas\u00e1s\u00e1t \u00e9s vesd \u00f6ssze a megold\u00e1sodat a lenti referencia megold\u00e1ssal! Sz\u00fcks\u00e9g szerint korrig\u00e1ld a saj\u00e1t megold\u00e1sod!

    Publikus l\u00e1that\u00f3s\u00e1g

    A p\u00e9lda \u00e9p\u00edt arra, hogy a r\u00e9sztvev\u0151 oszt\u00e1lyok, tulajdons\u00e1gok, delegate-ek publikus l\u00e1that\u00f3s\u00e1g\u00faak. Amennyiben fura ford\u00edt\u00e1si hib\u00e1val tal\u00e1lkozol, vagy az XmlSerializer fut\u00e1sid\u0151ben hib\u00e1t dob, els\u0151 k\u00f6rben azt ellen\u0151rizd, hogy minden \u00e9rintett helyen megfelel\u0151en be\u00e1ll\u00edtottad-e a publikus l\u00e1that\u00f3s\u00e1got.

    Referencia megold\u00e1s

    A referencia megold\u00e1s l\u00e9p\u00e9sei a k\u00f6vetkez\u0151k:

    1. Hozzunk l\u00e9tre egy \u00faj oszt\u00e1lyt, JediCouncil n\u00e9ven.
    2. Vegy\u00fcnk fel egy List<Jedi> t\u00edpus\u00fa mez\u0151t \u00e9s inicializ\u00e1ljuk egy \u00fcres list\u00e1val.
    3. Val\u00f3s\u00edtsuk meg az Add \u00e9s a Remove f\u00fcggv\u00e9nyeket.

      A fenti l\u00e9p\u00e9seket k\u00f6vet\u0151en az al\u00e1bbi k\u00f3dot kapjuk:

      public class JediCouncil\n{\n    List<Jedi> members = new List<Jedi>();\n\n    public void Add(Jedi newJedi)\n    {\n        members.Add(newJedi);\n    }\n\n    public void Remove()\n    {\n        // Elt\u00e1vol\u00edtja a lista utols\u00f3 elem\u00e9t\n        members.RemoveAt(members.Count - 1);\n    }\n}\n

      K\u00f6vetkez\u0151 l\u00e9p\u00e9sk\u00e9nt val\u00f3s\u00edtsuk meg az esem\u00e9nykezel\u00e9st.

    4. Defini\u00e1ljunk egy \u00faj deleg\u00e1t t\u00edpust (az oszt\u00e1lyon k\u00edv\u00fcl, mivel ez is egy t\u00edpus), mely az \u00e9rtes\u00edt\u00e9sek sz\u00f6veg\u00e9t adja majd \u00e1t:

      public delegate void CouncilChangedDelegate(string message);\n
    5. Eg\u00e9sz\u00edts\u00fck ki a JediCouncil oszt\u00e1lyt az esem\u00e9nykezel\u0151vel:

      public class JediCouncil\n{\n    public event CouncilChangedDelegate CouncilChanged;\n\n    // ...\n}\n
    6. S\u00fcss\u00fck el az esem\u00e9nyt, amikor \u00faj tan\u00e1cstagot vesz\u00fcnk fel. Ehhez az Add met\u00f3dust kell kieg\u00e9sz\u00edten\u00fcnk.

      public void Add(Jedi newJedi)\n{\n    members.Add(newJedi);\n\n    // TODO: Itt s\u00fcsd el az esem\u00e9nyt.\n    // Figyelj arra, hogy csak akkor tedd meg, ha van legal\u00e1bb egy feliratkoz\u00f3/el\u0151fizet\u0151.\n    // Ennek sor\u00e1n ne a terjeng\u0151sebb null ellen\u0151rz\u00e9st, hanem a modernebb, ?.Invoke-ot haszn\u00e1ld.\n}\n
    7. S\u00fcss\u00fck el az esem\u00e9nyt, amikor egy tan\u00e1cstag t\u00e1vozik! K\u00fcl\u00f6nb\u00f6ztess\u00fck meg azt az esetet, amikor a tan\u00e1cs teljesen ki\u00fcr\u00fcl. Ehhez a Remove met\u00f3dust kell kieg\u00e9sz\u00edten\u00fcnk.

      public void Remove()\n{\n    // Elt\u00e1vol\u00edtja a lista utols\u00f3 elem\u00e9t\n    members.RemoveAt(members.Count - 1);\n\n    // TODO: Itt s\u00fcsd el az esem\u00e9nyt.\n    // Figyelj arra, hogy csak akkor tedd meg, ha van legal\u00e1bb egy feliratkoz\u00f3/el\u0151fizet\u0151.\n}\n
    8. Megold\u00e1sunk tesztel\u00e9s\u00e9hez vegy\u00fcnk fel egy MessageReceived f\u00fcggv\u00e9nyt abba az oszt\u00e1lyba, ahol az esem\u00e9nyre val\u00f3 feliratkoz\u00e1st \u00e9s az esem\u00e9ny kezel\u00e9s\u00e9t tesztelni szeretn\u00e9nk (pl. a Program oszt\u00e1lyba). Ezt a f\u00fcggv\u00e9nyt fogjuk feliratkoztatni a JediCouncil \u00e9rtes\u00edt\u00e9seire.

      Program.cs
      private static void MessageReceived(string message)\n{\n    Console.WriteLine(message);\n}\n
    9. V\u00e9gezet\u00fcl tesztelj\u00fck az \u00faj oszt\u00e1lyunkat egy erre a c\u00e9lra dedik\u00e1lt f\u00fcggv\u00e9ny meg\u00edr\u00e1s\u00e1val (ez t\u00f6rt\u00e9nhet pl. a Program oszt\u00e1lyban), a f\u00fcggv\u00e9ny f\u00f6l\u00e9 tegy\u00fck oda a [Description(\"Feladat3\")] attrib\u00fatumot! A f\u00fcggv\u00e9ny v\u00e1za:

      // Tan\u00e1cs l\u00e9trehoz\u00e1sa\nvar council = new JediCouncil();\n\n// TODO: Itt iratkozz fel a council CouncilChanged esem\u00e9ny\u00e9re\n\n// TODO Itt adj hozz\u00e1 k\u00e9t Jedi objektumot a council objektumhoz az Add h\u00edv\u00e1s\u00e1val\n\ncouncil.Remove();\ncouncil.Remove();\n
    10. Ha j\u00f3l v\u00e9gezt\u00fck a dolgunkat, a program futtat\u00e1s\u00e1t k\u00f6vet\u0151en a k\u00f6vetkez\u0151 kimenetet kell kapnunk:

      \u00daj taggal b\u0151v\u00fclt\u00fcnk\n\u00daj taggal b\u0151v\u00fclt\u00fcnk\nZavart \u00e9rzek az er\u0151ben\nA tan\u00e1cs elesett!\n

    Esem\u00e9nyek null vizsg\u00e1lata

    Amennyiben a JediCouncil.Add m\u0171veletben null vizsg\u00e1lattal v\u00e9gezted annak ellen\u0151rz\u00e9s\u00e9t, hogy van-e legal\u00e1bb egy feliratkoz\u00f3 az esem\u00e9nyre, ezt alak\u00edtsd \u00e1t korszer\u0171bb megold\u00e1sra (?.Invoke alkalmaz\u00e1sa, mely t\u00f6m\u00f6rebb form\u00e1ban szint\u00e9n elv\u00e9gzi az ellen\u0151rz\u00e9st, de null vizsg\u00e1lat n\u00e9lk\u00fcl \u2013 err\u0151l a kapcsol\u00f3d\u00f3 el\u0151ad\u00e1son \u00e9s laboron is volt sz\u00f3). Ezt el\u00e9g a JediCouncil.Add kapcs\u00e1n megtenni, a JediCouncil.Remove eset\u00e9ben mindk\u00e9t megold\u00e1s elfogadhat\u00f3 most.

    "},{"location":"hazi/2-nyelvi-eszkozok/#feladat-4-delegatok","title":"Feladat 4 \u2013 Deleg\u00e1tok","text":""},{"location":"hazi/2-nyelvi-eszkozok/#feladat_3","title":"Feladat","text":"

    Eg\u00e9sz\u00edtsd ki a JediCouncil oszt\u00e1lyt egy olyan param\u00e9ter n\u00e9lk\u00fcli f\u00fcggv\u00e9nnyel (a f\u00fcggv\u00e9nyn\u00e9v v\u00e9gz\u0151dj\u00f6n _Delegate-re, ez k\u00f6telez\u0151), mely visszat\u00e9r\u00e9si \u00e9rt\u00e9k\u00e9ben visszaadja a Jedi tan\u00e1cs \u00f6sszes olyan tagj\u00e1t, melynek a midi-chlorian sz\u00e1ma 530 alatt van!

    \u00cdrj egy dedik\u00e1lt \u201etesztel\u0151\u201d f\u00fcggv\u00e9nyt is (pl. a Program oszt\u00e1lyba), mely megh\u00edvja a fenti f\u00fcggv\u00e9ny\u00fcnket \u00e9s ki\u00edrja a visszaadott jedi lovagok neveit! Ez a f\u00fcggv\u00e9ny nem szorosan a feladathoz tartoz\u00f3 k\u00f3dot ne tartalmazzon, \u00edgy m\u00e1s (r\u00e9sz)feladathoz tartoz\u00f3t sem.

    Fontos

    Ezt a \u201etesztel\u0151\u201d f\u00fcggv\u00e9nyt l\u00e1sd el a [Description(\"Feladat4\")] C# attrib\u00fatummal. A f\u00fcggv\u00e9nyt h\u00edvd meg a Program oszt\u00e1ly Main f\u00fcggv\u00e9ny\u00e9b\u0151l.

    L\u00e9nyeges, hogy

    Inicializ\u00e1ci\u00f3 kiszervez\u00e9se

    A megval\u00f3s\u00edt\u00e1s sor\u00e1n vezess be egy k\u00fcl\u00f6n statikus met\u00f3dust (pl. a Program oszt\u00e1lyba), mely param\u00e9terk\u00e9nt egy Jeditan\u00e1cs objektumot kap, abba legal\u00e1bb h\u00e1rom felparam\u00e9terezett Jedi objektumot az Add h\u00edv\u00e1s\u00e1val felvesz. A c\u00e9lunk ezzel az, hogy egy olyan inicializ\u00e1l\u00f3 met\u00f3dusunk legyen, mely a k\u00e9s\u0151bbi feladat(ok) sor\u00e1n is felhaszn\u00e1lhat\u00f3, ne kelljen a kapcsol\u00f3d\u00f3 inicializ\u00e1l\u00f3 k\u00f3dot duplik\u00e1lni.

    "},{"location":"hazi/2-nyelvi-eszkozok/#megoldas_3","title":"Megold\u00e1s","text":"

    A feladat megold\u00e1s\u00e1hoz a 2. labor 6. feladat\u00e1t haszn\u00e1lhatjuk referenciak\u00e9nt. Seg\u00edts\u00e9gk\u00e9nt megadjuk a k\u00f6vetkez\u0151ket:

    "},{"location":"hazi/2-nyelvi-eszkozok/#feladat-5-lambda-kifejezesek","title":"Feladat 5 \u2013 Lambda kifejez\u00e9sek","text":"

    A feladat megfelel az el\u0151z\u0151nek, csak most lambda kifejez\u00e9s seg\u00edts\u00e9g\u00e9vel fogunk dolgozni. Ez a t\u00e9mak\u00f6r szerepelt el\u0151ad\u00e1son \u00e9s laboron is (2. labor 6. feladat).

    Eg\u00e9sz\u00edtsd ki a JediCouncil oszt\u00e1lyt egy olyan param\u00e9ter n\u00e9lk\u00fcli f\u00fcggv\u00e9nnyel (a f\u00fcggv\u00e9nyn\u00e9v v\u00e9gz\u0151dj\u00f6n _Lambda-ra, ez k\u00f6telez\u0151), mely visszat\u00e9r\u00e9si \u00e9rt\u00e9k\u00e9ben visszaadja a Jedi tan\u00e1cs \u00f6sszes olyan tagj\u00e1t, melynek a midi-chlorian sz\u00e1ma 1000 alatt van!

    \u00cdrj egy dedik\u00e1lt \u201etesztel\u0151\u201d f\u00fcggv\u00e9nyt is (pl. a Program oszt\u00e1lyba), mely megh\u00edvja a fenti f\u00fcggv\u00e9ny\u00fcnket \u00e9s ki\u00edrja a visszaadott jedi lovagok neveit! Ez a f\u00fcggv\u00e9ny nem szorosan a feladathoz tartoz\u00f3 k\u00f3dot ne tartalmazzon, \u00edgy m\u00e1s (r\u00e9sz)feladathoz tartoz\u00f3t sem.

    Fontos

    Ezt a \u201etesztel\u0151\u201d f\u00fcggv\u00e9nyt l\u00e1sd el a [Description(\"Feladat5\")] C# attrib\u00fatummal. A f\u00fcggv\u00e9nyt h\u00edvd meg a Program oszt\u00e1ly Main f\u00fcggv\u00e9ny\u00e9b\u0151l.

    L\u00e9nyeges, hogy

    "},{"location":"hazi/2-nyelvi-eszkozok/#feladat-6-actionfunc-hasznalata","title":"Feladat 6 \u2013 Action/Func haszn\u00e1lata","text":"

    Ez a feladat a 3. el\u0151ad\u00e1s anyag\u00e1ra \u00e9p\u00edt, laboron (id\u0151 hi\u00e1ny\u00e1ban) nem szerepelt. Ett\u0151l f\u00fcggetlen\u00fcl ez egy l\u00e9nyeges alapt\u00e9mak\u00f6r a t\u00e1rgyban.

    A projektbe vegy\u00e9l fel egy Person \u00e9s egy ReportPrinter oszt\u00e1lyt (egy-egy, az oszt\u00e1ly nev\u00e9vel egyez\u0151 f\u00e1jlba, az alap\u00e9rtelmezett, ModernLangToolsApp n\u00e9vt\u00e9rbe), a k\u00f6vetkez\u0151 tartalommal:

    Person \u00e9s ReportPrinter oszt\u00e1lyok
    class Person\n{\n    public Person(string name, int age)\n    {\n        Name = name;\n        Age = age;\n    }\n\n    public string Name { get; set; }\n    public int Age { get; set; }\n}\n
    class ReportPrinter\n{\n    private readonly IEnumerable<Person> people;\n    private readonly Action headerPrinter;\n\n    public ReportPrinter(IEnumerable<Person> people, Action headerPrinter)\n    {\n        this.people = people;\n        this.headerPrinter = headerPrinter;\n    }\n\n    public void PrintReport()\n    {\n        headerPrinter();\n        Console.WriteLine(\"-----------------------------------------\");\n        int i = 0;\n        foreach (var person in people)\n        {\n            Console.Write($\"{++i}. \");\n            Console.WriteLine(\"Person\");\n        }\n        Console.WriteLine(\"--------------- Summary -----------------\");\n        Console.WriteLine(\"Footer\");\n    }\n}\n

    Ez a ReportPrinter oszt\u00e1ly arra haszn\u00e1lhat\u00f3, hogy a konstruktor\u00e1ban megadott szem\u00e9lyek adatair\u00f3l form\u00e1zott riportot \u00edrjon ki a konzolra fejl\u00e9c/adatok/l\u00e1bl\u00e9c h\u00e1rmas bont\u00e1sban. A Program.cs f\u00e1jlba vedd fel az al\u00e1bbi f\u00fcggv\u00e9nyt a ReportPrinter kipr\u00f3b\u00e1l\u00e1s\u00e1ra, \u00e9s ezt h\u00edvd is meg a Main f\u00fcggv\u00e9nyb\u0151l:

    ReportPrinter tesztel\u00e9se
    [Description(\"Feladat6\")]\nstatic void test6()\n{\n    var employees = new Person[] { new Person(\"Joe\", 20), new Person(\"Jill\", 30) };\n\n    ReportPrinter reportPrinter = new ReportPrinter(\n        employees,\n        () => Console.WriteLine(\"Employees\")\n        );\n\n    reportPrinter.PrintReport();\n}\n

    Futtassuk az alkalmaz\u00e1st. Az al\u00e1bbi kimenetet kapjuk a konzolon:

    Employees\n-----------------------------------------\n1. Person\n2. Person\n--------------- Summary -----------------\nFooter\n

    Az els\u0151 sorban \"----\" felett tal\u00e1lhat\u00f3 a fejl\u00e9c. Alatta az egye szem\u00e9lyekhez egy-egy \"Person\" be\u00e9getett sz\u00f6veg, majd a \"----\" alatt a l\u00e1bl\u00e9c, egyel\u0151re csak egy be\u00e9getett \"Footer\" sz\u00f6veggel.

    A megold\u00e1sban l\u00e1that\u00f3, hogy a fejl\u00e9c sz\u00f6vege a ReportPrinter oszt\u00e1lyba nincs be\u00e9getve. Ezt ReportPrinter felhaszn\u00e1l\u00f3ja adja meg konstruktor param\u00e9terben egy delegate, eset\u00fcnkben egy lambda kifejez\u00e9s form\u00e1j\u00e1ban. A delegate t\u00edpusa a .NET be\u00e9p\u00edtett Action t\u00edpusa.

    A feladatok a k\u00f6vetkez\u0151k:

    Warning

    A megold\u00e1s sor\u00e1n NEM haszn\u00e1lhatsz saj\u00e1t delegate t\u00edpust (a .NET be\u00e9p\u00edtett delegate t\u00edpusaival dolgozz, a megold\u00e1s csak ekkor elfogadhat\u00f3).

    1. Alak\u00edtsd \u00e1t a ReportPrinter oszt\u00e1lyt \u00fagy, hogy az oszt\u00e1ly haszn\u00e1l\u00f3ja ne csak a fejl\u00e9cet, hanem a l\u00e1bl\u00e9cet is meg tudja adni egy delegate form\u00e1j\u00e1ban a konstruktorban.

    2. Alak\u00edtsd tov\u00e1bb a ReportPrinter oszt\u00e1lyt \u00fagy, hogy az egyes szem\u00e9lyek ki\u00edr\u00e1sakor ne a fix \"Person\" sz\u00f6veg jelenjen meg, hanem a ReportPrinter oszt\u00e1ly haszn\u00e1l\u00f3ja tudja az egyes szem\u00e9lyek adatait az ig\u00e9nyeinek megfelel\u0151en ki\u00edrni a konzolra egy konstruktorban megadott delegate seg\u00edts\u00e9g\u00e9vel (a fix \"Person\" helyett). L\u00e9nyeges, hogy a sorsz\u00e1m a sor elej\u00e9n mindig meg kell jelenjen, ez nem lehet a ReportPrinter haszn\u00e1l\u00f3ja \u00e1ltal megv\u00e1ltoztathat\u00f3 (vagyis ezt a tov\u00e1bbiakban is a ReportPrinter oszt\u00e1lynak kell ki\u00edrnia)!

      Tipp a megold\u00e1shoz

      Hasonl\u00f3 megk\u00f6zel\u00edt\u00e9sben gondolkozz, mint a fejl\u00e9c \u00e9s l\u00e1bl\u00e9c eset\u00e9ben, de itt ehhez a ReportPrinter felhaszn\u00e1l\u00f3j\u00e1nak meg kell kapnia a szem\u00e9ly objektumot ahhoz, hogy azt form\u00e1zottan ki tudja \u00edrni a konzolra.

    3. A Program.cs f\u00e1jlban a ReportPrinter haszn\u00e1lat\u00e1t alak\u00edtsd \u00fagy (megfelel\u0151 lambda kifejez\u00e9sek megad\u00e1s\u00e1val), hogy a kimenet a konzolon a k\u00f6vetkez\u0151 legyen:

      Employees\n-----------------------------------------\n1. Name: Joe (Age: 20)\n2. Name: Jill (Age: 30)\n--------------- Summary -----------------\nNumber of Employees: 2\n

      L\u00e1bl\u00e9cben a dolgoz\u00f3k sz\u00e1m\u00e1nak ki\u00edr\u00e1sa

      Ahhoz, hogy a l\u00e1bl\u00e9cben a dolgoz\u00f3k sz\u00e1m\u00e1nak ki\u00edr\u00e1s\u00e1t eleg\u00e1ns m\u00f3don meg tudd tenni, sz\u00fcks\u00e9g van a \"variable capturing\" t\u00e9mak\u00f6r ismeret\u00e9re (l\u00e1sd 3. el\u0151ad\u00e1s \"Variable capturing, closure\" fejezet).

      H\u00e1zi feladat ellen\u0151rz\u00e9se

      A \"Feladat 6\" feladatot, vagyis azt, hogy a ReportPrinter-t \u00e9s annak haszn\u00e1lat\u00e1t j\u00f3l alak\u00edtottad-e \u00e1t, a GitHub-os automata ellen\u0151rz\u0151 NEM ellen\u0151rzi. Teszteld a megold\u00e1sod alaposan, hogy ne csak a hat\u00e1rid\u0151 ut\u00e1n ut\u00f3lag, a h\u00e1zi feladatok manu\u00e1lis ellen\u0151rz\u00e9se sor\u00e1n der\u00fclj\u00f6n ki, hogy a megold\u00e1s nem elfogadhat\u00f3. (Kieg\u00e9sz\u00edt\u00e9s: 2024.03.13 reggelt\u0151l kezdve m\u00e1r erre is van r\u00e9szleges automata ellen\u0151rz\u00e9s)

    4. A k\u00f6vetkez\u0151 feladat opcion\u00e1lis, a be\u00e9p\u00edtett Func delegate-ek gyakorl\u00e1s\u00e1ra ad j\u00f3 lehet\u0151s\u00e9get. A ReportPrinter oszt\u00e1lynak van egy komolyabb h\u00e1tr\u00e1nya: a kimeneti riportot csak a konzolon tudjuk a seg\u00edts\u00e9g\u00e9vel megjelen\u00edteni. Rugalmasabb megold\u00e1s lenne, ha nem \u00edrna a konzolra, hanem egy string form\u00e1j\u00e1ban lehetne a seg\u00edts\u00e9g\u00e9vel a riportot el\u0151\u00e1ll\u00edtani. Ezt a stringet m\u00e1r \u00fagy haszn\u00e1lhatn\u00e1nk fel, ahogy csak szeretn\u00e9nk (pl. \u00edrhatn\u00e1nk f\u00e1jlba is).

      A feladat a k\u00f6vetkez\u0151: vezess be egy ReportBuilder oszt\u00e1lyt a m\u00e1r megl\u00e9v\u0151 ReportPrinter mint\u00e1j\u00e1ra, de ez ne a konzolra \u00edrjon, hanem egy a teljes riportot tartalmaz\u00f3 stringet \u00e1ll\u00edtson el\u0151, melyet egy \u00fajonnan bevezetett, GetResult() m\u0171velettel lehessen t\u0151le lek\u00e9rdezni.

      Bead\u00e1s

      Ha beadod a feladatot, a ReportBuilder-t p\u00e9ld\u00e1nyos\u00edt\u00f3/tesztel\u0151 k\u00f3dot ne a fenti, test6 f\u00fcggv\u00e9nybe tedd, hanem vezess be egy test6b nev\u0171 f\u00fcggv\u00e9nyt, \u00e9s l\u00e1sd el a [Description(\"Feladat6b\")] attrib\u00fatummal.

      Tippek a megold\u00e1shoz

      • C\u00e9lszer\u0171 az oszt\u00e1lyba egy StringBuilder tagv\u00e1ltoz\u00f3t bevezetni, \u00e9s ennek seg\u00edts\u00e9g\u00e9vel dolgozni. Ez nagys\u00e1grenddel hat\u00e9konyabb, mint a stringek \"+\"-szal val\u00f3 \u00f6sszef\u0171z\u00f6get\u00e9se.
      • A ReportBuilder oszt\u00e1ly haszn\u00e1l\u00f3ja itt m\u00e1r ne a konzolra \u00edrjon, hanem megfelel\u0151 be\u00e9p\u00edtett t\u00edpus\u00fa delegate-ek (itt az Action nem lesz megfelel\u0151) seg\u00edts\u00e9g\u00e9vel adja vissza a ReportBuilder sz\u00e1m\u00e1ra azokat a stringeket, melyeket bele kell f\u0171znie a kimenetbe. A tesztel\u00e9s sor\u00e1n most is lambda kifejez\u00e9seket haszn\u00e1lj!
    "},{"location":"hazi/2-nyelvi-eszkozok/#feladat-7-imsc-beepitett-funcaction-generikus-delegate-tipusok-hasznalata","title":"Feladat 7 (IMSc) \u2013 be\u00e9p\u00edtett Func/Action generikus delegate t\u00edpusok haszn\u00e1lata","text":"

    A feladat megold\u00e1sa nem k\u00f6telez\u0151, de er\u0151sen aj\u00e1nlott: alapanyag, \u00edgy ZH-n/vizsg\u00e1n szerepelhet. Laboron nem volt, csak el\u0151ad\u00e1son.

    A megold\u00e1s\u00e9rt +2 IMSc pont is j\u00e1r.

    "},{"location":"hazi/2-nyelvi-eszkozok/#feladat_4","title":"Feladat","text":"

    B\u0151v\u00edtsd ki a JediCouncil oszt\u00e1lyt.

    "},{"location":"hazi/2-nyelvi-eszkozok/#megoldas_4","title":"Megold\u00e1s","text":""},{"location":"hazi/2-nyelvi-eszkozok/#beadas","title":"Bead\u00e1s","text":"

    Ellen\u0151rz\u0151lista ism\u00e9tl\u00e9sk\u00e9ppen:

    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/","title":"2. HF - Sprachwerkzeuge","text":""},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

    Die eigenst\u00e4ndige Aufgabe baut auf den Vorlesungen der Vorlesung 2 und der ersten H\u00e4lfte der Vorlesung 3 auf (diese sind im Vorlesungsmaterial \"Vorlesung 02 - Sprachliche Mittel\" enthalten). Labor 2 - Sprachwerkzeuge liefert den praktischen Hintergrund f\u00fcr die Labor\u00fcbung.

    Aufbauend auf den obigen Ausf\u00fchrungen k\u00f6nnen die Aufgaben in dieser Selbst\u00fcbung unter Verwendung der k\u00fcrzeren Richtlinien, die auf die Aufgabenbeschreibung folgen, erledigt werden.

    Das Ziel der unabh\u00e4ngigen \u00dcbung:

    Die erforderliche Entwicklungsumgebung wird hier beschrieben.

    Using C# 12 (and newer) language elements

    C# 12 und neuere Sprachelemente (z.B. prim\u00e4rer Konstruktor) k\u00f6nnen in dieser Hausaufgabe nicht verwendet werden, da der Checker auf GitHub sie noch nicht unterst\u00fctzt.

    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#einreichungsverfahren-pre-checker","title":"Einreichungsverfahren, Pre-Checker","text":"

    Der Einreichungsprozess ist derselbe wie bei der ersten Hausaufgabe (siehe Arbeitsablauf bei Hausaufgaben und Verwendung von Git/GitHub f\u00fcr eine detaillierte Beschreibung an der \u00fcblichen Stelle):

    1. Erstellen Sie mit GitHub Classroom ein Repository f\u00fcr sich selbst. Sie finden die Einladungs-URL in Moodle (Sie k\u00f6nnen sie sehen, indem Sie auf den Link*\"GitHub classroom links for homework*\" auf der Startseite des Fachs klicken). Es ist wichtig, dass Sie die richtige Einladungs-URL f\u00fcr diese Hausaufgabe verwenden (jede Hausaufgabe hat eine andere URL).
    2. Klonen Sie das resultierende Repository. Dazu geh\u00f6rt auch die erwartete Struktur der L\u00f6sung.
    3. Nachdem Sie die Aufgaben erledigt haben, \u00fcbergeben Sie Ihre L\u00f6sung alt und dr\u00fccken Sie sie alt.

    Auch der Pre-Checker funktioniert wie gewohnt. Ausf\u00fchrliche Beschreibung: Vorabkontrolle und formale Bewertung der Hausaufgaben.

    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#aufgabe-1-ominose-schatten","title":"Aufgabe 1 - Omin\u00f6se Schatten","text":""},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#verfasst-am","title":"Verfasst am","text":"

    Die Macht der Jedi-Ritter kommt bekanntlich von den winzigen Lebensformen, die in ihren Zellen leben, den Midi-Chlorianern. Der h\u00f6chste jemals gemessene Midi-Chlor-Wert (\u00fcber 20.000) wurde bei Anakin Skywalker gemessen.

    Erstellen Sie eine Klasse mit dem Namen Jedi, die eine Eigenschaft Name vom Typ string und eine Eigenschaft MidiChlorianCount vom Typ int hat. Bei letzterem ist darauf zu achten, dass der Wert von MidiChlorianCount nicht auf 35 oder weniger gesetzt werden kann. W\u00e4hlen Sie f\u00fcr die Validierung die einfachste und sauberste L\u00f6sung, die m\u00f6glich ist: Verwenden Sie ein einfaches ifim Property Setter und l\u00f6sen Sie eine Exception aus, keine else Verzweigung von if, und keine Notwendigkeit, return zu verwenden.

    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#losung","title":"L\u00f6sung","text":"

    Die L\u00f6sung dieser Aufgabe kann auf \u00e4hnliche Weise vorbereitet werden wie in Labor 2, Aufgabe 1. L\u00f6sen Sie im Setter der Eigenschaft MidiChlorianCount eine Ausnahme f\u00fcr einen ung\u00fcltigen Wert aus. Dies kann zum Beispiel mit dem folgenden Befehl geschehen:

    throw new ArgumentException(\"You are not a true jedi!\");\n
    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#aufgabe-2-angriff-auf-die-klone","title":"Aufgabe 2 - Angriff auf die Klone","text":""},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#verfasst-am_1","title":"Verfasst am","text":"

    F\u00fcgen Sie der Klasse, die Sie in \u00dcbung 1 erstellt haben, Attribute hinzu, so dass, wenn Sie ein Objekt Jedi mit der Klasse XmlSerializer in eine XML-Datendatei schreiben/zuweisen, seine Eigenschaften in Englisch als XML-Attribute angezeigt werden Schreiben Sie dann eine Funktion, die eine Instanz der Klasse Jedi in eine Textdatei sortiert und sie in ein neues Objekt zur\u00fcckliest (und damit das urspr\u00fcngliche Objekt klont).

    XML-Sortierattribute

    Platzieren Sie die Attribute, die die XML-Sortierung steuern, \u00fcber den Eigenschaften, nicht \u00fcber den Mitgliedsvariablen!

    Die Jedi-Klasse sollte \u00f6ffentlich sein

    Der XML-Sorter kann nur mit \u00f6ffentlichen Klassen arbeiten, daher sollte die Jedi-Klasse \u00f6ffentlich sein: csharp public class Jedi { ...}

    Wichtig

    Schreiben Sie den Code zum Speichern und Laden/Demonstrieren in eine gemeinsame dedizierte Funktion, und verweisen Sie auf die Funktion mit dem C#-Attribut [Description(\"Task2\")] (das in der Zeile vor der Funktion eingegeben werden muss). Das gespeicherte/geladene Objekt sollte in dieser Funktion als lokale Variable implementiert werden. Der Name der Klasse/Funktion kann beliebig sein (z. B. kann er in der Klasse Program stehen). Die Funktion sollte keinen Code enthalten, der nicht strikt mit der Aufgabe und somit auch nicht mit einer anderen (Unter-)Aufgabe zusammenh\u00e4ngt. Rufen Sie die Funktion \u00fcber die Funktion Main der Klasse Program auf. Um das oben genannte Attribut zu verwenden, m\u00fcssen Sie den Namespace System.ComponentModel verwenden.

    Es ist wichtig, dass

    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#losung_1","title":"L\u00f6sung","text":"

    Die L\u00f6sung dieser Aufgabe kann auf \u00e4hnliche Weise wie in Labor 2, Aufgabe 4, vorbereitet werden. Die folgende Hilfe wird angeboten:

    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#herausforderung-3-die-rache-der-sith","title":"Herausforderung 3 - Die Rache der Sith","text":""},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#verfasst-am_2","title":"Verfasst am","text":"

    Im Rat der Jedi hat es in letzter Zeit eine hohe Fluktuation gegeben. Um den \u00dcberblick \u00fcber \u00c4nderungen zu behalten, erstellen Sie eine Klasse, die Vorstandsmitglieder registrieren und eine Textbenachrichtigung \u00fcber \u00c4nderungen in Form eines Ereignisses senden kann! Die Liste kann mit zwei Funktionen bearbeitet werden. Die Funktion Add nimmt einen neuen Jedi-Ritter in den Rat auf, w\u00e4hrend die Funktion Remove das zuletzt aufgenommene Ratsmitglied wieder entfernt. Separate Benachrichtigung, wenn der Rat komplett leer ist (verwenden Sie dasselbe Ereignis wie f\u00fcr andere \u00c4nderungen, nur mit anderem Text).

    Die Liste der Vorstandsmitglieder (members) wird in einer Mitgliedsvariablen des Typs List<Jedi> gespeichert, die Funktion Add f\u00fcgt dieser Liste neue Mitglieder hinzu, w\u00e4hrend die Funktion Remove immer das letzte durch die generische Liste RemoveAt hinzugef\u00fcgte Mitglied entfernt (der Index des letzten Mitglieds wird durch die L\u00e4nge der Liste bestimmt, die durch die Eigenschaft Count zur\u00fcckgegeben wird).

    Die Benachrichtigung sollte \u00fcber ein C#-Ereignis erfolgen. Der Delegatentyp f\u00fcr das Ereignis sollte ein einfacher stringsein. Das Hinzuf\u00fcgen eines neuen Mitglieds, das Entfernen jedes Mitglieds und das Entfernen des letzten Mitglieds sollte durch einen anderen Nachrichtentext angezeigt werden. Das Ausl\u00f6sen von Ereignissen sollte direkt in Add und Remove erfolgen (f\u00fchren Sie keine Hilfsfunktion ein).

    Verwenden Sie keinen eingebauten Delegatentyp f\u00fcr den Ereignistyp, sondern f\u00fchren Sie einen eigenen ein.

    Wichtig

    Der Code, der das Jeditan\u00e1cs-Objekt erstellt und testet (Abonnieren eines C#-Ereignisses, Aufrufen von Add und Remove ), sollte in einer gemeinsamen, separaten Funktion untergebracht werden, und diese Funktion sollte durch das C#-Attribut [Description(\"Task3\")] dargestellt werden. Der Name der Klasse/Funktion kann beliebig sein. Die Funktion sollte keinen Code enthalten, der nicht strikt mit der Aufgabe und somit auch nicht mit einer anderen (Unter-)Aufgabe zusammenh\u00e4ngt. Rufen Sie die Funktion \u00fcber die Funktion Main der Klasse Program auf.

    Es ist wichtig, dass

    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#losung_2","title":"L\u00f6sung","text":"

    Die L\u00f6sung dieses Problems baut auf mehreren Details aus Labor 2 auf. Die Einf\u00fchrung einer neuen Veranstaltung kann wie in den \u00dcbungen 2 und 3 beschrieben erfolgen, wobei die Mitglieder des Gremiums in einer Liste eingetragen werden k\u00f6nnen.

    Versuchen Sie anhand der obigen Informationen, das Problem selbst zu l\u00f6sen. Wenn Sie fertig sind, lesen Sie die Anleitung im n\u00e4chsten zu \u00f6ffnenden Block weiter und vergleichen Sie Ihre L\u00f6sung mit der Referenzl\u00f6sung unten Korrigieren Sie gegebenenfalls Ihre eigene L\u00f6sung!

    \u00d6ffentliche Sichtbarkeit

    Das Beispiel baut auf der Tatsache auf, dass die beteiligten Klassen, Eigenschaften und Delegierten \u00f6ffentlich sichtbar sind. Wenn Sie auf einen seltsamen \u00dcbersetzungsfehler sto\u00dfen oder XmlSerializer zur Laufzeit einen Fehler ausl\u00f6st, \u00fcberpr\u00fcfen Sie zun\u00e4chst, ob Sie die \u00f6ffentliche Sichtbarkeit auf allen relevanten Websites korrekt eingestellt haben.

    Referenzl\u00f6sung

    Die Schritte der Referenzl\u00f6sung sind wie folgt:

    1. Erstelle eine neue Klasse mit dem Namen JediCouncil.
    2. Man nehme ein Feld vom Typ \"Liste\" und initialisiere es mit einer leeren Liste.
    3. Machen Sie die Funktionen \"Hinzuf\u00fcgen\" und \"Entfernen\" g\u00fcltig.

      Nach den obigen Schritten erhalten wir den folgenden Code:

      public class JediCouncil\n{\n    Liste<Jedi> members = new List<Jedi>();\n\n    public void Add(Jedi newJedi)\n    {\n        members.Add(newJedi);\n    }\n\n    public void Remove()\n    {\n        // Entfernt den letzten Eintrag in der Liste\n        members.RemoveAt(members.Count - 1);\n    }\n}\n

      Der n\u00e4chste Schritt ist die Implementierung der Ereignisbehandlung.

    4. Definieren Sie einen neuen Delegatentyp (au\u00dferhalb der Klasse, da es sich ebenfalls um einen Typ handelt), der den Benachrichtigungstext \u00fcbergeben wird:

      public delegate void CouncilChangedDelegate(string message);\n
    5. F\u00fcgen Sie die Klasse \"JediCouncil\" zum Ereignis-Handler hinzu:

      public class JediCouncil\n{\n    public event CouncilChangedDelegate CouncilChanged;\n\n    // ...\n}\n
    6. Lassen Sie uns das Ereignis feiern, wenn wir ein neues Vorstandsmitglied aufnehmen. Zu diesem Zweck m\u00fcssen wir die Methode \"Hinzuf\u00fcgen\" hinzuf\u00fcgen.

      public void Add(Jedi newJedi)\n{\n    members.Add(newJedi);\n\n    // TODO: Fry die Veranstaltung hier.\n    // Beachten Sie, dass Sie dies nur tun sollten, wenn Sie mindestens einen Teilnehmer haben.\n    // Verwenden Sie dabei das modernere ?.Invoke und nicht die h\u00e4ufigere Nullpr\u00fcfung.\n}\n
    7. Braten Sie das Ereignis, wenn ein Ratsmitglied geht! Unterscheiden Sie den Fall, dass der Rat v\u00f6llig leer ist. Dazu m\u00fcssen wir die Methode Remove hinzuf\u00fcgen.

      public void Remove()\n{\n    // Entfernt den letzten Eintrag in der Liste\n    members.RemoveAt(members.Count - 1);\n\n    // TODO: Fry die Veranstaltung hier.\n    // Beachten Sie, dass Sie dies nur tun sollten, wenn Sie mindestens einen Teilnehmer haben.\n}\n
    8. Um unsere L\u00f6sung zu testen, f\u00fcgen Sie eine Funktion MessageReceived zu der Klasse hinzu, in der wir das Ereignisabonnement und die Ereignisbehandlung testen wollen (z.B. die Klasse Program). Diese Funktion wird verwendet, um `JediCouncil'-Benachrichtigungen zu abonnieren.

      Programm.cs
      private static void MessageReceived(string message)\n{\n    Console.WriteLine(Nachricht);\n}\n
    9. Testen Sie schlie\u00dflich die neue Klasse, indem Sie eine eigene Funktion schreiben (dies kann in der Klasse Programm geschehen) und f\u00fcgen Sie das Attribut [Description(\"Task3\")] oberhalb der Funktion hinzu Das Grundger\u00fcst der Funktion:

      // Einrichtung des Rates\nvar council = new JediCouncil();\n\n// TODO: Melden Sie sich hier f\u00fcr die CouncilChanged-Veranstaltung an\n\n// TODO Hier f\u00fcgen Sie zwei Jedi-Objekte zum Ratsobjekt hinzu, indem Sie Add\n\ncouncil.Remove();\ncouncil.Remove();\n
    10. Wenn wir unsere Arbeit gut gemacht haben, sollten wir nach der Ausf\u00fchrung des Programms die folgende Ausgabe erhalten:

      ``Text Wir haben ein neues Mitglied Wir haben ein neues Mitglied Ich sp\u00fcre eine St\u00f6rung in der Kraft Der Rat ist gefallen! ```

    11. Nullpr\u00fcfung von Ereignissen

      Wenn Sie null in der Operation JediCouncil.Add verwendet haben, um zu pr\u00fcfen, ob es mindestens einen Abonnenten des Ereignisses gibt, konvertieren Sie dies in eine modernere L\u00f6sung (unter Verwendung von?.Invoke, die die Pr\u00fcfung auch in einer pr\u00e4gnanteren Form durchf\u00fchrt, aber ohne null Pr\u00fcfung - dies wurde in der zugeh\u00f6rigen Pr\u00e4sentation und im Labor besprochen). F\u00fcr JediCouncil.Add ist dies ausreichend, f\u00fcr JediCouncil.Remove sind beide L\u00f6sungen vorerst akzeptabel.

      "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#aufgabe-4-delegierte","title":"Aufgabe 4 - Delegierte","text":""},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#verfasst-am_3","title":"Verfasst am","text":"

      Erg\u00e4nzen Sie die Klasse JediCouncil um eine parameterlose Funktion**(der Funktionsname muss ** mit** _Delegateenden , das ist zwingend erforderlich**), die alle Mitglieder des Jedi-Rates mit einer Midi-Chlorzahl unter 530 zur\u00fcckgibt

      Schreibe auch eine eigene \"Tester\"-Funktion (z.B. in der Klasse Program ), die unsere obige Funktion aufruft und die Namen der zur\u00fcckgegebenen Jedi-Ritter ausgibt! Diese Funktion sollte keinen Code enthalten, der nicht strikt mit der Aufgabe und somit auch nicht mit einer anderen (Unter-)Aufgabe zusammenh\u00e4ngt.

      Danger

      Gefahr \"Wichtig\" Siehe diese \"Tester\"-Funktion mit dem [Description(\"Task4\")] C#-Attribut. Rufen Sie die Funktion \u00fcber die Funktion Main der Klasse Program auf.

      Es ist wichtig, dass

      Initialisierung auslagern

      F\u00fchren Sie bei der Implementierung eine eigene statische Methode ein (z.B. in der Klasse Program ), die ein Jeditan\u00e1cs-Objekt als Parameter annimmt und durch Aufruf von Add mindestens drei parametrisierte Jedi -Objekte hinzuf\u00fcgt. Unser Ziel ist es, eine Initialisierungsmethode zu haben, die in der/den sp\u00e4teren Aufgabe(n) verwendet werden kann, ohne dass der entsprechende Initialisierungscode dupliziert werden muss.

      "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#losung_3","title":"L\u00f6sung","text":"

      Zur L\u00f6sung dieser Aufgabe k\u00f6nnen Sie Labor 2 Labor 6 als Referenz verwenden. Um Sie zu unterst\u00fctzen, bieten wir Folgendes an:

      "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#ubung-5-lambda-ausdrucke","title":"\u00dcbung 5 - Lambda-Ausdr\u00fccke","text":"

      Die \u00dcbung ist dieselbe wie die vorhergehende, nur dass wir diesmal mit Lambda-Ausdr\u00fccken arbeiten werden. Dieses Thema wurde sowohl in der Vorlesung als auch im Labor (Labor 2, \u00dcbung 6) behandelt.

      F\u00fcge der Klasse JediCouncil eine Funktion ohne Parameter hinzu**(der Funktionsname muss ** mit** _Lambdaenden , das ist obligatorisch**), die alle Mitglieder des Jedi-Rates mit einer Midi-Chlorianzahl unter 1000 zur\u00fcckgibt

      Schreibe auch eine eigene \"Tester\"-Funktion (z.B. in der Klasse Program ), die unsere obige Funktion aufruft und die Namen der zur\u00fcckgegebenen Jedi-Ritter ausgibt! Diese Funktion sollte keinen Code enthalten, der nicht strikt mit der Aufgabe und somit auch nicht mit einer anderen (Unter-)Aufgabe zusammenh\u00e4ngt.

      Wichtig

      Siehe diese \"Tester\"-Funktion mit dem [Description(\"Task5\")] C#-Attribut. Rufen Sie die Funktion \u00fcber die Funktion Main der Klasse Program auf.

      Es ist wichtig, dass

      "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#aufgabe-6-actionfunc-verwenden","title":"Aufgabe 6 - Action/Func verwenden","text":"

      Diese \u00dcbung baut auf dem Stoff der Vorlesung 3 auf und war (aus Zeitgr\u00fcnden) nicht Bestandteil des Praktikums. Dennoch handelt es sich um ein wesentliches Kernthema des Fachs.

      F\u00fcgen Sie dem Projekt eine Klasse Person und eine Klasse ReportPrinter (jeweils in einer Datei mit dem gleichen Namen wie die Klasse) mit folgendem Inhalt hinzu:

      Person und ReportPrinter Klassen
      class Person\n{\n    public Person(string name, int age)\n    {\n        Name = name;\n        Age = age;\n    }\n\n    public string Name { get; set; }\n    public int Age { get; set; }\n}\n
      class ReportPrinter\n{\n    private readonly IEnumerable<Person> people;\n    private readonly Action headerPrinter;\n\n    public ReportPrinter(IEnumerable<Person> people, Action headerPrinter)\n    {\n        this.people = people;\n        this.headerPrinter = headerPrinter;\n    }\n\n    public void PrintReport()\n    {\n        headerPrinter();\n        Console.WriteLine(\"-----------------------------------------\");\n        int i = 0;\n        foreach (var person in people)\n        {\n            Console.Write($\"{++i}. \");\n            Console.WriteLine(\"Person\");\n        }\n        Console.WriteLine(\"--------------- Summary -----------------\");\n        Console.WriteLine(\"Footer\");\n    }\n}\n

      Diese Klasse ReportPrinter kann verwendet werden, um einen formatierten Bericht \u00fcber die Daten der in ihrem Konstruktor angegebenen Personen in die Konsole zu schreiben, und zwar in einer Dreifachaufteilung von Kopfzeile/Daten/Fu\u00dfzeile. F\u00fcgen Sie die folgende Funktion zu Program.cs hinzu, um ReportPrinter zu testen, und rufen Sie sie von Main aus auf:

      Test ReportPrinter
      [Description(\"Task6\")]\nstatic void test6()\n{\n    var employees = new Person[] { new Person(\"Joe\", 20), new Person(\"Jill\", 30) };\n\n    ReportPrinter reportPrinter = new ReportPrinter(\n        employees,\n        () => Console.WriteLine(\"Employees\")\n        );\n\n    reportPrinter.PrintReport();\n}\n

      F\u00fchren Sie die Anwendung aus. Die Ausgabe auf der Konsole sieht wie folgt aus:

      Employees\n-----------------------------------------\n1. Person\n2. Person\n--------------- Summary -----------------\nFooter\n

      Die erste Zeile \u00fcber \"----\" ist die Kopfzeile. Unter jeder Person befindet sich ein eingebrannter \"Person\"-Text, dann unter \"----\" die Fu\u00dfzeile, vorerst nur mit einem eingebrannten \"Footer\"-Text.

      In der L\u00f6sung k\u00f6nnen Sie sehen, dass der \u00dcberschriftentext nicht in die Klasse ReportPrinter eingebrannt wird. Diese wird vom Benutzer von ReportPrinter in einem Konstruktorparameter in Form eines Delegaten, in unserem Fall eines Lambda-Ausdrucks, angegeben. Der Delegatentyp ist der in .NET integrierte Typ Action.

      Die Aufgaben sind:

      Warning

      Sie k\u00f6nnen NICHT Ihren eigenen Delegattyp in der L\u00f6sung verwenden (arbeiten Sie mit .NET eingebauten Delegattypen, die L\u00f6sung ist nur dann akzeptabel).

      1. Umstrukturierung der Klasse ReportPrinter, so dass der Benutzer der Klasse nicht nur die Kopfzeile, sondern auch die Fu\u00dfzeile in Form eines Delegaten angeben kann.

      2. \u00c4ndern Sie die Klasse ReportPrinter so, dass der feste Text \"Person\" nicht angezeigt wird, wenn jede Person hinzugef\u00fcgt wird, sondern der Benutzer der Klasse ReportPrinter die Daten jeder Person nach Bedarf \u00fcber einen Delegaten hinzuf\u00fcgen kann (anstelle des festen Texts \"Person\"). Es ist wichtig, dass die Zeilennummer immer am Anfang der Zeile steht, sie kann vom Benutzer von ReportPrinter nicht ge\u00e4ndert werden!

        Tipp f\u00fcr die L\u00f6sung

        Denken Sie an einen \u00e4hnlichen Ansatz wie f\u00fcr die Kopf- und Fu\u00dfzeile, aber hier muss der Benutzer von ReportPrinter das Personenobjekt erhalten, um es formatiert in die Konsole schreiben zu k\u00f6nnen.

      3. \u00c4ndern Sie in der Datei Program.cs die Verwendung von ReportPrinter (mit den entsprechenden Lambda-Ausdr\u00fccken), so dass die Ausgabe auf der Konsole lautet:

        Employees\n-----------------------------------------\n1. Name: Joe (Age: 20)\n2. Name: Jill (Age: 30)\n--------------- Summary -----------------\nAnzahl der Mitarbeiter: 2\n

        Hausaufgabenpr\u00fcfung

        Die Aufgabe \"Aufgabe 6\", d.h. ob Sie ReportPrinterund dessen Verwendung korrekt konvertiert haben, wird NICHT vom automatischen GitHub-Checker gepr\u00fcft. Testen Sie Ihre L\u00f6sung gr\u00fcndlich, damit Sie nicht erst nach dem Abgabetermin bei der manuellen Kontrolle Ihrer Hausaufgaben feststellen, dass sie nicht akzeptabel ist.

      4. Die n\u00e4chste \u00dcbung ist optional und bietet Ihnen eine gute Gelegenheit, die eingebauten Func Delegierten zu \u00fcben. Die Klasse ReportPrinter hat einen gro\u00dfen Nachteil: Der Ausgabebericht kann nur auf der Konsole angezeigt werden. Eine flexiblere L\u00f6sung w\u00e4re, nicht in die Konsole zu schreiben, sondern einen String zu verwenden, um den Bericht zu erstellen. Diese Zeichenkette kann auf beliebige Weise verwendet werden (z. B. in eine Datei schreiben).

        Die Aufgabe besteht darin, eine Klasse ReportBuilder einzuf\u00fchren, die auf der bestehenden ReportPrinter basiert, aber nicht in die Konsole schreibt, sondern eine Zeichenkette mit dem vollst\u00e4ndigen Bericht erzeugt, der durch eine neu eingef\u00fchrte Operation GetResult() abgerufen werden kann.

        Tipps f\u00fcr die L\u00f6sung

        • Es ist eine gute Idee, eine StringBuilder Mitgliedsvariable in die Klasse einzuf\u00fchren und mit ihr zu arbeiten. Dies ist um Gr\u00f6\u00dfenordnungen effizienter als die Verkettung von Zeichenketten mit \"+\".
        • In diesem Fall sollte der Benutzer der Klasse ReportBuilder nicht mehr in die Konsole schreiben, sondern die an die Ausgabe anzuh\u00e4ngenden Zeichenketten an ReportBuilder zur\u00fcckgeben und dabei die entsprechenden eingebauten Typdelegierten verwenden ( Action ist hier nicht geeignet). Verwenden Sie jetzt Lambda-Terme in der Pr\u00fcfung!
      "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#aufgabe-7-imsc-verwendung-eingebauter-funcaction-generischer-delegatentypen","title":"Aufgabe 7 (IMSc) - Verwendung eingebauter Func/Action generischer Delegatentypen","text":"

      Das L\u00f6sen der Aufgabe ist nicht obligatorisch, aber sehr empfehlenswert: Es handelt sich um einen Grundstoff, der in die ZH/Pr\u00fcfung aufgenommen werden kann. Nicht in einem Labor, nur in einer Vorlesung.

      Die L\u00f6sung bringt au\u00dferdem +2 IMSc-Punkte ein.

      "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#verfasst-am_4","title":"Verfasst am","text":"

      Erweitern Sie die Klasse JediCouncil.

      "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#losung_4","title":"L\u00f6sung","text":""},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#vorlegen-bei","title":"Vorlegen bei","text":"

      Checkliste f\u00fcr Wiederholungen:

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/","title":"3. HF - Felhaszn\u00e1l\u00f3i fel\u00fclet kialak\u00edt\u00e1sa","text":""},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#bevezetes","title":"Bevezet\u00e9s","text":"

      A h\u00e1zi feladatban elk\u00e9sz\u00edtend\u0151 kis szoftver egy egyszer\u0171 feladatkezel\u0151 alkalmaz\u00e1s, amelyben a felhaszn\u00e1l\u00f3k feladatokat tudnak list\u00e1zni l\u00e9trehozni, m\u00f3dos\u00edtani.

      Az \u00f6n\u00e1ll\u00f3 feladat a XAML el\u0151ad\u00e1sokon elhangzottakra \u00e9p\u00edt. A feladatok gyakorlati h\u00e1tter\u00e9\u00fcl a 3. labor \u2013 Felhaszn\u00e1l\u00f3i fel\u00fcletek kialak\u00edt\u00e1sa laborgyakorlat szolg\u00e1l.

      A fentiekre \u00e9p\u00edtve, jelen \u00f6n\u00e1ll\u00f3 gyakorlat feladatai a feladatle\u00edr\u00e1st k\u00f6vet\u0151 r\u00f6videbb ir\u00e1nymutat\u00e1s seg\u00edts\u00e9g\u00e9vel (n\u00e9ha alap\u00e9rtelmezetten \u00f6sszecsukva) \u00f6n\u00e1ll\u00f3an elv\u00e9gezhet\u0151k.

      Az \u00f6n\u00e1ll\u00f3 gyakorlat c\u00e9lja:

      A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s.

      Fejleszt\u0151k\u00f6rnyezet WinUI3 fejleszt\u00e9shez

      A kor\u00e1bbi laborokhoz k\u00e9pest plusz komponensek telep\u00edt\u00e9se sz\u00fcks\u00e9ges. A fenti oldal eml\u00edti, hogy sz\u00fcks\u00e9g van a \".NET desktop development\" Visual Studio Workload telep\u00edt\u00e9s\u00e9re, valamint ugyanitt az oldal alj\u00e1n van egy \"WinUI t\u00e1mogat\u00e1s\" fejezet, az itt megadott l\u00e9p\u00e9seket is mindenk\u00e9ppen meg kell tenni!

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#a-beadas-menete","title":"A bead\u00e1s menete","text":"

      B\u00e1r az alapok hasonl\u00f3k, vannak l\u00e9nyeges, a folyamatra \u00e9s k\u00f6vetelm\u00e9nyekre vonatkoz\u00f3 elt\u00e9r\u00e9sek a kor\u00e1bbi h\u00e1zi feladatokhoz k\u00e9pest, \u00edgy mindenk\u00e9ppen figyelmesen olvasd el a k\u00f6vetkez\u0151ket.

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#kikotesek","title":"Kik\u00f6t\u00e9sek","text":"

      MVVM minta - ne alkalmazd! Jelen h\u00e1zi feladatban az MVVM mint\u00e1t m\u00e9g NE haszn\u00e1ld (egyik k\u00e9s\u0151bbi r\u00e9szfeladatn\u00e1l sem), ViewModel oszt\u00e1lyt NE vezess be. Az MVVM egy k\u00e9s\u0151bb h\u00e1zi feladatnak lesz a t\u00e1rgya.

      Layout - egyszer\u0171s\u00e9g Mint \u00e1ltal\u00e1ban, a jelen h\u00e1zi feladat keret\u00e9ben elk\u00e9sz\u00edtend\u0151 feladatra is igaz, hogy az oldal alapelrendez\u00e9s\u00e9t Grid-del c\u00e9lszer\u0171 kialak\u00edtani. Ugyanakkor az egyes bels\u0151 r\u00e9szek elrendez\u00e9s\u00e9nek kialak\u00edt\u00e1sakor t\u00f6rekedj az egyszer\u0171s\u00e9gre: ahol az StackPanel-t is lehet haszn\u00e1lni, ne haszn\u00e1lj Grid-et.

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#1-feladat-modell-kialakitasa-es-tesztadatok","title":"1. feladat - Modell kialak\u00edt\u00e1sa \u00e9s tesztadatok","text":"

      A projekten bel\u00fcl hozzunk l\u00e9tre egy Models mapp\u00e1t (VS Solution Exporerben), majd a mapp\u00e1ba az al\u00e1bbi \u00e1br\u00e1n l\u00e1that\u00f3 oszt\u00e1lyt \u00e9s enum t\u00edpust. A TodoItem oszt\u00e1ly fogja tartalmazni a teend\u0151k adatait, a priorit\u00e1shoz egy felsorolt t\u00edpust hozunk l\u00e9tre.

      Mindk\u00e9t t\u00edpus legyen publikus (\u00edrjuk a class \u00e9s az enum el\u00e9 a public kulcssz\u00f3t), k\u00fcl\u00f6nben \"Inconsistent accessibility\" hib\u00e1t kapn\u00e1nk a k\u00e9s\u0151bbiekben a ford\u00edt\u00e1s sor\u00e1n.

      A MainPage oldal fogja a teend\u0151k list\u00e1j\u00e1t megjelen\u00edteni. Most mem\u00f3ri\u00e1ban l\u00e9v\u0151 tesztadatokat haszn\u00e1ljunk, melyeket a Views mapp\u00e1ban tal\u00e1lhat\u00f3 MainPage.xaml.cs-ben hozzunk l\u00e9tre: itt Todos n\u00e9ven vezess\u00fcnk be egy List<TodoItem> tulajdons\u00e1got (melyet k\u00e9s\u0151bb a fel\u00fcleten elhelyezett ListView vez\u00e9rl\u0151h\u00f6z k\u00f6t\u00fcnk adatk\u00f6t\u00e9ssel). Ez a lista TodoItem objektumokat tartalmaz.

      MainPage.xaml.cs
      public List<TodoItem> Todos { get; set; } = new()\n{\n    new TodoItem()\n    {\n        Id = 3,\n        Title = \"Add Neptun code to neptun.txt\",\n        Description = \"NEPTUN\",\n        Priority = Priority.Normal,\n        IsDone = false,\n        Deadline = new DateTime(2024, 11, 08)\n    },\n    new TodoItem()\n    {\n        Id = 1,\n        Title = \"Buy milk\",\n        Description = \"Should be lactose and gluten free!\",\n        Priority = Priority.Low,\n        IsDone = true,\n        Deadline = DateTimeOffset.Now + TimeSpan.FromDays(1)\n    },\n    new TodoItem()\n    {\n        Id = 2,\n        Title = \"Do the Computer Graphics homework\",\n        Description = \"Ray tracing, make it shiny and gleamy! :)\",\n        Priority = Priority.High,\n        IsDone = false,\n        Deadline = new DateTime(2024, 11, 08)\n    },\n};\n
      A fenti k\u00f3d magyar\u00e1zata

      A fenti k\u00f3dr\u00e9szletben t\u00f6bb modern C# nyelvi elemet kombin\u00e1ltunk:

      MainPage oszt\u00e1ly

      A h\u00e1zi feladat sor\u00e1n a be\u00e9p\u00edtett Page oszt\u00e1lyb\u00f3l sz\u00e1rmaz\u00f3 MainPage oszt\u00e1lyban dolgozunk. A Page oszt\u00e1ly az ablakon bel\u00fcli oldalak k\u00f6z\u00f6tti navig\u00e1ci\u00f3t seg\u00edti. B\u00e1r jelen feladatban ezt nem haszn\u00e1ljuk ki, \u00e9rdemes megszokni a haszn\u00e1lat\u00e1t. Mivel alkalmaz\u00e1sunk egyetlen oldalb\u00f3l \u00e1ll, a f\u0151ablakban egyszer\u0171en csak p\u00e9ld\u00e1nyos\u00edtunk egy MainPage objektumot (\u00e9rdemes a MainWindow.xaml f\u00e1jlban ezt megtekinteni).

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#2-feladat-oldal-elrendezese-layout-lista-megjelenitese","title":"2. feladat - Oldal elrendez\u00e9se (layout), lista megjelen\u00edt\u00e9se","text":""},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#layout","title":"Layout","text":"

      A MainPage.xaml-ben hozzuk l\u00e9tre a fel\u00fcletet, amelyen a teend\u0151k list\u00e1j\u00e1t megjelen\u00edtj\u00fck.

      K\u00e9sz\u00edtend\u0151 alkalmaz\u00e1s list\u00e1z\u00f3 fel\u00fclettel

      Mint a fenti \u00e1bra a h\u00e1rom teend\u0151vel mutatja, a teend\u0151k adatait egym\u00e1s alatt kell megjelen\u00edteni, a teend\u0151k priorit\u00e1s\u00e1t sz\u00ednek jelzik, a k\u00e9sz teend\u0151k mellett azok jobb oldal\u00e1n egy pipa jelenik meg.

      A fel\u00fcleten a k\u00f6vetkez\u0151 strukt\u00far\u00e1ban helyezkednek el az elemek:

      Ha j\u00f3l dolgoztunk, az alkalmaz\u00e1st futtatva, CommandBar-nak a megfelel\u0151 helyen meg kell jelennie.

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#lista-megjelenitese","title":"Lista megjelen\u00edt\u00e9se","text":"

      A CommandBar alatti cell\u00e1ban egy list\u00e1ba (ListView) ker\u00fcljenek a teend\u0151k a k\u00f6vetkez\u0151 tartalommal egym\u00e1s alatt. Az adatok adatk\u00f6t\u00e9sen kereszt\u00fcl hassanak a fel\u00fclet megjelen\u00edt\u00e9s\u00e9re (a kor\u00e1bban bevezetett Todos list\u00e1b\u00f3l jelenjenek meg adatk\u00f6t\u00e9ssel az elemek).

      Elemek a list\u00e1ban

      Mindig gondoljuk \u00e1t, hogy egy objektumhoz t\u00f6rt\u00e9n\u0151, vagy list\u00e1s adatk\u00f6t\u00e9sr\u0151l van-e sz\u00f3, \u00e9s ennek megfelel\u0151 technik\u00e1t alkalmazzunk! Jelen h\u00e1zi feladatban nem biztos, olyan sorrendben j\u00f6nnek ezek el\u0151, mint ahogy laboron szerepeltek!\"

      Felt\u00e9teles sz\u00ednez\u00e9s

      A c\u00edm sz\u00ednez\u00e9s\u00e9re haszn\u00e1lhatunk konvertert vagy x:Bind alap\u00fa f\u00fcggv\u00e9ny k\u00f6t\u00e9st is.

      A Brushok p\u00e9ld\u00e1nyos\u00edt\u00e1s\u00e1hoz haszn\u00e1ljuk a SolidColorBrush oszt\u00e1lyt, vagy haszn\u00e1lhatunk be\u00e9p\u00edtett ecseteket is C#-k\u00f3db\u00f3l (mint fentebb a ThemeResource-szal).

      new SolidColorBrush(Colors.Red);\n\n(Brush)App.Current.Resources[\"ApplicationForegroundThemeBrush\"]\n
      F\u00e9lk\u00f6v\u00e9r bet\u0171t\u00edpus

      A bet\u0171jellemz\u0151ket a \"Font...\" nev\u0171 tulajdons\u00e1gok hat\u00e1rozz\u00e1k meg: FontFamily, FontSize, FontStyle, FontStretch \u00e9s FontWeight.

      Pipa ikon l\u00e1that\u00f3s\u00e1ga

      A pipa ikonhoz haszn\u00e1ljunk egy SymbolIcon-t, aminek az Symbol tulajdons\u00e1g\u00e1t \u00e1ll\u00edtsuk be Accept \u00e9rt\u00e9kre.

      A pipa ikon megjelen\u00edt\u00e9sekor egy igaz-hamis \u00e9rt\u00e9ket kell \u00e1talak\u00edtani Visibility t\u00edpus\u00fara. Erre ugyan haszn\u00e1lhatn\u00e1nk konvertert is, de ez a konverzi\u00f3 annyira gyakori, hogy az x:Bind adatk\u00f6t\u00e9s be\u00e9p\u00edtetten konvert\u00e1lja a bool \u00e9rt\u00e9ket Visibility-re.

      Pipa ikon igaz\u00edt\u00e1sa

      A teend\u0151 c\u00edme \u00e9s a pipa ikon egy sorban kell elhelyezkedjenek (egyik balra, m\u00e1sik jobbra igaz\u00edtva). Ehhez egy tipp: pl. be lehet vetni egy egycell\u00e1s Grid-et. Grid-ben lehet olyat csin\u00e1lni, hogy egy cell\u00e1ba t\u00f6bb vez\u00e9rl\u0151t tesz\u00fcnk \"egym\u00e1sra\", melyek igaz\u00edt\u00e1sa k\u00fcl\u00f6n szab\u00e1lyozhat\u00f3. A m\u00e1sodik laboron \u00edgy oldottuk meg a ListView DataTemplate-ben a n\u00e9v \u00e9s a kor megjelen\u00edt\u00e9s\u00e9t.

      D\u00e1tumok form\u00e1z\u00e1sa

      A hat\u00e1rid\u0151 d\u00e1tum form\u00e1z\u00e1s\u00e1ra haszn\u00e1lhatunk szint\u00e9n konvertert vagy x:Bind alap\u00fa f\u00fcggv\u00e9ny k\u00f6t\u00e9st is, ahol a DateTime.ToString f\u00fcggv\u00e9ny\u00e9t k\u00f6tj\u00fck ki param\u00e9terezve.

      Text=\"{x:Bind Deadline.ToString('yyyy.MM.dd', x:Null)}\"\n

      A x:Null az\u00e9rt kell, mert a ToString f\u00fcggv\u00e9nynek a m\u00e1sodik param\u00e9ter\u00e9t is meg kell adni, de az lehet null is ebben az esetben.

      Listaelemek k\u00f6z\u00f6tti hely

      Az \u00fatmutat\u00f3 k\u00e9perny\u0151ment\u00e9s\u00e9n l\u00e1tszik, hogy a listaelemek k\u00f6z\u00f6tt f\u00fcgg\u0151legesen van kihagyott hely, a listaelemek \u00edgy j\u00f3l elk\u00fcl\u00f6n\u00fclnek. Alapesetben ez nincs \u00edgy. Szerencs\u00e9re a megold\u00e1s sor\u00e1n \u00fagyis kell DataTemplate-et alkalmazni az elemek megjelen\u00edt\u00e9s\u00e9re, \u00edgy ennek kicsi hangol\u00e1s\u00e1val (tipp: egyetlen Margin/Padding megad\u00e1sa) k\u00f6nnyed\u00e9n el\u00e9rhetj\u00fck, hogy a listaelemek k\u00f6z\u00f6tt legyen n\u00e9mi hely a jobb olvashat\u00f3s\u00e1g \u00e9rdek\u00e9ben.

      2. feladat BEADAND\u00d3

      Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol az egyik teend\u0151nek a list\u00e1ban a neve vagy le\u00edr\u00e1sa a NEPTUN k\u00f3dod legyen! (f2.png)

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#3-feladat-uj-teendo-hozzaadasa","title":"3. feladat - \u00daj teend\u0151 hozz\u00e1ad\u00e1sa","text":"

      A grid jobb oldal\u00e1n az 1. sorban a \"To-Do item\" sz\u00f6veg legyen l\u00e1that\u00f3, 25-\u00f6s bet\u0171m\u00e9rettel, v\u00edzszintesen balra, f\u00fcgg\u0151legesen pedig k\u00f6z\u00e9pre igaz\u00edtva, baloldalon 20 pixelnyi \u00fcres hellyel.

      A fel\u00fcleten a Hozz\u00e1ad\u00e1s gombra kattintva jelenjen a 2. sorban egy \u0171rlap, ahol \u00faj teend\u0151t lehet felvenni.

      Az \u0171rlap kin\u00e9zete legyen a k\u00f6vetkez\u0151:

      Teend\u0151 szerkeszt\u0151 \u0171rlap

      Az \u0171rlapban a k\u00f6vetkez\u0151 elemek legyenek egym\u00e1s alatt.

      Az \u0171rlaphoz nem kell speci\u00e1lis, egyedi vez\u00e9rl\u0151t (pl. UserControl k\u00e9sz\u00edteni): egyszer\u0171en haszn\u00e1ljuk valamelyik, a feladathoz j\u00f3l illeszked\u0151 layout panel t\u00edpust.

      N\u00e9h\u00e1ny fenti \u00e9s al\u00e1bb meghat\u00e1rozott k\u00f6vetelm\u00e9ny megval\u00f3s\u00edt\u00e1sa kapcs\u00e1n lentebb g\u00f6rgetve leny\u00edl\u00f3 mez\u0151kben n\u00e9mi ir\u00e1nymutat\u00e1st ad az \u00fatmutat\u00f3.

      Tov\u00e1bbi funkcion\u00e1lis k\u00f6vetelm\u00e9nyek:

      Az \u0171rlap elrendez\u00e9se

      Ment\u00e9s megval\u00f3s\u00edt\u00e1s\u00e1nak l\u00e9p\u00e9sei
      1. Az \u0171rlapban l\u00e9v\u0151 adatokat egy \u00faj TodoItem objektumba gy\u0171jts\u00fck \u00f6ssze, melynek tulajdons\u00e1gait adatk\u00f6tj\u00fck (k\u00e9t ir\u00e1ny\u00faan!) a fel\u00fcleten. Vezess\u00fcnk be egy tulajdons\u00e1got ehhez EditedTodo n\u00e9ven. Ett\u0151l a pontt\u00f3l kezdve k\u00e9t megk\u00f6zel\u00edt\u00e9ssel dolgozhatunk:
        1. Az EditedTodo alapesetben null. Amikor a felhaszn\u00e1l\u00f3 \u00faj to-do elem felv\u00e9tel\u00e9t kezdem\u00e9nyezi, akkor hozzuk l\u00e9tre az \u00faj EditedTodo objektumot, mely az adott \u00faj elem adatait t\u00e1rolja. Ment\u00e9skor ezt az objektumot tessz\u00fck bele a list\u00e1ba. \u00cdgy minden \u00faj elem felv\u00e9telekor az EditedTodo egy \u00faj objektumra hivatkozik.
        2. Egy k\u00f6z\u00f6s EditedTodo objektumot haszn\u00e1lunk minden to-do elem felv\u00e9telekor. Ezt m\u00e1r az oldal l\u00e9trehoz\u00e1skor p\u00e9ld\u00e1nyos\u00edtjuk. Amikor a felhaszn\u00e1l\u00f3 \u00faj to-do elem felv\u00e9tel\u00e9t kezdem\u00e9nyezi (vagy a ment\u00e9s v\u00e9g\u00e9n), akkor gondoskodni kell az EditedTodo alap\u00e9rtelmezett \u00e9rt\u00e9kekkel val\u00f3 felt\u00f6lt\u00e9s\u00e9r\u0151l. Ment\u00e9skor egy m\u00e1solatot kell k\u00e9sz\u00edteni r\u00f3la \u00e9s ezt kell a k\u00f6z\u00f6s list\u00e1ba beletenni.
      2. A k\u00f6vezkez\u0151kben a fenti 1. megk\u00f6zel\u00edt\u00e9s l\u00e9p\u00e9seire adunk ir\u00e1nymutat\u00e1st, de mindenk\u00e9ppen \u00e9rdemes el\u0151sz\u00f6r \u00f6n\u00e1l\u00f3an pr\u00f3b\u00e1lkozni.
      3. Az EditedTodo kezd\u0151\u00e9rt\u00e9ke legyen null, illetve a Hozz\u00e1ad\u00e1s gombra kattintva legyen p\u00e9ld\u00e1nyos\u00edtva az EditedTodo.
      4. A ment\u00e9s sor\u00e1n a Todos list\u00e1hoz adjuk hozz\u00e1 a szerkesztett teend\u0151 objektumot. Gondoljunk arra, hogy az adatk\u00f6t\u00e9seknek friss\u00fclni\u00fck kell a fel\u00fcleten a lista tartalm\u00e1nak v\u00e1ltoz\u00e1sa sor\u00e1n (ehhez az adataink t\u00e1rol\u00e1s\u00e1n kell v\u00e1ltoztatni).
      5. A ment\u00e9s sor\u00e1n az EditedTodo property-t nullozzuk ki. Ezt annak \u00e9rdek\u00e9ben, tessz\u00fck, hogy a k\u00f6vetkez\u0151 to-do elem felv\u00e9telekor az adatk\u00f6t\u00e9s miatt \u00fcresek legyenek az \u0171rlapon a vez\u00e9rl\u0151k, ne a kor\u00e1bbi to-do elem adatai legyenek rajta. Gondoljuk \u00e1t, ez el\u00e9g lesz-e a megold\u00e1shoz? Pr\u00f3b\u00e1ljuk is ki a megold\u00e1sunkat! Amikor az EditedTodo tulajdons\u00e1got \u00e1ll\u00edtjuk, a k\u00f6t\u00f6tt vez\u00e9rl\u0151knek friss\u00fclni\u00fck kell. Mire van ehhez sz\u00fcks\u00e9g? (Tipp: itt most nem az \u00e9rdekel minket, hogy az EditedTodo \u00e1ltal hivatkozott TodoItem tulajdons\u00e1gai, pl. Title, Description v\u00e1ltoznak, hanem a MainPage oszt\u00e1ly EditedTodo tulajdons\u00e1ga v\u00e1ltozik: ennek megfelel\u0151en az EditedTodo-t tartalmaz\u00f3 oszt\u00e1lyban kell a megfelel\u0151 interf\u00e9szt megval\u00f3s\u00edtani).
      Az \u0171rlap l\u00e1that\u00f3s\u00e1g szab\u00e1lyoz\u00e1sa

      Ha a fentieknek megfelel\u0151en dolgoztunk, az \u0171rlapunk pontosan akkor kell l\u00e1that\u00f3 legyen, amikor az EditedTodo \u00e9rt\u00e9ke nem null (gondoljuk \u00e1t, hogy val\u00f3ban \u00edgy van). Erre \u00e9p\u00edtve t\u00f6bb megold\u00e1st is kidolgozhatunk. A legegyszer\u0171bb a klasszikus x:Bind tulajdons\u00e1g alap\u00fa adatk\u00f6t\u00e9s alkalmaz\u00e1sa:

      1. Vezess\u00fcnk be egy \u00faj tulajdons\u00e1got a MainPage oszt\u00e1lyunkban (pl. IsFormVisible n\u00e9ven, bool t\u00edpussal).
      2. Ez pontosan akkor legyen igaz, amikor az EditedTodo nem null. Ennek a karbantart\u00e1sa a mi feladatunk, pl. az EditedTodo setter\u00e9ben.
      3. Ezt a tulajdons\u00e1got lehet adatk\u00f6tni az \u0171rlapunkat reprezent\u00e1l\u00f3 kont\u00e9ner l\u00e1that\u00f3s\u00e1g\u00e1hoz (Visibility tulajdons\u00e1g). Igaz, hogy a t\u00edpusuk nem egyezik, de WinUI alatt van automatikus konverzi\u00f3 a bool \u00e9s Visibility t\u00edpusok k\u00f6z\u00f6tt.
      4. Gondoljunk arra is, hogy amikor a forr\u00e1s tulajdons\u00e1g (IsFormVisible) v\u00e1ltozik, a hozz\u00e1 k\u00f6t\u00f6tt c\u00e9l tulajdons\u00e1got (vez\u00e9rl\u0151 l\u00e1that\u00f3s\u00e1g) eset\u00fcnkben mindig friss\u00edteni kell. Mire van ehhez sz\u00fcks\u00e9g? (Tipp: a tulajdons\u00e1got k\u00f6zvetlen\u00fcl tartalmaz\u00f3 oszt\u00e1lynak - gondoljuk \u00e1t, eset\u00fcnkben ez melyik oszt\u00e1ly - egy megfelel\u0151 interf\u00e9szt meg kell val\u00f3s\u00edtania stb.)
      Alternat\u00edv lehet\u0151s\u00e9gek a megold\u00e1sra

      Egy\u00e9b alternat\u00edv\u00e1k alkalmaz\u00e1sa is lehets\u00e9ges (csak \u00e9rdekess\u00e9gk\u00e9ppen, de ne ezeket alkalmazzuk a megold\u00e1s sor\u00e1n):

      1. F\u00fcggv\u00e9ny alap\u00fa adatk\u00f6t\u00e9s megval\u00f3s\u00edt\u00e1sa, de eset\u00fcnkben ez k\u00f6r\u00fclm\u00e9nyesebb lenne.
        • A x:Bind alapon k\u00f6t\u00f6tt f\u00fcggv\u00e9nynek a megjelen\u00edt\u00e9s \u00e9s elrejt\u00e9shez az EditedTodo property null vagy nem null \u00e9rt\u00e9k\u00e9t kell konvert\u00e1lni Visibility-re.
        • Az adatk\u00f6t\u00e9s sor\u00e1n a FallbackValue='Collapsed' be\u00e1ll\u00edt\u00e1st is haszn\u00e1lnunk kell, mert sajnos az x:Bind alap\u00e9rtelmezetten nem h\u00edvja meg a f\u00fcggv\u00e9nyt, ha az \u00e9rt\u00e9k null.
        • A k\u00f6t\u00f6tt f\u00fcggv\u00e9nynek param\u00e9terben meg kell adni azt a tulajdons\u00e1got, melynek v\u00e1ltoz\u00e1sa eset\u00e9n az adatk\u00f6t\u00e9st friss\u00edteni kell, illetve a tulajdons\u00e1gra vonatkoz\u00f3 v\u00e1ltoz\u00e1s\u00e9rtes\u00edt\u00e9st itt is meg kell val\u00f3s\u00edtani.
      2. Konverter alkalmaz\u00e1sa.
      Priorit\u00e1sok list\u00e1ja

      A ComboBox-ban a Priority felsorolt t\u00edpus \u00e9rt\u00e9keit jelen\u00edts\u00fck meg. Ehhez haszn\u00e1lhatjuk a Enum.GetValues f\u00fcggv\u00e9nyt, amihez k\u00e9sz\u00edts\u00fcnk egy tulajdons\u00e1got a MainPage.xaml.cs-ben.

      public List<Priority> Priorities { get; } = Enum.GetValues(typeof(Priority)).Cast<Priority>().ToList();\n

      A ComboBox ItemsSource tulajdons\u00e1g\u00e1hoz k\u00f6ss\u00fck az Priorities list\u00e1t.

      <ComboBox ItemsSource=\"{x:Bind Priorities}\" />\n

      A fenti p\u00e9ld\u00e1ban az ItemsSource csak azt hat\u00e1rozza meg, hogy milyen elemek jelenjenek meg a ComboBox list\u00e1j\u00e1ban. De ez semmit nem mond arr\u00f3l, hogy a ComboBox kiv\u00e1lasztott elem\u00e9t mihez kell k\u00f6tni. Ehhez sz\u00fcks\u00e9g van m\u00e9g egy adatk\u00f6t\u00e9sre. Laboron ez nem szerepelt, el\u0151ad\u00e1sanyagban pl. a SelectedItem-re \u00e9rdemes r\u00e1keresni (minden el\u0151fordul\u00e1s\u00e1t \u00e9rdemes megn\u00e9zni).

      N\u00e9h\u00e1ny fontosabb vez\u00e9rl\u0151 tulajdons\u00e1g Furcsa, adatk\u00f6t\u00e9shez kapcsol\u00f3d\u00f3 NullReferenceException

      Ha egy \"megfoghatatlannak\" t\u0171n\u0151 NullReferenceException-t kapsz az \u00faj elem felv\u00e9telekor, akkor ellen\u0151rizd, hogy a ComboBox eset\u00e9ben a SelectedValue-t k\u00f6t\u00f6tted-e esetleg a SelectedItem helyett (a SelectedItem haszn\u00e1land\u00f3).

      3. feladat BEADAND\u00d3

      Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol az \u00faj teend\u0151 felv\u00e9tele l\u00e1that\u00f3 m\u00e9g ment\u00e9s el\u0151tt! (f3.1.png)

      Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol az el\u0151z\u0151 k\u00e9pen l\u00e9v\u0151 teend\u0151 a list\u00e1ba ker\u00fclt \u00e9s elt\u0171nt az \u0171rlap! (f3.2.png)

      Fontos krit\u00e9riumok

      Az al\u00e1bbiakban megadunk n\u00e9h\u00e1ny fontos krit\u00e9riumot, melyek mindenk\u00e9ppen felt\u00e9telei a h\u00e1zi feladat elfogad\u00e1s\u00e1nak:

      Opcion\u00e1lis gyakorl\u00f3 feladatok

      Opcion\u00e1lis gyakorl\u00f3 feladat 1 - \u0170rlap g\u00f6rgethet\u0151v\u00e9 t\u00e9tele

      Ehhez mind\u00f6ssze be kell csomagolni az \u0171rlapot egy ScrollViewer vez\u00e9rl\u0151be (illetve ne feledkezz\u00fcnk meg arr\u00f3l, hogy \u00edgy m\u00e1r ez lesz a legk\u00fcls\u0151 elem a grid cell\u00e1ban, \u00edgy r\u00e1 vonatkoz\u00f3an kell megadni a gridbeli poz\u00edci\u00f3t). Ha ezt megval\u00f3s\u00edtod, benne lehet a beadott megold\u00e1sodban.

      Opcion\u00e1lis gyakorl\u00f3 feladat 2 - Fix sz\u00e9less\u00e9g\u0171 \u0171rlap

      Jelen megold\u00e1sunkban az \u0171rlap automatikusan m\u00e9retez\u0151dik az ablakkal. J\u00f3 gyakorl\u00e1si lehet\u0151s\u00e9g ennek olyan \u00e1talak\u00edt\u00e1sa, mely esetben az \u0171rlap fix sz\u00e9less\u00e9g\u0171 (pl. 500 pixel) \u00e9s olyan magass\u00e1g\u00fa, mint a benne lev\u0151 elemek \u00f6ssz magass\u00e1ga. Ha az \u0171rlap eset\u00e9n StackPanellel dolgozt\u00e1l, ehhez mind\u00f6ssze h\u00e1rom attrib\u00fatumot kell felvenni vagy megv\u00e1ltoztatni. Ezt a viselked\u00e9st az al\u00e1bbi anim\u00e1lt k\u00e9p illusztr\u00e1lja. L\u00e9nyeges, hogy beadni a kor\u00e1bbi megold\u00e1st kell, nem ez az opcion\u00e1lis feladatban le\u00edrt viselked\u00e9st!

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#4-opcionalis-feladat-3-imsc-pontert-teendo-szerkesztese","title":"4. Opcion\u00e1lis feladat 3 IMSc pont\u00e9rt - Teend\u0151 szerkeszt\u00e9se","text":"

      Val\u00f3s\u00edtsd meg a teend\u0151k szerkeszt\u00e9s\u00e9nek lehet\u0151s\u00e9g\u00e9t az al\u00e1bbiak szerint:

      Megold\u00e1si tippek

      4. iMSc feladat BEADAND\u00d3

      Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol egy megl\u00e9v\u0151 elemre kattintva kit\u00f6lt\u0151dik az \u0171rlap! (f4.imsc.1.png)

      Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol az el\u0151z\u0151 k\u00e9pen kiv\u00e1lasztott teend\u0151 ment\u00e9s hat\u00e1s\u00e1ra friss\u00fcl a list\u00e1ban! (f4.imsc.2.png)

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#beadas","title":"Bead\u00e1s","text":"

      Ellen\u0151rz\u0151lista ism\u00e9tl\u00e9sk\u00e9ppen:

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/","title":"3. HA - Entwurf der Benutzeroberfl\u00e4che","text":""},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

      Die kleine Software, die in der Hausaufgabe verwirklicht werden soll, ist eine einfache Anwendung zur Aufgabenverwaltung, mit der Benutzer Aufgaben auflisten, erstellen und \u00e4ndern k\u00f6nnen.

      Die eigenst\u00e4ndige Aufgabe baut auf dem auf, was in den XAML-Vorlesungen vermittelt wurde. Den praktischen Hintergrund f\u00fcr die Aufgaben liefert das Labor 3 - Entwurf der Benutzeroberfl\u00e4che.

      Darauf aufbauend k\u00f6nnen die Aufgaben dieser Selbst\u00fcbung mit Hilfe der k\u00fcrzeren Leitf\u00e4den, die auf die Aufgabenbeschreibung folgen (manchmal standardm\u00e4\u00dfig eingeklappt), selbst\u00e4ndig bearbeitet werden.

      Das Ziel der Hausaufgabe:

      Die erforderliche Entwicklungsumgebung wird hier beschrieben.

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#das-verfahren-der-eingabe","title":"Das Verfahren der Eingabe","text":"

      Auf das Moodle soll ein ZIP-Archiv hochgeladen werden, das die folgenden Anforderungen entspricht:

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#vorbedingungen","title":"Vorbedingungen","text":"

      MVVM-Modell - nicht benutzen! Verwenden Sie in dieser Hausaufgabe NICHT das MVVM-Muster (auch nicht in den sp\u00e4teren Teilaufgaben), f\u00fchren Sie NICHT die Klasse ViewModel ein. MVVM wird das Thema einer sp\u00e4teren Hausaufgabe sein.

      Layout - Einfachheit Wie im Allgemeinen, auch in dieser Hausaufgabe sollte das grundlegende Layout der Seite mit Grid gestaltet werden. Bei der Gestaltung der einzelnen internen Abschnitte sollten Sie jedoch darauf achten, dass sie einfach gehalten sind: Wo StackPanelverwendet werden kann, sollten Sie nicht Gridverwenden.

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#aufgabe-1-modellentwurf-und-testdaten","title":"Aufgabe 1. - Modellentwurf und Testdaten","text":"

      Erstellen Sie ein neuen Projekt mit Visual Studio (WinUI 3 Projekt, Blank App, Packaged (WinUI 3 in Desktop) type), und addieren Sie einen Ordner namens Models zu dem erzeugten Projekt. Erstellen Sie die Klasse und den Enum-Typ, die in der folgenden Abbildung gezeigt werden, im Ordner Models. Die Klasse TodoItem enth\u00e4lt die Details zu den Aufgaben, f\u00fcr die Priorit\u00e4t wird ein aufgelisteter Typ erstellt.

      Beide Typen sollten \u00f6ffentlich sein ( class und enum mit public vorangestellt), da Ihr sonst sp\u00e4ter bei der \u00dcbersetzung einen Fehler \"Inconsistent accessibility\" erhalten w\u00fcrden.

      Auf der Seite MainPage wird eine Liste der zu erledigenden Aufgaben angezeigt. Jetzt verwenden Sie speicherinterne Testdaten, die in MainPage.xaml.cs erstellt wurden: Hier f\u00fchren Sie eine Eigenschaft List<TodoItem> mit dem Namen Todos ein (die sp\u00e4ter an das Steuerelement ListView auf der Benutzeroberfl\u00e4che gebunden wird). Diese Liste enth\u00e4lt TodoItem Objekte.

      MainPage.xaml.cs
      public List<TodoItem> Todos { get; set; } = new()\n{\n    new TodoItem()\n    {\n        Id = 3,\n        Title = \"Add Neptun code to neptun.txt\",\n        Description = \"NEPTUN\",\n        Priority = Priority.Normal,\n        IsDone = false,\n        Deadline = new DateTime(2024, 11, 08)\n    },\n    new TodoItem()\n    {\n        Id = 1,\n        Title = \"Buy milk\",\n        Description = \"Should be lactose and gluten free!\",\n        Priority = Priority.Low,\n        IsDone = true,\n        Deadline = DateTimeOffset.Now + TimeSpan.FromDays(1)\n    },\n    new TodoItem()\n    {\n        Id = 2,\n        Title = \"Do the Computer Graphics homework\",\n        Description = \"Ray tracing, make it shiny and gleamy! :)\",\n        Priority = Priority.High,\n        IsDone = false,\n        Deadline = new DateTime(2024, 11, 08)\n    },\n};\n
      Erkl\u00e4rung des obigen Codes

      In dem obigen Code sind mehrere moderne C#-Sprachelemente kombiniert:

      MainPage Klasse

      W\u00e4hrend der Hausaufgabe werden Sie in der Klasse MainPage arbeiten, die aus der eingebauten Klasse Page abgeleitet ist. Die Klasse Page hilft Ihnen, zwischen den Seiten innerhalb des Fensters zu navigieren. Obwohl sie in dieser Hausaufgaa\u00f3be nicht verwendet wird, lohnt es sich, sich an ihre Verwendung zu gew\u00f6hnen. Da unsere Anwendung aus einer einzigen Seite besteht, instanziieren wir einfach ein Objekt MainPage im Hauptfenster (Sie k\u00f6nnen es sich in der Datei MainWindow.xaml ansehen).

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#aufgabe-2-seitenlayout-liste-anzeigen","title":"Aufgabe 2 - Seitenlayout, Liste anzeigen","text":""},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#layout","title":"Layout","text":"

      Unter MainPage.xamlerstellen Sie die Oberfl\u00e4che, auf der die Liste der Aufgaben angezeigt wird.

      Die zu erstellende Anwendung mit einer Benutzeroberfl\u00e4che f\u00fcr Listen

      Wie in der obigen Abbildung mit den drei Aufgaben zu sehen ist, werden die Aufgabendetails untereinander angezeigt, die Priorit\u00e4t der Aufgaben wird durch Farben angezeigt, und neben den erledigten Aufgaben werden mit einem H\u00e4kchen rechts bezeichnet.

      Die Elemente sind in der folgenden Struktur auf der Oberfl\u00e4che angeordnet:

      Wenn Sie Ihre Arbeit richtig gemacht haben, sollte bei der Ausf\u00fchrung der Anwendung CommandBaran der richtigen Stelle erscheinen.

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#liste-anzeigen","title":"Liste anzeigen","text":"

      Stellen Sie in der Zelle unter CommandBar in einer Liste (ListView) die Aufgaben mit folgendem Inhalt untereinander. Die Daten sollen \u00fcber Datenverbindung in der Benutzeroberfl\u00e4che angezeigt werden (die Elemente sollen \u00fcber Datenverbindung aus der zuvor vorgestellten Liste Todos angezeigt werden).

      Elemente in der Liste

      \u00dcberlegen Sie immer, ob Sie Daten an ein Objekt oder an eine Liste binden, und verwenden Sie die entsprechende Technik! Bei dieser Hausaufgabe ist es nicht sicher, dass sie in der Reihenfolge kommen, in der sie im Labor waren!\"

      Bedingte Einf\u00e4rbung

      Sie k\u00f6nnen einen Konverter oder eine Funktionsbindung auf Basis von x:Bind verwenden, um die Adresse einzuf\u00e4rben.

      Um die Pinsel (Brush) zu instanziieren, verwenden Sie die Klasse SolidColorBrush, oder k\u00f6nnen Sie auch eingebaute Pinsel aus C#-Code (wie mit ThemeResource oben) benutzen.

      new SolidColorBrush(Colors.Red);\n\n(Brush)App.Current.Resources[\"ApplicationForegroundThemeBrush\"]\n
      Fette Schriftart

      Schriftattribute k\u00f6nnen unter die Eigenschaften namens \"Font...\" eingestellt werden: FontFamily , FontSize, FontStyle, FontStretch und FontWeight.

      Sichtbarkeit des H\u00e4kchen-Symbol

      F\u00fcr das H\u00e4kchen-Symbol verwenden Sie SymbolIcon, wobei die Eigenschaft Symbol auf Accept gesetzt ist.

      Wenn das H\u00e4kchen-Symbol angezeigt wird, muss ein Wahr-Falsch-Wert in einen Sichtbarkeit-Typ umgewandelt werden. Man k\u00f6nnte daf\u00fcr einen Konverter verwenden, aber diese Konvertierung ist so \u00fcblich, dass in der Datenverbindung x:Bind die Konvertierung von bool in Sichtbarkeit bereits eingebaut ist.

      Ausrichtung des H\u00e4kchen-Symbols

      Der Titel der Aufgabe und das H\u00e4kchen-Symbol m\u00fcssen ausgerichtet sein (eines nach links und eines nach rechts). Hier ein Tipp: Sie k\u00f6nnen z. B. eine einzelne Zelle verwenden Grid. In Gridk\u00f6nnen Sie mehrere Steuerelemente in einer Zelle \"stapeln\" und ihre Ausrichtung separat einstellen. Im zweiten Labor haben wir das Problem der Anzeige von Name und Alter in ListView DataTemplatefolgenderma\u00dfen gel\u00f6st.

      Datumsformatierung

      Zur Formatierung des Datums der Abgabefrist k\u00f6nnen Sie auch einen Konverter oder eine Funktionsbindung auf der Grundlage von x:Bind verwenden, wobei Sie die Funktion DateTime.ToString mit Parametern binden.

      Text=\"{x:Bind Deadline.ToString('yyyy.MM.dd', x:Null)}\"\n

      Das x:Null wird ben\u00f6tigt, weil der zweite Parameter der Funktion ToString angegeben werden muss, aber in diesem Fall kann er null sein.

      Abstand zwischen den Listenelementen

      Auf dem Screenshot der Anleitung sehen Sie, dass zwischen den Listenelementen ein vertikaler Abstand besteht, so dass die Listenelemente gut voneinander getrennt sind. Dies ist nicht standardm\u00e4\u00dfig der Fall. Gl\u00fccklicherweise erfordert die L\u00f6sung, dass DataTemplate f\u00fcr die Anzeige der Elemente verwendet wird, so dass Sie durch eine kleine Anpassung (Tipp: geben Sie einen einzelnen Margin/Padding an) leicht etwas Platz zwischen den Listenelementen f\u00fcr eine bessere Lesbarkeit erreichen k\u00f6nnen.

      Aufgabe 2 - EINGABE

      F\u00fcgen Sie ein Bildschirmfoto der Anwendung ein, in der eine der Aufgaben in der Liste Ihren NEPTUN-Code als Namen oder Beschreibung hat (f2.png).

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#aufgabe-3-eine-neue-aufgabe-hinzufugen","title":"Aufgabe 3 - Eine neue Aufgabe hinzuf\u00fcgen","text":"

      Der Text \"To-Do item\" sollte auf der rechten Seite des Grids in Zeile 1 angezeigt werden, mit Schriftgrad 25, horizontal links ausgerichtet und vertikal zentriert, mit 20 Pixel Leerraum auf der linken Seite.

      Klicken Sie auf der Oberfl\u00e4che auf die Taste Add, um in der zweiten Zeile ein Formular anzuzeigen, in dem Sie eine neue Aufgabe hinzuf\u00fcgen k\u00f6nnen.

      Das Formular sollte wie das folgende aussehen:

      Formular f\u00fcr die Bearbeitung einer Aufgabe

      Das Formular sollte die folgenden Elemente enthalten, die untereinander angeordnet sind.

      Das Formular ben\u00f6tigt kein spezielles, benutzerdefiniertes Steuerelement (z. B. UserControl ): Verwenden Sie einfach einen der Layout-Paneltypen, die f\u00fcr die Aufgabe geeignet sind.

      Zus\u00e4tzliche funktionale Anforderungen:

      Layout des Formulars

      Schritte zur Implementierung des Speicherns und der Kontrolle der Formularsichtbarkeit
      1. Die Daten im Formular werden in einem neuen \"ToDoItem\"-Objekt gesammelt, dessen Eigenschaften (bidirektional!) zu der Oberfl\u00e4che gebunden werden. Erstellen Sie eine Eigenschaft mit dem Namen EditedTodo (der Anfangswert sollte null sein).
      2. Klicken Sie auf die Taste Add, um EditedTodo zu kopieren.
      3. F\u00fcgen Sie beim Speichern das zu bearbeitende Objekt in die Liste \"ToDos\" ein. Denken Sie daran, dass die Datenverbindungen in der Oberfl\u00e4che aktualisiert werden m\u00fcssen, wenn sich der Inhalt der Liste \u00e4ndert (dies erfordert \u00c4nderungen an der Art und Weise, wie wir unsere Daten speichern).
      4. W\u00e4hrend des Speicherns wird die Eigenschaft \"EditedTodo\" gel\u00f6scht, auf \"null\" gesetzt.
      5. Wenn Sie das oben beschriebene getan haben, sollte das Formular genau dann sichtbar sein, wenn EditedTodo nicht null ist (stellen Sie sicher, dass es so ist). Darauf aufbauend k\u00f6nnen Sie mehrere L\u00f6sungen entwickeln. Am einfachsten ist es, die klassische, auf Eigenschaften basierende Datenverbindung \"x:Bind\" zu verwenden:
        1. F\u00fchren Sie eine neue Eigenschaft in unsere Klasse Page ein (z.B. IsFormVisible, mit dem Typ bool).
        2. Dies sollte genau dann wahr sein, wenn EditedTodo nicht null ist. Sie sind daf\u00fcr verantwortlich, dies zu pflegen, z.B. im Setter EditedTodo.
        3. Diese Eigenschaft kann mit der Sichtbarkeit des Containers, der unser Formular darstellt, verkn\u00fcpft werden (Eigenschaft \"Visibility\"). Sie sind zwar nicht vom selben Typ, aber unter WinUI gibt es eine automatische Konvertierung zwischen den Typen bool und Visibility.
        4. Beachten Sie auch, dass bei einer \u00c4nderung der Quelleigenschaft (IsFormVisible) die damit verbundene Zieleigenschaft (Sichtbarkeit des Steuerelements) immer aktualisiert werden muss. Was wird ben\u00f6tigt? (Hinweis: in der Klasse, die direkt die Eigenschaft enth\u00e4lt - \u00fcberlegen Sie, um welche Klasse es in unserem Fall ist - muss eine geeignete Schnittstelle implementiert werden usw.)
      Alternative M\u00f6glichkeiten f\u00fcr die L\u00f6sung

      Andere Alternativen sind ebenfalls m\u00f6glich (nur interessehalber, aber verwenden Sie sie nicht diese in der L\u00f6sung):

      1. Implementieren einer funktionsbasierte Datenverbindung, aber in unserem Fall w\u00e4re dies komplizierter.
        • Bei einer auf der Grundlage von \"x:Bind\" gebundenen Funktion wird der Wert \"null\" oder ein anderer Wert als \"null\" der Eigenschaft \"EditedTodo\" zum Anzeigen und Ausblenden in \"Sichtbarkeit\" umgewandelt.
        • Wenn wir Daten binden, m\u00fcssen wir auch FallbackValue='Collapsed' verwenden, denn leider ruft x:Bind die Funktion standardm\u00e4\u00dfig nicht auf, wenn der Wert null ist.
        • Die gebundene Funktion muss einen Parameter haben, der die Eigenschaft angibt, deren \u00c4nderung die Aktualisierung der Datenverbindung bewirkt, und auch die \u00c4nderungsmeldung f\u00fcr die Eigenschaft muss hier implementiert werden.
      2. Anwendung des Konverters.
      Liste der Priorit\u00e4ten

      Zeigen Sie in ComboBoxdie Werte des aufgelisteten Typs Priority an. Zu diesem Zweck k\u00f6nnen Sie die Funktion Enum.GetValues verwenden und eine Eigenschaft in MainPage.xaml.cserstellen.

      public List<Priority> Priorities { get; } = Enum.GetValues(typeof(Priority)).Cast<Priority>().ToList();\n

      Binden Sie die Liste \"Priorities\" an die Eigenschaft \"ItemsSource\" der \"ComboBox\".

      <ComboBox ItemsSource=\"{x:Bind Priorities}\" />\n

      Im obigen Beispiel gibt ItemsSource nur an, welche Elemente in der Liste der ComboBox erscheinen sollen. Aber das sagt nichts dar\u00fcber aus, woran das ausgew\u00e4hlte Element in der \"ComboBox\" gebunden sein soll. Dies erfordert eine weitere Datenverbindung. Dies wurde in der \u00dcbung nicht erw\u00e4hnt, aber es lohnt sich im Vorlesungsmaterial zum Beispiel SelectedItem suchen (alle Vorkommen lohnt es sich anzuschauen).

      Einige wichtige Controller-Eigenschaften

      Aufgabe 3 - EINGABE

      F\u00fcgen Sie ein Bildschirmfoto der Anwendung ein, auf dem das Hinzuf\u00fcgen der neuen Aufgabe vor dem Speichern sehbar ist! (f3.1.png)

      F\u00fcgen Sie ein Bildschirmfoto der Anwendung ein, auf dem die Aufgabe im vorherigen Bild der Liste hinzugef\u00fcgt wurde und das Formular verschwunden ist (f3.2.png)

      Optionale \u00dcbungsaufgaben

      Optionale \u00dcbungsaufgabe 1 - Ein Formular scrollbar machen

      Alles, was Sie tun m\u00fcssen, ist, das Formular in ein ScrollViewer Steuerelement einzuschlie\u00dfen (und denken Sie daran, dass dies das \u00e4u\u00dferste Element in der Gridzelle sein wird, so dass Sie die Position innerhalb dem Grid daf\u00fcr angeben m\u00fcssen). Wenn Sie dies implementieren, kann es in Ihre eingereichte L\u00f6sung aufgenommen werden.

      Optionale \u00dcbungsaufgabe 2 - Formular mit fester Breite

      In unserer L\u00f6sung wird das Formular automatisch mit dem Fenster skaliert. Eine gute M\u00f6glichkeit ist zu \u00fcben, dies so zu \u00e4ndern, dass das Formular eine feste Breite (z. B. 500 Pixel) und eine H\u00f6he hat, die der Gesamth\u00f6he der darin enthaltenen Elemente entspricht. Wenn Sie f\u00fcr das Formular mit StackPanel gearbeitet haben, m\u00fcssen Sie nur drei Attribute hinzuf\u00fcgen oder \u00e4ndern. Dieses Verhalten wird in der nachstehenden animierten Abbildung veranschaulicht. Es ist wichtig, dass Sie die vorherige L\u00f6sung eingaben soll und nicht das in dieser optionalen \u00dcbung beschriebene Verhalten!

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#4-optionale-aufgabe-fur-3-imsc-punkte-bearbeiten-einer-aufgabe-todo","title":"4. Optionale Aufgabe f\u00fcr 3 IMSc-Punkte - Bearbeiten einer Aufgabe (ToDo)","text":"

      Machen Sie es m\u00f6glich, die Aufgaben wie folgt zu bearbeiten:

      Tipps zur L\u00f6sung

      Aufgave 4. iMSc - EINGABE

      F\u00fcgen Sie ein Bildschirmfoto der Anwendung ein, bei der ein Klick auf einen vorhandenen Eintrag das Formular ausf\u00fcllt (f4.imsc.1.png)

      F\u00fcgen Sie ein Bildschirmfoto der Anwendung ein, auf dem die im vorherigen Screenshot ausgew\u00e4hlte Aufgabe in der Liste als Ergebnis der Speicheraktion aktualisiert wird! (f4.imsc.2.png)

      "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#eingabe","title":"Eingabe","text":"

      Checkliste f\u00fcr Wiederholungen:

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/","title":"4. HF - T\u00f6bbsz\u00e1l\u00fa alkalmaz\u00e1sok fejleszt\u00e9se","text":""},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#bevezetes","title":"Bevezet\u00e9s","text":"

      Az \u00f6n\u00e1ll\u00f3 feladat a konkurens/t\u00f6bbsz\u00e1l\u00fa alkalmaz\u00e1sok fejleszt\u00e9se el\u0151ad\u00e1sokon elhangzottakra \u00e9p\u00edt. A feladatok gyakorlati h\u00e1tter\u00e9\u00fcl a 4. labor \u2013 T\u00f6bbsz\u00e1l\u00fa alkalmaz\u00e1sok fejleszt\u00e9se laborgyakorlat szolg\u00e1l.

      A fentiekre \u00e9p\u00edtve, jelen \u00f6n\u00e1ll\u00f3 gyakorlat feladatai a feladatle\u00edr\u00e1st k\u00f6vet\u0151 r\u00f6videbb ir\u00e1nymutat\u00e1s seg\u00edts\u00e9g\u00e9vel elv\u00e9gezhet\u0151k. Az \u00f6n\u00e1ll\u00f3 gyakorlat a k\u00f6vetkez\u0151 ismeretek elm\u00e9ly\u00edt\u00e9s\u00e9t c\u00e9lozza:

      A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezet a szok\u00e1sos, itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s (a le\u00edr\u00e1sban szerepl\u0151 Windows App SDK-ra is sz\u00fcks\u00e9g van).

      Ellen\u0151rz\u0151 futtat\u00e1sa

      Ehhez a feladathoz \u00e9rdemi el\u0151ellen\u0151rz\u0151 nem tartozik: minden push ut\u00e1n lefut ugyan, de csak a neptun.txt kit\u00f6lt\u00f6tts\u00e9g\u00e9t ellen\u0151rzi \u00e9s azt, van-e ford\u00edt\u00e1si hiba. Az \u00e9rdemi ellen\u0151rz\u00e9st a hat\u00e1rid\u0151 lej\u00e1rta ut\u00e1n a laborvezet\u0151k teszik majd meg.

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#a-beadas-menete","title":"A bead\u00e1s menete","text":""},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-0-a-feladat-attekintese-ismerkedes-a-kiindulo-kerettel","title":"Feladat 0 \u2013 A feladat \u00e1ttekint\u00e9se, ismerked\u00e9s a kiindul\u00f3 kerettel","text":"

      A feladat egy bicikliversenyt szimul\u00e1l\u00f3 alkalmaz\u00e1s elk\u00e9sz\u00edt\u00e9se. A megval\u00f3s\u00edt\u00e1s alappill\u00e9re az alkalmaz\u00e1slogika \u00e9s a megjelen\u00edt\u00e9s k\u00fcl\u00f6nv\u00e1laszt\u00e1sa: az alkalmaz\u00e1slogika semmilyen szinten nem f\u00fcgghet a megjelen\u00edt\u00e9st\u0151l, a megjelen\u00edt\u00e9s pedig f\u00fcgg az alkalmaz\u00e1slogik\u00e1t\u00f3l (\u00e9rtelemszer\u0171en, hiszen annak aktu\u00e1lis \u00e1llapot\u00e1t jelen\u00edti meg).

      A kiindul\u00f3 keret m\u00e1r tartalmaz n\u00e9mi alkalmaz\u00e1s \u00e9s megjelen\u00edt\u00e9shez kapcsol\u00f3d\u00f3 logik\u00e1t. Futtassuk az alkalmaz\u00e1st, \u00e9s tekints\u00fck \u00e1t a fel\u00fclet\u00e9t:

      Az al\u00e1bbi anim\u00e1lt k\u00e9pen azt illusztr\u00e1lja, hogy a megold\u00e1s sor\u00e1n hova szeretn\u00e9nk eljutni:

      A j\u00e1t\u00e9k/szimul\u00e1ci\u00f3 alapelvelve a k\u00f6vetkez\u0151 (m\u00e9g nincs megval\u00f3s\u00edtva):

      Egy extra megval\u00f3s\u00edtott funkci\u00f3 (ez m\u00e1r m\u0171k\u00f6dik): a vil\u00e1gos \u00e9s s\u00f6t\u00e9t t\u00e9ma k\u00f6z\u00f6tti v\u00e1lt\u00e1sra lehet\u0151s\u00e9g van a Ctrl+T billenty\u0171kombin\u00e1ci\u00f3val.

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#alkalmazaslogika","title":"Alkalmaz\u00e1slogika","text":"

      A kiindul\u00f3 keretben az alkalmaz\u00e1slogika oszt\u00e1lyai csak kezdetleges \u00e1llapotban vannak megval\u00f3s\u00edtva. Az oszt\u00e1lyok az AppLogic mapp\u00e1ban/n\u00e9vt\u00e9rben tal\u00e1lhat\u00f3k, n\u00e9zz\u00fck meg ezek k\u00f3dj\u00e1t:

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#megjelenites","title":"Megjelen\u00edt\u00e9s","text":"

      A kiindul\u00f3 keretben a megjelen\u00edt\u00e9s viszonylag j\u00f3l el\u0151 van k\u00e9sz\u00edtve, de ezen is fogunk m\u00e9g dolgozni.

      A fel\u00fclet kialak\u00edt\u00e1sa a MainWindow.xaml-ben tal\u00e1lhat\u00f3, a k\u00f6vetkez\u0151 alapelvek szerint:

      Az ablakhoz tartoz\u00f3 MainWindow.cs code behind f\u00e1jlt is n\u00e9zz\u00fck meg, f\u0151bb elemei a k\u00f6vetkez\u0151k:

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-1-a-felulet-frissitese","title":"Feladat 1 \u2013 A fel\u00fclet friss\u00edt\u00e9se","text":"

      Jelen pillanatban hi\u00e1ba m\u00f3dos\u00edtan\u00e1nk fut\u00e1s k\u00f6zben a j\u00e1t\u00e9k \u00e1llapot\u00e1t: a fel\u00fcletbe be van \u00e9getve a h\u00e1rom bicikli fix poz\u00edci\u00f3ban, ezen fel\u00fcl a fel\u00fcletet friss\u00edt\u0151 UpdateUI m\u0171velet egyel\u0151re soha nem h\u00edv\u00f3dik. Miel\u0151tt belev\u00e1gn\u00e1nk a j\u00e1t\u00e9klogika megval\u00f3s\u00edt\u00e1s\u00e1ba, m\u00f3dos\u00edtsuk a fel\u00fclethez tartoz\u00f3 logik\u00e1t, hogy az k\u00e9pes legyen folyamatosan a j\u00e1t\u00e9k friss \u00e1llapot\u00e1t megjelen\u00edteni.

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#a-biciklik-dinamikus-kezelese","title":"A biciklik dinamikus kezel\u00e9se","text":"

      Az els\u0151 probl\u00e9ma: a MainWindow.xaml-be be van \u00e9getve a h\u00e1rom, biciklit megjelen\u00edt\u0151 TextBlock. \u00cdgy a fel\u00fclet\u00fcnk csak olyan j\u00e1t\u00e9k megjelen\u00edt\u00e9s\u00e9re lenne k\u00e9pes, melyben pontosan h\u00e1rom versenyz\u0151 szerepel. K\u00e9sz\u00edts\u00fck el\u0151 a megjelen\u00edt\u00e9st tetsz\u0151leges sz\u00e1m\u00fa bicikli kezel\u00e9s\u00e9re. Els\u0151 l\u00e9p\u00e9sben t\u00e1vol\u00edtsuk el a MainWindow.xaml-b\u0151l a h\u00e1rom biciklihez tartoz\u00f3 \"be\u00e9getett\" TextBlock defin\u00edci\u00f3t (kommentezz\u00fck ki a h\u00e1rom sort). Ezt k\u00f6vet\u0151en, a code behind f\u00e1jlban, a PrepareRaceButton_Click esem\u00e9nykezel\u0151ben a verseny el\u0151k\u00e9sz\u00edt\u00e9se (game.PrepareRace() h\u00edv\u00e1s) ut\u00e1n:

      1. Dinamikusan hozzunk l\u00e9tre minden, a game objektumban szerepl\u0151 biciklihez (game.Bikes tulajdons\u00e1g!) egy megfelel\u0151 TextBlock objektumot . A l\u00e9trehozott TextBlock tulajdons\u00e1gai pontosan feleljenek meg annak, mint amit a xaml f\u00e1jlban kiiktattunk (FontFamily, FontSize, Margin, Text)
      2. A l\u00e9trehozott TextBlock objektumokat fel kell venni a bikesPanel nev\u0171 StackPanel gyerekei k\u00f6z\u00e9 (a xaml f\u00e1jlban kikommentezett TextBlock-ok is ennek gyerekei voltak, ezt n\u00e9zz\u00fck meg!), m\u00e9gpedig a bikesPanel.Children.Add h\u00edv\u00e1s\u00e1val.
      3. A l\u00e9trehozott TextBlock objektumokat vegy\u00fck fel a bikeTextBlocks list\u00e1ba is. Ez az\u00e9rt fontos - n\u00e9zz\u00fck is meg a k\u00f3dban - mert az UpdateUI fel\u00fcletfriss\u00edt\u0151 f\u00fcggv\u00e9ny a biciklikhez tartoz\u00f3 TextBlock-okat a bikeTextBlocks list\u00e1ban keresi (t\u00f6mbindex alapj\u00e1n p\u00e1ros\u00edtja a bicikliket \u00e9s a TextBlock-okat).

      Annyiban megv\u00e1ltozik az alkalmaz\u00e1s m\u0171k\u00f6d\u00e9se (de ez sz\u00e1nd\u00e9kos), hogy indul\u00e1skor nem jelennek meg biciklik, hanem csak a Prepare Race gombon kattint\u00e1skor.

      Pr\u00f3b\u00e1ljuk a megold\u00e1st magunkt\u00f3l megval\u00f3s\u00edtani a fenti pontokat k\u00f6vetve, majd ellen\u0151rizz\u00fck, hogy alapvet\u0151en megfelel-e az al\u00e1bbi megold\u00e1snak.

      Megold\u00e1s
      foreach (var bike in game.Bikes)\n{\n    var bikeTextBlock = new TextBlock()\n    {\n        Text = \"b\",\n        FontFamily = new FontFamily(\"Webdings\"),\n        FontSize = 64,\n        Margin = new Thickness(10, 0, 0, 0)\n    };\n\n    bikesPanel.Children.Add(bikeTextBlock);\n    bikeTextBlocks.Add(bikeTextBlock);\n}\n
      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#a-feluletfrissites-megvalositasa","title":"A fel\u00fcletfriss\u00edt\u00e9s megval\u00f3s\u00edt\u00e1sa","text":"

      Most m\u00e1r pontosan annyi TextBlock-unk lesz, ah\u00e1ny bicikli van a game objektumban. S\u0151t, az UpdateUI m\u0171velettel tudjuk is a fel\u00fcletet b\u00e1rmikor friss\u00edteni (a game aktu\u00e1lis \u00e1llapot\u00e1nak megfelel\u0151en). A k\u00f6vetkez\u0151 kardin\u00e1lis k\u00e9rd\u00e9s: mikor h\u00edvjuk ez a f\u00fcggv\u00e9nyt, vagyis mikor friss\u00edts\u00fck a fel\u00fcletet. T\u00f6bb megold\u00e1s k\u00f6z\u00fcl v\u00e1laszthatunk:

      \u00c1ltal\u00e1noss\u00e1g\u00e1ban mindk\u00e9t megold\u00e1snak lehetnek el\u0151nyei \u00e9s h\u00e1tr\u00e1nyai. A b) bizonyos tekintetben egyszer\u0171bb (nem kell tudni, mikor v\u00e1ltozik a Game \u00e1llapota), ugyanakkor felesleges friss\u00edt\u00e9s is t\u00f6rt\u00e9nhet (ha nem v\u00e1ltozott az \u00e1llapot k\u00e9t friss\u00edt\u00e9s k\u00f6z\u00f6tt). De hat\u00e9konyabb is lehet, ha az \u00e1llapot nagyon gyakran v\u00e1ltozik, \u00e9s nem akarjuk minden v\u00e1ltoz\u00e1skor a fel\u00fcletet friss\u00edteni, el\u00e9g adott id\u0151k\u00f6z\u00f6nk\u00e9nt egyszer (pl. a szem\u00fcnk \u00fagysem tudja lek\u00f6vetni). Eset\u00fcnkben - els\u0151sorban egyszer\u0171s\u00e9ge miatt - a \"b)\", vagyis id\u0151z\u00edt\u0151 alap\u00fa megold\u00e1st v\u00e1lasztjuk.

      WinUI 3 k\u00f6rnyezetben periodikus esem\u00e9nyek kezel\u00e9s\u00e9re a DispatchTimer oszt\u00e1ly alkalmaz\u00e1sa javasolt (k\u00fcl\u00f6n\u00f6sen, ha a fel\u00fcletelemekhez is hozz\u00e1 k\u00edv\u00e1nunk f\u00e9rni az id\u0151z\u00edtett m\u0171veletben).

      A MainWindow oszt\u00e1lyban vezess\u00fcnk be egy tagv\u00e1ltoz\u00f3t:

          private DispatcherTimer timer;\n

      Ezt k\u00f6vet\u0151en a konstruktorban p\u00e9ld\u00e1nyos\u00edtsuk a timert, rendelj\u00fcnk a Tick esem\u00e9ny\u00e9hez egy esem\u00e9nykezel\u0151 f\u00fcggv\u00e9nyt (ez h\u00edv\u00f3dik adott id\u0151k\u00f6z\u00f6nk\u00e9nt), \u00e1ll\u00edtsuk be az id\u0151k\u00f6zt 100 ms-ra (Interval tulajdons\u00e1g), \u00e9s ind\u00edtsuk el a timert:

      public MainWindow()\n{\n    ...\n\n    timer = new DispatcherTimer();\n    timer.Tick += Timer_Tick;\n    timer.Interval = TimeSpan.FromMilliseconds(100);\n    timer.Start();\n}\n\nprivate void Timer_Tick(object sender, object e)\n{\n    UpdateUI();\n}\n

      Mint l\u00e1that\u00f3, az id\u0151z\u00edt\u0151 esem\u00e9nykezel\u0151ben az UpdateUI h\u00edv\u00e1s\u00e1val friss\u00edtj\u00fck a fel\u00fcletet.

      K\u00e9rd\u00e9s, hogyan tudjuk a megold\u00e1sunkat tesztelni, vagyis azt ellen\u0151rizni, hogy a Timer_Tick esem\u00e9nykezel\u0151 val\u00f3ban megh\u00edv\u00f3dik-e 100 ms-k\u00e9nt. Ehhez Trace-elj\u00fck ki ideiglenesen a Visual Studio Output ablak\u00e1ba az aktu\u00e1lis id\u0151t megfelel\u0151en form\u00e1zva az esem\u00e9nykezel\u0151ben:

      private void Timer_Tick(object sender, object e)\n{\n    System.Diagnostics.Trace.WriteLine($\"Time: {DateTime.Now.ToString(\"hh:mm:ss.fff\")}\");\n\n    UpdateUI();\n}\n

      A Trace.WriteLine m\u0171velet a Visual Studio Output ablak\u00e1ba \u00edr egy sort, a DateTime.Now-val pedig az aktu\u00e1lis id\u0151t lehet lek\u00e9rdeni. Ezt alak\u00edtjuk a ToString h\u00edv\u00e1ssal megfelel\u0151 form\u00e1tum\u00fa sz\u00f6vegg\u00e9. Futtassuk az alkalmaz\u00e1st (l\u00e9nyeges, hogy debuggolva, vagyis az F5 billenty\u0171vel) \u00e9s ellen\u0151rizz\u00fck a Visual Studio Output ablak\u00e1t, hogy val\u00f3ban megjelenik egy \u00faj sor 100 ms-k\u00e9nt. Ha minden j\u00f3l m\u0171k\u00f6dik, a Trace-el\u0151 sort kommentezz\u00fck ki.

      A DispatcherTimer pontoss\u00e1ga

      Azt megfigyelhetj\u00fck, hogy a DispatcherTimer nem k\u00fcl\u00f6n\u00f6sebben pontos, de c\u00e9ljainknak t\u00f6k\u00e9letesen megfelel. Ugyanakkor sz\u00e1munkra fontos tulajdons\u00e1ga, hogy a UI sz\u00e1lon h\u00edv\u00f3dik (a Tick esem\u00e9nye ezen s\u00fcl el), \u00edgy a kezel\u0151f\u00fcggv\u00e9ny\u00fcnkb\u0151l (Timer_Tick) hozz\u00e1 tudunk f\u00e9rni a fel\u00fcletelemekhez.

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#foablak-fejlece","title":"F\u0151ablak fejl\u00e9ce","text":"

      A f\u0151ablak fejl\u00e9ce a \"Tour de France\" sz\u00f6veg legyen, hozz\u00e1f\u0171zve a saj\u00e1t Neptun k\u00f3dod: (pl. \"ABCDEF\" Neptun k\u00f3d eset\u00e9n \"Tour de France - ABCDEF\"), fontos, hogy ez legyen a sz\u00f6veg! Ehhez a f\u0151ablakunk Title tulajdons\u00e1g\u00e1t \u00e1ll\u00edtsuk be erre a sz\u00f6vegre a MainWindow.xaml f\u00e1jlban.

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-2-a-verseny-elokeszitese","title":"Feladat 2 \u2013 A verseny el\u0151k\u00e9sz\u00edt\u00e9se","text":"

      A fentiek sor\u00e1n el is k\u00e9sz\u00fclt\u00fcnk a megjelen\u00edt\u00e9si logik\u00e1val, a f\u00f3kuszunkat most m\u00e1r az alkalmaz\u00e1slogik\u00e1ra, \u00e9s az ahhoz kapcsol\u00f3d\u00f3 sz\u00e1lkezel\u00e9si t\u00e9mak\u00f6rre helyezz\u00fck \u00e1t. Ennek megfelel\u0151en mostant\u00f3l els\u0151dlegesen a Game oszt\u00e1lyban fogunk dolgozni.

      Eml\u00e9keztet\u0151k\u00e9nt, a megold\u00e1sunk alapelve a k\u00f6vetkez\u0151 lesz:

      A k\u00f6vetkez\u0151 l\u00e9p\u00e9seknek megfelel\u0151en alak\u00edtsuk ki a kereteket:

      1. A Game oszt\u00e1ly CreateBike f\u00fcggv\u00e9ny\u00e9nek a v\u00e9g\u00e9n ind\u00edtsunk el egy a ker\u00e9kp\u00e1rhoz tartoz\u00f3 sz\u00e1lat.
      2. A sz\u00e1lf\u00fcggv\u00e9ny a Game oszt\u00e1lyban legyen.
      3. A sz\u00e1lf\u00fcggv\u00e9nynek a CreateBike adja \u00e1t param\u00e9terk\u00e9nt a bicikli objektumot, melyet az adott sz\u00e1l mozgatni fog.
      4. A fut\u00f3 sz\u00e1lak ne blokkolj\u00e1k az alkalmaz\u00e1s bez\u00e1r\u00e1s\u00e1t (vagyis, amikor bez\u00e1rjuk a f\u0151ablakot, de van m\u00e9g fut\u00f3 sz\u00e1l, a process azonnal sz\u0171nj\u00f6n meg, ne v\u00e1rja be ezeket a sz\u00e1lakat)
      5. A sz\u00e1lf\u00fcggv\u00e9ny megval\u00f3s\u00edt\u00e1sa els\u0151 k\u00f6rben a k\u00f6vetkez\u0151kre terjedjen ki.

        Egy ciklusban minden iter\u00e1ci\u00f3ban:

        • v\u00e9letlenszer\u0171 l\u00e9p\u00e9ssel (Bike oszt\u00e1ly Step f\u00fcggv\u00e9ny\u00e9nek h\u00edv\u00e1sa) l\u00e9ptesse a biciklit,
        • majd altassa a sz\u00e1lat 100 ms-ig.

        Mindez a mozgat\u00e1s addig tartson, m\u00edg a bicikli el nem \u00e9ri a startvonalat (a poz\u00edci\u00f3ja el nem \u00e9ri a StartLinePosition tagv\u00e1ltoz\u00f3 \u00e1ltal meghat\u00e1rozott \u00e9rt\u00e9ket).

      Pr\u00f3b\u00e1ld a fentieket \u00f6n\u00e1ll\u00f3an megval\u00f3s\u00edtani az el\u0151ad\u00e1son \u00e9s a laboron tanultak alapj\u00e1n. A megold\u00e1sod debuggol\u00e1ssal tudod tesztelni, illetve mivel a fel\u00fclet logik\u00e1t kor\u00e1bban megval\u00f3s\u00edtottuk, az alkalmaz\u00e1st futtatva a Prepare Race gombra kattintva is: ekkor a biciklik el kell g\u00f6rd\u00fcljenek fokozatosan haladva eg\u00e9szen a startvonalig.

      Ezekhez a l\u00e9p\u00e9sekhez m\u00e9g adunk megold\u00e1st (de sokkal t\u00f6bbet tanulsz bel\u0151le, ha magad pr\u00f3b\u00e1lkozol, csak ellen\u0151rz\u00e9sk\u00e9pen haszn\u00e1ld a megold\u00e1st):

      Megold\u00e1s

      A Game oszt\u00e1lyban a sz\u00e1lf\u00fcggv\u00e9ny:

      void BikeThreadFunction(object bikeAsObject)\n{\n    Bike bike = (Bike)bikeAsObject;\n    while (bike.Position <= StartLinePosition)\n    {\n        bike.Step();\n\n        Thread.Sleep(100);\n    }\n}\n

      Mint l\u00e1that\u00f3, sz\u00e1lf\u00fcggv\u00e9nyn\u00e9l nem a param\u00e9ter n\u00e9lk\u00fcli, hanem az object param\u00e9ter\u0171 lehet\u0151s\u00e9get v\u00e1lasztottuk, hiszen a sz\u00e1lf\u00fcggv\u00e9nynek \u00e1t kell adni az \u00e1ltala mozgatott biciklit.

      A sz\u00e1l ind\u00edt\u00e1sa a CreateBike f\u00fcggv\u00e9ny v\u00e9g\u00e9n:

      private void CreateBike()\n{\n    ...\n\n    var thread = new Thread(BikeThreadFunction);\n    thread.IsBackground = true; // Ne blokkolja a sz\u00e1l a processz megsz\u0171n\u00e9s\u00e9t\n    thread.Start(bike); // itt adjuk \u00e1t param\u00e9terben a sz\u00e1lf\u00fcggv\u00e9nynek a biciklit\n}\n

      BEADAND\u00d3

      Miel\u0151tt tov\u00e1bbmenn\u00e9l a k\u00f6vetkez\u0151 feladatra, egy k\u00e9perny\u0151ment\u00e9st kell k\u00e9sz\u00edtened.

      K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st Feladat1.png n\u00e9ven az al\u00e1bbiak szerint:

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-2-a-verseny-inditasa","title":"Feladat 2 \u2013 A verseny ind\u00edt\u00e1sa","text":"

      Val\u00f3s\u00edtsd meg a verseny ind\u00edt\u00e1s\u00e1t a rajtvonalr\u00f3l \u00e9s futtat\u00e1s\u00e1t mindaddig, am\u00edg a biciklik meg nem \u00e9rkeznek a dep\u00f3ba, a k\u00f6vetkez\u0151 ir\u00e1nyelveknek megfelel\u0151en:

      Tipp a megold\u00e1shoz

      Mivel a v\u00e1rakoz\u00e1st k\u00f6vet\u0151en a versenyz\u0151knek egyszerre kell indulniuk, a v\u00e1rakoz\u00e1s \u00e9s ind\u00edt\u00e1s megval\u00f3s\u00edt\u00e1s\u00e1ra egy ManualResetEvent objektumot c\u00e9lszer\u0171 haszn\u00e1lni.

      BEADAND\u00d3

      Miel\u0151tt tov\u00e1bbmenn\u00e9l a k\u00f6vetkez\u0151 feladatra, egy k\u00e9perny\u0151ment\u00e9st kell k\u00e9sz\u00edtened.

      K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st Feladat2.png n\u00e9ven az al\u00e1bbiak szerint:

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-3-a-versenyzok-inditasa-a-depobol","title":"Feladat 3 \u2013 A versenyz\u0151k ind\u00edt\u00e1sa a dep\u00f3b\u00f3l","text":"

      Val\u00f3s\u00edtsd meg a versenyz\u0151k ind\u00edt\u00e1s\u00e1t a dep\u00f3b\u00f3l \u00e9s futtat\u00e1s\u00e1t mindaddig, am\u00edg a biciklik meg nem \u00e9rkeznek a c\u00e9lba, a k\u00f6vetkez\u0151 ir\u00e1nyelveknek megfelel\u0151en:

      Tipp a megold\u00e1shoz

      A feladat megold\u00e1sa anal\u00f3g az el\u0151z\u0151\u00e9vel, \u00e1m ez\u00fattal a ManualResetEvent helyett egy m\u00e1s t\u00edpus\u00fa, de hasonl\u00f3 objektumot kell haszn\u00e1lni...

      BEADAND\u00d3

      Miel\u0151tt tov\u00e1bbmenn\u00e9l a k\u00f6vetkez\u0151 feladatra, egy k\u00e9perny\u0151ment\u00e9st kell k\u00e9sz\u00edtened.

      K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st Feladat3.png n\u00e9ven az al\u00e1bbiak szerint:

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-4-gyoztes-bicikli-megvalositasa","title":"Feladat 4 \u2013 Gy\u0151ztes bicikli megval\u00f3s\u00edt\u00e1sa","text":"

      Val\u00f3s\u00edtsd meg a gy\u0151ztes bicikli meghat\u00e1roz\u00e1s\u00e1nak \u00e9s megjelen\u00edt\u00e9s\u00e9nek logik\u00e1j\u00e1t, a k\u00f6vetkez\u0151 ir\u00e1nyelveknek megfelel\u0151en:

      A logika megval\u00f3s\u00edt\u00e1sa el\u0151tt egy kicsit finom\u00edtunk a megjelen\u00edt\u00e9sen, annak \u00e9rdek\u00e9ben, hogy a gy\u0151ztes bicikli megk\u00fcl\u00f6nb\u00f6ztethet\u0151 legyen a t\u00f6bbit\u0151l a fel\u00fcleten. Ehhez a MainWindow oszt\u00e1ly UpdateUI f\u00fcggv\u00e9ny\u00e9be tegy\u00fcnk be egy kis plusz logik\u00e1t: ha az adott bicikli gy\u0151ztes lett, akkor a megjelen\u00edt\u00e9s\u00e9t v\u00e1ltoztassuk \u00e1t egy serlegre. Ehhez a biciklihez tartoz\u00f3 TextBlock sz\u00f6veg\u00e9t kell \"%\"-ra v\u00e1ltoztatni:

      private void UpdateUI()\n{\n    for (int i = 0; i < game.Bikes.Count;i++)\n    {\n        ...\n\n        if (bike.IsWinner)\n            tbBike.Text = \"%\";\n    }\n}\n

      A logik\u00e1t ezt k\u00f6vet\u0151en \u00f6n\u00e1ll\u00f3an val\u00f3s\u00edtsd meg, az al\u00e1bbi ir\u00e1nyleveknek \u00e9s tippeknek megfelel\u0151en.

      Ir\u00e1nyelvek \u00e9s tippek a megold\u00e1shoz

      BEADAND\u00d3

      Miel\u0151tt tov\u00e1bbmenn\u00e9l a k\u00f6vetkez\u0151 feladatra, egy k\u00e9perny\u0151ment\u00e9st kell k\u00e9sz\u00edtened.

      K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st Feladat4.png n\u00e9ven az al\u00e1bbiak szerint:

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-5-kolcsonos-kizaras-valamint-volatile","title":"Feladat 5 \u2013 K\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1s, valamint volatile","text":"

      Az el\u0151z\u0151 feladatban l\u00e1ttuk, hogy a hasWinner lek\u00e9rdez\u00e9s\u00e9t \u00e9s be\u00e1ll\u00edt\u00e1s\u00e1t \"oszthatatlann\u00e1\", \"atomiv\u00e1\" kellett tegy\u00fck, vagyis ennek sor\u00e1n meg kellett val\u00f3s\u00edtsuk a k\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1st. K\u00e9rd\u00e9s, van-e esetleg m\u00e1r olyan m\u00e1s logika is az alkalmaz\u00e1sban, ahol ezt meg kellet volna tenni a konzisztencia garant\u00e1l\u00e1s\u00e1nak \u00e9rdek\u00e9ben. Ehhez azt kell megvizsg\u00e1ljuk, melyek azok a v\u00e1ltoz\u00f3k, melyeket t\u00f6bb sz\u00e1lb\u00f3l is \u00edrunk (vagy egyikb\u0151l \u00edrunk \u00e9s m\u00e1sikb\u00f3l olvasunk). A k\u00f6vetkez\u0151k \u00e9rintettek:

      Azt is mondhatn\u00e1nk, hogy a fenti h\u00e1rom v\u00e1ltoz\u00f3 tekintet\u00e9ben akkor minden rendben is van, de ez nincs \u00edgy. Amikor a v\u00e1ltoz\u00f3k \u00e9rt\u00e9k\u00e9t az egyik sz\u00e1l m\u00f3dos\u00edtja, el\u0151fordulhat, hogy a v\u00e1ltoz\u00f3k \u00e9rt\u00e9k\u00e9t a rendszer cache-eli (pl. regiszterben), \u00edgy a m\u00e1sik sz\u00e1l a v\u00e1ltoztat\u00e1s ut\u00e1n is a kor\u00e1bbi \u00e9rt\u00e9ket l\u00e1tja. Ennek megakad\u00e1lyoz\u00e1s\u00e1ra ezeket a v\u00e1ltoz\u00f3kat volatile-nak kell defini\u00e1lni a volatile kulcssz\u00f3val, mely a v\u00e1ltoz\u00f3 megv\u00e1ltoztat\u00e1sa ut\u00e1n garant\u00e1lja, hogy annak ki\u00edr\u00e1sa megt\u00f6rt\u00e9nik a mem\u00f3ri\u00e1ba, \u00e9s a m\u00e1sik sz\u00e1l friss \u00e9rt\u00e9ket olvas (a volatile m\u0171k\u00f6d\u00e9se enn\u00e9l valamivel \u00f6sszetettebb, el\u0151ad\u00e1son b\u0151vebben kifejt\u00e9sre ker\u00fcl). Fontos megjegyz\u00e9s: a volatile alkalmaz\u00e1s\u00e1ra nincs sz\u00fcks\u00e9g, ha az adott v\u00e1ltoz\u00f3t lock blokkb\u00f3l \u00edrjuk \u00e9s olvassuk, vagy az Interlocked oszt\u00e1ly seg\u00edts\u00e9g\u00e9vel m\u00f3dos\u00edtjuk. Amiatt csak a position \u00e9s az isWinner eset\u00e9ben vezess\u00fck be:

      class Bike\n{\n    private volatile int position = 65;\n    private volatile bool isWinner;\n
      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-5-lepesek-naplozasa-nem-szalbiztos-net-osztalyok","title":"Feladat 5 \u2013 L\u00e9p\u00e9sek napl\u00f3z\u00e1sa (nem sz\u00e1lbiztos .NET oszt\u00e1lyok)","text":"

      Val\u00f3s\u00edtsd meg a verseny sor\u00e1n a biciklik \u00e1ltal megtett minden egyes l\u00e9p\u00e9s napl\u00f3z\u00e1s\u00e1t a Game oszt\u00e1lyban egy (minden biciklire k\u00f6z\u00f6s) List<int> t\u00edpus\u00fa v\u00e1ltoz\u00f3ba. A napl\u00f3zott \u00e9rt\u00e9kekkel nem kell semmit csin\u00e1lni (pl. megjelen\u00edteni sem). A megold\u00e1s sor\u00e1n ki kell haszn\u00e1lni, hogy a Bike oszt\u00e1ly Step m\u0171velete visszaadja a megtett l\u00e9p\u00e9st egy int v\u00e1ltoz\u00f3 form\u00e1j\u00e1ban, ezt kell napl\u00f3zni (csak bele kell tenni a list\u00e1ba).

      Tipp a megold\u00e1shoz

      Mivel a List<T> oszt\u00e1ly nem sz\u00e1lbiztos (nem thread safe), \u00e9s t\u00f6bb sz\u00e1lb\u00f3l is \u00edrunk bele, meg kell val\u00f3s\u00edtani a hozz\u00e1f\u00e9r\u00e9s sor\u00e1n a k\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1st a lock utas\u00edt\u00e1s seg\u00edts\u00e9g\u00e9vel.

      System.Collections.Concurrent n\u00e9vt\u00e9r gy\u0171jtem\u00e9nyoszt\u00e1lyai

      Ha a List<T> helyett egy a c\u00e9lnak megfelel\u0151, System.Collections.Concurrent n\u00e9vt\u00e9rbeli oszt\u00e1ly objektum\u00e1ba napl\u00f3zn\u00e1nk (pl. ConcurrentQueue), akkor nem lenne sz\u00fcks\u00e9g a k\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1s megval\u00f3s\u00edt\u00e1s\u00e1ra, mert ebben a n\u00e9vt\u00e9rben sz\u00e1lbiztos (thread safe) gy\u0171jtem\u00e9nyoszt\u00e1lyok tal\u00e1lhat\u00f3k.

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-6-felulet-frissitese-minden-valtozas-eseten-felhasznaloi-feluletelemek-elerese-munkaszalakbol","title":"Feladat 6 \u2013 Fel\u00fclet friss\u00edt\u00e9se minden v\u00e1ltoz\u00e1s eset\u00e9n (felhaszn\u00e1l\u00f3i fel\u00fcletelemek el\u00e9r\u00e9se munkasz\u00e1lakb\u00f3l)","text":"

      Aktu\u00e1lis megold\u00e1sunkban a fel\u00fclet friss\u00edt\u00e9s\u00e9t periodikusan, adott id\u0151k\u00f6z\u00f6nk\u00e9nt val\u00f3s\u00edtjuk meg egy id\u0151z\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel. Ezt a megold\u00e1st most lecser\u00e9lj\u00fck. Alak\u00edtsd \u00e1t a megold\u00e1st \u00fagy, hogy a fel\u00fclet friss\u00edt\u00e9se minden esetben azonnal megt\u00f6rt\u00e9njen, amikor a Game \u00e1llapota megv\u00e1ltozik (az id\u0151z\u00edtett friss\u00edt\u00e9st pedig m\u00e1r ne haszn\u00e1ld).

      A k\u00f6vetkez\u0151 fejezetben a lehets\u00e9ges megold\u00e1sok r\u00f6viden \u00e1ttekint\u00e9sre ker\u00fclnek, \u00e9s v\u00e1lasztunk is egyet k\u00f6z\u00fcl\u00fck, de el\u0151bb pr\u00f3b\u00e1ld magadt\u00f3l \u00e1tgondolni, milyen megold\u00e1st c\u00e9lszer\u0171 ehhez v\u00e1lasztani. Kulcsfontoss\u00e1g\u00fa, hogy csak olyan megold\u00e1s fogadhat\u00f3 el, mely nem vezet be az alkalmaz\u00e1slogik\u00e1ban (Game oszt\u00e1ly) f\u00fcgg\u0151s\u00e9get a fel\u00fcltett\u0151l. Eml\u00e9kezz\u00fcnk vissza, az alapelv\u00fcnk az volt, hogy az alkalmaz\u00e1slogika nem f\u00fcgghet semmilyen szinten a fel\u00fclet logik\u00e1t\u00f3l!

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#a-felulet-ertesitesenek-megvalositasa","title":"A fel\u00fclet \u00e9rtes\u00edt\u00e9s\u00e9nek megval\u00f3s\u00edt\u00e1sa","text":"

      Alternat\u00edv\u00e1k:

      1. Alkalmazhatjuk az Observer tervez\u00e9si mint\u00e1t. Err\u0151l a f\u00e9l\u00e9v sor\u00e1n k\u00e9s\u0151bb fogunk tanulni, b\u00e1r \u00e9rdemes megjegyezni, hogy a C# esem\u00e9nyek is az Observer minta alapkoncepci\u00f3ira \u00e9p\u00fclnek.
      2. K\u00e9zenfekv\u0151 megold\u00e1s lehet egy C# esem\u00e9ny bevezet\u00e9se (pl. BikeStateChanged n\u00e9ven), melyet a Game oszt\u00e1ly akkor s\u00fct el, amikor egy bicikli \u00e1llapota megv\u00e1ltozott, param\u00e9terk\u00e9nt \u00e1tadva a bicikli objektumot. Ez egy kerek, \u00e1ltal\u00e1nos megold\u00e1s lenne: b\u00e1rmikor, b\u00e1rmely oszt\u00e1ly feliratkozhatna az esem\u00e9nyre. Ehhez - ha k\u00f6vetni szeretn\u00e9nk a Microsoft aj\u00e1nl\u00e1sokat - be kellene vezetni egy EventArgs lesz\u00e1rmazott oszt\u00e1lyt (esem\u00e9ny param\u00e9ter), \u00e9s be kellene vezetni egy \u00faj delegate t\u00edpust (vagy haszn\u00e1lhatn\u00e1nk a be\u00e9p\u00edtett EventHandler<TEventArgs> generikus delegate t\u00edpust).
      3. Az el\u0151z\u0151 pontban eml\u00edtett C# esem\u00e9ny alap\u00fa megold\u00e1s teljesen \"korrekt\" lenne, ugyanakkor nek\u00fcnk nem felt\u00e9tlen c\u00e9lunk, hogy b\u00e1rmikor b\u00e1rmely oszt\u00e1ly feliratkozhasson az \u00e1llapotv\u00e1ltoz\u00e1s esem\u00e9nyre. Emiatt \u00e1tgondolhatunk egy \"c\u00e9lir\u00e1nyosabb\" megold\u00e1st (\u00e9s ezt is fogjuk alkalmazni). Ez, b\u00e1r delegate-et haszn\u00e1l, nem vezet be event esem\u00e9nyt, \u00e9s alapvet\u0151en csak egyetlen objektum sz\u00e1m\u00e1ra biztos\u00edt \u00e9rtes\u00edt\u00e9st/visszah\u00edv\u00e1st (a MainWindow-nak, hiszen \u0151 kell friss\u00edtse a fel\u00fclet\u00e9t, amikor v\u00e1ltozik egy bicikli \u00e1llapota). Ezen megk\u00f6zel\u00edt\u00e9s elemei a k\u00f6vetkez\u0151k:

        • Game oszt\u00e1ly, mint \"\u00e9rtes\u00edt\u0151\":
          • Azt a f\u00fcggv\u00e9nyt (delegate objektumot), melyet Game oszt\u00e1ly a biciklik \u00e1llapot\u00e1nak v\u00e1ltoz\u00e1sakor megh\u00edv (\u00e9rtes\u00edt\u00e9s/visszah\u00edv\u00e1s), a PrepareRace m\u0171velet param\u00e9terek\u00e9nt kapja meg a Game oszt\u00e1ly, melyet egy tagv\u00e1ltoz\u00f3ban el is t\u00e1rol.
          • Ennek a param\u00e9ternek \u00e9s tagv\u00e1ltoz\u00f3nak a t\u00edpusa legyen Action<Bike> (az Action \u00e9s Action<T> t\u00edpusokr\u00f3l m\u00e1r kor\u00e1bban tanultunk).
          • Amikor megv\u00e1ltozik egy bicikli \u00e1llapota (helye vagy \"nyertes\" \u00e1llapota a sz\u00e1lf\u00fcggv\u00e9nyben), akkor a Game oszt\u00e1ly h\u00edvja meg ezt a tagv\u00e1ltoz\u00f3ban t\u00e1rolt f\u00fcggv\u00e9nyt (de csak ha nem null, vagyis ez a f\u00fcggv\u00e9ny m\u00e1r be lett \u00e1ll\u00edtva, ill. a ?.Invoke is haszn\u00e1lhat\u00f3), param\u00e9terk\u00e9nt \u00e1tadva neki a megv\u00e1ltozott bicikli objektumot. Ez\u00e1ltal \u00e9rtes\u00edti az el\u0151fizet\u0151t.
        • MainWindow, mint \"el\u0151fizet\u0151\":
          • A MainWindow oszt\u00e1lyban be kell vezetni egy UpdateBikeUI(Bike bike) f\u00fcggv\u00e9nyt, \u00e9s a Game.PrepareRace h\u00edv\u00e1sakor ezt kell \u00e1tadni param\u00e9terk\u00e9nt (delegate objektumk\u00e9nt). Ebben az UpdateBikeUI f\u00fcggv\u00e9nyben kell gondoskodni arr\u00f3l, hogy a param\u00e9terk\u00e9nt kapott bicikli objektumhoz tartoz\u00f3 fel\u00fcletelem (TextBlock) friss\u00fclj\u00f6n.
          • Az el\u0151z\u0151 pontban v\u00e1lik egy\u00e9rtelm\u0171v\u00e9, mi\u00e9rt Action<Bike> t\u00edpus\u00fa delegate-et haszn\u00e1ltunk, \u00e9s mi\u00e9rt nem pl. Action-t: a Game a \u00e9rtes\u00edt\u00e9s/visszah\u00edv\u00e1s sor\u00e1n \u00edgy meg tudja adna, mely bicikli v\u00e1ltozott, \u00e9s a visszah\u00edvott/beregisztr\u00e1lt f\u00fcggv\u00e9ny (eset\u00fcnkben MainWindow.UpdateBikeUI) \u00edgy megkapja ezt param\u00e9terben, \u00e9s \u00edgy tudja a megjelen\u00e9s\u00e9t friss\u00edteni (kapott bicikli \u00e1llapota alapj\u00e1n).
        • Az id\u0151z\u00edt\u0151 ind\u00edt\u00e1s\u00e1t (MainWindow konstruktorban timer.Start() h\u00edv\u00e1s) kommentezd ki (hiszen a fel\u00fclet friss\u00edt\u00e9s\u00e9t m\u00e1r a fenti Action<Bike>) alap\u00fa \u00e9rtes\u00edt\u00e9s/visszah\u00edv\u00e1s seg\u00edts\u00e9g\u00e9vel oldjuk meg.

      Val\u00f3s\u00edtsd meg a fenti 3. pontban v\u00e1zolt \u00e9rtes\u00edt\u00e9st! A MainWindow.UpdateBikeUI implement\u00e1ci\u00f3j\u00e1t megadjuk seg\u00edts\u00e9gk\u00e9ppen (a l\u00e9nyege az, hogy a param\u00e9terben kapott Bike alapj\u00e1n friss\u00edti a biciklit megjelen\u00edt\u0151 TextBlock-ot):

      private void UpdateBikeUI(Bike bike)\n{\n    // El\u0151fordulhat, hogy az UpdateBikeUI olyan kor\u00e1n h\u00edv\u00f3dik, hogy a\n    // bikeTextBlocks m\u00e9g nincs felt\u00f6ltve, ilyenkor m\u00e9g nem tudjuk friss\u00edteni\n    // a fel\u00fcletet, t\u00e9rj\u00fcnk vissza.\n    if (bikeTextBlocks.Count != game.Bikes.Count)\n        return;\n\n    int marginAdjustmentForWheel = 8;\n\n    // Biciklihez tartoz\u00f3 TextBlock kikeres\u00e9se (azonos t\u00f6mbindex alapj\u00e1n).\n    var tbBike = bikeTextBlocks[game.Bikes.IndexOf(bike)];\n\n    // Akkor m\u00e9g ne \u00e1ll\u00edtsuk a bicikli poz\u00edci\u00f3j\u00e1t, amikor a m\u00e9rete a layout sor\u00e1n nem\n    // ker\u00fclt meghat\u00e1roz\u00e1sra (k\u00fcl\u00f6nben ugr\u00e1lna a bicikli, hiszen al\u00e1bb, a marg\u00f3 be\u00e1ll\u00edt\u00e1sakor\n    // \"\u00e9rv\u00e9nytelen\" 0 sz\u00e9less\u00e9g\u00e9rt\u00e9kkel sz\u00e1moln\u00e1nk.\n    if (tbBike.ActualWidth == 0)\n        return;\n\n    // Az ablak 0,0 pontja az orig\u00f3, ehhez k\u00e9pest n\u00e9zz\u00fck a start/dep\u00f3/finish vonalat.\n    // A gomb jobb sz\u00e9l\u00e9n van a ker\u00e9k, de ezt a gomb bal oldal\u00e1ra kell mozgatni: ActualWidth-et ki kell vonni.\n    tbBike.Margin = new Thickness(bike.Position - tbBike.ActualWidth + marginAdjustmentForWheel, 0, 0, 0);\n\n    if (bike.IsWinner)\n        tbBike.Text = \"%\"; // display a cup\n}\n

      Fontos

      A fenti l\u00e9p\u00e9sek/elvek megfelel\u0151 k\u00f6vet\u00e9se eset\u00e9n is fenn\u00e1ll, hogy megold\u00e1s m\u00e9g nem m\u0171k\u00f6d\u0151k\u00e9pes. Ha elind\u00edtjuk a versenyt, az al\u00e1bbi kiv\u00e9tel dob\u00f3dik az UpdateBikeUI f\u00fcggv\u00e9nyben a biciklihez tartoz\u00f3 TextBlock hozz\u00e1f\u00e9r\u00e9s sor\u00e1n: System.Runtime.InteropServices.COMException: 'The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD))

      Mi ennek a hib\u00e1nak az oka? Miel\u0151tt az al\u00e1bbi eml\u00e9keztet\u0151t kinyitod, pr\u00f3b\u00e1lj magadt\u00f3l r\u00e1j\u00f6nni az el\u0151ad\u00e1son/laboron tanultak alapj\u00e1n.

      Eml\u00e9keztet\u0151

      Egy WinUI fel\u00fcletelemhez/vez\u00e9rl\u0151h\u00f6z csak abb\u00f3l a sz\u00e1lb\u00f3l lehet hozz\u00e1f\u00e9rni, mely az adott fel\u00fcletelemet l\u00e9trehozta, ugyanis ezek a fel\u00fcletelemek nem sz\u00e1lbiztosak, \u00e9s kiv\u00e9tel dob\u00e1s\u00e1val jelzik, ha m\u00e9gis \u201erosszul\u201d pr\u00f3b\u00e1ljuk \u0151ket haszn\u00e1lni.

      A megold\u00e1st a k\u00f6vetkez\u0151 r\u00e9szfeladatban dolgozzuk ki.

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#a-dispatecherqueue-alkalmazasa","title":"A DispatecherQueue alkalmaz\u00e1sa","text":"

      Eset\u00fcnkben a konkr\u00e9t probl\u00e9m\u00e1t az okozza, hogy amikor a Game \u00e1llapota megv\u00e1ltozik, akkor Game oszt\u00e1lyban a v\u00e1ltoz\u00e1s\u00e9rtes\u00edt\u0151 delegate h\u00edv\u00e1sa a biciklikhez tartoz\u00f3 munkasz\u00e1lakon t\u00f6rt\u00e9nik, \u00edgy a beregisztr\u00e1lt MainWindow.UpdateBikeUI kezel\u0151f\u00fcggv\u00e9ny is ezekr\u0151l a sz\u00e1lakr\u00f3l h\u00edv\u00f3dik. Az UpdateBikeUI f\u00fcggv\u00e9nyben hozz\u00e1f\u00e9r\u00fcnk a fel\u00fcletelemekhez (biciklihez tartoz\u00f3 TextBlock- hoz). De ezeket a fel\u00fcletelemeket a f\u0151sz\u00e1lb\u00f3l hoztuk l\u00e9tre: \u00edgy csak a f\u0151 sz\u00e1lb\u00f3l szabad(na) hozz\u00e1juk f\u00e9rni.

      A probl\u00e9m\u00e1ra a DispatcherQueue alkalmaz\u00e1sa jelent megold\u00e1st, mellyel a munkasz\u00e1lakb\u00f3l a h\u00edv\u00e1st \"\u00e1t tudjuk j\u00e1tszani\" a f\u0151sz\u00e1lba, melyb\u0151l m\u00e1r hozz\u00e1 tudunk f\u00e9rni a vez\u00e9rl\u0151kh\u00f6z. A DispacherQueue alkalmaz\u00e1sa el\u0151ad\u00e1son \u00e9s a kapcsol\u00f3d\u00f3 laboron is r\u00e9szletesen ismertet\u00e9sre ker\u00fclt.

      Feladat: m\u00f3dos\u00edtsd \u00fagy a MainWindow.UpdateBikeUI f\u00fcggv\u00e9nyt, hogy a DispacherQueue alkalmaz\u00e1s\u00e1val a megfelel\u0151 sz\u00e1lb\u00f3l t\u00f6rt\u00e9njen a fel\u00fcletelemekhez t\u00f6rt\u00e9n\u0151 hozz\u00e1f\u00e9r\u00e9s (\u00e9s \u00edgy a mostani kiv\u00e9telt el tudd ker\u00fclni).

      BEADAND\u00d3

      Miel\u0151tt tov\u00e1bbmenn\u00e9l a k\u00f6vetkez\u0151 feladatra, egy k\u00e9perny\u0151ment\u00e9st kell k\u00e9sz\u00edtened.

      K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st Feladat6.png n\u00e9ven az al\u00e1bbiak szerint:

      Hasonl\u00f3 j\u00e1t\u00e9k megval\u00f3s\u00edt\u00e1sa a gyakorlatban

      L\u00e9nyeges, hogy egy hasonl\u00f3 \"j\u00e1t\u00e9k\" megval\u00f3s\u00edt\u00e1s\u00e1ra nem szoktunk sz\u00e1lakat ind\u00edtani: a biciklik l\u00e9ptet\u00e9s\u00e9re egy timer sokkal praktikusabb lenne, mert az eg\u00e9sz j\u00e1t\u00e9k egysz\u00e1l\u00fa maradhatna, \u00e9s elker\u00fclhetn\u00e9nk sz\u00e1mos, a t\u00f6bbsz\u00e1l\u00fas\u00e1gb\u00f3l ad\u00f3d\u00f3 neh\u00e9zs\u00e9ge (jelen feladat keret\u00e9ben a c\u00e9lunk \u00e9rtelemszer\u0171en pont a t\u00f6bbsz\u00e1l\u00fas\u00e1g t\u00e9mak\u00f6r\u00e9nek gyakorl\u00e1sa volt).

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#opcionalis-feladat-2-imsc-pontert","title":"Opcion\u00e1lis feladat \u2013 2 IMSc pont\u00e9rt","text":""},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat","title":"Feladat","text":"

      Tedd lehet\u0151v\u00e9 a biciklik gombkattint\u00e1sra t\u00f6rt\u00e9n\u0151 meg\u00e1ll\u00edt\u00e1s\u00e1t:

      "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#megoldas","title":"Megold\u00e1s","text":"

      A k\u00f6vetkez\u0151kben megadjuk a feladat megold\u00e1s\u00e1nak n\u00e9h\u00e1ny fontos elem\u00e9t:

      Megjegyz\u00e9s: sz\u00e1lak kil\u00e9ptet\u00e9s\u00e9re alternat\u00edv megold\u00e1s lett volna a bool \u00e9s ManualResetEvent bevezet\u00e9se helyett a sz\u00e1lakra Interrupt m\u0171velet h\u00edv\u00e1sa, \u00e9s a sz\u00e1lf\u00fcggv\u00e9nyekben az ennek hat\u00e1s\u00e1ra kiv\u00e1lt\u00f3d\u00f3 ThreadInterruptedException elkap\u00e1sa. Ez a t\u00e9mak\u00f6r el\u0151ad\u00e1son ker\u00fclt ismertet\u00e9sre.

      BEADAND\u00d3

      K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st Feladat_IMSc.png n\u00e9ven az al\u00e1bbiak szerint:

      "},{"location":"hazi/5-mvvm/","title":"5. HF - Az MVVM minta \u00e9s az MVVM Toolkit alkalmaz\u00e1sa","text":""},{"location":"hazi/5-mvvm/#bevezetes","title":"Bevezet\u00e9s","text":"

      A h\u00e1zi feladatban a 3. XAML laboron megval\u00f3s\u00edtott szem\u00e9ly regisztr\u00e1ci\u00f3s alkalmaz\u00e1st alak\u00edtjuk \u00e1t olyan m\u00f3don, hogy az MVVM mint\u00e1ra \u00e9p\u00fclj\u00f6n, valamint megismerked\u00fcnk az MVVM Toolkit alkalmaz\u00e1s\u00e1val.

      Az \u00f6n\u00e1ll\u00f3 feladat a WinUI el\u0151ad\u00e1ssorozat v\u00e9g\u00e9n elhangzott MVVM t\u00e9mak\u00f6rre \u00e9p\u00edt. Megjegyz\u00e9s: az 5. labor \u2013 MVVM labor nagyon szerte\u00e1gaz\u00f3, \u00e9s egy komplexebb alkalmaz\u00e1s kontextus\u00e1ban mutat p\u00e9ld\u00e1t az MVVM minta alkalmaz\u00e1s\u00e1ra, sok m\u00e1s t\u00e9mak\u00f6r mellett. Jelen h\u00e1zi feladat sokkal f\u00f3kusz\u00e1ltabb, kisebb l\u00e9p\u00e9sekben \u00e9p\u00edtkezik: est\u00fcnkben esetben ink\u00e1bb a jelen h\u00e1zi feladat megold\u00e1sa seg\u00edti az 5. labor \u2013 MVVM kapcsol\u00f3d\u00f3 r\u00e9szeinek k\u00f6nnyebb meg\u00e9rt\u00e9s\u00e9t.

      Az kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sanyag feldolgoz\u00e1s\u00e1val, jelen \u00f6n\u00e1ll\u00f3 gyakorlat feladatai a feladatle\u00edr\u00e1st k\u00f6vet\u0151 r\u00f6videbb ir\u00e1nymutat\u00e1s seg\u00edts\u00e9g\u00e9vel (n\u00e9ha alap\u00e9rtelmezetten \u00f6sszecsukva) \u00f6n\u00e1ll\u00f3an elv\u00e9gezhet\u0151k.

      Az \u00f6n\u00e1ll\u00f3 gyakorlat c\u00e9lja:

      A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s, megegyezik a 3. h\u00e1zi feladat\u00e9val (XAML alapok).

      "},{"location":"hazi/5-mvvm/#a-beadas-menete","title":"A bead\u00e1s menete","text":""},{"location":"hazi/5-mvvm/#kikotesek","title":"Kik\u00f6t\u00e9sek","text":"

      MVVM minta k\u00f6telez\u0151 alkalmaz\u00e1sa! Jelen h\u00e1zi feladatban az MVVM mint\u00e1t gyakoroljuk, \u00edgy a feladatok megold\u00e1s\u00e1ban k\u00f6telez\u0151 az MVVM minta alkalmaz\u00e1sa. Az ett\u0151l val\u00f3 elt\u00e9r\u00e9s a feladatok \u00e9rt\u00e9kel\u00e9s\u00e9nek elutas\u00edt\u00e1s\u00e1t vonja maga ut\u00e1n.

      "},{"location":"hazi/5-mvvm/#feladat-0-kiindulo-allapot-attekintese","title":"Feladat 0 - Kiindul\u00f3 \u00e1llapot \u00e1ttekint\u00e9se","text":"

      A kiindul\u00f3 \u00e1llapot alapvet\u0151en megegyezik a 3. A felhaszn\u00e1l\u00f3i fel\u00fclet kialak\u00edt\u00e1sa v\u00e9g\u00e1llapot\u00e1val. Vagyis egy olyan alkalmaz\u00e1s, melyben egy list\u00e1ban szem\u00e9lyek adatait lehet r\u00f6gz\u00edteni. A labor v\u00e9g\u00e1llapot\u00e1hoz k\u00e9pest egy kisebb v\u00e1ltoz\u00e1st tartalmaz. Laboron a fel\u00fclet teljes le\u00edr\u00e1s\u00e1t a MainWindow.xaml (\u00e9s a kapcsol\u00f3d\u00f3 code-behind f\u00e1jl) tartalmazta. Jelen kiindul\u00f3 megold\u00e1sban az a k\u00fcl\u00f6nbs\u00e9g, hogy ez \u00e1t lett mozgatva a Views mapp\u00e1ban lev\u0151 PersonListPage.xaml (\u00e9s code behind) f\u00e1jlba. A PersonListPage nem egy Window, hanem egy Page lesz\u00e1rmazott oszt\u00e1ly (ellen\u0151rizz\u00fck ezt a code behind f\u00e1jlban). De semmi m\u00e1s v\u00e1ltoz\u00e1s nincs! Mint a neve is utal r\u00e1, a Page egy \"oldalt\" reprezent\u00e1l az alkalmaz\u00e1sban: \u00f6nmag\u00e1ban nem tud megjelenni, hanem pl. egy ablakon kell elhelyezni. El\u0151nye, hogy az ablakon - megfelel\u0151 navig\u00e1ci\u00f3 kialak\u00edt\u00e1s\u00e1val - lehet\u0151s\u00e9g van oldalak (k\u00fcl\u00f6nb\u00f6z\u0151 Page lesz\u00e1rmazottak) k\u00f6z\u00f6tt navig\u00e1lni. Ezt mi nem fogjuk kihaszn\u00e1lni, egyetlen oldalunk lesz csak. Az oldal bevezet\u00e9s\u00e9vel a c\u00e9lunk mind\u00f6ssze az volt, hogy szeml\u00e9ltess\u00fck: az MVVM architekt\u00far\u00e1ban a n\u00e9zeteket nem csak Window (teljes ablak), hanem pl. Page objektumokkal is meg lehet val\u00f3s\u00edtani.

      Mivel mindent \u00e1tmozgattunk a MainWindow-b\u00f3l a PersonListPage-be, a MainWindow.xaml-ban m\u00e1r semmi m\u00e1s nincs, mint egy ilyen PersonListPage objektum p\u00e9ld\u00e1nyos\u00edt\u00e1sa:

      <views:PersonListPage/>\n

      Ellen\u0151rizd a k\u00f3dban, hogy val\u00f3ban ez a helyzet!

      "},{"location":"hazi/5-mvvm/#foablak-fejlece","title":"F\u0151ablak fejl\u00e9ce","text":"

      A f\u0151ablak fejl\u00e9ce az \"MVVM\" sz\u00f6veg legyen, hozz\u00e1f\u0171zve a saj\u00e1t Neptun k\u00f3dod: (pl. \"ABCDEF\" Neptun k\u00f3d eset\u00e9n \"MVVM - ABCDEF\"), fontos, hogy ez legyen a sz\u00f6veg! Ehhez a f\u0151ablakunk Title tulajdons\u00e1g\u00e1t \u00e1ll\u00edtsuk be erre a sz\u00f6vegre a MainWindow.xaml f\u00e1jlban.

      "},{"location":"hazi/5-mvvm/#feladat-1-mvvm-toolkit-alkalmazasa","title":"Feladat 1 - MVVM Toolkit alkalmaz\u00e1sa","text":"

      A megl\u00e9v\u0151 alkalmaz\u00e1sban a Models mapp\u00e1ban lev\u0151 Person oszt\u00e1ly m\u00e1r implement\u00e1lja az INotifyPropertyChanged (becenev\u00e9n INPC) interf\u00e9szt (\u00edgy rendelkezik egy PropertyChanged esem\u00e9nnyel), valamint a Name \u00e9s az Age setter\u00e9ben jelzi is a tulajdons\u00e1g v\u00e1ltoz\u00e1s\u00e1t a PropertyChanged esem\u00e9ny els\u00fct\u00e9s\u00e9vel (n\u00e9zd meg ezt alaposan a Person.cs f\u00e1jlban).

      Bemeleg\u00edt\u00e9sk\u00e9ppen/ism\u00e9tl\u00e9sk\u00e9ppen - a k\u00f3dot (PersonListPage.xaml \u00e9s PersonListPage.xaml.cs) alaposan \u00e1tn\u00e9zve \u00e9s az alkalmaz\u00e1st futtatva - fogalmazd meg magadban, mi\u00e9rt is volt erre az alkalmaz\u00e1sban sz\u00fcks\u00e9g!

      A v\u00e1lasz (ism\u00e9tl\u00e9s)

      Az alkalmaz\u00e1sban a PersonListPage.xaml-ben a TextBox-ok Text tulajdons\u00e1ga (ez a c\u00e9l tulajdons\u00e1g) hozz\u00e1 vannak k\u00f6tve a code behindban lev\u0151 Person t\u00edpus\u00fa NewPerson tag Age \u00e9s Name tulajdons\u00e1gaihoz (ezek a forr\u00e1sok a k\u00e9t adatk\u00f6t\u00e9sben). N\u00e9zz\u00fck meg a k\u00f3dban, hogy a NewPerson.Name \u00e9s NewPerson.Age forr\u00e1s tulajdons\u00e1gokat v\u00e1ltoztatjuk is a k\u00f3dban: a vez\u00e9rl\u0151 csak akkor tud ezekr\u0151l \u00e9rtes\u00fclni (\u00e9s \u00edgy szinkronban maradni a forr\u00e1ssal), ha ezekr\u0151l a Name \u00e9s Age v\u00e1ltoz\u00e1sokr\u00f3l \u00e9rtes\u00edt\u00e9st kap. Emiatt az Age \u00e9s Name tulajdons\u00e1gokat tartalmaz\u00f3 oszt\u00e1lynak, vagyis a Person-nek meg kell val\u00f3s\u00edtania az INotifyPropertyChanged interf\u00e9szt, \u00e9s a tulajdons\u00e1gok v\u00e1ltoz\u00e1sakor el kell s\u00fctnie a PropertyChanged esem\u00e9nyt megfelel\u0151en param\u00e9terezve.

      Az alkalmaz\u00e1st futtatva ellen\u0151rizd, hogy a '+' \u00e9s '-' gombok hat\u00e1s\u00e1ra eszk\u00f6z\u00f6lt NewPerson.Age v\u00e1ltoz\u00e1sok val\u00f3ban \u00e9rv\u00e9nyre jutnak az \u00e9letkort megjelen\u00edt\u0151 TextBox-ban.

      A Person oszt\u00e1lyban l\u00e1tszik, hogy az INotifyPropertyChanged megval\u00f3s\u00edt\u00e1sa \u00e9s a kapcsol\u00f3d\u00f3 k\u00f3d igencsak terjeng\u0151s. N\u00e9zd meg az el\u0151ad\u00e1sanyagban, milyen alternat\u00edv\u00e1k vannak az interf\u00e9sz megval\u00f3s\u00edt\u00e1s\u00e1ra (az \"INPC p\u00e9lda 1\" c\u00edm\u0171 di\u00e1t\u00f3l kezd\u0151d\u0151en kb. n\u00e9gy dia a n\u00e9gy lehet\u0151s\u00e9g illusztr\u00e1l\u00e1s\u00e1ra)! A legt\u00f6m\u00f6rebb legold\u00e1st az MVVM Toolkit alkalmaz\u00e1sa jelenti. A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben jelen terjeng\u0151sebb \"manu\u00e1lis\" INPC megval\u00f3s\u00edt\u00e1st \u00e1talak\u00edtjuk MVVM toolkit alap\u00fara.

      "},{"location":"hazi/5-mvvm/#feladat-1a-mvvm-toolkit-nuget-referencia-felvetele","title":"Feladat 1/a - MVVM Toolkit NuGet referencia felv\u00e9tele","text":"

      Els\u0151 l\u00e9p\u00e9sben NuGet referenci\u00e1t kell tenni az MVVM Toolkitre annak \u00e9rdek\u00e9ben, hogy haszn\u00e1lni lehessen a projektben.

      Feladat: Vegy\u00e9l fel egy NuGet referenci\u00e1t a projektben a \"CommunityToolkit.Mvvm\" NuGet csomagra. Ez a Visual Studio oldal \u00edrja le, hogyan lehet egy NuGet referenci\u00e1t a projektbe felvenni NuGet Package Manager. Az el\u0151z\u0151 link az oldalon bel\u00fcl a \"NuGet Package Manager\" fejezetre ugrik, az itt megadott n\u00e9gy l\u00e9p\u00e9st kell k\u00f6vetni (term\u00e9szetesen azzal a k\u00fcl\u00f6nbs\u00e9ggel, hogy nem a \"Newtonsoft.Json\" hanem a \"CommunityToolkit.Mvvm\" csomagra kell a referenci\u00e1t felvenni).

      Most, hogy a projekt\u00fcnkbe felvett\u00fck ezt a NuGet referenci\u00e1t, a k\u00f6vetkez\u0151 build sor\u00e1n (mivel annak r\u00e9szek\u00e9nt lefut egy NuGet restore l\u00e9p\u00e9s!) let\u00f6lt\u0151dik a NuGet csomag, kicsomagol\u00f3dnak a benne lev\u0151 DLL-ek a kimeneti mapp\u00e1ba, \u00edgy azok m\u00e1r szerves r\u00e9sz\u00e9t k\u00e9pezik az alkalmaz\u00e1snak (egy NuGet csomag tulajdonk\u00e9ppen egy zip \u00e1llom\u00e1ny). Fontos megeml\u00edteni, hogy Git-be sem a NuGet zip, sem a benne lev\u0151 dll-ek nem ker\u00fclnek fel, a solution gy\u00f6ker\u00e9ben lev\u0151 .gitignore f\u00e1jl ezeket kisz\u0171ri. Pont ez a NuGet koncepci\u00f3 l\u00e9nyege: a repository kicsi maradhat, mert a projektf\u00e1jl csak hivatkoz\u00e1sokat tartalmazza a NuGet csomagokra, \u00e9s amikor valaki egy frissen clone-ozott solution-t buildel, csak ekkor t\u00f6lt\u0151dnek le az online NuGet forr\u00e1sokb\u00f3l a hivatkozott NuGet csomagok.

      A fenti NuGet-re vonatkoz\u00f3 koncepci\u00f3k ismerete fontos, a tananyag fontos r\u00e9sz\u00e9t k\u00e9pezik!

      Egy NuGet referencia tulajdonk\u00e9ppen csak egy sor a .csproj projektle\u00edr\u00f3 f\u00e1jlban. A Solution Explorerben a \"HelloXaml\" projekt csom\u00f3pontra kattintva nyisd meg a .csproj projektf\u00e1jlt, \u00e9s ellen\u0151rizd, benne van ez a sor (a verzi\u00f3 lehet m\u00e1s lesz):

          <PackageReference Include=\"CommunityToolkit.Mvvm\" Version=\"8.2.2\" />\n

      A csproj f\u00e1jl megnyit\u00e1sa n\u00e9lk\u00fcl is ellen\u0151rizd a NuGet referenci\u00e1nkat: Solution Explorerben nyisd le a \"HelloXaml\"/\"Dependencies\"/\"Packages\" csom\u00f3pontot: ha minden rendben van, alatta l\u00e1that\u00f3 egy \"CommunityToolkit.Mvvm (verzi\u00f3)\" csom\u00f3pont.

      "},{"location":"hazi/5-mvvm/#feladat-1b-inpc-megvalositas-mvvm-toolkit-alapokon","title":"Feladat 1/b - INPC megval\u00f3s\u00edt\u00e1s MVVM Toolkit alapokon","text":"

      Most m\u00e1r tudjuk haszn\u00e1lni az MVVM Toolkit NuGet package-ben lev\u0151 oszt\u00e1lyokat, interf\u00e9szeket, attrib\u00fatumokat stb., \u00edgy \u00e1t tudunk t\u00e9rni az MVVM Toolkit alap\u00fa INPC megval\u00f3s\u00edt\u00e1sra.

      Meg is vagyunk.

      A megold\u00e1s ellen\u0151rz\u00e9se
      public partial class Person : ObservableObject\n{\n    [ObservableProperty]\n    private string name;\n\n    [ObservableProperty]\n    private int age;\n}\n

      Ez a k\u00f3d, egy ford\u00edt\u00e1st k\u00f6vet\u0151en, alapjaiban ugyanazt a megold\u00e1st eredm\u00e9nyezi, mint a kor\u00e1bbi, sokkal terjeng\u0151sebb, imm\u00e1r kikommentezett forma. Vagyis (m\u00e9g ha nem is l\u00e1tjuk egyel\u0151re) sz\u00fcletik Name \u00e9s Age tulajdons\u00e1g, megfelel\u0151 PropertyChanged esem\u00e9ny els\u00fct\u00e9sekkel. Hogyan lehets\u00e9ges ez?

      A k\u00f3dgener\u00e1tor szok\u00e1sosan az oszt\u00e1lyunk m\u00e1sik \"partial\" fel\u00e9be dolgozik, annak \u00e9rdek\u00e9ben, hogy ne keveredjen az \u00e1ltalunk \u00edrt \u00e9s a gener\u00e1lt k\u00f3d! A partial classokat leggyakrabban a k\u00e9zzel \u00edrt \u00e9s a gener\u00e1lt k\u00f3d \"k\u00fcl\u00f6nv\u00e1laszt\u00e1s\u00e1ra\" haszn\u00e1ljuk.

      Mivel sokkal kevesebb k\u00f3dot kell \u00edrni, a gyakorlatban az MVVM Toolkit alap\u00fa megold\u00e1st szoktuk haszn\u00e1lni (de a manu\u00e1lis megold\u00e1st is tudni kell, ez alapj\u00e1n \u00e9rthet\u0151, mi is t\u00f6rt\u00e9nik a sz\u00ednfalak m\u00f6g\u00f6tt).

      BEADAND\u00d3

      K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st f1b.png n\u00e9ven az al\u00e1bbiak szerint:

      "},{"location":"hazi/5-mvvm/#feladat-2-atteres-mvvm-alapu-megoldasra","title":"Feladat 2 - \u00c1tt\u00e9r\u00e9s MVVM alap\u00fa megold\u00e1sra","text":"

      Az el\u0151z\u0151 l\u00e9p\u00e9sben, b\u00e1r az MVVM Toolkitet haszn\u00e1ltuk, m\u00e9g nem t\u00e9rt\u00fcnk \u00e1t MVVM alap\u00fa megold\u00e1ra (a toolkitet csak az INPC egyszer\u0171bb megval\u00f3s\u00edt\u00e1s\u00e1ra haszn\u00e1ltuk).

      A k\u00f6vetkez\u0151kben \u00e1talak\u00edtjuk az alkalmaz\u00e1sunk architekt\u00far\u00e1j\u00e1t, hogy az MVVM koncepci\u00f3j\u00e1t k\u00f6vesse. Az egyszer\u0171bb megval\u00f3s\u00edt\u00e1s \u00e9rdek\u00e9ben \u00e9p\u00edt\u00fcnk az MVVM Toolkitre.

      Feladat: Dolgozd fel a kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sanyagot (WinUI anyagr\u00e9sz v\u00e9g\u00e9n tal\u00e1lhat\u00f3):

      Mit is jelent az MVVM minta a p\u00e9ld\u00e1nkra vet\u00edtve:

      Feladat: alak\u00edtsd \u00e1t a megl\u00e9v\u0151 logik\u00e1t \u00edgy, hogy a fenti elveket k\u00f6vet\u0151 MVVM mint\u00e1t k\u00f6vesse. A PersonListPageViewModel oszt\u00e1lyt egy \u00fajonnan l\u00e9trehozott ViewModels mapp\u00e1ba tedd. Pr\u00f3b\u00e1ld magad kidolgozni a megold\u00e1st a fenti seg\u00edts\u00e9g alapj\u00e1n! Ehhez egy el\u0151zetes tippet adunk, mert erre nehezebb r\u00e1j\u00f6nni: Az esem\u00e9nyekhez az esem\u00e9nykezel\u0151 m\u0171veleteket adatk\u00f6t\u00e9ssel is meg lehet adni: l\u00e1sd el\u0151ad\u00e1s dia \"Esem\u00e9nyek \u00e9s funkci\u00f3k k\u00f6t\u00e9se\" c\u00edmmel (az \u00e1talak\u00edt\u00e1s ut\u00e1n az esem\u00e9nykezel\u0151ket csak \u00edgy tudjuk megadni). Az is fontos, hogy adatk\u00f6tni csak publikus tulajdons\u00e1ghoz/m\u0171velethez lehet, ennek kapcs\u00e1n is lesz \u00e1talak\u00edtand\u00f3!

      Tippek / megold\u00e1s visszaellen\u0151rz\u00e9se
      1. PersonListPage.xaml.cs code-behind f\u00e1jlb\u00f3l szinte mindent (kiv\u00e9ve this.InitializeComponent() h\u00edv\u00e1s a konstruktorban) \u00e1t kell mozgatni az \u00fajonnan bevezetett PersonListPageViewModel-be, mert ez mind UI logika.
      2. A PersonListPageViewModel publikus oszt\u00e1ly legyen.
      3. A PersonListPage code behindba fel kell venni egy ViewModel nev\u0171, PersonListPageViewModel t\u00edpus\u00fa, csak getterrel rendelkez\u0151 auto implement\u00e1lt tulajdons\u00e1got, \u00e9s ezt egy \u00faj objektumra inicializ\u00e1lni is kell. Vagyis a view hozza l\u00e9tre \u00e9s tartalmazza a ViewModel-t!
      4. A PersonListPage.xaml-ben a k\u00e9t TextBox adatk\u00f6t\u00e9s\u00e9t megfelel\u0151en igaz\u00edtani kell (a NewPerson.Name \u00e9s NewPerson.Age m\u00e1r egy szinttel m\u00e9lyebben, a code behind ViewModel tulajdons\u00e1g\u00e1n kereszt\u00fcl \u00e9rhet\u0151 el).
      5. A PersonListPage.xaml-ben az esem\u00e9nykezel\u0151k (Click) igaz\u00edt\u00e1sa h\u00e1rom helyen. Ezt tr\u00fckk\u00f6sebb. Esem\u00e9nykezel\u0151 f\u00fcggv\u00e9ny az eddig alkalmazott szintaktik\u00e1val nem adhat\u00f3 m\u00e1r meg, mert az esem\u00e9nykezel\u0151k nem a code behindban tal\u00e1lhat\u00f3k (\u00e1tker\u00fcltek a ViewModel-be).
        • Az esem\u00e9nyekhez az esem\u00e9nykezel\u0151 m\u0171veleteket adatk\u00f6t\u00e9ssel is meg lehet adni! L\u00e1sd el\u0151ad\u00e1s dia \"Esem\u00e9nyek \u00e9s funkci\u00f3k k\u00f6t\u00e9se\" c\u00edmmel. Ez nek\u00fcnk az\u00e9rt j\u00f3, mert a code behind ViewModel tulajdons\u00e1g\u00e1ban ott a PersonListPageViewModel objektum, melyben ott vannak az esem\u00e9nykezel\u0151k (AddButton_Click, IncreaseButton_Click, DecreaseButton_Click), ezeket kell k\u00f6t\u00f6tt tulajdons\u00e1gk\u00e9nt megadni az adatk\u00f6t\u00e9sben (pl. ViewModel.AddButton_Click stb.).
        • Fontos, hogy az esem\u00e9nykezel\u0151 f\u00fcggv\u00e9nyek legyenek publikusak, m\u00e1sk\u00fcl\u00f6nben nem m\u0171k\u00f6dik az adatk\u00f6t\u00e9s (\u00e1t kell alak\u00edtani priv\u00e1tr\u00f3l).

      Tov\u00e1bbi l\u00e9nyeges \u00e1talak\u00edtand\u00f3k:

      Ellen\u0151rizd, hogy az \u00e1talak\u00edt\u00e1sok ut\u00e1n is pontosan ugyan\u00fagy m\u0171k\u00f6dik az alkalmaz\u00e1s, mint el\u0151tte!

      Mit nyert\u00fcnk azzal, hogy kor\u00e1bbi megold\u00e1sunkat MVVM alap\u00fara alak\u00edtottuk \u00e1t? A v\u00e1laszt az el\u0151ad\u00e1sanyag adja meg! P\u00e1r dolog kiemelve:

      Min\u00e9l komplexebb egy alkalmaz\u00e1s, ann\u00e1l ink\u00e1bb igazak ezek.

      BEADAND\u00d3

      K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st f2.png n\u00e9ven az al\u00e1bbiak szerint:

      "},{"location":"hazi/5-mvvm/#feladat-3-vezerlok-tiltasaengedelyezese","title":"Feladat 3 - Vez\u00e9rl\u0151k tilt\u00e1sa/enged\u00e9lyez\u00e9se","text":"

      Jelen \u00e1llapotban kiss\u00e9 furcs\u00e1n viselkedik az alkalmaz\u00e1s: a \"-\" gombbal negat\u00edv tartom\u00e1nyba is vihet\u0151 egy \u00e9letkor, vagy a \"+\"-szal 150 f\u00f6l\u00e9, illetve a \"+Add\" gombbal olyan szem\u00e9ly is felvehet\u0151, mely \u00e9rtelmetlen tulajdons\u00e1gokkal rendelkezik. Ezeket a gombokat le kellene tiltani, amikor az \u00e1ltaluk kiv\u00e1ltott m\u0171veletnek nincs \u00e9rtelme, illetve enged\u00e9lyezni, amikor van.

      A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben val\u00f3s\u00edtsuk meg a \"-\" gomb tilt\u00e1s\u00e1t/enged\u00e9lyez\u00e9s\u00e9t ennek megfelel\u0151en. A gomb akkor legyen csak enged\u00e9lyezett, ha a szem\u00e9ly \u00e9letkora 0-n\u00e1l nagyobb.

      Pr\u00f3b\u00e1ld ezt els\u0151 k\u00f6rben magadt\u00f3l megval\u00f3s\u00edtani, legal\u00e1bbis az alapjait lefektetni! Mindenk\u00e9ppen adatk\u00f6t\u00e9s alap\u00fa megold\u00e1sban gondolkozz, csak ez fogadhat\u00f3 el! Ha elakadsz, a megold\u00e1sod nem \"akar\" m\u0171k\u00f6dni, akkor gondold \u00e1t, mi lehet az oka, a megold\u00e1st pedig az al\u00e1bbiaknak megfelel\u0151en alak\u00edtsd ki.

      A probl\u00e9m\u00e1ra t\u00f6bbf\u00e9le megold\u00e1s is kidolgozhat\u00f3. Mindben k\u00f6z\u00f6s, hogy a \"-\" gomb IsEnabled tulajdons\u00e1g\u00e1t k\u00f6tj\u00fck valamilyen m\u00f3don. Az \u00e1ltalunk v\u00e1lasztott megold\u00e1sban egy a PersonListPageViewModel-ben \u00fajonnan bevezetett bool tulajdons\u00e1ghoz k\u00f6ss\u00fck.

      PersonListPageViewModel.cs
          public bool IsDecrementEnabled\n    {\n        get { return NewPerson.Age > 0; }\n    }\n
      PersonListPage.xaml-be a '-' gombhoz
          IsEnabled=\"{x:Bind ViewModel.IsDecrementEnabled, Mode=OneWay}\"\n

      Pr\u00f3b\u00e1ljuk ki! Sajnos nem m\u0171k\u00f6dik, a \"-\" gomb nem tilt\u00f3dik le, amikor 0 vagy kisebb \u00e9rt\u00e9k\u0171 lesz az \u00e9letkor (pl. a gomb sokszori kattint\u00e1s\u00e1val). Ha t\u00f6r\u00e9spontot tesz\u00fcnk az IsDecrementEnabled belsej\u00e9be, \u00e9s \u00edgy ind\u00edtjuk az alkalmaz\u00e1st, azt tapasztaljuk, hogy a tulajdons\u00e1g \u00e9rt\u00e9k\u00e9t csak egyszer k\u00e9rdezi le a k\u00f6t\u00f6tt vez\u00e9rl\u0151, az alkalmaz\u00e1s indul\u00e1sakor: ut\u00e1na hi\u00e1ba kattintunk pl. a \"-\" gombon, t\u00f6bbsz\u00f6r nem. Pr\u00f3b\u00e1ld is ki!

      Gondold \u00e1t, mi okozza ezt, \u00e9s csak ut\u00e1na haladj tov\u00e1bb az \u00fatmutat\u00f3val!

      Indokl\u00e1s

      A kor\u00e1bban tanultaknak megfelel\u0151en az adatk\u00f6t\u00e9s csak akkor k\u00e9rdezi le a forr\u00e1stulajdons\u00e1g (eset\u00fcnkben IsDecrementEnabled) \u00e9rt\u00e9k\u00e9t, ha annak v\u00e1ltoz\u00e1s\u00e1r\u00f3l az INotifyPropertyChanged seg\u00edts\u00e9g\u00e9vel \u00e9rtes\u00edt\u00e9st kap! M\u00e1rpedig, jelen megold\u00e1sunkban hi\u00e1ba v\u00e1ltozik a NewPerson objektum Age tulajdons\u00e1ga, ennek megt\u00f6rt\u00e9ntekor a semmif\u00e9le \u00e9rtes\u00edt\u00e9s nincs az erre \u00e9p\u00fcl\u0151 IsDecrementEnabled tulajdons\u00e1g megv\u00e1ltoz\u00e1s\u00e1r\u00f3l!

      A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben val\u00f3s\u00edtsd meg a kapcsol\u00f3d\u00f3 v\u00e1ltoz\u00e1s\u00e9rtes\u00edt\u00e9st a PersonListPageViewModel oszt\u00e1lyban:

      Teszteld is a megold\u00e1sod! Ha j\u00f3l dolgozt\u00e1l, a gombnak akkor is le kell tilt\u00f3dnia, ha a TextBoxba k\u00e9zzel \u00edrsz be negat\u00edv \u00e9letkor \u00e9rt\u00e9ket (\u00e9s ut\u00e1na kikattintasz a TextBoxb\u00f3l). Gondold \u00e1t, mi\u00e9rt van ez \u00edgy!

      A \"+\" gombra \u00e9s a \"+Add\" gomra is dolgozz ki hasonl\u00f3 megold\u00e1st!

      A tesztel\u00e9s sor\u00e1n azt tapasztaljuk, hogy ha pl. kit\u00f6r\u00f6lj\u00fck a nevet a n\u00e9v TextBox-ban, a \"+Add\" gomb \u00e1llapota nem azonnal v\u00e1ltozik, hanem csak ha elhagyjuk a TextBox-ot? Mi\u00e9rt van ez? M\u00f3dos\u00edtsd a megold\u00e1sod, hogy ez minden sz\u00f6veg v\u00e1ltoz\u00e1skor, a TextBox elhagy\u00e1sa n\u00e9lk\u00fcl is megt\u00f6rt\u00e9njen. Tipp: l\u00e1sd el\u0151ad\u00e1sanyag \"x:Bind mikor friss\u00fcl az adat?\" c\u00edm\u0171 dia.

      BEADAND\u00d3

      K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st f3.png n\u00e9ven az al\u00e1bbiak szerint:

      "},{"location":"hazi/5-mvvm/#feladat-4-command-hasznalata","title":"Feladat 4 - Command haszn\u00e1lata","text":"

      Jelen pillanatban a \"-\" gomb vonatkoz\u00e1s\u00e1ban eset\u00e9ben k\u00e9t feladatunk van:

      Bizonyos vez\u00e9rl\u0151k - ilyen a gomb is - t\u00e1mogatj\u00e1k, hogy ezt a kett\u0151t, a Command mint\u00e1ra \u00e9p\u00edtve, egy parancs objektum seg\u00edts\u00e9g\u00e9vel adhassuk meg. A Command tervez\u00e9si minta koncepci\u00f3j\u00e1val a \"Tervez\u00e9si mint\u00e1k 3\" el\u0151ad\u00e1s alapj\u00e1n lehet r\u00e9sztelesebben megismerkedni (b\u00e1r ott csak az alap Command mint\u00e1val ismerkedt\u00fcnk meg, mely a parancs futtat\u00e1s\u00e1t t\u00e1mogatja, tilt\u00e1s\u00e1t/enged\u00e9lyez\u00e9s\u00e9t nem). A Command minta MVVM specifikus megval\u00f3s\u00edt\u00e1s\u00e1val a WinUI el\u0151ad\u00e1ssorozat v\u00e9ge fel\u00e9, a \"Command minta\" c\u00edm\u0171 di\u00e1t\u00f3l kezdve lehet megismerkedni.

      Az alapelv a k\u00f6vetkez\u0151: a gombn\u00e1l a Click \u00e9s IsEnabled \"megad\u00e1sa\" helyett a gomb Command tulajdons\u00e1g\u00e1t \u00e1ll\u00edtjuk egy ICommand interf\u00e9szt megval\u00f3s\u00edt\u00f3 command objektumra. A futtat\u00e1s, illetve tilt\u00e1s/enged\u00e9lyez\u00e9s m\u00e1r ezen command objektum feladata.

      Alapesetben egy alkalmaz\u00e1sban minden parancshoz egy k\u00fcl\u00f6n ICommand implement\u00e1ci\u00f3t kellene k\u00e9sz\u00edteni. Ez azonban sok parancs eset\u00e9n sok oszt\u00e1ly bevezet\u00e9s\u00e9t ig\u00e9nyli. Az MVVM Toolkit ebben is a seg\u00edts\u00e9g\u00fcnkre siet. Biztos\u00edt egy RelayCommand oszt\u00e1lyt, mely megval\u00f3s\u00edtja az ICommand interf\u00e9szt. Ez az oszt\u00e1ly b\u00e1rmilyen parancs/k\u00f3d futtat\u00e1s\u00e1ra haszn\u00e1lhat\u00f3, \u00edgy nem kell tov\u00e1bbi command oszt\u00e1lyokat bevezetni. Hogyan lehets\u00e9ges ez? \u00dagy, hogy a RelayCommand-nak konstruktor param\u00e9terekben, k\u00e9t delegate form\u00e1j\u00e1ban tudjuk a v\u00e9grehajt\u00e1shoz \u00e9s a tilt\u00e1shoz/enged\u00e9lyez\u00e9shez tartoz\u00f3k k\u00f3dot:

      A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben a \"-\" gomb kezel\u00e9s\u00e9t alak\u00edtjuk \u00e1t command alap\u00fara. El\u0151sz\u00f6r pr\u00f3b\u00e1ld a nagyj\u00e1t \u00f6n\u00e1ll\u00f3an megval\u00f3s\u00edtani a kapcsol\u00f3d\u00f3 WinUI el\u0151ad\u00e1sanyag alapj\u00e1n. A parancs futtat\u00e1sa egyszer\u0171bb, de a parancs tilt\u00e1s-enged\u00e9lyez\u00e9shez lesz m\u00e9g teend\u0151nk. F\u0151bb l\u00e9p\u00e9sek:

      Ha kipr\u00f3b\u00e1ljuk, a parancs futtat\u00e1s m\u0171k\u00f6dik, a tilt\u00e1s/enged\u00e9lyez\u00e9s viszont m\u00e9g nem: ha j\u00f3l megfigyelj\u00fck, a gomb mindig enged\u00e9lyezett marad megjelen\u00e9s\u00e9ben. Ennek, kicsit jobban belegondolva, logikus oka van: a RelayCommand meg tudja ugyan h\u00edvni a m\u00e1sodik konstruktor param\u00e9ter\u00e9ben megadott m\u0171veletet az \u00e1llapot ellen\u0151rz\u00e9s\u00e9hez, de nem tudja, hogy minden NewPerson.Age v\u00e1ltoz\u00e1skor meg kellene ezt tennie! Ezen tudunk seg\u00edteni. A ViewModel-\u00fcnk konstruktor\u00e1ban m\u00e1r feliratkoztunk kor\u00e1bban a NewPerson.PropertyChanged esem\u00e9nyre: erre \u00e9p\u00edtve, amikor v\u00e1ltozik az \u00e9letkor (vagy amikor v\u00e1ltozhat, az nem probl\u00e9ma, ha n\u00e9ha feleslegesen megtessz\u00fck) h\u00edvd meg a DecreaseAgeCommand NotifyCanExecuteChanged m\u0171velet\u00e9t. Ennek a m\u0171veletnek nagyon besz\u00e9des neve van: \u00e9rtes\u00edti a parancsot, hogy megv\u00e1ltoz(hat)ott azon \u00e1llapot, mely alapj\u00e1n a parancs tiltott/enged\u00e9lyezett \u00e1llapota \u00e9p\u00edt. \u00cdgy a parancs friss\u00edteni fogja mag\u00e1t, pontosabban a parancshoz tartoz\u00f3 gomb \u00e1llapot\u00e1t.

      \u00cdrd \u00e1t \"+\" gomb kezel\u00e9s\u00e9t is hasonl\u00f3an, parancs alap\u00fara! A \"+Add\" gomb kezel\u00e9s\u00e9t ne v\u00e1ltoztasd meg!

      BEADAND\u00d3

      K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st f4.png n\u00e9ven az al\u00e1bbiak szerint:

      "},{"location":"hazi/5-mvvm/#feladat-5-command-hasznalata-mvvm-toolkit-alapu-kodgeneralassal","title":"Feladat 5 - Command haszn\u00e1lata MVVM Toolkit alap\u00fa k\u00f3dgener\u00e1l\u00e1ssal","text":"

      Az el\u0151z\u0151 feladatban a command tulajdons\u00e1gok bevezet\u00e9s\u00e9t \u00e9s azok p\u00e9ld\u00e1nyos\u00edt\u00e1s\u00e1t \"manu\u00e1lisan\" oldottuk meg. Az MVVM Toolkit ezt le tudja egyszer\u0171s\u00edteni: megfelel\u0151 attrib\u00fatum alkalmaz\u00e1sa eset\u00e9n a tulajdons\u00e1got \u00e9s a p\u00e9ld\u00e1nyos\u00edt\u00e1st automatikusan le tudja gener\u00e1lni.

      Alak\u00edtsuk \u00e1t a DecreaseAgeCommand kezel\u00e9s\u00e9t (csak ezt, az IncreaseAgeCommand maradjon!) gener\u00e1lt k\u00f3d alap\u00fara:

      1. L\u00e1sd el a PersonListPageViewModel oszt\u00e1lyt a partial kulcssz\u00f3val.
      2. T\u00f6r\u00f6ld ki a DecreaseAgeCommand tulajdons\u00e1got \u00e9s ennek p\u00e9ld\u00e1nyos\u00edt\u00e1s\u00e1t a konstruktorb\u00f3l.
      3. A DecreaseAge m\u0171veletet l\u00e1sd el ezzel az attrib\u00fatummal: [RelayCommand(CanExecute = nameof(IsDecrementEnabled))].
        • Ennek hat\u00e1s\u00e1ra a k\u00f3dgener\u00e1tor bevezet egy RelayCommand tulajdons\u00e1got az oszt\u00e1lyban, melynek neve a m\u0171velet\u00fcnk neve (DecreaseAge), hozz\u00e1f\u0171zve a \"Command\" stringet. Ezzel meg is kapjuk a kor\u00e1bban k\u00e9zzel bevezetett DecreaseAgeCommand nev\u0171 tulajdons\u00e1got.
        • A CanExecute attrib\u00fatum tulajdons\u00e1gban egy string form\u00e1ban annak a boollal visszat\u00e9r\u0151 m\u0171veletnek vagy tulajdons\u00e1gnak a nev\u00e9t lehet megadni, melyet a gener\u00e1lt k\u00f3d a parancs tilt\u00e1s\u00e1nak/enged\u00e9lyez\u00e9s\u00e9nek sor\u00e1n haszn\u00e1l (a RelayCommand konstruktor m\u00e1sodik param\u00e9tere lesz). Nek\u00fcnk m\u00e1r van ilyen tulajdons\u00e1gunk, \"IsDecrementEnabled\" n\u00e9vben. Az\u00e9rt nem egyszer\u0171 string form\u00e1j\u00e1ban adjuk meg, mert ha ut\u00f3lag valaki \u00e1tnevezi az IsDecrementEnabled m\u0171veletet, akkor a mostani \"IsDecrementEnabled\" m\u00e1r nem j\u00f3 m\u0171veletre mutatna. A nameof kifejez\u00e9s haszn\u00e1lat\u00e1val ez a probl\u00e9ma elker\u00fclhet\u0151. A CanExecute megad\u00e1sa \u00e1ltal\u00e1noss\u00e1g\u00e1ban nem k\u00f6telez\u0151 (nem adjuk meg, ha nem akarjuk a parancsot soha tiltani).

      Teszteld a megold\u00e1st (\u00e9letkor cs\u00f6kkent\u00e9se), ugyan\u00fagy kell m\u0171k\u00f6dnie, mint kor\u00e1bban.

      BEADAND\u00d3

      K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st f5.png n\u00e9ven az al\u00e1bbiak szerint:

      "},{"location":"hazi/5-mvvm/#feladat-6-strict-mvvm","title":"Feladat 6 - Strict MVVM","text":"

      Jelen megold\u00e1sunk a Relaxed MVVM megk\u00f6zel\u00edt\u00e9st k\u00f6veti. A k\u00f6vetkez\u0151 l\u00e9p\u00e9sekben \u00e1tgondoljuk, mit is jelent ez pontosan, \u00e9s mit jelentene a Strict MVVM megk\u00f6zel\u00edt\u00e9sre val\u00f3 \u00e1t\u00e1ll\u00e1s (megval\u00f3s\u00edtani nem fogjuk).

      Jelen megold\u00e1sunk a Relaxed MVVM megk\u00f6zel\u00edt\u00e9st k\u00f6veti, vagyis a View-ban k\u00f6zvetlen\u00fcl a Person modell oszt\u00e1lyhoz adatk\u00f6t\u00fcnk (\u00e9s a PersonPageViewModel-ben is a Person modell oszt\u00e1lyt haszn\u00e1ljuk). Ennek el\u0151nye az egyszer\u0171s\u00e9g. De van egy h\u00e1tr\u00e1nya is: a Person modell oszt\u00e1lyunkban k\u00e9nytelenek voltunk megval\u00f3s\u00edtani az INotifyPropertyChanged interf\u00e9szt (m\u00e9gha az MVVM toolkit seg\u00edts\u00e9g\u00e9vel is), k\u00fcl\u00f6nben nem m\u0171k\u00f6dne j\u00f3l az adatk\u00f6t\u00e9s. Vannak olyan helyzetek, amikor a modell oszt\u00e1lyunkat nem szeretn\u00e9nk ilyen, n\u00e9mik\u00e9ppen m\u00e1r a fel\u00fcletet kiszolg\u00e1l\u00f3 logik\u00e1val \"szennyezni\", hanem a lehet\u0151 legtiszt\u00e1bb form\u00e1ban szeretn\u00e9nk a modell oszt\u00e1lyunkat tartani. Ekkor a Strict MVVM megk\u00f6zel\u00edt\u00e9s jelenti a megold\u00e1st (l\u00e1sd \"Strict MVVM - be\u00e1gyaz\u00e1s\" el\u0151ad\u00e1sdia). Mit jelentene ez a sz\u00e1munkra, mit kellene a k\u00f3don v\u00e1ltoztatni? Gondold \u00e1t az el\u0151ad\u00e1sdia alapj\u00e1n a sz\u00fcks\u00e9ges v\u00e1ltoztat\u00e1sokat! Megval\u00f3s\u00edtani/dokument\u00e1lni nem kell, ez csak egy \u00e1tgondol\u00f3s feladat \ud83d\ude0a!

      Strict MVVM alap\u00fa megold\u00e1s "},{"location":"hazi/5-mvvm/index_ger/","title":"5. HA - Verwendung der MVVM-Muster und des MVVM-Toolkits","text":""},{"location":"hazi/5-mvvm/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

      In dieser Hausaufgabe werden wir die w\u00e4hrend der 3. Labor\u00fcbung (XAML) implementierte Anwendung f\u00fcr Personenregistrierung so ver\u00e4ndern, dass sie auf dem MVVM-Muster basiert, und wir werden das MVVM-Toolkit kennenlernen.

      Die Hausaufgabe baut auf dem MVVM-Thema auf, das am Ende der WinUI-Vorlesungsreihe behandelt wurde. Hinweis: Labor 5 - MVVM-Labor ist sehr abwechslungsreich und zeigt neben vielen anderen Themen ein Beispiel f\u00fcr das MVVM-Muster im Kontext einer komplexeren Anwendung. Die vorliegende Hausaufgabe ist fokussierter, in kleineren Schritten aufgebaut: In unserem Fall wird die L\u00f6sung dieser Hausaufgabe helfen, um die zugeh\u00f6rige Teile von Lab 5 - MVVM zu verstehen.

      Durch das Durcharbeiten des zugeh\u00f6rigen Vorlesungsmaterials k\u00f6nnen die Aufgaben dieser eigenst\u00e4ndigen \u00dcbung mit Hilfe der k\u00fcrzeren Leitf\u00e4den, die auf die Aufgabenbeschreibung folgen (manchmal standardm\u00e4\u00dfig eingefaltet), selbst\u00e4ndig bearbeitet werden.

      Das Ziel der Hausaufgabe:

      Die erforderliche Entwicklungsumgebung wird hier beschrieben, identisch mit Hausaufgabe 3 (XAML-Grundlagen).

      "},{"location":"hazi/5-mvvm/index_ger/#das-verfahren-fur-die-einreichung","title":"Das Verfahren f\u00fcr die Einreichung","text":"

      Auf das Moodle soll ein ZIP-Archiv hochgeladen werden, das die folgenden Anforderungen entspricht:

      "},{"location":"hazi/5-mvvm/index_ger/#bedingungen","title":"Bedingungen","text":"

      Obligatorische Verwendung der MVVM-Muster! In dieser Hausaufgabe \u00fcben wir das MVVM-Muster, daher ist das MVVM-Muster f\u00fcr die L\u00f6sung der Aufgaben obligatorisch erforderlich. Andernfalls wird die Bewertung der Aufgaben verweigert.

      "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-0-uberblick-uber-den-ausgangszustand","title":"Aufgabe 0 - \u00dcberblick \u00fcber den Ausgangszustand","text":"

      Der Ausgangszustand ist im Grunde derselbe wie die Endzustand von der Labor\u00fcbung 3. Entwurf der Benutzeroberfl\u00e4che. Also eine solche Anwendung, die die Speicherung der Daten von Personen in einer Liste erm\u00f6glicht. Sie enth\u00e4lt eine kleinere \u00c4nderung im Vergleich zum Endzustand des Labors. Im Labor war die vollst\u00e4ndige Beschreibung der Oberfl\u00e4che in MainWindow.xaml (und die zugeh\u00f6rige Code-Behind-Datei) verf\u00fcgbar. Der Unterschied zu dieser urspr\u00fcnglichen L\u00f6sung besteht darin, dass sie nach PersonListPage.xaml (und in den Code dahinter) im Ordner Views verschoben wurde. PersonListPage ist keine Window, sondern eine von Page abgeleitete Klasse (siehe den Code hinter der Datei). Aber sonst hat sich nichts ge\u00e4ndert! Wie der Name schon sagt, stellt Page eine \"Seite\" in der Anwendung dar: Sie kann nicht selbst angezeigt werden, sondern muss z. B. in einem Fenster platziert werden. Der Vorteil dieses Fensters ist, dass es m\u00f6glich ist, zwischen den Seiten (verschiedene Page Nachkommen) zu navigieren, indem man die entsprechende Navigation verwendet. Wir werden das nicht ausnutzen, wir werden nur eine Seite haben. Der Zweck der Einf\u00fchrung dieser Seite war nur zu veranschaulichen, dass in der MVVM-Architektur, Ansichten k\u00f6nnen nicht nur mit Window (full window), sondern auch mit Objekten wie Page implementiert werden.

      Da alles von MainWindow nach PersonListPage verschoben wurde, gibt es auf MainWindow.xaml nichts anderes als eine Kopie eines solchen PersonListPage Objekts:

      <views:PersonListPage/>\n

      Pr\u00fcfen Sie im Code, ob dies tats\u00e4chlich der Fall ist!

      "},{"location":"hazi/5-mvvm/index_ger/#kopfzeile-des-hauptfensters","title":"Kopfzeile des Hauptfensters","text":"

      Die \u00dcberschrift des Hauptfensters sollte \"MVVM\" sein, angeh\u00e4ngt mit Ihrem Neptun-Code: (z.B.\"MVVM - ABCDEF\" im Falle des Neptun-Codes \"ABCDEF\"), ist es wichtig, dass dies der Text ist! Setzen Sie dazu die Eigenschaft Title Ihres Hauptfensters auf diesen Text in der Datei MainWindow.xaml.

      "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-1-verwendung-des-mvvm-toolkits","title":"Aufgabe 1 - Verwendung des MVVM-Toolkits","text":"

      In der bestehenden Anwendung implementiert die Klasse Person im Ordner Models bereits die Schnittstelle INotifyPropertyChanged (Spitzname INPC) (sie hat also ein Ereignis PropertyChanged ) und zeigt au\u00dferdem eine Eigenschafts\u00e4nderung in den Settern Name und Age an, indem sie das Ereignis PropertyChanged ausl\u00f6st (siehe Person.cs f\u00fcr eine detaillierte Betrachtung).

      Zum Aufw\u00e4rmen/Wiederholen - nachdem Sie sich den Code (PersonListPage.xaml und PersonListPage.xaml.cs) genau angesehen und die Anwendung ausgef\u00fchrt haben - sagen Sie sich, warum dies in der Anwendung erforderlich war!

      Die Antwort (Wiederholung)

      In der Anwendung ist die Eigenschaft Text von TextBox (dies ist die Zieleigenschaft) in PersonListPage.xaml an die Eigenschaften Age und Name des Members NewPerson mit dem Typ Person im Code-Behind-Datei gebunden (dies sind die Quellen in den beiden Datenverbindungen). Beachten Sie im Code, dass die Quelleneigenschaften NewPerson.Name und NewPerson.Age ebenfalls im Code ge\u00e4ndert werden: Der Controller kann nur \u00fcber diese \u00c4nderungen informiert werden (und somit mit der Quelle synchron bleiben), wenn er \u00fcber diese \u00c4nderungen an Name und Age informiert wird. Aus diesem Grund muss die Klasse, die die Eigenschaften Age und Name enth\u00e4lt, d.h. Person, die Schnittstelle INotifyPropertyChanged implementieren und das Ereignis PropertyChanged ausl\u00f6sen, wenn sich die Eigenschaften \u00e4ndern, wobei das Ereignis entsprechend parametrisiert sein muss.

      Wenn Sie die Anwendung ausf\u00fchren, \u00fcberpr\u00fcfen Sie, ob die \u00c4nderungen, die Sie auf NewPerson.Age durch Dr\u00fccken der Tasten \"+\" und \"-\" vornehmen, tats\u00e4chlich in der TextBox, die das Alter anzeigt, wiedergegeben werden.

      In der Klasse Person k\u00f6nnen Sie sehen, dass die Implementierung von INotifyPropertyChanged und der dazugeh\u00f6rige Code recht umfangreich ist. Schauen Sie sich die Vorlesungsunterlagen an, um zu sehen, welche Alternativen es f\u00fcr die Implementierung der Schnittstelle gibt (ausgehend von der Folie \"INPC Beispiel 1\", etwa vier Folien zur Veranschaulichung der vier M\u00f6glichkeiten)! Die kompakteste L\u00f6sung ist das MVVM-Toolkit. Im n\u00e4chsten Schritt werden wir die derzeitige umfangreichere \"manuelle\" INPC-Implementierung in ein MVVM-Toolkit umwandeln.

      "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-1a-aufnahme-des-mvvm-toolkit-nuget-referenzes","title":"Aufgabe 1/a - Aufnahme des MVVM Toolkit NuGet Referenzes","text":"

      Zun\u00e4chst muss eine NuGet-Referenz auf das MVVM-Toolkit erstellt werden, damit es im Projekt verwendet werden kann.

      Aufgabe: F\u00fcgen Sie eine NuGet-Referenz f\u00fcr das NuGet-Paket \"CommunityToolkit.Mvvm\" in das Projekt ein. Auf dieser Visual Studio-Seite wird beschrieben, wie eine NuGet-Referenz mit dem NuGet Package Manager zu einem Projekt hinzugef\u00fcgt wird. Der vorhergehende Link auf der Seite f\u00fchrt Sie zum Abschnitt \"NuGet Package Manager\". Folgen Sie den vier hier angegebenen Schritten (mit dem Unterschied, dass Sie auf das Paket \"CommunityToolkit.Mvvm\" statt auf \"Newtonsoft.Json\" verweisen m\u00fcssen).

      Nachdem wir nun diese NuGet-Referenz zu unserem Projekt hinzugef\u00fcgt haben, wird der n\u00e4chste Build (da er einen NuGet restore Schritt enth\u00e4lt!) das NuGet-Paket herunterladen, die darin enthaltenen DLLs in den Ausgabeordner entpacken und sie zu einem integralen Bestandteil der Anwendung machen (ein NuGet-Paket ist eigentlich eine Zip-Datei). Es ist wichtig zu beachten, dass weder die NuGet-Zipdatei noch die darin enthaltenen DLLs in Git enthalten sind. Sie werden von der Datei .gitignore im Stammverzeichnis der L\u00f6sung herausgefiltert. Dies ist der eigentliche Kern des NuGet-Konzepts: Das Repository kann klein bleiben, da die Projektdatei nur Verweise auf NuGet-Pakete enth\u00e4lt, und wenn jemand eine frisch geklonte L\u00f6sung erstellt, werden die referenzierten NuGet-Pakete erst dann aus den Online-NuGet-Ressourcen heruntergeladen.

      Die Kenntnis der oben genannten NuGet-Konzepte ist wichtig, sie sind ein wichtiger Teil des Lehrmaterials!

      Eine NuGet-Referenz ist eigentlich nur eine Zeile in der Projektbeschreibungsdatei .csproj. Klicken Sie im Solution Explorer auf den Projektknoten \"HelloXaml\", \u00f6ffnen Sie die Projektdatei .csproj und \u00fcberpr\u00fcfen Sie, ob diese Zeile enthalten ist (die Version kann unterschiedlich sein):

          <PackageReference Include=\"CommunityToolkit.Mvvm\" Version=\"8.2.2\" />\n

      Sie k\u00f6nnen unsere NuGet-Referenz \u00fcberpr\u00fcfen, ohne die Datei csproj zu \u00f6ffnen: \u00d6ffnen Sie im Solution Explorer den Knoten \"HelloXaml\"/\"Dependencies\"/\"Packages\": Wenn alles in Ordnung ist, sehen Sie darunter einen Knoten \"CommunityToolkit.Mvvm (Version)\".

      "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-1b-inpc-implementierung-auf-basis-des-mvvm-toolkits","title":"Aufgabe 1/b - INPC-Implementierung auf Basis des MVVM-Toolkits","text":"

      Jetzt k\u00f6nnen wir die Klassen, Schnittstellen, Attribute usw. im MVVM Toolkit NuGet-Paket verwenden, so dass wir zur MVVM Toolkit-basierten INPC-Implementierung wechseln k\u00f6nnen.

      Wir sind fertig.

      \u00dcberpr\u00fcfung der L\u00f6sung

      ```` csharp public partial class Person : ObservableObject { [ObservableProperty] private string name;

      [ObservableProperty]\nprivate int age;\n

      } ```

      Dieser Code ergibt nach einer \u00dcbersetzung im Wesentlichen die gleiche L\u00f6sung wie die fr\u00fchere, viel ausf\u00fchrlichere und jetzt auskommentierte L\u00f6sung. Das hei\u00dft (auch wenn wir es noch nicht sehen), es werden die Eigenschaften Name und Age erstellt, mit entsprechenden PropertyChanged Ereignisausl\u00f6sern. Wie ist das m\u00f6glich?

      Der Code-Generator arbeitet in der Regel in der anderen \"partiellen\" H\u00e4lfte unserer Klasse, um den von uns geschriebenen und den von uns generierten Code nicht zu verwechseln! Teilklassen werden am h\u00e4ufigsten verwendet, um handgeschriebenen Code von generiertem Code zu \"trennen\".

      Da viel weniger Code geschrieben werden muss, verwenden wir in der Praxis die auf dem MVVM-Toolkit basierende L\u00f6sung (aber Sie m\u00fcssen auch die manuelle L\u00f6sung kennen, damit Sie verstehen k\u00f6nnen, was hinter den Kulissen geschieht).

      EINGABE

      Machen Sie einen Screenshot mit dem Namen f1b.png wie folgt:

      "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-2-umstellung-auf-eine-mvvm-basierte-losung","title":"Aufgabe 2 - Umstellung auf eine MVVM-basierte L\u00f6sung","text":"

      Im vorherigen Schritt haben wir zwar das MVVM-Toolkit verwendet, sind aber noch nicht zu einer MVVM-basierten L\u00f6sung gewechselt (das Toolkit wurde nur f\u00fcr eine einfachere Implementierung von INPC verwendet).

      Im Folgenden werden wir die Architektur unserer Anwendung so anpassen, dass sie dem MVVM-Konzept folgt. Wir bauen auf dem MVVM-Toolkit auf, um die Implementierung zu erleichtern.

      Aufgabe: Arbeiten Sie das entsprechende Vorlesungsmaterial durch (am Ende des WinUI-Abschnitts):

      Was bedeutet das MVVM-Muster f\u00fcr unser Beispiel:

      Aufgabe: \u00c4ndern Sie die bestehende Logik so, dass sie dem MVVM-Muster folgt und den oben genannten Grunds\u00e4tzen entspricht. Legen Sie die Klasse PersonListPageViewModel in einem neu erstellten Ordner ViewModels ab. Versuchen Sie, die L\u00f6sung anhand der obigen Hilfe selbst zu bearbeiten! Dazu geben wir einen vorherigen Hinweis, da das schwieriger herauszufinden ist: Sie k\u00f6nnen auch Ereignishandler f\u00fcr Ereignisse durch Datenverbindung angeben: siehe die Folie \"Bindung von Ereignissen und Funktionen\" (nach der Modifikation ist dies die einzige M\u00f6glichkeit, Ereignishandler anzugeben). Es ist auch wichtig zu beachten, dass Daten nur an \u00f6ffentliche Eigenschaften/Operationen gebunden werden k\u00f6nnen, so dass auch dies ge\u00e4ndert werden muss!

      Tipps/Pr\u00fcfung der L\u00f6sung
      1. Aus der PersonListPage.xaml.cs Code-Behind-Datei sollte fast alles (au\u00dfer this.InitializeComponent() Aufruf im Konstruktor) in die neu eingef\u00fchrte PersonListPageViewModel verschoben werden, da es sich um UI-Logik handelt.
      2. PersonListPageViewModel sollte eine \u00f6ffentliche Klasse sein.
      3. In der PersonListPage Code-Behind-Datei m\u00fcssen Sie eine automatisch implementierte Eigenschaft namens ViewModel vom Typ PersonListPageViewModel mit nur Getter einf\u00fcgen und diese auf ein neues Objekt initialisieren. Mit anderen Worten, die Ansicht erstellt und enth\u00e4lt das ViewModel!
      4. In PersonListPage.xaml m\u00fcssen die beiden Datenverbindungen der zwei TextBox entsprechend korrigiert werden ( NewPerson.Name und NewPerson.Age sind jetzt eine Ebene tiefer verf\u00fcgbar, \u00fcber die ViewModel-Eigenschaft der Code-Behind-Datei).
      5. In PersonListPage.xaml m\u00fcssen die Ereignishandler (Click) an drei Stellen korrigiert werden. Dies ist komplizierter. Die Ereignishandler-Funktion kann nicht mehr mit der bisher verwendeten Syntax angegeben werden, da die Ereignishandler nicht mehr in der Code-Behind-Datei liegen (sie wurden in das ViewModel verschoben).
        • Ereignishandler k\u00f6nnen f\u00fcr Ereignisse durch Datenverbindung angegeben werden! Siehe Pr\u00e4sentationsfolie \"Binden von Ereignissen und Funktionen\". Das ist gut f\u00fcr uns, denn in der ViewModel-Eigenschaft der Code-Behind-Datei ist das PersonListPageViewModel-Objekt, das die Ereignishandler enth\u00e4lt (AddButton_Click, IncreaseButton_Click, DecreaseButton_Click), und diese m\u00fcssen als gebundene Eigenschaften in der Datenverbindung angegeben werden (z.B. ViewModel.AddButton_Click usw.).
        • Es ist wichtig, dass die Ereignishandler-Funktionen \u00f6ffentlich sind, sonst funktioniert die Datenverbindung nicht (muss von privat konvertiert werden).

      Andere wichtige Modifikationen:

      Pr\u00fcfen Sie, ob die Anwendung nach den \u00c4nderungen genauso funktioniert wie vorher!

      Was haben wir durch die Umstellung unserer bisherigen L\u00f6sung auf eine MVVM-Basis gewonnen? Die Antwort finden Sie in den Vorlesungsmaterial! Ein paar Dinge sind hervorzuheben:

      Je komplexer eine Anwendung ist, desto mehr sind diese wahr.

      EINGABE

      Machen Sie einen Screenshot mit dem Namen f2.png wie folgt:

      "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-3-deaktivierenaktivieren-von-controllern","title":"Aufgabe 3 - Deaktivieren/Aktivieren von Controllern","text":"

      In diesem Stadium verh\u00e4lt sich die Anwendung etwas komisch: Sie k\u00f6nnen die Taste \"-\" verwenden, um ein Alter in den negativen Bereich zu verschieben, oder die Taste \"+\", um es \u00fcber 150 zu verschieben, oder die Taste \"+Add\", um eine Person mit sinnlosen Attributen hinzuzuf\u00fcgen. Diese Tasten sollten deaktiviert werden, wenn die von ihnen ausgel\u00f6ste Aktion keinen Sinn ergibt, und aktiviert werden, wenn sie Sinn hat.

      Im n\u00e4chsten Schritt deaktivieren/aktivieren Sie die Taset \"-\" entsprechend. Die Taste sollte nur aktiviert werden, wenn das Alter der Person gr\u00f6\u00dfer als 0 ist.

      Versuchen Sie, es zuerst selbst zu l\u00f6sen, zumindest um die Grundlagen zu schaffen! Denken Sie unbedingt \u00fcber eine L\u00f6sung mit Datenverbindung, nur diese ist akzeptabel! Wenn Sie nicht weiterkommen kann, Ihre L\u00f6sung nicht funktionieren \"will\", \u00fcberdenken Sie, was der Grund daf\u00fcr sein k\u00f6nnte, und konstruiren Sie Ihre L\u00f6sung wie folgt.

      Es gibt mehrere m\u00f6gliche L\u00f6sungen f\u00fcr dieses Problem. In allen gemeinsam ist, dass die Eigenschaft IsEnabled der Taste \"-\" in irgendeiner Weise gebunden ist. In unserer L\u00f6sung binden wir sie an eine bool-Eigenschaft, die in PersonListPageViewModel neu eingef\u00fchrt wurde.

      PersonListPageViewModel.cs
          public bool IsDecrementEnabled\n    {\n        get { return NewPerson.Age > 0; }\n    }\n
      In PersonListPage.xaml zu der Taste '-'
          IsEnabled=\"{x:Bind ViewModel.IsDecrementEnabled, Mode=OneWay}\"\n

      Probieren wir es aus! Leider funktioniert es nicht, die \"-\"-Taste wird nicht deaktiviert, wenn das Alter auf 0 oder weniger gesetzt wird (z.B. durch wiederholtes Anklicken der Taste). Wenn Sie einen Haltepunkt in IsDecrementEnabled setzen und die Anwendung auf diese Weise starten, werden Sie feststellen, dass der Wert der Eigenschaft nur einmal vom gebundenen Steuerelement abgefragt wird, wenn die Anwendung startet: Danach k\u00f6nnen Sie auf die Taste \"-\" mehrmals klicken, aber es wird nicht mehr als einmal abgefragt. Probieren Sie es aus!

      \u00dcberdenken Sie, was die Ursache daf\u00fcr ist, und lesen Sie erst dann der Leitfaden weiter!

      Begr\u00fcndung

      Wie wir bereits gelernt haben, ruft die Datenverbindung den Wert der Quelleigenschaft (in diesem Fall IsDecrementEnabled) nur ab, wenn sie \u00fcber INotifyPropertyChanged \u00fcber eine \u00c4nderung informiert wird! Aber in unserer L\u00f6sung gibt es jedoch, selbst wenn sich die Eigenschaft Age des Objekts NewPerson \u00e4ndert, keine Benachrichtigung \u00fcber die \u00c4nderung der darauf basierenden Eigenschaft IsDecrementEnabled!

      Im n\u00e4chsten Schritt implementieren Sie die entsprechende \u00c4nderungsmeldung in der Klasse PersonListPageViewModel:

      Testen Sie Ihre L\u00f6sung! Wenn Sie richtig gearbeitet haben, sollte die Taste auch dann deaktiviert sein, wenn Sie manuell einen negativen Alterswert in die Textbox eingeben (und dann aus der Textbox herausklicken). Denken Sie dar\u00fcber nach, warum das so ist!

      Erarbeiten Sie eine \u00e4hnliche L\u00f6sung f\u00fcr die Taste \"+\" und die Taste \"+Add\"!

      Beim Testen haben wir festgestellt, dass sich der Zustand der Taste \"+Add\" nicht sofort \u00e4ndert, wenn wir beispielsweise den Namen in der Textbox \"Name\" l\u00f6schen, sondern erst, wenn wir die Textbox verlassen? Warum ist das so? \u00c4ndern Sie Ihre L\u00f6sung so, dass dies bei jeder Text\u00e4nderung geschieht, ohne die TextBox zu verlassen. Hinweis: siehe die Folie \"x:Bind wann werden die Daten aktualisiert?\" in der Vorlesungsmaterial.

      EINGABE

      Machen Sie einen Screenshot mit dem Namen f3.png wie folgt:

      "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-4-command-verwenden","title":"Aufgabe 4 - Command verwenden","text":"

      Derzeit haben wir zwei Aufgaben f\u00fcr die Taste \"-\":

      Einige Controller, wie z. B. die Taste, unterst\u00fctzen die M\u00f6glichkeit, beide Aufgaben, aufbauend auf dem Command-Muster, mit einem Command-Objekt zu machen. Das Konzept des Command-Entwurfsmusters kann in der Vorlesung \"Design Patterns 3\" ausf\u00fchrlicher behandelt werden (obwohl wir dort nur das grundlegende Command-Muster kennengelernt haben, das die Ausf\u00fchrung von Befehlen unterst\u00fctzt, nicht aber das Verbieten/Erlauben). Die MVVM-spezifische Umsetzung des Command-Patterns finden Sie gegen Ende der WinUI-Vorlesungsreihe, beginnend mit der Folie \"Command-Muster\".

      Das Grundprinzip ist: Anstatt die \"Angaben\" von Click und IsEnabled f\u00fcr die Taste, setzen wir die Eigenschaft Command der Taste auf ein Befehlsobjekt, das die Schnittstelle ICommand implementiert. Es liegt an diesem Befehlsobjekt, den Befehl auszuf\u00fchren oder zu deaktivieren/aktivieren.

      Standardm\u00e4\u00dfig sollte eine Anwendung f\u00fcr jeden Befehl eine eigene ICommand Implementierung haben. Dies erfordert jedoch die Einf\u00fchrung vieler Klassen f\u00fcr viele Befehle. Das MVVM-Toolkit ist hier, um zu helfen. Stellt eine Klasse RelayCommand zur Verf\u00fcgung, die die Schnittstelle ICommand implementiert. Diese Klasse kann zur Ausf\u00fchrung beliebiger Befehle/Codes verwendet werden, so dass keine zus\u00e4tzlichen Befehlsklassen eingef\u00fchrt werden m\u00fcssen. Wie ist das m\u00f6glich? So, dass RelayCommand hat den Code f\u00fcr die Ausf\u00fchrung und deaktivieren/aktivieren in Konstruktor-Parameter, in Form von zwei Delegaten:

      Der n\u00e4chste Schritt besteht darin, die Behandlung der Taste \"-\" auf command basierende umzustellen. Versuchen Sie zuerst, das meiste davon selbst zu implementieren, basierend auf dem zugeh\u00f6rigen WinUI-Vorlesungen. Das Ausf\u00fchren des Befehls ist einfacher, aber Sie m\u00fcssen etwas Arbeit investieren, um den Befehl zu deaktivieren und zu aktivieren. Die wichtigsten Schritte:

      Wenn Sie es ausprobieren, funktioniert die Ausf\u00fchrund des Befehls, aber das Deaktivieren/Aktivieren nicht: Wenn Sie es gut beobachten, bleibt die Taste in ihrem Aussehen immer aktiviert. Es gibt einen logischen Grund daf\u00fcr, wenn man dar\u00fcber nachdenkt: RelayCommand kann die Aktion im zweiten Konstruktorparameter aufrufen, um den Zustand zu \u00fcberpr\u00fcfen, aber es wei\u00df nicht, dass es dies jedes Mal tun sollte, wenn NewPerson.Age sich \u00e4ndert! Wir k\u00f6nnen dabei helfen. In unserem ViewModel-Konstruktor haben wir bereits das NewPerson.PropertyChanged -Ereignis abonniert: Darauf aufbauend rufen wir, wenn sich das Alter \u00e4ndert (oder wenn es sich \u00e4ndern k\u00f6nnte, es ist kein Problem, dies manchmal unn\u00f6tigerweise zu tun), die Method NotifyCanExecuteChanged von DecreaseAgeCommand auf. Diese Operation hat einen sehr aussagekr\u00e4ftigen Namen: Sie teilt dem Befehl mit, dass sich der Zustand, auf dem der verbotene/erlaubte Zustand des Befehls aufgebaut ist, ge\u00e4ndert hat. Auf diese Weise wird der Befehl selbst aktualisiert, genauer gesagt der Zustand der mit dem Befehl verbundenen Taste.

      \u00c4ndern Sie die Behandlung der \"+\"-Taste auf \u00e4hnliche Weise auf Befehlsbasis! \u00c4ndern Sie nicht die Behandlung der Taste \"+Add\"!

      EINGABE

      Machen Sie einen Screenshot mit dem Namen f4.png wie folgt:

      "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-5-verwendung-von-command-mit-mvvm-toolkit-basierter-codegenerierung","title":"Aufgabe 5 - Verwendung von Command mit MVVM Toolkit-basierter Codegenerierung","text":"

      In der vorigen Aufgabe wurde die Einf\u00fchrung von Command-Eigenschaften und deren Instanziierung \"manuell\" gemacht. Das MVVM Toolkit kann dies vereinfachen: Wenn das richtige Attribut verwendet wird, k\u00f6nnen die Eigenschaft und die Instanziierung automatisch generiert werden.

      \u00c4ndern wir die Behandlung von DecreaseAgeCommand (nur dieses, IncreaseAgeCommand soll unver\u00e4ndert bleiben! ) auf eine generierte Codebasis:

      1. Erg\u00e4nzen Sie die Klasse PersonListPageViewModel mit dem Schl\u00fcsselwort partial.
      2. Entfernen Sie die Eigenschaft DecreaseAgeCommand und ihre Instanziierung aus dem Konstruktor.
      3. Erg\u00e4nzen Sie DecreaseAge mit diesem Attribut: [RelayCommand(CanExecute = nameof(IsDecrementEnabled))].
        • Als Ergebnis f\u00fchrt der Codegenerator eine Eigenschaft RelayCommand in die Klasse ein, die mit dem Namen unserer Operation (DecreaseAge) benannt ist und an die die Zeichenfolge \"Command\" angeh\u00e4ngt ist. So erhalten wir die Eigenschaft DecreaseAgeCommand, die wir zuvor manuell eingef\u00fchrt haben.
        • Die Attributeigenschaft CanExecute kann verwendet werden, um in Form einer Zeichenkette den Namen der Operation oder Eigenschaft mit booleschen R\u00fcckgabewert anzugeben, die der generierte Code verwenden wird, wenn er den Befehl verbietet/erlaubt (er ist der zweite Parameter des Konstruktors RelayCommand). Wir haben bereits eine solche Eigenschaft, die \"IsDecrementEnabled\" hei\u00dft. Sie wird nicht als einfache Zeichenkette angegeben, denn wenn jemand die Operation IsDecrementEnabled nachtr\u00e4glich umbenennt, w\u00fcrde die aktuelle \"IsDecrementEnabled\" nicht auf die richtige Operation verweisen. Die Verwendung des Ausdrucks nameof vermeidet dieses Problem. Die Angabe von CanExecute ist im Allgemeinen optional (geben Sie es nicht an, wenn Sie den Befehl niemals deaktivieren wollen).

      Testen Sie die L\u00f6sung (Verkleinerung des Alters), sie sollte genauso funktionieren wie zuvor.

      EINGABE

      Machen Sie einen Screenshot mit dem Namen f5.png wie folgt:

      "},{"location":"hazi/5b-mvvm-advanced/","title":"5b. HF - MVVM mint\u00e1ra \u00e9p\u00fcl\u0151 alkalmaz\u00e1sok (opcion\u00e1lis)","text":""},{"location":"hazi/5b-mvvm-advanced/#bevezetes","title":"Bevezet\u00e9s","text":"

      A h\u00e1zi feladatban a laboron elkezdett recept alkalmaz\u00e1st fogjuk tov\u00e1bb b\u0151v\u00edteni az MVVM mint\u00e1t haszn\u00e1lva.

      Az \u00f6n\u00e1ll\u00f3 feladat az MVVM el\u0151ad\u00e1sokon elhangzottakra \u00e9p\u00edt. A feladatok gyakorlati h\u00e1tter\u00e9\u00fcl a 5. labor \u2013 MVVM laborgyakorlat szolg\u00e1l.

      A fentiekre \u00e9p\u00edtve, jelen \u00f6n\u00e1ll\u00f3 gyakorlat feladatai a feladatle\u00edr\u00e1st k\u00f6vet\u0151 r\u00f6videbb ir\u00e1nymutat\u00e1s seg\u00edts\u00e9g\u00e9vel (n\u00e9ha alap\u00e9rtelmezetten \u00f6sszecsukva) \u00f6n\u00e1ll\u00f3an elv\u00e9gezhet\u0151k.

      Az \u00f6n\u00e1ll\u00f3 gyakorlat c\u00e9lja:

      A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s.

      Fejleszt\u0151k\u00f6rnyezet WinUI3 fejleszt\u00e9shez

      A kor\u00e1bbi laborokhoz hasonl\u00f3an plusz komponensek telep\u00edt\u00e9se sz\u00fcks\u00e9ges. A fenti oldal eml\u00edti, hogy sz\u00fcks\u00e9g van a \".NET desktop development\" Visual Studio Workload telep\u00edt\u00e9s\u00e9re, valamint ugyanitt az oldal alj\u00e1n van egy \"WinUI t\u00e1mogat\u00e1s\" fejezet, az itt megadott l\u00e9p\u00e9seket is mindenk\u00e9ppen meg kell tenni!

      "},{"location":"hazi/5b-mvvm-advanced/#a-beadas-menete","title":"A bead\u00e1s menete","text":""},{"location":"hazi/5b-mvvm-advanced/#kikotesek","title":"Kik\u00f6t\u00e9sek","text":"

      MVVM minta k\u00f6telez\u0151 alkalmaz\u00e1sa! Jelen h\u00e1zi feladatban az MVVM mint\u00e1t gyakoroljuk, \u00edgy a feladatok megold\u00e1s\u00e1ban k\u00f6telez\u0151 az MVVM minta alkalmaz\u00e1sa. Az ett\u0151l val\u00f3 elt\u00e9r\u00e9s a feladatok \u00e9rt\u00e9kel\u00e9s\u00e9nek elutas\u00edt\u00e1s\u00e1t vonja maga ut\u00e1n.

      "},{"location":"hazi/5b-mvvm-advanced/#kiindulo-allapot","title":"Kiindul\u00f3 \u00e1llapot","text":"

      A kiindul\u00f3 \u00e1llapot \u00e9p\u00edt az 5. labor v\u00e9g\u00e1llapot\u00e1ra, de ahhoz k\u00e9pest egy l\u00e9nyeges v\u00e1ltoztat\u00e1st tartalmaz.

      Az alkalmaz\u00e1s az indul\u00e1sa ut\u00e1n l\u00e9trehoz egy ShellPage t\u00edpus\u00fa oldalt, ami a projektben a Views mapp\u00e1ban tal\u00e1lhat\u00f3 meg. Ez egy NavigationView-t tartalmaz (aka. Hamburger men\u00fc), mely a navig\u00e1ci\u00f3t fogja eset\u00fcnkben kezelni. Tartalmazhat NavigationViewItem-eket, melyek a men\u00fcpontokat reprezent\u00e1lj\u00e1k, \u00e9s mindig el\u00e9rhet\u0151ek az alkalmaz\u00e1sban. A men\u00fcpontokra kattintva a Frame-en bel\u00fcl a megfelel\u0151 oldal jelenik meg a projektben tal\u00e1lhat\u00f3 seg\u00e9doszt\u00e1lyok seg\u00edts\u00e9g\u00e9vel, ami t\u00e1mogatja a kor\u00e1bbi oldalra t\u00f6rt\u00e9n\u0151 vissza navig\u00e1ci\u00f3t is.

      "},{"location":"hazi/5b-mvvm-advanced/#1-feladat-receptek-kedvenckent-kezelese","title":"1. Feladat - Receptek kedvenck\u00e9nt kezel\u00e9se","text":"

      Feladatunk funkcion\u00e1lis k\u00f6vetelm\u00e9nyei a k\u00f6vetkez\u0151ek:

      "},{"location":"hazi/5b-mvvm-advanced/#11-kedvencek-kezelese-a-szolgaltatas-retegben","title":"1.1 Kedvencek kezel\u00e9se a szolg\u00e1ltat\u00e1s r\u00e9tegben","text":"

      Bottom-up megval\u00f3s\u00edt\u00e1si sorrendben haladva k\u00e9sz\u00edts\u00fck el el\u0151sz\u00f6r a szolg\u00e1ltat\u00e1s r\u00e9tegben a kedvencek kezel\u00e9s\u00e9hez sz\u00fcks\u00e9ges funkci\u00f3kat.

      A kedvencnek megjel\u00f6l\u00e9st az online szolg\u00e1ltat\u00e1s nem t\u00e1mogatja. A megold\u00e1s alapelve \u00edgy a k\u00f6vetkez\u0151 lesz:

      Lok\u00e1lis perzisztens adatt\u00e1rol\u00e1shoz a kiindul\u00f3 projektben el\u0151 van k\u00e9sz\u00edtve az ILocalSettingsService interf\u00e9sz (\u00e9s egy ezt megval\u00f3s\u00edt\u00f3 implement\u00e1ci\u00f3). Erre \u00e9p\u00edtve kulcs \u00e9rt\u00e9k p\u00e1rokat tudunk JSON soros\u00edtva t\u00e1rolni lok\u00e1lisan az alkalmaz\u00e1sban.

      public interface ILocalSettingsService\n{\n    Task<T> ReadSettingAsync<T>(string key);\n    Task SaveSettingAsync<T>(string key, T value);\n}\n

      Haszn\u00e1lata sor\u00e1n \u00e9rdemes odafigyelni arra, hogy a f\u00fcggv\u00e9nyek generikusak, \u00edgy a t\u00edpusokat explicit meg kell(het) adni a h\u00edv\u00e1s sor\u00e1n.

      A fenti ILocalSettingsService seg\u00edts\u00e9g\u00e9vel egy adott kulcs alatt fogjuk a kedvenc receptek azonos\u00edt\u00f3inak list\u00e1j\u00e1t elt\u00e1rolni.

      Szint\u00e9n fontos, hogy a f\u00fcggv\u00e9nyek Task-kal t\u00e9rnek vissza, teh\u00e1t aszinkronok, \u00edgy await kulcssz\u00f3val kell h\u00edvni \u0151ket, \u00e9s a h\u00edv\u00f3 f\u00fcggv\u00e9nynek is aszinkronnak kell lennie (a r\u00e9szletesebb szab\u00e1lyhalmaz a kapcsol\u00f3d\u00f3 \"5. MVVM\" labor le\u00edr\u00e1s\u00e1ban tal\u00e1lhat\u00f3).

      A kedvencek kezel\u00e9se a labor sor\u00e1n bevezetett IRecipeService interf\u00e9sz \u00e9s az ezt megval\u00f3s\u00edt\u00f3 RecipeService oszt\u00e1ly feladata legyen.

      Els\u0151 l\u00e9p\u00e9sben azt kell megoldani, hogy a RecipeService sz\u00e1m\u00e1ra rendelkez\u00e9sre \u00e1lljon egy ILocalSettingsService interf\u00e9szt megval\u00f3s\u00edt\u00f3 objektum, melyet fel tud haszn\u00e1lni megval\u00f3s\u00edt\u00e1s\u00e1ban a kedvenc receptazonos\u00edt\u00f3k elt\u00e1rol\u00e1s\u00e1ra \u00e9s lek\u00e9rdez\u00e9s\u00e9re. A c\u00e9lunk az, hogy RecipeService-ben ILocalSettingsService interf\u00e9szk\u00e9nt kapjuk meg \u00e9s t\u00e1roljuk ezt az implement\u00e1ci\u00f3s objektumot, semmif\u00e9le f\u00fcgg\u00e9st nem szeretn\u00e9nk itt bevezetni a konkr\u00e9t implement\u00e1ci\u00f3t\u00f3l. Ezt a laboron m\u00e1r alkalmazott DI kont\u00e9ner seg\u00edts\u00e9g\u00e9vel val\u00f3s\u00edtsuk meg.

      Tip

      A megval\u00f3s\u00edt\u00e1s sor\u00e1n a RecipeService-ben ahhoz hasonl\u00f3an kell kezelj\u00fck a ILocalSettingsService-t, mint a ahogy a labor sor\u00e1n a MainPageViewModel-ben kezelt\u00fck a IRecipeService-t.

      Miut\u00e1n a fenti el\u0151k\u00e9sz\u00edt\u00e9ssel elk\u00e9sz\u00fclt\u00e9l, val\u00f3s\u00edtsd meg a sz\u00fcks\u00e9ges funkci\u00f3kat a RecipeService oszt\u00e1lyban! Az al\u00e1bbiakban ehhez n\u00e9mi ir\u00e1nymutat\u00e1st adunk.

      RecipeService v\u00e1za

      A RecipeService-nek (\u00e9s interf\u00e9sznek) a k\u00f6vetkez\u0151 \u00faj funkci\u00f3kkal kell rendelkeznie:

      1. Recept kedvenc \u00e1llapot\u00e1nak m\u00f3dos\u00edt\u00e1sa id (int) alapj\u00e1n az \u00faj \u00e1llapottal (bool). (Recept r\u00e9szletes oldalon gombra kattint\u00e1s sor\u00e1n haszn\u00e1ljuk.)

        1. K\u00e9rdezz\u00fck le az ILocalSettingsService-b\u0151l kedvencek azonos\u00edt\u00f3inak list\u00e1j\u00e1t.
        2. Lista m\u00f3dos\u00edt\u00e1sa a kapott id \u00e9s \u00faj kedvenc \u00e1llapot alapj\u00e1n.
          1. Kedvencnek jel\u00f6l\u00e9s eset\u00e9n, berakjuk, egy\u00e9bk\u00e9nt t\u00f6r\u00f6lj\u00fck.
          2. Gondoljunk arra is, ha a lista m\u00e1r tartalmazza az adott id-t, akkor ne adjuk hozz\u00e1 \u00fajra. (Lista helyett egy\u00e9bk\u00e9nt lehet haszn\u00e1lni egy speci\u00e1lis halmaz tulajdons\u00e1g\u00fa kollekci\u00f3t is, a HashSet<T>-et, mely egy elemet csak egyszer tartalmaz.)
      2. Kedvenc receptek lek\u00e9rdez\u00e9se. (Kedvencek oldalon list\u00e1z\u00e1s sor\u00e1n haszn\u00e1ljuk.)

        1. K\u00e9rdezz\u00fck le az ILocalSettingsService-b\u0151l a kedvenc receptek azonos\u00edt\u00f3inak list\u00e1j\u00e1t.
        2. A kapott id-k alapj\u00e1n egyes\u00e9vel k\u00e9rj\u00fck le a recepteket a REST API-t\u00f3l, a GET /api/Recipes/{id}/Header v\u00e9gponton kereszt\u00fcl. Ez a laborhoz k\u00e9pest egy \u00faj v\u00e9gpont, \u00e9s az adott azonos\u00edt\u00f3j\u00fa recept RecipeHeader-be soros\u00edtott adataival t\u00e9r vissza. Ehhez a v\u00e9gponthoz \u00e9rdemes \u00faj seg\u00e9df\u00fcggv\u00e9nyt is k\u00e9sz\u00edteni. Dolgozhatunk a laboron m\u00e1r bevezetett RecipeService-ben lev\u0151 HttpClient-et haszn\u00e1l\u00f3 m\u0171veletek \"mint\u00e1j\u00e1ra\".
        3. A lek\u00e9rdezett RecipeHeader objektumokb\u00f3l \u00f6ssze\u00e1ll\u00edtott list\u00e1val t\u00e9rj\u00fcnk vissza.
      3. Recept kedvenc \u00e1llapot\u00e1nak lek\u00e9rdez\u00e9se id alapj\u00e1n. (Recept r\u00e9szletes oldal bet\u00f6lt\u00e9sekor a gomb \u00e1llapot\u00e1nak be\u00e1ll\u00edt\u00e1s\u00e1hoz haszn\u00e1ljuk.)

        1. Igaz hamis \u00e9rt\u00e9kkel t\u00e9rj\u00fcnk vissza, att\u00f3l f\u00fcgg\u0151en, hogy az adott azonos\u00edt\u00f3 szerepel-e a kedvenc receptek list\u00e1j\u00e1ban.

      Els\u0151 h\u00edv\u00e1s

      Gondolni kell arra is, ha m\u00e9g most h\u00edvjuk meg el\u0151sz\u00f6r a lek\u00e9rdez\u0151 f\u00fcggv\u00e9nyt, \u00e9s nincs m\u00e9g mentett kedvenc recept azonos\u00edt\u00f3 list\u00e1nk (null-lal t\u00e9r vissza az adott kulcs\u00fa elem lek\u00e9rdez\u00e9sekor az ILocalSettingsService.ReadSettingAsync).

      "},{"location":"hazi/5b-mvvm-advanced/#12-kedvencnek-jeloles-a-reszletes-oldalon","title":"1.2 Kedvencnek jel\u00f6l\u00e9s a r\u00e9szletes oldalon","text":"

      A recept r\u00e9szletes oldalon (a RecipeDetailPage-en) meg kell jelen\u00edteni egy gombot, melynek k\u00e9t \u00e1llapota van:

      1. Ha a recept nincs kedvencnek jel\u00f6lve, akkor egy \u00fcres csillag ikon jelenik meg a gombon, a gomb felirata pedig legyen \"Add to Favorites\".
      2. Ha a recept kedvencnek van jel\u00f6lve, akkor egy kit\u00f6lt\u00f6tt csillag ikon jelenik meg a gombon, a gomb felirata pedig legyen \"Remove from Favorites\".
      Add To FavoritesRemove From Favorites

      Ezt az igaz-hamis \u00e1llapotot \u00e9s m\u00f3dos\u00edt\u00f3 m\u0171veletet c\u00e9lszer\u0171 a RecipeDetailPageViewModel-ban t\u00e1rolni/bevezetni (mivel a ViewModelnek defin\u00edci\u00f3 szerint ez a feladata), majd adatk\u00f6t\u00e9ssel k\u00f6tni az \u00e1llapotot gomb kin\u00e9zet\u00e9hez, illetve a m\u0171veletet commandj\u00e1hoz. Mindenk\u00e9ppen az MVVM mint\u00e1t k\u00f6vetve dolgozzunk!

      RecipeDetailPageViewModel m\u00f3dos\u00edt\u00e1sa

      A RecipeDetailViewModel-t m\u00f3dos\u00edtani sz\u00fcks\u00e9ges a k\u00f6vetkez\u0151kkel:

      1. Kedvenc \u00e1llapot t\u00e1rol\u00e1sa
        1. Az \u00e1llapotot egy bool t\u00edpus\u00fa property-ben t\u00e1roljuk (mindenk\u00e9ppen \u00e9rdemes az [ObservableProperty] attrib\u00fatumot haszn\u00e1lni, m\u0171k\u00f6d\u00e9s\u00e9nek \u00e9s jelent\u0151s\u00e9g\u00e9nek \u00e1tism\u00e9tl\u00e9s\u00e9vel).
        2. Az \u00e1llapotot az IRecipeService-b\u0151l lek\u00e9rdezve inicializ\u00e1ljuk az oldalra val\u00f3 navig\u00e1l\u00e1skor.
        1. Elmenti az \u00faj kedvenc \u00e1llapotot az IRecipeService seg\u00edts\u00e9g\u00e9vel.
        2. Gondoskodik a ViewModel oszt\u00e1lyunkban t\u00e1rolt bool kedvenc \u00e1llapot tulajdons\u00e1g karbantart\u00e1s\u00e1r\u00f3l.

        \u00daj command f\u00fcggv\u00e9ny k\u00e9sz\u00edt\u00e9se, amely

        Tipp a megold\u00e1shoz

        A megold\u00e1s elve hasonl\u00edt a SendComment parancsf\u00fcggv\u00e9nyhez, de itt a CanExecute-tal nem kell foglalkozzunk, hiszen az \u00faj commandunk mindig futtathat\u00f3.

      \u00c1llapot t\u00e1rol\u00e1sa a modellben

      A kedvenc \u00e1llapotot a RecipeHeader modellben is t\u00e1rolhatn\u00e1nk, viszont az k\u00e9t m\u00e1sik probl\u00e9m\u00e1t is gener\u00e1lna: a modellnek kell megval\u00f3s\u00edtania az INotifyPropertyChanged interf\u00e9szt, hogy az \u00e1llapot v\u00e1ltoz\u00e1s\u00e1t jelezni tudja. Ezen fel\u00fcl az \u00faj property \u00e9rt\u00e9k\u00e9t valamelyik m\u00e1sik r\u00e9tegben (ViewModel vagy Service) kellene kit\u00f6lteni, mivel ez az inf\u00f3 csak lok\u00e1lisan \u00e9rhet\u0151 el, a RecipeHeaderpedig alapvet\u0151en most csak egy DTO (Data Transfer Object) a modell r\u00e9tegben.

      RecipeDetailPage (vagyis a View) m\u00f3dos\u00edt\u00e1sa

      A RecipeDetailPage-en a k\u00f6vetkez\u0151ket kell m\u00f3dos\u00edtani:

      1. \u00daj gomb hozz\u00e1ad\u00e1sa az oldal tetej\u00e9re, tartalma legyen egy SymbolIcon \u00e9s egy TextBlock egym\u00e1s mellett.
        1. A SymbolIcon-nak a Symbol tulajdons\u00e1g\u00e1hoz haszn\u00e1ljuk a Symbol.SolidStar \u00e9s Symbol.OutlineStar enum \u00e9rt\u00e9keket a csillag ikonokhoz.
      2. A gomb commandj\u00e1t adatk\u00f6tni kell a ViewModel-ben tal\u00e1lhat\u00f3 command-hoz.

      A ViewModel-ben t\u00e1rolt bool \u00e9rt\u00e9ket valamilyen m\u00f3don Symbol enumra (gomb ikonja) \u00e9s string-re (gomb aktu\u00e1lis sz\u00f6vege) kell konvert\u00e1lni, hogy a fel\u00fcleten a gomb megjelen\u00e9se mindk\u00e9t \u00e1llapotban a megfelel\u0151 legyen. Erre t\u00f6bb megold\u00e1s is lehets\u00e9ges:

      1.2. feladat BEADAND\u00d3

      Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol a teend\u0151 r\u00e9szletes oldalon megjelenik a kedvencnek jel\u00f6l\u00e9s gomb! (f1.2.1.png)

      Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol a teend\u0151 r\u00e9szletes oldalon egy m\u00e1r kedvencnek jel\u00f6lt recepthez a kedvencekb\u0151l elt\u00e1vol\u00edt\u00e1s gomb jelenik meg! (f1.2.2.png)

      "},{"location":"hazi/5b-mvvm-advanced/#13-kedvencek-oldal-navigacio","title":"1.3 Kedvencek oldal navig\u00e1ci\u00f3","text":"

      A kedvencek oldalra navig\u00e1l\u00e1shoz t\u00f6bb l\u00e9p\u00e9sre is sz\u00fcks\u00e9g\u00fcnk lesz, melyek a kiindul\u00f3 projekt saj\u00e1toss\u00e1gaib\u00f3l ad\u00f3d\u00f3dnak, de ezeket itt r\u00e9szletesen \u00e1tvessz\u00fck (a navig\u00e1ci\u00f3 megval\u00f3s\u00edt\u00e1sa nem r\u00e9sze a tanagyagnak).

      1. Hozzuk l\u00e9tre a FavoritesPage-et a Views mapp\u00e1ban (Add/New Item/Blank Page (WinUI3))

        Ford\u00edt\u00e1si hib\u00e1k

        Ha valami\u00e9rt egzotikus hib\u00e1kat kapn\u00e1nk az \u00faj oldal felv\u00e9tele ut\u00e1n t\u00f6r\u00f6lj\u00fck ki a projekt f\u00e1jlb\u00f3l az al\u00e1bbi sorokat:

        <ItemGroup>\n    <None Remove=\"Views\\FavoritesPage.xaml\" />\n</ItemGroup>\n
        <Page Update=\"Views\\FavoritesPage.xaml\">\n    <Generator>MSBuild:Compile</Generator>\n</Page>\n
      2. Hozzuk l\u00e9tre a FavoritesPageViewModel oszt\u00e1lyt a ViewModels mapp\u00e1ban

        1. Gondoskodjunk arr\u00f3l, hogy a megfelel\u0151 oszt\u00e1lyb\u00f3l sz\u00e1rmazzon!
        2. Val\u00f3s\u00edtsa meg az INavigationAware interf\u00e9szt a navig\u00e1ci\u00f3 t\u00e1mogat\u00e1s\u00e1hoz (egyel\u0151re \u00fcres f\u00fcggv\u00e9nyt\u00f6rzzsel).
      3. Regisztr\u00e1ljuk be az App.xaml.cs-ben a Dependency Injection kont\u00e9nerbe az \u00faj n\u00e9zetet \u00e9s az \u00faj ViewModelt:

        services.AddTransient<FavoritesPage>();\nservices.AddTransient<FavoritesPageViewModel>();\n
      4. A Pages oszt\u00e1lyban (PageService.cs) vegy\u00fcnk fel egy \u00faj kulcsot a kedvencek oldalhoz, \u00e9s konfigur\u00e1ljuk a navig\u00e1ci\u00f3t ehhez a kulcshoz:

        Pages
        public static string Favorites { get; } = \"Favorites\";\n
        PageService konstruktor
        Configure<FavoritesPageViewModel, FavoritesPage>(Pages.Favorites);\n
      5. A ShellPage-en a NavigationView-hoz adjunk hozz\u00e1 egy \u00faj NavigationViewItem-et a kedvencek oldalhoz:

        <NavigationViewItem helpers:NavigationHelper.NavigateTo=\"Favorites\" Content=\"Favorites\">\n    <NavigationViewItem.Icon>\n        <SymbolIcon Symbol=\"SolidStar\" />\n    </NavigationViewItem.Icon>\n</NavigationViewItem>\n

        Navig\u00e1ci\u00f3

        A navig\u00e1ci\u00f3 a helpers:NavigationHelper.NavigateTo=\"Favorites\" attached property seg\u00edts\u00e9g\u00e9vel t\u00f6rt\u00e9nik, ahol azt a kulcsot adhatjuk meg, amilyen kulcs\u00fa oldalra navig\u00e1lni szeretn\u00e9nk.

      "},{"location":"hazi/5b-mvvm-advanced/#14-kedvencek-oldal-logika","title":"1.4 Kedvencek oldal logika","text":"

      A kedvencek oldal (FavoritesPage) a MainPage mint\u00e1j\u00e1ra k\u00e9sz\u00fclj\u00f6n el, \u00e9s a receptek list\u00e1j\u00e1t jelen\u00edtse meg, csoportos\u00edt\u00e1s n\u00e9lk\u00fcl (!) egy AdaptiveGridView vez\u00e9rl\u0151ben.

      A ViewModel (FavoritesPageViewModel) a MainPageViewModel mint\u00e1j\u00e1ra k\u00e9sz\u00fclj\u00f6n el, \u00e9s a navig\u00e1ci\u00f3 sor\u00e1n k\u00e9rdezze le az IRecipeService-t\u0151l a kedvenc receptek list\u00e1j\u00e1t (GetFavoriteRecipesAsync) \u00e9s t\u00e1rolja el egy megfelel\u0151, pl. gener\u00e1lt tulajdons\u00e1gba. Mivel itt nem csoportos\u00edtjuk a recepteket, RecipeGroup-ok helyett RecipeHeader-ekkel kell dolgozni.

      1.4. feladat BEADAND\u00d3

      Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol kedvencek lista l\u00e1that\u00f3! (f1.4.png)

      "},{"location":"hazi/5b-mvvm-advanced/#beadas","title":"Bead\u00e1s","text":"

      Ellen\u0151rz\u0151lista ism\u00e9tl\u00e9sk\u00e9ppen:

      "},{"location":"hazi/5b-mvvm-advanced/index_ger/","title":"5. HF - Anwendungen auf der Grundlage des MVVM-Modells","text":""},{"location":"hazi/5b-mvvm-advanced/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

      Als Hausaufgabe werden wir die in der \u00dcbung begonnene Rezeptanwendung mit Hilfe der MVVM-Vorlage erweitern.

      Die eigenst\u00e4ndige \u00dcbung baut auf dem auf, was in den MVVM-Vorlesungen gesagt wurde. Den praktischen Hintergrund f\u00fcr die \u00dcbungen liefert die Labor\u00fcbung 5 - MVVM-Labor\u00fcbung.

      Darauf aufbauend k\u00f6nnen die Aufgaben dieser Selbst\u00fcbung mit Hilfe der k\u00fcrzeren Leitf\u00e4den, die auf die Aufgabenbeschreibung folgen (manchmal standardm\u00e4\u00dfig eingeklappt), selbst\u00e4ndig bearbeitet werden.

      Das Ziel der unabh\u00e4ngigen \u00dcbung:

      Die erforderliche Entwicklungsumgebung wird hier beschrieben.

      Entwicklungsumgebung f\u00fcr WinUI3-Entwicklung

      Wie in den vorherigen \u00dcbungen m\u00fcssen zus\u00e4tzliche Komponenten installiert werden. Auf der obigen Seite wird erw\u00e4hnt, dass Sie Visual Studio Workload f\u00fcr die \".NET-Desktop-Entwicklung\" installieren m\u00fcssen, und es gibt einen Abschnitt \"WinUI-Unterst\u00fctzung\" am unteren Ende der Seite, Sie sollten den Schritten dort folgen!

      "},{"location":"hazi/5b-mvvm-advanced/index_ger/#das-verfahren-fur-die-einreichung","title":"Das Verfahren f\u00fcr die Einreichung","text":""},{"location":"hazi/5b-mvvm-advanced/index_ger/#verbindungen","title":"Verbindungen","text":"

      Obligatorische Verwendung des MVVM-Beispiels! In dieser Hausaufgabe \u00fcben wir das MVVM-Pattern, daher ist das MVVM-Pattern f\u00fcr die L\u00f6sung der Aufgaben zwingend erforderlich. Andernfalls wird die Bewertung der Aufgaben verweigert.

      "},{"location":"hazi/5b-mvvm-advanced/index_ger/#ausgangszustand","title":"Ausgangszustand","text":"

      Der Ausgangszustand baut auf dem Endzustand von Labor 5 auf, allerdings mit einer wichtigen \u00c4nderung.

      Wenn die Anwendung gestartet wird, wird eine Seite des Typs ShellPage erstellt, die sich im Ordner Views des Projekts befindet. Es enth\u00e4lt eine NavigationView(aka. Hamburger Men\u00fc), das in unserem Fall die Navigation \u00fcbernimmt. Sie kann NavigationViewItementhalten, die Men\u00fcpunkte darstellen und in der Anwendung immer verf\u00fcgbar sind. Wenn Sie auf die Men\u00fcpunkte innerhalb von Frameklicken, wird die entsprechende Seite mit Hilfe der Hilfsklassen im Projekt aufgerufen, die auch die Navigation zur\u00fcck zur vorherigen Seite unterst\u00fctzt.

      "},{"location":"hazi/5b-mvvm-advanced/index_ger/#1-aufgabe-rezepte-als-favoriten-verwalten","title":"1. Aufgabe - Rezepte als Favoriten verwalten","text":"

      Die funktionalen Anforderungen an unsere Aufgabe sind:

      "},{"location":"hazi/5b-mvvm-advanced/index_ger/#11-verwaltung-von-favoriten-in-der-dienstebene","title":"1.1 Verwaltung von Favoriten in der Dienstebene","text":"

      In einer Bottom-up-Implementierungsreihenfolge erstellen wir zun\u00e4chst die Funktionen, die f\u00fcr die Verwaltung der Favoriten in der Dienstschicht erforderlich sind.

      Favoriten werden vom Online-Dienst nicht unterst\u00fctzt. Das Grundprinzip der L\u00f6sung lautet also:

      F\u00fcr die lokale persistente Datenspeicherung wird die Schnittstelle ILocalSettingsService (und eine Implementierung) im urspr\u00fcnglichen Projekt vorbereitet. Darauf aufbauend k\u00f6nnen wir nach JSON sortierte Schl\u00fcssel-Wert-Paare lokal in der Anwendung speichern.

      public interface ILocalSettingsService\n{\n    Task<T> ReadSettingAsync<T>(string key);\n    Task SaveSettingAsync<T>(string key, T value);\n}\n

      Bei der Verwendung ist zu beachten, dass die Funktionen generisch sind, so dass die Typen beim Aufruf explizit angegeben werden m\u00fcssen.

      Mit Hilfe der obigen ILocalSettingsService speichern wir eine Liste der bevorzugten Rezept-IDs unter einem bestimmten Schl\u00fcssel.

      Wichtig ist auch, dass die Funktionen Taskzur\u00fcckgeben, also asynchron sind. Sie m\u00fcssen also mit dem Schl\u00fcsselwort await aufgerufen werden, und die aufrufende Funktion muss ebenfalls asynchron sein (f\u00fcr einen detaillierteren Satz von Regeln siehe den zugeh\u00f6rigen Abschnitt \"5. MVVM\" Laborbeschreibung).

      Die Verwaltung der Favoriten sollte in der Verantwortung der Schnittstelle IRecipeService und der Klasse RecipeService liegen, die sie implementiert.

      Der erste Schritt besteht darin, RecipeService ein Objekt zur Verf\u00fcgung zu stellen, das die Schnittstelle ILocalSettingsService implementiert, die es in seiner Implementierung verwenden kann, um seine bevorzugten Rezeptbezeichnungen zu speichern und abzurufen. Unser Ziel ist es, dieses Implementierungsobjekt in RecipeServiceals Schnittstelle zu ILocalSettingsService zu erhalten und zu speichern, wir wollen hier keine Abh\u00e4ngigkeiten von der spezifischen Implementierung einf\u00fchren. Dazu wird der bereits im Labor verwendete DI-Beh\u00e4lter verwendet.

      Tip

      Bei der Umsetzung sollten wir ILocalSettingsServicein RecipeServicegenauso behandeln, wie wir IRecipeServicein MainPageViewModelim Labor behandelt haben.

      Nachdem Sie die obigen Vorbereitungen getroffen haben, implementieren Sie die notwendige Funktionalit\u00e4t in der Klasse RecipeService! Hier finden Sie einige Hinweise dazu.

      RezeptService vase

      Der RecipeService (und die Schnittstelle) sollten die folgenden neuen Eigenschaften haben:

      1. \u00c4ndern Sie den Status des Rezeptfavoriten basierend auf id (int) mit dem neuen Status (bool). (Rezeptdetailseite, die beim Anklicken der Schaltfl\u00e4che angezeigt wird)

        1. Abfrage von ILocalSettingsService nach einer Liste von Favoriten-IDs.
        2. \u00c4ndern Sie die Liste anhand der erhaltenen ID und des neuen Favoritenstatus.
          1. Wenn Sie es als Favorit markieren, wird es hinzugef\u00fcgt, andernfalls wird es gel\u00f6scht.
          2. Wenn die Liste die ID bereits enth\u00e4lt, f\u00fcgen Sie sie nicht erneut hinzu. (Anstelle einer Liste k\u00f6nnen Sie auch eine spezielle Sammlung mit Mengeneigenschaften verwenden, HashSet<T>, die ein Element nur einmal enth\u00e4lt)
      2. Fragen Sie Ihre Lieblingsrezepte ab. (Wird f\u00fcr die Auflistung auf der Seite Favoriten verwendet.)

        1. Abfrage von ILocalSettingsService nach der Liste der IDs Ihrer Lieblingsrezepte.
        2. Auf der Grundlage der empfangenen IDs rufen wir die Rezepte einzeln von der REST-API ab, und zwar \u00fcber den Endpunkt \"GET /api/Recipes/{id}/Header\". Dies ist ein neuer Endpunkt in Bezug auf das Labor und gibt die Rezeptdaten mit der angegebenen ID zur\u00fcck, sortiert nach dem RecipeHeader. F\u00fcr diesen Endpunkt lohnt es sich auch, eine neue Hilfsfunktion zu erstellen. Wir k\u00f6nnen ein \"Muster\" von Operationen mit HttpClient in RecipeService erarbeiten, das bereits im Labor implementiert wurde.
        3. R\u00fcckgabe mit einer Liste der abgerufenen RecipeHeader-Objekte.
      3. Abfrage des Favoritenstatus eines Rezepts anhand der ID. (Dient zum Einstellen des Schaltfl\u00e4chenstatus beim Laden einer Rezeptdetailseite)

        1. R\u00fcckgabe mit einem true/false-Wert, je nachdem, ob die Kennung in der Liste der Lieblingsrezepte enthalten ist.

      Erster Anruf

      Sie sollten auch bedenken, wenn Sie die Abfragefunktion zum ersten Mal aufrufen und keine ID-Liste der Lieblingsrezepte gespeichert haben (null wird zur\u00fcckgegeben, wenn ILocalSettingsService.ReadSettingAsync f\u00fcr das angegebene Schl\u00fcsselelement aufgerufen wird).

      "},{"location":"hazi/5b-mvvm-advanced/index_ger/#12-auf-der-detailseite-als-favorit-markieren","title":"1.2 Auf der Detailseite als Favorit markieren","text":"

      Auf der Rezeptseite (unter RecipeDetailPage) sollten Sie eine Schaltfl\u00e4che mit zwei Zust\u00e4nden sehen:

      1. Wenn das Rezept nicht als Favorit markiert ist, erscheint ein leeres Sternsymbol auf der Schaltfl\u00e4che und die Schaltfl\u00e4che ist mit \"Zu Favoriten hinzuf\u00fcgen\" beschriftet.
      2. Wenn das Rezept als Favorit markiert ist, erscheint ein ausgef\u00fclltes Sternsymbol auf der Schaltfl\u00e4che und die Schaltfl\u00e4che ist mit \"Aus Favoriten entfernen\" beschriftet.

      === \"Zu Favoriten hinzuf\u00fcgen\"

      === \"Aus Favoriten entfernen\"

      Dieser true/false-Zustand und die \u00e4ndernde Aktion sollten in RecipeDetailPageViewModelgespeichert/implementiert werden (da dies per Definition die Aufgabe des ViewModels ist) und dann mit dem Zustand der Schaltfl\u00e4che und dem Befehl der Aktion datengebunden werden. Achten Sie darauf, das MVVM-Modell zu befolgen!

      RecipeDetailPageViewModel modification

      Das RecipeDetailViewModel sollte wie folgt ge\u00e4ndert werden:

      1. Favoritenstatus speichern
        1. Der Zustand wird in einer Eigenschaft vom Typ bool gespeichert (verwenden Sie unbedingt das Attribut [ObservableProperty] und wiederholen Sie dessen Funktion und Bedeutung).
        2. Der Status wird initialisiert, indem er beim Aufrufen der Seite von \"IRecipeService\" abgerufen wird.
        1. Speichert den neuen Favoritenstatus unter Verwendung von IRecipeService.
        2. K\u00fcmmert sich um die Pflege der in unserer ViewModel-Klasse gespeicherten Eigenschaft \"bool\".

        Erstellen Sie eine neue Befehlsfunktion, die

        Tipp f\u00fcr die L\u00f6sung

        Das Prinzip ist \u00e4hnlich wie bei der Befehlsfunktion SendComment, aber hier m\u00fcssen wir uns nicht mit CanExecute befassen, da unser neuer Befehl immer ausf\u00fchrbar ist.

      Speichern eines Zustands im Modell

      Der Status der Favoriten k\u00f6nnte im Modell \"RecipeHeader\" gespeichert werden, aber das w\u00fcrde zu zwei weiteren Problemen f\u00fchren: Das Modell m\u00fcsste die Schnittstelle \"INotifyPropertyChanged\" implementieren, um eine Status\u00e4nderung anzuzeigen. Dar\u00fcber hinaus sollte der Wert der neuen Eigenschaft in einer anderen Schicht (ViewModel oder Service) gef\u00fcllt werden, da diese Information nur lokal verf\u00fcgbar ist und der \"RecipeHeader\" im Grunde nur noch ein DTO (Data Transfer Object) in der Modellschicht ist.

      RecipeDetailPage (d.h. die Ansicht) \u00e4ndern

      Auf der \"RecipeDetailPage\" sollte folgendes ge\u00e4ndert werden:

      1. F\u00fcgen Sie oben auf der Seite eine neue Schaltfl\u00e4che hinzu, mit einem \"SymbolSymbol\" und einem \"Textblock\" nebeneinander.
        1. F\u00fcr die Eigenschaft SymbolIcon von Symbol sind die Enum-Werte Symbol.SolidStar und Symbol.OutlineStar f\u00fcr die Sternsymbole zu verwenden.
      2. Der Schaltfl\u00e4chenbefehl muss mit dem Befehl im ViewModel datengebunden sein.

      Der im ViewModel gespeicherte \"bool\"-Wert muss auf irgendeine Weise in ein \"Symbol\"-Enum (Schaltfl\u00e4chensymbol) und einen \"String\" (tats\u00e4chlicher Schaltfl\u00e4chentext) umgewandelt werden, so dass die Schaltfl\u00e4che in beiden Zust\u00e4nden auf der Oberfl\u00e4che erscheint. Es gibt mehrere m\u00f6gliche L\u00f6sungen:

      Aufgabe 1.2. einzureichen

      F\u00fcgen Sie einen Screenshot des Antrags ein, auf dem Sie eine Schaltfl\u00e4che zum Markieren als Favorit auf der Detailseite sehen (f1.2.1.png)

      F\u00fcgen Sie einen Screenshot der App ein, auf der die Schaltfl\u00e4che \"Aus Favoriten entfernen\" auf der Detailseite eines bereits als Favorit markierten Rezepts erscheint (f1.2.2.png)

      "},{"location":"hazi/5b-mvvm-advanced/index_ger/#13-navigation-der-favoritenseite","title":"1.3 Navigation der Favoritenseite","text":"

      Um zur Favoritenseite zu navigieren, sind mehrere Schritte erforderlich, die f\u00fcr das urspr\u00fcngliche Projekt spezifisch sind, aber wir werden sie hier im Detail erl\u00e4utern (die Implementierung der Navigation ist nicht Teil des Tutorials).

      1. Erstellen Sie FavoritesPageim Ordner Views (Add/New Item/Blank Page (WinUI3))

        \u00dcbersetzungsfehler

        Wenn Sie aus irgendeinem Grund exotische Fehler erhalten, nachdem Sie eine neue Seite hinzugef\u00fcgt haben, l\u00f6schen Sie die folgenden Zeilen in der Projektdatei:

        <ItemGroup>\n    <Keine Remove=\"ViewsFavoritesPage.xaml\" />\n</EinzelteilGruppe>\n
        <Seite Update=\"ViewsFavoritesPage.xaml\">\n    <Generator>MSBuild:Compile</Generator>\n</Seite>\n
      2. Erstellen Sie die Klasse FavoritesPageViewModel im Ordner ViewModels

        1. Achten Sie darauf, dass es aus der richtigen Klasse kommt!
        2. Konfigurieren Sie die Schnittstelle INavigationAware so, dass sie die Navigation unterst\u00fctzt (vorerst mit einer leeren Funktionstaste).
      3. Registrieren Sie den neuen View und das neue ViewModel im Dependency Injection Container in App.xaml.cs:

        services.AddTransient<FavoritesPage>();\nservices.AddTransient<FavoritesPageViewModel>();\n
      4. F\u00fcgen Sie in der Klasse Pages (PageService.cs) einen neuen Schl\u00fcssel f\u00fcr die Favoritenseite hinzu und konfigurieren Sie die Navigation zu diesem Schl\u00fcssel:

        Pages
        public static string Favorites { get; } = \"Favorites\";\n
        PageService konstruktor
        Configure<FavoritesPageViewModel, FavoritesPage>(Pages.Favorites);\n
      5. F\u00fcgen Sie unter ShellPageeine neue NavigationViewItembis NavigationViewf\u00fcr die Favoritenseite hinzu:

        <NavigationViewItem helpers:NavigationHelper.NavigateTo=\"Favorites\" Content=\"Favorites\">\n    <NavigationViewItem.Icon>\n        <SymbolIcon Symbol=\"SolidStar\" />\n    </NavigationViewItem.Icon>\n</NavigationViewItem>\n

        Navigation

        Die Navigation erfolgt \u00fcber die angeh\u00e4ngte Eigenschaft helpers:NavigationHelper.NavigateTo=\"Favorites\", in der Sie den Schl\u00fcssel angeben k\u00f6nnen, um zu der Seite mit dem Schl\u00fcssel zu navigieren, zu dem Sie navigieren m\u00f6chten.

      "},{"location":"hazi/5b-mvvm-advanced/index_ger/#14-logik-der-favoritenseite","title":"1.4 Logik der Favoritenseite","text":"

      Die Favoritenseite (FavoritesPage) sollte nach dem Vorbild von MainPage gestaltet werden und die Liste der Rezepte ohne Gruppierung (!) in einem AdaptiveGridView Steuerelement anzeigen.

      Erstellen Sie ein ViewModel (FavoritesPageViewModel) basierend auf MainPageViewModel und rufen Sie die Liste der Lieblingsrezepte ( IRecipeService) w\u00e4hrend der Navigation (GetFavoriteRecipesAsync) von ab und speichern Sie sie in einer geeigneten Eigenschaft, z.B. generated. Da wir die Rezepte hier nicht gruppieren, m\u00fcssen Sie mit RecipeHeaderstatt mit RecipeGrouparbeiten.

      1.4. exercise REQUIRED

      Einf\u00fcgen eines Screenshots der Anwendung mit einer Liste von Favoriten (f1.4.png)

      "},{"location":"hazi/5b-mvvm-advanced/index_ger/#vorlegen-bei","title":"Vorlegen bei","text":"

      Checkliste f\u00fcr Wiederholungen:

      "},{"location":"hazi/6-tervezesi-mintak/","title":"6. HF - Tervez\u00e9si mint\u00e1k (kiterjeszthet\u0151s\u00e9g)","text":"

      A h\u00e1zi feladatban a kapcsol\u00f3d\u00f3 laboron (6. labor \u2013 Tervez\u00e9si mint\u00e1k (kiterjeszthet\u0151s\u00e9g)) elkezdett adatfeldolgoz\u00f3/anonimiz\u00e1l\u00f3 alkalmaz\u00e1st fogjuk tov\u00e1bbfejleszteni.

      Az \u00f6n\u00e1ll\u00f3 feladat az tervez\u00e9si mint\u00e1k el\u0151ad\u00e1sokon elhangzottakra \u00e9p\u00edt: - \"El\u0151ad\u00e1s 08 - Tervez\u00e9si mint\u00e1k 1\" el\u0151ad\u00e1s: \"B\u0151v\u00edthet\u0151s\u00e9ghez, kiterjeszthet\u0151s\u00e9ghez kapcsol\u00f3d\u00f3 alap tervez\u00e9si mint\u00e1k\" nagyfejezet: bevezet\u0151 p\u00e9lda, Template Method, Strategy, Open/Closed elv, SRP elv, egy\u00e9b technik\u00e1k (met\u00f3dusreferencia/lambda) - \"El\u0151ad\u00e1s 09 - Tervez\u00e9si mint\u00e1k 1\" el\u0151ad\u00e1s: Dependency Injection minta

      A feladatok gyakorlati h\u00e1tter\u00e9\u00fcl a 6. labor \u2013 Tervez\u00e9si mint\u00e1k (kiterjeszthet\u0151s\u00e9g) laborgyakorlat szolg\u00e1l.

      Az \u00f6n\u00e1ll\u00f3 gyakorlat c\u00e9lja:

      A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s. Enn\u00e9l a h\u00e1zi feladatn\u00e1l nincs sz\u00fcks\u00e9g WinUI-ra (egy konzol alap\u00fa alkalmaz\u00e1s kontextus\u00e1ban kell dolgozni), \u00edgy pl. Linux/MacOS k\u00f6rnyezetben is elv\u00e9gezhet\u0151.

      "},{"location":"hazi/6-tervezesi-mintak/#a-beadas-menete","title":"A bead\u00e1s menete","text":""},{"location":"hazi/6-tervezesi-mintak/#1-feladat","title":"1. Feladat","text":"

      A h\u00e1zi feladat megold\u00e1s\u00e1nak alapja a k\u00f6vetkez\u0151:

      A h\u00e1zi feladat kiindul\u00f3 \u00e1llapota megfelel a 6. labor v\u00e9g\u00e1llapot\u00e1nak: ez a h\u00e1zi feladat solutionj\u00e9ben a \"Strategy-DI\" projekt. Futtat\u00e1shoz/debuggol\u00e1shoz be kell \u00e1ll\u00edtani, hogy ez legyen a startup projekt (jobb katt, \"Set as Startup Project\"). Ennek forr\u00e1sk\u00f3dj\u00e1t alaposan n\u00e9zd \u00e1t \u00e9s \u00e9rtsd meg.

      Dependency Injection (manu\u00e1lis) vs. Dependency Injection Container

      A labor sor\u00e1n, \u00e9s jelen h\u00e1zi feladatban a Dependency Injection egyszer\u0171, manu\u00e1lis v\u00e1ltozat\u00e1t haszn\u00e1ljuk (el\u0151ad\u00e1son is ez szerepel). Ez esetben az oszt\u00e1ly f\u00fcgg\u0151s\u00e9geit manu\u00e1lisan p\u00e9ld\u00e1nyos\u00edtjuk \u00e9s adjuk \u00e1t az oszt\u00e1ly konstruktor\u00e1ban. Alternat\u00edv \u00e9s komplexebb alkalmaz\u00e1sok eset\u00e9ben gyakran haszn\u00e1lt alternat\u00edva egy Dependency Injection Container alkalmaz\u00e1sa, melybe beregisztr\u00e1lhatjuk, hogy az egyes interf\u00e9sz t\u00edpusokhoz milyen implement\u00e1ci\u00f3t k\u00edv\u00e1nunk haszn\u00e1lni. Az MVVM labor sor\u00e1n \"mell\u00e9kesen\" haszn\u00e1ltuk ezt a technik\u00e1t, de a DI kont\u00e9nerek alkalmaz\u00e1sa nem tananyag. A manu\u00e1lis v\u00e1ltozata viszont az, \u00e9s kiemelt fontoss\u00e1g\u00fa, hiszen en\u00e9lk\u00fcl nincs \u00e9rtelme a Strategy minta alkalmaz\u00e1s\u00e1nak.

      Saj\u00e1t szavaiddal megfogalmazva adj r\u00f6vid v\u00e1laszt a Feladatok mapp\u00e1ban tal\u00e1lthat\u00f3 readme.md f\u00e1jlban az al\u00e1bbi k\u00e9rd\u00e9sekre:

      "},{"location":"hazi/6-tervezesi-mintak/#2-feladat-null-strategy","title":"2. Feladat - Null Strategy","text":"

      Az Anonymizer konstruktor param\u00e9tereit megvizsg\u00e1lva azt l\u00e1tjuk, hogy progress strat\u00e9gi\u00e1nak null is megadhat\u00f3. Ez logikus, hiszen lehet, hogy az Anonymizer felhaszn\u00e1l\u00f3ja nem k\u00edv\u00e1ncsi semmif\u00e9le progress inform\u00e1ci\u00f3ra. Ennek a megk\u00f6zel\u00edt\u00e9snek van egy h\u00e1tr\u00e1nya is. Ez esetben az oszt\u00e1lyban a _progress tagv\u00e1ltoz\u00f3 null lesz, \u00e9s \u00edgy az alkalmaz\u00e1sa sor\u00e1n sz\u00fcks\u00e9g van a null vizsg\u00e1latra. Ellen\u0151rizz\u00fck, hogy a _progess haszn\u00e1latakor val\u00f3ban van null vizsg\u00e1lat a ?. oper\u00e1tor alkalmaz\u00e1s\u00e1val. De ez egy vesz\u00e9lyes j\u00e1t\u00e9k, mert komplexebb esetben hacsak egyetlen helyen is lefelejt\u0151dik a null vizsg\u00e1lat, akkor fut\u00e1s k\u00f6zben NullReferenceException-t kapunk. Az ehhez hasonl\u00f3 null hivatkoz\u00e1s hib\u00e1k a leggyakoribbak k\u00f6z\u00e9 tartoznak.

      Feladat: Dolgozz ki egy olyan megold\u00e1st, mely a fent v\u00e1zolt hibalehet\u0151s\u00e9get kiz\u00e1rja. Tipp: olyan megold\u00e1sra van sz\u00fcks\u00e9g, melyn\u00e9l a _progress tag soha nem lehet null. A megold\u00e1sra el\u0151sz\u00f6r magadt\u00f3l pr\u00f3b\u00e1lj r\u00e1j\u00f6nni.

      Megold\u00e1s alapelve

      A megold\u00e1s \"tr\u00fckkje\" a k\u00f6vetkez\u0151. Egy olyan IProgress strategy implement\u00e1ci\u00f3t kell k\u00e9sz\u00edteni (pl. NullProgress n\u00e9ven), melyet akkor haszn\u00e1lunk, amikor nincs sz\u00fcks\u00e9g progress inform\u00e1ci\u00f3ra. Ez az implement\u00e1ci\u00f3 a progress \"sor\u00e1n\" nem csin\u00e1l semmit, a f\u00fcggv\u00e9ny t\u00f6rzse \u00fcres. Amikor az Anonymizer konstruktor\u00e1ban null-t ad meg az oszt\u00e1ly p\u00e9ld\u00e1nyos\u00edt\u00f3ja progressk\u00e9nt, akkor egy NullProgress objektumot hozzunk l\u00e9tre a konstruktorban, \u00e9s a _progress tagot \u00e1ll\u00edtsuk erre. Most m\u00e1r a _progress soha nem lehet null, a null vizsg\u00e1latot vegy\u00fck is ki a k\u00f3db\u00f3l.

      Ennek a technik\u00e1nak is van neve, Null Object n\u00e9ven szok\u00e1s r\u00e1 hivatkozni.

      "},{"location":"hazi/6-tervezesi-mintak/#3-feladat-tesztelhetoseg","title":"3. Feladat - Tesztelhet\u0151s\u00e9g","text":"

      Vegy\u00fck \u00e9szre, hogy az Anonymizer oszt\u00e1ly m\u0171k\u00f6d\u00e9s\u00e9nek van m\u00e9g sz\u00e1mos aspektusa, melyeket valamelyik megold\u00e1sunkkal kiterjeszthet\u0151v\u00e9 lehetne tenni. T\u00f6bbek k\u00f6z\u00f6tt ilyen a:

      Ezeket az SRP elve miatt illene az oszt\u00e1lyr\u00f3l lev\u00e1lasztani, m\u00e1s oszt\u00e1lyba tenni (ism\u00e9teld \u00e1t, mit jelent az SRP elv). A lev\u00e1laszt\u00e1st nem felt\u00e9telen kiterjeszthet\u0151 m\u00f3don kellene megtenni, hiszen nem mer\u00fclt fel ig\u00e9ny arra, hogy k\u00fcl\u00f6nb\u00f6z\u0151 bemenetekkel \u00e9s kimenetekkel kellene tudni dolgozni. \u00cdgy a lev\u00e1laszt\u00e1s sor\u00e1n nem alkalmazn\u00e1nk a Strategy mint\u00e1t.

      Ugyanakkor van m\u00e9g egy kritikus szempont, melyr\u0151l nem besz\u00e9lt\u00fcnk (\u00e9s a r\u00e9gebbi, klasszikus design pattern irodalmak sem felt\u00e9tlen emlegetik). Ez az egys\u00e9gtesztelhet\u0151s\u00e9g.

      Jelen pillanatban az Anonymizer oszt\u00e1lyunkhoz automata integr\u00e1ci\u00f3s teszteket tudunk \u00edrni, automata egys\u00e9gteszteket nem:

      A fentiek miatt sokszor nagyobb k\u00f3dlefedetts\u00e9get nem a lassabb integr\u00e1ci\u00f3s, hanem nagyon gyorsan fut\u00f3 egys\u00e9gtesztekkel szoktunk/tudunk el\u00e9rni. Ezek mindenf\u00e9le lass\u00fa f\u00e1jl/adatb\u00e1zis/h\u00e1l\u00f3zat/felh\u0151 el\u00e9r\u00e9s n\u00e9lk\u00fcl \u00f6nmag\u00e1ban egy-egy logikai egys\u00e9get tesztelnek a k\u00f3dban, ezt viszont \u00edgy m\u00e1r vill\u00e1mgyorsan. \u00cdgy sokat tudunk futtatni adott id\u0151 alatt, j\u00f3 tesztlefedetts\u00e9ggel.

      Tesztpiramis

      Ezt egy tesztpiramissal szok\u00e1s szeml\u00e9ltetni, melynek t\u00f6bb form\u00e1ja terjedt el az irodalomban. Egy egyszer\u0171 vari\u00e1ns a k\u00f6vetkez\u0151:

      Min\u00e9l fentebb vagyunk a piramis r\u00e9tegeiben, ann\u00e1l \u00e1tfog\u00f3bbak ugyan a tesztek, de ann\u00e1l lassabbak \u00e9s k\u00f6lts\u00e9gesebben is futtathat\u00f3k. \u00cdgy ezekb\u0151l \u00e1ltal\u00e1ban kevesebbet is k\u00e9sz\u00edt\u00fcnk (ez\u00e1ltal kisebb k\u00f3dlefedetts\u00e9get is \u00e9r\u00fcnk el vel\u00fck). A piramis cs\u00facs\u00e1n az automata E2E (End-to-end) vagy GUI tesztek vannak. Alatta vannak t\u00f6bb egys\u00e9get/modult egyben tesztel\u0151 integr\u00e1ci\u00f3s tesztek. A piramis talapzat\u00e1ban az egys\u00e9gtesztek vannak, ezekb\u0151l k\u00e9sz\u00edt\u00fcnk a legt\u00f6bbet (a piramis talapzata a legsz\u00e9lesebb).

      Fun fact: Amikor egy term\u00e9k fejleszt\u00e9se sor\u00e1n hossz\u00fa ideig elhanyagolj\u00e1k az egys\u00e9gtesztek k\u00e9sz\u00edt\u00e9s\u00e9t, akkor - mivel a k\u00f3d szerkezete nem t\u00e1mogatja - m\u00e1r nagyon neh\u00e9z egys\u00e9gteszteket ut\u00f3lag k\u00e9sz\u00edteni. \u00cdgy ezekb\u0151l csak nagyon kev\u00e9s lesz, n\u00e9mi integr\u00e1ci\u00f3s tesztekkel kieg\u00e9sz\u00edtve, \u00e9s jobb h\u00edj\u00e1n tesztel\u0151csapatok \u00e1ltal elk\u00e9sz\u00edtett sok-sok end-to-end/GUI teszttel (de ezzel sokszor nem lehet j\u00f3 tesztlefedetts\u00e9get el\u00e9rni egy komplex term\u00e9kben). Egy piramissal szemben ennek fagyit\u00f6lcs\u00e9r form\u00e1ja van, csak p\u00e1r gomb\u00f3cot kell a tetej\u00e9re k\u00e9pzelni. Szok\u00e1s ezt fagyi \"mint\u00e1nak\" is nevezni (\u00e9s ez nem az a fagyi, amit szeret\u00fcnk). Azt az\u00e9rt \u00e9rdemes megjegyezni, hogy mindent a hely\u00e9n kell kezelni: vannak kiv\u00e9telek (olyan alkalmaz\u00e1sok, ahol az egyes r\u00e9szekben alig van logika, az eg\u00e9sz alkalmaz\u00e1sban az egyes nagyon egyszer\u0171 r\u00e9szek integr\u00e1ci\u00f3ja a hangs\u00falyos: ilyen esetben term\u00e9szetszer\u0171en az integr\u00e1ci\u00f3s tesztek t\u00fals\u00falyosak).

      Az oszt\u00e1lyok k\u00f3dja alapesetben sokszor nem egys\u00e9gtesztelhet\u0151. Jelen form\u00e1j\u00e1ban ilyen az Anonymizer is. Ebbe be van \u00e9getve, hogy csak a lass\u00fa, f\u00e1jl alap\u00fa bemenettel tud dolgozni. De amikor mi pl. a Run m\u0171velet logik\u00e1j\u00e1t szeretn\u00e9nk egys\u00e9gtesztelni, teljesen mindegy, hogy f\u00e1jlb\u00f3l j\u00f6nnek-e az adatok (lassan), vagy egyszer\u0171en k\u00f3db\u00f3l a new oper\u00e1torral el\u0151\u00e1ll\u00edtunk n\u00e9h\u00e1ny Person objektumot a tesztel\u00e9shez (t\u00f6bb nagys\u00e1grenddel gyorsabban).

      A megold\u00e1s - a k\u00f3dunk egys\u00e9gtesztelhet\u0151v\u00e9 t\u00e9tel\u00e9hez - egyszer\u0171:

      Ennek megfelel\u0151en elk\u00e9sz\u00edtj\u00fck a megold\u00e1sunk egys\u00e9gtesztel\u00e9sre is el\u0151k\u00e9sz\u00edtett v\u00e1ltozat\u00e1t, melyben a bemenet \u00e9s kimenet kezel\u00e9se is le van v\u00e1lasztva a Strategy minta alkalmaz\u00e1s\u00e1val.

      Feladat: Alak\u00edtsd \u00e1t a Strategy-DI projektben tal\u00e1lhat\u00f3 megold\u00e1st olyan m\u00f3don, hogy az oszt\u00e1ly egys\u00e9g tesztelhet\u0151 legyen, m\u00e9gpedig a Strategy minta seg\u00edts\u00e9g\u00e9vel. R\u00e9szletesebben:

      A k\u00f6vetkez\u0151 l\u00e9p\u00e9s egys\u00e9gtesztek k\u00e9sz\u00edt\u00e9se (lenne) az Anonymizer oszt\u00e1lyhoz. Ehhez olyan, \u00fan. mock strategy implement\u00e1ci\u00f3kat kell bevezetni, melyek nemcsak tesztadatokat szolg\u00e1ltatnak (term\u00e9szetesen gyorsan, f\u00e1jlkezel\u00e9s n\u00e9lk\u00fcl), hanem ellen\u0151rz\u00e9seket is v\u00e9geznek (adott logikai egys\u00e9g val\u00f3ban j\u00f3l m\u0171k\u00f6dik-e). Ez most bonyolultnak hangzik, de szerencs\u00e9re a legt\u00f6bb modern keretrendszerben van r\u00e1 k\u00f6nyvt\u00e1r t\u00e1mogat\u00e1s (.NET-ben a moq). Ennek alkalmaz\u00e1sa t\u00falmutat a t\u00e1rgy keretein, \u00edgy a feladatunk egys\u00e9gtesztelhet\u0151s\u00e9ghez kapcsol\u00f3d\u00f3 vonulat\u00e1t ebben a pontban lez\u00e1rjuk.

      3. feladat BEADAND\u00d3

      "},{"location":"hazi/6-tervezesi-mintak/#4-feladat-delegate-ek-alkalmazasa","title":"4. Feladat - Delegate-ek alkalmaz\u00e1sa","text":"

      Napjainkban rohamosan terjed a kor\u00e1bban szigor\u00faan objektumorient\u00e1lt nyelvekben is a funkcion\u00e1lis programoz\u00e1st t\u00e1mogat\u00f3 eszk\u00f6z\u00f6k megjelen\u00e9se, \u00e9s az alkalmaz\u00e1sfejleszt\u0151k is egyre nagyobb szeretettel alkalmazz\u00e1k ezeket (merthogy sokszor jelent\u0151sen r\u00f6videbb k\u00f3ddal, kisebb \"cerem\u00f3ni\u00e1val\" lehet ugyanazt seg\u00edts\u00e9g\u00fckkel megval\u00f3s\u00edtani). Egy ilyen eszk\u00f6z C# nyelven a delegate, \u00e9s ehhez kapcsol\u00f3d\u00f3an a lambda kifejez\u00e9s.

      Mint a f\u00e9l\u00e9v sor\u00e1n kor\u00e1bban l\u00e1ttuk, delegate-ek seg\u00edts\u00e9g\u00e9vel olyan k\u00f3dot tudunk \u00edrni, melybe bizonyos logik\u00e1k/viselked\u00e9sek nincsenek be\u00e9getve, ezeket \"k\u00edv\u00fclr\u0151l\" kap meg a k\u00f3d. Pl. egy sorrendez\u0151 f\u00fcggv\u00e9nynek delegate form\u00e1j\u00e1ban adjuk \u00e1t param\u00e9terk\u00e9nt, hogyan kell k\u00e9t elemet \u00f6sszehasonl\u00edtani, vagy mely mez\u0151je/tulajdons\u00e1ga szerint kell az \u00f6sszehasonl\u00edt\u00e1st elv\u00e9gezni (\u00edgy v\u00e9gs\u0151 soron meghat\u00e1rozni a k\u00edv\u00e1nt sorrendet).

      Ennek megfelel\u0151en a delegate-ek alkalmaz\u00e1sa egy \u00fajabb alternat\u00edva (a Template Method \u00e9s a Strategy mellett) a k\u00f3d \u00fajrafelhaszn\u00e1lhat\u00f3v\u00e1/kiterjeszthet\u0151v\u00e9 t\u00e9tel\u00e9re, kiterjeszt\u00e9si pontok bevezet\u00e9s\u00e9re.

      A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben a kor\u00e1bban Strategy mint\u00e1val megval\u00f3s\u00edtott progress kezel\u00e9st alak\u00edtjuk \u00e1t delegate alap\u00fara (\u00faj funkci\u00f3t nem vezet\u00fcnk be, ez egy puszt\u00e1n \"technikai\" \u00e1talak\u00edt\u00e1s lesz).

      Feladat: Alak\u00edtsd \u00e1t a Strategy-DI projektben tal\u00e1lhat\u00f3 megold\u00e1st olyan m\u00f3don, hogy a progress kezel\u00e9s Strategy helyett delegate alapon legyen megval\u00f3s\u00edtva. R\u00e9szletesebben:

      Tippek

      4. feladat BEADAND\u00d3

      "},{"location":"hazi/6-tervezesi-mintak/#5-feladat-delegate-ek-alkalmazasa-ujrafelhasznalhato-logikaval","title":"5. Feladat - Delegate-ek alkalmaz\u00e1sa \u00fajrafelhaszn\u00e1lhat\u00f3 logik\u00e1val","text":"

      Az el\u0151z\u0151 feladatban feltett\u00fck, hogy a \"simple progress\" \u00e9s a \"percent progress\" logik\u00e1j\u00e1t csak egyszer haszn\u00e1ltuk, \u00edgy nem kellett \u00fajrafelhaszn\u00e9lhat\u00f3v\u00e1 tenni. Ennek megfelel\u0151en pl. a \"simple progress\" logik\u00e1j\u00e1t a lehet\u0151 legegyszer\u0171bb form\u00e1ban, egy lambda kifejez\u00e9ssel adtuk meg (nem kellett k\u00fcl\u00f6n f\u00fcggv\u00e9nyt bevezetni r\u00e1). Amennyiben az Anonymizer l\u00e9trehoz\u00e1sakor a delegate-nek mindig m\u00e1s \u00e9s m\u00e1s implement\u00e1ci\u00f3t adunk meg, akkor ez a lambda alap\u00fa megold\u00e1s t\u00f6k\u00e9letes.

      Viszont mi a helyzet akkor, ha a fenti p\u00e9ld\u00e1ban szerepl\u0151 \"simple progress\" logik\u00e1t t\u00f6bb helyen, t\u00f6bb Anonymizer objektumn\u00e1l is fel szeretn\u00e9nk haszn\u00e1lni? S\u00falyos hiba lenne a lambda kifejez\u00e9st copy-paste-tel \"szapor\u00edtani\", k\u00f3dduplik\u00e1ci\u00f3hoz vezetne (ellentmondana a \"Do Not Repeat Yourself\", r\u00f6viden DRY elvnek).

      K\u00e9rd\u00e9s: van-e megold\u00e1s arra, hogy delegate-ek eset\u00e9ben is \u00fajrafelhaszn\u00e1lhat\u00f3 k\u00f3dot adjunk meg? Term\u00e9szetesen igen, hiszen delegate-ek eset\u00e9ben nem k\u00f6telez\u0151 a lambda kifejez\u00e9sek haszn\u00e1lata, lehet vel\u00fck k\u00f6z\u00f6ns\u00e9ges m\u0171veletekre (ak\u00e1r statikus, ak\u00e1r nem statikusakra is), mint azt kor\u00e1bban a f\u00e9l\u00e9v sor\u00e1n l\u00e1ttuk, \u00e9s sz\u00e1mos esetben alkalmaztuk is.

      Amennyiben a \"simple progress\" \u00e9s/vagy \"percent progress\" logik\u00e1t/logik\u00e1kat \u00fajrafelhaszn\u00e1lhat\u00f3v\u00e1 szeretn\u00e9nk tenni delegate-ek alkalmaz\u00e1sakor, tegy\u00fck ezeket egy k\u00fcl\u00f6n f\u00fcggv\u00e9nyekbe valamilyen, az adott esetben legink\u00e1bb passzol\u00f3 oszt\u00e1lyba/oszt\u00e1lyokba, \u00e9s egy ilyen m\u0171veletet adjuk meg az Anonymizer konstruktornak param\u00e9terk\u00e9nt.

      Feladat: B\u0151v\u00edtsd ki a kor\u00e1bbi megold\u00e1st \u00fagy, hogy a \"simple progress\" \u00e9s \"percent progress\" logik\u00e1ja \u00fajrafelhaszn\u00e1lhat\u00f3 legyen. R\u00e9szletesebben:

      Elk\u00e9sz\u00fclt\u00fcnk, \u00e9rt\u00e9kelj\u00fck a megold\u00e1st:

      5. feladat BEADAND\u00d3

      "},{"location":"hazi/6-tervezesi-mintak/#refaktoralas-refactoring-fogalma","title":"Refaktor\u00e1l\u00e1s (Refactoring) fogalma","text":"

      A labor \u00e9s a h\u00e1zi feladat megval\u00f3s\u00edt\u00e1sa sor\u00e1n sz\u00e1mos olyan l\u00e9p\u00e9s volt, mely sor\u00e1n a k\u00f3dot \u00fagy alak\u00edtottuk \u00e1t, hogy az alkalmaz\u00e1s k\u00fcls\u0151 viselked\u00e9se nem v\u00e1ltozott, csak a bels\u0151 fel\u00e9p\u00edt\u00e9se. M\u00e9gpedig annak \u00e9rdek\u00e9ben, hogy valamilyen szempontb\u00f3l jobb k\u00f3dmin\u0151s\u00e9gi jellemz\u0151kkel rendelkezzen. Ezt a k\u00f3d refaktor\u00e1l\u00e1s\u00e1nak (angolul refactoring) nevezz\u00fck. Ez egy nagyon fontos fogalom, a mindennapi munka sor\u00e1n nagyon gyakran haszn\u00e1ljuk. K\u00fcl\u00f6n irodalma van, a fontosabb technik\u00e1kkal a k\u00e9s\u0151bbiekben \u00e9rdemes megismerkedni. A komolyabb fejleszt\u0151eszk\u00f6z\u00f6k be\u00e9p\u00edtetten t\u00e1mogatnak bizonyos refaktor\u00e1l\u00e1si m\u0171veleteket: a Visual Studio ebben nem a leger\u0151sebb, de az\u00e9rt p\u00e1r alapm\u0171veletet t\u00e1mogat (pl. Extract Method, Extract base class stb.). Manu\u00e1lisan gyakoroltuk, ennek kapcs\u00e1n k\u00fcl\u00f6n feladatunk nem lesz, de a Refaktor\u00e1l\u00e1s fogalm\u00e1t ismerni kell.

      "},{"location":"hazi/6-tervezesi-mintak/#6-opcionalis-feladat-integracios-teszt-keszitese","title":"6. Opcion\u00e1lis feladat - Integr\u00e1ci\u00f3s teszt k\u00e9sz\u00edt\u00e9se","text":"

      A feladat megold\u00e1s\u00e1val +1 IMSc pont szerezhet\u0151.

      A kor\u00e1bbi, 3. feladat sor\u00e1n ismertet\u00e9sre ker\u00fclt az integr\u00e1ci\u00f3s teszt fogalma. Jelen opcion\u00e1lis feladat c\u00e9lja ennek gyakorl\u00e1sa, jobb meg\u00e9rt\u00e9se egy egyszer\u0171 feladaton kereszt\u00fcl.

      K\u00e9sz\u00edts egy integr\u00e1ci\u00f3s tesztet az Anonymizer oszt\u00e1lyhoz, a k\u00f6vetkez\u0151k szerint:

      1. A Solutionben a Test mapp\u00e1ban el\u0151k\u00e9sz\u00edtett IntegrationTest projektben dolgozz. Ez egy NUnit teszt projekt.
      2. Ebben a projektben m\u00e1r el\u0151re felvett\u00fcnk egy projekt referenci\u00e1t a Strategy-DI projektre, \u00edgy l\u00e1tjuk a Strategy-DI projektben lev\u0151 (publikus) oszt\u00e1lyokat. \u00c9rtelemszer\u0171en ez el\u0151felt\u00e9tele annak, hogy tudjuk tesztelni \u0151ket. Ellen\u0151rizd a projekt referencia megl\u00e9t\u00e9t (Solution Explorerben a projekt alatt a Dependencies/Projects csom\u00f3pont).
      3. Az AnonymizerIntegrationTest oszt\u00e1lyban m\u00e1r van egy Anonymize_CleanInput_MaskNames_Test nev\u0171 tesztel\u00e9st v\u00e9gz\u0151 m\u0171velet (a teszt m\u0171veleteket [Test] attrib\u00fatummal kell ell\u00e1tni, ez erre a m\u0171veletre m\u00e1r el\u0151 van k\u00e9sz\u00edtve). A m\u0171velet t\u00f6rzse egyel\u0151re \u00fcres, ebben kell dolgozni a k\u00f6vetkez\u0151 l\u00e9p\u00e9sekben.
        1. Hozz l\u00e9tre egy Anonymizer objektumot, mely
          • a @\"TestFiles\\us-500-01-clean.input.csv\" bemenettel dolgozik (ez megtal\u00e1lhat\u00f3 a projekt TestFiles mapp\u00e1j\u00e1ban, n\u00e9zd meg a tartalm\u00e1t),
          • a kimente legyen a @\"us-500-01-maskedname.processed.txt\" f\u00e1jl,
          • \"***\" param\u00e9ter\u0171 NameMaskingAnonymizerAlgorithm-t haszn\u00e1l.
        2. Futtasd az anonimiz\u00e1l\u00f3t a Run m\u0171velet\u00e9nek h\u00edv\u00e1s\u00e1val, hogy \u00e1lljon el\u0151 a kimenti \u00e1llom\u00e1ny.
        3. Az Assert.AreEqual h\u00edv\u00e1ssal ellen\u0151rizd, hogy az anonimiz\u00e1l\u00e1s sor\u00e1n el\u0151\u00e1llt kimeneti \u00e1llom\u00e1ny tartalma megegyezik-e a v\u00e1rt tartalommal. A v\u00e1rt tartalom a @\"TestFiles\\us-500-01-maskedname.processed-expected.txt\" f\u00e1jlban \u00e9rhet\u0151 el (ez megtal\u00e1lhat\u00f3 a projekt TestFiles mapp\u00e1j\u00e1ban, n\u00e9zd meg a tartalm\u00e1t). Tipp: egy f\u00e1jl tartalm\u00e1t pl. a File.ReadAllBytes statikus m\u0171velettel egy l\u00e9p\u00e9sben be lehet olvasni.
      4. Ellen\u0151rizd, hogy az integr\u00e1ci\u00f3s teszt hiba n\u00e9lk\u00fcl lefut.
      5. Buildeld meg a projektet
      6. Nyisd meg a Test Explorert (Test/Test Explorer men\u00fc)
      7. A teszt futtat\u00e1s\u00e1ra a Test Explorer n\u00e9zet tetej\u00e9n tal\u00e1lhat\u00f3 eszk\u00f6zs\u00e1von lev\u0151 gombokkal van lehet\u0151s\u00e9g. De a teszt debuggol\u00e1s\u00e1ra is van lehet\u0151s\u00e9g, jobb gombbal a tesztre kattintva \u00e9s a Debug men\u00fc kiv\u00e1laszt\u00e1s\u00e1val: ez nagyon hasznos tud lenni, ha a teszt\u00fcnk hib\u00e1san fut, \u00e9s szeretn\u00e9nk t\u00f6r\u00e9spontok seg\u00edts\u00e9g\u00e9vel a k\u00f3don l\u00e9pkedni, illetve a v\u00e1ltoz\u00f3k \u00e9rt\u00e9k\u00e9t megn\u00e9zni.
      8. Ha a teszt hiba n\u00e9lk\u00fcl fut le, a teszthez tartoz\u00f3 ikon z\u00f6ld lesz. Ha hib\u00e1val, akkor piros, \u00e9s a hiba\u00fczenetr\u0151l a tesztet kiv\u00e1lasztva Test Explorer n\u00e9zet alj\u00e1n kapunk b\u0151vebb inform\u00e1ci\u00f3t.
      "},{"location":"hazi/6-tervezesi-mintak/#7-opcionalis-feladat-unit-teszt-keszitese","title":"7. Opcion\u00e1lis feladat - Unit teszt k\u00e9sz\u00edt\u00e9se","text":"

      A feladat megold\u00e1s\u00e1val +2 IMSc pont szerezhet\u0151.

      A kor\u00e1bbi, 3. feladat sor\u00e1n ismertet\u00e9sre ker\u00fclt az egys\u00e9gteszt fogalma. Jelen opcion\u00e1lis feladat c\u00e9lja ennek gyakorl\u00e1sa, jobb meg\u00e9rt\u00e9se egy feladaton kereszt\u00fcl.

      El\u0151k\u00e9sz\u00edt\u00e9s:

      1. Vegy\u00e9l fel a solution-be egy \u00faj \"NUnit Test Project\" t\u00edpus\u00fa projektet \"UnitTest\" n\u00e9ven (jobb katt a Solution-\u00f6n a Solution Explorerben/Add/New Project).
      2. Ebben az \u00faj projektben vegy\u00e9l fel projekt referenci\u00e1t a Strategy-DI projektre, hogy a projektben el\u00e9rhet\u0151k legyenek a Strategy-DI-ben defini\u00e1lt t\u00edpusok (jobb katt a Unit Test projekt Dependencies csom\u00f3pontj\u00e1n/Add Project Reference, a megjelen\u0151 ablakban pipa a Strategy-DI projekten, \"OK\").
      3. A projektben sz\u00fcletik egy UnitTest1.cs \u00e1llom\u00e1ny, benne egy Test oszt\u00e1ly. Ezeket c\u00e9lszer\u0171 AnonymizerTest-re nevezni.

      K\u00e9sz\u00edts egy egys\u00e9gtesztet az Anonymizer oszt\u00e1lyhoz, mely ellen\u0151rzi, hogy a Run m\u0171velete pontosan azokkal a szem\u00e9ly adatokkal h\u00edvja meg sorrendhelyesen az anonimiz\u00e1l\u00f3 algoritmust, melyeket az Anonymizer a bemenet\u00e9n beolvas (amennyiben nincsenek trimmelend\u0151 v\u00e1rosnevek).

      Tov\u00e1bbi gyakorl\u00e1sk\u00e9ppen k\u00e9sz\u00edthetsz egy olyan m\u00e1sik egys\u00e9gtesztet, mely azt ellen\u0151rzi, hogy minden bemeneti szem\u00e9lyadat eljut-e a kimenetre is.

      "},{"location":"hazi/6-tervezesi-mintak/#osszegzes","title":"\u00d6sszegz\u00e9s","text":"

      T\u00f6bb feladat nem lesz \ud83d\ude0a. De ha k\u00edv\u00e1ncsi vagy pl. arra, hogy jelen megold\u00e1s mennyire tekinthet\u0151 \"t\u00f6k\u00e9letesnek\"/hi\u00e1nyosnak, illetve mikor \u00e9rdemes Template Methoddal, Strategyvel, vagy ink\u00e1bb delegate-ekkel dolgozni, akkor \u00e9rdemes elolvasnod az al\u00e1bbiakat, melyben \u00e9rt\u00e9kelj\u00fck a laboron elkezdett \u00e9s a h\u00e1zi feladat keret\u00e9ben befejezett megold\u00e1st.

      "},{"location":"hazi/6-tervezesi-mintak/#a-munkafolyamatunk-attekintese","title":"A munkafolyamatunk \u00e1ttekint\u00e9se","text":""},{"location":"hazi/6-tervezesi-mintak/#ujrafelhasznalhatosagi-es-kiterjeszthetoseg-szintjei-az-egyes-megoldasokban","title":"\u00dajrafelhaszn\u00e1lhat\u00f3s\u00e1gi \u00e9s kiterjeszthet\u0151s\u00e9g szintjei az egyes megold\u00e1sokban","text":"

      Megpr\u00f3b\u00e1lhatjuk \u00e1br\u00e1ba \u00f6nteni, hogy v\u00e1lt a megold\u00e1sunk az egyes iter\u00e1ci\u00f3kkal egyre ink\u00e1bb \u00fajrafelhaszn\u00e1lhat\u00f3v\u00e1 \u00e9s kiterjeszthet\u0151v\u00e9:

      Term\u00e9szetesen a % szinteket nem szabad t\u00fal komolyan venni. Mindenesetre a fejl\u0151d\u00e9s j\u00f3l megfigyelhet\u0151.

      Mi\u00e9rt \"csak\" 70%-os a v\u00e9gs\u0151 megold\u00e1sn\u00e1l mutat\u00f3nk?

      Felmer\u00fclhet a k\u00e9rd\u00e9s, mi\u00e9rt adunk jelem megold\u00e1sra kb. 70%-ot? T\u00f6bbek k\u00f6z\u00f6tt:

      "},{"location":"hazi/6-tervezesi-mintak/#kiterjesztesi-technikak-attekintese","title":"Kiterjeszt\u00e9si technik\u00e1k \u00e1ttekint\u00e9se","text":"

      \u00c9rdemes \u00f6sszeszedni, hogy a Strategy-nek mikor lehet/van van el\u0151nye a delegate-ekkel szemben:

      Mindenk\u00e9ppen meg kell eml\u00edteni, hogy nem csak jelen gyakorlatban eml\u00edtett n\u00e9h\u00e1ny minta szolg\u00e1lja a kiterjeszthet\u0151s\u00e9get \u00e9s \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1got, hanem gyakorlatilag az \u00f6sszes. Most kiemelt\u00fcnk p\u00e1rat, melyek (m\u00e9g p. az Observert/Iteratort/Adaptert ide sorolva) tal\u00e1n a leggyakrabban, legsz\u00e9lesebb k\u00f6rben alkalmazhat\u00f3k \u00e9s bukkannak is fel keretrendszerekben.

      Ha id\u00e1ig olvastad, mindenk\u00e9ppen j\u00e1r egy extra thumbs up \ud83d\udc4d!

      "},{"location":"hazi/beadas-ellenorzes/","title":"H\u00e1zi feladat bead\u00e1sa sor\u00e1n ellen\u0151rizend\u0151k","text":""},{"location":"hazi/beadas-ellenorzes/index_ger/","title":"Bei der Abgabe von Hausaufgaben sollten Sie Folgendes \u00fcberpr\u00fcfen","text":""},{"location":"hazi/eloellenorzes-ertekeles/","title":"A h\u00e1zi feladat el\u0151ellen\u0151rz\u00e9se \u00e9s hivatalos \u00e9rt\u00e9kel\u00e9se","text":"

      Minden egyes alkalommal, miut\u00e1n a GitHub-ra push-olt\u00e1l k\u00f3dot, a GitHub-on automatikusan lefut a felt\u00f6lt\u00f6tt k\u00f3d (el\u0151)ellen\u0151rz\u00e9se, \u00e9s meg lehet n\u00e9zni a kimenet\u00e9t! Az ellen\u0151rz\u0151t maga a GitHub futtatja. A push-t k\u00f6vet\u0151en a feladat egy v\u00e1rakoz\u00e1si sorba ker\u00fcl, majd adott id\u0151 ut\u00e1n lefutnak az ellen\u0151rz\u0151 tesztek. Azt nem lehet tudni, mennyi ez az id\u0151, a GitHub-on m\u00falik. Amikor csak egy-k\u00e9t feladat van a sorban a szervezetre (ez n\u00e1lunk a t\u00e1rgy), akkor a tapasztalatok alapj\u00e1n az ellen\u0151rz\u00e9s 1-2 percen bel\u00fcl elindul. De ha a t\u00e1rgy alatt egyszerre sokan kezdik majd felt\u00f6lteni a megold\u00e1st, akkor ez j\u00f3 es\u00e9llyel belassul. Nem \u00e9rdemes ez\u00e9rt sem az utols\u00f3 pillanatra hagyni a bead\u00e1st: lehet, hogy ekkor a k\u00e9sleltet\u00e9sek miatt m\u00e1r nem kapsz esetleg id\u0151ben visszajelz\u00e9st.

      Hivatalosan a feladat azon \u00e1llapota ker\u00fcl \u00e9rt\u00e9kel\u00e9sre, amely a hat\u00e1rid\u0151 lej\u00e1rtakor GitHub-on fent van. A hivatalos ellen\u0151rz\u00e9st szok\u00e1sos m\u00f3don, saj\u00e1t, oktat\u00f3i k\u00f6rnyezetben v\u00e9gezz\u00fck \u00e9s az eredm\u00e9nyt Moodleben publik\u00e1ljuk a sz\u00e1monk\u00e9r\u00e9sn\u00e9l. Vagyis a hivatalos eredm\u00e9ny tekintet\u00e9ben teljesen mindegy, hogy a GitHub-on a hat\u00e1rid\u0151 lej\u00e1rta lefutott-e m\u00e1r b\u00e1rmif\u00e9le (el\u0151)ellen\u0151rz\u00e9s, vagy hogy az ellen\u0151rz\u00e9s esetleg csak k\u00e9s\u0151bb tudott elindulni. A GitHub \u00e1ltali ellen\u0151rz\u00e9s csak azt a c\u00e9lt szolg\u00e1lja, hogy m\u00e9g a hat\u00e1rid\u0151 lej\u00e1rta el\u0151tt visszajelz\u00e9st kaphasson mindenki. A hat\u00e1rid\u0151 lej\u00e1rta ut\u00e1ni hivatalos ellen\u0151rz\u00e9s tartalmaz m\u00e9g plusz l\u00e9p\u00e9seket a GitHub alap\u00fa el\u0151ellen\u0151rz\u00e9shez k\u00e9pest, az el\u0151ellen\u0151rz\u00e9s ilyen \u00e9rtelemben r\u00e9szleges, de az\u00e9rt sok probl\u00e9m\u00e1t seg\u00edthet megfogni!

      Arra k\u00e9r\u00fcnk, hogy ne apr\u00e1nk\u00e9nt push-olj, csak a k\u00e9sz, \u00e1tn\u00e9zett, fordul\u00f3 megold\u00e1st tedd fel! Ez nem a legszerencs\u00e9sebb, de a GitHub korl\u00e1tozott id\u0151t biztos\u00edt az ellen\u0151rz\u0151k futtat\u00e1s\u00e1ra: ha elfogy a havi keret, akkor m\u00e1r nem fogsz visszajelz\u00e9st kapni, csak a hat\u00e1rid\u0151 ut\u00e1ni hivatalos ellen\u0151rz\u00e9s kimenet\u00e9t kapja meg mindenki.

      A (f\u00e9l)automata ellen\u0151rz\u0151, most m\u00e9g egy r\u00e9szben k\u00eds\u00e9rleti projekt. Ha valaki az \u00fatmutat\u00f3ban inkonzisztenci\u00e1t tal\u00e1l, vagy az ellen\u0151rz\u0151 adott helyzetet nem kezel \u00e9s indokolatlanul panaszkodik, Benedek Zolt\u00e1n felel\u0151s oktat\u00f3 fel\u00e9 legyen sz\u00edves jelezni! Ugyanakkor ezeket nagy t\u00f6megben nem fogjuk tudni kezelni. Ha j\u00f3 a megold\u00e1sod, \u00e9s az ellen\u0151rz\u0151 indokolatlanul panaszkodik, a hivatalos ellen\u0151rz\u00e9s sor\u00e1n term\u00e9szetesen el fogjuk fogadni.

      Az el\u0151ellen\u0151rz\u0151 \u2013 k\u00fcl\u00f6n\u00f6sen az els\u0151 h\u00e1zi feladat eset\u00e9ben \u2013 sokszor el\u00e9gg\u00e9 \"g\u00e9pk\u00f6zeli megfogalmaz\u00e1sban\" jelzi az esetleges probl\u00e9m\u00e1kat. Ha semmik\u00e9ppen nem tudod \u00e9rtelmezni, \u00edrj Benedek Zolt\u00e1nnak Teams-ben, a hiba\u00fczenet megad\u00e1s\u00e1val, illetve egy linkkel a GitHub repository-dra (m\u00e1sk\u00fcl\u00f6nben nem tudjuk, hol tal\u00e1lhat\u00f3 a k\u00f3dod).

      Az, hogy az el\u0151ellen\u0151rz\u0151 milyen m\u00e9lys\u00e9gben ellen\u0151rzi a megold\u00e1st, a h\u00e1zi feladatt\u00f3l f\u00fcgg. Az 1-3 feladat eset\u00e9ben el\u00e9g alapos, m\u00edg a 4-5 feladat eset\u00e9n csak a Neptun.txt kit\u00f6lt\u00f6tts\u00e9g\u00e9t ellen\u0151rzi, \u00e9s azt, van-e ford\u00edt\u00e1si hiba (az \u00e9rdemi \u00e9rt\u00e9kel\u00e9s ut\u00f3lag t\u00f6rt\u00e9nik).

      "},{"location":"hazi/eloellenorzes-ertekeles/#a-github-altal-futtatott-ellenorzesek-megtekintese","title":"A GitHub \u00e1ltal futtatott ellen\u0151rz\u00e9sek megtekint\u00e9se","text":"
      1. GitHub-on a navig\u00e1l\u00e1s a repository-hoz
      2. Actions tabf\u00fclre v\u00e1lt\u00e1s
      3. Itt megjelenik egy t\u00e1bl\u00e1zat, minden push \u00e1ltal futtatott ellen\u0151rz\u00e9shez egy k\u00fcl\u00f6n sor, a tetej\u00e9n van legfrissebb. A sor elej\u00e9n lev\u0151 ikon jelzi a st\u00e1tuszt: v\u00e1r, fut, sikeres, sikertelen lehet. A sor sz\u00f6vege a Git commit neve.
      4. Egy sorban a commit nev\u00e9n kattintva jelenik meg egy \u00e1tfog\u00f3 oldal az ellen\u0151rz\u0151 fut\u00e1s\u00e1r\u00f3l, ez sok inform\u00e1ci\u00f3t nem tartalmaz. Ezen az oldalon baloldalt kell a \"build\" vagy \"build-and-check\" (vagy hasonl\u00f3 nev\u0171) linken kattintani, ez \u00e1tnavig\u00e1l az ellen\u0151rz\u00e9s r\u00e9szletes n\u00e9zet\u00e9re. Ez egy \u201e\u00e9l\u0151\u201d n\u00e9zet, ha fut a teszt, folyamatosan friss\u00fcl. Ha v\u00e9gzett, a csom\u00f3pontokat lenyitva lehet megn\u00e9zni az adott l\u00e9p\u00e9s kimenet\u00e9t. Ha minden siker\u00fclt, egy ehhez hasonl\u00f3 n\u00e9zet l\u00e1that\u00f3:

      5. Itt a legfontosabb tal\u00e1n a \"Run tests\" l\u00e9p\u00e9s. Ha valamelyik l\u00e9p\u00e9s sikertelen, pipa helyett piros x van a csom\u00f3pont elej\u00e9n, \u00e9s a csom\u00f3pontot kibontva a teszt kimenete utal a hiba ok\u00e1ra. Az els\u0151 h\u00e1zi feladat eset\u00e9ben az \"Error Message\"-re, ill. az \"Assert\"-re \u00e9rdemes sz\u00f6vegesen (control+F) keresni a kimenetben, ennek a k\u00f6rny\u00e9k\u00e9n szokott lenni hivatkoz\u00e1s a hiba ok\u00e1ra.

      "},{"location":"hazi/fejlesztokornyezet/","title":"Fejleszt\u0151k\u00f6rnyezet h\u00e1zi feladatokhoz","text":""},{"location":"hazi/fejlesztokornyezet/#bevezetes","title":"Bevezet\u00e9s","text":"

      A f\u00e9l\u00e9v sor\u00e1n a h\u00e1zi feladatok megold\u00e1s\u00e1hoz a Visual Studio 2022 fejleszt\u0151k\u00f6rnyezetet kell haszn\u00e1lni (a Visual Studio for Mac nem alkalmas). Ennek futtat\u00e1s\u00e1hoz Windows oper\u00e1ci\u00f3s rendszerre van sz\u00fcks\u00e9g. Ha telep\u00edtve van m\u00e1r a g\u00e9p\u00fcnkre a Visual Studio 2022, akkor a Start men\u00fcb\u0151l ind\u00edtsuk el a \u201eVisual Studio Installer\u201d-t. Ez indul\u00e1skor ellen\u0151rzi, \u00e9rhet\u0151-e el Visual Studio-b\u00f3l \u00fajabb v\u00e1ltozat online, \u00e9s ha igen, az Update gombra kattintva ind\u00edtsuk is el a legfrissebb verzi\u00f3 telep\u00edt\u00e9s\u00e9t.

      Mi\u00e9rt is van sz\u00fcks\u00e9g Visual Studiora \u00e9s Windowsra?

      VS Code, illetve a Visual Studio for Mac a k\u00f6vetkez\u0151k miatt nem haszn\u00e1lhat\u00f3k:

      "},{"location":"hazi/fejlesztokornyezet/#visual-studio-edition-ok","title":"Visual Studio edition-\u00f6k","text":"

      A Visual Studionak t\u00f6bb kiad\u00e1sa l\u00e9tezik:

      "},{"location":"hazi/fejlesztokornyezet/#telepitendo-komponensek","title":"Telep\u00edtend\u0151 komponensek","text":"

      A t\u00e1rgy els\u0151 el\u0151ad\u00e1sa r\u00f6viden kit\u00e9r a .NET k\u00fcl\u00f6nb\u00f6z\u0151 v\u00e1ltozataira (.NET Framework, .NET Core, .NET 5-8 \u00e9s stb.). A feladatok megold\u00e1s\u00e1hoz a .NET 8-et haszn\u00e1ljuk a f\u00e9l\u00e9v sor\u00e1n. A Visual Studio ezt telep\u00edti, de sz\u00fcks\u00e9g van a \".NET desktop development\" Visual Studio Workload telep\u00edt\u00e9s\u00e9re:

      1. Visual Studio telep\u00edt\u0151 ind\u00edt\u00e1sa (pl. a Windows Start men\u00fcben a \u201eVisual Studio Installer\u201d beg\u00e9pel\u00e9s\u00e9vel).
      2. Modify gombra kattint\u00e1s
      3. A megjelen\u0151 ablakban ellen\u0151rizz\u00fck, hogy a \".NET desktop development\" k\u00e1rtya ki van-e pip\u00e1lva.
      4. Ha nincs, pip\u00e1ljuk ki, majd a jobb als\u00f3 sarokban a Modify gombra kattintva telep\u00edts\u00fck.
      "},{"location":"hazi/fejlesztokornyezet/#class-diagram-tamogatas","title":"Class diagram t\u00e1mogat\u00e1s","text":"

      Bizonyos h\u00e1zi feladatok eset\u00e9n (m\u00e1r az els\u0151n\u00e9l is) sz\u00fcks\u00e9g van Visual Studio Class Diagram t\u00e1mogat\u00e1sra. Ezt a k\u00f6vetkez\u0151k\u00e9ppen tudjuk ut\u00f3lag telep\u00edteni a Visual Studio al\u00e1:

      1. Visual Studio telep\u00edt\u0151 ind\u00edt\u00e1sa (pl. a Windows Start men\u00fcben a \u201eVisual Studio Installer\u201d beg\u00e9pel\u00e9s\u00e9vel).
      2. Modify gombra kattint\u00e1s
      3. A megjelen\u0151 ablakban \"Individual components\" f\u00fcl kiv\u00e1laszt\u00e1sa
      4. A keres\u0151mez\u0151be \"class designer\" beg\u00e9pel\u00e9se, majd gy\u0151z\u0151dj\u00fcnk meg, hogy a sz\u0171rt list\u00e1ban a \"Class Designer\" elem ki van pip\u00e1lva.
      5. Ha nincs, pip\u00e1ljuk ki, majd a jobb als\u00f3 sarokban a Modify gombra kattintva telep\u00edts\u00fck.

      "},{"location":"hazi/fejlesztokornyezet/#winui-tamogatas","title":"WinUI t\u00e1mogat\u00e1s","text":"

      XAML/WinUI technol\u00f3gi\u00e1khoz kapcsol\u00f3d\u00f3 feladatok eset\u00e9n (3. h\u00e1zi feladatt\u00f3l kezd\u0151d\u0151en) sz\u00fcks\u00e9g van Windows App SDK el\u0151zetes telep\u00edt\u00e9s\u00e9re \u00e9s bizonyos speci\u00e1lis g\u00e9pi szint\u0171 be\u00e1ll\u00edt\u00e1sok m\u00f3dos\u00edt\u00e1s\u00e1ra.

      1. A sz\u00e1m\u00edt\u00f3g\u00e9pen enged\u00e9lyezni kell a \"Developer mode\" (\"Fejleszt\u0151i m\u00f3d\")-ot. A Windows Start men\u00fcben a \"Developer settings\"/\"Fejleszt\u0151i funkci\u00f3k\"-ra \u00e9rdemes keresni (annak f\u00fcggv\u00e9ny\u00e9ben hogy angol vagy magyar Windowst haszn\u00e1lunk).

      2. A Visual Studio telep\u00edt\u0151ben gy\u0151z\u0151dj\u00fcnk meg, hogy a \".NET Desktop Development\" workload telep\u00edtve van (ha nincs, pip\u00e1ljuk \u00e9s telep\u00edts\u00fck)

      3. \"Windows App SDK C# templates\" Visual Studio komponens telep\u00edt\u00e9se.

        A Visual Studio telep\u00edt\u0151ben v\u00e1lasszuk ki a \".NET Desktop Development\" workload-ot, jobb oldalt az \"Installation details\" panelen alul pip\u00e1ljuk a \"Windows App SDK C# Templates\" komponenst, majd jobb als\u00f3 sarokban \"Modify\" gomb.

      4. Windows App SDK telep\u00edt\u00e9se

        A legfrissebb innen telep\u00edthet\u0151: https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/downloads. Ugyanakkor a f\u00e9l\u00e9v sor\u00e1n laborokon, h\u00e1zikban az \"1.4.4 (1.4.231219000)\" verzi\u00f3t haszn\u00e1ljuk, \u00e9rdemes ezt telep\u00edteni akkor is, ha \u00fajabb verzi\u00f3 j\u00f6nne ki, mely innen \u00e9rhet\u0151 el: https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/older-downloads. Egy modern g\u00e9pre az x64-es verzi\u00f3t kell telep\u00edteni.

      5. Ha a fentiek telep\u00edt\u00e9se ut\u00e1n Windows 11-en nem akarna m\u0171k\u00f6dni, akkor fel kell tenni a Visual Studio telep\u00edt\u0151ben a Windows 10 SDK-b\u00f3l a 10.0.19041-et, vagy \u00fajabbat (az Idividual Comopnents alatt tal\u00e1lhat\u00f3)

      "},{"location":"hazi/fejlesztokornyezet/#macbook-es-linux-hasznalok-szamara-informaciok","title":"MacBook \u00e9s Linux haszn\u00e1l\u00f3k sz\u00e1m\u00e1ra inform\u00e1ci\u00f3k","text":"

      A t\u00e1rgy felel\u0151s oktat\u00f3j\u00e1t\u00f3l (Benedek Zolt\u00e1n) BME Cloud hozz\u00e1f\u00e9r\u00e9s ig\u00e9nylelhet\u0151 e-mailben.

      "},{"location":"hazi/fejlesztokornyezet/index_ger/","title":"Entwicklungsumgebung f\u00fcr Hausaufgaben","text":""},{"location":"hazi/fejlesztokornyezet/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

      F\u00fcr die Hausaufgaben w\u00e4hrend des Semesters muss die Entwicklungsumgebung Visual Studio 2022 verwendet werden (Visual Studio f\u00fcr Mac ist nicht geeignet). Zum Ausf\u00fchren ben\u00f6tigen Sie ein Windows-Betriebssystem. Wenn Sie Visual Studio 2022 bereits auf Ihrem Computer installiert haben, starten Sie den \"Visual Studio Installer\" \u00fcber das Startmen\u00fc. Dadurch wird beim Start gepr\u00fcft, ob eine neuere Version von Visual Studio online verf\u00fcgbar ist. Ist dies der Fall, klicken Sie auf Aktualisieren, um die Installation der neuesten Version zu starten.

      Warum brauche ich Visual Studio und Windows?

      VS Code oder Visual Studio f\u00fcr Mac kann aus folgenden Gr\u00fcnden nicht verwendet werden:

      "},{"location":"hazi/fejlesztokornyezet/index_ger/#visual-studio-ausgabe-ok","title":"Visual Studio Ausgabe-\u00f6k","text":"

      Es gibt verschiedene Editionen von Visual Studio:

      "},{"location":"hazi/fejlesztokornyezet/index_ger/#zu-installierende-komponenten","title":"Zu installierende Komponenten","text":"

      In der ersten Vorlesung des Kurses werden kurz die verschiedenen Versionen von .NET (.NET Framework, .NET Core, .NET 5-8 usw.) behandelt. Wir werden .NET 8 verwenden, um die Probleme w\u00e4hrend des Semesters zu l\u00f6sen. Visual Studio installiert dies, aber Sie m\u00fcssen den \".NET Desktop Development\" Visual Studio Workload installieren:

      1. Starten Sie das Visual Studio-Installationsprogramm (z. B. durch Eingabe von \"Visual Studio Installer\" im Windows-Startmen\u00fc).
      2. Klicken Sie auf die Schaltfl\u00e4che \u00c4ndern
      3. Vergewissern Sie sich in dem nun erscheinenden Fenster, dass die Karte \".NET-Desktop-Entwicklung\" aktiviert ist.
      4. Wenn nicht, entfernen Sie das H\u00e4kchen und klicken Sie unten rechts auf \u00c4ndern, um es zu installieren.
      "},{"location":"hazi/fejlesztokornyezet/index_ger/#unterstutzung-von-klassendiagrammen","title":"Unterst\u00fctzung von Klassendiagrammen","text":"

      F\u00fcr bestimmte Hausaufgaben (sogar f\u00fcr die erste) ben\u00f6tigen Sie die Unterst\u00fctzung von Visual Studio Class Diagram. Diese kann unter Visual Studio wie folgt installiert werden:

      1. Starten Sie das Visual Studio-Installationsprogramm (z. B. durch Eingabe von \"Visual Studio Installer\" im Windows-Startmen\u00fc).
      2. Klicken Sie auf die Schaltfl\u00e4che \u00c4ndern
      3. W\u00e4hlen Sie in dem nun erscheinenden Fenster die Registerkarte \"Einzelne Komponenten\"
      4. Geben Sie in das Suchfeld \"Klassendesigner\" ein und vergewissern Sie sich, dass \"Klassendesigner\" in der gefilterten Liste nicht angekreuzt ist.
      5. Wenn nicht, entfernen Sie das H\u00e4kchen und klicken Sie unten rechts auf \u00c4ndern, um es zu installieren.

      "},{"location":"hazi/fejlesztokornyezet/index_ger/#winui-unterstutzung","title":"WinUI-Unterst\u00fctzung","text":"

      F\u00fcr Aufgaben, die sich auf XAML/WinUI-Technologien beziehen (ab Hausaufgabe 3), ist es notwendig, das Windows App SDK vorzuinstallieren und einige spezifische Einstellungen auf Maschinenebene zu \u00e4ndern.

      1. Der \"Entwicklermodus\" muss auf dem Computer aktiviert sein. Suchen Sie im Windows-Startmen\u00fc nach \"Entwicklereinstellungen\" (je nachdem, ob Sie ein englisches oder ungarisches Windows verwenden).

      2. Vergewissern Sie sich im Visual Studio-Installationsprogramm, dass der \".NET Desktop Development\"-Workload installiert ist (falls nicht, entfernen Sie die Markierung und installieren Sie ihn)

      3. installation der Visual Studio Komponente \"Windows App SDK C# Templates\".

        W\u00e4hlen Sie im Visual Studio-Installationsprogramm den Workload \".NET Desktop Development\", markieren Sie die Komponente \"Windows App SDK C# Templates\" im Bereich \"Installationsdetails\" auf der rechten Seite und klicken Sie dann auf die Schaltfl\u00e4che \"\u00c4ndern\" in der rechten unteren Ecke.

      4. Windows-SDK installieren

        Es kann installiert werden von: https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/downloads. W\u00e4hrend des Semesters werden wir \"1.4.4 (1.4.231219000)\" in \u00dcbungen und Tutorien verwenden. Es wird empfohlen, diese Version zu installieren, auch wenn eine neuere Version ver\u00f6ffentlicht wird.

      "},{"location":"hazi/fejlesztokornyezet/index_ger/#informationen-fur-macbook-und-linux-benutzer","title":"Informationen f\u00fcr MacBook- und Linux-Benutzer","text":"

      Sie k\u00f6nnen den Zugang zur BME-Cloud beim zust\u00e4ndigen Dozenten (Zolt\u00e1n Benedek) per E-Mail anfordern.

      "},{"location":"hazi/git-github-github-classroom/","title":"Git, GitHub, GitHub Classroom","text":"

      A t\u00e1rgy keret\u00e9ben nem c\u00e9lunk a Git \u00e9s GitHub r\u00e9szletes megismer\u00e9se, csak a legsz\u00fcks\u00e9gesebb l\u00e9p\u00e9sekre szor\u00edtkozunk, valamint a legfontosabb parancsokat haszn\u00e1ljuk ahhoz, hogy a h\u00e1zi feladat(ok) kiindul\u00e1si programv\u00e1z\u00e1t hallgat\u00f3k\u00e9nt egy dedik\u00e1lt GitHub repository-b\u00f3l le tudjuk t\u00f6lteni, illetve a k\u00e9sz munk\u00e1t GitHubra fel tudjuk t\u00f6lteni.

      "},{"location":"hazi/git-github-github-classroom/#git","title":"Git","text":"

      A Git egy sok szolg\u00e1ltat\u00e1ssal rendelkez\u0151, rendk\u00edv\u00fcl n\u00e9pszer\u0171 \u00e9s elterjedt, ingyenesen let\u00f6lthet\u0151 \u00e9s telep\u00edthet\u0151, elosztottan is haszn\u00e1lhat\u00f3 verzi\u00f3kezel\u0151 rendszer. A k\u00f6zpontos\u00edtott rendszerekhez k\u00e9pest (TFS, CVS, SVN) a GIT eset\u00e9ben nem egyetlen k\u00f6zponti repository-ba dolgoznak a fejleszt\u0151k, hanem mindenki egy saj\u00e1t lok\u00e1lis repository p\u00e9ld\u00e1nnyal rendelkezik.

      Egy Git repository (becenev\u00e9n rep\u00f3) nem m\u00e1s, mint egy k\u00f6z\u00f6ns\u00e9ges k\u00f6nyvt\u00e1r a f\u00e1jlrendszerben, benne \u00e1llom\u00e1nyokkal (pl. forr\u00e1sk\u00f3d) \u00e9s alk\u00f6nyvt\u00e1rakkal, illetve egy \".git\" alk\u00f6nyvt\u00e1rral, melyben minden, a verzi\u00f3kezel\u00e9shez kapcsol\u00f3d\u00f3 extra inform\u00e1ci\u00f3 megtal\u00e1lhat\u00f3.

      A Git alap\u00fa munkafolyamat legfontosabb l\u00e9p\u00e9sei - n\u00e9mi egyszer\u0171s\u00edt\u00e9ssel - a k\u00f6vetkez\u0151k (felt\u00e9ve, hogy l\u00e9tezik egy k\u00f6zponti repository, ahol a verzi\u00f3kezelt k\u00f3d adott v\u00e1ltozata m\u00e1r el\u00e9rhet\u0151):

      1. A fejleszt\u0151 kl\u00f3nozza (clone) az adott k\u00f6zponti repository-t, melynek sor\u00e1n egy azzal megegyez\u0151 helyi repository j\u00f6n l\u00e9tre a saj\u00e1t sz\u00e1m\u00edt\u00f3g\u00e9p\u00e9n. Ezt a m\u0171veletet el\u00e9g egyszer elv\u00e9gezni.
      2. A fejleszt\u0151 a helyi repository-hoz tartoz\u00f3 munkak\u00f6nyvt\u00e1rban (working directory) v\u00e1ltoztat\u00e1sokat v\u00e9gez a k\u00f3don: \u00faj f\u00e1jlokat vesz fel, megl\u00e9v\u0151ket m\u00f3dos\u00edt \u00e9s t\u00f6r\u00f6l.
      3. Ha elk\u00e9sz\u00fcl egy \u00e9rdemi r\u00e9szfeladat, akkor a fejleszt\u0151 a v\u00e1ltoztat\u00e1sokat commit-olja a sz\u00e1m\u00edt\u00f3g\u00e9p\u00e9n lev\u0151 helyi repository-ba. Ennek sor\u00e1n a commit-ot c\u00e9lszer\u0171 egy a v\u00e1ltoztat\u00e1sok jelleg\u00e9t j\u00f3l \u00f6sszefoglal\u00f3 megjegyz\u00e9ssel ell\u00e1tni.
      4. A helyi repository-b\u00f3l egy push m\u0171velettel a fejleszt\u0151 fel\u00f6lti a v\u00e1ltoz\u00e1sokat a k\u00f6zponti repository-ba, ahol \u00edgy v\u00e1ltoztat\u00e1sai m\u00e1sok sz\u00e1m\u00e1ra is l\u00e1that\u00f3v\u00e1 v\u00e1lnak.

      Minden egyes commit tulajdonk\u00e9ppen egy id\u0151b\u00e9lyeggel, a fejleszt\u0151 felhaszn\u00e1l\u00f3nev\u00e9vel \u00e9s e-mail c\u00edm\u00e9vel ell\u00e1tott k\u00f3dot \u00e9rint\u0151 v\u00e1ltoz\u00e1shalmaz. A repositoryban ezek \"egym\u00e1sut\u00e1nis\u00e1g\u00e1b\u00f3l\" \u00e1ll \u00f6ssze a teljes verzi\u00f3t\u00f6rt\u00e9net. Mivel a legt\u00f6bb esetben a fejleszt\u0151k csapatban dolgoznak, id\u0151nk\u00e9nt sz\u00fcks\u00e9g van arra, hogy m\u00e1sok \u00e1ltal a k\u00f6zponti repository-ba push-olt v\u00e1ltoztat\u00e1sokat a fejleszt\u0151k a saj\u00e1t lok\u00e1lis repository-jukba let\u00f6lts\u00e9k \u00e9s belemerge-elj\u00e9k: erre szolg\u00e1l a pull m\u0171velet. Fontos szab\u00e1ly, hogy push-olni csak akkor lehet a k\u00f6zponti repository-ba (a Git csak akkor engedi), ha el\u0151tte m\u00e1sok v\u00e1ltoztat\u00e1sait a saj\u00e1t lok\u00e1lis repository-nkba egy pull m\u0171velettel el\u0151tte belemerge-elt\u00fck. A Szoftvertechnik\u00e1k t\u00e1rgy keret\u00e9ben a pull m\u0171veletet nem kell haszn\u00e1lni, mert mindenki \u00f6n\u00e1ll\u00f3an, saj\u00e1t repository-ba dolgozik. Megjegyz\u00e9s: ha esetleg a GitHub fel\u00fclet\u00e9n k\u00f6zvetlen v\u00e1ltoztatunk f\u00e1jlokon (vagy t\u00f6bb clone-ban is dolgozunk), akkor sz\u00fcks\u00e9g van a pull haszn\u00e1lat\u00e1ra ez esetben is. A fentieken t\u00falmen\u0151en a Git sz\u00e1mos tov\u00e1bbi szolg\u00e1ltat\u00e1st biztos\u00edt (pl. teljes verzi\u00f3t\u00f6rt\u00e9net megtekint\u00e9se minden f\u00e1jlra, commit t\u00f6rt\u00e9net megtekint\u00e9se, tetsz\u0151leges m\u00faltbeli verzi\u00f3ra vissza\u00e1ll\u00e1s, \u00e1gak kezel\u00e9se stb.).

      "},{"location":"hazi/git-github-github-classroom/#github","title":"GitHub","text":"

      A GitHub egy online el\u00e9rhet\u0151 website \u00e9s szolg\u00e1ltat\u00e1s (https://github.com), mely teljes k\u00f6r\u0171 Git szolg\u00e1ltat\u00e1st biztos\u00edt. Mindezt r\u00e1ad\u00e1sul \u2013 legal\u00e1bbis publikus, vagyis mindenki sz\u00e1m\u00e1ra hozz\u00e1f\u00e9rhet\u0151 repositoryk vonatkoz\u00e1s\u00e1ban \u2013 teljesen ingyenesen biztos\u00edtja. Napjainkra a GitHub v\u00e1lt a k\u00f6z\u00f6ss\u00e9gi k\u00f3d (verzi\u00f3kezelt) t\u00e1rol\u00e1s\u00e1nak els\u0151 sz\u00e1m\u00fa platformj\u00e1v\u00e1, a legt\u00f6bb ny\u00edlt forr\u00e1sk\u00f3d\u00fa projekt \u201eotthon\u00e1v\u00e1\u201d.

      "},{"location":"hazi/git-github-github-classroom/#github-classroom","title":"GitHub Classroom","text":"

      A GitHub Classroom egy ingyenesen el\u00e9rhet\u0151 GitHub-bal integr\u00e1lt szolg\u00e1ltat\u00e1s, mely t\u00f6bbek k\u00f6z\u00f6tt oktat\u00e1si int\u00e9zm\u00e9nyek sz\u00e1m\u00e1ra lehet\u0151v\u00e9 teszi \u00f6n\u00e1ll\u00f3 tanul\u00f3i feladatokhoz tartoz\u00f3, tanul\u00f3nk\u00e9nt egyedi GitHub repository-k l\u00e9trehoz\u00e1s\u00e1t, ez\u00e1ltal a kiindul\u00e1si k\u00f3d tanul\u00f3k sz\u00e1m\u00e1ra t\u00f6rt\u00e9n\u0151 \u201ekioszt\u00e1s\u00e1t\u201d, valamint az elk\u00e9sz\u00fclt feladatok \u201ebeszed\u00e9s\u00e9t\u201d.

      "},{"location":"hazi/git-github-github-classroom/#git-github-es-github-classroom-a-targy-kontextusaban","title":"Git, GitHub \u00e9s GitHub Classroom a t\u00e1rgy kontextus\u00e1ban","text":"

      A t\u00e1rgy keret\u00e9ben a GitHub Classroom seg\u00edts\u00e9g\u00e9vel kap minden hallgat\u00f3 minden h\u00e1zi feladat\u00e1hoz egy dedik\u00e1lt, a GitHub-on hostolt repository-t, mely a megfelel\u0151 kiindul\u00e1si k\u00f6rnyezettel (kiindul\u00f3 Visual Studio solution-\u00f6k) inicializ\u00e1l\u00e1sra ker\u00fcl. Mindenkinek a sz\u00e1m\u00e1ra dedik\u00e1lt repository-t kell a saj\u00e1t g\u00e9p\u00e9re clone-oznia, ebbe a v\u00e1ltoztat\u00e1sait commit-olni, \u00e9s a hat\u00e1rid\u0151ig az elk\u00e9sz\u00fclt megold\u00e1s\u00e1t push-olni (hogy GitHub-on is el\u00e9rhet\u0151 legyen a megold\u00e1s). A pontos l\u00e9p\u00e9sekre r\u00f6videsen visszat\u00e9r\u00fcnk.

      "},{"location":"hazi/git-github-github-classroom/#visual-studio-es-a-git","title":"Visual Studio \u00e9s a Git","text":"

      A Git egy elosztott verzi\u00f3kezel\u0151 rendszer. Ahhoz, hogy a saj\u00e1t g\u00e9p\u00fcnk\u00f6n dolgozni tudjunk vele, a Git-nek telep\u00edtve kell lennie. K\u00e9tf\u00e9le m\u00f3don tudjuk haszn\u00e1lni:

      A k\u00e9t megk\u00f6zel\u00edt\u00e9st a mindennapokban kombin\u00e1ltan szoktuk haszn\u00e1lni. Egy repo lekl\u00f3noz\u00e1sa sokszor parancssorb\u00f3l a legegyszer\u0171bb/leggyorsabb. A v\u00e1ltoz\u00e1sok commit-ol\u00e1s\u00e1ra, a k\u00f6zponti repositoryval val\u00f3 szinkroniz\u00e1ci\u00f3ra (push, pull), a verzi\u00f3t\u00f6rt\u00e9nek megjelen\u00edt\u00e9s\u00e9re m\u00e1r c\u00e9lszer\u0171bb egy grafikus eszk\u00f6zt haszn\u00e1lni, k\u00fcl\u00f6n\u00f6sen akkor, ha m\u00e9g kev\u00e9sb\u00e9 vagyunk rutinosak. A t\u00e1rgy keret\u00e9ben a kl\u00f3noz\u00e1sra a parancssor vagy a Visual Studio, az egy\u00e9b parancsok kiad\u00e1s\u00e1ra a Visual Studio javasolt. A git haszn\u00e1latr\u00f3l (a h\u00e1zi feladatok kontextus\u00e1ban) itt tal\u00e1lhat\u00f3 b\u0151vebb le\u00edr\u00e1s.

      "},{"location":"hazi/git-github-github-classroom/#git-telepitese","title":"Git telep\u00edt\u00e9se","text":"

      Amennyiben a sz\u00e1m\u00edt\u00f3g\u00e9p\u00fcnkre nincs m\u00e9g a Git telep\u00edtve, \u00e9s szeretn\u00e9nk azt parancssorb\u00f3l is haszn\u00e1lni, akkor innen telep\u00edthet\u0151 Windows oper\u00e1ci\u00f3s rendszerre: https://git-scm.com/download/win. Egy\u00e9b oper\u00e1ci\u00f3s rendszerek eset\u00e9n pedig innen \u00e9rdemes indulni: https://git-scm.com/downloads.

      Git Credential Manager telep\u00edt\u00e9se

      A GitHub m\u00e1r egy ideje nem t\u00e1mogatja az egyszer\u0171 felhaszn\u00e1l\u00f3n\u00e9v/jelsz\u00f3 alap\u00fa hiteles\u00edt\u00e9st. Ha git parancssorban a login sor\u00e1n \"Support for password authentication was removed.\" hiba\u00fczenetet kapunk, ez az oka. K\u00e9t megold\u00e1s is l\u00e9tezik a probl\u00e9ma megold\u00e1s\u00e1ra:

      "},{"location":"hazi/hf-folyamat/","title":"H\u00e1zi feladat munkafolyamat \u00e9s a Git/GitHub haszn\u00e1lata","text":"

      Ha m\u00e9g nem olvastad, c\u00e9lszer\u0171 itt kezdeni: Git, GitHub, GitHub Classroom

      "},{"location":"hazi/hf-folyamat/#lepesek","title":"L\u00e9p\u00e9sek","text":"

      Az egyes h\u00e1zi feladatok kiindul\u00f3 keret\u00e9t GitHub/GitHub Classroom seg\u00edts\u00e9g\u00e9vel publik\u00e1ljuk. Az \u00edgy publik\u00e1lt h\u00e1zi feladatok kiindul\u00f3 k\u00f6rnyezet let\u00f6lt\u00e9s\u00e9nek \u00e9s a megold\u00e1s bead\u00e1s\u00e1nak l\u00e9p\u00e9sei a k\u00f6vetkez\u0151k:

      1. Az elindul\u00e1ssal ne v\u00e1rd meg a hat\u00e1rid\u0151 k\u00f6zeledt\u00e9t, legal\u00e1bb a saj\u00e1t repository l\u00e9trehoz\u00e1s\u00e1ig juss el miel\u0151bb. \u00cdgy, ha b\u00e1rmi elakad\u00e1s lenne, m\u00e9g id\u0151ben tudunk seg\u00edteni.
      2. Regisztr\u00e1lj egy GitHub accountot (https://github.com/), ha m\u00e9g nem regisztr\u00e1lt\u00e1l, \u00e9s l\u00e9pj be vele GitHub-ra.
      3. A feladathoz tartoz\u00f3 linket nyisd meg. Ez minden feladathoz m\u00e1s lesz, Moodle-ben ker\u00fclnek meghirdet\u00e9sre fokozatosan a f\u00e9l\u00e9v folyam\u00e1n. A form\u00e1tuma a k\u00f6vetkez\u0151h\u00f6z hasonl\u00f3: https://classroom.github.com/abcdefgh. Ha a hivatkoz\u00e1sra kattintva hib\u00e1t kapsz (\"There was a problem authenticating with GitHub, please try again.\"), copy-paste-tel m\u00e1sold be k\u00f6zvetlen\u00fcl a b\u00f6ng\u00e9sz\u0151 c\u00edmsor\u00e1ba a c\u00edmet.
      4. Ha k\u00e9ri, adj enged\u00e9lyt a GitHub Classroom alkalmaz\u00e1snak, hogy haszn\u00e1lja az account adataidat.
      5. L\u00e1tni fogsz egy oldalt, ahol elfogadhatod a feladatot (\"Accept this assignment\"). Kattints a gombra.
      6. V\u00e1rd meg, am\u00edg elk\u00e9sz\u00fcl a repository. A GitHub nem mindig friss\u00edti az oldalt mag\u00e1t\u00f3l, c\u00e9lszer\u0171 az oldal id\u0151nk\u00e9nti k\u00e9zi friss\u00edt\u00e9s\u00e9vel pr\u00f3b\u00e1lkozni (pl. F5 billenty\u0171). Ha elk\u00e9sz\u00fclt a repository, az oldal ki\u00edrja az \u00faj repository url-j\u00e9t, amin kattintva a repository-ra lehet navig\u00e1lni (ehhez hasonl\u00f3: https://github.com/bmeviauab00/hazi1-2024-username). De nem is felt\u00e9tlen sz\u00fcks\u00e9ges az url elment\u00e9se, a GitHub nyit\u00f3oldal\u00e1n (https://github.com/) baloldalt a saj\u00e1t repository-k k\u00f6zt b\u00e1rmikor meg lehet k\u00e9s\u0151bb is tal\u00e1lni.
      7. Kl\u00f3nozd le a repository-t (ennek mik\u00e9ntj\u00e9re r\u00f6videsen visszat\u00e9r\u00fcnk). Ebben tal\u00e1lni fogsz egy keretet, vagy kiindul\u00f3 k\u00f3dot. Ezen dolgozz, ezt v\u00e1ltoztasd. Az alap\u00e9rtelmezett git \u00e1gon/branchen dolgozz (ha ez nem mond semmit, nem baj: ez csak azoknak sz\u00f3l, akik a git haszn\u00e1lat\u00e1ban j\u00e1rtasak \u00e9s t\u00f6bb \u00e1gon szoktak dolgozni).
      8. A kiindul\u00f3 projektben van egy .github/workflows mappa, ennek tartalm\u00e1t tilos megv\u00e1ltoztatni, t\u00f6r\u00f6lni stb.
      9. A munka sor\u00e1n a kiindul\u00e1si rep\u00f3ban lev\u0151 solutionben/projektben kell dolgozni, \u00faj projektet/solutiont ne hozz l\u00e9tre.
      10. A repository gy\u00f6k\u00e9rmapp\u00e1j\u00e1ban tal\u00e1lhat\u00f3 neptun.txt f\u00e1jlba \u00edrd bele a Neptun k\u00f3dod, csupa nagybet\u0171vel. A f\u00e1jlban csak ez a hat karakter legyen, semmi m\u00e1s.
      11. Oldd meg a feladatot. Pushold a hat\u00e1rid\u0151ig. Az alap\u00e9rtelmezett \"Main\" \u00e1gon kell dolgozni k\u00f6zvetlen\u00fcl, nincsenek pull requestek. Ak\u00e1rh\u00e1ny commitod lehet, a legutols\u00f3 \u00e1llapotot fogjuk n\u00e9zni.
      12. Az eredm\u00e9nyek Moodle-ben ker\u00fclnek meghirdet\u00e9sre (a nyit\u00f3oldalon kapcsol\u00f3d\u00f3 h\u00e1zi feladat oldal\u00e1t Moodle-ben megnyitva az oldal alj\u00e1n a \"Visszajelz\u00e9s\" szekci\u00f3ban l\u00e1that\u00f3). Eredm\u00e9nyek az adott feladatra vonatkoz\u00f3 hat\u00e1rid\u0151t k\u00f6vet\u0151 p\u00e1r napon bel\u00fcl v\u00e1rhat\u00f3k.
      13. A h\u00e1zi feladatot k\u00fcl\u00f6n explicit beadni nem kell, csak legyen fent GitHub-on hat\u00e1rid\u0151re a megold\u00e1s.
      14. Amikor a h\u00e1zi feladatod beadottnak tekinted, c\u00e9lszer\u0171 ellen\u0151rizni a b\u00f6ng\u00e9sz\u0151ben a GitHub webes fel\u00fclet\u00e9n a repository-ban a f\u00e1jlokra val\u00f3 r\u00e1pillant\u00e1ssal, hogy val\u00f3ban minden v\u00e1ltoztat\u00e1st push-olt\u00e1l-e, \u00e9s hogy a neptun.txt val\u00f3ban ki van-e t\u00f6ltve.

      A fenti l\u00e9p\u00e9sek kapcs\u00e1n k\u00e9t k\u00e9rd\u00e9s v\u00e1r m\u00e9g megv\u00e1laszol\u00e1sra:

      Ezek nagy r\u00e9sz\u00e9t Szoftvertechnol\u00f3gia t\u00e1rgyb\u00f3l m\u00e1r tanultad a k\u00e9pz\u00e9s sor\u00e1n. De ha esetleg nem eml\u00e9kszel ennek minden r\u00e9szleteire, vagy ha szeretn\u00e9l megismerkedni azzal, hogyan tudod ezeket nem csak parancssorb\u00f3l, hanem Visual Studio-b\u00f3l haszn\u00e1lni, akkor mindenk\u00e9ppen olvasd el az al\u00e1bbiakat. R\u00f6viden mindenre kit\u00e9r\u00fcnk a git haszn\u00e1lata kapcs\u00e1n, amire a h\u00e1zi feladatok megold\u00e1sa sor\u00e1n sz\u00fcks\u00e9g lehet (m\u00e9g azok is meg tudj\u00e1k oldani a h\u00e1zi feladatot, akik git-et nem tanultak, \u00e9s \u00edgy kapcsol\u00f3dnak be a t\u00e1rgyba).

      Amennyiben a git login sor\u00e1n \"Support for password authentication was removed\" hiba\u00fczenetet kapsz, a git telep\u00edt\u00e9s\u00e9t ismertet\u0151 oldal alj\u00e1n a Git Credential Manager-r\u0151l sz\u00f3l\u00f3 szakaszt \u00e9rdemes elolvasni.

      "},{"location":"hazi/hf-folyamat/#github-repository-klonozasa","title":"GitHub repository kl\u00f3noz\u00e1sa","text":"

      K\u00e9t lehet\u0151s\u00e9get n\u00e9z\u00fcnk meg al\u00e1bb:

      "},{"location":"hazi/hf-folyamat/#clone-a-github-webes-feluleterol-indulva-visual-studio-ban","title":"Clone a GitHub webes fel\u00fclet\u00e9r\u0151l indulva Visual Studio-ban","text":"

      Egy (h\u00e1zi feladathoz tartoz\u00f3) repository kl\u00f3noz\u00e1sra sz\u00e1mos m\u00f3d van, egy lehet\u0151s\u00e9g a k\u00f6vetkez\u0151. Nyissuk meg az elk\u00e9sz\u00fclt repository online oldal\u00e1t, melyre t\u00f6bb m\u00f3don eljuthatunk. Lehet\u0151s\u00e9gek pl.:

      Az oldal k\u00e9pe nagyj\u00e1b\u00f3l megfelel a k\u00f6vetkez\u0151nek (az mindenk\u00e9ppen k\u00fcl\u00f6nbs\u00e9g, hogy a rep\u00f3 url v\u00e9g\u00e9n mindenkin\u00e9l a saj\u00e1t felhaszn\u00e1l\u00f3neve szerepel):

      Kattintsunk a z\u00f6ld sz\u00edn\u0171 Code gombon, majd a leny\u00edl\u00f3 men\u00fcben az \"Open in Visual Studio\" linkre:

      A b\u00f6ng\u00e9sz\u0151nk ekkor j\u00f3 es\u00e9llyel feldob egy ablakot (pl. a Chrome/Edge eset\u00e9ben a c\u00edmsor alatt) melyben egy k\u00fcl\u00f6n gombkattint\u00e1ssal (Open\u2026) tudjuk ind\u00edtani a Visual Studio-t. A felk\u00edn\u00e1lt lehet\u0151s\u00e9gnek lehet, kiss\u00e9 fura a neve, ha \"Microsoft Visual Studio Web Protocol Handler Selector\" n\u00e9ven hivatkozik r\u00e1, v\u00e1lasszuk/enged\u00e9lyezz\u00fck ki b\u00e1tran. Illetve, itt c\u00e9lszer\u0171 az \"Always allow github.com to open links ...\" vagy hasonl\u00f3 sz\u00f6veg\u0171 jel\u00f6l\u0151n\u00e9gyzetet is pip\u00e1lni. Ha minden j\u00f3l megy, a Visual Studio elindul, \u00e9s indul\u00e1s ut\u00e1n feldob egy ablakot, melyben a \"Repository location\" ki is van t\u00f6ltve a repository-nk URL-j\u00e9vel. A Path alatt adjuk meg, hogy hova szeretn\u00e9nk a h\u00e1tt\u00e9rt\u00e1runkon clone-ozni, majd kattintsunk a Clone gombra:

      Alternat\u00edv kl\u00f3noz\u00e1si lehet\u0151s\u00e9g Visual Studioban

      Ha nem m\u0171k\u00f6dik a b\u00f6ng\u00e9sz\u0151ablakban az \"Open in Visual Studio\" vagy \"Microsoft Visual Studio Web Protocol Handler Selector\" hivatkoz\u00e1s, akkor indulhatunk egyb\u0151l a Visual Studio-b\u00f3l is. Csak ind\u00edtsuk el a Visual Studio-t, \u00e9s a startup ablakban v\u00e1lasszuk jobboldalt a \"Clone Repository\" gombot (vagy a startup ablakot \u00e1tugorva v\u00e1lasszuk ki a \"Git/Clone Repository men\u00fct\" a VS f\u0151ablak\u00e1ban), melynek hat\u00e1s\u00e1ra a fenti ablak jelenik meg, a Repository URL-be pedig \u00edrjuk be a rep\u00f3nk URL-j\u00e9t. A Clone-ra kattintva n\u00e9h\u00e1ny m\u00e1sodperc alatt a repository a megadott c\u00e9lmapp\u00e1ba kl\u00f3noz\u00f3dik.

      A kl\u00f3noz\u00e1st k\u00f6vet\u0151en pl. Windows Explorer-ben meg tudjuk tekinteni a l\u00e9trehozott mapp\u00e1kat \u00e9s f\u00e1jlokat:

      Ebb\u0151l j\u00f3l l\u00e1that\u00f3, hogy egy Git repository nem m\u00e1s, mint mapp\u00e1k \u00e9s f\u00e1jlok gy\u0171jtem\u00e9nye, valamint egy a gy\u00f6k\u00e9rben tal\u00e1lhat\u00f3 .git mappa, mely (n\u00e9mi egyszer\u0171s\u00edt\u00e9ssel \u00e9lve) az egyes f\u00e1jlok verzi\u00f3t\u00f6rt\u00e9net\u00e9t tartalmazza. A munka megkezd\u00e9s\u00e9hez csak meg kell nyissuk az adott h\u00e1zi feladathoz tartoz\u00f3 .sln kiterjeszt\u00e9s\u0171 solution f\u00e1jlt (pl. duplakatt Windows Explorerben).

      Az els\u0151 h\u00e1zi feladat speci\u00e1lis (k\u00e9t solution is van)!

      Az els\u0151 h\u00e1zi feladat kiv\u00e9telesen k\u00e9t f\u0151 r\u00e9szb\u0151l \u00e1ll, melyekhez elt\u00e9r\u0151 solution tartozik. Az els\u0151h\u00f6z a Feladat1 mapp\u00e1ban tal\u00e1lhat\u00f3 MusicApp.sln f\u00e1jlt, a m\u00e1sodikhoz a Feladat2-ben tal\u00e1lhat\u00f3 Shapes.sln-t kell megnyitni. A megnyit\u00e1st megtehetj\u00fck Explorerb\u0151l, az adott .sln f\u00e1jlon dupl\u00e1n kattintva. Ugyanakkor van erre m\u00e1s m\u00f3d is: amennyiben Visual Studio-ban a Git gy\u00f6k\u00e9rmapp\u00e1t nyitottuk meg (a Clone-t k\u00f6vet\u0151en is ez a helyzet \u00e1llt el\u0151) a Solution Explorer n\u00e9zet fejl\u00e9c\u00e9ben \"Switch View\" gombot lenyomva a Solution Explorer list\u00e1zza a Git gy\u00f6k\u00e9rmappa alatti solution-\u00f6ket, \u00e9s ezek b\u00e1rmelyik\u00e9n dupl\u00e1n kattintva az adott solution megny\u00edlik:

      "},{"location":"hazi/hf-folyamat/#clone-parancssorbol","title":"Clone parancssorb\u00f3l","text":"

      Alternat\u00edv lehet\u0151s\u00e9g a parancssor haszn\u00e1lata. Parancssorban navig\u00e1ljunk abba a mapp\u00e1ba, ahov\u00e1 a forr\u00e1sk\u00f3dot ki szeretn\u00e9nk clone-ozni, \u00e9s itt adjuk ki a k\u00f6vetkez\u0151 parancsot: git clone <repo url> , ahol a <repo url> a repositorynk c\u00edme (pl. b\u00f6ng\u00e9sz\u0151 c\u00edms\u00e1vj\u00e1b\u00f3l bem\u00e1solva, ehhez hasonl\u00f3: https://github.com/bmeviauab00/hazi1-2024-myusername). A parancs lefut\u00e1sa ut\u00e1n egy a repository nev\u00e9nek megfelel\u0151 alk\u00f6nyvt\u00e1rban tal\u00e1ljuk az \u00faj helyi rep\u00f3nkat.

      Parancssori git

      Ne f\u00e9lj\u00fcnk a parancssori git-et haszn\u00e1lni, egy repository clone-oz\u00e1s\u00e1nak tulajdonk\u00e9ppen ez a legegyszer\u0171bb m\u00f3dja.

      Amennyiben a parancs futtat\u00e1sa sor\u00e1n azt tapasztaljuk, hogy a git parancsot nem ismeri fel a k\u00f6rnyezet, annak oka val\u00f3sz\u00edn\u0171leg az, hogy nem telep\u00edtett\u00fcnk m\u00e9g a parancssori gitet a g\u00e9p\u00fcnkre. Err\u0151l b\u0151vebben itt.

      "},{"location":"hazi/hf-folyamat/#napi-git-munka-visual-studio-segitsegevel-commit-push","title":"Napi Git munka Visual Studio seg\u00edts\u00e9g\u00e9vel (commit, push)","text":"

      Miut\u00e1n lekl\u00f3noztuk az adott h\u00e1zi feladathoz tartoz\u00f3 GitHub repository-t a sz\u00e1m\u00edt\u00f3g\u00e9p\u00fcnkre, \u00e9s ennek sor\u00e1n l\u00e9trej\u00f6tt a lok\u00e1lis Git repository-nk, a benne lev\u0151 .sln f\u00e1jlokat Visual Studioban megnyitva pont \u00fagy dolgozunk \u2013 vesz\u00fcnk fel \u00faj f\u00e1jlokat, m\u00f3dos\u00edtunk/t\u00f6rl\u00fcnk megl\u00e9v\u0151ket \u2013 mintha a f\u00e1jlok nem is tartozn\u00e1nak semmif\u00e9le Git rep\u00f3hoz. Ugyanakkor, legk\u00e9s\u0151bb a feladat bead\u00e1sakor a v\u00e1ltoztat\u00e1sainkat commit-olni kell, majd push-olni GitHub-ra. A munka sor\u00e1n ak\u00e1rh\u00e1nyszor commit-\u00e1lhatjuk/push-olhatjuk az el\u0151z\u0151 commit \u00f3ta eszk\u00f6z\u00f6lt m\u00f3dos\u00edt\u00e1sainkat: a h\u00e1zi feladat ellen\u0151rz\u00e9sekor a hat\u00e1rid\u0151 pillanat\u00e1ban a GitHub-on tal\u00e1lhat\u00f3 \u00e1llapot ker\u00fcl elb\u00edr\u00e1l\u00e1sra, teljesen mindegy, h\u00e1ny commit tartozik hozz\u00e1. A commit \u00e9s push m\u0171veletek v\u00e9grehajt\u00e1s\u00e1hoz a Visual Studio \"Git\" men\u00fcj\u00e9ben lev\u0151 parancsokat haszn\u00e1ljuk.

      "},{"location":"hazi/hf-folyamat/#commit","title":"Commit","text":"

      Az el\u0151z\u0151 commit \u00f3ta eszk\u00f6z\u00f6lt v\u00e1ltoztat\u00e1sok megtekint\u00e9s\u00e9hez v\u00e1lasszuk ki a \"View\\Git Changes\" men\u00fct. Ennek hat\u00e1s\u00e1ra megjelenik a \"Git Changes\" n\u00e9zet a v\u00e1ltoz\u00e1sok list\u00e1j\u00e1val:

      A v\u00e1ltoztat\u00e1sok commit-\u00e1l\u00e1s\u00e1hoz \u00edrjunk a fenti sz\u00f6vegmez\u0151be egy a v\u00e1ltoztat\u00e1sokra jellemz\u0151 egy-k\u00e9t soros le\u00edr\u00e1st (pl. \"V\u00e9gs\u0151 megold\u00e1s\", \"Az xyz hiba jav\u00edt\u00e1sa\" stb.). A lehet\u0151s\u00e9geink ezt k\u00f6vet\u0151en a k\u00f6vetkez\u0151k:

      Note

      A git commit-ot mindig meg kell el\u0151zze egy \u00fan. stage l\u00e9p\u00e9s, mely sor\u00e1n kiv\u00e1lasztjuk azokat a helyi v\u00e1ltoztat\u00e1sokat, melyeket a k\u00f6vetkez\u0151 commit-ba be k\u00edv\u00e1nunk tenni. Ez az \u00fan. staging area ter\u00fcletre teszi az \u00e1ltalunk kiv\u00e1lasztott v\u00e1ltoz\u00e1sokat (a f\u00e1jlrendszerben nem mozgat semmif\u00e9le f\u00e1jlt, ez csak a git a bels\u0151 nyilv\u00e1ntart\u00e1s\u00e1ban jelenik meg). Ez az\u00e9rt j\u00f3, mert plusz rugalmass\u00e1got biztos\u00edt, hiszen nem biztos, mindig minden v\u00e1ltoztat\u00e1st bele k\u00edv\u00e1nunk tenni a k\u00f6vetkez\u0151 commit-ba. A fenti \"Commit all\" stb. parancsok nev\u00e9ben nem v\u00e9letlen van benne az \"all\": ezek a sz\u00ednfalak m\u00f6g\u00f6tt a commit el\u0151tt egy megfelel\u0151 git paranccsal valamennyi v\u00e1ltoz\u00e1st a git staging area-ra tesznek, \u00edgy ezt nek\u00fcnk nem kell k\u00fcl\u00f6n megtenn\u00fcnk.

      "},{"location":"hazi/hf-folyamat/#push-pull","title":"Push, Pull","text":"

      A commit m\u0171velet csak a helyi repository-ban \"\u00e9rv\u00e9nyes\u00edti\" a v\u00e1ltoztat\u00e1sokat. Ezt k\u00f6vet\u0151en a v\u00e1ltoztat\u00e1sokat a GitHub k\u00f6zponti repository-nkba fel kell t\u00f6lteni a push m\u0171velettel. Erre a l\u00e9p\u00e9sre csak akkor van sz\u00fcks\u00e9g, ha a commit sor\u00e1n nem haszn\u00e1ltuk a \"Commit All and Push\" vagy \"Commit All and Sync\" parancsokat. A push m\u0171velet VS-ben a \"Git/Push\" men\u00fc seg\u00edt\u00e9s\u00e9vel ind\u00edthat\u00f3. Ha t\u00f6bben dolgozunk, a k\u00f6zponti repository-ban lehetnek m\u00e1sok \u00e1ltal pusholt, hozz\u00e1nk m\u00e9g le nem t\u00f6lt\u00f6tt commitok (vagy ak\u00e1r olyanok, melyeket mi magunk push-oltunk egy m\u00e1sik lok\u00e1lis clone-b\u00f3l, vagy ha a GitHub online fel\u00fclet\u00e9n eszk\u00f6z\u00f6lt\u00fcnk a k\u00f3don v\u00e1ltoz\u00e1sokat). Ezeket a pull m\u0171velettel tudjuk a helyi rep\u00f3nkba merge-elni (Git/Pull men\u00fc). A h\u00e1zi feladat vonatkoz\u00e1s\u00e1ban ezt nem haszn\u00e1ljuk, hiszen mindenki saj\u00e1t dedik\u00e1lt k\u00f6zponti repositoryval rendelkezik, melyben egyed\u00fcl dolgozik (kiv\u00e9ve, ha esetleg valaki a GitHub fel\u00fclet\u00e9nek seg\u00edts\u00e9g\u00e9vel v\u00e1ltoztatott a k\u00f3don, akkor ezt egy pull-lal tudja a helyi rep\u00f3j\u00e1ba lehozni).

      Note

      A push csak akkor hajthat\u00f3 v\u00e9gre, ha a k\u00f6zponti rep\u00f3ban nincs olyan v\u00e1ltoz\u00e1s, melyet m\u00e9g a pull paranccsal nem hoztunk le \u00e9s merge-elt\u00fcnk a saj\u00e1t lok\u00e1lis rep\u00f3nkba. Ha ez nincs \u00edgy, egy ehhez hasonl\u00f3 hiba\u00fczenet kapunk: \"Unable to push to the remote repository because your local branch is behind the remote branch\". Ekkor pull-oljunk, ut\u00e1na ism\u00e9telj\u00fck meg a pusht.

      Note

      A pull m\u0171velet csak akkor hajthat\u00f3 v\u00e9gre, ha nincs olyan v\u00e1ltoztat\u00e1sunk helyben, melyeket m\u00e9g nem commit\u00e1ltunk. Ha van ilyen, akkor azokat vagy commit\u00e1ljuk (vagy ha ezt nem akarjuk megtenni m\u00e9g, akkor stash-elj\u00fck a pull idej\u00e9re).

      Tip

      A Pull \u00e9s Push parancsok a \u201eGit Changes\u201d (View/Git Changes men\u00fc jelen\u00edti meg) panel tetej\u00e9n el\u00e9rhet\u0151 le \u00e9s fel nyilakkal is v\u00e9grehajthat\u00f3k:

      "},{"location":"hazi/hf-folyamat/#git-history","title":"Git history","text":"

      A Git egy v\u00e1ltoz\u00e1sk\u00f6vet\u0151 rendszer. A v\u00e1ltoz\u00e1s egys\u00e9ge a commit (melyben tetsz\u0151leges sz\u00e1m\u00fa f\u00e1jlt \u00e9rint\u0151 v\u00e1ltoz\u00e1s lehet), a Git historyban a commitok egym\u00e1sut\u00e1nis\u00e1g\u00e1t l\u00e1thatjuk. A f\u00e1jlokat \u00e9rint\u0151 v\u00e1ltoz\u00e1sokon t\u00falmen\u0151en minden commithoz tartozik egy egyedi azonos\u00edt\u00f3 (commit hash), id\u0151b\u00e9lyeg, illetve egy szerz\u0151. A szerz\u0151 felhaszn\u00e1l\u00f3, aki a v\u00e1ltoz\u00e1sokat eszk\u00f6z\u00f6lte (val\u00f3j\u00e1ban van k\u00fcl\u00f6n Author \u00e9s Commiter, de a kett\u0151 \u00e1ltal\u00e1ban ugyanaz). Visual Studioban a historyt a View/Git Repository men\u00fcvel tudjuk megjelen\u00edteni, de a history term\u00e9szetesen a GitHub online fel\u00fclet\u00e9n is megjelen\u00edthet\u0151. A Visual Studioban a \"Git Repository\" n\u00e9zetet a View/Git Repository men\u00fcvel tudjuk megjelen\u00edteni.

      P\u00e9lda:

      Tip

      Am\u00edg nem vagyunk rutinosak a Visual Studio Git szolg\u00e1ltat\u00e1sainak haszn\u00e1lat\u00e1ban, a push-t k\u00f6vet\u0151en (legk\u00e9s\u0151bb akkor, amikor a h\u00e1zi feladatot beadottnak tekintj\u00fck) c\u00e9lszer\u0171 ellen\u0151rizni a GitHub webes fel\u00fclet\u00e9n a repository-ban a f\u00e1jlokra val\u00f3 r\u00e1pillant\u00e1ssal, hogy val\u00f3ban minden v\u00e1ltoztat\u00e1st felt\u00f6lt\u00f6tt\u00fcnk-e.

      "},{"location":"hazi/hf-folyamat/#egyeb-iranyelvek","title":"Egy\u00e9b ir\u00e1nyelvek","text":"

      A Git commit \u00e9s push sor\u00e1n megfigyelhetj\u00fck, hogy a solution-jeink k\u00f6ztes \u00e9s kimeneti \u00e1llom\u00e1nyai (.dll, .exe stb. f\u00e1jlok) nem ker\u00fclnek bele a commitba, \u00e9s \u00edgy nem ker\u00fclnek fel GitHubra sem. Ez \u00edgy is van j\u00f3l, ezen \u00e1llom\u00e1nyok b\u00e1rmikor reproduk\u00e1lhat\u00f3k, a verzi\u00f3kezel\u0151 rendszernek nem feladata ezek t\u00e1rol\u00e1sa, csak felesleges \u00e9s zavar\u00f3 helyfoglal\u00f3k lenn\u00e9nek. Felmer\u00fcl a k\u00e9rd\u00e9s, honnan tudja a Git, hogy mely \u00e1llom\u00e1nyokat sz\u00fcks\u00e9ges figyelmen k\u00edv\u00fcl hagyni a commit sor\u00e1n. Erre szolg\u00e1l a repository-ban (tipikusan annak gy\u00f6k\u00e9rmapp\u00e1j\u00e1ban) tal\u00e1lhat\u00f3 .gitignore f\u00e1jl, mely felsorolja azon mapp\u00e1kat, f\u00e1jlkiterjeszt\u00e9seket, illetve egyedi f\u00e1jlokat, melyeket a commit sor\u00e1n figyelmen k\u00edv\u00fcl szeretn\u00e9nk hagyni. A .gitignore f\u00e1jl tartalma teljes eg\u00e9sz\u00e9ben a kez\u00fcnk al\u00e1 tartozik, szabadon szerkeszthet\u0151/commit\u00e1lhat\u00f3/pusholhat\u00f3. A t\u00e1rgy keret\u00e9ben minden kiindul\u00f3 rep\u00f3nak r\u00e9sze egy .gitignore f\u00e1jl, ne v\u00e1ltoztassuk a tartalm\u00e1t! \u00cdgy a commit/push sor\u00e1n a kimeneti \u00e1llom\u00e1nyok a h\u00e1zi feladatok eset\u00e9ben sem ker\u00fclnek fel GitHub-ra, \u00e9s egy \u00edgy is van rendj\u00e9n.

      A f\u00e9l\u00e9vben a feladatok megold\u00e1sa sor\u00e1n az egyes oszt\u00e1lyok, interf\u00e9szek stb. forr\u00e1sk\u00f3dj\u00e1t k\u00fcl\u00f6n f\u00e1jlba kell tenni, vagyis egy C# forr\u00e1sf\u00e1jlban egy oszt\u00e1ly/interf\u00e9sz/stb. defin\u00edci\u00f3ja legyen.

      "},{"location":"hazi/hf-folyamat/#git-hasznalata-parancssorbol","title":"Git haszn\u00e1lata parancssorb\u00f3l","text":"

      B\u00e1r sokan \u00f3dzkodnak a git parancssori alkalmaz\u00e1s\u00e1t\u00f3l, az egyszer\u0171bb m\u0171veleteket gyakran gyorsabban v\u00e9gre tudjuk hajtani parancssorb\u00f3l, mint a GUI fel\u00fcleteken t\u00f6rt\u00e9n\u0151 kattintgat\u00e1sokkal. Az al\u00e1bbiakban egy egyszer\u0171 l\u00e9p\u00e9ssorozattal illusztr\u00e1ljuk ezt. Ezeket a t\u00e1rgy keret\u00e9ben nem kell tudni, de hosszabb t\u00e1von mindenk\u00e9ppen hasznos (\u00e9s az ipar\u00e1gban elv\u00e1r\u00e1s is) az ismeret\u00fck.

      1. Repository clone (ezt csak egyszer)

        git clone https://github.com/bmeviauab00/hazi1-2022-myusername

      2. V\u00e1ltoztat\u00e1sok v\u00e9grehajt\u00e1sa a helyi rep\u00f3ban (f\u00e1jlrendszerben, fejleszt\u0151eszk\u00f6zben).

      3. V\u00e1ltoztat\u00e1sok megtekint\u00e9se, mutatja melyek az \u00faj/t\u00f6r\u00f6lt/m\u00f3dosult f\u00e1jlok (nem k\u00f6telez\u0151, csak ha k\u00edv\u00e1ncsiak vagyunk r\u00e1)*

        git status

      4. Minden v\u00e1ltoztat\u00e1s felt\u00e9tele a staging area-ra

        git add -A

        Ha ezt k\u00f6vet\u0151en ism\u00e9t kiadjuk git status parancsot (nem k\u00f6telez\u0151), l\u00e1tjuk, hogy minden v\u00e1ltoz\u00e1s stage-elve van.

      5. Commit

        git commit -m \"megjegyz\u00e9s a commithoz\"

      6. Push

        git push

      Megjegyz\u00e9sek:

      "},{"location":"hazi/imsc-liftsystem/","title":"IMSc. HF - Liftrendszer","text":""},{"location":"hazi/imsc-liftsystem/#bevezetes","title":"Bevezet\u00e9s","text":"

      A h\u00e1zi feladatban egy konzol alap\u00fa alkalmaz\u00e1st kell elk\u00e9sz\u00edteni, mely egy liftrendszert szimul\u00e1l, az Observer tervez\u00e9si mint\u00e1ra \u00e9p\u00edtve.

      A megval\u00f3s\u00edt\u00e1s\u00e9rt 7 IMSc pont szerezhet\u0151.

      A h\u00e1zi feladat az Observer \u00e9s Adapter tervez\u00e9si minta ismeret\u00e9re \u00e9p\u00edt (l\u00e1sd kapcsol\u00f3d\u00f3 tervez\u00e9si mint\u00e1k el\u0151ad\u00e1sanyag).

      A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s. Ez egy konzolos alkalmaz\u00e1s, ak\u00e1r Linux/Mac k\u00f6rnyezetben is megval\u00f3s\u00edthat\u00f3.

      "},{"location":"hazi/imsc-liftsystem/#a-beadas-menete","title":"A bead\u00e1s menete","text":""},{"location":"hazi/imsc-liftsystem/#1-feladat-liftrendszer-alapok","title":"1. feladat - Liftrendszer alapok","text":"

      K\u00e9sz\u00edts egy Lift oszt\u00e1lyt, mely egy emeletes h\u00e1z felvon\u00f3j\u00e1t reprezent\u00e1lja! A k\u00f6vetkez\u0151 tagokkal rendelkezzen:

      K\u00e9sz\u00edts egy LiftDoor oszt\u00e1lyt, mely egy liftajt\u00f3t reprezent\u00e1l:

      A liftrendszer konfigur\u00e1ci\u00f3 \u00f6ssze\u00e1ll\u00edt\u00e1s\u00e9rt \u00e9s a szimul\u00e1ci\u00f3 futtat\u00e1s\u00e1\u00e9rt egy LiftSystemModel oszt\u00e1ly legyen a felel\u0151s. Ennek forr\u00e1sk\u00f3dj\u00e1t al\u00e1bb megadjuk, ebb\u0151l kell egy p\u00e9ld\u00e1nyt a Main f\u00fcggv\u00e9nyben l\u00e9trehozni, \u00e9s a Run f\u00fcggv\u00e9ny\u00e9t megh\u00edvni:

      class LiftSystemModel\n{\n    int iterationCount = 0;\n\n    Lift lift1 = new() { Stairway = 1 };\n    Lift lift2 = new() { Stairway = 2 };\n\n    public LiftSystemModel()\n    {\n        var a1 = new LiftDoor(1, lift1);\n        var a2 = new LiftDoor(2, lift1);\n        var a3 = new LiftDoor(3, lift1);\n        var a4 = new LiftDoor(4, lift1);\n        var a5 = new LiftDoor(5, lift1);\n\n        var b1 = new LiftDoor(1, lift2);\n        var b2 = new LiftDoor(2, lift2);\n        var b3 = new LiftDoor(3, lift2);\n        var b4 = new LiftDoor(4, lift2);\n        var b5 = new LiftDoor(5, lift2);\n    }\n\n    public void Run()\n    {\n        while (true)\n        {\n            Step();\n            Thread.Sleep(1000);\n            iterationCount++;\n        }\n    }\n\n    private void Step()\n    {\n        lift1.Step();\n        lift2.Step();\n\n        if (iterationCount == 0)\n            lift1.Call(5);\n        if (iterationCount == 2)\n            lift2.Call(5);\n\n        if (iterationCount == 6)\n            lift1.Call(1);\n        if (iterationCount == 9)\n            lift2.Call(1);\n    }\n\n}\n

      A fenti k\u00f3d r\u00f6vid magyar\u00e1zata:

      A k\u00f6vetkez\u0151 mozg\u00f3k\u00e9p illusztr\u00e1lja a m\u0171k\u00f6d\u00e9st:

      "},{"location":"hazi/imsc-liftsystem/#2-feladat-liftcontroller-osztaly-bevezetese","title":"2. Feladat - LiftController oszt\u00e1ly bevezet\u00e9se","text":"

      K\u00e9sz\u00edts el egy LiftController oszt\u00e1lyt, mely egy adott liftre vonatkoz\u00f3an folyamatosan meg tudja jelen\u00edteni, mely szinten van \u00e9s mely szintre h\u00edvt\u00e1k utolj\u00e1ra (ez a k\u00f6zponti vez\u00e9rl\u0151terem sz\u00e1m\u00e1ra hasznos).

      A megold\u00e1s illusztr\u00e1l\u00e1sa:

      "},{"location":"hazi/imsc-liftsystem/#3-feladat-meglevo-liftmonitor-osztaly-beillesztese","title":"3. feladat - Megl\u00e9v\u0151 LiftMonitor oszt\u00e1ly beilleszt\u00e9se","text":"

      A feladat a liftek m\u0171k\u00f6d\u00e9si st\u00e1tusz\u00e1r\u00f3l inform\u00e1ci\u00f3 megjelen\u00edt\u00e9se. Eml\u00e9kezz\u00fcnk: a liftek v\u00e9letlenszer\u0171 id\u0151k\u00f6z\u00f6nk\u00e9nt elakadnak, mint ahogy a kor\u00e1bbi le\u00edr\u00e1sban szerepelt! Minden id\u0151pillanatban tudni szeretn\u00e9nk, hogy egy lift m\u0171k\u00f6dik (st\u00e1tusza \"OK\"), vagy el van akadva (st\u00e1tusza \"stuck\"). Ehhez rendelkez\u00e9sre is \u00e1ll az al\u00e1bbi oszt\u00e1ly:

      class LiftMonitor\n{\n    int prevFloor;\n    bool isPrevFloorInitialized;\n\n    public void CheckLift(Lift lift)\n    {\n        Console.SetCursorPosition(lift.Stairway * 20, 13);\n        if (lift.Floor == prevFloor && isPrevFloorInitialized)\n        {\n            Console.Write($\"LiftMonitor: stuck!\");\n        }\n        else\n            Console.Write($\"LiftMonitor: OK    \");\n\n        prevFloor = lift.Floor;\n        isPrevFloorInitialized = true;\n    }\n}\n

      Vegy\u00fck fel a fenti oszt\u00e1lyt!

      Ett\u0151l a pillanatt\u00f3l feltessz\u00fck, hogy a fenti oszt\u00e1lyt egy k\u00f6nyvt\u00e1r form\u00e1j\u00e1ban kaptuk meg, \u00edgy forr\u00e1sk\u00f3dja nem m\u00f3dos\u00edthat\u00f3!

      Illessz\u00fck be az Adapter minta seg\u00edts\u00e9g\u00e9vel a fenti oszt\u00e1lyt a megold\u00e1sunkba:

      A megold\u00e1s m\u0171k\u00f6d\u00e9s\u00e9nek illusztr\u00e1l\u00e1sa:

      "},{"location":"labor/1-model-es-kod-kapcsolata/","title":"1. A modell \u00e9s a k\u00f3d kapcsolata","text":""},{"location":"labor/1-model-es-kod-kapcsolata/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

      A gyakorlat c\u00e9lja:

      Gyakorlatvezet\u0151knek

      B\u00e1r a hallgat\u00f3k k\u00f6z\u00f6tt biztosan vannak olyanok, akik kor\u00e1bban, a Prog2 (C++) t\u00e1rgy keret\u00e9ben vagy m\u00e1s okb\u00f3l kifoly\u00f3lag m\u00e1r haszn\u00e1lt\u00e1k a Visual Studio k\u00f6rnyezetet, szinte biztosan lesznek olyanok is, akik m\u00e9g nem haszn\u00e1lt\u00e1k, vagy m\u00e1r kev\u00e9sb\u00e9 eml\u00e9keznek r\u00e1. A c\u00e9l jelen esetben a fel\u00fclettel val\u00f3 ismerked\u00e9s, ez\u00e9rt a feladatok megold\u00e1sa sor\u00e1n folyamatosan ismertess\u00fck a haszn\u00e1lt dolgokat (pl. Solution Explorer, F5-futtat\u00e1s, breakpoint haszn\u00e1lat stb.), hogy elk\u00e9sz\u00edts\u00fck \u00e9let\u00fcnk els\u0151 C# alkalmaz\u00e1s\u00e1t.

      "},{"location":"labor/1-model-es-kod-kapcsolata/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

      A gyakorlat elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

      Visual Studio-b\u00f3l a legfrissebb verzi\u00f3t c\u00e9lszer\u0171 feltenni. A Community Edition, Professional \u00e9s az Enterprise verzi\u00f3 is megfelel. A Community Edition ingyenes, let\u00f6lthet\u0151 a Microsoft honlapj\u00e1r\u00f3l. A Professional fizet\u0151s, de az egyetem hallgat\u00f3i sz\u00e1m\u00e1ra ez is ingyenesen el\u00e9rhet\u0151 (https://azureforeducation.microsoft.com/devtools honlapon, az Azure Dev Tools for Teaching program keret\u00e9ben).

      Visual Studio Class Diagram t\u00e1mogat\u00e1s

      Jelen gyakorlat bizonyos feladatain\u00e1l (\u00e9s az els\u0151 h\u00e1zi feladat eset\u00e9ben is) a Visual Studio Class Designer t\u00e1mogat\u00e1s\u00e1t haszn\u00e1ljuk. A Visual Studio nem teszi fel minden esetben a Class Designer komponenst a telep\u00edt\u00e9s sor\u00e1n. Ha nem lehet Class Diagram-ot felvenni a Visual Studio projektbe (mert a Class Diagram nem szerepel a list\u00e1ban az Add New Item parancs sor\u00e1n megjelen\u0151 ablak list\u00e1j\u00e1ban \u2013 err\u0151l a jelen \u00fatmutat\u00f3 k\u00e9s\u0151bbi fejezet\u00e9ben b\u0151vebben), akkor a Class Diagram komponenst ut\u00f3lag kell telep\u00edteni:

      1. Visual Studio telep\u00edt\u0151 ind\u00edt\u00e1sa (pl. a Windows Start men\u00fcben a \u201eVisual Studio Installer\u201d beg\u00e9pel\u00e9s\u00e9vel).
      2. A megjelen\u0151 ablakban \u201eIndividual components\u201d f\u00fcl kiv\u00e1laszt\u00e1sa
      3. A keres\u0151mez\u0151be \u201eclass designer\u201d beg\u00e9pel\u00e9se, majd gy\u0151z\u0151dj\u00fcnk meg, hogy a sz\u0171rt list\u00e1ban a \u201eClass Designer\u201d elem ki van pip\u00e1lva.

      Amit \u00e9rdemes \u00e1tn\u00e9zned:

      "},{"location":"labor/1-model-es-kod-kapcsolata/#gyakorlat-menete","title":"Gyakorlat menete","text":"

      A gyakorlatvezet\u0151 a gyakorlat elej\u00e9n \u00f6sszefoglalja a gyakorlatokra vonatkoz\u00f3 k\u00f6vetelm\u00e9nyeket:

      Visual Studio fejleszt\u0151eszk\u00f6zzel, .NET alkalmaz\u00e1sokat fogunk k\u00e9sz\u00edteni C# nyelven. A C# hasonl\u00edt a Java-hoz, fokozatosan ismerj\u00fck meg a k\u00fcl\u00f6nbs\u00e9geket. A gyakorlat vezetett, gyakorlatvezet\u0151 instrukci\u00f3i alapj\u00e1n egy\u00fctt ker\u00fclnek elv\u00e9gz\u00e9sre a feladatok.

      "},{"location":"labor/1-model-es-kod-kapcsolata/#megoldas","title":"Megold\u00e1s","text":"A k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se

      L\u00e9nyeges, hogy a labor sor\u00e1n a laborvezet\u0151t k\u00f6vetve kell dolgozni, tilos (\u00e9s \u00e9rtelmetlen) a k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se. Ugyanakkor az ut\u00f3lagos \u00f6n\u00e1ll\u00f3 gyakorl\u00e1s sor\u00e1n hasznos lehet a k\u00e9sz megold\u00e1s \u00e1ttekint\u00e9se, \u00edgy ezt el\u00e9rhet\u0151v\u00e9 tessz\u00fck.

      A megold\u00e1s GitHubon \u00e9rhet\u0151 el. A legegyszer\u0171bb m\u00f3d a let\u00f6lt\u00e9s\u00e9re, ha parancssorb\u00f3l a git clone utas\u00edt\u00e1ssal lekl\u00f3nozzuk a g\u00e9p\u00fcnkre:

      git clone https://github.com/bmeviauab00/lab-modellkod-kiindulo -b megoldas

      Ehhez telep\u00edtve kell legyen a g\u00e9pre a parancssori git, b\u0151vebb inform\u00e1ci\u00f3 itt.

      "},{"location":"labor/1-model-es-kod-kapcsolata/#1-feladat-hello-world-net-konzol-alkalmazas-elkeszitese","title":"1. Feladat - \u201eHello world\u201d .NET konzol alkalmaz\u00e1s elk\u00e9sz\u00edt\u00e9se","text":"

      A feladat egy olyan C# nyelv\u0171 konzol alkalmaz\u00e1s elk\u00e9sz\u00edt\u00e9se, amely a konzolra ki\u00edrja a \u201eHello world!\u201d sz\u00f6veget.

      Az alkalmaz\u00e1st C# nyelven k\u00e9sz\u00edtj\u00fck el. A leford\u00edtott alkalmaz\u00e1s futtat\u00e1s\u00e1t a .NET runtime v\u00e9gzi. A ford\u00edt\u00e1s/futtat\u00e1s elm\u00e9leti h\u00e1tter\u00e9t, valamint a .NET alapjait az els\u0151 el\u0151ad\u00e1s ismerteti.

      A solution \u00e9s azon bel\u00fcli projekt l\u00e9trehoz\u00e1s\u00e1nak l\u00e9p\u00e9sei Visual Studio 2022 eset\u00e9n:

      1. \u00daj projekt var\u00e1zsl\u00f3 elind\u00edt\u00e1sa, melyre k\u00e9t m\u00f3d is van
        • Ind\u00edt\u00f3ablak seg\u00edts\u00e9g\u00e9vel
          1. Ind\u00edtsuk el a Visual Studio-t
          2. A megjelen\u0151 ind\u00edt\u00f3ablak jobb oldali s\u00e1vj\u00e1ban Create new project
        • M\u00e1r fut\u00f3 Visual Studio-ban
          1. File / New-Project
      2. A Create new project var\u00e1zsl\u00f3ban a Console app (\u00e9s NEM a Console app (.NET Framework) sablont v\u00e1lasszuk ki, ebb\u0151l is a C#-osat. Azt, hogy C#-os, a sablon ikonj\u00e1nak bal fels\u0151 sarka jelzi. Ha nem l\u00e1tjuk a list\u00e1ban, r\u00e1 kell keresni/sz\u0171rni. R\u00e1kereshet\u00fcnk a fels\u0151 keres\u0151s\u00e1vban a \u201econsole\u201d be\u00edr\u00e1s\u00e1val. Vagy az alatta lev\u0151 leny\u00edl\u00f3 mez\u0151k seg\u00edts\u00e9g\u00e9vel: az els\u0151ben (nyelvkiv\u00e1laszt\u00f3) \u201eC#\u201d, a harmadikban (projektt\u00edpus kiv\u00e1laszt\u00f3) \u201eConsole\u201d.

      3. Next gomb az var\u00e1zsl\u00f3ablak alj\u00e1n, a k\u00f6vetkez\u0151 var\u00e1zsl\u00f3oldalon:

        1. Project name: Hello World
        2. Location: a laborokban a c:\\work\\ mapp\u00e1ba dolgozzunk, ehhez van \u00edr\u00e1si jogunk.
        3. Solution name: Hello World (elvileg ez be is lesz \u00edrva, mire ide\u00e9r\u00fcnk)
        4. Place solution and project in the same directory: nincs pipa (de nincs k\u00fcl\u00f6n\u00f6sebb jelent\u0151s\u00e9ge).
        5. Next gomb az var\u00e1zsl\u00f3ablak alj\u00e1n, a k\u00f6vetkez\u0151 var\u00e1zsl\u00f3oldalon:

          1. Framework: .NET 8 (Long-term support).
          2. A \"Do not use top level statements\" jel\u00f6l\u0151n\u00e9gyzetet pip\u00e1ljuk be (ennek magyar\u00e1zat\u00e1ra mindj\u00e1rt visszat\u00e9r\u00fcnk).
        6. A projekttel egy \u00faj solution is l\u00e9trej\u00f6n, mely strukt\u00far\u00e1ja a Visual Studio Solution Explorer ablak\u00e1ban tekinthet\u0151 \u00e1t. Egy solution t\u00f6bb projectb\u0151l \u00e1llhat, egy project pedig t\u00f6bb f\u00e1jlb\u00f3l. A solution a teljes munkak\u00f6rnyezetet fogja \u00f6ssze (egy .sln kiterjeszt\u00e9s\u0171 f\u00e1jl tartozik hozz\u00e1), m\u00edg egy projekt kimenete egy .exe vagy .dll f\u00e1jl jellemz\u0151en, vagyis egy \u00f6sszetett alkalmaz\u00e1s/rendszer egy komponens\u00e9t \u00e1ll\u00edtja el\u0151. A projektf\u00e1jlok kiterjeszt\u00e9se C# alkalmaz\u00e1sok eset\u00e9n .csproj.

          A Program.cs f\u00e1jlunk tartalma a k\u00f6vetkez\u0151:

          Program.cs
          namespace HelloWorld\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n        }\n    }\n}\n

          Vegy\u00fcnk fel egy Console.ReadKey() sort:

          namespace HelloWorld\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n            Console.ReadKey();\n        }\n    }\n}\n
          1. Futtassuk az alkalmaz\u00e1st (pl. az F5 billenty\u0171 haszn\u00e1lat\u00e1val).

            A k\u00f3d fel\u00e9p\u00edt\u00e9se nagyon hasonl\u00edt a Java-hoz, illetve a C++-hoz. Az oszt\u00e1lyaink n\u00e9vterekbe szervezettek. N\u00e9vteret defini\u00e1lni a namespace kulcssz\u00f3val tudunk. N\u00e9vtereket hat\u00f3k\u00f6rbe \u201ehozni\u201d a using kulcssz\u00f3val tudjuk. pl.:

            using System.Collections.Generic;\n
          2. Egy konzolos C# alkalmaz\u00e1sban az alkalmaz\u00e1sunk bel\u00e9p\u00e9si pontj\u00e1t egy statikus Main nev\u0171 f\u00fcggv\u00e9ny meg\u00edr\u00e1s\u00e1val adjuk meg. Az oszt\u00e1lyunk neve b\u00e1rmi lehet, a VS egy Program nev\u0171 oszt\u00e1lyt gener\u00e1lt eset\u00fcnkben. A Main f\u00fcggv\u00e9ny param\u00e9terlist\u00e1ja k\u00f6t\u00f6tt: vagy ne adjunk meg param\u00e9tereket, vagy egy string[]-\u00f6t adjunk meg, amiben fut\u00e1s k\u00f6zben megkapjuk az parancssori argumentumokat.

          3. .NET-ben a standard ki \u00e9s bemenet kezel\u00e9s\u00e9re a System n\u00e9vt\u00e9r Console oszt\u00e1lya haszn\u00e1land\u00f3. A WriteLine statikus m\u0171velet\u00e9vel egy sort tudunk ki\u00edrni, a ReadKey m\u0171velettel egy billenty\u0171 lenyom\u00e1s\u00e1ra v\u00e1rakozhatunk.

          Top level statements, Implicit \u00e9s static usings \u00e9s n\u00e9vterek

          A projekt l\u00e9trehoz\u00e1sakor kor\u00e1bban bepip\u00e1ltuk a \"Do not use top level statements\" jel\u00f6l\u0151n\u00e9gyzetet. Ha ezt nem tett\u00fck volna meg, akkor a Program.cs f\u00e1jlunkban mind\u00f6ssze egyetlen \u00e9rdemi sort tal\u00e1ltunk volna:

          // See https://aka.ms/new-console-template for more information\nConsole.WriteLine(\"Hello World!\");\n

          Ez m\u0171k\u00f6d\u00e9s\u00e9ben ekvivalens a fenti Program oszt\u00e1lyt \u00e9s ebben Main f\u00fcggv\u00e9nyt tartalmaz\u00f3 k\u00f3ddal. N\u00e9zz\u00fck, mik teszik ezt lehet\u0151v\u00e9 (ezekr\u0151l pl. itt https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/top-level-statements olvashatunk b\u0151vebben, mindkett\u0151 C# 10 \u00fajdons\u00e1g):

          • Top level statements. Ennek az a l\u00e9nyege, hogy mindenf\u00e9le oszt\u00e1ly/Main \u00e9s egy\u00e9b f\u00fcggv\u00e9nydefin\u00edci\u00f3 n\u00e9lk\u00fcl a projektben egyetlen forr\u00e1sf\u00e1jlban k\u00f6zvetlen\u00fcl is \u00edrhatunk k\u00f3dot. Ez esetben ezt a sz\u00ednfalak m\u00f6g\u00f6tt a ford\u00edt\u00f3 berakja egy \u00e1ltalunk nem l\u00e1that\u00f3 oszt\u00e1ly statikus Main f\u00fcggv\u00e9ny\u00e9be. A bevezet\u00e9s\u00e9nek a motiv\u00e1ci\u00f3ja az volt, hogy a nagyon egyszer\u0171, \u201escript\u201d szer\u0171 alkalmaz\u00e1sok eset\u00e9n kevesebb legyen a boilerplate k\u00f3d.
          • Implicit global usings. Annak f\u00fcggv\u00e9ny\u00e9ben, hogy pontosan milyen projektt\u00edpust hoztunk l\u00e9tre, bizonyos alapn\u00e9vterek a sz\u00ednfalak m\u00f6g\u00f6tt automatikusan using-olva lesznek minden forr\u00e1sf\u00e1jlban (ehhez a compiler a global using utas\u00edt\u00e1st haszn\u00e1lja). A l\u00e9nyeg: a fejleszt\u0151knek \u00edgy bizonyos, gyakran haszn\u00e1lt n\u00e9vtereket (pl. System.IO, System.Collections.Generic stb.) nem kell a forr\u00e1sf\u00e1jlonk\u00e9nt using-olni.
          • Static using. Lehet\u0151s\u00e9g\u00fcnk van C#-ban n\u00e9vterek helyett statikus oszt\u00e1lyokat is usingolni, \u00edgy azokat a haszn\u00e1latuk sor\u00e1n nem fontos ki\u00edrni. Gyakori eset erre a Console vagy a Math oszt\u00e1ly usingol\u00e1sa.

            using static System.Console;\n\nnamespace ConsoleApp12\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            WriteLine(\"Hello, World!\");\n        }\n    }\n}\n
          • F\u00e1jl szint\u0171 n\u00e9vterek. C# 10-ben szint\u00e9n egy egyszer\u0171s\u00edt\u00e9st kapunk a n\u00e9vterek deklar\u00e1l\u00e1sa sor\u00e1n, mert m\u00e1r nem k\u00f6telez\u0151 a kapcsos z\u00e1r\u00f3jeleket kitenni, \u00edgy az adott namespace a teljes f\u00e1jlra \u00e9rv\u00e9nyes lesz pl.:

            namespace HelloWorld;\n\ninternal class Program\n{\n    // ...\n}\n

          Inconsistent visibility vagy inconsistent accessibility hiba

          A f\u00e9l\u00e9v sor\u00e1n a programoz\u00e1si feladatok megval\u00f3s\u00edt\u00e1sa sor\u00e1n tal\u00e1lkozhatunk inconsistent visibility-re vagy inconsistent accessibility-re panaszkod\u00f3 ford\u00edt\u00e1si hiba\u00fczenetekkel. A jelens\u00e9g h\u00e1tter\u00e9ben az \u00e1ll, hogy .NET k\u00f6rnyezetben lehet\u0151s\u00e9g van az egyes t\u00edpusok (oszt\u00e1ly, interf\u00e9sz stb.) l\u00e1that\u00f3s\u00e1g\u00e1nak szab\u00e1lyoz\u00e1s\u00e1ra:

          • internal vagy nem adjuk meg a l\u00e1that\u00f3s\u00e1got: a t\u00edpus csak az adott szerelv\u00e9nyen (.exe, .dll)/projekten, bel\u00fcl l\u00e1that\u00f3
          • public: a t\u00edpus m\u00e1s szerelv\u00e9nyek/projektek sz\u00e1m\u00e1ra is l\u00e1that\u00f3

          A hiba legegyszer\u0171bben \u00fagy h\u00e1r\u00edthat\u00f3 el, ha minden t\u00edpusunkat publikusnak defini\u00e1ljuk, pl.:

          public class HardDisk\n{\n    // ...\n}\n
          "},{"location":"labor/1-model-es-kod-kapcsolata/#elmeleti-attekintes","title":"Elm\u00e9leti \u00e1ttekint\u00e9s","text":"

          Az alfejezetek nem tartalmaznak feladatot, a hallgat\u00f3k sz\u00e1m\u00e1ra ismertetik a kapcsol\u00f3d\u00f3 elm\u00e9leti t\u00e9mak\u00f6r\u00f6ket, p\u00e9ld\u00e1kkal illusztr\u00e1lva.

          "},{"location":"labor/1-model-es-kod-kapcsolata/#a-az-uml-osztalydiagram-es-a-kod-kapcsolatanak-elmelete-hallgato","title":"A) Az UML oszt\u00e1lydiagram \u00e9s a k\u00f3d kapcsolat\u00e1nak elm\u00e9lete [hallgat\u00f3]*","text":"

          Az anyag itt el\u00e9rhet\u0151: Az UML oszt\u00e1lydiagram \u00e9s a k\u00f3d kapcsolata. Ez a t\u00e9mak\u00f6r kor\u00e1bbi f\u00e9l\u00e9vben a Szoftvertechnol\u00f3gia t\u00e1rgy keret\u00e9ben ker\u00fclt ismertet\u00e9sre.

          "},{"location":"labor/1-model-es-kod-kapcsolata/#b-interfesz-es-absztrakt-ososztaly-hallgato","title":"B) Interf\u00e9sz \u00e9s absztrakt (\u0151s)oszt\u00e1ly [hallgat\u00f3]*","text":"

          Az anyag itt el\u00e9rhet\u0151: Interf\u00e9sz \u00e9s absztrakt (\u0151s)oszt\u00e1ly.

          T\u00e9mak\u00f6r\u00f6k:

          • Absztrakt oszt\u00e1ly fogalma \u00e9s defini\u00e1l\u00e1sa C# nyelven
          • Interf\u00e9sz fogalma \u00e9s defini\u00e1l\u00e1sa C# nyelven
          • Absztrakt \u0151s \u00e9s interf\u00e9sz \u00f6sszehasonl\u00edt\u00e1sa
          "},{"location":"labor/1-model-es-kod-kapcsolata/#2-feladat-az-uml-es-a-kod-kapcsolatanak-szemleltetese","title":"2. Feladat - Az UML \u00e9s a k\u00f3d kapcsolat\u00e1nak szeml\u00e9ltet\u00e9se","text":""},{"location":"labor/1-model-es-kod-kapcsolata/#feladat-leirasa-equipment-inventory","title":"Feladat le\u00edr\u00e1sa - Equipment inventory","text":"

          Feladat: Egy sz\u00e1m\u00edt\u00f3g\u00e9palkatr\u00e9sz nyilv\u00e1ntart\u00f3 alkalmaz\u00e1s kifejleszt\u00e9s\u00e9vel b\u00edztak meg benn\u00fcnket. B\u0151vebben:

          • K\u00fcl\u00f6nb\u00f6z\u0151 t\u00edpus\u00fa alkatr\u00e9szeket kell tudni kezelni. Kezdetben a HardDisk, SoundCard \u00e9s LedDisplay t\u00edpusokat kell t\u00e1mogatni, de a rendszer legyen k\u00f6nnyen b\u0151v\u00edthet\u0151 \u00faj t\u00edpusokkal.
          • Az alkatr\u00e9szekhez tartoz\u00f3 adatok: beszerz\u00e9s \u00e9ve, \u00e9letkora (sz\u00e1m\u00edtott), beszerz\u00e9si \u00e1ra \u00e9s aktu\u00e1lis \u00e1ra (sz\u00e1m\u00edtott), de ezeken fel\u00fcl t\u00edpusf\u00fcgg\u0151 adatokat is tartalmazhatnak (pl. a HardDisk eset\u00e9ben a kapacit\u00e1s).
          • Az aktu\u00e1lis \u00e1r f\u00fcgg az alkatr\u00e9sz t\u00edpus\u00e1t\u00f3l, a beszerz\u00e9si \u00e1rt\u00f3l \u00e9s az alkatr\u00e9sz gy\u00e1rt\u00e1si \u00e9v\u00e9t\u0151l. Pl. min\u00e9l \u00f6regebb egy alkatr\u00e9sz, ann\u00e1l nagyobb kedvezm\u00e9nyt adunk r\u00e1, de a kedvezm\u00e9ny m\u00e9rt\u00e9ke f\u00fcgg az alkatr\u00e9sz t\u00edpust\u00f3l is.
          • List\u00e1zni kell tudni a k\u00e9szleten lev\u0151 alkatr\u00e9szeket.
          • A LedDisplay oszt\u00e1lynak k\u00f6telez\u0151en egy DisplayBase oszt\u00e1lyb\u00f3l kell sz\u00e1rmaznia, \u00e9s a DisplayBase oszt\u00e1ly forr\u00e1sk\u00f3dja nem megv\u00e1ltoztathat\u00f3. Jelen p\u00e9ld\u00e1ban ennek nincs sok \u00e9rtelme, a gyakorlatban azonban gyakran tal\u00e1lkozunk hasonl\u00f3 helyzettel, amikor is az \u00e1ltalunk haszn\u00e1lt keretrendszer/platform el\u0151\u00edrja, hogy adott esetben egy-egy be\u00e9p\u00edtett oszt\u00e1lyb\u00f3l kell sz\u00e1rmaztassunk. Tipikusan ez a helyzet, amikor ablakokkal, \u0171rlapokkal, saj\u00e1t vez\u00e9rl\u0151t\u00edpusokkal dolgozunk: ezeket a keretrendszer be\u00e9p\u00edtett oszt\u00e1lyaib\u00f3l kell sz\u00e1rmaztatnunk, \u00e9s a keretrendszer - pl. Java, .NET - forr\u00e1sk\u00f3dja nem \u00e1ll rendelkez\u00e9s\u00fcnkre (de legal\u00e1bbis biztosan nem akarjuk megv\u00e1ltoztatni). A p\u00e9ld\u00e1nkban a DisplayBase-b\u0151l val\u00f3 sz\u00e1rmaztat\u00e1s el\u0151\u00edr\u00e1s\u00e1val ezt a helyzetet szimul\u00e1ljuk.

          A megval\u00f3s\u00edt\u00e1s sor\u00e1n jelent\u0151s egyszer\u0171s\u00edt\u00e9ssel \u00e9l\u00fcnk: az alkatr\u00e9szeket csak mem\u00f3ri\u00e1ban tarjuk nyilv\u00e1n, a list\u00e1z\u00e1s is a lehet\u0151 legegyszer\u0171bb, egyszer\u0171en csak ki\u00edrjuk a nyilv\u00e1ntartott alkatr\u00e9szek adatait a konzolra.

          A kezdeti egyeztet\u00e9sek sor\u00e1n a megrendel\u0151nkt\u0151l a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3t kapjuk: egy bels\u0151 munkat\u00e1rsuk m\u00e1r elindult a fejleszt\u00e9ssel, de id\u0151 hi\u00e1ny\u00e1ban csak f\u00e9lk\u00e9sz megold\u00e1sig jutott. A feladatunk r\u00e9sz\u00e9t k\u00e9pezi a f\u00e9lk\u00e9sz megold\u00e1s megismer\u00e9se, illetve ebb\u0151l kiindulva kell a feladatot megval\u00f3s\u00edtani.

          "},{"location":"labor/1-model-es-kod-kapcsolata/#class-diagram","title":"Class Diagram","text":"

          Nyissuk meg a megrendel\u0151nkt\u0151l kapott forr\u00e1sk\u00f3d solution-j\u00e9t, melyet a k\u00f6vetkez\u0151 l\u00e9p\u00e9seket k\u00f6vetve tudunk megtenni.

          Ehhez kl\u00f3nozzuk le a kiindul\u00f3 projekt online GitHub rendszerben el\u00e9rhet\u0151 Git repositoryj\u00e1t a C:\\Work mapp\u00e1n bel\u00fcl egy \u00faj saj\u00e1t mapp\u00e1ba: pl.: C:\\Work\\NEPTUN\\lab1. Ebben az \u00faj mapp\u00e1ban nyissunk meg egy command line-t vagy powershellt \u00e9s futtassuk az al\u00e1bbi git parancsot:

          git clone https://github.com/bmeviauab00/lab-modellkod-kiindulo.git\n

          Git \u00e9s GitHub

          A Git-r\u0151l, mint forr\u00e1sk\u00f3dkezel\u0151 rendszerr\u0151l, az els\u0151 h\u00e1zi feladat kontextus\u00e1ban olvashatunk majd b\u0151vebben.

          Nyissuk meg a lekl\u00f3nozott mapp\u00e1ban tal\u00e1lhat\u00f3 src/EquipmentInventory.sln Visual Studio solutiont.

          A Solution Explorerben szemmel fussuk \u00e1t a f\u00e1jlokat. Az meg\u00e9rt\u00e9st seg\u00edten\u00e9, ha egy oszt\u00e1lydiagramon megjelen\u00edten\u00e9nk az oszt\u00e1lyok k\u00f6z\u00f6tti kapcsolatokat. Vegy\u00fcnk is fel egy oszt\u00e1lydiagramot a projekt\u00fcnkbe. A Solution Explorerben a projekten (\u00e9s nem a solution-\u00f6n!) jobb gombbal kattintva a felugr\u00f3 men\u00fcben az Add/New Item elemet v\u00e1lasztva, majd a megjelen\u0151 ablakban a Class Diagram elemet v\u00e1lasszuk ki, az ablak alj\u00e1n a diagram nev\u00e9nek a Main.cd-t adjuk meg, \u00e9s OK-zuk le az ablakot.

          Class Diagram hi\u00e1nyz\u00f3 sablon

          Ha a Class Diagram elem nem jelenik meg a list\u00e1ban, akkor nincs telep\u00edtve a VS megfelel\u0151 komponense. Err\u0151l jelen dokumentum El\u0151felt\u00e9telek fejezet\u00e9ben olvashatsz b\u0151vebben.

          Ekkor a Solution Explorerben megjelenik a Main.cd diagramf\u00e1jl, duplakattint\u00e1ssal nyissuk meg. A diagramunk jelenleg \u00fcres. A Solution Explorerb\u0151l drag&drop-pal dobjuk r\u00e1 a .cs forr\u00e1sf\u00e1jlokat a diagramra. Ekkor a VS megn\u00e9zi, milyen oszt\u00e1lyok vannak ezekben a forr\u00e1sf\u00e1jlokban, \u00e9s visszafejti \u0151ket UML oszt\u00e1lyokk\u00e1. Alak\u00edtsuk ki a k\u00f6vetkez\u0151 \u00e1br\u00e1nak megfelel\u0151 elrendez\u00e9st (az oszt\u00e1lyok tagjainak megjelen\u00edt\u00e9s\u00e9t a t\u00e9glalapuk jobb fels\u0151 sark\u00e1ban lev\u0151 duplany\u00edlra kattint\u00e1ssal \u00e9rhetj\u00fck el):

          Az oszt\u00e1lyokhoz tartoz\u00f3 forr\u00e1sk\u00f3dot is megn\u00e9zhetj\u00fck, ak\u00e1r a diagramon a megfelel\u0151 oszt\u00e1lyra dupl\u00e1n kattintva, ak\u00e1r a Solution Explorerb\u0151l a .cs f\u00e1jlokat megnyitva. A k\u00f6vetkez\u0151ket tapasztaljuk:

          • A SoundCard, HardDisk \u00e9s LedDisplay oszt\u00e1lyok viszonylag j\u00f3l kidolgozottak, rendelkeznek a sz\u00fcks\u00e9ges attrib\u00fatumokkal \u00e9s lek\u00e9rdez\u0151 f\u00fcggv\u00e9nyekkel.
          • Az LedDisplay a k\u00f6vetelm\u00e9nyeknek megfelel\u0151en a DisplayBase oszt\u00e1lyb\u00f3l sz\u00e1rmazik.
          • Az EquipmentInventory felel\u0151s ugyan a k\u00e9szleten lev\u0151 alkatr\u00e9szek nyilv\u00e1ntart\u00e1s\u00e1\u00e9rt, de gyakorlatilag semmi nincs ebb\u0151l megval\u00f3s\u00edtva.
          • Tal\u00e1lunk egy IEquipment interf\u00e9szt, GetAge \u00e9s GetPrice m\u0171veletekkel
          "},{"location":"labor/1-model-es-kod-kapcsolata/#equipmentinventory","title":"EquipmentInventory","text":"

          \u00c1lljunk neki a megold\u00e1s kidolgoz\u00e1s\u00e1nak. El\u0151sz\u00f6r is az alapkoncepci\u00f3kat fektess\u00fck le. Az EquipmentInventory oszt\u00e1lyban egy heterog\u00e9n kollekci\u00f3ban t\u00e1roljuk a k\u00fcl\u00f6nb\u00f6z\u0151 alkatr\u00e9sz t\u00edpusokat. Ez a kulcsa az alkatr\u00e9szek egys\u00e9ges kezel\u00e9s\u00e9nek, vagyis annak, hogy a megold\u00e1sunk \u00faj alkatr\u00e9szt\u00edpusokkal k\u00f6nnyen b\u0151v\u00edthet\u0151 legyen.

          Mint kor\u00e1bban taglaltuk, az egys\u00e9ges kezel\u00e9st vagy k\u00f6z\u00f6s \u0151soszt\u00e1ly, vagy k\u00f6z\u00f6s interf\u00e9sz bevezet\u00e9s\u00e9vel lehet megoldani. Eset\u00fcnkben a k\u00f6z\u00f6s \u0151soszt\u00e1ly (pl. EquipmentBase) \u00fagy t\u0171nik, kiesik, mert ennek bevezet\u00e9s\u00e9vel az LedDisplay oszt\u00e1lynak k\u00e9t \u0151soszt\u00e1lya is lenne: a k\u00f6telez\u0151nek kik\u00f6t\u00f6tt DisplayBase, \u00e9s az \u00e1ltalunk az egys\u00e9ges kezel\u00e9sre bevezetett EquipmentBase. Ez nem lehets\u00e9ges, .NET k\u00f6rnyezetben egy oszt\u00e1lynak csak egy \u0151se lehet. Az a megold\u00e1s pedig, hogy a DisplayBase-t \u00fagy m\u00f3dos\u00edtjuk, hogy \u0151 is az EquipmentBase-b\u0151l sz\u00e1rmazik, a k\u00f6vetelm\u00e9ny\u00fcnknek megfelel\u0151en nem lehets\u00e9ges (kik\u00f6t\u00e9s volt, hogy a forr\u00e1sk\u00f3dja nem m\u00f3dos\u00edthat\u00f3). Marad teh\u00e1t az interf\u00e9sz alap\u00fa megk\u00f6zel\u00edt\u00e9s. Minden bizonnyal az alkalmaz\u00e1s kor\u00e1bbi fejleszt\u0151je is erre a k\u00f6vetkeztet\u00e9sre jutott, ez\u00e9rt is vezette be az IEquipment interf\u00e9szt.

          Vegy\u00fcnk fel egy IEquipment t\u00edpus\u00fa elemekb\u0151l \u00e1ll\u00f3 generikus list\u00e1t (ne property-t hanem field-et!) az EquipmentInventory oszt\u00e1lyba. A l\u00e1that\u00f3s\u00e1ga \u2013 az egys\u00e9gbez\u00e1r\u00e1sra t\u00f6rekedve \u2013 legyen private. A neve legyen equipment (ne legyen \u201es\u201d a v\u00e9g\u00e9n, angolban az equipment t\u00f6bbes sz\u00e1ma is equipment). A tagv\u00e1ltoz\u00f3 felv\u00e9tel\u00e9hez a Visual Studio Class Details ablak\u00e1t haszn\u00e1ljuk. Ha az ablak nem l\u00e1that\u00f3, a View / Other Windows / Class Details men\u00fc kiv\u00e1laszt\u00e1s\u00e1val jelen\u00edthet\u0151 meg.

          A tagv\u00e1ltoz\u00f3 t\u00edpusa teh\u00e1t List<IEquipment>. A .NET List t\u00edpusa egy dinamikusan ny\u00fajt\u00f3zkod\u00f3 generikus t\u00f6mb (mint Java-ban az ArrayList). A diagramon az EquipmentInventory oszt\u00e1lyra pillantva azt l\u00e1tjuk, hogy csak a tagv\u00e1ltoz\u00f3 neve jelenik meg, a t\u00edpusa nem. A diagram h\u00e1tter\u00e9n jobb gombbal kattintva a Change Members Format men\u00fcb\u0151l a Display Full Signature-t v\u00e1lasszuk ki. Ezt k\u00f6vet\u0151en a diagramon l\u00e1that\u00f3v\u00e1 v\u00e1lik a tagv\u00e1ltoz\u00f3k t\u00edpusa, valamint a m\u0171veletek teljes szignat\u00far\u00e1ja.

          Az EquipmentInventory oszt\u00e1lyon dupl\u00e1n kattintva elnavig\u00e1lhatunk a forr\u00e1sk\u00f3dba, \u00e9s mint l\u00e1that\u00f3, val\u00f3ban egy lista t\u00edpus\u00fa tagv\u00e1ltoz\u00f3k\u00e9nt jelenik meg a k\u00f3dban:

          class EquipmentInventory\n{\n    private List<IEquipment> equipment;\n

          Ennek egyr\u00e9szt \u00f6r\u00fcl\u00fcnk, mert a Visual Studio t\u00e1mogatja a round-trip engineering technik\u00e1t: a modellt \u00e9rint\u0151 v\u00e1ltoz\u00e1sokat azonnal \u00e1tvezeti a k\u00f3dba, \u00e9s viszont. M\u00e1sr\u00e9szt a kor\u00e1bbiakban azt taglaltuk, hogy ha egy oszt\u00e1lyban egy gy\u0171jtem\u00e9ny tag van egy m\u00e1sik oszt\u00e1ly elemeib\u0151l, akkor annak az UML modellben egy 1-t\u00f6bb t\u00edpus\u00fa asszoci\u00e1ci\u00f3s kapcsolatk\u00e9nt \u201eillik\u201d megjelennie a k\u00e9t oszt\u00e1ly k\u00f6z\u00f6tt. A modell\u00fcnkben egyel\u0151re nem ezt tapasztaljuk. Szerencs\u00e9re a VS modellez\u0151 fel\u00fclete r\u00e1vehet\u0151, hogy ilyen form\u00e1ban jelen\u00edtse meg ezt a kapcsolatt\u00edpust. Ehhez kattintsunk a diagramon jobb gombbal az equipment tagv\u00e1ltoz\u00f3n, \u00e9s a men\u00fcb\u0151l v\u00e1lasszuk ki a Show as Collection Association elemet. Az IEquipment interf\u00e9szt ezt k\u00f6vet\u0151en mozgassuk ki jobbra, hogy kell\u0151 hely legyen a diagramon az asszoci\u00e1ci\u00f3s kapcsolat \u00e9s a kapcsolaton lev\u0151 szerep (role) adatainak megjelen\u00edt\u00e9s\u00e9re:

          A dupla ny\u00edl v\u00e9gz\u0151d\u00e9s a \u201et\u00f6bbes\u201d oldalon nem szabv\u00e1nyos UML, de ne szomorodjunk el t\u0151le k\u00fcl\u00f6n\u00f6sebben, nincs semmi jelent\u0151s\u00e9ge. Annak mindenk\u00e9ppen \u00f6r\u00fcl\u00fcnk, hogy a kapcsolatot reprezent\u00e1l\u00f3 ny\u00edl az IEquipment v\u00e9g\u00e9n a szerepben a tagv\u00e1ltoz\u00f3 neve (s\u0151t, m\u00e9g a pontos t\u00edpusa is) fel van t\u00fcntetve.

          Navig\u00e1ljunk el az EquipmentInventory forr\u00e1sk\u00f3dj\u00e1hoz, \u00e9s \u00edrjuk meg a konstruktor\u00e1t, ami inicializ\u00e1lja az equipment gy\u0171jtem\u00e9nyt!

          public EquipmentInventory()\n{\n    equipment = new List<IEquipment>();\n}\n

          Ezut\u00e1n \u00edrjuk meg a ListAll met\u00f3dust, ami ki\u00edrja az elemek \u00e9letkor\u00e1t, \u00e9s az aktu\u00e1lis \u00e9rt\u00e9k\u00fcket:

          public void ListAll()\n{\n    foreach (IEquipment eq in equipment)\n    {\n        Console.WriteLine($\"\u00c9letkor: {eq.GetAge()}\\t\u00c9rt\u00e9ke: {eq.GetPrice()}\");\n    }\n}\n

          Az elemeken a foreach utas\u00edt\u00e1ssal iter\u00e1lunk v\u00e9gig. A foreach utas\u00edt\u00e1s haszn\u00e1lata sor\u00e1n az in kulcssz\u00f3 ut\u00e1n egy gy\u0171jtem\u00e9nynek kell \u00e1llnia, az in el\u0151tt pedig egy v\u00e1ltoz\u00f3 deklar\u00e1ci\u00f3nak (eset\u00fcnkben IEquipment eq), ahol a t\u00edpus a gy\u0171jtem\u00e9ny elemt\u00edpusa. Minden iter\u00e1ci\u00f3ban ez a v\u00e1ltoz\u00f3 a gy\u0171jtem\u00e9ny iter\u00e1ci\u00f3beli \u00e9rt\u00e9k\u00e9t veszi fel.

          A Console.WriteLine m\u0171veletnek vagy egy egyszer\u0171 stringet adunk meg, vagy, mint eset\u00fcnkben, egy form\u00e1z\u00e1si stringet. A behelyettes\u00edt\u00e9seket string interpol\u00e1ci\u00f3val oldottuk meg: a behelyettes\u00edtend\u0151 \u00e9rt\u00e9keket {} k\u00f6z\u00f6tt kell megadni. Ha string interpol\u00e1ci\u00f3t haszn\u00e1lunk, a stringnek $ jellel kell kezd\u0151dnie.

          \u00cdrjunk meg egy AddEquipment nev\u0171 f\u00fcggv\u00e9nyt, ami felvesz egy \u00faj eszk\u00f6zt a k\u00e9szletbe:

          public void AddEquipment(IEquipment eq)\n{\n     equipment.Add(eq);\n}\n
          "},{"location":"labor/1-model-es-kod-kapcsolata/#iequipment-megvalositok","title":"IEquipment megval\u00f3s\u00edt\u00f3k","text":"

          Kor\u00e1bbi d\u00f6nt\u00e9s\u00fcnk \u00e9rtelm\u00e9ben az IEquipment interf\u00e9szt haszn\u00e1ljuk az k\u00fcl\u00f6nb\u00f6z\u0151 alkatr\u00e9sz t\u00edpusok egys\u00e9ges kezel\u00e9s\u00e9re. Est\u00fcnkben mind a SoundCard, mind a HardDisk oszt\u00e1ly rendelkezik GetAge() \u00e9s GetPrice() met\u00f3dussal, m\u00e9gsem tudjuk \u0151ket egys\u00e9gesen kezelni (pl. k\u00f6z\u00f6s list\u00e1ban t\u00e1rolni). Ahhoz, hogy ezt meg tudjuk tenni, el kell \u00e9rn\u00fcnk, hogy mindk\u00e9t oszt\u00e1ly megval\u00f3s\u00edtsa az IEquipment interf\u00e9szt. M\u00f3dos\u00edtsuk a forr\u00e1sukat:

          public class SoundCard : IEquipment\n
          public class HardDisk : IEquipment\n

          Ezt k\u00f6vet\u0151en a SoundCard \u00e9s HardDisk oszt\u00e1lyban implement\u00e1lnunk kell az IEquipment interf\u00e9szben lev\u0151 met\u00f3dusokat. Azt tapasztaljuk, hogy ezzel nincs most teend\u0151k, a GetPrice \u00e9s GetAge f\u00fcggv\u00e9nyek m\u00e1r meg vannak \u00edrva mindk\u00e9t helyen.

          Pr\u00f3bak\u00e9ppen a Program.cs f\u00e1jlban tal\u00e1lhat\u00f3 Main f\u00fcggv\u00e9ny\u00fcnkben hozzunk l\u00e9tre egy EquipmentInventory objektumot, t\u00f6lts\u00fck fel HardDisk \u00e9s SoundCard objektumokkal, majd list\u00e1zzuk a k\u00e9sztelet a konzolra. Ammennyiben nem 2021 az aktu\u00e1lis \u00e9v, az al\u00e1bbi sorokn\u00e1l a 2021-es \u00e9vet \u00edrjuk \u00e1t az aktu\u00e1lis \u00e9vre, a 2020-at pedig enn\u00e9l eggyel kisebb sz\u00e1mra!

          static void Main( string[] args )\n{\n    EquipmentInventory ei = new EquipmentInventory();\n\n    ei.AddEquipment(new HardDisk(2021, 30000, 80));\n    ei.AddEquipment(new HardDisk(2020, 25000, 120));\n    ei.AddEquipment(new HardDisk(2020, 25000, 250));\n\n    ei.AddEquipment(new SoundCard(2021, 8000));\n    ei.AddEquipment(new SoundCard(2020, 7000));\n    ei.AddEquipment(new SoundCard(2020, 6000));\n\n    ei.ListAll();\n}\n

          Az alkalmaz\u00e1st futtatva azt tapasztaljuk, hogy b\u00e1r megold\u00e1sunk kezdetleges, de m\u0171k\u00f6dik:

          Folytassuk a munk\u00e1t a LedDisplay oszt\u00e1llyal. A DisplayBase \u0151s forr\u00e1sk\u00f3dj\u00e1t a k\u00f6vetelm\u00e9nyek miatt nem m\u00f3dos\u00edthatjuk. De ez semmif\u00e9le probl\u00e9m\u00e1t nem okoz, a LedDisplay oszt\u00e1lyunk fogja az IEquipment interf\u00e9szt implement\u00e1lni, m\u00f3dos\u00edtsuk a k\u00f3dot ennek megfelel\u0151en:

          public class LedDisplay : DisplayBase, IEquipment\n

          A LedDisplay oszt\u00e1lyban m\u00e1r meg kell \u00edrni az interf\u00e9szben szerepl\u0151 f\u00fcggv\u00e9nyeket:

          public double GetPrice()\n{\n    return this.price;\n}\n\npublic int GetAge()\n{\n    return DateTime.Today.Year - this.manufacturingYear;\n}\n

          B\u0151v\u00edts\u00fck a Main f\u00fcggv\u00e9ny\u00fcnket is, vegy\u00fcnk fel k\u00e9t LedDisplay objektumot a k\u00e9szlet\u00fcnkbe (itt is \u00e9l, hogy ammennyiben nem 2021 az aktu\u00e1lis \u00e9v, az al\u00e1bbi sorokn\u00e1l a 2021-es \u00e9vet \u00edrjuk \u00e1t az aktu\u00e1lis \u00e9vre, a 2020-at pedig enn\u00e9l eggyel kisebb sz\u00e1mra!

          ei.AddEquipment(new LedDisplay(2020, 80000, 17, 16));\nei.AddEquipment(new LedDisplay (2021, 70000, 17, 12));\n\nei.ListAll();\nConsole.ReadKey();\n

          Tesztel\u00e9sk\u00e9ppen futtassuk az alkalmaz\u00e1st.

          "},{"location":"labor/1-model-es-kod-kapcsolata/#3-feladat-az-interfesz-es-az-absztrakt-ososztaly-alkalmazastechnikaja","title":"3. Feladat - Az interf\u00e9sz \u00e9s az absztrakt \u0151soszt\u00e1ly alkalmaz\u00e1stechnik\u00e1ja","text":""},{"location":"labor/1-model-es-kod-kapcsolata/#interfesz-problematikaja","title":"Interf\u00e9sz problematik\u00e1ja","text":"

          \u00c9rt\u00e9kelj\u00fck a jelenlegi, interf\u00e9sz alap\u00fa megold\u00e1sunkat.

          Az egyik f\u0151 probl\u00e9ma, hogy k\u00f3dunk tele van a karbantarthat\u00f3s\u00e1got \u00e9s b\u0151v\u00edthet\u0151s\u00e9get rombol\u00f3 k\u00f3dduplik\u00e1ci\u00f3val:

          • A yearOfCreation \u00e9s newPrice tagok minden alkatr\u00e9sz t\u00edpusban (kiv\u00e9ve a speci\u00e1lis LedDisplay-t) k\u00f6z\u00f6sek, ezeket \u00faj t\u00edpus bevezet\u00e9sekor is copy-paste technik\u00e1val \u00e1t kell venni.
          • A GetAge f\u00fcggv\u00e9ny implement\u00e1ci\u00f3ja szinten minden alkatr\u00e9sz t\u00edpusban (kiv\u00e9ve a speci\u00e1lis LedDisplay-t) azonos, szint\u00e9n copy-paste-tel \u201eszapor\u00edtand\u00f3\u201d.
          • A konstruktorok yearOfCreation \u00e9s newPrice tagokat inicializ\u00e1l\u00f3 sorai szint\u00e9n duplik\u00e1ltak az egyes oszt\u00e1lyokban.

          B\u00e1r ez a k\u00f3dduplik\u00e1ci\u00f3 egyel\u0151re nem t\u0171nik jelent\u0151snek, \u00faj alkatr\u00e9sz t\u00edpusok bevezet\u00e9s\u00e9vel egyre ink\u00e1bb elm\u00e9rgesedik a helyzet, jobb id\u0151ben elej\u00e9t venni a j\u00f6v\u0151beli f\u00e1jdalmaknak.

          A m\u00e1sik probl\u00e9ma abb\u00f3l ad\u00f3dik, hogy az alkatr\u00e9sz adatok list\u00e1z\u00e1sa jelenleg f\u00e1jdalmasan hi\u00e1nyos, nem jelenik meg az alkatr\u00e9sz t\u00edpusa (csak a kora \u00e9s az \u00e1ra). A t\u00edpus megjelen\u00edt\u00e9s\u00e9hez az IEquipment interf\u00e9szt b\u0151v\u00edteni kell, pl. egy GetDescription nev\u0171 m\u0171velet bevezet\u00e9s\u00e9vel. Vegy\u00fcnk is fel egy GetDescription f\u00fcggv\u00e9nyt az interf\u00e9szbe!

          public interface IEquipment\n{\n    double GetPrice();\n    int GetAge();\n    string GetDescription();\n}\n

          Ekkor minden IEquipment interf\u00e9szt implement\u00e1l\u00f3 oszt\u00e1lyban meg kellene val\u00f3s\u00edtani ezt a met\u00f3dust is, ami sok oszt\u00e1ly eset\u00e9n sok munka (valamint egy t\u00f6bbkomponens\u0171, vagyis t\u00f6bb DLL-b\u0151l \u00e1ll\u00f3 alkalmaz\u00e1s eset\u00e9ben, amikor ezek nem egy fejleszt\u0151 c\u00e9g kez\u00e9ben vannak, sokszor nem is megoldhat\u00f3). A Build parancs futtat\u00e1s\u00e1val ellen\u0151rizz\u00fck, hogy a GetDescription felv\u00e9tele ut\u00e1n h\u00e1rom helyen is ford\u00edt\u00e1si hib\u00e1t kapunk.

          Interf\u00e9szben alap\u00e9rtelmezett implement\u00e1ci\u00f3 megad\u00e1sa

          \u00c9rdemes tudni, hogy C# 8-t\u00f3l (illetve .NET vagy .NET Core runtime is kell hozz\u00e1, .NET Framework alatt nem t\u00e1mogatott) kezdve interf\u00e9sz m\u0171veleteknek is lehet alap\u00e9rtelmezett implement\u00e1ci\u00f3t adni (default interface methods), \u00edgy a fenti probl\u00e9ma megold\u00e1s\u00e1hoz nincs sz\u00fcks\u00e9g absztrakt oszt\u00e1lyra, de interf\u00e9sznek tov\u00e1bbiakban sem lehet tagv\u00e1ltoz\u00f3ja. B\u0151vebben inform\u00e1ci\u00f3 itt: default interface methods.

          public interface IEquipment\n{\n    double GetPrice();\n    int GetAge();\n    string GetDescription() { return \"EquipmentBase\"; }\n}\n
          "},{"location":"labor/1-model-es-kod-kapcsolata/#absztrakt-osztaly","title":"Absztrakt oszt\u00e1ly","text":"

          Mindk\u00e9t probl\u00e9m\u00e1ra megold\u00e1st jelent egy k\u00f6z\u00f6s absztrakt \u0151s bevezet\u00e9se (kiv\u00e9ve az LedDisplay oszt\u00e1lyt, amire m\u00e9g visszat\u00e9r\u00fcnk). Ebbe fel tudjuk k\u00f6lt\u00f6ztetni a lesz\u00e1rmazottakra k\u00f6z\u00f6s k\u00f3dot, valamint az \u00fajonnan bevezetett GetDescription m\u0171velethez egy alap\u00e9rtelmezett implement\u00e1ci\u00f3t tudunk megadni. Legyen az \u00faj absztrakt \u0151soszt\u00e1lyunk neve EquipmentBase. K\u00e9rd\u00e9s, sz\u00fcks\u00e9g van-e a tov\u00e1bbiakban az IEquipment interf\u00e9szre, vagy az teljesen kiv\u00e1lthat\u00f3 az \u00faj EquipmentBase oszt\u00e1llyal. Az IEquipment interf\u00e9szt meg kell tartsuk, mert a LedDisplay oszt\u00e1lyunkat nem tudjuk az EquipmentBase-b\u0151l sz\u00e1rmaztatni: m\u00e1r van egy k\u00f6telez\u0151en el\u0151\u00edrt \u0151soszt\u00e1lya, a DisplayBase: emiatt az EquipmentInventory a tov\u00e1bbfejlesztett megold\u00e1sunkban is IEquipment interf\u00e9szk\u00e9nt hivatkozik az k\u00fcl\u00f6nb\u00f6z\u0151 alkatr\u00e9szekre.

          \u00c1lljunk is neki az \u00e1talak\u00edt\u00e1snak. Legyen az oszt\u00e1lydiagramunk az akt\u00edv tabf\u00fcl. A Toolbox-b\u00f3l drag&drop-pal dobjunk fel egy Abstract Class elemet a diagramra, a neve legyen EquipmentBase.

          A k\u00f6vetkez\u0151kben azt kell el\u00e9rj\u00fck, hogy a SoundCard \u00e9s a HardDisk oszt\u00e1lyok sz\u00e1rmazzanak az EquipmentBase-b\u0151l (a LedDisplay-nek m\u00e1r van m\u00e1sik \u0151se, \u00edgy ott ezt nem tudjuk megtenni). Ehhez v\u00e1lasszuk ki az Inheritance kapcsolatot a Toolbox-ban, majd h\u00fazzunk egy-egy vonalat a gyermekoszt\u00e1lyb\u00f3l kiindulva az \u0151soszt\u00e1lyba a SoundCard \u00e9s HardDisk eset\u00e9ben egyar\u00e1nt.

          A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben alak\u00edtsuk \u00e1t \u00fagy a k\u00f3dot, hogy ne a HardDisk \u00e9s SoundCard val\u00f3s\u00edts\u00e1k meg k\u00fcl\u00f6n-k\u00fcl\u00f6n az IEquipment interf\u00e9szt, hanem a k\u00f6z\u00f6s \u0151s\u00fck, az EquipmentBase egyszer. Ehhez m\u00f3dos\u00edtsuk az EquipmentBase oszt\u00e1lyt \u00fagy, hogy val\u00f3s\u00edtsa meg az interf\u00e9szt (ak\u00e1r a diagramon h\u00fazzunk be egy inheritance kapcsolatot az EquipmentBase-b\u0151l az IEquipment-be, vagy az EquipmentBase forr\u00e1sk\u00f3dj\u00e1t m\u00f3dos\u00edtsuk). A HardDisk \u00e9s SoundCard oszt\u00e1lyokb\u00f3l t\u00f6r\u00f6lj\u00fck az IEquipment megval\u00f3s\u00edt\u00e1s\u00e1t (az \u0151s m\u00e1r implement\u00e1lja).

          A diagramunk \u00e9s a forr\u00e1sk\u00f3dunk vonatkoz\u00f3 r\u00e9szei ezt k\u00f6vet\u0151en \u00edgy n\u00e9znek ki:

          public abstract class EquipmentBase : IEquipment\n
          public class HardDisk : EquipmentBase\n
          public class SoundCard : EquipmentBase\n

          A k\u00f3dunk m\u00e9g nem fordul, ennek t\u00f6bb oka is van. Az EquipmentBase implement\u00e1lja az IEquipment interf\u00e9szt, de m\u00e9g nincsenek benne implement\u00e1lva az interf\u00e9sz m\u0171veletei. Vagy gener\u00e1ltassuk le a met\u00f3dusokat a smart tag haszn\u00e1lat\u00e1val, vagy g\u00e9pelj\u00fck be a k\u00f6vetkez\u0151 elveknek megfelel\u0151en:

          • A newPrice \u00e9s yearOfCreation duplik\u00e1lva vannak a HardDisk \u00e9s SoundCard oszt\u00e1lyokban: mozgassuk (\u00e9s ne m\u00e1soljuk!) \u00e1t ezeket a k\u00f6z\u00f6s EquipmentBase \u0151sbe, \u00e9s protected l\u00e1that\u00f3s\u00e1got adjunk meg.
          • A GetAge m\u0171velet duplik\u00e1lva van a HardDisk \u00e9s SoundCard oszt\u00e1lyokban, ezekb\u0151l t\u00f6r\u00f6lj\u00fck ki az implement\u00e1ci\u00f3t \u00e9s vigy\u00fck \u00e1t az EquipmentBase oszt\u00e1lyba.
          • A GetPrice m\u0171veletet absztrakt m\u0171veletk\u00e9nt vegy\u00fck fel az \u0151sbe. Ez sz\u00e1nd\u00e9kos tervez\u0151i d\u00f6nt\u00e9s, \u00edgy r\u00e1k\u00e9nyszer\u00edtj\u00fck a lesz\u00e1rmazott oszt\u00e1lyokat, hogy mindenk\u00e9ppen defini\u00e1lj\u00e1k fel\u00fcl ezt a m\u0171veletet.
          • A GetDescription eset\u00e9ben viszont pont ford\u00edtottja a helyzet: ezt virtu\u00e1lisnak defini\u00e1ljuk (\u00e9s nem absztraktnak), vagyis m\u00e1r az \u0151sben is adunk meg implement\u00e1ci\u00f3t. \u00cdgy a lesz\u00e1rmazottak nincsenek r\u00e1k\u00e9nyszer\u00edtve a m\u0171velet fel\u00fcldefini\u00e1l\u00e1s\u00e1ra.

          A fentieknek megfelel\u0151 k\u00f3d a k\u00f6vetkez\u0151:

          public abstract class EquipmentBase : IEquipment\n{\n    protected int yearOfCreation;\n    protected int newPrice;\n\n    public int GetAge()\n    {\n        return DateTime.Today.Year - yearOfCreation;\n    }\n\n    public abstract double GetPrice();\n\n    public virtual string GetDescription()\n    {\n        return \"EquipmentBase\";\n    }\n}\n

          N\u00e9h\u00e1ny kieg\u00e9sz\u00edt\u0151 gondolat a k\u00f3dr\u00e9szletre vonatkoz\u00f3an:

          • Az absztrakt oszt\u00e1lyok eset\u00e9ben az abstract kulcssz\u00f3t ki kell \u00edrni a class sz\u00f3 el\u00e9.
          • Az absztrakt m\u0171veletek eset\u00e9ben az abstract kulcssz\u00f3t kell megadni
          • .NET k\u00f6rnyezetben lehet\u0151s\u00e9g\u00fcnk van szab\u00e1lyozni, hogy egy m\u0171velet virtu\u00e1lis-e vagy sem. Ebb\u0151l a szempontb\u00f3l a C++ nyelvhez hasonl\u00edt. Amennyiben egy m\u0171veletet virtu\u00e1liss\u00e1 szeretn\u00e9nk tenni, a virtual kulcssz\u00f3t kell a m\u0171veletre megadni. Eml\u00e9keztet\u0151: akkor defini\u00e1ljunk egy m\u0171veletet virtu\u00e1lisnak, ha a lesz\u00e1rmazottak azt fel\u00fcldefini\u00e1l(hat)j\u00e1k. Csak ekkor garant\u00e1lt, hogy egy \u0151sreferenci\u00e1n megh\u00edvva az adott m\u0171veletet a lesz\u00e1rmazottbeli verzi\u00f3 h\u00edv\u00f3dik meg.
          "},{"location":"labor/1-model-es-kod-kapcsolata/#leszarmazottak","title":"Lesz\u00e1rmazottak","text":"

          A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben t\u00e9rj\u00fcnk \u00e1t az EquipmentBase lesz\u00e1rmazottakra. C# nyelven az absztrakt \u00e9s virtu\u00e1lis m\u0171veletek fel\u00fcldefini\u00e1l\u00e1sakor a lesz\u00e1rmazottban meg kell adni az override kulcssz\u00f3t. Els\u0151 l\u00e9p\u00e9sben a GetPrice m\u0171veletet defini\u00e1ljuk fel\u00fcl:

          HardDisk.cs
          public override double GetPrice()\n{\n    return yearOfCreation < (DateTime.Today.Year - 4)\n        ? 0\n        : newPrice - (DateTime.Today.Year - yearOfCreation) * 5000;\n}\n
          SoundCard.cs
          public override double GetPrice()\n{\n    return yearOfCreation < (DateTime.Today.Year - 4)\n        ? 0 \n        : newPrice - (DateTime.Today.Year - yearOfCreation) * 2000;\n}\n

          A k\u00f6vetkez\u0151kben l\u00e9p\u00e9sben a GetDescription m\u0171veletet \u00edrjuk meg a HardDisk \u00e9s SoundCard oszt\u00e1lyokban. Mivel itt az \u0151sbeli virtu\u00e1lis f\u00fcggv\u00e9nyt defini\u00e1ljuk fel\u00fcl, szint\u00e9n meg kell adni az override kulcssz\u00f3t:

          HardDisk.cs
          public override string GetDescription()\n{\n    return \"Hard Disk\";\n}\n
          SoundCard.cs
          public override string GetDescription()\n{\n    return \"Sound Card\";\n}\n

          Felmer\u00fclhet benn\u00fcnk a k\u00e9rd\u00e9s, mi\u00e9rt d\u00f6nt\u00f6ttek \u00fagy a C# nyelv tervez\u0151i, hogy a m\u0171veletek fel\u00fcldefini\u00e1l\u00e1sakor egy extra kulcssz\u00f3t kelljen megadni, hasonl\u00f3ra pl. a C++ nyelv eset\u00e9ben nem volt sz\u00fcks\u00e9g. Az ok egyszer\u0171: a k\u00f3d \u00edgy kifejez\u0151bb. A lesz\u00e1rmazottak k\u00f3dj\u00e1t n\u00e9zve az override sz\u00f3 azonnal egy\u00e9rtelm\u0171v\u00e9 teszi, hogy valamelyik \u0151sben ez a m\u0171velet absztrakt vagy virtu\u00e1lis, nem kell valamennyi \u0151s k\u00f3dj\u00e1t ehhez \u00e1ttekinteni.

          "},{"location":"labor/1-model-es-kod-kapcsolata/#leddisplay-ose","title":"LedDisplay \u0151se","text":"

          A LedDisplay oszt\u00e1lyunk \u0151se meg van k\u00f6tve, annak k\u00f3dja nem m\u00f3dos\u00edthat\u00f3, \u00edgy nem tudjuk az EquipmentBase-b\u0151l sz\u00e1rmaztatni. A GetAge m\u0171veletet \u00edgy nem tudjuk t\u00f6r\u00f6lni, ez a k\u00f3dduplik\u00e1ci\u00f3 itt megmarad (de csak a LedDisplay eset\u00e9ben, ami csak egy oszt\u00e1ly a sok k\u00f6z\u00fcl!).

          Note

          Val\u00f3j\u00e1ban egy kis plusz munk\u00e1val ett\u0151l a duplik\u00e1ci\u00f3t\u00f3l is meg tudn\u00e1nk szabadulni. Ehhez valamelyik oszt\u00e1lyban (pl. EquipmentBase) fel kellene venni egy statikus seg\u00e9df\u00fcggv\u00e9nyt, mely param\u00e9terben megkapn\u00e1 a gy\u00e1rt\u00e1si \u00e9vet, \u00e9s visszaadn\u00e1 az \u00e9letkort. Az EquipmentBase.GetAge \u00e9s a LedDisplay.GetAge ezt a seg\u00e9df\u00fcggv\u00e9nyt haszn\u00e1ln\u00e1 kimenete el\u0151\u00e1ll\u00edt\u00e1s\u00e1ra.

          A LedDisplay oszt\u00e1lyunkban ad\u00f3sak vagyunk m\u00e9g a GetDescription meg\u00edr\u00e1s\u00e1val:

          LedDisplay.cs
          public string GetDescription()\n{\n    return \"Led Display\";\n}\n

          Figyelj\u00fck meg, hogy itt NEM adtuk meg az override kulcssz\u00f3t. Mikor egy interf\u00e9sz f\u00fcggv\u00e9nyt implement\u00e1lunk, az override-ot nem kell/szabad ki\u00edrni.

          "},{"location":"labor/1-model-es-kod-kapcsolata/#getdescription-hasznalata","title":"GetDescription haszn\u00e1lata","text":"

          M\u00f3dos\u00edtsuk az EquipmentInventory.ListAll m\u0171velet\u00e9t, hogy az elemek le\u00edr\u00e1s\u00e1t is \u00edrja ki a kimenetre:

          EquipmentInventory.cs
          public void ListAll()\n{\n    foreach (IEquipment eq in equipment)\n    {\n        Console.WriteLine($\"Le\u00edr\u00e1s: {eq.GetDescription()}\\t\" +\n            $\"\u00c9letkor: {eq.GetAge()}\\t\u00c9rt\u00e9ke: {eq.GetPrice()}\");\n    }\n}\n

          \u00cdgy m\u00e1r sokkal informat\u00edvabb kimetet kapunk az alkalmaz\u00e1s futtat\u00e1sakor:

          "},{"location":"labor/1-model-es-kod-kapcsolata/#konstruktor-kodduplikacio","title":"Konstruktor k\u00f3dduplik\u00e1ci\u00f3","text":"

          A k\u00f3dunkat \u00e1ttekintve m\u00e9g egy helyen tal\u00e1lunk k\u00f3dduplik\u00e1ci\u00f3t. Valamennyi EquipmentBase lesz\u00e1rmazott (HardDisk, SoundCard) konstruktor\u00e1ban ott van ez a k\u00e9t sor:

           this.yearOfCreation = yearOfCreation;\n this.newPrice = newPrice;\n

          Ha belegondolunk, ezek a yearOfCreation \u00e9s newPrice tagok az \u0151sben vannak defini\u00e1lva, \u00edgy egy\u00e9bk\u00e9nt is az \u0151 felel\u0151ss\u00e9ge kellene legyen ezek inicializ\u00e1l\u00e1sa. Vegy\u00fcnk is fel egy megfelel\u0151 konstruktort az EquipmentBase-ben:

          EquipmentBase.cs
          public EquipmentBase(int yearOfCreation, int newPrice)\n{\n    this.yearOfCreation = yearOfCreation;\n    this.newPrice = newPrice;\n}\n

          A HardDisk \u00e9s SoundCard lesz\u00e1rmazottak konstruktor\u00e1nak t\u00f6rzs\u00e9b\u0151l vegy\u00fck ki a k\u00e9t tag inicializ\u00e1l\u00e1s\u00e1t, helyette a base kulcssz\u00f3val hivatkozva h\u00edvjuk meg az \u0151s konstruktor\u00e1t:

          HardDisk.cs
          public HardDisk(int yearOfCreation, int newPrice, int capacityGB)\n    : base(yearOfCreation, newPrice)\n{\n    this.capacityGB = capacityGB;\n}\n
          SoundCard.cs
          public SoundCard(int yearOfCreation, int newPrice)\n    : base(yearOfCreation, newPrice)\n{\n}\n
          "},{"location":"labor/1-model-es-kod-kapcsolata/#ertekeles","title":"\u00c9rt\u00e9kel\u00e9s","text":"

          Az interf\u00e9sz \u00e9s absztrakt \u0151s egy\u00fcttes haszn\u00e1lat\u00e1val siker\u00fclt a legkevesebb kompromisszummal j\u00e1r\u00f3 megold\u00e1st kidolgoznunk:

          • IEquipment interf\u00e9szk\u00e9nt hivatkozva egys\u00e9gesen tudjuk kezelni az alkatr\u00e9szek valamennyi t\u00edpus\u00e1t, m\u00e9g azokat is, melyekn\u00e9l az \u0151soszt\u00e1ly meg volt k\u00f6tve (puszt\u00e1n absztrakt \u0151s haszn\u00e1lat\u00e1val ezt nem tudtuk volna el\u00e9rni).
          • Az EquipmentBase absztrakt \u0151s bevezet\u00e9s\u00e9vel egy kiv\u00e9telt\u0151l eltekintve a k\u00fcl\u00f6nb\u00f6z\u0151 alkatr\u00e9szt\u00edpusokra k\u00f6z\u00f6s k\u00f3dot fel tudtuk vinni egy k\u00f6z\u00f6s \u0151sbe, \u00edgy el tudtuk ker\u00fclni a k\u00f3dduplik\u00e1ci\u00f3t.
          • Az EquipmentBase absztrakt \u0151s bevezet\u00e9s\u00e9vel alap\u00e9rtelmezett implement\u00e1ci\u00f3t tudunk megadni az \u00fajonnan bevezetett IEquipment m\u0171veletek eset\u00e9ben (pl. GetDescripton), \u00edgy nem vagyunk r\u00e1k\u00e9nyszer\u00edtve, hogy minden IEquipment implement\u00e1ci\u00f3s oszt\u00e1lyban meg kelljen azt adni.

          Z\u00e1r\u00e1sk\u00e9ppen vess\u00fcnk egy pillant\u00e1st megold\u00e1sunk UML (szer\u0171) oszt\u00e1lydiagramj\u00e1ra:

          C# 11 - Statikus interf\u00e9szek

          A C# 11 leg\u00fajabb \u00fajdons\u00e1ga a statikus interf\u00e9sz tagok defini\u00e1l\u00e1sa, amivel olyan tagokat k\u00f6vetelhet\u00fcnk meg az implement\u00e1l\u00f3 oszt\u00e1lyt\u00f3l, amelyek nem az objektum p\u00e9ld\u00e1nyra vonatkoznak, hanem az oszt\u00e1lynak kell egy adott statikus taggal rendelkeznie. B\u0151vebben

          "},{"location":"labor/1-model-es-kod-kapcsolata/#megjegyzes-opcionalis-hazi-gyakorlo-feladat","title":"Megjegyz\u00e9s - opcion\u00e1lis h\u00e1zi gyakorl\u00f3 feladat","text":"

          Jelen megold\u00e1sunk nem t\u00e1mogatja az alkatr\u00e9szspecifikus adatok (pl. HardDisk eset\u00e9ben a kapacit\u00e1s) megjelen\u00edt\u00e9s\u00e9t a list\u00e1z\u00e1s sor\u00e1n. Ahhoz, hogy ezt meg tudjuk tenni, az alkatr\u00e9sz adatok form\u00e1zott stringbe \u00edr\u00e1s\u00e1t az EqipmentInventory oszt\u00e1lyb\u00f3l az alkatr\u00e9sz oszt\u00e1lyokba kellene vinni, a k\u00f6vetkez\u0151 elveknek megfelel\u0151en:

          • Bevezethet\u00fcnk ehhez az IEquipment interf\u00e9szbe egy GetFormattedString m\u0171veletet, mely egy string t\u00edpus\u00fa objektummal t\u00e9r vissza. Alternat\u00edv megold\u00e1s lehet, ha a System.Object ToString() m\u0171velet\u00e9t defini\u00e1ljuk fel\u00fcl. .NET-ben ugyanis minden t\u00edpus implicit m\u00f3don a System.Object-b\u0151l sz\u00e1rmazik, aminek van egy virtu\u00e1lis ToString() m\u0171velete.
          • Az EquipmentBase-ben meg\u00edrjuk a k\u00f6z\u00f6s tagok (le\u00edr\u00e1s, \u00e1r, kor) stringbe form\u00e1z\u00e1s\u00e1t.
          • Amennyiben egy alkatr\u00e9sz t\u00edpusspecifikus adattal is rendelkezik, akkor oszt\u00e1ly\u00e1ban override-oljuk a stringbe form\u00e1z\u00f3 f\u00fcggv\u00e9nyt: ennek a f\u00fcggv\u00e9nynek egyr\u00e9szt meg kell h\u00edvnia az \u0151s v\u00e1ltozat\u00e1t (a base kulcssz\u00f3 haszn\u00e1lat\u00e1val), majd ehhez hozz\u00e1 kell f\u0171zni a saj\u00e1t form\u00e1zott adatait, \u00e9s ezzel a stringgel kell visszat\u00e9rnie.
          "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/","title":"1. Relationship between the model and the code","text":""},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#the-goal-of-the-exercise","title":"The goal of the exercise","text":"

          The goal of the exercise:

          • Getting to know the students/trainer
          • Clarification of the requirements for exercises
          • Getting started with Visual Studio and .NET application development.
          • Building a simple Hello World .NET application, C# basics
          • Illustrating the relationship between UML and code
          • The interface and the abstract primitive class application technique
          For teachers

          While there will certainly be some students who have used the Visual Studio environment before, in Prog2 (C++) or for other reasons, there will almost certainly be others who have not used it or who remember it less. The goal here is to get familiar with the interface, so as you work through the exercises, you will be introduced to the things you use (e.g. Solution Explorer, F5 running, using breakpoints, etc.) to build your first C# application.

          "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#prerequisites","title":"Prerequisites","text":"

          The tools needed to carry out the exercise:

          • Visual Studio 2022

          The latest version of Visual Studio should be installed. The Community Edition, Professional and Enterprise versions are also suitable. The Community Edition is free and can be downloaded from the Microsoft website. The Professional is paid, but it is also available free of charge to students of the university (on the website, as part of the Azure Dev Tools for Teaching programme).

          Visual Studio Class Diagram support

          For some of the exercises in this exercise (and also for the first homework) we will use the Visual Studio Class Designer support. Visual Studio does not always add the Class Designer component during installation. If it is not possible to add a Class Diagram to your Visual Studio project (because the Class Diagram is not listed in the list of the window that appears during the Add New Item command - more on this later in this guide), you will need to install the Class Diagram component later:

          1. Start the Visual Studio installer (e.g. by typing \"Visual Studio Installer\" in the Windows Start menu).
          2. In the window that appears, select the \"Individual components\" tab
          3. In the search box, type \"class designer\" and then make sure that \"Class Designer\" is unchecked in the filtered list.

          What you should check out:

          • The exercise does not include a lecture on the subject. At the same time, the exercise builds on basic UML knowledge and the basics of mapping UML class diagrams to code.
          "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#course-of-exercise","title":"Course of exercise","text":"

          The trainer will summarise the requirements for the exercises at the beginning of the exercise:

          • Most of these can be found in the fact sheet
          • Information on homework is available on the subject's website.

          Using Visual Studio development tool, we will build .NET applications in C#. C# is similar to Java, we will gradually learn the differences. The tutorial is guided, with instructions from the tutor, and the tasks are done together.

          "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#solution","title":"Solution","text":"Download the finished solution

          It is essential that you follow the lab guide during the lab, it is forbidden (and pointless) to download the ready-made solution. However, during subsequent self-practice, it can be useful to review the ready-made solution, so we make it available.

          The solution is available on GitHub. The easiest way to download it is to clone it from the command line to your machine using the git clone command:

          git clone https://github.com/bmeviauab00/lab-modellkod-kiindulo -b solved

          You need to have git installed on your machine, more information here.

          "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#1-task-build-a-hello-world-net-console-application","title":"1. Task - Build a \"Hello world\" .NET console application","text":"

          The task is to create a C# console application that prints the text \"Hello world!\" to the console.

          The application is written in C#. The compiled application is run by the .NET runtime. The theoretical background of compiling/running and the basics of .NET are covered in the first lecture.

          The steps to create a solution and a project within it in Visual Studio 2022:

          1. Start a new project wizard, which can be done in two ways
            • Using the startup window
              1. Launch Visual Studio
              2. In the right-hand sidebar of the launch window that appears Create new project
            • Already running in Visual Studio
              1. File / New-Project
          2. In the Create new project wizard, select the Console app (and NOT the Console app (.NET Framework) template, including the C# one. That it is C# is indicated by the top left corner of the template icon. If you don't see it in the list, you have to search/filter for it. You can search for it by typing \"console\" in the top search bar. Or by using the drop-down boxes below: in the first (language selector) \"C#\", in the third (project type selector) \"Console\".

            Creating a project

          3. Next button at the bottom of the wizard window, on the next wizard page:

            1. Project name: Hello World
            2. Location: in the labs, work in the c:\\work\\ folder, you have write access to it.
            3. Solution name: Hello World (this should be written in by the time we get here)
            4. Place solution and project in the same directory: no tick (but not particularly significant).
            5. Next button at the bottom of the wizard window, on the next wizard page:

              1. Framework: .NET 8 (Long-term support).
              2. Check the \"Do not use top level statements\" checkbox (we'll explain this in a moment).
            6. The project also creates a new solution, whose structure can be viewed in the Visual Studio Solution Explorer window. A solution can consist of several projects, and a project can consist of several files. A solution is a summary of the entire working environment (it includes a file with the extension .sln), while the output of a project is typically a file .exe or .dll, i.e. a component of a complex application/system. The project file extension for C# applications is .csproj.

              The content of our Program.cs file is as follows:

              Program.cs
              namespace HelloWorld\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n        }\n    }\n}\n

              Take a Console.ReadKey() line:

              namespace HelloWorld\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n            Console.ReadKey();\n        }\n    }\n}\n
              1. Run the application (e.g. using the F5 key).

                The structure of the code is very similar to Java and C++. Our classes are organised into namespaces. You can define a namespace with the keyword namespace. You can \"scope\" namespaces with the using keyword. e.g:

                using System.Collections.Generic;\n
              2. In a console C# application, you specify the entry point of your application by writing a static function called Main. Our class name can be anything, VS generated a class called Program in our case. The parameter list of the Main function is bound: either no parameters are given, or a string[] is given, in which the command line arguments are given at runtime.

              3. in .NET, the Console class of the System namespace is used to handle standard input and output. With the static operation WriteLine you can write a line, with ReadKey you can wait for a key to be pressed.

              Top level statements, Implicit and static usings and namespaces

              When the project was created, we previously checked the \"Do not use top level statements\" checkbox. If we had not done this, we would have found only one meaningful line in our Program.cs file:

              // See https://aka.ms/new-console-template for more information\nConsole.WriteLine(\"Hello World!\");\n

              This is functionally equivalent to the code above containing the Program class and its Main function. Let's look at what makes this possible (you can read more about them here https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/top-level-statements, both new in C# 10):

              • Top level statements. The idea is that you can write code directly in a single source file without any class/Main and other function definitions in the project. In this case, behind the scenes, the compiler puts this into a static Main function of a class we don't see. The motivation for its introduction was to reduce boilerplate code for very simple, \"script-like\" applications.
              • Implicit global usings. Depending on exactly what project type you have created, certain base namespaces will be automatically using behind the scenes in all source files (the compiler uses the global using directive for this). The point is: this way, developers don't have to use certain frequently used namespaces (e.g. System.IO, System.Collections.Generic, etc.) as source files.
              • Static using. It is possible to use static classes instead of namespaces in C#, so it is not important to write them when using them. A common case is the use of the Console or Math class.

                using static System.Console;\n\nnamespace ConsoleApp12\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            WriteLine(\"Hello, World!\");\n        }\n    }\n}\n
              • File-level namespaces. In C# 10, we also get a simplification when declaring namespaces, because it is no longer mandatory to use brackets, so the given namespace will be valid for the whole file, e.g.:

                namespace HelloWorld;\n\ninternal class Program\n{\n    // ...\n}\n

              Inconsistent visibility or inconsistent accessibility error

              During the semester, you may encounter translation error messages complaining about inconsistent visibility or inconsistent accessibility when implementing programming tasks. This phenomenon is due to the possibility to control the visibility of each type (class, interface, etc.) in a .NET environment:

              • internal or no visibility is specified: the type is visible only inside the assembly (.exe, .dll)/project
              • public: the type is visible to other assemblies/projects

              The easiest way to avoid this error is to define all our types as public, e.g.:

              public class HardDisk\n{\n    // ...\n}\n
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#theoretical-overview","title":"Theoretical overview","text":"

              The sub-chapters do not contain exercises, but provide students with an introduction to the related theoretical topics, illustrated with examples.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#a-theory-of-the-relationship-between-the-uml-class-diagram-and-code-student","title":"A) Theory of the relationship between the UML class diagram and code [student]*","text":"

              The material is available here: The relationship between the UML class diagram and code. The relationship between the UML class diagram and code. This topic was covered in the previous semester in the Software Engineering course.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#b-interface-and-abstract-parent-class-student","title":"B) Interface and abstract (parent) class [student]*","text":"

              The material is available here: Interface and abstract (base) class. Interface and abstract (base) class.

              Topics:

              • Abstract class concept and definition in C#
              • Interface concepts and definitions in C#
              • Comparison of abstract base class and interface
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#2-task-illustrate-the-relationship-between-uml-and-code","title":"2. Task - Illustrate the relationship between UML and code","text":""},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#task-description-equipment-inventory","title":"Task description - Equipment inventory","text":"

              Task: We were asked to develop a computer parts inventory application. Read more:

              • You need to be able to handle different types of parts. Initially, HardDisk, SoundCard and LedDisplay types should be supported, but the system should be easily extensible to new types.
              • The data related to the parts are: year of purchase, age (calculated), purchase price and current price (calculated), but may also include type-specific data (e.g. capacity for HardDisk).
              • The actual price depends on the type of part, the purchase price and the year of production of the part. For example, the older the part, the bigger the discount, but the discount depends on the part type.
              • You must be able to list the parts in stock.
              • The LedDisplay class must be derived from an DisplayBase class, and the source code of the DisplayBase class cannot be changed. In this example this does not make much sense, but in practice we often encounter similar situations where the framework/platform we are using requires us to derive from a built-in class. Typically, this is the case when working with windows, forms, custom control types: we have to derive them from the framework's built-in classes, and we don't have (or at least certainly don't want to change) the source code of the framework - e.g. Java, .NET. In our example, we simulate this situation by specifying a derivation from DisplayBase.

              The implementation is simplified considerably: the parts are only stored in memory, and the listing is as simple as possible, simply by writing the data of the registered parts to the console.

              During the initial discussions, we receive the following information from the client: an internal staff member has already started the development, but due to lack of time, they have only reached a half-finished solution. Part of our task is to understand the semi-finished solution and to implement the task from there.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#class-diagram","title":"Class Diagram","text":"

              Let's open the source code solution from our customer, which we can do by following the steps below.

              To do this, clone the Git repository of the initial project, available online on GitHub, to a new folder of its own within C:\\Work: e.g: C:\\Work\\NEPTUN\\lab1. In this new folder, open a command line or powershell and run the following git command:

              git clone https://github.com/bmeviauab00/lab-modellkod-kiindulo.git\n

              Note

              You will read more about Git as a source code management system in the context of the first homework assignment.

              Open the Visual Studio solution src/EquipmentInventory.sln in the cloned folder.

              In Solution Explorer, run through the files by eye. It would help to understand the relationships between classes by displaying them on a class diagram. Let's include a class diagram in our project. In the Solution Explorer, right-click on the project (not the solution!), select Add/New Item from the pop-up menu, then in the window that appears, select Class Diagram, enter Main.cd as the name of the diagram at the bottom of the window, and OK-close the window.

              Missing Class Diagram template

              If the Class Diagram item does not appear in the list, then the appropriate component of VS is not installed. You can read more about this in the Prerequisites section of this document.

              The chart file Main.cd will then appear in Solution Explorer, double-click on it to open it. Our chart is currently empty. From Solution Explorer, drag&drop the .cs source files onto the diagram. VS then looks at what classes are in these source files and decomposes them into UML classes. Build the layout as shown in the following figure (you can display the members of the classes by clicking on the double arrow in the top right corner of their rectangle):

              Starting class diagram

              You can also view the source code for the classes, either by double-clicking on the corresponding class on the diagram or by opening the .cs files from Solution Explorer. Here's what we see:

              • The SoundCard, HardDisk and LedDisplay classes are relatively well developed, with the necessary attributes and query functions.
              • The LedDisplay is derived from the DisplayBase class as required.
              • EquipmentInventory is responsible for the inventory of parts in stock, but practically none of this is implemented.
              • We find an interface IEquipment with operations GetAge and GetPrice
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#equipmentinventory","title":"EquipmentInventory","text":"

              Let's start working on a solution. First, let's lay down the basic concepts. In the EquipmentInventory class, we store a heterogeneous collection of different types of equipment. This is the key to consistent parts management, so that our solution can be easily extended with new parts types.

              As discussed earlier, unified management can be achieved either by implementing a common base class or a common interface. In our case, the common base class (e.g. EquipmentBase) seems to be dropped, because by introducing it, the LedDisplay class would have two base classes: the mandatory DisplayBase, and the EquipmentBase that we introduce for uniform management. This is not possible, in a .NET environment a class can have only one base class. The solution to modify DisplayBase to be derived from EquipmentBase is not possible according to our requirement (it was a requirement that its source code cannot be modified). This leaves the interface-based approach. This was probably the conclusion of the previous developer of the application, which is why he introduced the IEquipment interface.

              Add a generic list of items of type IEquipment (not property but field!) to the EquipmentInventory class. Its visibility - in an effort to be unified - should be private. The name should be equipment (no \"s\" at the end, in English the plural of equipment is also equipment). To add a member variable, we use the Visual Studio Class Details window. If the window is not visible, it can be displayed by selecting View / Other Windows / Class Details.

              Class Details

              The member variable type is therefore List. The type of .NET List is a dynamically stretching generic array (like ArrayList in Java). Looking at the EquipmentInventory class in the diagram, we see that only the name of the member variable is displayed, not the type. Right-click on the background of the diagram and select Display Full Signature from the Change Members Format menu. The chart will then display the type of member variables and the full signature of the operations.

              EquipmentInventory

              By double-clicking on the EquipmentInventory class, you can navigate to the source code, and as you can see, it does indeed appear in the code as a member variable of type list:

              class EquipmentInventory\n{\n    private List<IEquipment> equipment;\n

              On the one hand, we're happy about this because Visual Studio supports round-trip engineering: changes to the model are immediately reflected in the code, and vice versa. On the other hand, we have previously discussed that if a class has a collection of members from another class, then it \"fits\" in the UML model as a type 1-more association relation between the two classes. This is not yet the case in our model. Fortunately, the VS modelling interface can be made to display this type of connection in this form. To do this, right-click on the equipment tag variable on the diagram and select Show as Collection Association from the menu. The IEquipment interface should then be moved to the right to allow enough space on the diagram to display the association relationship and the role on the relationship:

              Collection association

              The double arrow ending on the \"plural\" side is not standard UML, but don't be too sad about it, it's not important. We are certainly pleased that the arrow representing the relationship at the end of the IEquipment role shows the name (and even the exact type) of the member variable.

              Navigate to the source code of EquipmentInventory and write the constructor that initializes the equipment collection

              public EquipmentInventory()\n{\n    equipment = new List<IEquipment>();\n}\n

              Then write the ListAll method, which prints the age of the elements and their current values:

              public void ListAll()\n{\n    foreach (IEquipment eq in equipment)\n    {\n        Console.WriteLine($\"Age: {eq.GetAge()}\\t\u00c9rt\u00e9ke: {eq.GetPrice()}\");\n    }\n}\n

              Iterate through the elements using the foreach statement. When using the foreach statement, the in keyword should be followed by a collection and preceded by a variable declaration (in this case IEquipment eq), where type is the element type of the collection. In each iteration, this variable takes the iteration value of the collection.

              Console.WriteLine is either a simple string or, as in this case, a formatting string. The substitutions are solved by string interpolation: the values to be substituted must be given between `. If string interpolation is used, the string must start with$`.

              Write a function called AddEquipment that adds a new device to the inventory:

              public void AddEquipment(IEquipment eq)\n{\n     equipment.Add(eq);\n}\n
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#iequipment-implementers","title":"IEquipment implementers","text":"

              We have previously decided to use the IEquipment interface to manage the different component types in a uniform way. In our example, both SoundCard and HardDisk have GetAge() and GetPrice() methods, yet we cannot manage them in a unified way (e.g., store them in a common list). To do this, we need to get both classes to implement the IEquipment interface. Change their source:

              public class SoundCard : IEquipment\n
              public class HardDisk : IEquipment\n

              Then we need to implement the methods in the IEquipment interface in the SoundCard and HardDisk classes. We find that there is nothing to do with this now, the GetPrice and GetAge functions are already written in both places.

              As a test, in our Main function in Program.cs, create an EquipmentInventory object, populate it with HardDisk and SoundCard objects, and then list the object on the console. If 2021 is not the current year, in the following rows, copy the year 2021 to the current year and the year 2020 to a smaller number!

              static void Main( string[] args )\n{\n    EquipmentInventory ei = new EquipmentInventory();\n\n    ei.AddEquipment(new HardDisk(2021, 30000, 80));\n    ei.AddEquipment(new HardDisk(2020, 25000, 120));\n    ei.AddEquipment(new HardDisk(2020, 25000, 250));\n\n    ei.AddEquipment(new SoundCard(2021, 8000));\n    ei.AddEquipment(new SoundCard(2020, 7000));\n    ei.AddEquipment(new SoundCard(2020, 6000));\n\n    ei.ListAll();\n}\n

              Running the application, we find that although our solution is rudimentary, it works:

              Console output

              Continue with the LedDisplay class. The DisplayBase base class source code cannot be modified due to requirements. But this doesn't cause any problems, our LedDisplay class will implement the IEquipment interface, so modify the code accordingly:

              public class LedDisplay : DisplayBase, IEquipment\n

              In the LedDisplay class, the functions in the interface must already be written:

              public double GetPrice()\n{\n    return this.price;\n}\n\npublic int GetAge()\n{\n    return DateTime.Today.Year - this.manufacturingYear;\n}\n

              Let's extend our Main function by adding two LedDisplay objects to our set (again, if 2021 is not the current year, we should rewrite 2021 to the current year in the following lines, and 2020 to a smaller number!

              ei.AddEquipment(new LedDisplay(2020, 80000, 17, 16));\nei.AddEquipment(new LedDisplay (2021, 70000, 17, 12));\n\nei.ListAll();\nConsole.ReadKey();\n

              As a test, run the application.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#3-task-application-of-the-interface-and-the-abstract-primitive-class","title":"3. Task - Application of the interface and the abstract primitive class","text":""},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#interface-problems","title":"Interface problems","text":"

              Evaluate our current interface-based solution.

              One of the main problems is that our code is full of code duplication that destroys maintainability and extensibility:

              • The yearOfCreation and newPrice tags are common to all part types (except the special LedDisplay), and must be copy-pasted when a new type is introduced.
              • The implementation of the GetAge function is the same for all component types (except for the special LedDisplay), also copy-paste \"propagated\".
              • The lines of the constructors yearOfCreation and newPrice initializing tags are also duplicated in each class.

              Although this code duplication does not seem significant at the moment, the situation is getting worse as new component types are introduced, and it is better to prevent future pains in time.

              The other problem is that the listing of parts data is currently painfully incomplete, with no part type (only age and price). To display the type, the IEquipment interface must be extended, e.g. by introducing an operation called GetDescription. Let's add a GetDescription function to the interface!

              public interface IEquipment\n{\n    double GetPrice();\n    int GetAge();\n    string GetDescription();\n}\n

              Then every class implementing the IEquipment interface would have to implement this method, which is a lot of work for many classes (and often not even feasible for a multi-component application, i.e. one with several DLLs, when they are not in the hands of a single developer). Run the Build command to check that after adding GetDescription, you get compilation errors in three places.

              Specifying default implementation in interface

              It is worth knowing that starting from C# 8 (or .NET or .NET Core runtime, not supported under .NET Framework), interface operations can be given default implementation (default interface methods), so to solve the above problem you don't need an abstract class, but interface can no longer have member variables. More information here: default interface methods.

              public interface IEquipment\n{\n    double GetPrice();\n    int GetAge();\n    string GetDescription() { return \"EquipmentBase\"; }\n}\n
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#abstract-class","title":"Abstract class","text":"

              A solution to both problems is the introduction of a common abstract base class (except for the LedDisplay class, which we will come back to). We can move the code common to descendants into it, and provide a default implementation for the newly introduced GetDescription operation. Let our new abstract base class be called EquipmentBase. The question is whether the IEquipment interface is still needed, or whether it can be completely replaced by the new EquipmentBase class. We need to keep the IEquipment interface, because we cannot derive our LedDisplay class from EquipmentBase: it already has a mandatory base class, DisplayBase: for this reason, EquipmentInventory in our enhanced solution also refers to the various components as IEquipment interface.

              Let's start the transformation. Let our class diagram be the active tab. From the Toolbox, drag&drop an Abstract Class element onto the diagram, name it EquipmentBase.

              Toolbox - abstract class

              In the following, we need to make the SoundCard and HardDisk classes derive from EquipmentBase (LedDisplay already has another base class, so we cannot do this there). To do this, select the Inheritance link in the Toolbox, then draw a line from the child class to the base class for both SoundCard and HardDisk.

              In the next step, let's modify the code so that HardDisk and SoundCard do not implement the IEquipment interface separately, but rather their common base class EquipmentBase implement it once. To do this, modify the EquipmentBase class to implement the interface (either by drawing an inheritance link from EquipmentBase to IEquipment on the diagram, or by modifying the source code of EquipmentBase). Delete the implementation of IEquipment from the HardDisk and SoundCard classes (the base class already implements it).

              The relevant parts of our diagram and source code will then look like this:

              EquipmentBase and HardDisk/SoundCard

              public abstract class EquipmentBase : IEquipment\n
              public class HardDisk : EquipmentBase\n
              public class SoundCard : EquipmentBase\n

              Our code is not yet turning, for several reasons. The EquipmentBase implements the IEquipment interface, but it does not yet implement the interface operations. Either generate the methods using the smart tag, or type them according to the following principles:

              • The newPrice and yearOfCreation are duplicated in the HardDisk and SoundCard classes: move (not copy!) them to the common EquipmentBase base class and give protected visibility.
              • The GetAge operation is duplicated in the HardDisk and SoundCard classes, delete the implementation from these and move it to the EquipmentBase class.
              • The GetPrice operation is included in the base class as an abstract operation. This is a deliberate design decision, so we force descendant classes to override this operation anyway.
              • In the case of GetDescription, the opposite is true: it is defined as virtual (and not abstract), i.e. we provide an implementation in the base class. This way, descendants are not forced to override the operation.

              The code corresponding to the above is:

              public abstract class EquipmentBase : IEquipment\n{\n    protected int yearOfCreation;\n    protected int newPrice;\n\n    public int GetAge()\n    {\n        return DateTime.Today.Year - yearOfCreation;\n    }\n\n    public abstract double GetPrice();\n\n    public virtual string GetDescription()\n    {\n        return \"EquipmentBase\";\n    }\n}\n

              Some additional thoughts on the code fragment:

              • For abstract classes, the keyword abstract must be written before the word class.
              • For abstract operations, the keyword abstract must be specified
              • in a .NET environment, you can control whether an operation is virtual or not. In this respect, it is similar to C++. To make an operation virtual, the keyword virtual must be specified for the operation. Reminder: define an operation as virtual if its descendants overdefine it. Only then is it guaranteed that the descendant version will be called when invoking the given operation on an ancestor reference.
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#descendants","title":"Descendants","text":"

              In the next step, let's move on to the EquipmentBase descendants. When overriding abstract and virtual operations in C#, you must specify the override keyword in the descendant. First, the GetPrice operation is redefined:

              HardDisk.cs
              public override double GetPrice()\n{\n    return yearOfCreation < (DateTime.Today.Year - 4)\n        ? 0\n        : newPrice - (DateTime.Today.Year - yearOfCreation) * 5000;\n}\n
              SoundCard.cs
              public override double GetPrice()\n{\n    return yearOfCreation < (DateTime.Today.Year - 4)\n        ? 0 \n        : newPrice - (DateTime.Today.Year - yearOfCreation) * 2000;\n}\n

              In the next step, the GetDescription operation is written in the HardDisk and SoundCard classes. Since the virtual function of the base class is being overridden here, the override keyword must also be specified:

              HardDisk.cs
              public override string GetDescription()\n{\n    return \"Hard Disk\";\n}\n
              SoundCard.cs
              public override string GetDescription()\n{\n    return \"Sound Card\";\n}\n

              One might ask why the designers of the C# language decided to add an extra keyword to the definition of operations, which was not necessary in the case of C++. The reason is simple: the code is more expressive. Looking at the descendant code, the word override immediately makes it clear whether this operation is abstract or virtual in one of the base classes, without having to look at the code of all the ancestors.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#base-class-of-leddisplay","title":"Base class of LedDisplay","text":"

              The base class of our LedDisplay class is bound, its code cannot be modified, so we cannot derive it from EquipmentBase. We cannot delete the GetAge operation, this code duplication is preserved here (but only for LedDisplay, which is only one class among many!).

              Note

              In fact, with a little extra work we could get rid of this duplication. This would require a static helper function in one of the classes (e.g. EquipmentBase), which would get the year of manufacture and return the age. EquipmentBase.GetAge and LedDisplay.GetAge would use this helper function to produce their output.

              In our LedDisplay class, we are yet to write GetDescription:

              LedDisplay.cs
              public string GetDescription()\n{\n    return \"Led Display\";\n}\n

              Note that we have NOT specified the override keyword here. When an interface function is implemented, override is not required/allowed to be written.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#use-getdescription","title":"Use GetDescription","text":"

              Modify the EquipmentInventory.ListAll operation to also write the description of the items to the output:

              EquipmentInventory.cs
              public void ListAll()\n{\n    foreach (IEquipment eq in equipment)\n    {\n        Console.WriteLine($\"Description: {eq.GetDescription()}\\t\" +\n            $\"Age: {eq.GetAge()}\\t\u00c9rt\u00e9ke: {eq.GetPrice()}\");\n    }\n}\n

              This gives a more informative output when the application is run:

              Console output

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#constructor-code-duplication","title":"Constructor code duplication","text":"

              Looking at our code, there is one more duplication. All EquipmentBase descendants (HardDisk, SoundCard) have these two lines in their constructor:

               this.yearOfCreation = yearOfCreation;\n this.newPrice = newPrice;\n

              If you think about it, these yearOfCreation and newPrice members are defined in the base class, so it should be his responsibility to initialize them anyway. Let's add a corresponding constructor in EquipmentBase:

              EquipmentBase.cs
              public EquipmentBase(int yearOfCreation, int newPrice)\n{\n    this.yearOfCreation = yearOfCreation;\n    this.newPrice = newPrice;\n}\n

              Remove the initialization of the two members from the constructor of the descendants HardDisk and SoundCard, and instead invoke the base class\u2019s constructor by referencing the base keyword:

              HardDisk.cs
              public HardDisk(int yearOfCreation, int newPrice, int capacityGB)\n    : base(yearOfCreation, newPrice)\n{\n    this.capacityGB = capacityGB;\n}\n
              SoundCard.cs
              public SoundCard(int yearOfCreation, int newPrice)\n    : base(yearOfCreation, newPrice)\n{\n}\n
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#evaluation","title":"Evaluation","text":"

              By using a combination of interface and abstract base class, we have managed to develop the solution with the least compromise:

              • By referring to IEquipment as an interface, we can uniformly handle all types of parts, even those where the base class was bound (using abstract base classes alone would not have achieved this).
              • By introducing the EquipmentBase abstract base class, we were able to put the code common to different part types into a common base, with one exception, thus avoiding code duplication.
              • By introducing the EquipmentBase abstract ancestor, we can specify a default implementation for newly introduced IEquipment operations (e.g. GetDescripton), so we are not forced to specify it in every IEquipment implementation class.

              Finally, let's take a look at the UML (like) class diagram of our solution:

              Ultimate class diagram

              Static interfaces

              The latest addition to C# 11 is the definition of static interface members, which allows you to require an implementing class to have members that do not refer to the object instance, but rather the class must have a specific static member. Read more

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#note-optional-homework-exercise","title":"Note - optional homework exercise","text":"

              Our solution does not support the display of component specific data (e.g. capacity for HardDisk) during listing. To do this, the writing of component data to a formatted string should be moved from the EqipmentInventory class to the component classes, following the principles below:

              • To do this, we can introduce an GetFormattedString operation in the IEquipment interface, which returns an object of type string. Alternatively, you can override the ToString()operation ofSystem.Object. indeed, in .NET, all types are implicitly derived from System.Object, which has a virtual ToString() operation.
              • In EquipmentBase we write the formatting of the common tags (description, price, age) into a string.
              • If a component also has type-specific data, then its class overrides the function that formats it into a string: this function must first call its ancestor (using the base keyword), then append its own formatted data to it, and return with this string.
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/","title":"1. Beziehung zwischen dem Modell und dem Code","text":""},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#das-ziel-der-ubung","title":"Das Ziel der \u00dcbung","text":"

              Das Ziel der \u00dcbung:

              • Kennenlernen der Studenten/Studentinnen und des \u00dcbungsleiters/ins
              • Kl\u00e4rung der Anforderungen f\u00fcr \u00dcbungen
              • Erste Schritte mit Visual Studio und der Entwicklung von .NET-Anwendungen.
              • Erstellen einer einfachen Hello World .NET-Anwendung, C#-Grundlagen
              • Veranschaulichung der Beziehung zwischen UML und Code
              • Anwendungstechnik der Schnittstelle und der abstrakte Basisklasse
              F\u00fcr \u00dcbungsleiter/in

              Sicherlich gibt es einige Teilnehmer, die Visual Studio bereits in Prog2 (C++) oder aus anderen Gr\u00fcnden verwendet haben, aber es wird auch einige geben, die es noch nicht verwendet haben oder sich weniger daran erinnern. Das Ziel ist in diesem Fall, die Benutzeroberfl\u00e4che kennenzulernen. So w\u00e4hrend der L\u00f6sung der \u00dcbungen, sollten die benutzte Dinge (z. B. Solution Explorer, Ausf\u00fchren mit F5, Verwenden von Haltepunkten usw.) auch besprochen werden.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#voraussetzungen","title":"Voraussetzungen","text":"

              Die f\u00fcr die Ausf\u00fchrung der \u00dcbung ben\u00f6tigten Werkzeuge:

              • Visual Studio 2022

              Es sollte die neueste Version von Visual Studio installiert sein. Die Versionen Community Edition, Professional und Enterprise sind ebenfalls geeignet. Die Community Edition ist kostenlos und kann von der Microsoft-Website heruntergeladen werden. Der Professional ist kostenpflichtig, steht aber auch f\u00fcr Studenten der Universit\u00e4t kostenlos zur Verf\u00fcgung (auf der Website, im Rahmen des Programms Azure Dev Tools for Teaching).

              Visual Studio Class Diagram support

              F\u00fcr einige Aufgaben in dieser \u00dcbung (und auch f\u00fcr die erste Hausaufgabe) werden wir die Unterst\u00fctzung des Visual Studio Class Designer nutzen. Visual Studio f\u00fcgt die Komponente Class Designer w\u00e4hrend der Installation nicht immer hinzu. Wenn es nicht m\u00f6glich ist, ein Klassendiagramm zu Ihrem Visual Studio-Projekt hinzuzuf\u00fcgen (weil das Klassendiagramm nicht in der Liste des Fensters aufgef\u00fchrt ist, das w\u00e4hrend des Befehls Neues Element hinzuf\u00fcgen angezeigt wird - mehr dazu sp\u00e4ter in diesem Handbuch), m\u00fcssen Sie die Komponente Klassendiagramm sp\u00e4ter installieren:

              1. Starten Sie das Visual Studio-Installationsprogramm (z. B. durch Eingabe von \"Visual Studio Installer\" im Windows-Startmen\u00fc).
              2. W\u00e4hlen Sie in dem nun erscheinenden Fenster die Registerkarte \"Individual components\"
              3. Geben Sie in das Suchfeld \"class designer\" ein und vergewissern Sie sich, dass \"Class Designer\" in der gefilterten Liste angekreuzt ist.

              Was Sie sich ansehen sollten:

              • Die \u00dcbung beinhaltet keine Vorlesung zu diesem Thema. Gleichzeitig baut die \u00dcbung auf grundlegendem UML-Kenntnisse und den Grundlagen der Abbildung von UML-Klassendiagrammen auf Code.
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#verlauf-der-ubung","title":"Verlauf der \u00dcbung","text":"

              Der/die \u00dcbungsleiter/in fasst die Anforderungen f\u00fcr die \u00dcbungen am Anfang der \u00dcbung zusammen:

              • Die meisten davon finden Sie in dem Merkblatt
              • Informationen zu den Hausaufgaben finden Sie auf der Website des Fachs.

              Mit dem Entwicklungsumgebung Visual Studio werden wir .NET-Anwendungen in C# erstellen. C# ist \u00e4hnlich wie Java, wir lernen stufenweise die Unterschiede. Die Aufgaben werden gemeinsam unter der Leitung des \u00dcbungsleiters/ins durchgef\u00fchrt.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#losung","title":"L\u00f6sung","text":"Laden Sie die fertige L\u00f6sung herunter

              Es ist wichtig, dass Sie sich w\u00e4hrend des Praktikums an die Anleitung halten. Es ist verboten (und sinnlos), die fertige L\u00f6sung herunterzuladen. Allerdings kann es bei der anschlie\u00dfenden Selbstein\u00fcbung n\u00fctzlich sein, die fertige L\u00f6sung zu \u00fcberpr\u00fcfen, daher stellen wir sie zur Verf\u00fcgung.

              Die L\u00f6sung ist auf GitHub verf\u00fcgbar. Der einfachste Weg, es herunterzuladen, ist, es von der Kommandozeile aus mit dem Befehl git clone auf Ihren Computer zu klonen:

              git clone https://github.com/bmeviauab00/lab-modellkod-kiindulo -b solved

              Sie m\u00fcssen Git auf Ihrem Rechner installiert haben, weitere Informationen hier.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#1-aufgabe-erstellen-einer-hello-world-net-konsolenanwendung","title":"1. Aufgabe - Erstellen einer \"Hello World\" .NET-Konsolenanwendung","text":"

              Die Aufgabe ist die Erstellung einer C#-Konsolenanwendung, die den Text \"Hello world!\" auf der Konsole ausgibt.

              Die Anwendung wird in C# geschrieben. Die kompilierte Anwendung wird von der .NET-Laufzeitumgebung ausgef\u00fchrt. In der ersten Vorlesung werden die theoretischen Hintergr\u00fcnde des Kompilierens/Ablaufens und die Grundlagen von .NET behandelt.

              Die Schritte zum Erstellen einer Projektmappe und eines Projekts in Visual Studio 2022:

              1. Starten Sie einen neuen \"Neues Projekt erstellen\" Dialogfeld, was auf zwei Arten geschehen kann
                • Verwendung des Startfensters
                  1. Visual Studio starten
                  2. In der rechten Seitenleiste des erscheinenden Startfensters Create new project
                • Bereits in Visual Studio ausgef\u00fchrt
                  1. File / New-Project
              2. W\u00e4hlen Sie im Dialogfeld \"Neues Projekt erstellen\" die Vorlage \" Console app \" (und NICHT die Vorlage \" Console app (.NET Framework)\", einschlie\u00dflich der C#-Vorlage. Dass es sich um C# handelt, ist an der oberen linken Ecke des Vorlagensymbols zu erkennen. Wenn Sie es nicht in der Liste sehen, m\u00fcssen Sie es suchen/filtern. Sie k\u00f6nnen danach suchen, falls Sie in der oberen Suchleiste \"console\" eingeben. Oder verwenden Sie die Dropdown-Felder unten: im ersten (Sprachauswahl) \"C#\", im dritten (Projekttypauswahl) \"Console\".

              3. Next-Taste am unteren Rand des Dialogfeldes \"Neues Projekt erstellen\", auf der n\u00e4chsten Seite:

                1. Project name: Hello World
                2. Location: In den Labors arbeiten wir im Ordner c:\\work\\<IhreName>, auf den Sie Schreibrechte haben.
                3. Solution name: Hello World (dies sollte bis zu unserer Ankunft hier eingeschrieben sein)
                4. Place solution and project in the same directory: kein H\u00e4kchen (aber nicht besonders wichtig).
              4. Next-Taste am unteren Rand des Dialogfeldes \"Neues Projekt erstellen\", auf der n\u00e4chsten Seite:

                1. Framework: .NET 8 (Langfristige Unterst\u00fctzung).
                2. Aktivieren Sie das Kontrollk\u00e4stchen \"Do not use top level statements\" (wir werden dies gleich erkl\u00e4ren).

              Das Projekt erstellt auch eine neue Projektmappe, deren Struktur im Visual Studio Solution Explorer-Fenster angezeigt werden kann. Eine L\u00f6sung kann aus mehreren Projekten bestehen, und ein Projekt kann aus mehreren Dateien bestehen. Ein Solution ist eine Zusammenfassung der gesamten Arbeitsumgebung (sie hat die Dateierweiterung .sln ), w\u00e4hrend die Ausgabe eines Projekts typischerweise eine Datei .exe oder .dll ist, d. h. eine Komponente einer komplexen Anwendung/eines komplexen Systems. Projektdateierweiterung f\u00fcr C#-Anwendungen .csproj.

              Der Inhalt unserer Datei Program.cs ist die folgende:

              Program.cs
              namespace HelloWorld\n{\n    internal class  Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n        }\n    }\n}\n

              Nehmen wir eine Console.ReadKey() Zeile aus:

              namespace HelloWorld\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n            Console.ReadKey();\n        }\n    }\n}\n
              1. F\u00fchren wir die Anwendung aus (z. B. mit der Taste F5 ).

                Die Struktur des Codes ist sehr \u00e4hnlich zu Java und C++. Unsere Klassen sind in Namespaces organisiert. Sie k\u00f6nnen einen Namespace mit dem Schl\u00fcsselwort namespace definieren. Wir k\u00f6nnen Namespaces mit dem Schl\u00fcsselwort using \"ins Geltungsbereich bringen\". z.B.:

                using System.Collections.Generic;\n
              2. In einer C#-Konsolenanwendung wird der Eintrittspunkt der Anwendung mit einer statischen Funktion namens Main gegeben. Unser Klassenname kann beliebig sein, in unserem Fall hat VS eine Klasse namens Program erzeugt. Die Parameterliste der Funktion Main ist gebunden: entweder werden keine Parameter angegeben, oder es wird ein string[]angegeben, in dem die Befehlszeilenargumente zur Laufzeit angegeben werden.

              3. in .NET wird die Klasse Console aus dem Namensraum System verwendet, um die Standardeingabe und -ausgabe zu verarbeiten. Mit der statischen Aktion WriteLine k\u00f6nnen Sie eine Zeile drucken, mit ReadKey k\u00f6nnen Sie auf das Dr\u00fccken einer Taste warten.

              Top-Level-Anweisungen, implizite und statische Verwendungen und Namespaces

              Bei der Projekterstellung haben wir zuvor das Kontrollk\u00e4stchen \"Do not use top level statements\" aktiviert. Falls wir dies nicht getan h\u00e4tten, h\u00e4tten wir in unserer Datei Program.cs nur eine einzige Zeile mit Inhalt gefunden:

              // siehe https://aka.ms/new-console-template f\u00fcr weitere Informationen\nConsole.WriteLine(\"Hello World!\");\n

              Es ist funktionell \u00e4quivalent zu dem obigen Code, der die Klasse Program und ihre Funktion Main enth\u00e4lt. Schauen wir uns an, was dies m\u00f6glich macht (Sie k\u00f6nnen hier mehr dar\u00fcber lesen https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/top-level-statements, beide neu in C# 10):

              • Top level statements. Die Idee ist, dass man Code direkt in einer einzigen Quelldatei schreiben kann, ohne dass Klassen/Main und andere Funktionsdefinitionen im Projekt vorhanden sind. In diesem Fall setzt der Compiler dies hinter den Kulissen in eine statische Main-Funktion einer Klasse, die wir nicht sehen. Die Motivation f\u00fcr seine Einf\u00fchrung war die Reduzierung von \"Boilerplate\"-Code f\u00fcr sehr einfache, \"skriptartige\" Anwendungen.
              • Implicit global usings. Je nachdem, welchen Projekttyp Sie erstellt haben, werden bestimmte Basis-Namensr\u00e4ume automatisch im Hintergrund in allen Quelldateien verwendet (der Compiler verwendet dazu die global using-Direktive). Der Punkt ist: Auf diese Weise m\u00fcssen Entwickler bestimmte h\u00e4ufig verwendete Namespaces (z.B. System.IO, System.Collections.Generic, etc.) nicht als Quelldateien verwenden.
              • Static using. Es ist m\u00f6glich, statische Klassen statt Namespaces in C# mit using zu verwenden, so es nicht wichtig ist, diese auszuschreiben, wenn sie verwendet werden. Ein h\u00e4ufiger Fall ist die Verwendung der Klasse \"Console\" oder \"Math\".

                using static System.Console;\n\nnamensraum ConsoleApp12\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            WriteLine(\"Hello World!\");\n        }\n    }\n}\n
              • Namensr\u00e4ume auf Dateiebene. In C# 10 gibt es auch eine Vereinfachung bei der Deklaration von Namespaces, da es nicht mehr zwingend erforderlich ist, Klammern zu verwenden, so dass der angegebene Namespace f\u00fcr die ganze Datei g\u00fcltig ist, z.B:

                namespace HelloWorld;\n\ninternal class Program\n{\n    // ...\n}\n

              Inconsistent visibility oder inconsistent accessibility Fehler

              W\u00e4hrend des Semesters k\u00f6nnen Sie bei der Durchf\u00fchrung von Programmieraufgaben auf \u00dcbersetzungsfehlermeldungen sto\u00dfen, die sich \u00fcber inconsistent visibility oder inconsistent accessibility beschweren. Dieses Ph\u00e4nomen ist auf die M\u00f6glichkeit zur\u00fcckzuf\u00fchren, die Sichtbarkeit der einzelnen Typen (Klassen, Schnittstellen usw.) in einer .NET-Umgebung zu steuern:

              • internal oder keine Sichtbarkeit angeben: der Typ ist nur in der angegebenen Assembly (.exe, .dll)/dem angegebenen Projekt sichtbar
              • public: der Typ ist auch f\u00fcr andere Assemblys/Projekte sichtbar

              Der einfachste Weg, diesen Fehler zu vermeiden, ist, alle unsere Typen als \u00f6ffentlich zu definieren, z.B.:

              public class HardDisk\n{\n    // ...\n}\n
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#theoretischer-uberblick","title":"Theoretischer \u00dcberblick","text":"

              Die Unterkapitel enthalten keine \u00dcbungen, sondern bieten den Studierenden eine mit Beispielen illustrierte Einf\u00fchrung in die entsprechenden theoretischen Themen.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#a-theorie-der-beziehung-zwischen-dem-uml-klassendiagramm-und-dem-code-student","title":"A) Theorie der Beziehung zwischen dem UML-Klassendiagramm und dem Code [Student]*","text":"

              Das Material ist hier verf\u00fcgbar: Die Beziehung zwischen dem UML-Klassendiagramm und dem Code Dieses Thema wurde im vorangegangenen Semester in der Vorlesung Softwaretechnologien behandelt.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#b-schnittstelle-und-abstrakte-basisklasse-student","title":"B) Schnittstelle und abstrakte (Basis)Klasse [Student]*","text":"

              Das Material ist hier verf\u00fcgbar: Schnittstelle und abstrakte (angestammte) Klasse.

              Themen:

              • Konzept und Definition abstrakter Klassen in C#
              • Schnittstellenkonzepte und -definitionen in C#
              • Vergleich von abstraktem Basisklasse und Schnittstelle
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#2-aufgabe-veranschaulichen-der-beziehung-zwischen-uml-und-code","title":"2. Aufgabe - Veranschaulichen der Beziehung zwischen UML und Code","text":""},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#aufgabenbeschreibung-equipment-inventory","title":"Aufgabenbeschreibung - Equipment inventory","text":"

              Aufgabe: Wir haben die Aufgabe bekommen, eine Computerteilregister-Anwendung zu entwickeln. Lesen Sie mehr:

              • Es soll f\u00e4hig sein, verschiedene Arten von Teilen zu behandeln. Anf\u00e4nglich sollten die Typen HardDisk, SoundCard und LedDisplay unterst\u00fctzt werden, aber das System sollte leicht auf neue Typen erweiterbar sein.
              • Daten der Teilen: Kaufsjahr, Alter (berechnet), Kaufspreis und aktueller Preis (berechnet), kann aber auch typspezifische Daten enthalten (z. B. Kapazit\u00e4t f\u00fcr HardDisk ).
              • Der aktueller Preis h\u00e4ngt von der Art des Teils, dem Einkaufspreis und dem Produktionsjahr des Teils ab. Je \u00e4lter das Teil ist, desto h\u00f6her ist der Preisnachlass, aber der Preisnachlass h\u00e4ngt von der Art des Teils ab.
              • Es soll f\u00e4hig sein, die speicherte Teilen aufzulisten.
              • Die Klasse LedDisplay muss von einer Klasse DisplayBase abgeleitet sein, und der Quellcode der Klasse DisplayBase darf nicht ver\u00e4ndert werden. In diesem Beispiel hat dies nicht viel Sinn, aber in der Praxis treffen wir oft auf \u00e4hnliche Situationen, in denen das von uns verwendete Framework/die Plattform verlangt, dass wir von einer eingebauten Klasse ableiten. Typischerweise ist dies der Fall, wenn wir mit Fenstern, Formularen oder benutzerdefinierten Steuerelementen arbeiten: Wir m\u00fcssen sie von den eingebauten Klassen des Frameworks ableiten, und wir haben den Quellcode des Frameworks nicht (oder wollen ihn zumindest nicht \u00e4ndern) - z.B. Java, .NET. In unserem Beispiel simulieren wir diese Situation, indem wir eine Ableitung von DisplayBaseverlangen.

              Die Implementierung ist erheblich vereinfacht: Die Teile werden nur im Speicher abgelegt, und die Auflistung ist so einfach wie m\u00f6glich, einfach die Daten der registrierten Teile werden auf die Konsole geschrieben.

              Bei den ersten Gespr\u00e4chen erhalten wir vom Kunden folgende Information: Ein interner Mitarbeiter hat bereits mit der Entwicklung begonnen, ist aber aus Zeitmangel nur zu einer halbfertigen L\u00f6sung gekommen. Ein Teil unserer Aufgabe besteht darin, die halbfertige L\u00f6sung zu verstehen und die Aufgabe von dort aus umzusetzen.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#klassendiagramm","title":"Klassendiagramm","text":"

              \u00d6ffnen wir die Quellcode-L\u00f6sung unseres Kunden source code mit dem Ausf\u00fchren der nachstehenden Schritte.

              Klonen wir das Git-Repository des urspr\u00fcnglichen Projekts, das online auf GitHub verf\u00fcgbar ist, in einen eigenen Ordner innerhalb des Ordners C:\\Work: z. B.: C:\\Work\\NEPTUN\\lab1. \u00d6ffnen wir in diesem neuen Ordner eine Befehlszeile oder Powershell und f\u00fchren wir den folgenden git-Befehl aus:

              git clone https://github.com/bmeviauab00/lab-modellkod-kiindulo.git\n

              Git und GitHub

              Sie werden mehr \u00fcber Git als Quellcode-Verwaltungssystem im Rahmen der ersten Hausaufgabe erfahren.

              \u00d6ffnen wir die Visual Studio Solution src/EquipmentInventory.sln im geklonten Ordner.

              Blicken wir die Dateien im Solution Explorer lurz \u00fcber. Es w\u00e4re hilfreich, die Beziehungen zwischen den Klassen in einem Klassendiagramm darzustellen, um sie zu verstehen. Wir wollen ein Klassendiagramm in unser Projekt einf\u00fcgen. Klicken wir im Solution Explorer mit der rechten Maustaste auf das Projekt (nicht auf das Solution!), und w\u00e4hlen wir im Popup-Men\u00fc die Option Add/New Item. Dann w\u00e4hlen wir in dem erscheinenden Fenster die Option Class Diagram, geben wir am unten im Fenster Main.cd als der Namen des Diagramms ein, und schlie\u00dfen wir das Fenster mit OK.

              Fehlende Class Diagram-Vorlage

              Wenn das Element Class Diagram nicht in der Liste erscheint, ist die entsprechende Komponente von VS nicht installiert. Weitere Informationen hierzu finden Sie im Abschnitt Voraussetzungen in diesem Dokument.

              Die Diagrammdatei Main.cd wird dann im Solution Explorer angezeigt. Doppelklicken wir darauf, um sie zu \u00f6ffnen. Unseres Diagramm ist derzeit leer. Ziehen wir die .cs-Quelldateien aus Solution Explorer mit drag&drop auf das Diagramm. VS pr\u00fcft dann, welche Klassen in diesen Quelldateien enthalten sind, und zerlegt sie in UML-Klassen. Erstellen wir das Layout wie in der folgenden Abbildung gezeigt (man kann die Mitglieder der Klassen anzeigen, falls man auf den Doppelpfeil in der oberen rechten Ecke ihres Rechtecks klickt):

              Wir k\u00f6nnen auch den Quellcode der Klassen anschauen, falls wir entweder auf die entsprechende Klasse im Diagramm doppelklicken oder die .cs-Dateien im Solution Explorer \u00f6ffnen. Wir werden die Folgenden erfahren:

              • Die Klassen SoundCard, HardDisk und LedDisplay sind relativ gut entwickelt und verf\u00fcgen \u00fcber die notwendigen Attribute und Abfragefunktionen.
              • LedDisplay wird bei Bedarf von DisplayBase abgeleitet.
              • Obwohl EquipmentInventory f\u00fcr die Register der auf Lager befindlichen Teile verantwortlich ist, wird praktisch nichts davon umgesetzt.
              • Wir finden eine Schnittstelle IEquipment, mit GetAge und GetPrice Funktionen.
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#equipmentinventory","title":"EquipmentInventory","text":"

              Lassen wir uns an der L\u00f6sung arbeiten. Lassen wir uns zuerst die grundlegenden Konzepte festlegen. In der Klasse EquipmentInventory speichern wir eine heterogene Sammlung verschiedener Teiltypen. Dies ist der Schl\u00fcssel zu einer konsistenten Teilverwaltung, so dass unsere L\u00f6sung problemlos mit neuen Teiltypen erweitert werden kann.

              Wie fr\u00fcher erw\u00e4hnt, kann eine einheitliche Verwaltung entweder durch die Implementierung einer gemeinsamen Basisklasse oder einer gemeinsamen Schnittstelle erreicht werden. In unserem Fall scheint die gemeinsame Basisklasse (z. B. EquipmentBase) eliminiert zu werden, denn durch ihre Einf\u00fchrung h\u00e4tte die Klasse LedDisplay zwei Basisklassen: DisplayBase, die obligatorisch ist, und EquipmentBase, die wir zur einheitlichen Verwaltung einf\u00fchren. Dies ist nicht m\u00f6glich, in einer .NET-Umgebung kann eine Klasse nur einen Vorg\u00e4nger haben. Die L\u00f6sung, DisplayBaseso zu \u00e4ndern, dass es von EquipmentBasestammt, ist nach unseren Anforderungen nicht m\u00f6glich (es war eine Anforderung, dass der Quellcode nicht ge\u00e4ndert werden durfte). Es bleibt also der schnittstellenbasierte Ansatz. Dies ist sicherlich die Schlussfolgerung des vorherigen Entwicklers der Anwendung, weshalb er die Schnittstelle IEquipment eingef\u00fchrt hat.

              F\u00fcgen wir eine generische Liste von Elementen des Typs IEquipment (keine Eigenschaft, sondern ein Feld!) zur Klasse EquipmentInventory hinzu. Ihre Sichtbarkeit sollte - in dem Bem\u00fchen um Integration - privatesein. Der Name sollte equipment sein (ohne \"s\" am Ende, im Englisch ist der Plural von equipment auch equipment). Um eine Membervariable hinzuzuf\u00fcgen, verwenden wir das Class Details Fenster von Visual Studio. Wenn das Fenster nicht sichtbar ist, kann es durch Auswahl von View / Other Windows / Class Details angezeigt werden.

              Der Typ der Mitgliedsvariablen ist List<IEquipment>. Der .NET-Typ List ist ein dynamisch dehnbares generisches Array (wie ArrayListin Java). Falls wir auf die Klasse EquipmentInventory im Diagramm blicken, so siehen wir, dass nur der Name der Mitgliedsvariablen angezeigt wird, nicht aber der Typ. Klicken wit mit der rechten Maustaste auf den Hintergrund des Diagramms und w\u00e4hlen wir im Change Members Format Men\u00fc die Option Display Full Signature. Das Diagramm zeigt dann den Typ der Mitgliedsvariablen und die vollst\u00e4ndige Signatur der Operationen.

              Wenn wir auf die Klasse EquipmentInventory doppelklicken, k\u00f6nnen wir zum Quellcode navigieren, und wie wir sehen k\u00f6nnen, erscheint sie im Code tats\u00e4chlich als Mitgliedsvariable vom Typ Liste:

              class EquipmentInventory\n{\n    private List<IEquipment> equipment;\n

              Einerseits freuen wir uns dar\u00fcber, weil Visual Studio Round-Trip-Engineering unterst\u00fctzt: \u00c4nderungen am Modell spiegeln sich sofort im Code wider und umgekehrt. Andererseits haben wir bereits dar\u00fcber gesprochen, dass eine Klasse, die eine Sammlung von Mitgliedern einer anderen Klasse hat, sollte in das UML-Modell als eine Assoziationsbeziehung vom Typ 1-mehr zwischen den beiden Klassen erscheinen. Dies ist noch nicht der Fall in unserem Modell. Gl\u00fccklicherweise kann die VS-Modellierungsschnittstelle dazu gebracht werden, diese Art von Verbindung in dieser Form anzuzeigen. Klicken wir dazu im Diagramm mit der rechten Maustaste auf die Membervariable equipment und w\u00e4hlen wir im Men\u00fc die Option Show as Collection Association aus. Die Schnittstelle IEquipment sollte dann nach rechts verschoben werden, damit im Diagramm gen\u00fcgend Platz f\u00fcr die Darstellung der Assoziationsverbindung und der Rolle der Verbindung bleibt:

              Der Doppelpfeil, der auf der \"Mehr\"-Seite endet, entspricht nicht dem UML-Standard, aber sei man nicht zu traurig dar\u00fcber, es ist nicht wichtig. Wir freuen uns dar\u00fcber, dass der Name (und sogar der genaue Typ) der Mitgliedsvariablen am IEquipment Ende der die Beziehung darstellende Pfeil in der Rolle anzeigt ist.

              Navigieren wir zum Quellcode von EquipmentInventory und schreiben wir den Konstruktor, der die Sammlung equipment initialisiert!

              public EquipmentInventory()\n{\n    equipment = new List<IEquipment>();\n}\n

              Schreiben wir dann die Methode ListAll, die das Alter der Elemente und ihren aktuellen Preis ausgibt:

              public void ListAll()\n{\n    foreach (IEquipment eq in equipment)\n    {\n        Console.WriteLine($\"Alter: {eq.GetAge()}\\t\u00c9rt\u00e9ke: {eq.GetPrice()}\");\n    }\n}\n

              Mit dem Befehl foreach durchlaufen wir die Elemente. Bei der Verwendung des Befehls foreach sollte in von einer Sammlung gefolgt werden, und in sollte eine Variablendeklaration (in diesem Fall IEquipment eq) vorangestellt werden, wo type der Elementtyp der Sammlung ist. Bei jeder Iteration nimmt diese Variable den Iterationswert der Sammlung an.

              Der Operation Console.WriteLine wird entweder eine einfache Zeichenfolge oder, wie in unserem Fall, eine Formatierungszeichenfolge \u00fcbergeben. Die Ersetzungen werden durch String-Interpolation gel\u00f6st: Die zu ersetzenden Werte m\u00fcssen zwischen {} angegeben werden. Bei der String-Interpolation muss der String mit $ beginnen.

              Schreiben wir eine Funktion mit der Bezeichnung AddEquipment, die ein neues Ger\u00e4t zu der Menge hinzuf\u00fcgt:

              public void AddEquipment(IEquipment eq)\n{\n     equipment.Add(eq);\n}\n
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#verwirklichern-von-iequipment","title":"Verwirklichern von IEquipment","text":"

              Wir haben entschieden, die Schnittstelle IEquipment zu verwenden, um die verschiedenen Komponententypen einheitlich zu verwalten. In unserem Fall haben sowohl die Klassen SoundCard als auch HardDisk die Methoden GetAge() und GetPrice(), aber wir k\u00f6nnen sie nicht einheitlich verwalten (z. B. in einer gemeinsamen Liste speichern). Zu diesem Zweck m\u00fcssen wir beide Klassen dazu bringen, die Schnittstelle IEquipment zu implementieren. \u00c4ndern Sie ihr Quellcode:

              public class SoundCard : IEquipment\n
              public class HardDisk : IEquipment\n

              Dann m\u00fcssen wir die Methoden der Schnittstelle IEquipment in den Klassen SoundCard und HardDisk implementieren. Wir stellen fest, dass es damit nichts mehr zu tun gibt, die Funktionen GetPrice und GetAge sind bereits an beiden Stellen geschrieben.

              Erstellen wir testweise ein Objekt EquipmentInventory in unserer Main Funktion in Program.cs, f\u00fcllen wir es mit den Objekten HardDisk und SoundCard auf, und listen wir das Objekt dann in der Konsole aus. Wenn 2021 nicht das aktuelle Jahr ist, schreiben wir in den folgenden Zeilen das Jahr 2021 auf das aktuelle Jahr und das Jahr 2020 auf eine mit eins kleinere Zahl um!

              static void Main( string[] args )\n{\n    EquipmentInventory ei = new EquipmentInventory();\n\n    ei.AddEquipment(new HardDisk(2021, 30000, 80));\n    ei.AddEquipment(new HardDisk(2020, 25000, 120));\n    ei.AddEquipment(new HardDisk(2020, 25000, 250));\n\n    ei.AddEquipment(new SoundCard(2021, 8000));\n    ei.AddEquipment(new SoundCard(2020, 7000));\n    ei.AddEquipment(new SoundCard(2020, 6000));\n\n    ei.ListAll();\n}\n

              Wenn wir die Anwendung ausf\u00fchren, stellen wir fest, dass unsere L\u00f6sung zwar anf\u00e4nglich ist, aber funktioniert:

              Arbeiten wir weiter mit der Klasse LedDisplay. Der Quellcode von DisplayBase kann aufgrund der Anforderungen nicht ge\u00e4ndert werden. Aber das ist kein Problem, unsere Klasse LedDisplay wird die Schnittstelle IEquipment implementieren, lassen wir uns den Code entsprechend \u00e4ndern:

              public class LedDisplay : DisplayBase, IEquipment\n

              In der Klasse LedDisplay m\u00fcssen die Funktionen der Schnittstelle bereits geschrieben sein:

              public double GetPrice()\n{\n    return this.price;\n}\n\npublic int GetAge()\n{\n    return DateTime.Today.Year - this.manufacturingYear;\n}\n

              Erweitern wir unsere Main Funktion, f\u00fcgen wir zwei LedDisplay Objekte zu unserer Liste hinzu (auch hier gilt: Wenn 2021 nicht das aktuelle Jahr ist, schreiben wir in den folgenden Zeilen das Jahr 2021 auf das aktuelle Jahr und das Jahr 2020 auf eine mit eins kleinere Zahl um!)

              ei.AddEquipment(new LedDisplay(2020, 80000, 17, 16));\nei.AddEquipment(new LedDisplay (2021, 70000, 17, 12));\n\nei.ListAll();\nConsole.ReadKey();\n

              F\u00fchren wir die Anwendung testweise aus.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#3-aufgabe-anwendung-der-schnittstelle-und-der-abstrakten-basisklasse","title":"3. Aufgabe - Anwendung der Schnittstelle und der abstrakten Basisklasse","text":""},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#schnittstellenprobleme","title":"Schnittstellenprobleme","text":"

              Bewerten wir unsere aktuelle schnittstellenbasierte L\u00f6sung.

              Eines der Hauptprobleme ist, dass unser Code mit Code-Duplikationen voll ist, die die Wartbarkeit und Erweiterbarkeit zerst\u00f6ren:

              • Die Mitglieder yearOfCreation und newPrice gelten f\u00fcr alle Komponententypen (mit Ausnahme des speziellen LedDisplay) und m\u00fcssen immer mit copy-paste hinzugef\u00fcgt werden, wenn ein neuer Typ eingef\u00fchrt wird.
              • Die Implementierungsebene der Funktion GetAge ist f\u00fcr alle Komponententypen (mit Ausnahme der speziellen LedDisplay) gleich, auch mit copy-paste wird \"vermehrt\".
              • Die Zeilen in den Konstruktoren, die die Mitglieder yearOfCreation und newPrice initialisieren, werden ebenfalls in jeder Klasse dupliziert.

              Auch wenn diese Codeduplizierung im Moment noch unbedeutend zu sein scheint, wird die Situation mit der Einf\u00fchrung neuer Komponententypen immer schlechter, und es ist besser, k\u00fcnftigen Problemen rechtzeitig vorzubeugen.

              Ein weiteres Problem besteht darin, dass die Auflistung der Teiledaten derzeit schmerzlich unvollst\u00e4ndig ist, da es keine Teileart gibt (nur Alter und Preis). Um den Typ anzuzeigen, muss die Schnittstelle IEquipment erweitert werden, z. B. durch Einf\u00fchrung einer Operation namens GetDescription. F\u00fcgen wir der Schnittstelle eine Funktion GetDescription hinzu!

              public interface IEquipment\n{\n    double GetPrice();\n    int GetAge();\n    string GetDescription();\n}\n

              Dann m\u00fcsste jede Klasse, die die Schnittstelle IEquipment implementiert, diese Methode implementieren, was f\u00fcr viele Klassen eine Menge Arbeit bedeutet (und f\u00fcr eine Mehrkomponenten-Anwendung, d.h. eine Anwendung, die aus mehreren DLLs besteht, oft gar nicht machbar ist, wenn sie nicht in den H\u00e4nden eines einzigen Entwicklers liegen). F\u00fchren wir den Befehl Build aus, um zu \u00fcberpr\u00fcfen, ob Sie nach dem Hinzuf\u00fcgen von GetDescription an drei Stellen \u00dcbersetzungsfehler erhalten.

              Standardimplementierung in der Schnittstelle festlegen

              Es ist wichtig zu wissen, dass ab C# 8 (genauer .NET oder .NET Core Runtime ist auch n\u00f6tig, es ist unter .NET Framework nicht unterst\u00fctzt ) Schnittstellenoperationen eine Standardimplementierung erhalten k\u00f6nnen (default interface methods), so dass wir zur L\u00f6sung des obigen Problems keine abstrakte Klasse ben\u00f6tigen, aber die Schnittstelle kann keine Mitgliedsvariablen mehr haben. Weitere Informationen finden Sie hier: default interface methods.

              public interface IEquipment\n{\n    double GetPrice();\n    int GetAge();\n    string GetDescription() { return \"EquipmentBase\"; }\n}\n
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#abstrakte-klasse","title":"Abstrakte Klasse","text":"

              Eine L\u00f6sung f\u00fcr beide Probleme ist die Einf\u00fchrung eines gemeinsamen abstrakten Vorfahres (mit Ausnahme der Klasse LedDisplay, auf die wir noch zur\u00fcckkommen werden). Wir k\u00f6nnen den Code, der allen Nachkommen gemeinsam ist, dorthin verschieben und eine Standardimplementierung f\u00fcr die neu eingef\u00fchrte Operation GetDescription bereitstellen. Nennen wir unsere neue abstrakte Basisklasse EquipmentBase. Die Frage ist, ob die Schnittstelle IEquipment noch ben\u00f6tigt wird oder ob sie vollst\u00e4ndig durch die neue Klasse EquipmentBase ersetzt werden kann. Wir m\u00fcssen die Schnittstelle IEquipment beibehalten, weil wir unsere Klasse LedDisplay nicht von EquipmentBaseableiten k\u00f6nnen: Sie hat bereits eine obligatorische Basisklasse, DisplayBase, deshalb bezieht sich EquipmentInventory in unserer erweiterten L\u00f6sung auf die verschiedenen Komponenten als Schnittstelle IEquipment.

              Beginnen wir mit der Umwandlung. Unser Klassendiagramm soll die aktive Registerkarte sein. Ziehen wir aus der Toolbox mit drag&drop ein Abstract Class Element auf das Diagramm und benennen wir es EquipmentBase.

              Im Folgenden m\u00fcssen wir die Klassen SoundCard und HardDisk von EquipmentBaseableiten ( LedDisplayhat bereits einen anderen Vorfahren, so dass wir dies dort nicht tun k\u00f6nnen). W\u00e4hlen wir dazu die Verkn\u00fcpfung Inheritance in der Toolbox und ziehen wir dann eine Linie von der Kindklasse zur Basisklasse sowohl f\u00fcr SoundCard als auch f\u00fcr HardDisk.

              Im n\u00e4chsten Schritt \u00e4ndern wir den Code so, dass HardDisk und SoundCard die Schnittstelle IEquipment nicht separat implementieren, sondern ihr gemeinsamer Vorfahre EquipmentBase dies tut. \u00c4ndern wir dazu die Klasse EquipmentBase so, dass sie die Schnittstelle implementiert (entweder durch Einf\u00fcgen eines inheritance Beziehung von EquipmentBasezu IEquipmentim Diagramm oder durch \u00c4ndern des Quellcodes von EquipmentBase ). Entfernen wir die Implementierung von IEquipment aus den Klassen HardDisk und SoundCard (der Vorg\u00e4nger implementiert sie bereits).

              Die relevanten Teile unseres Diagramms und des Quellcodes sehen dann wie folgt aus:

              public abstract class EquipmentBase : IEquipment\n
              public class HardDisk : EquipmentBase\n
              public class SoundCard : EquipmentBase\n

              Unser Code kann aus mehreren Gr\u00fcnden noch nicht kompiliert werden. EquipmentBase implementiert die Schnittstelle IEquipment, aber sie implementiert noch nicht die Operationen der Schnittstelle. Erzeugen wir die Methoden entweder mit Hilfe des Smarttags oder geben wir sie nach den folgenden Grunds\u00e4tzen ein:

              • Die Mitglieder newPrice und yearOfCreation sind in den Klassen HardDisk und SoundCard dupliziert: verschieben (nicht kopieren!) wir sie in den gemeinsamen Vorfahren EquipmentBase und geben wir protected Sichtbarkeit.
              • Die Operation GetAge wird in den Klassen HardDisk und SoundCard dupliziert, l\u00f6schen wir die Implementierung aus diesen Klassen und verschieben wir sie in die Klasse EquipmentBase.
              • Die Operation GetPrice wird als abstrakte Operation in den Vorg\u00e4nger aufgenommen. Dies ist eine bewusste Design-Entscheidung, so dass wir nachkommende Klassen zwingen, diesen Vorgang trotzdem zu \u00fcberschreiben.
              • F\u00fcr GetDescription gilt das Gegenteil: Wir definieren es als virtuell (und nicht abstrakt), d. h. wir geben eine Implementierung im Vorg\u00e4nger an. Auf diese Weise sind die Nachkommen nicht gezwungen, den Vorgang au\u00dfer Kraft zu setzen.

              Der entsprechende Code lautet:

              public abstract class EquipmentBase : IEquipment\n{\n    protected int yearOfCreation;\n    protected int newPrice;\n\n    public int GetAge()\n    {\n        return DateTime.Today.Year - yearOfCreation;\n    }\n\n    public abstract double GetPrice();\n\n    public virtual string GetDescription()\n    {\n        r\u00fcckgabe \"EquipmentBase\";\n    }\n}\n

              Einige zus\u00e4tzliche Gedanken zum Codefragment:

              • Bei abstrakten Klassen muss das Schl\u00fcsselwort \"abstrakt\" vor das Wort \"Klasse\" geschrieben werden.
              • F\u00fcr abstrakte Operationen muss das Schl\u00fcsselwort abstract angegeben werden.
              • In .NET-Umgebung kann man steuern, ob ein Vorgang virtuell ist oder nicht. In dieser Hinsicht ist es \u00e4hnlich wie C++. Wenn man eine Operation virtuell machen will, muss man das Schl\u00fcsselwort virtual f\u00fcr die Operation angeben. Zur Erinnerung: Man definiert eine Operation als virtuell, wenn ihre Nachkommen sie \u00fcberdefinieren. Nur dann ist gew\u00e4hrleistet, dass die Nachfolgeversion aufgerufen wird, wenn die angegebene Operation auf einen Vorg\u00e4ngerverweis angewendet wird.
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#nachkommenschaft","title":"Nachkommenschaft","text":"

              Im n\u00e4chsten Schritt gehen wir zu den Nachkommen von EquipmentBase \u00fcber. Wenn abstrakte und virtuelle Operationen in C# \u00fcberschrieben werden, muss das Schl\u00fcsselwort override im Nachfahren angegeben werden. Zuerst wird die Methode GetPrice neu definiert:

              HardDisk.cs
              public override double GetPrice()\n{\n    return yearOfCreation < (DateTime.Today.Year - 4)\n        ? 0\n        : newPrice - (DateTime.Today.Year - yearOfCreation) * 5000;\n}\n
              SoundCard.cs
              public override double GetPrice()\n{\n    return yearOfCreation < (DateTime.Today.Year - 4)\n        ? 0 \n        : newPrice - (DateTime.Today.Year - yearOfCreation) * 2000;\n}\n

              Im n\u00e4chsten Schritt werden wir die Operation GetDescription in die Klassen HardDisk und SoundCard schreiben. Da wir hier die virtuelle Vorg\u00e4ngerfunktion umdefinieren, m\u00fcssen wir auch das Schl\u00fcsselwort override angeben:

              HardDisk.cs
              public override string GetDescription()\n{\n    return \"Hard Disk\";\n}\n
              SoundCard.cs
              public override string GetDescription()\n{\n    return \"Sound Card\";\n}\n

              Man k\u00f6nnte sich fragen, warum die Entwickler der Sprache C# beschlossen haben, der Definition von Operationen ein zus\u00e4tzliches Schl\u00fcsselwort hinzuzuf\u00fcgen, was im Fall von C++ nicht notwendig war. Der Grund daf\u00fcr ist einfach: Der Code ist aussagekr\u00e4ftiger. Wenn man sich den Code der Nachkommen ansieht, macht das Wort override sofort klar, dass diese Operation in einem der Vorfahren abstrakt oder virtuell ist, ohne dass man sich den Code aller Vorfahren ansehen muss.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#vorfahre-von-leddisplay","title":"Vorfahre von LedDisplay","text":"

              Die Basisklasse unserer LedDisplay Klasse ist gebunden, ihr Code kann nicht ge\u00e4ndert werden, daher k\u00f6nnen wir sie nicht von EquipmentBaseableiten. Wir k\u00f6nnen die Funktion GetAge nicht l\u00f6schen, diese Code-Duplizierung bleibt hier erhalten (aber nur f\u00fcr LedDisplay, die nur eine Klasse unter vielen ist!).

              Note

              Mit ein wenig zus\u00e4tzlicher Arbeit k\u00f6nnten wir diese Doppelung beseitigen. Dazu m\u00fcsste eine statische Hilfsfunktion in eine der Klassen aufgenommen werden (z. B. EquipmentBase) , die das Produktionsjahr ermittelt und das Alter zur\u00fcckgibt. EquipmentBase.GetAge und LedDisplay.GetAge w\u00fcrden diese Hilfsfunktion f\u00fcr ihre Ausgabe verwenden.

              In unserer Klasse LedDisplay m\u00fcssen wir noch GetDescription schreiben:

              LedDisplay.cs
              public string GetDescription()\n{\n    return \"Led Display\";\n}\n

              Beachten wir, dass das Schl\u00fcsselwort override hier NICHT angegeben ist. Wenn eine Schnittstellenfunktion implementiert ist, muss/darf overridenicht ausgeschrieben werden.

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#getdescription-verwenden","title":"GetDescription verwenden","text":"

              \u00c4ndern wir die Operation EquipmentInventory.ListAll, um auch die Beschreibung der Elemente in die Ausgabe zu schreiben:

              EquipmentInventory.cs
              public void ListAll()\n{\n    foreach (IEquipment eq in equipment)\n    {\n        Console.WriteLine(\"$Description: {eq.GetDescription()}\\t\" +\n            $\"Alter: {eq.GetAge()}\\tValue: {eq.GetPrice()}\");\n    }\n}\n

              Dies f\u00fchrt zu einer informativeren Ausgabe, wenn die Anwendung ausgef\u00fchrt wird:

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#duplizierung-von-konstruktorcode","title":"Duplizierung von Konstruktorcode","text":"

              Ein Blick auf unseren Code zeigt, dass es eine weitere Duplikation gibt. Alle Nachfahren von EquipmentBase (HardDisk, SoundCard) haben diese beiden Zeilen in ihrem Konstruktor:

               this.yearOfCreation = yearOfCreation;\n this.newPrice = newPrice;\n

              Wenn wir nachdenken, werden diese yearOfCreation und newPrice Mitglieder im Vorfahren definiert, also sollte es seine Verantwortung sein, sie zu initialisieren. F\u00fcgen wir einen entsprechenden Konstruktor in EquipmentBasehinzu:

              EquipmentBase.cs
              public EquipmentBase(int Erstellungsjahr, int neuerPreis)\n{\n    this.yearOfCreation = yearOfCreation;\n    this.newPrice = newPrice;\n}\n

              Entfernen wir die Initialisierung der beiden Mitglieder aus dem Konstruktor der Nachfahren HardDisk und SoundCard und rufen wir stattdessen den Konstruktor des Vorfahren auf, indem wir auf das Schl\u00fcsselwort base verweisen:

              HardDisk.cs
              public HardDisk(int yearOfCreation, int newPrice, int capacityGB)\n    : base(yearOfCreation, newPrice)\n{\n    this.capacityGB = capacityGB;\n}\n
              SoundCard.cs
              public SoundCard(int yearOfCreation, int newPrice)\n    : base(yearOfCreation, newPrice)\n{\n}\n
              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#bewertung","title":"Bewertung","text":"

              Durch die Verwendung einer Kombination aus Schnittstelle und abstrakter Basisklasse ist es uns gelungen, die L\u00f6sung mit dem geringsten Kompromiss zu entwickeln:

              • IEquipment als Schnittstelle k\u00f6nnen wir alle Arten von Teilen einheitlich behandeln, auch solche, bei denen die Basisklasse gebunden war (mit abstrakter Basisklasse allein h\u00e4tten wir dies nicht erreichen k\u00f6nnen).
              • Durch die Einf\u00fchrung der abstrakten Basisklasse EquipmentBase konnten wir den Code, der in den verschiedenen Komponententypen gemeinsam ist, mit einer Ausnahme in einen gemeinsamen Basisklasse bringen und so Code-Duplikationen vermeiden.
              • Durch die Einf\u00fchrung des abstrakten Vorg\u00e4ngers EquipmentBase k\u00f6nnen wir eine Standardimplementierung f\u00fcr neu eingef\u00fchrte IEquipment Operationen (z.B. GetDescripton) angeben, so dass wir nicht gezwungen sind, diese in jeder IEquipment Implementierungsklasse anzugeben.

              Werfen wir abschlie\u00dfend noch einen Blick auf das UML-Klassendiagramm unserer L\u00f6sung:

              C# 11 - Statische Schnittstellen

              Die neueste Funktion von C# 11 ist die Definition von statischen Schnittstellenmitgliedern, die es Ihnen erm\u00f6glicht, von einer implementierenden Klasse zu verlangen, dass sie Mitglieder hat, die sich nicht auf die Objektinstanz beziehen, sondern die Klasse muss \u00fcber ein bestimmtes statisches Mitglied verf\u00fcgen. Mehr lesen

              "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#hinweis-fakultative-hausaufgabe","title":"Hinweis - fakultative Hausaufgabe","text":"

              Unsere L\u00f6sung unterst\u00fctzt nicht die Anzeige von komponentenspezifischen Daten (z.B. Kapazit\u00e4t f\u00fcr HardDisk ) w\u00e4hrend der Auflistung. Zu diesem Zweck sollte das Schreiben von Komponentendaten in eine formatierte Zeichenkette von der Klasse EqipmentInventory in die Komponentenklassen verlagert werden, und zwar nach den folgenden Grunds\u00e4tzen:

              • Sie k\u00f6nnen eine GetFormattedString Operation in die IEquipment Schnittstelle einf\u00fchren, die ein Objekt vom Typ string zur\u00fcckgibt. Alternativ kann die Operation System.Object ToString() au\u00dfer Kraft gesetzt werden. In .NET sind alle Typen implizit von System.Objectabgeleitet, das \u00fcber eine virtuelle Operation ToString() verf\u00fcgt.
              • In EquipmentBaseschreiben Sie die Formatierung der gemensamen Mitglieder (Beschreibung, Preis, Alter) in Strings.
              • Wenn eine Komponente auch typspezifische Daten hat, dann \u00fcberschreibt ihre Klasse die Funktion, die sie in eine Zeichenkette formatiert: Diese Funktion muss zuerst ihren Vorg\u00e4nger aufrufen (mit dem Schl\u00fcsselwort base ), dann ihre eigenen formatierten Daten an sie anh\u00e4ngen und mit dieser Zeichenkette zur\u00fcckkehren.
              "},{"location":"labor/2-nyelvi-eszkozok/","title":"2. Nyelvi eszk\u00f6z\u00f6k","text":""},{"location":"labor/2-nyelvi-eszkozok/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

              A gyakorlat sor\u00e1n a hallgat\u00f3k megismerkednek a legfontosabb modern, a .NET k\u00f6rnyezetben is rendelkez\u00e9sre \u00e1ll\u00f3 nyelvi eszk\u00f6z\u00f6kkel. Felt\u00e9telezz\u00fck, hogy a hallgat\u00f3 a kor\u00e1bbi tanulm\u00e1nyai sor\u00e1n elsaj\u00e1t\u00edtotta az objektum-orient\u00e1lt szeml\u00e9letm\u00f3dot, \u00e9s tiszt\u00e1ban van az objektum-orient\u00e1lt alapfogalmakkal. Jelen gyakorlat sor\u00e1n azokra a .NET-es nyelvi elemekre koncentr\u00e1lunk, amelyek t\u00falmutatnak az \u00e1ltal\u00e1nos objektum-orient\u00e1lt szeml\u00e9leten, ugyanakkor nagyban hozz\u00e1j\u00e1rulnak a j\u00f3l \u00e1tl\u00e1that\u00f3 \u00e9s k\u00f6nnyen karbantarthat\u00f3 k\u00f3d elk\u00e9sz\u00edt\u00e9s\u00e9hez. Ezek a k\u00f6vetkez\u0151k:

              • Tulajdons\u00e1g (property)
              • Deleg\u00e1t (delegate, met\u00f3dusreferencia)
              • Esem\u00e9ny (event)
              • Attrib\u00fatum (attribute)
              • Lambda kifejez\u00e9s (lambda expression)
              • Generikus t\u00edpus (generic type)
              • N\u00e9h\u00e1ny tov\u00e1bbi nyelvi konstrukci\u00f3

              Kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sok: a 2. el\u0151ad\u00e1s \u00e9s a 3. el\u0151ad\u00e1s eleje \u2013 Nyelvi eszk\u00f6z\u00f6k.

              "},{"location":"labor/2-nyelvi-eszkozok/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

              A gyakorlat elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

              • Visual Studio 2022

              Gyakorlat Linuxon vagy macOS alatt

              A gyakorlat anyag alapvet\u0151en Windowsra \u00e9s Visual Studiora k\u00e9sz\u00fclt, de az elv\u00e9gezhet\u0151 m\u00e1s oper\u00e1ci\u00f3s rendszereken is m\u00e1s fejleszt\u0151eszk\u00f6z\u00f6kkel (pl. VS Code, Rider, Visual Studio for Mac), vagy ak\u00e1r egy sz\u00f6vegszerkeszt\u0151vel \u00e9s CLI (parancssori) eszk\u00f6z\u00f6kkel. Ezt az teszi lehet\u0151v\u00e9, hogy a p\u00e9ld\u00e1k egy egyszer\u0171 Console alkalmaz\u00e1s kontextus\u00e1ban ker\u00fclnek ismertet\u00e9sre (nincsenek Windows specifikus elemek), a .NET SDK pedig t\u00e1mogatott Linuxon \u00e9s macOS alatt. Hello World Linuxon

              "},{"location":"labor/2-nyelvi-eszkozok/#bevezeto","title":"Bevezet\u0151","text":"

              Kitekint\u0151 r\u00e9szek

              Jelen \u00fatmutat\u00f3 t\u00f6bb helyen is b\u0151v\u00edtett ismeretanyagot, illetve extra magyar\u00e1zatot ad meg jelen megjegyz\u00e9ssel egyez\u0151 sz\u00ednnel keretezett \u00e9s ugyanilyen ikonnal ell\u00e1tott form\u00e1ban. Ezek hasznos kitekint\u00e9sek, de nem k\u00e9pezik az alap tananyag r\u00e9sz\u00e9t.

              "},{"location":"labor/2-nyelvi-eszkozok/#megoldas","title":"Megold\u00e1s","text":"A k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se

              L\u00e9nyeges, hogy a labor sor\u00e1n a laborvezet\u0151t k\u00f6vetve kell dolgozni, tilos (\u00e9s \u00e9rtelmetlen) a k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se. Ugyanakkor az ut\u00f3lagos \u00f6n\u00e1ll\u00f3 gyakorl\u00e1s sor\u00e1n hasznos lehet a k\u00e9sz megold\u00e1s \u00e1ttekint\u00e9se, \u00edgy ezt el\u00e9rhet\u0151v\u00e9 tessz\u00fck.

              A megold\u00e1s GitHubon \u00e9rhet\u0151 el itt. A legegyszer\u0171bb m\u00f3d a let\u00f6lt\u00e9s\u00e9re, ha parancssorb\u00f3l a git clone utas\u00edt\u00e1ssal lekl\u00f3nozzuk a g\u00e9p\u00fcnkre:

              git clone https://github.com/bmeviauab00/lab-nyelvieszkozok-megoldas

              Ehhez telep\u00edtve kell legyen a g\u00e9pre a parancssori git, b\u0151vebb inform\u00e1ci\u00f3 itt.

              "},{"location":"labor/2-nyelvi-eszkozok/#0-feladat-var-kulcsszo-implicit-tipusu-lokalis-valtozok-implicitly-typed-local-variables","title":"0. Feladat - var kulcssz\u00f3 - Implicit t\u00edpus\u00fa lok\u00e1lis v\u00e1ltoz\u00f3k (implicitly typed local variables)","text":"

              Egy egyszer\u0171, bemeleg\u00edt\u0151 feladattal kezd\u00fcnk. A k\u00f6vetkez\u0151 p\u00e9ld\u00e1ban egy Person nev\u0171 oszt\u00e1lyt fogunk elk\u00e9sz\u00edteni, mely egy szem\u00e9lyt reprezent\u00e1l.

              1. Hozzunk l\u00e9tre egy \u00faj C# konzolos alkalmaz\u00e1st. .NET alap\u00fat (vagyis ne .NET Framework-\u00f6set):
                • Erre az els\u0151 gyakorlat alkalm\u00e1val l\u00e1ttunk p\u00e9ld\u00e1t, le\u00edr\u00e1sa annak \u00fatmutat\u00f3j\u00e1ban szerepel.
                • A \"Do not use top level statements\" jel\u00f6l\u0151n\u00e9gyzetet pip\u00e1ljuk be a projekt l\u00e9trehoz\u00e1s sor\u00e1n.
              2. Adjunk hozz\u00e1 egy \u00faj oszt\u00e1lyt az alkalmaz\u00e1sunkhoz Person n\u00e9ven. (\u00daj oszt\u00e1ly hozz\u00e1ad\u00e1s\u00e1hoz a Solution Explorerben kattintsunk jobb eg\u00e9rgombbal a projekt f\u00e1jlra \u00e9s v\u00e1lasszuk az Add / Class men\u00fcpontot. Az el\u0151ugr\u00f3 ablakban a l\u00e9trehozand\u00f3 f\u00e1jl nev\u00e9t m\u00f3dos\u00edtsuk Person.cs-re, majd nyomjuk meg az Add gombot.)
              3. Tegy\u00fck az oszt\u00e1lyt publikuss\u00e1. Ehhez az oszt\u00e1ly neve el\u00e9 be kell \u00edrni a public kulcssz\u00f3t. Erre a m\u00f3dos\u00edt\u00e1sra itt val\u00f3j\u00e1ban m\u00e9g nem volna sz\u00fcks\u00e9g, ugyanakkor egy k\u00e9s\u0151bbi feladat m\u00e1r egy publikus oszt\u00e1lyt fog ig\u00e9nyelni.

                public class Person\n{\n}\n
              4. Eg\u00e9sz\u00edts\u00fck ki a Program.cs f\u00e1jl Main f\u00fcggv\u00e9ny\u00e9t, hogy kipr\u00f3b\u00e1lhassuk az \u00faj oszt\u00e1lyunkat.

                static void Main(string[] args)\n{\n    Person p = new Person();\n}\n
              5. A lok\u00e1lis v\u00e1ltoz\u00f3k t\u00edpus\u00e1nak explicit megad\u00e1sa helyett haszn\u00e1lhatjuk a var kulcssz\u00f3t is:

                static void Main(string[] args)\n{\n    var p = new Person();\n}\n

                Ezt implicitly typed local variables-nek, magyarul implicit t\u00edpus\u00fa lok\u00e1lis v\u00e1ltoz\u00f3-nak nevezz\u00fck. Ilyenkor a ford\u00edt\u00f3 a kontextusb\u00f3l, az egyenl\u0151s\u00e9gjel jobb oldal\u00e1b\u00f3l megpr\u00f3b\u00e1lja kital\u00e1lni a v\u00e1ltoz\u00f3 t\u00edpus\u00e1t, fenti esetben ez egy Person lesz. Fontos, hogy ett\u0151l a nyelv m\u00e9g statikusan tipusos marad (teh\u00e1t nem \u00fagy m\u0171k\u00f6dik mint a JavaScript-es var kulcssz\u00f3), mert a p v\u00e1ltoz\u00f3 t\u00edpusa a k\u00e9s\u0151bbiekben nem v\u00e1ltozhat meg, ez csak egy egyszer\u0171 szintaktikai \u00e9des\u00edt\u0151szer annek \u00e9rdek\u00e9ben, hogy t\u00f6m\u00f6rebben tudjunk lok\u00e1lis v\u00e1ltoz\u00f3kat defini\u00e1lni (ne kelljen a t\u00edpust \"dupl\u00e1n\", az = bal \u00e9s jobb oldal\u00e1n is megadni).

                Target-typed new expressions

                Egy m\u00e1sik megk\u00f6zel\u00edt\u00e9s lehet a a C# 9-ben megjelent Target-typed new expressions, ahol a new oper\u00e1tor eset\u00e9n hagyhat\u00f3 el a t\u00edpus, ha az a ford\u00edt\u00f3 \u00e1ltal kital\u00e1lhat\u00f3 a kontextusb\u00f3l (pl.: \u00e9rt\u00e9kad\u00e1s bal oldala, param\u00e9ter t\u00edpusa stb.). A fenti Person konstruktorunk a k\u00f6vetkez\u0151k\u00e9ppen n\u00e9zne ki:

                Person p = new();\n

                Ennek a megk\u00f6zel\u00edt\u00e9snek az el\u0151nye a var-ral szemben, hogy tagv\u00e1ltoz\u00f3k eset\u00e9ben is alkalmazhat\u00f3.

              "},{"location":"labor/2-nyelvi-eszkozok/#1-feladat-tulajdonsag-property","title":"1. Feladat \u2013 Tulajdons\u00e1g (property)","text":"

              A tulajdons\u00e1gok seg\u00edts\u00e9g\u00e9vel tipikusan (de mint l\u00e1tni fogjuk, nem kiz\u00e1r\u00f3lagosan) oszt\u00e1lyok tagv\u00e1ltoz\u00f3ihoz f\u00e9rhet\u00fcnk hozz\u00e1 szintaktika tekintet\u00e9ben hasonl\u00f3 m\u00f3don, mintha egy hagyom\u00e1nyos tagv\u00e1ltoz\u00f3t \u00e9rn\u00e9nk el. A hozz\u00e1f\u00e9r\u00e9s sor\u00e1n azonban lehet\u0151s\u00e9g\u00fcnk van arra, hogy az egyszer\u0171 \u00e9rt\u00e9k lek\u00e9rdez\u00e9s vagy be\u00e1ll\u00edt\u00e1s helyett met\u00f3dusszer\u0171en implement\u00e1ljuk a v\u00e1ltoz\u00f3 el\u00e9r\u00e9s\u00e9nek a m\u00f3dj\u00e1t, s\u0151t k\u00fcl\u00f6n k\u00fcl\u00f6n is meghat\u00e1rozhatjuk a lek\u00e9rdez\u00e9s \u00e9s a be\u00e1ll\u00edt\u00e1s l\u00e1that\u00f3s\u00e1g\u00e1t.

              "},{"location":"labor/2-nyelvi-eszkozok/#tulajdonsag-szintaktikaja","title":"Tulajdons\u00e1g szintaktik\u00e1ja","text":"

              A k\u00f6vetkez\u0151 p\u00e9ld\u00e1ban egy Person nev\u0171 oszt\u00e1lyt fogunk elk\u00e9sz\u00edteni, mely egy szem\u00e9lyt reprezent\u00e1l. K\u00e9t tagv\u00e1ltoz\u00f3ja van, name \u00e9s age. A tagv\u00e1ltoz\u00f3khoz k\u00f6zvetlen\u00fcl nem f\u00e9rhet\u00fcnk hozz\u00e1 (mivel priv\u00e1tok), csak a Name, illetve Age publikus tulajdons\u00e1gokon kereszt\u00fcl kezelhetj\u00fck \u0151ket. A p\u00e9lda j\u00f3l szeml\u00e9lteti, hogy a .NET-es tulajdons\u00e1gok egy\u00e9rtelm\u0171en megfelelnek a C++-b\u00f3l \u00e9s Java-b\u00f3l m\u00e1r j\u00f3l ismert SetX(\u2026) illetve GetX() t\u00edpus\u00fa met\u00f3dusoknak, csak itt ez a megold\u00e1s egys\u00e9gbez\u00e1rtabb m\u00f3don nyelvi szinten t\u00e1mogatott.

              1. Az el\u0151z\u0151 feladatban bevezetett Person oszt\u00e1lyon bel\u00fcl hozzunk l\u00e9tre egy int t\u00edpus\u00fa age nev\u0171 tagv\u00e1ltoz\u00f3t \u00e9s egy ezt el\u00e9rhet\u0151v\u00e9 tev\u0151 Age tulajdons\u00e1got.

                public class Person\n{\n    private int age;\n    public int Age\n    {\n        get { return age; }\n        set { age = value; }\n    }\n}\n

                Visual Studio snippetek

                A laboron ugyan a gyakorl\u00e1s kedv\u00e9\u00e9rt k\u00e9zzel g\u00e9pelt\u00fck be a teljes tulajdons\u00e1got, de a Visual Studio-ban a gyakran el\u0151fordul\u00f3 k\u00f3dr\u00e9szletek l\u00e9trehoz\u00e1s\u00e1ra \u00fagynevezett code snippetek \u00e1llnak rendelkez\u00e9s\u00fcnkre, melyekkel a gyakori nyelvi konstrukci\u00f3kat tudjuk sablonszer\u0171en felhaszn\u00e1lni. A fenti property k\u00f3dr\u00e9szletet a propfull snippettel tudjuk el\u0151csalni. G\u00e9pelj\u00fck be a snippet nev\u00e9t (propfull), majd addig nyomjuk a Tab billenty\u0171t am\u00edg a snippet nem aktiv\u00e1l\u00f3dik (tipikusan 2x).

                Eml\u00edt\u00e9sre m\u00e9lt\u00f3 egy\u00e9b snippetek a teljess\u00e9g ig\u00e9nye n\u00e9lk\u00fcl:

                • ctor: konstruktor
                • for: for ciklus
                • foreach: foreach ciklus
                • prop: auto property (l\u00e1sd k\u00e9s\u0151bb)
                • switch: switch utas\u00edt\u00e1s
                • cw: Console.WriteLine

                Ilyen snippeteket egy\u00e9bk\u00e9nt mi is k\u00e9sz\u00edthet\u00fcnk.

              2. Eg\u00e9sz\u00edts\u00fck ki a Program.cs f\u00e1jl Main f\u00fcggv\u00e9ny\u00e9t, hogy kipr\u00f3b\u00e1lhassuk az \u00faj tulajdons\u00e1gunkat.

                static void Main(string[] args)\n{\n    var p = new Person();\n    p.Age = 17;\n    p.Age++;\n    Console.WriteLine(p.Age);\n}\n
              3. Futtassuk a programunkat (F5)

                L\u00e1thatjuk, hogy a tulajdons\u00e1g a tagv\u00e1ltoz\u00f3khoz hasonl\u00f3an haszn\u00e1lhat\u00f3. A tulajdons\u00e1g lek\u00e9rdez\u00e9se eset\u00e9n a tulajdons\u00e1gban defini\u00e1lt get r\u00e9sz fog lefutni, \u00e9s a tulajdons\u00e1g \u00e9rt\u00e9ke a return \u00e1ltal visszaadott \u00e9rt\u00e9k lesz. A tulajdons\u00e1g be\u00e1ll\u00edt\u00e1sa eset\u00e9n a tulajdons\u00e1gban defini\u00e1lt set r\u00e9sz fog lefutni, \u00e9s a speci\u00e1lis value v\u00e1ltoz\u00f3 \u00e9rt\u00e9ke ebben a szakaszban megfelel a tulajdons\u00e1gnak \u00e9rt\u00e9k\u00fcl adott kifejez\u00e9ssel.

                Figyelj\u00fck meg a fenti megold\u00e1sban azt, hogy milyen eleg\u00e1nsan tudjuk egy \u00e9vvel megemelni az ember \u00e9letkor\u00e1t. Java, vagy C++ k\u00f3dban egy hasonl\u00f3 m\u0171veletet a p.setAge(p.getAge() + 1) form\u00e1ban \u00edrhattunk volna le, amely jelent\u0151sen k\u00f6r\u00fclm\u00e9nyesebb \u00e9s nehezen olvashat\u00f3bb szintaktika a fentin\u00e9l. A tulajdons\u00e1gok haszn\u00e1lat\u00e1nak legf\u0151bb hozad\u00e9ka, hogy k\u00f3dunk szintaktikailag tiszt\u00e1bb lesz, az \u00e9rt\u00e9kad\u00e1sok/lek\u00e9rdez\u00e9sek pedig az esetek t\u00f6bbs\u00e9g\u00e9ben j\u00f3l elv\u00e1lnak a t\u00e9nyleges f\u00fcggv\u00e9nyh\u00edv\u00e1sokt\u00f3l.

              4. Gy\u0151z\u0151dj\u00fcnk meg r\u00f3la, hogy a programunk val\u00f3ban elv\u00e9gzi a get \u00e9s set r\u00e9szek h\u00edv\u00e1s\u00e1t. Ehhez helyezz\u00fcnk t\u00f6r\u00e9spontokat (breakpoint) a getter \u00e9s setter blokkok belsej\u00e9be a k\u00f3dszerkeszt\u0151 bal sz\u00e9l\u00e9n l\u00e1that\u00f3 sz\u00fcrke s\u00e1vra kattintva.

              5. Futtassuk a programot l\u00e9p\u00e9sr\u0151l l\u00e9p\u00e9sre. Ehhez a programot F5 helyett az F11 billenty\u0171vel ind\u00edtsuk, majd az F11 tov\u00e1bbi megnyom\u00e1saival engedj\u00fck sorr\u00f3l sorra a v\u00e9grehajt\u00e1st.

                L\u00e1thatjuk, hogy a programunk val\u00f3ban minden egyes alkalommal megh\u00edvja a gettert, amikor \u00e9rt\u00e9klek\u00e9rdez\u00e9s, illetve a settert, amikor \u00e9rt\u00e9kbe\u00e1ll\u00edt\u00e1s t\u00f6rt\u00e9nik.

              6. A setter f\u00fcggv\u00e9nyek egyik fontos funkci\u00f3ja, hogy lehet\u0151s\u00e9get k\u00edn\u00e1lnak az \u00e9rt\u00e9kvalid\u00e1ci\u00f3ra. Eg\u00e9sz\u00edts\u00fck ki ennek szellem\u00e9ben az Age tulajdons\u00e1g setter-\u00e9t.

                public int Age\n{\n    get { return age; }\n    set \n    {\n        if (value < 0)\n            throw new ArgumentException(\"\u00c9rv\u00e9nytelen \u00e9letkor!\");\n        age = value; \n    }\n}\n

                Figyelj\u00fck meg, hogy m\u00edg az egyszer\u0171 getter \u00e9s setter eset\u00e9ben az \u00e9rt\u00e9klek\u00e9rdez\u00e9st/be\u00e1ll\u00edt\u00e1st egy sorban tartjuk, addig komplexebb t\u00f6rzs eset\u00e9n m\u00e1r t\u00f6bb sorra t\u00f6rdelj\u00fck.

              7. Az alkalmaz\u00e1s tesztel\u00e9s\u00e9hez rendelj\u00fcnk hozz\u00e1 negat\u00edv \u00e9rt\u00e9ket az \u00e9letkorhoz a Program oszt\u00e1ly Main f\u00fcggv\u00e9ny\u00e9ben.

                p.Age = -2;\n
              8. Futtassuk a programot, gy\u0151z\u0151dj\u00fcnk meg arr\u00f3l, hogy az ellen\u0151rz\u00e9s helyesen m\u0171k\u00f6dik, majd h\u00e1r\u00edtsuk el a hib\u00e1t azzal, hogy pozit\u00edvra cser\u00e9lj\u00fck a be\u00e1ll\u00edtott \u00e9letkort.

                p.Age = 2;\n
              "},{"location":"labor/2-nyelvi-eszkozok/#autoimplementalt-tulajdonsag-auto-implemented-property","title":"Autoimplement\u00e1lt tulajdons\u00e1g (auto-implemented property)","text":"

              A mindennapi munk\u00e1nk sor\u00e1n tal\u00e1lkozhatunk a tulajdons\u00e1goknak egy sokkal t\u00f6m\u00f6rebb szintaktik\u00e1j\u00e1val is. Ez a szintaktika akkor alkalmazhat\u00f3, ha egy olyan tulajdons\u00e1got szeretn\u00e9nk l\u00e9trehozni, melyben:

              • nem szeretn\u00e9nk semmilyen kieg\u00e9sz\u00edt\u0151 logik\u00e1val ell\u00e1tni a getter \u00e9s setter met\u00f3dusokat,
              • nincs sz\u00fcks\u00e9g\u00fcnk a priv\u00e1t tagv\u00e1ltoz\u00f3 k\u00f6zvetlen el\u00e9r\u00e9s\u00e9re.

              Erre n\u00e9zz\u00fcnk a k\u00f6vetkez\u0151kben p\u00e9ld\u00e1t.

              1. Eg\u00e9sz\u00edts\u00fck ki a Person oszt\u00e1lyunkat egy ilyen, \u00fan. \u201eautoimplement\u00e1lt\u201d tulajdons\u00e1ggal (auto-implemented property). K\u00e9sz\u00edts\u00fcnk egy string t\u00edpus\u00fa Name nev\u0171 tulajdons\u00e1got.

                public string Name { get; set; }\n

                A szintaktikai k\u00fcl\u00f6nbs\u00e9g a kor\u00e1bbiakhoz k\u00e9pest: a get \u00e9s a set \u00e1gnak sem adtunk implement\u00e1ci\u00f3t (nincsenek kapcsos z\u00e1r\u00f3jelek). Autoimplemet\u00e1lt tulajdons\u00e1g eset\u00e9n a ford\u00edt\u00f3 egy rejtett, k\u00f3db\u00f3l nem el\u00e9rhet\u0151 v\u00e1ltoz\u00f3t gener\u00e1l az oszt\u00e1lyba, mely a tulajdons\u00e1g aktu\u00e1lis \u00e9rt\u00e9k\u00e9nek t\u00e1rol\u00e1s\u00e1ra szolg\u00e1l. Hangs\u00falyozand\u00f3, hogy ez nem a kor\u00e1bban bevezetett name tagv\u00e1ltoz\u00f3t \u00e1ll\u00edtja \u00e9s k\u00e9rdezi le (az ki is t\u00f6r\u00f6lhetn\u00e9nk), hanem egy rejtett, \u00faj v\u00e1ltoz\u00f3n dolgozik!

              2. Most ellen\u0151rizz\u00fck a m\u0171k\u00f6d\u00e9s\u00e9t a Main f\u00fcggv\u00e9ny kieg\u00e9sz\u00edt\u00e9s\u00e9vel.

                static void Main(string[] args)\n{\n    // ...\n    p.Name = \"Luke\";\n    // ...\n    Console.WriteLine(p.Name);\n}\n
              "},{"location":"labor/2-nyelvi-eszkozok/#alapertelmezett-ertek-default-value","title":"Alap\u00e9rtelmezett \u00e9rt\u00e9k (default value)","text":"

              Az autoimplement\u00e1lt tulajdons\u00e1gok eset\u00e9ben megadhat\u00f3 a kezdeti \u00e9rt\u00e9k\u00fck is a deklar\u00e1ci\u00f3 sor\u00e1n.

              1. Adjunk kiindul\u00f3 \u00e9rt\u00e9ket a Name tulajdons\u00e1gnak.

                public string Name { get; set; } = \"anonymous\";\n
              "},{"location":"labor/2-nyelvi-eszkozok/#tulajdonsagok-lathatosaga","title":"Tulajdons\u00e1gok l\u00e1that\u00f3s\u00e1ga","text":"

              A tulajdons\u00e1gok nagy el\u0151nye a teljesen szabad implement\u00e1ci\u00f3 mellett, hogy a getter \u00e9s a setter l\u00e1that\u00f3s\u00e1g\u00e1t k\u00fcl\u00f6n k\u00fcl\u00f6n is lehet \u00e1ll\u00edtani.

              1. \u00c1ll\u00edtsuk a Name tulajdons\u00e1g setter\u00e9nek a l\u00e1that\u00f3s\u00e1g\u00e1t priv\u00e1tra.

                public string Name { get; private set; }\n

                Ilyenkor a Program oszt\u00e1lyban ford\u00edt\u00e1si hib\u00e1t kapunk a p.Name = \"Luke\"; utas\u00edt\u00e1sra. Az alapvet\u0151 szab\u00e1ly az, hogy a getter \u00e9s a setter \u00f6r\u00f6kli a property l\u00e1that\u00f3s\u00e1g\u00e1t, mely tov\u00e1bb sz\u0171k\u00edthet\u0151, de nem laz\u00edthat\u00f3. A l\u00e1that\u00f3s\u00e1g szab\u00e1lyoz\u00e1sa autoimplement\u00e1lt \u00e9s nem autoimplement\u00e1lt tulajdons\u00e1gok eset\u00e9n is haszn\u00e1lhat\u00f3.

              2. \u00c1ll\u00edtsuk vissza a l\u00e1that\u00f3s\u00e1got (t\u00e1vol\u00edtsuk el a private kulcssz\u00f3t a Name tulajdons\u00e1g settere el\u0151l), hogy megsz\u0171nj\u00f6n a ford\u00edt\u00e1si hiba.

              "},{"location":"labor/2-nyelvi-eszkozok/#csak-olvashato-tulajdonsag-readonly-property","title":"Csak olvashat\u00f3 tulajdons\u00e1g (readonly property)","text":"

              A setter elhagyhat\u00f3, \u00edgy egy olyan tulajdons\u00e1got kapunk, mely csak olvashat\u00f3. Autoimplement\u00e1lt tulajdons\u00e1g eset\u00e9n ennek is adhat\u00f3 kezd\u0151\u00e9rt\u00e9k: erre csak konstruktorban, vagy alap\u00e9rtelmezett \u00e9rt\u00e9kkel val\u00f3 ell\u00e1t\u00e1ssal (l\u00e1sd fent) van lehet\u0151s\u00e9g, ellent\u00e9tben a priv\u00e1t setterrel rendelkez\u0151 tulajdons\u00e1gokkal, melyek settere b\u00e1rmely, az oszt\u00e1lyban tal\u00e1lhat\u00f3 tagf\u00fcggv\u00e9nyb\u0151l h\u00edvhat\u00f3.

              Csak olvashat\u00f3 tulajdons\u00e1g defini\u00e1l\u00e1s\u00e1t a k\u00f6vetkez\u0151 k\u00f3dr\u00e9szletek illusztr\u00e1lj\u00e1k (a k\u00f3dunkba NE vezess\u00fck be):

              a) Autoimplement\u00e1lt eset

              public string Name { get; }\n

              b) Nem autoimplement\u00e1lt eset

              private string name;\n...\npublic string Name { get {return name; } }\n
              "},{"location":"labor/2-nyelvi-eszkozok/#szamitott-ertek-calculated-value","title":"Sz\u00e1m\u00edtott \u00e9rt\u00e9k (calculated value)","text":"

              A csak getterrel rendelkez\u0151 tulajdons\u00e1goknak van m\u00e9g egy haszn\u00e1lati m\u00f3dja. Valamilyen sz\u00e1m\u00edtott \u00e9rt\u00e9k meghat\u00e1roz\u00e1s\u00e1ra is haszn\u00e1lhat\u00f3, mely mindig kisz\u00e1mol egy megadott logika alapj\u00e1n egy \u00e9rt\u00e9ket, de a \"csak olvashat\u00f3 tulajdons\u00e1g\"-gal szemben nincs m\u00f6g\u00f6tte k\u00f6zvetlen\u00fcl a tulajdons\u00e1ghoz tartoz\u00f3 adattag. Ezt a k\u00f6vetkez\u0151 k\u00f3dr\u00e9szlet illusztr\u00e1lja (a k\u00f3dunkba NE vezess\u00fck be):

              public int AgeInDogYear { get { return Age * 7; } }\n
              "},{"location":"labor/2-nyelvi-eszkozok/#2-feladat-delegat-delegate-metodusreferencia","title":"2. Feladat \u2013 Deleg\u00e1t (delegate, met\u00f3dusreferencia)","text":"

              Forduljon a k\u00f3d!

              A tov\u00e1bbi feladatok \u00e9p\u00edteni fognak az el\u0151z\u0151 feladatok v\u00e9geredm\u00e9nyeire. Ha programod nem fordul le, vagy nem megfelel\u0151en m\u0171k\u00f6dik, jelezd ezt a gyakorlatvezet\u0151dnek a feladatok v\u00e9g\u00e9n, \u00e9s seg\u00edt elh\u00e1r\u00edtani a hib\u00e1t.

              A deleg\u00e1tok t\u00edpusos met\u00f3dusreferenci\u00e1kat jelentenek .NET-ben, a C/C++ f\u00fcggv\u00e9nypointerek modern megfelel\u0151i. Egy deleg\u00e1t seg\u00edts\u00e9g\u00e9vel egy olyan t\u00edpus\u00fa v\u00e1ltoz\u00f3t defini\u00e1lhatunk, amellyel met\u00f3dusokra tudunk mutatni/hivatkozni. Nem ak\u00e1rmilyenre, hanem - a C++ f\u00fcggv\u00e9nypointerekkel anal\u00f3g m\u00f3don - olyanokra, amely t\u00edpusa (param\u00e9terlist\u00e1ja \u00e9s visszat\u00e9r\u00e9si \u00e9rt\u00e9ke) megfelel a deleg\u00e1t t\u00edpus\u00e1nak. A deleg\u00e1t v\u00e1ltoz\u00f3 \"megh\u00edv\u00e1s\u00e1val\" az \u00e9rt\u00e9k\u00fcl adott (beregisztr\u00e1lt) met\u00f3dus automatikusan megh\u00edv\u00f3dik. A deleg\u00e1tok haszn\u00e1lat\u00e1nak egyik el\u0151nye az, hogy fut\u00e1si id\u0151ben d\u00f6nthetj\u00fck el, hogy t\u00f6bb met\u00f3dus k\u00f6z\u00fcl \u00e9ppen melyiket szeretn\u00e9nk megh\u00edvni.

              N\u00e9h\u00e1ny p\u00e9lda deleg\u00e1tok haszn\u00e1lat\u00e1ra:

              • egy univerz\u00e1lis sorrendez\u0151 f\u00fcggv\u00e9nynek param\u00e9terk\u00e9nt az elemek \u00f6sszehasonl\u00edt\u00e1s\u00e1t v\u00e9gz\u0151 f\u00fcggv\u00e9ny \u00e1tad\u00e1sa,
              • egy \u00e1ltal\u00e1nos gy\u0171jtem\u00e9nyen univerz\u00e1lis sz\u0171r\u00e9si logika megval\u00f3s\u00edt\u00e1sa, melynek param\u00e9terben egy deleg\u00e1t form\u00e1j\u00e1ban adjuk \u00e1t azt a f\u00fcggv\u00e9nyt, amely eld\u00f6nti, hogy egy elemet bele kell-e venni a sz\u0171rt list\u00e1ba,
              • a publish-subscribe minta megval\u00f3s\u00edt\u00e1sa, amikor bizonyos objektumok m\u00e1s objektumokat \u00e9rtes\u00edtenek bizonyos magukkal kapcsolatos esem\u00e9nyek bek\u00f6vetkez\u00e9s\u00e9r\u0151l.

              A k\u00f6vetkez\u0151 p\u00e9ld\u00e1nkban lehet\u0151v\u00e9 tessz\u00fck, hogy a kor\u00e1bban l\u00e9trehozott Person oszt\u00e1ly objektumai szabadon \u00e9rtes\u00edthess\u00e9k m\u00e1s oszt\u00e1lyok objektumait arr\u00f3l, ha egy szem\u00e9ly \u00e9letkora megv\u00e1ltozott. Ennek \u00e9rdek\u00e9ben bevezet\u00fcnk egy deleg\u00e1t t\u00edpust (AgeChangingDelegate), mely param\u00e9terlist\u00e1j\u00e1ban \u00e1t tudja adni az ember\u00fcnk \u00e9letkor\u00e1nak aktu\u00e1lis, illetve \u00faj \u00e9rt\u00e9k\u00e9t. Ezt k\u00f6vet\u0151en l\u00e9trehozunk egy publikus AgeChangingDelegate t\u00edpus\u00fa tagv\u00e1ltoz\u00f3t a Person oszt\u00e1lyban, mely lehet\u0151v\u00e9 teszi, hogy egy k\u00fcls\u0151 f\u00e9l megadhassa azt a f\u00fcggv\u00e9nyt, amelyen kereszt\u00fcl az adott Person p\u00e9ld\u00e1ny v\u00e1ltoz\u00e1sair\u00f3l \u00e9rtes\u00edt\u00e9st k\u00e9r.

              1. Hozzunk l\u00e9tre egy \u00faj deleg\u00e1t t\u00edpust, mely void visszat\u00e9r\u00e9si \u00e9rt\u00e9k\u0171, \u00e9s k\u00e9t darab int param\u00e9tert elv\u00e1r\u00f3 f\u00fcggv\u00e9nyre tud hivatkozni. Figyelj\u00fcnk r\u00e1, hogy az \u00faj t\u00edpust a Person oszt\u00e1ly el\u0151tt, k\u00f6zvetlen\u00fcl a n\u00e9vt\u00e9r scope-j\u00e1ban defini\u00e1ljuk!

                namespace PropertyDemo\n{\n    public delegate void AgeChangingDelegate(int oldAge, int newAge);\n\n    public class Person\n    {\n        // ...\n

                Az AgeChangingDelegate egy t\u00edpus (figyelj\u00fck a VS sz\u00ednez\u00e9s\u00e9t is), mely b\u00e1rhol szerepelhet, ahol t\u00edpus \u00e1llhat (pl. lehet l\u00e9trehozni ez alapj\u00e1n tagv\u00e1ltoz\u00f3t, lok\u00e1lis v\u00e1ltoz\u00f3t, f\u00fcggv\u00e9ny param\u00e9tert stb.).

              2. Tegy\u00fck lehet\u0151v\u00e9, hogy a Person objektumai r\u00e1mutathassanak tetsz\u0151leges, a fenti szignat\u00far\u00e1nak megfelel\u0151 f\u00fcggv\u00e9nyre. Ehhez hozzunk l\u00e9tre egy AgeChangingDelegate t\u00edpus\u00fa tagv\u00e1ltoz\u00f3t a Person oszt\u00e1lyban!

                public class Person\n{\n    public AgeChangingDelegate AgeChanging;\n

                Ez \u00edgy most mennyire objektumorient\u00e1lt?

                A publikus tagv\u00e1ltoz\u00f3k\u00e9nt l\u00e9trehozott met\u00f3dusreferencia val\u00f3j\u00e1ban (egyel\u0151re) s\u00e9rti az objektumorint\u00e1lt egys\u00e9gbez\u00e1r\u00e1si/inform\u00e1ci\u00f3rejt\u00e9si elveket. Erre k\u00e9s\u0151bb visszat\u00e9r\u00fcnk m\u00e9g.

              3. H\u00edvjuk meg a f\u00fcggv\u00e9nyt minden alkalommal, amikor az ember\u00fcnk kora megv\u00e1ltozik. Ehhez eg\u00e9sz\u00edts\u00fck ki az Age tulajdons\u00e1g setter\u00e9t a k\u00f6vetkez\u0151kkel.

                public int Age\n{\n    get { return age; }\n    set \n    {\n        if (value < 0)\n            throw new ArgumentException(\"\u00c9rv\u00e9nytelen \u00e9letkor!\");\n        if (AgeChanging != null)\n            AgeChanging(age, value);\n        age = value; \n    }\n}\n

                A fenti k\u00f3dr\u00e9szlet sz\u00e1mos fontos szab\u00e1lyt demonstr\u00e1l:

                • A valid\u00e1ci\u00f3s logika \u00e1ltal\u00e1ban megel\u0151zi az \u00e9rtes\u00edt\u00e9si logik\u00e1t.
                • Az \u00e9rtes\u00edt\u00e9si logika jelleg\u00e9t\u0151l f\u00fcgg, hogy az \u00e9rt\u00e9kad\u00e1s el\u0151tt, vagy ut\u00e1n futtatjuk le (ebben az esetben, mivel a \"changing\" sz\u00f3 egy folyamatban l\u00e9v\u0151 dologra utal, az \u00e9rtes\u00edt\u00e9s megel\u0151zi az \u00e9rt\u00e9kad\u00e1st, a bek\u00f6vetkez\u00e9st m\u00falt id\u0151 jelezni: \"changed\")
                • Fel kell k\u00e9sz\u00fcln\u00fcnk r\u00e1, hogy a delegate t\u00edpus\u00fa tagv\u00e1ltoz\u00f3hoz m\u00e9g senki nem rendelt \u00e9rt\u00e9ket (nincs egy subscriber/el\u0151fizet\u0151 sem). Ilyen esetekben a megh\u00edv\u00e1suk kiv\u00e9telt okozna, ez\u00e9rt megh\u00edv\u00e1s el\u0151tt mindig ellen\u0151rizni kell, hogy a tagv\u00e1ltoz\u00f3 \u00e9rt\u00e9ke null-e.
                • Az esem\u00e9ny els\u00fct\u00e9sekor a null vizsg\u00e1latot \u00e9s az esem\u00e9ny els\u00fct\u00e9st eleg\u00e1nsabb, t\u00f6m\u00f6rebb, \u00e9s sz\u00e1lbiztosabb form\u00e1ban is meg tudjuk tenni a \"?.\" null-conditional oper\u00e1torral (C# 6-t\u00f3l):
                if (AgeChanging != null)\n    AgeChanging(age, value);\n

                helyett

                AgeChanging?.Invoke(age, value);\n

                Ez csak akkor s\u00fcti el az esem\u00e9nyt, ha nem null, egy\u00e9bk\u00e9nt semmit nem csin\u00e1l.

              4. Ha szigor\u00faan n\u00e9zz\u00fck, akkor csak akkor kellene els\u00fctni az esem\u00e9nyt, ha a kor val\u00f3ban v\u00e1ltozik is, vagyis a property set \u00e1g\u00e1ban meg kellene vizsg\u00e1lni, az \u00faj \u00e9rt\u00e9k egyezik-e a r\u00e9givel. Megold\u00e1s lehet, ha a setter els\u0151 sor\u00e1ban azonnal visszat\u00e9r\u00fcnk, ha az \u00faj \u00e9rt\u00e9k egyezik a r\u00e9givel:

                if (age == value) \n    return;\n\u2026\n
              5. K\u00e9sz vagyunk a Person oszt\u00e1ly k\u00f3dj\u00e1val. T\u00e9rj\u00fcnk \u00e1t az el\u0151fizet\u0151re! Ehhez mindenek el\u0151tt a Program oszt\u00e1lyt kell kieg\u00e9sz\u00edten\u00fcnk egy \u00fajabb f\u00fcggv\u00e9nnyel.

                class Program\n{\n    // ...\n\n    private static void PersonAgeChanging(int oldAge, int newAge)\n    {\n        Console.WriteLine(oldAge + \" => \" + newAge);\n    }\n}\n

                Tipp

                Fokozottan \u00fcgyelj\u00fcnk r\u00e1, hogy az \u00faj f\u00fcggv\u00e9ny a megfelel\u0151 scope-ba ker\u00fclj\u00f6n! M\u00edg a delegate t\u00edpust az oszt\u00e1lyon k\u00edv\u00fclre (de namespace-en bel\u00fclre) helyezt\u00fck el, a f\u00fcggv\u00e9nyt az oszt\u00e1lyon bel\u00fclre helyezz\u00fck!

              6. V\u00e9gezet\u00fcl iratkozzunk fel a v\u00e1ltoz\u00e1sk\u00f6vet\u00e9sre a Main f\u00fcggv\u00e9nyben!

                static void Main(string[] args)\n{\n  Person p = new Person();\n  p.AgeChanging = new AgeChangingDelegate(PersonAgeChanging);\n  // ...\n
              7. Futtassuk a programot!

                Pl. az AgeChanging?.Invoke(age, value); sorra t\u00f6r\u00e9spontot helyezve, az alkalmaz\u00e1st debuggolva futtatva, \u00e9s a k\u00f3dot l\u00e9ptetve figyelj\u00fck meg, hogy az esem\u00e9ny minden egyes setter fut\u00e1skor, \u00edgy az els\u0151 \u00e9rt\u00e9kad\u00e1skor \u00e9s az inkrement\u00e1l\u00e1s sor\u00e1n egyar\u00e1nt lefut.

              8. Eg\u00e9sz\u00edts\u00fck ki a Main f\u00fcggv\u00e9nyt t\u00f6bbsz\u00f6ri feliratkoz\u00e1ssal (a += oper\u00e1torral lehet \u00faj feliratkoz\u00f3t felvenni a megl\u00e9v\u0151k mell\u00e9), majd futtassuk a programot.

                p.AgeChanging = new AgeChangingDelegate(PersonAgeChanging);\np.AgeChanging += new AgeChangingDelegate(PersonAgeChanging);\np.AgeChanging += PersonAgeChanging; // T\u00f6m\u00f6rebb szintaktika\n

                L\u00e1that\u00f3an minden egyes \u00e9rt\u00e9kv\u00e1ltoz\u00e1skor mind a h\u00e1rom beregisztr\u00e1lt/\u201efeliratkozott\u201d f\u00fcggv\u00e9ny lefut. Ez az\u00e9rt lehets\u00e9ges, mert a delegate t\u00edpus\u00fa tagv\u00e1ltoz\u00f3k val\u00f3j\u00e1ban nem csup\u00e1n egy f\u00fcggv\u00e9nyreferenci\u00e1t, hanem egy f\u00fcggv\u00e9nyreferencia-list\u00e1t tartalmaznak (\u00e9s tartanak karban).

                Figyelj\u00fck meg a fenti harmadik sorban, hogy a f\u00fcggv\u00e9nyreferenci\u00e1kat az el\u0151sz\u00f6r l\u00e1tottn\u00e1l t\u00f6m\u00f6rebb szintaxissal is le\u00edrhatjuk: csak a f\u00fcggv\u00e9ny nev\u00e9t adjuk meg a += oper\u00e1tor ut\u00e1n, a new AgeChangingDelegate(...) n\u00e9lk\u00fcl. Ett\u0151l f\u00fcggetlen\u00fcl ekkor is egy AgeChangingDelegate objektum fogja becsomagolni a PersonAgeChanging f\u00fcggv\u00e9nyeket a sz\u00ednfalak m\u00f6g\u00f6tt. A gyakorlatban ezt a t\u00f6m\u00f6rebb szintaktik\u00e1t szoktuk haszn\u00e1lni.

              9. Pr\u00f3b\u00e1ljuk ki a leiratkoz\u00e1st is (szabadon v\u00e1lasztott ponton), majd futtassuk a programot.

                p.AgeChanging -= PersonAgeChanging;\n
              "},{"location":"labor/2-nyelvi-eszkozok/#3-feladat-esemeny-event","title":"3. Feladat \u2013 Esem\u00e9ny (event)","text":"

              Ahogyan a tulajdons\u00e1gok a getter \u00e9s setter met\u00f3dusoknak, addig a fent l\u00e1tott delegate mechanizmus a Java-b\u00f3l ismert Event Listener-eknek k\u00edn\u00e1lj\u00e1k egy a szintaktika tekintet\u00e9ben letisztultabb alternat\u00edv\u00e1j\u00e1t. A fenti megold\u00e1sunk azonban egyel\u0151re m\u00e9g s\u00falyosan s\u00e9rt p\u00e1r OO elvet (egys\u00e9gbez\u00e1r\u00e1s, inform\u00e1ci\u00f3rejt\u00e9s). Ezt az al\u00e1bbi k\u00e9t p\u00e9ld\u00e1val tudjuk demonstr\u00e1lni.

              1. Az esem\u00e9nyt val\u00f3j\u00e1ban k\u00edv\u00fclr\u0151l (m\u00e1s oszt\u00e1lyok m\u0171veleteib\u0151l) is ki tudjuk v\u00e1ltani. Ez szerencs\u00e9tlen, hiszen \u00edgy az esem\u00e9ny hamis m\u00f3don akkor is kiv\u00e1lthat\u00f3, r\u00e1ad\u00e1sul val\u00f3tlan adatokkal, amikor az a gyakorlatban be sem k\u00f6vetkezett, becsapva az \u00f6sszes el\u0151fizet\u0151t. Ennek demonstr\u00e1l\u00e1s\u00e1ra sz\u00farjuk be a k\u00f6vetkez\u0151 sort a Main f\u00fcggv\u00e9ny v\u00e9g\u00e9re.

                p.AgeChanging(67, 12);\n

                Itt a p Person objektum vonatkoz\u00e1s\u00e1ban egy val\u00f3tlan \u00e9letkorv\u00e1ltoz\u00e1s esem\u00e9nyt v\u00e1ltottunk ki, becsapva minden el\u0151fizet\u0151t. A j\u00f3 megold\u00e1s az lenne, ha az esem\u00e9nyt csak a Person oszt\u00e1ly m\u0171veletei tudn\u00e1k kiv\u00e1ltani.

              2. Egy m\u00e1sik probl\u00e9ma a k\u00f6vetkez\u0151. B\u00e1r a += \u00e9s a -= tekintettel vannak a list\u00e1ba feliratkozott t\u00f6bbi f\u00fcggv\u00e9nyre, val\u00f3j\u00e1ban az = oper\u00e1torral b\u00e1rmikor fel\u00fcl\u00edrhatjuk (kit\u00f6r\u00f6lhetj\u00fck) m\u00e1sok feliratkoz\u00e1sait. Pr\u00f3b\u00e1ljuk ki ezt is, a k\u00f6vetkez\u0151 sor besz\u00far\u00e1s\u00e1val (k\u00f6zvetlen\u00fcl a fel \u00e9s leiratkoz\u00e1sok ut\u00e1n sz\u00farjuk be).

                p.AgeChanging = null;\n
              3. L\u00e1ssuk el az event kulcssz\u00f3val az AgeChanging tagv\u00e1ltoz\u00f3t Person.cs-ben!

                Person.cs
                public event AgeChangingDelegate AgeChanging;\n

                Az event kulcssz\u00f3 feladata val\u00f3j\u00e1ban az, hogy a fenti k\u00e9t probl\u00e9m\u00e1t kiz\u00e1rva visszak\u00e9nyszer\u00edtse programunkat az objektumorient\u00e1lt mederbe.

              4. Pr\u00f3b\u00e1ljuk meg leford\u00edtani a programot. L\u00e1tni fogjuk, hogy a ford\u00edt\u00f3 a kor\u00e1bbi kih\u00e1g\u00e1sainkat most m\u00e1r ford\u00edt\u00e1si hibak\u00e9nt kezeli.

              5. T\u00e1vol\u00edtsuk el a h\u00e1rom hib\u00e1s k\u00f3dsort (figyelj\u00fck meg, hogy m\u00e1r az els\u0151 k\u00f6zvetlen \u00e9rt\u00e9kad\u00e1s is hib\u00e1nak min\u0151s\u00fcl), majd ford\u00edtsuk le \u00e9s futtassuk az alkalmaz\u00e1sunkat!

              "},{"location":"labor/2-nyelvi-eszkozok/#4-feladat-attributumok","title":"4. Feladat \u2013 Attrib\u00fatumok","text":""},{"location":"labor/2-nyelvi-eszkozok/#sorositas-testreszabasa-attributummal","title":"Soros\u00edt\u00e1s testreszab\u00e1sa attrib\u00fatummal","text":"

              Az attrib\u00fatumok seg\u00edts\u00e9g\u00e9vel deklarat\u00edv m\u00f3don metaadatokkal l\u00e1thatjuk el forr\u00e1sk\u00f3dunkat. Az attrib\u00fatum is tulajdonk\u00e9ppen egy oszt\u00e1ly, melyet hozz\u00e1k\u00f6t\u00fcnk a program egy megadott elem\u00e9hez (t\u00edpushoz, oszt\u00e1lyhoz, interf\u00e9szhez, met\u00f3dushoz stb.). Ezeket a metainform\u00e1ci\u00f3kat a program fut\u00e1sa k\u00f6zben b\u00e1rki (ak\u00e1r mi magunk is) kiolvashatja az \u00fagynevezett reflection mechanizmus seg\u00edts\u00e9g\u00e9vel. Az attrib\u00fatumok a Java annot\u00e1ci\u00f3k .NET-beli megfelel\u0151inek is tekinthet\u0151k.

              property vs. attrib\u00fatum vs. static

              Felmer\u00fcl a k\u00e9rd\u00e9s, hogy milyen oszt\u00e1lyjellemz\u0151k ker\u00fcljenek tulajdons\u00e1gokba \u00e9s melyek attrib\u00fatumokba egy oszt\u00e1ly eset\u00e9ben. A tulajdons\u00e1gok mag\u00e1ra az objektum p\u00e9ld\u00e1nyra vonatkoznak, m\u00edg az attrib\u00fatum az azt le\u00edr\u00f3 oszt\u00e1lyra (vagy annak valamilyen tagj\u00e1ra).

              Ilyen szempontb\u00f3l az attrib\u00fatumok k\u00f6zelebb \u00e1llnak a statikus tulajdons\u00e1gokhoz, m\u00e9gis megfontoland\u00f3, hogy egy adott adatot statikus tagk\u00e9nt vagy attrib\u00fatumk\u00e9nt defini\u00e1ln\u00e1nk. Attrib\u00fatummal sokkal deklarat\u00edvabb a le\u00edr\u00e1s, \u00e9s nem szennyezz\u00fck olyan r\u00e9szletekkel a k\u00f3dot, melyeknek nem kellene az oszt\u00e1ly publikus interf\u00e9sz\u00e9n megjelennie.

              A NET sz\u00e1mos be\u00e9p\u00edtett attrib\u00fatumot defini\u00e1l, melyek funkci\u00f3ja a legk\u00fcl\u00f6nb\u00f6z\u0151bb f\u00e9le lehet. A k\u00f6vetkez\u0151 p\u00e9ld\u00e1ban haszn\u00e1lt attrib\u00fatumok p\u00e9ld\u00e1ul az XML soros\u00edt\u00f3val k\u00f6z\u00f6lnek k\u00fcl\u00f6nb\u00f6z\u0151 metainform\u00e1ci\u00f3kat.

              1. Sz\u00farjuk be a Main f\u00fcggv\u00e9ny v\u00e9g\u00e9re a k\u00f6vetkez\u0151 k\u00f3dr\u00e9szletet, majd futtassuk a programunkat!

                var serializer = new XmlSerializer(typeof(Person));\nvar stream = new FileStream(\"person.txt\", FileMode.Create);\nserializer.Serialize(stream, p);\nstream.Close();\nProcess.Start(new ProcessStartInfo\n{\n    FileName = \"person.txt\",\n    UseShellExecute = true,\n});\n

                A fenti p\u00e9ld\u00e1b\u00f3l az utols\u00f3 Process.Start f\u00fcggv\u00e9nyh\u00edv\u00e1s nem a soros\u00edt\u00f3 logika r\u00e9sze, csup\u00e1n egy frapp\u00e1ns megold\u00e1s arra, hogy a Windows alap\u00e9rtelmezett sz\u00f6vegf\u00e1jl n\u00e9zeget\u0151j\u00e9vel megnyissuk a keletkezett adat\u00e1llom\u00e1nyt. Ezt kipr\u00f3b\u00e1lhatjuk, de a haszn\u00e1lt .NET runtime-t\u00f3l \u00e9s az oper\u00e1ci\u00f3s rendszer\u00fcnkt\u0151l f\u00fcgg, t\u00e1mogatott-e. Ha nem, fut\u00e1s k\u00f6zben hib\u00e1t kapunk. Ez esetben hagyjuk kikommentezve, \u00e9s a person.txt f\u00e1jlt a f\u00e1jlrendszerben megkeresve k\u00e9zzel nyissuk meg (a Visual Studio mapp\u00e1nkban a *\\bin\\Debug\\* alatt tal\u00e1lhat\u00f3 az .exe alkalmaz\u00e1sunk mellett).

              2. N\u00e9zz\u00fck meg a keletkezett f\u00e1jl szerkezet\u00e9t. Figyelj\u00fck meg, hogy minden tulajdons\u00e1g a nev\u00e9nek megfelel\u0151 XML elemre lett lek\u00e9pezve.

              3. .NET attrib\u00fatumok seg\u00edts\u00e9g\u00e9vel olyan metaadatokkal l\u00e1thatjuk el a Person oszt\u00e1lyunkat, melyek k\u00f6zvetlen\u00fcl m\u00f3dos\u00edtj\u00e1k a soros\u00edt\u00f3 viselked\u00e9s\u00e9t. Az XmlRoot attrib\u00fatum lehet\u0151s\u00e9get k\u00edn\u00e1l a gy\u00f6k\u00e9relem \u00e1tnevez\u00e9s\u00e9re. Helyezz\u00fck el a Person oszt\u00e1ly f\u00f6l\u00e9!

                [XmlRoot(\"Szem\u00e9ly\")]\npublic class Person \n{\n    // ...\n}\n
              4. Az XmlAttribute attrib\u00fatum jelzi a soros\u00edt\u00f3 sz\u00e1m\u00e1ra, hogy a jel\u00f6lt tulajdons\u00e1got ne xml elemre, hanem xml attrib\u00fatumra k\u00e9pezze le. L\u00e1ssuk el ezzel az Age tulajdons\u00e1got (\u00e9s ne a tagv\u00e1ltoz\u00f3t!)!

                [XmlAttribute(\"Kor\")]\npublic int Age\n
              5. Az XmlIgnore attrib\u00fatum jelzi a soros\u00edt\u00f3nak, hogy a jel\u00f6lt tulajdons\u00e1g teljesen elhagyand\u00f3 az eredm\u00e9nyb\u0151l. Pr\u00f3b\u00e1ljuk ki a Name tulajdons\u00e1g f\u00f6l\u00f6tt.

                [XmlIgnore]\npublic string Name { get; set; }\n
              6. Futtassuk az alkalmaz\u00e1sunkat! Hasonl\u00edtsuk \u00f6ssze az eredm\u00e9nyt a kor\u00e1bbiakkal.

              7. "},{"location":"labor/2-nyelvi-eszkozok/#5-feladat-delegat-2","title":"5. Feladat \u2013 Deleg\u00e1t 2.","text":"

                A 2. \u00e9s 3. feladatokban a deleg\u00e1tokkal esem\u00e9ny alap\u00fa \u00fczenetk\u00fcld\u00e9st val\u00f3s\u00edtottunk meg. A deleg\u00e1tok haszn\u00e1lat\u00e1nak m\u00e1sik tipikus eset\u00e9ben a f\u00fcggv\u00e9nyreferenci\u00e1kat arra haszn\u00e1ljuk, hogy egy algoritmus vagy \u00f6sszetettebb m\u0171velet sz\u00e1m\u00e1ra egy el\u0151re nem defini\u00e1lt l\u00e9p\u00e9s implement\u00e1ci\u00f3j\u00e1t \u00e1tadjuk.

                A be\u00e9p\u00edtett generikus lista oszt\u00e1ly (List<T>) FindAll f\u00fcggv\u00e9nye p\u00e9ld\u00e1ul k\u00e9pes arra, hogy visszaadjon egy \u00faj list\u00e1ban minden olyan elemet, mely egy adott felt\u00e9telnek eleget tesz. A konkr\u00e9t sz\u0171r\u00e9si felt\u00e9telt egy f\u00fcggv\u00e9ny, pontosabban delegate form\u00e1j\u00e1ban adhatjuk meg param\u00e9terben (ez a FindAll minden elemre megh\u00edvja), mely igazat ad minden olyan elemre, amit az eredm\u00e9nylist\u00e1ban szeretn\u00e9nk l\u00e1tni. A f\u00fcggv\u00e9ny param\u00e9ter\u00e9nek a t\u00edpusa a k\u00f6vetkez\u0151 el\u0151re defini\u00e1lt delegate t\u00edpus (nem kell beg\u00e9pelni/l\u00e9trehozni, hiszen m\u00e1r l\u00e9tezik):

                public delegate bool Predicate<T>(T obj)\n

                Note

                A fenti teljes defin\u00edci\u00f3 megjelen\u00edt\u00e9s\u00e9hez csak g\u00e9pelj\u00fck be valahova, pl. a Main f\u00fcggv\u00e9ny v\u00e9g\u00e9re a Predicate t\u00edpusnevet, kattintsunk rajta eg\u00e9rrel, \u00e9s az F12 billenty\u0171vel navig\u00e1ljunk el a defin\u00edci\u00f3j\u00e1hoz.

                Vagyis bemenetk\u00e9nt egy olyan t\u00edpus\u00fa v\u00e1ltoz\u00f3t v\u00e1r, mint a listaelemek t\u00edpusa, kimenetk\u00e9nt pedig egy logikai (bool) \u00e9rt\u00e9ket. A fentiek demonstr\u00e1l\u00e1s\u00e1ra kieg\u00e9sz\u00edtj\u00fck a kor\u00e1bbi programunkat egy sz\u0171r\u00e9ssel, mely a list\u00e1b\u00f3l csak a p\u00e1ratlan elemeket fogja megtartani.

                1. Val\u00f3s\u00edtsunk meg egy olyan sz\u0171r\u0151f\u00fcggv\u00e9nyt az alkalmaz\u00e1sunkban, amely a p\u00e1ratlan sz\u00e1mokat adja vissza:

                  private static bool MyFilter(int n)\n{\n    return n % 2 == 1;\n}\n
                2. Eg\u00e9sz\u00edts\u00fck ki a kor\u00e1bban \u00edrt k\u00f3dunkat a sz\u0171r\u0151 f\u00fcggv\u00e9ny\u00fcnk haszn\u00e1lat\u00e1val:

                  var list = new List<int>();\nlist.Add(1);\nlist.Add(2);\nlist.Add(3);\nlist = list.FindAll(MyFilter);\n\nforeach (int n in list)\n{\n    Console.WriteLine($\"Value: {n}\");\n}\n
                3. Futtassuk az alkalmaz\u00e1st. Figyelj\u00fck meg, hogy a konzolon val\u00f3ban csak a p\u00e1ratlan sz\u00e1mok jelennek meg.

                4. \u00c9rdekess\u00e9gk\u00e9nt elhelyezhet\u00fcnk egy t\u00f6r\u00e9spontot (breakpoint) a MyFilter f\u00fcggv\u00e9ny\u00fcnk belsej\u00e9ben, \u00e9s megfigyelhetj\u00fck, hogy a f\u00fcggv\u00e9ny val\u00f3ban minden egyes listaelemre k\u00fcl\u00f6n-k\u00fcl\u00f6n megh\u00edv\u00f3dik.

                Collection initializer szintaxis

                Minden Add met\u00f3dussal rendelkez\u0151, az IEnumerable interf\u00e9szt implement\u00e1l\u00f3 oszt\u00e1lyra (tipikusan kollekci\u00f3k) a collection initializer szintaxis az al\u00e1bbi m\u00f3don:

                var list = new List<int>() { 1, 2, 3 };\n

                C# 12-t\u0151l kezdve m\u00e9g egyszer\u0171bb szintaxis (\u00fan. collection expression) is haszn\u00e1lhat\u00f3 egy gy\u0171jtem\u00e9ny inicializ\u00e1l\u00e1s\u00e1ra, ha v\u00e1ltoz\u00f3 t\u00edpus\u00e1ra a ford\u00edt\u00f3 ki tudja k\u00f6vetkeztetni, hogy gy\u0171jetm\u00e9nyr\u0151l van sz\u00f3. Pl.:

                List<int> list = [1, 2, 3];\n
                "},{"location":"labor/2-nyelvi-eszkozok/#6-feladat-lambda-kifejezesek","title":"6. Feladat \u2013 Lambda kifejez\u00e9sek","text":"

                Az \u00e9rintett t\u00e9mak\u00f6r\u00f6k az el\u0151ad\u00e1sanyagban r\u00e9szletesen szerepelnek, itt nem ism\u00e9telj\u00fck meg \u0151ket L\u00e1sd \u201eEl\u0151ad\u00e1s 02 - Nyelvi eszk\u00f6z\u00f6k.pdf\u201d dokumentum \"Lambda expression (lambda kifejez\u00e9s)\" fejezete. A kulcselem a => (lambda oper\u00e1tor), mely seg\u00edts\u00e9g\u00e9vel lambda kifejez\u00e9sek, vagyis n\u00e9vtelen f\u00fcggv\u00e9nyek defini\u00e1l\u00e1s\u00e1ra van lehet\u0151s\u00e9g.

                Action \u00e9s Func

                A .NET be\u00e9p\u00edtett Func \u00e9s Action generikus delegate t\u00edpusokra itt id\u0151 hi\u00e1ny\u00e1ban nem t\u00e9r\u00fcnk ki. Ett\u0151l m\u00e9g beletartoznak az alapanyagba!

                Az el\u0151z\u0151, 5. feladatot oldjuk meg a k\u00f6vetkez\u0151k\u00e9ppen: ne adjunk meg k\u00fcl\u00f6n sz\u0171r\u0151f\u00fcggv\u00e9nyt, hanem a sz\u0171r\u00e9si logik\u00e1t egy lambda kifejez\u00e9s form\u00e1j\u00e1ban adjuk meg a FindAll m\u0171veletnek.

                Ehhez mind\u00f6ssze egy sort kell megv\u00e1ltoztatni:

                list = list.FindAll((int n) => { return n % 2 == 1; });\n

                Egy n\u00e9v n\u00e9lk\u00fcli f\u00fcggv\u00e9nyt defini\u00e1ltunk \u00e9s adtunk \u00e1t a FindAll m\u0171veletnek:

                • ez egy lambda kifejez\u00e9s,
                • a => bal oldal\u00e1n megadtuk a m\u0171velet param\u00e9tereket (itt csak egy volt),
                • a => jobb oldal\u00e1n adtuk meg a m\u0171velet t\u00f6rzs\u00e9t (ugyanaz, mint a kor\u00e1bbi MyFilter t\u00f6rzse).

                A fenti sort j\u00f3val egyszer\u0171bb \u00e9s \u00e1ttekinthet\u0151bb form\u00e1ba is \u00edrhatjuk:

                list = list.FindAll(n => n % 2 == 1);\n

                A k\u00f6vetkez\u0151 egyszer\u0171s\u00edt\u00e9seket eszk\u00f6z\u00f6lt\u00fck:

                • a param\u00e9ter t\u00edpus\u00e1t nem \u00edrtuk ki: a ford\u00edt\u00f3 ki tudja k\u00f6vetkeztetni a FindAll delegate param\u00e9teram\u00e9ter\u00e9nek t\u00edpus\u00e1b\u00f3l, mely a kor\u00e1bban vizsg\u00e1lt Predicate.
                • a param\u00e9ter k\u00f6r\u00fcli z\u00e1r\u00f3jelet elhagyhattuk (mert csak egy param\u00e9ter van)
                • a => jobb oldal\u00e1n elhagyhattuk a {} z\u00e1r\u00f3jeleket \u00e9s a return-t (mert egyetlen kifejez\u00e9sb\u0151l \u00e1llt a f\u00fcggv\u00e9ny t\u00f6rzse, mellyel a f\u00fcggv\u00e9ny visszat\u00e9r).
                "},{"location":"labor/2-nyelvi-eszkozok/#7-tovabbi-nyelvi-konstrukciok","title":"7. Tov\u00e1bbi nyelvi konstrukci\u00f3k","text":"

                Az al\u00e1bbiakban kitekint\u00fcnk n\u00e9h\u00e1ny olyan C# nyelvi elemre, melyek a napi programoz\u00e1si feladatok sor\u00e1n egyre gyakrabban haszn\u00e1latosak. A gyakorlat sor\u00e1n j\u00f3 es\u00e9llyel m\u00e1r nem marad id\u0151 ezek \u00e1ttekint\u00e9s\u00e9re.

                "},{"location":"labor/2-nyelvi-eszkozok/#kifejezestorzsu-tagok-expression-bodied-members","title":"Kifejez\u00e9st\u00f6rzs\u0171 tagok (Expression-bodied members)","text":"

                Id\u0151nk\u00e9nt olyan r\u00f6vid f\u00fcggv\u00e9nyeket, illetve tulajdons\u00e1gok eset\u00e9n kifejezetten gyakran olyan r\u00f6vid get/set/init defin\u00edci\u00f3kat \u00edrunk, melyek egyetlen kifejez\u00e9sb\u0151l \u00e1llnak. Ez esetben a f\u00fcggv\u00e9ny, illetve tulajdons\u00e1g eset\u00e9n a get/set/init t\u00f6rzse megadhat\u00f3 \u00fan. kifejez\u00e9st\u00f6rzs\u0171 tagok (expression-bodied members) szintaktik\u00e1val is, a => alkalmaz\u00e1s\u00e1val. Ez akkor is megtehet\u0151, ha az adott kontextusban van visszat\u00e9r\u00e9si \u00e9rt\u00e9k (return utas\u00edt\u00e1s), ak\u00e1r nincs.

                A p\u00e9ld\u00e1kban l\u00e1tni fogjuk, hogy a kifejez\u00e9stest\u0171 tagok alkalmaz\u00e1sa nem t\u00f6bb, mint egy kisebb szintaktikai \"csavar\" annak \u00e9rdek\u00e9ben, hogy ilyen egyszer\u0171 esetekben min\u00e9l kevesebb k\u00f6r\u00edt\u0151 k\u00f3dot kelljen \u00edrni.

                N\u00e9zz\u00fcnk el\u0151sz\u00f6r egy f\u00fcggv\u00e9ny p\u00e9ld\u00e1t (feltessz\u00fck, hogy az oszt\u00e1lyban van egy Age tagv\u00e1ltoz\u00f3 vagy tulajdons\u00e1g):

                public int GetAgeInDogYear() => Age * 7; \npublic void DisplayName() => Console.WriteLine(ToString());\n
                Mint l\u00e1that\u00f3, elhagytuk a {} z\u00e1r\u00f3jeleket \u00e9s a return utas\u00edt\u00e1st, \u00edgy t\u00f6m\u00f6rebb a szintaktika.

                Fontos

                B\u00e1r itt is a => tokent haszn\u00e1ljuk, ennek semmi k\u00f6ze nincs a kor\u00e1bban t\u00e1rgyalt lambda kifejez\u00e9sekhez: egyszer\u0171en csak arr\u00f3l van sz\u00f3, hogy ugyanazt a => tokent (szimb\u00f3lump\u00e1rt) k\u00e9t teljesen elt\u00e9r\u0151 dologra haszn\u00e1lja a C# nyelv.

                P\u00e9lda tulajdons\u00e1g getter megad\u00e1s\u00e1ra:

                public int AgeInDogYear { get => Age * 7; }\n

                S\u0151t, ha csak getterje van a tulajdons\u00e1gnak, a get kulcssz\u00f3t \u00e9s a kapcsos z\u00e1r\u00f3jeleket is lehagyhatjuk.

                public int AgeInDogYear => Age * 7;\n

                Ezt az k\u00fcl\u00f6nb\u00f6zteti meg a kor\u00e1bban l\u00e1tott f\u00fcggv\u00e9nyek hasonl\u00f3 szintaktik\u00e1j\u00e1t\u00f3l, hogy itt nem \u00edrtuk ki a kerek z\u00e1r\u00f3jeleket.

                Note

                A Microsoft hivatalos dokument\u00e1ci\u00f3j\u00e1nak magyar ford\u00edt\u00e1s\u00e1ban az \"expression-bodied members\" nem \"kifejez\u00e9st\u00f6rzs\u0171\", hanem \"kifejez\u00e9stest\u0171\" tagk\u00e9nt szerepel. K\u00f6sz\u00f6nj\u00fck sz\u00e9pen, de a f\u00fcggv\u00e9nyeknek sokkal ink\u00e1bb t\u00f6rzse, mint teste van a magyar terminol\u00f3gi\u00e1ban, \u00edgy ezt nem vessz\u00fck \u00e1t...

                "},{"location":"labor/2-nyelvi-eszkozok/#objektuminicializalo-object-initializer","title":"Objektuminicializ\u00e1l\u00f3 (Object initializer)","text":"

                A publikus tulajdons\u00e1gok/tagv\u00e1ltoz\u00f3k inicializ\u00e1l\u00e1sa \u00e9s a konstruktorh\u00edv\u00e1s kombin\u00e1lhat\u00f3 egy \u00fagynevezett objektuminicializ\u00e1l\u00f3 (object initializer) szintaxis seg\u00edts\u00e9g\u00e9vel. Ennek alkalmaz\u00e1sa sor\u00e1n a konstruktorh\u00edv\u00e1s ut\u00e1n kapcsos z\u00e1r\u00f3jelekkel blokkot nyitunk, ahol a publikus tulajdons\u00e1gok/tagv\u00e1ltoz\u00f3k \u00e9rt\u00e9ke adhat\u00f3 meg, az al\u00e1bbi szintaktik\u00e1val.

                var p = new Person()\n{\n    Age = 17,\n    Name = \"Luke\",\n};\n

                Az tulajdons\u00e1gok/tagok inicializ\u00e1l\u00e1sa a konstruktor lefut\u00e1sa ut\u00e1n t\u00f6rt\u00e9nik (amennyiben tartozik az oszt\u00e1lyhoz konstruktor). Ez a szintaktika az\u00e9rt is el\u0151ny\u00f6s, mert egy kifejez\u00e9snek sz\u00e1m\u00edt (azon h\u00e1rommal szemben, mintha l\u00e9trehozn\u00e1nk egy inicializ\u00e1latlan, Person objektumot, \u00e9s k\u00e9t tov\u00e1bbi l\u00e9p\u00e9sben adn\u00e1nk \u00e9rt\u00e9ket az Age \u00e9s Name tagoknak). \u00cdgy ak\u00e1r k\u00f6zvetlen\u00fcl f\u00fcggv\u00e9nyh\u00edv\u00e1s param\u00e9terek\u00e9nt \u00e1tadhat\u00f3 egy inicializ\u00e1lt objektum, an\u00e9lk\u00fcl, hogy k\u00fcl\u00f6n v\u00e1ltoz\u00f3t kellene deklar\u00e1lni.

                void Foo(Person p)\n{\n    // do something with p\n}\n
                Foo(new Person() { Age = 17, Name = \"Luke\" });\n

                A szintaxis r\u00e1ad\u00e1sul copy-paste bar\u00e1t, mert ahogy a fenti p\u00e9ld\u00e1kban is l\u00e1tszik, hogy nem sz\u00e1m\u00edt, hogy az utols\u00f3 tulajdons\u00e1g megad\u00e1sa ut\u00e1n van-e vessz\u0151, vagy nincs.

                "},{"location":"labor/2-nyelvi-eszkozok/#tulajdonsagok-init-only-setter","title":"Tulajdons\u00e1gok - Init only setter","text":"

                Az el\u0151z\u0151 pontban l\u00e9v\u0151 objektuminicializ\u00e1l\u00f3 szintaxis nagyon k\u00e9nyelmes, viszont azt k\u00f6veteli meg a tulajdons\u00e1gt\u00f3l, hogy publikus legyen. Ha azt akarjuk, hogy egy tulajdons\u00e1g \u00e9rt\u00e9ke csak az objektum l\u00e9trehoz\u00e1sakor legyen megadhat\u00f3, ahhoz konstruktor param\u00e9tert kell bevezess\u00fcnk, \u00e9s egy csak olvashat\u00f3 (csak getterrel rendelkez\u0151) tulajdons\u00e1gnak kell azt \u00e9rt\u00e9k\u00fcl adjuk. Erre a probl\u00e9m\u00e1ra ad egyszer\u0171bb megold\u00e1st az \u00fan. Init only setter szintaxis, ahol olyan \"settert\" tudunk k\u00e9sz\u00edteni az init kulcssz\u00f3val, mely \u00e1ll\u00edt\u00e1sa csak a konstruktorban \u00e9s az el\u0151z\u0151 fejezetben ismertetett objektuminicializ\u00e1l\u00f3 szintaxis alkalmaz\u00e1sa sor\u00e1n enged\u00e9lyezett, ezt k\u00f6vet\u0151en m\u00e1r nem.

                public string Name { get; init; }\n
                var p = new Person()\n{\n    Age = 17,\n    Name = \"Luke\",\n};\n\np.Name = \"Test\"; // build hiba, ut\u00f3lag nem megv\u00e1ltoztathat\u00f3\n

                Tov\u00e1bb\u00e1 lehet\u0151s\u00e9g\u00fcnk van az init only setter k\u00f6telez\u0151s\u00e9g\u00e9t is be\u00e1ll\u00edtani a tulajdons\u00e1gon alkalmazott required kulcssz\u00f3val. Ekkor a tulajdons\u00e1g \u00e9rt\u00e9k\u00e9t mindenk\u00e9ppen meg kell adni az objektuminicializ\u00e1l\u00f3 szintaxisban, k\u00fcl\u00f6nben ford\u00edt\u00e1si hib\u00e1t kapunk.

                public required string Name { get; init; }\n

                Ez az\u00e9rt is hasznos, mert ha egy\u00e9bk\u00e9nt is szeretn\u00e9nk tulajdons\u00e1gokat publik\u00e1lni az oszt\u00e1lyb\u00f3l, \u00e9s egy\u00e9bk\u00e9nt is szeretn\u00e9nk t\u00e1mogatni az objektum inicializ\u00e1l\u00f3 szintaxist, akkor \u00edgy meg tudjuk sp\u00f3rolni a k\u00f6telez\u0151 konstruktor param\u00e9tereket.

                "},{"location":"labor/2-nyelvi-eszkozok/#8-feladat-generikus-osztalyok","title":"8. Feladat \u2013 Generikus oszt\u00e1lyok","text":"

                Megjegyz\u00e9s: erre a feladatra j\u00f3 es\u00e9llyel nem marad id\u0151. Ez esetben c\u00e9lszer\u0171 a feladatot gyakorl\u00e1sk\u00e9ppen otthon elv\u00e9gezni.

                A .NET generikus oszt\u00e1lyai hasonl\u00edtanak C++ nyelv template oszt\u00e1lyaihoz, de k\u00f6zelebb \u00e1llnak a Java-ban m\u00e1r megismert generikus oszt\u00e1lyokhoz. A seg\u00edts\u00e9g\u00fckkel \u00e1ltal\u00e1nos (t\u00f6bb t\u00edpusra is m\u0171k\u00f6d\u0151), de ugyanakkor t\u00edpusbiztos oszt\u00e1lyokat hozhatunk l\u00e9tre. Generikus oszt\u00e1lyok n\u00e9lk\u00fcl, ha \u00e1ltal\u00e1nosan szeretn\u00e9nk kezelni egy probl\u00e9m\u00e1t, akkor object t\u00edpus\u00fa adatokat haszn\u00e1lunk (mert .NET-ben minden oszt\u00e1ly az object oszt\u00e1lyb\u00f3l sz\u00e1rmazik). Ez a helyzet p\u00e9ld\u00e1ul az ArrayList-tel is, ami egy \u00e1ltal\u00e1nos c\u00e9l\u00fa gy\u0171jtem\u00e9ny, tetsz\u0151leges, object t\u00edpus\u00fa elemek t\u00e1rol\u00e1s\u00e1ra alkalmas. L\u00e1ssunk egy p\u00e9ld\u00e1t az ArrayList haszn\u00e1lat\u00e1ra:

                var list = new ArrayList();\nlist.Add(1);\nlist.Add(2);\nlist.Add(3);\nfor (int n = 0; n < list.Count; n++)\n{\n    // Castolni kell, k\u00fcl\u00f6nben nem fordul\n    int i = (int)list[n];\n    Console.WriteLine($\"Value: {i}\");\n}\n

                A fenti megold\u00e1ssal a k\u00f6vetkez\u0151 probl\u00e9m\u00e1k ad\u00f3dnak:

                • Az ArrayList minden egyes elemet object-k\u00e9nt t\u00e1rol.
                • Amikor hozz\u00e1 szeretn\u00e9nk f\u00e9rni a lista egy elem\u00e9hez, mindig a megfelel\u0151 t\u00edpus\u00fav\u00e1 kell cast-olni.
                • Nem t\u00edpusbiztos. A fenti p\u00e9ld\u00e1ban semmi nem akad\u00e1lyoz meg abban (\u00e9s semmilyen hiba\u00fczenet sem jelzi), hogy az int t\u00edpus\u00fa adatok mell\u00e9 besz\u00farjunk a list\u00e1ba egy m\u00e1sik t\u00edpus\u00fa objektumot. Ilyenkor csak a lista bej\u00e1r\u00e1sa sor\u00e1n kapn\u00e1nk hib\u00e1t, amikor a nem int t\u00edpust int t\u00edpus\u00fara pr\u00f3b\u00e1lunk castolni. Generikus gy\u0171jtem\u00e9nyek haszn\u00e1latakor az ilyen hib\u00e1k m\u00e1r a ford\u00edt\u00e1s sor\u00e1n kider\u00fclnek.
                • \u00c9rt\u00e9k t\u00edpus\u00fa adatok t\u00e1rol\u00e1sakor a lista lassabban m\u0171k\u00f6dik, mert az \u00e9rt\u00e9k t\u00edpust el\u0151sz\u00f6r be kell dobozolni (boxing), hogy az object-k\u00e9nt (azaz referencia t\u00edpusk\u00e9nt) t\u00e1rolhat\u00f3 legyen.

                A fenti probl\u00e9ma megold\u00e1sa egy generikus lista haszn\u00e1lat\u00e1val a k\u00f6vetkez\u0151k\u00e9ppen n\u00e9z ki (a gyakorlat sor\u00e1n csak a kiemelt sort m\u00f3dos\u00edtsuk a kor\u00e1bban beg\u00e9pelt p\u00e9ld\u00e1ban):

                var list = new List<int>();\nlist.Add(1);\nlist.Add(2);\nlist.Add(3);\nfor (int n = 0; n < list.Count; n++)\n{\n    int i = list[n]; // Nem kell cast-olni\n    Console.WriteLine($\"Value: {i}\");\n}\n
                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/","title":"2. Sprachliche Mittel","text":""},{"location":"labor/2-nyelvi-eszkozok/index_ger/#das-ziel-der-ubung","title":"Das Ziel der \u00dcbung","text":"

                W\u00e4hrend der \u00dcbung lernen die Studenten die wichtigsten modernen Sprachelementen kennen, die auch in der .NET-Umgebung verf\u00fcgbar sind. Es wird vorausgesetzt, dass der/die Student/in den objektorientierten Ansatz in seinem/ihrem bisherigen Studium beherrscht und mit den grundlegenden Konzepten der Objektorientierung vertraut ist. In dieser \u00dcbung werden wir uns auf die Sprachelemente in .NET konzentrieren, die \u00fcber den allgemeinen objektorientierten Ansatz hinausgehen, aber wesentlich zur Erstellung von transparentem und wartbarem Code beitragen. Diese sind:

                • Eigenschaft (property)
                • Delegat (delegate, Methodenreferenz)
                • Ereignis (event)
                • Attribut (attribute)
                • Lambda-Ausdruck (lambda expression)
                • Generischer Typ (generic type)
                • Einige zus\u00e4tzliche Sprachkonstruktionen

                Zugeh\u00f6rige Vorlesungen: Vorlesung 2 und Anfang der Vorlesung 3 - Sprachliche Mittel.

                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#voraussetzungen","title":"Voraussetzungen","text":"

                Die f\u00fcr die Durchf\u00fchrung der \u00dcbung ben\u00f6tigten Werkzeuge:

                • Visual Studio 2022

                \u00dcbung unter Linux oder macOS

                Das \u00dcbungsmaterial ist grunds\u00e4tzlich f\u00fcr Windows und Visual Studio gedacht, kann aber auch auf anderen Betriebssystemen mit anderen Entwicklungswerkzeugen (z.B. VS Code, Rider, Visual Studio f\u00fcr Mac) oder sogar mit einem Texteditor und CLI (Kommandozeilen)-Tools durchgef\u00fchrt werden. Dies wird dadurch erm\u00f6glicht, dass die Beispiele im Kontext einer einfachen Konsolenanwendung pr\u00e4sentiert werden (keine Windows-spezifischen Elemente) und das .NET SDK auf Linux und macOS unterst\u00fctzt wird. Hello World unter Linuxon

                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

                Ausblick

                Dieser Leitfaden enth\u00e4lt an mehreren Stellen zus\u00e4tzliche Informationen und Erkl\u00e4rungen, die in derselben Farbe wie dieser Hinweis und mit demselben Symbol umrahmt sind. Dies sind n\u00fctzliche Erkenntnisse, die jedoch nicht Teil des Kernlehrmaterial sind.

                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#losung","title":"L\u00f6sung","text":"Laden Sie die fertige L\u00f6sung herunter

                Es ist wichtig, dass Sie sich w\u00e4hrend des Praktikums an die Anleitung halten. Es ist verboten (und sinnlos), die fertige L\u00f6sung herunterzuladen. Allerdings kann es bei der anschlie\u00dfenden Selbstein\u00fcbung n\u00fctzlich sein, die fertige L\u00f6sung zu \u00fcberpr\u00fcfen, daher stellen wir sie zur Verf\u00fcgung.

                Die L\u00f6sung ist auf GitHub [hier] verf\u00fcgbar (https://github.com/bmeviauab00/lab-nyelvieszkozok-megoldas). Der einfachste Weg, es herunterzuladen, ist, es von der Kommandozeile aus mit dem Befehl git clone auf Ihren Computer zu klonen:

                git clone https://github.com/bmeviauab00/lab-nyelvieszkozok-megoldas

                Sie m\u00fcssen Git auf Ihrem Computer installiert haben, weitere Informationen hier.

                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#0-aufgabe-schlusselwort-var-implizit-typisierte-lokale-variablen-implicitly-typed-local-variables","title":"0. Aufgabe - Schl\u00fcsselwort var - Implizit typisierte lokale Variablen (implicitly typed local variables)","text":"

                Wir beginnen mit einer einfachen Aufw\u00e4rm\u00fcbung. Im folgenden Beispiel erstellen wir eine Klasse namens Person, die eine Person darstellt.

                1. Erstellen wir eine neue C#-Konsolenanwendung. .NET-Basis (d.h. nicht.NET Framework):
                  • Ein Beispiel daf\u00fcr haben wir in der ersten \u00dcbung gesehen, die im Leitfaden beschrieben wird.
                  • Das Kontrollk\u00e4stchen \"Do not use top level statements\" ist bei der Projekterstellung aktiviert.
                2. F\u00fcgen wir eine neue Klasse mit dem Namen Person zu unserer Anwendung hinzu. (Um eine neue Klasse im Solution Explorer hinzuzuf\u00fcgen, klicken wir mit der rechten Maustaste auf die Projektdatei und w\u00e4hlen wir Add / Class. \u00c4ndern wir den Namen der zu erstellenden Datei im erscheinenden Fenster auf Person.csund klicken wir auf Add.)
                3. Lassen wir uns die Klasse \u00f6ffentlich machen. Dazu m\u00fcssen wir das Schl\u00fcsselwort public vor dem Klassennamen eingeben. Diese \u00c4nderung w\u00e4re hier eigentlich nicht n\u00f6tig, aber eine sp\u00e4tere Aufgabe wird eine \u00f6ffentliche Klasse erfordern.

                  public class Person\n{\n}\n
                4. Erg\u00e4nzen wir die Funktion Main in der Datei Program.cs, um unsere neue Klasse zu testen.

                  static void Main(string[] args)\n{\n    Person p = new Person();\n}\n
                5. Anstatt den Typ der lokalen Variablen explizit anzugeben, k\u00f6nnen wir das Schl\u00fcsselwort var verwenden:

                  static void Main(string[] args)\n{\n    var p = new Person();\n}\n

                  Dies wird als implicitly typed local variables bezeichnet, auf Deutsch implizit typisierte lokale Variablen genannt. In diesem Fall versucht der Compiler, den Typ der Variablen aus dem Kontext, aus der rechten Seite des Gleichheitszeichens zu erkennen. In diesem Fall ist es Person. Es ist wichtig anzumerken, dass die Sprache dadurch statisch typisiert bleibt (es funktioniert also nicht wie das JavaScript-Schl\u00fcsselwort var ), da der Typ der p -Variable sp\u00e4ter nicht mehr ge\u00e4ndert werden kann. Es ist nur ein einfaches syntaktisches Bonbon, um die Definition lokaler Variablen kompakter zu machen (keine Notwendigkeit, den Typ \"zweimal\" anzugeben, auf der linken und auf der rechten Seite von = ).

                  Target-typed new expressions

                  Ein weiterer Ansatz k\u00f6nnte die Target-typed new expressions in C# 9 sein, wo der Typ f\u00fcr den neuen Operator weggelassen werden kann, wenn er vom Compiler aus dem Kontext erkannt werden kann (z.B.: linke Seite eines Wertes, Typ eines Parameters, etc.). Unser obiger Person -Konstruktor w\u00fcrde wie folgt aussehen:

                  Person p = new();\n

                  Der Vorteil dieses Ansatzes gegen\u00fcber var ist, dass er auch f\u00fcr Membervariablen verwendet werden kann.

                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#1-aufgabe-eigenschaft-property","title":"1. Aufgabe - Eigenschaft (property)","text":"

                Eigenschaften erlauben uns typischerweise (aber nicht ausschlie\u00dflich, wie wir noch sehen werden) den Zugriff auf Membervariablen von Klassen auf eine syntaktisch \u00e4hnliche Weise wie den Zugriff auf eine traditionelle Membervariable. Beim Zugriff haben wir jedoch die M\u00f6glichkeit, anstelle einer einfachen Wertabfrage oder Einstellung eine methoden\u00e4hnliche Art des Zugriffs auf die Variable zu implementieren, und wir k\u00f6nnen sogar die Sichtbarkeit der Abfrage und der Einstellung separat definieren.

                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#syntax-von-eigenschaften","title":"Syntax von Eigenschaften","text":"

                Im folgenden Beispiel erstellen wir eine Klasse namens Person, die eine Person darstellt. Sie hat zwei Mitgliedsvariablen, name und age. Auf Mitgliedsvariablen kann nicht direkt zugegriffen werden (da sie privat sind), sie k\u00f6nnen nur \u00fcber die \u00f6ffentlichen Eigenschaften Name und Age verwaltet werden. Das Beispiel veranschaulicht, dass die .NET-Eigenschaften eindeutig den aus C++ und Java bekannten Methoden SetX(\u2026) und GetX() entsprechen, aber die sind auf einheitlichere Weise, auf Sprachebene unterst\u00fctzt.

                1. Erstellen wir in der Klasse Person, die in der vorherigen Aufgabe erstellt war, eine Membervariable des Typs int mit dem Namen age und eine Eigenschaft Age, die sie verf\u00fcgbar macht.

                  public class Person\n{\n    private int age;\n    public int Age\n    {\n        get { return age; }\n        set { age = value; }\n    }\n}\n

                  Visual Studio Snippets

                  Obwohl wir die gesamte Eigenschaft im Labor zu \u00dcbungszwecken manuell eingegeben haben, stellt Visual Studio Code Snippets zur Verf\u00fcgung, um h\u00e4ufig vorkommende Codeteile zu erstellen, mit denen wir allgemeine Sprachkonstrukte als Vorlagen verwenden k\u00f6nnen. Der obige Eigenschaftscodeschnipsel kann mit dem Schnipsel propfull abgerufen werden. Geben Sie den Namen des Schnipsels ein (propfull) und dr\u00fccken Sie dann die Tab -Taste, bis der Schnipsel aktiviert ist (normalerweise 2x).

                  Weitere erw\u00e4hnenswerte Schnipseln sind unter anderem:

                  • ctor: Konstruktor
                  • for: f\u00fcr Zyklus
                  • foreach: foreach-Schleife
                  • prop: automatische Eigenschaft (siehe sp\u00e4ter)
                  • switch: Schaltbefehl
                  • cw: Console.WriteLine

                  Wir k\u00f6nnen solche Schnipseln herstellen.

                2. Ergn\u00e4nzen wir die Funktion Main in der Datei Program.cs, um unsere neue Eigenschaft zu testen.

                  static void Main(string[] args)\n{\n    var p = new Person();\n    p.Age = 17;\n    p.Age++;\n    Console.WriteLine(p.Age);\n}\n
                3. F\u00fchren wir unseren Programm aus (F5)

                  Wir sehen, dass die Eigenschaft auf \u00e4hnliche Weise wie die Mitgliedsvariablen verwendet werden kann. Wenn die Eigenschaft abgefragt wird, wird der in der Eigenschaft definierte Teil get ausgef\u00fchrt und der Wert der Eigenschaft ist der durch return zur\u00fcckgegebene Wert. Wenn die Eigenschaft gesetzt ist, wird der in der Eigenschaft definierte Abschnitt set ausgef\u00fchrt, und der Wert der speziellen Variablen value in diesem Abschnitt entspricht dem als Eigenschaftswert angegebenen Ausdruck.

                  Beachten wir in der obigen L\u00f6sung, wie elegant wir ein Jahr zum Alter einer Person hinzuf\u00fcgen k\u00f6nnen. In Java- oder C++-Code h\u00e4tte ein \u00e4hnlicher Vorgang in der Form p.setAge(p.getAge() + 1) geschrieben werden k\u00f6nnen, was eine wesentlich umst\u00e4ndlichere und schwieriger zu lesende Syntax ist als die Obige. Der Hauptvorteil der Verwendung von Eigenschaften besteht darin, dass unser Code syntaktisch sauberer ist und Wertzuweisungen/-abfragen in den meisten F\u00e4llen gut von tats\u00e4chlichen Funktionsaufrufen getrennt sind.

                4. \u00dcberpr\u00fcfen wir, dass unser Programm wirklich get und set aufruft. Dazu setzen wir Haltepunkte (breakpoints) innerhalb der Getter- und Setter-Bl\u00f6cke, dazu klicken wir auf den grauen Balken am linken Rand des Code-Editors.

                5. F\u00fchren wir das Programm Schritt f\u00fcr Schritt aus. Starten wir dazu das Programm mit F11 statt F5, und dr\u00fccken wir dann erneut F11, um es Zeile f\u00fcr Zeile ablaufen zu lassen.

                  Wir sehen, dass unser Programm tats\u00e4chlich jedes Mal den Getter aufruft, wenn ein Wert abgefragt wird, und den Setter, wenn ein Wert gesetzt wird.

                6. Ein wichtiges Merkmal von Setter-Funktionen ist, dass sie die M\u00f6glichkeit der Wert\u00fcberpr\u00fcfung bieten. F\u00fcgen wir in diesem Sinne dem Setter der Eigenschaft Age etwas hinzu.

                  public int Age\n{\n    get { return age; }\n    set \n    {\n        if (value < 0)\n            throw new ArgumentException(\"Ung\u00fcltiges Alter!\");\n        age = value; \n    }\n}\n

                  Beachten wir, dass bei einfachen Gettern und Settern die Abfrage bzw. das Setzen von Werten in einer Zeile erfolgt, w\u00e4hrend sie bei komplexeren Stammdaten auf mehrere Zeilen aufgeteilt wird.

                7. Um die Anwendung zu testen, ordnen wir dem Alter einen negativen Wert in der Funktion Main der Klasse Program zu.

                  p.Age = -2;\n
                8. F\u00fchren wir das Programm aus, um es zu testen, ob die Pr\u00fcfung korrekt funktioniert, und korrigieren wir dann den Fehler, \u00e4ndern wir das eingestellte Alter auf positiv.

                  p.Age = 2;\n
                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#auto-implementierte-eigenschaft-auto-implemented-property","title":"Auto-implementierte Eigenschaft (auto-implemented property)","text":"

                In unserer t\u00e4glichen Arbeit begegnen wir auch einer viel kompakteren Syntax von Eigenschaften. Diese Syntax kann verwendet werden, wenn wir eine Eigenschaft erstellen m\u00f6chten, in der:

                • wir wollen keine zus\u00e4tzliche Logik zu den Getter- und Setter-Methoden hinzuf\u00fcgen,
                • m\u00fcssen wir nicht direkt auf die private Mitgliedsvariable zugreifen.

                Nachfolgend ein Beispiel daf\u00fcr.

                1. F\u00fcgen wir eine solche automatisch implementierte Eigenschaft (auto-implemented property) zu unserer Klasse Person hinzu. Erstellen wir eine Eigenschaft vom Typ string mit dem Namen Name.

                  public string Name { get; set; }\n

                  Der syntaktische Unterschied zu den vorherigen ist, dass weder der get- noch der set-Zweig implementiert wurden (keine Klammern). Im Falle einer automatisch implementierten Eigenschaft erzeugt der Compiler eine versteckte Variable in der Klasse, auf die vom Code aus nicht zugegriffen werden kann und die zum Speichern des aktuellen Werts der Eigenschaft verwendet wird. Es sollte betont werden, dass dies nicht die zuvor eingef\u00fchrte name Mitgliedsvariable (die gel\u00f6scht werden k\u00f6nnte) anh\u00e4lt und abfragt, sondern auf eine versteckte, neue Variable wirkt!

                2. \u00dcberpr\u00fcfen wir nun ihre Funktionalit\u00e4t, und erg\u00e4nzen wir die Funktion Main.

                  static void Main(string[] args)\n{\n    // ...\n    p.Name = \"Lukas\";\n    // ...\n    Console.WriteLine(p.Name);\n}\n
                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#standardwert-default-value","title":"Standardwert (default value)","text":"

                F\u00fcr automatisch implementierte Eigenschaften k\u00f6nnen wir bei der Deklaration auch deren Anfangswert angeben.

                1. Geben wir der Eigenschaft Name einen Anfangswert.

                  public string Name { get; set; } = \"anonymous\";\n
                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#sichtbarkeit-von-eigenschaften","title":"Sichtbarkeit von Eigenschaften","text":"

                Ein gro\u00dfer Vorteil der Eigenschaften, neben der v\u00f6llig freien Implementierung, ist, dass die Sichtbarkeit des Getters und des Setters getrennt eingestellt werden kann.

                1. Setzen wir die Sichtbarkeit des Setters der Eigenschaft Name auf privat.

                  public string Name { get; private set; }\n

                  In diesem Fall wird ein \u00dcbersetzungsfehler in der Klasse Program f\u00fcr die Richtlinie p.Name = \"Luke\"; zur\u00fcckgegeben. Die Grundregel ist, dass Getter und Setter die Sichtbarkeit der Eigenschaft erben, die weiter eingeschr\u00e4nkt, aber nicht gelockert werden kann. Die Sichtbarkeitskontrolle kann sowohl f\u00fcr autoimplementierte als auch f\u00fcr nicht autoimplementierte Eigenschaften verwendet werden.

                2. Stellen wir die Sichtbarkeit wieder her (entfernen wir das Schl\u00fcsselwort private aus dem Property Setter Name ), um den \u00dcbersetzungsfehler zu vermeiden.

                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#nur-lese-eigenschaft-readonly-property","title":"Nur-Lese-Eigenschaft (readonly property)","text":"

                Der Setter kann weggelassen werden, um eine schreibgesch\u00fctzte Eigenschaft zu erhalten. F\u00fcr eine automatisch implementierte Eigenschaft kann auch ein Anfangswert angegeben werden: Dies ist nur in einem Konstruktor oder durch Angabe eines Standardwerts (siehe oben) m\u00f6glich, im Gegensatz zu Eigenschaften mit einem privaten Setter, deren Setter von jeder Mitgliedsfunktion der Klasse aufgerufen werden kann.

                Die Definition einer schreibgesch\u00fctzten Eigenschaft wird in den folgenden Codeschnipseln veranschaulicht (implementieren wir sie NICHT in unserem Code):

                a) Autoimplementierter Fall

                public string Name { get; }\n

                b) Nicht automatisch implementierter Fall

                private string name;\n...\npublic string Name { get {return name; } }\n
                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#berechneter-wert-calculated-value","title":"Berechneter Wert (calculated value)","text":"

                Eigenschaften mit nur Getter haben eine andere Verwendung. Sie kann auch verwendet werden, um einen berechneten Wert zu ermitteln, der immer einen Wert auf der Grundlage einer bestimmten Logik berechnet, aber im Gegensatz zur \"Nur-Lese-Eigenschaft\" verf\u00fcgt sie nicht \u00fcber ein Datenelement direkt hinter ihr. Dies wird im folgenden Codeschnipsel veranschaulicht (\u00fcbernehmen wir ihn NICHT in unserem Code):

                public int AgeInDogYear { get { return Age * 7; } }\n
                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#2-aufgabe-delegat-delegate-methodenreferenz","title":"2. Aufgabe - Delegat (delegate, Methodenreferenz)","text":"

                Stellen wir sicher, dass der Code kompilierbar ist!

                Die folgenden \u00dcbungen bauen auf den Ergebnissen der vorherigen \u00dcbungen auf. Wenn Ihr Programm nicht abst\u00fcrzt oder nicht richtig funktioniert, melden Sie dies Ihrem/er \u00dcbungsleiter/in am Ende der Aufgaben, damit er/sie Ihnen bei der Behebung des Problems helfen kann.

                Delegate sind Methodenreferenzen in .NET, das moderne \u00c4quivalent zu C/C++-Funktionszeigern. Ein Delegat ist eine M\u00f6glichkeit, einen Variablentyp zu definieren, der verwendet werden kann, um auf Methoden zu verweisen. Nicht irgendein Zeiger, sondern - \u00e4hnlich wie bei C++-Funktionszeigern - solche, deren Typ (Parameterliste und R\u00fcckgabewert) dem Typ des Delegaten entspricht. Durch das \"Aufrufen\" der Delegatvariable wird die als Wert angegebene (registrierte) Methode automatisch aufgerufen. Ein Vorteil der Verwendung von Delegaten ist, dass wir zur Laufzeit entscheiden k\u00f6nnen, welche von mehreren Methoden wir aufrufen m\u00f6chten.

                Einige Beispiele f\u00fcr den Einsatz von Delegaten:

                • die Funktion, die die Elemente vergleicht, als Parameter an eine universelle Ordnungsfunktion \u00fcbergeben,
                • ist die Implementierung einer universellen Filterlogik f\u00fcr eine allgemeine Sammlung, bei der eine Funktion als Delegat in einem Parameter \u00fcbergeben wird, um zu entscheiden, ob ein Element in die gefilterte Liste aufgenommen werden soll,
                • Implementierung des Publish-Subscribe-Musters, bei dem bestimmte Objekte andere Objekte \u00fcber sich selbst betreffender Ereignisse informieren.

                Im folgenden Beispiel werden wir Objekten der zuvor erstellten Klasse Person erlauben, Objekte anderer Klassen frei zu benachrichtigen, wenn sich das Alter einer Person ge\u00e4ndert hat. Zu diesem Zweck f\u00fchren wir einen Delegatentyp (AgeChangingDelegate) ein, der den aktuellen und neuen Wert des Alters der Person in seiner Parameterliste \u00fcbergeben kann. Als N\u00e4chstes erstellen wir eine \u00f6ffentliche Mitgliedsvariable des Typs AgeChangingDelegate in der Klasse Person, die es einer externen Partei erm\u00f6glicht, die Funktion anzugeben, \u00fcber die sie die Benachrichtigung \u00fcber \u00c4nderungen an der Instanz Person anfordern wird.

                1. Erstellen wir einen neuen Delegatentyp, der auf solche Funktionen verweisen kann, die void zur\u00fcckgeben und zwei int Parameter annehmen. \u00dcberpr\u00fcfen wir, dass der neue Typ vor der Klasse Person definiert ist, direkt im G\u00fcltigkeitsbereich des Namespaces!

                  namespace PropertyDemo\n{\n    public delegate void AgeChangingDelegate(int oldAge, int newAge);\n\n    public class Person\n    {\n        // ...\n

                  AgeChangingDelegate ist ein Typ (man beachte auch die VS-F\u00e4rbung), der \u00fcberall dort verwendet werden kann, wo ein Typ gesetzt werden kann (z.B. kann man eine Membervariable, eine lokale Variable, einen Funktionsparameter, etc. auf dieser Basis erstellen).

                2. Erm\u00f6glichen wir Objekten in Person, auf jede Funktion zu zeigen, die der obigen Signatur entspricht. Erstellen wir dazu eine Membervariable vom Typ AgeChangingDelegate in der Klasse Person!

                  public class Person\n{\n    public AgeChangingDelegate AgeChanging;\n

                  Wie objektorientiert ist das?

                  Die Methodenreferenz, die als \u00f6ffentliche Membervariable erstellt wurde, verst\u00f6\u00dft (vorerst) gegen die Grunds\u00e4tze der objektorientierten Einheitsbegrenzung/Informationsverschleierung. Wir werden sp\u00e4ter darauf zur\u00fcckkommen.

                3. Rufen wir die Funktion jedes Mal auf, wenn sich das Alter unseres Person \u00e4ndert. Dazu f\u00fcgen wir dem Setter der Eigenschaft Age Folgendes hinzu.

                  public int Age\n{\n    get { return age; }\n    set \n    {\n        if (value < 0)\n            throw new ArgumentException(\"Ung\u00fcltiges Alter!\");\n        if (AgeChanging != null)\n            AgeChanging(age, value);\n        age = value; \n    }\n}\n

                  Die obige Codezeile veranschaulichen mehrere wichtige Regeln:

                  • Die Validierungslogik geht in der Regel der Meldungslogik voraus.
                  • Es h\u00e4ngt von der Art der Meldelogik ab, ob sie vor oder nach der Auswertung ausgef\u00fchrt wird (in diesem Fall, da sich das Wort \"changing\" auf etwas in Arbeit befindliches bezieht, geht die Meldung der Auswertung voraus, das Vorkommen wird durch die Vergangenheitsform angezeigt: \"changed\")
                  • Beachten wir, dass noch niemand der Mitgliedsvariablen vom Typ Delegat einen Wert zugewiesen hat (kein Abonnent/Teilnehmer). In solchen F\u00e4llen w\u00fcrde der Aufruf zu einer Ausnahme f\u00fchren. \u00dcberpr\u00fcfen wir daher immer, ob die Mitgliedsvariable null ist, bevor wir sie aufrufen.
                  • Wenn das Ereignis ausgel\u00f6st wird, k\u00f6nnen wir auch die \u00dcberpr\u00fcfung von null und die Ausl\u00f6sung des Ereignisses auf elegantere, kompaktere und thread-sichere Weise mit dem \"?.\" Null-Bedingungs-Operator durchf\u00fchren (C# 6 und h\u00f6her):

                  statt

                  if (AgeChanging != null)\n    AgeChanging(age, value);\n

                  k\u00f6nnen wir

                  AgeChanging?.Invoke(age, value);\n

                  schreiben.

                  Das Ereignis wird nur ausgel\u00f6st, wenn es nicht null ist, ansonsten geschieht nichts.

                4. Genauer gesehen, sollte das Ereignis nur ausgel\u00f6st werden, wenn sich das Alter tats\u00e4chlich \u00e4ndert, d. h. die Verzweigung der Eigenschaft set sollte pr\u00fcfen, ob der neue Wert mit dem alten \u00fcbereinstimmt. Eine L\u00f6sung k\u00f6nnte darin bestehen, in der ersten Zeile des Setters sofort zur\u00fcckzukehren, wenn der neue Wert mit dem alten \u00fcbereinstimmt:

                  if (age == value) \n    return;\n\u2026\n
                5. Wir sind fertig mit dem Code f\u00fcr die Klasse Person. Kommen wir zum Abonnenten! Als erstes m\u00fcssen wir der Klasse Program eine neue Funktion hinzuf\u00fcgen.

                  class Program\n{\n    // ...\n\n    private static void PersonAgeChanging(int oldAge, int newAge)\n    {\n        Console.WriteLine(oldAge + \" => \" + newAge);\n    }\n}\n

                  Tipp

                  \u00dcberpr\u00fcfen Sie, dass die neue Funktion im richtigen Bereich platziert ist! W\u00e4hrend der Delegatentyp au\u00dferhalb der Klasse (aber innerhalb des Namespace) platziert ist, befindet sich die Funktion innerhalb der Klasse!

                6. Melden wir uns schlie\u00dflich f\u00fcr die \u00c4nderungsverfolgung in der Funktion Main an!

                  static void Main(string[] args)\n{\n  Person p = new Person();\n  p.AgeChanging = new AgeChangingDelegate(PersonAgeChanging);\n  // ...\n
                7. Starten wir das Programm!

                  Wenn wir z. B. einen Haltepunkt in der Zeile AgeChanging?.Invoke(age, value); setzen, die Anwendung debuggen und den Code schrittweise ausf\u00fchrem, k\u00f6nnen wir feststellen, dass das Ereignis bei jedem Setter-Durchlauf ausgef\u00fchrt wird, sowohl bei der ersten Wertzuweisung als auch beim Inkrement.

                8. F\u00fcgen wir der Funktion Main mehrere Abonnenten hinzu (mit dem Operator += k\u00f6nnen wir neue Abonnenten zu den bereits vorhandenen hinzuf\u00fcgen) und f\u00fchren wir das Programm dann aus.

                  p.AgeChanging = new AgeChangingDelegate(PersonAgeChanging);\np.AgeChanging += new AgeChangingDelegate(PersonAgeChanging);\np.AgeChanging += PersonAgeChanging; // Kompaktere Syntax\n

                  Es ist zu erkennen, dass alle drei registrierten/\"abonnierten\" Funktionen bei jeder Wert\u00e4nderung ausgef\u00fchrt werden. Dies ist m\u00f6glich, weil die Mitgliedsvariablen des Delegatentyps nicht nur eine Funktionsreferenz, sondern eine Funktionsreferenzliste enthalten (und pflegen).

                  Beachten wir in der dritten Zeile oben, dass wir Funktionsreferenzen mit einer kompakteren Syntax schreiben k\u00f6nnen, als wir sie beim ersten Mal gesehen haben: Geben wir einfach den Namen der Funktion nach dem += Operator an, ohne das new AgeChangingDelegate(...). Unabh\u00e4ngig davon wird ein AgeChangingDelegate -Objekt die PersonAgeChanging -Funktionen hinter den Kulissen umh\u00fcllen. In der Praxis verwenden wir diese kompaktere Syntax.

                9. Versuchen wir auch, uns abzumelden (an einem Punkt unserer Wahl) und starten wir dann das Programm.

                  p.AgeChanging -= PersonAgeChanging;\n
                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#3-aufgabe-ereignis-event","title":"3. Aufgabe - Ereignis (event)","text":"

                So wie Eigenschaften eine syntaktisch schlankere Alternative zu Getter- und Setter-Methoden sind, bietet der oben beschriebene Delegat-Mechanismus eine schlankere Alternative zu den aus Java bekannten Event Listenern. Allerdings verst\u00f6\u00dft unsere obige L\u00f6sung immer noch erheblich gegen einige OO-Prinzipien (Einheiteneinschr\u00e4nkung, Verbergen von Informationen). Wir k\u00f6nnen dies anhand der folgenden zwei Beispiele veranschaulichen.

                1. Das Ereignis kann auch von au\u00dfen ausgel\u00f6st werden (durch die Operationen anderer Klassen). Das ist ungl\u00fccklich, denn so kann das Ereignis f\u00e4lschlicherweise ausgel\u00f6st werden, auch wenn es in Wirklichkeit nicht eingetreten ist, und alle Teilnehmer werden get\u00e4uscht. Um dies zu demonstrieren, f\u00fcgen wir die folgende Zeile am Ende der Funktion Main ein.

                  p.AgeChanging(67, 12);\n

                  Hier haben wir ein gef\u00e4lschtes Alters\u00e4nderungsereignis f\u00fcr das Objekt p Person ausgel\u00f6st und damit alle Abonnenten get\u00e4uscht. Eine gute L\u00f6sung w\u00e4re, wenn das Ereignis nur durch Aktionen der Klasse Person ausgel\u00f6st werden k\u00f6nnte.

                2. Ein weiteres Problem ist das folgende. W\u00e4hrend += und -= andere Funktionen, die die Liste abonniert haben, respektieren, k\u00f6nnen wir die Abonnements anderer jederzeit mit dem Operator = \u00fcberschreiben (l\u00f6schen). Versuchen wir dies, indem wir die folgende Zeile einf\u00fcgen (direkt nach den An- und Abmeldungen).

                  p.AgeChanging = null;\n
                3. F\u00fcgen wir das Schl\u00fcsselwort event zur AgeChanging Member-Variable Person.cshinzu!

                  Person.cs
                  public event AgeChangingDelegate AgeChanging;\n

                  Das Schl\u00fcsselwort event ist eigentlich dazu gedacht, unser Programm zur\u00fcck auf den objektorientierten Weg zu zwingen und die beiden oben genannten Probleme auszuschlie\u00dfen.

                4. Lassen wir uns versuchen, das Programm zu \u00fcbersetzen. wir werden sehen, dass der \u00dcbersetzer unsere fr\u00fcheren \u00dcbertretungen jetzt als \u00dcbersetzungsfehler behandelt.

                5. Entfernen wir die drei fehlerhaften Codezeilen (beachten wir, dass die erste direkte Wertzuweisung bereits ein Fehler ist), kompilieren wir dann und f\u00fchren wir unsere Anwendung aus!

                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#4-aufgabe-attribute","title":"4. Aufgabe - Attribute","text":""},{"location":"labor/2-nyelvi-eszkozok/index_ger/#anpassen-der-serialisierung-nach-attribut","title":"Anpassen der Serialisierung nach Attribut","text":"

                Attribute sind ein deklarativer Weg, um Metadaten f\u00fcr Ihren Quellcode bereitzustellen. Ein Attribut ist eigentlich eine Klasse, die an ein bestimmtes Element des Programms (Typ, Klasse, Schnittstelle, Methode usw.) angeh\u00e4ngt ist. Diese Metainformationen k\u00f6nnen von jedem (auch von uns selbst) gelesen werden, w\u00e4hrend das Programm l\u00e4uft, und zwar \u00fcber einen Mechanismus, der Reflection genannt wird. Die Attribute k\u00f6nnen auch als das .NET-\u00c4quivalent zu den Java-Annotationen betrachtet werden.

                property vs. attribute vs. static

                Es stellt sich die Frage, welche Klasseneigenschaften in properties und welche in attributes einer Klasse untergebracht werden sollten. Eigenschaften beziehen sich auf die Objektinstanz selbst, w\u00e4hrend sich ein Attribut auf die Klasse (oder ein Mitglied der Klasse) bezieht, die das Objekt beschreibt.

                In dieser Hinsicht sind Attribute n\u00e4her an statischen Eigenschaften, aber es lohnt sich immer noch eine \u00dcberlegung, ob man ein bestimmtes Datenelement als statisches Mitglied oder als Attribut definiert. Mit einem Attribut ist die Beschreibung deklarativer, und wir verschmutzen den Code nicht mit Details, die nicht in der \u00f6ffentlichen Schnittstelle der Klasse erscheinen sollten.

                .NET definiert viele eingebaute Attribute, die eine gro\u00dfe Vielfalt an Funktionen haben k\u00f6nnen. Die im folgenden Beispiel verwendeten Attribute kommunizieren beispielsweise verschiedene Metainformationen mit dem XML-Serialisierer.

                1. F\u00fcgen wir den folgenden Zeilen am Ende der Funktion Main ein und f\u00fchren wir dann unser Programm aus!

                  var serializer = new XmlSerializer(typeof(Person));\nvar stream = new FileStream(\"person.txt\", FileMode.Create);\nserializer.Serialize(stream, p);\nstream.Close();\nProcess.Start(new ProcessStartInfo\n{\n    FileName = \"person.txt\",\n    UseShellExecute = true,\n});\n

                  Der letzte Funktionsaufruf Process.Start im obigen Beispiel ist nicht Teil der Serialisierungslogik, sondern lediglich sondern nur eine kluge Methode, um die resultierende Datendatei mit dem Windows-Standardtextdateibetrachter zu \u00f6ffnen. Wir k\u00f6nnen dies versuchen, aber es h\u00e4ngt davon ab, welche .NET-Laufzeitumgebung wir verwenden und ob diese von unserem Betriebssystem unterst\u00fctzt wird. Ist dies nicht der Fall, erhalten wir bei der Ausf\u00fchrung eine Fehlermeldung. In diesem Fall lassen wir es unkommentiert und \u00f6ffnen wir die Datei person.txt manuell im Dateisystem (sie befindet sich in unserem Visual Studio Ordner unter \\bin\\Debug\\ neben unserer .exe Anwendung).

                2. Schauen wir uns die Struktur der resultierenden Datei an. Beachten wir, dass jede Eigenschaft auf das XML-Element abgebildet wird, das ihrem Namen entspricht.

                3. .NET-Attribute erm\u00f6glichen es uns, unsere Klasse Person mit Metadaten zu versehen, die das Verhalten der Serialisierung direkt ver\u00e4ndern. Das Attribut XmlRoot bietet die M\u00f6glichkeit, das Wurzelelement umzubenennen. Platzieren wir es \u00fcber der Klasse Person!

                  [XmlRoot(\"deutsche Person\")]\npublic class Person \n{\n    // ...\n}\n
                4. Das XmlAttribute -Attribut zeigt dem Serialisier an, dass die markierte Eigenschaft auf ein xml-Attribut und nicht auf ein xml-Element abgebildet werden soll. Machen wir daraus die Eigenschaft Age (und nicht die Member-Variable!)!

                  [XmlAttribute(\"Alter\")]\npublic int Age\n
                5. Das Attribut XmlIgnore zeigt dem Serialiser an, dass die markierte Eigenschaft vollst\u00e4ndig aus dem Ergebnis ausgelassen werden soll. Versuchen wir es \u00fcber die Eigenschaft Name.

                  [XmlIgnore]\npublic string Name { get; set; }\n
                6. F\u00fchren wir unsere App aus! Vergleichen wir die Ergebnisse mit den vorherigen Ergebnissen.

                7. "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#5-aufgabe-delegaten-2","title":"5. Aufgabe - Delegaten 2.","text":"

                  In den Aufgaben 2 und 3 haben wir ereignisbasierte Nachrichten\u00fcbermittlung mit Delegaten implementiert. Als einer anderen typischen Verwendung von Delegaten ist ihre Verwendung als Funktionsreferenzen, um eine Implementierung eines undefinierten Schritts an einen Algorithmus oder eine komplexere Operation zu \u00fcbergeben.

                  Zum Beispiel kann die eingebaute generische Listenklasse (List<T>) mit der Funktion FindAll eine neue Liste mit allen Elementen zur\u00fcckgeben, die eine bestimmte Bedingung erf\u00fcllen. Die spezifische Filterbedingung kann als Funktion angegeben werden, genauer gesagt als Delegate-Parameter (dies ruft FindAll f\u00fcr jedes Element auf), der f\u00fcr jedes Element, das wir in der Ergebnisliste sehen wollen, true zur\u00fcckgibt. Der Typ des Funktionsparameters ist der folgende vordefinierte Delegatentyp (er muss nicht eingegeben/erstellt werden, er existiert bereits):

                  public delegate bool Predicate<T>(T obj)\n

                  Note

                  Um die vollst\u00e4ndige Definition oben anzuzeigen, geben Sie einfach Predicate irgendwo ein, z. B. am Ende der Funktion Main, klicken Sie mit der Maus darauf, und verwenden Sie F12, um zur Definition zu navigieren.

                  Das hei\u00dft, sie nimmt als Eingabe eine Variable des gleichen Typs wie der Typ des Listenelements und als Ausgabe einen logischen (booleschen) Wert. Um dies zu veranschaulichen, f\u00fcgen wir unserem vorherigen Programm einen Filter hinzu, der nur die ungeraden Eintr\u00e4ge in der Liste beh\u00e4lt.

                  1. Stellen wir in unserer Anwendung eine Filterfunktion bereit, die ungerade Zahlen zur\u00fcckgibt:

                    private static bool MyFilter(int n)\n{\n    return n % 2 == 1;\n}\n
                  2. Vervollst\u00e4ndigen wir den Code, den wir zuvor geschrieben haben, mit unserer Filterfunktion:

                    var list = new List<int>();\nlist.Add(1);\nlist.Add(2);\nlist.Add(3);\nlist = list.FindAll(MyFilter);\n\nforeach (int n in list)\n{\n    Console.WriteLine($\"Wert: {n}\");\n}\n
                  3. F\u00fchren wir die Anwendung aus. Beachten wir, dass in der Konsole nur ungerade Zahlen angezeigt werden.

                  4. Als Kuriosit\u00e4t k\u00f6nnen wir einen Haltepunkt innerhalb unserer Funktion MyFilter setzen und beobachten, dass die Funktion tats\u00e4chlich f\u00fcr jedes Listenelement einzeln aufgerufen wird.

                  Collection initializer syntax

                  F\u00fcr alle Klassen (typischerweise Sammlungen) mit der Methode Add, die die Schnittstelle IEnumerable implementieren, lautet die Syntax f\u00fcr die Sammlungsinitialisierung wie folgt:

                  var list = new List<int>() { 1, 2, 3 };\n

                  Ab C# 12 kann eine noch einfachere Syntax (sogenannte collection expression) verwendet werden, um eine Sammlung zu initialisieren, wenn der Compiler aus dem Typ der Variablen schlie\u00dfen kann, dass es sich um eine Sammlung handelt. Z.B.:

                  List<int> list = [1, 2, 3];\n
                  "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#6-aufgabe-lambda-begriffe","title":"6. Aufgabe - Lambda-Begriffe","text":"

                  Die entsprechenden Themen werden in dem Vorlesungsmaterial ausf\u00fchrlich behandelt, sie werden hier nicht wiederholt. Siehe das Kapitel \"Lambda-Ausdruck\" im Dokument \"Vorlesung 02 - Sprachwerkzeuge.pdf\". Das Schl\u00fcsselelement ist => (Lambda-Operator), das die Definition von Lambda-Ausdr\u00fccken, d. h. anonymen Funktionen, erm\u00f6glicht.

                  Action und Func

                  Die in .NET eingebauten generischen Delegatentypen Func und Action werden hier aus Zeitgr\u00fcnden nicht behandelt. Sie sind immer noch Teil des grundlegende Kenntnisse!

                  Die vorherige Aufgabe 5 wird wie folgt gel\u00f6st: Geben wir keine separate Filterfunktion an, sondern spezifizieren wir die Filterlogik in Form eines Lambda-Ausdrucks f\u00fcr die Operation FindAll.

                  Wir brauchen nur eine Zeile zu \u00e4ndern:

                  list = list.FindAll((int n) => { return n % 2 == 1; });\n

                  Eine unbenannte Funktion wird definiert und an die Funtkion FindAll \u00fcbergeben:

                  • dies ist ein Lambda-Term,
                  • auf der linken Seite von => haben wir die Parameter der Operation angegeben (hier gab es nur einen),
                  • auf der rechten Seite von => haben wir der Stamm der Operation angegeben (die gleiche wie der Stamm der vorherigen MyFilter ).

                  Die obige Zeile kann in einer viel einfacheren und klareren Form geschrieben werden:

                  list = list.FindAll(n => n % 2 == 1);\n

                  Es wurden die folgenden Vereinfachungen vorgenommen:

                  • wird der Typ des Parameters nicht geschrieben: der Compiler kann ihn aus dem Typ des Delegatenparameters von FindAll ableiten, der Predicateist.
                  • die Klammern um den Parameter k\u00f6nnen weggelassen werden (da es nur einen Parameter gibt)
                  • auf der rechten Seite von => k\u00f6nnten wir die Klammern und return weglassen (weil es nur einen Ausdruck im Funktionsrumpf gab, der von der Funktion zur\u00fcckgegeben wird).
                  "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#7-andere-sprachkonstruktionen","title":"7. Andere Sprachkonstruktionen","text":"

                  Im Folgenden werfen wir einen Blick auf einige der C#-Sprachelemente, die bei allt\u00e4glichen Programmieraufgaben immer h\u00e4ufiger verwendet werden. W\u00e4hrend der \u00dcbung kann es sein, dass keine Zeit bleibt, diese zu \u00fcberpr\u00fcfen.

                  "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#ausdruckskorpermember-expression-bodied-members","title":"Ausdrucksk\u00f6rpermember (Expression-bodied members)","text":"

                  Manchmal schreiben wir kurze Funktionen oder, im Falle von Eigenschaften, sehr oft kurze get/set/init-Definitionen, die aus einem einzigen Ausdruck bestehen. In diesem Fall kann der get/set/init-Stamm einer Funktion oder Eigenschaft unter Verwendung der Syntax f\u00fcr sogenannten Ausdrucksk\u00f6rpermember (expression-bodied members) angegeben werden, unter =>. Dies kann unabh\u00e4ngig davon geschehen, ob es im Kontext einen R\u00fcckgabewert (Return-Anweisung) gibt oder nicht.

                  In den Beispielen werden wir sehen, dass die Verwendung von Ausdrucks-Tags nichts weiter als eine kleine syntaktische \"Wendung\" ist, um die Notwendigkeit zu minimieren, so viel umgebenden Code wie m\u00f6glich in solch einfachen F\u00e4llen zu schreiben.

                  Schauen wir uns zun\u00e4chst ein Funktionsbeispiel an (angenommen, die Klasse hat eine Mitgliedsvariable oder eine Eigenschaft Age ):

                  public int GetAgeInDogYear() => Age * 7; \npublic void DisplayName() => Console.WriteLine(ToString());\n
                  Wie wir sehen k\u00f6nnen, haben wir die Klammern und die Anweisung return entfernt, so dass die Syntax kompakter ist.

                  Wichtig

                  Obwohl hier das Token => verwendet wird, hat dies nichts mit den zuvor besprochenen Lambda-Ausdr\u00fccken zu tun: Es ist einfach so, dass dasselbe => Token (Symbolpaar) von C# f\u00fcr zwei v\u00f6llig unterschiedliche Dinge verwendet wird.

                  Beispiel f\u00fcr die Angabe eines Property Getters:

                  public int AgeInDogYear { get => Age * 7; }\n

                  Wenn wir nur einen Getter f\u00fcr die Eigenschaft haben, k\u00f6nnen wir sogar das Schl\u00fcsselwort get und die Klammern weglassen.

                  public int AgeInDogYear => Age * 7;\n

                  Der Unterschied zur \u00e4hnlichen Syntax der bisherigen Funktionen ist, dass wir die geschweifte Klammern nicht ausgeschrieben haben.

                  "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#objektinitialisierer-object-initializer","title":"Objektinitialisierer (Object initializer)","text":"

                  Die Initialisierung von \u00f6ffentlichen Eigenschaften/Mitgliedsvariablen und der Aufruf des Konstruktors k\u00f6nnen mit einer Syntax kombiniert werden, die als Objektinitialisierung bezeichnet wird. Dazu wird nach dem Konstruktoraufruf ein Block mit geschweifte Klammern ge\u00f6ffnet, in dem der Wert der \u00f6ffentlichen Eigenschaften/Mitgliedsvariablen unter Verwendung der folgenden Syntax angegeben werden kann.

                  var p = new Person()\n{\n    Age = 17,\n    Name = \"Lukas\",\n};\n

                  Eigenschaften/Mitglieder werden initialisiert, nachdem der Konstruktor ausgef\u00fchrt wurde (wenn die Klasse einen Konstruktor hat). Diese Syntax ist auch deshalb vorteilhaft, weil sie als ein Ausdruck z\u00e4hlt (im Gegensatz zu drei Ausdr\u00fccken, wenn wir ein nicht initialisiertes Objekt Person erstellen und dann in zwei weiteren Schritten Werte an Age und Name \u00fcbergeben). Auf diese Weise k\u00f6nnen wir ein initialisiertes Objekt direkt als Parameter f\u00fcr einen Funktionsaufruf \u00fcbergeben, ohne eine separate Variable deklarieren zu m\u00fcssen.

                  void Foo(Person p)\n{\n    // etwas mit p machen\n}\n
                  Foo(new Person() { Age = 17, Name = \"Lukas\" });\n

                  Die Syntax ist auch zum Kopieren und Einf\u00fcgen geeignet, denn wie wir in den obigen Beispielen sehen k\u00f6nnen, spielt es keine Rolle, ob nach der letzten Eigenschaft ein Komma steht oder nicht.

                  "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#eigenschaften-init-only-setter","title":"Eigenschaften - Init only setter","text":"

                  Die Syntax f\u00fcr die Objektinitialisierung im vorigen Abschnitt ist sehr praktisch, erfordert aber, dass die Eigenschaft \u00f6ffentlich ist. Wenn wir m\u00f6chten, dass eine Eigenschaft nur bei der Erstellung des Objekts auf einen Wert gesetzt wird, m\u00fcssen wir einen Konstruktorparameter einf\u00fchren und ihn auf eine Nur-Lesbare-Eigenschaft (Getter-Only) setzen. Eine einfachere L\u00f6sung f\u00fcr dieses Problem ist die so genannte Init only setter-Syntax, bei der wir mit dem Schl\u00fcsselwort init einen \"Setter\" erstellen k\u00f6nnen, der nur im Konstruktor und in der im vorigen Kapitel beschriebenen Syntax f\u00fcr die Objektinitialisierung gesetzt werden darf, nicht aber danach.

                  public string Name { get; init; }\n
                  var p = new Person()\n{\n    Age = 17,\n    Name = \"Lukas\",\n};\n\np.Name = \"Test\"; // Erstellungsfehler, kann nicht nachtr\u00e4glich ge\u00e4ndert werden\n

                  Wir k\u00f6nnen auch den init only setter als obligatorisch festlegen, indem wir das Schl\u00fcsselwort required f\u00fcr die Eigenschaft verwenden. In diesem Fall muss der Wert der Eigenschaft in der Syntax der Objektinitialisierung angegeben werden, da sonst ein \u00dcbersetzungsfehler auftritt.

                  public required string Name { get; init; }\n

                  Dies ist auch deshalb n\u00fctzlich, weil wir die obligatorischen Konstruktorparameter speichern k\u00f6nnen, wenn wir die Eigenschaften der Klasse ohnehin ver\u00f6ffentlichen und die Syntax der Objektinitialisierung unterst\u00fctzen wollen.

                  "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#8-aufgabe-generische-klassen","title":"8. Aufgabe - Generische Klassen","text":"

                  Hinweis: Die Zeit f\u00fcr diese \u00dcbung reicht wahrscheinlich nicht aus. In diesem Fall ist es ratsam, die \u00dcbung zu Hause zu machen.

                  Generische Klassen in .NET \u00e4hneln den Template-Klassen in C++, sind aber n\u00e4her an den bereits bekannten generischen Klassen in Java. Sie k\u00f6nnen verwendet werden, um generische (Multi-Typ), aber typsichere Klassen zu erstellen. Wenn wir ohne generische Klassen ein Problem allgemein behandeln wollen, verwenden wir Daten des Typs object (da in .NET alle Klassen von der Klasse object abgeleitet sind). Dies ist z. B. bei ArrayListder Fall, einer Allzwecksammlung zum Speichern beliebiger Elemente des Typs object. Schauen wir uns ein Beispiel f\u00fcr die Verwendung von ArrayList an:

                  var list = new ArrayList();\nlist.Add(1);\nlist.Add(2);\nlist.Add(3);\nfor (int n = 0; n < list.Count; n++)\n{\n    //cast ist n\u00f6tig, sonder es kann nicht kompiliert werden\n    int i = (int)list[n];\n    Console.WriteLine($\"Wert: {i}\");\n}\n

                  Bei der obigen L\u00f6sung ergeben sich folgende Probleme:

                  • ArrayList speichert jedes Element als object.
                  • Wenn wir auf ein Element in der Liste zugreifen wollen, m\u00fcssen wir es immer in den richtigen Typ umwandeln.
                  • Nicht typsicher. Im obigen Beispiel hindert wir nichts (und keine Fehlermeldung) daran, ein Objekt eines anderen Typs in die Liste neben dem Typ int einzuf\u00fcgen. In diesem Fall w\u00fcrden wir nur dann einen Fehler erhalten, wenn wir versuchen, den Typ, der nicht int ist, auf int zu \u00fcbertragen. Bei der Verwendung generischer Sammlungen werden solche Fehler w\u00e4hrend der \u00dcbersetzung erkannt.
                  • Bei der Speicherung von Daten des Typs \"Wert\" ist die Liste langsamer, da der Typ \"Wert\" zun\u00e4chst in eine Box eingeschlossen werden muss, um als object(d. h. als Referenztyp) gespeichert werden zu k\u00f6nnen.

                  Die L\u00f6sung des obigen Problems unter Verwendung einer allgemeinen Liste sieht wie folgt aus (in der \u00dcbung wird nur die hervorgehobene Zeile im zuvor eingegebenen Beispiel ge\u00e4ndert):

                  var list = new List<int>();\nlist.Add(1);\nlist.Add(2);\nlist.Add(3);\nfor (int n = 0; n < list.Count; n++)\n{\n    int i = list[n]; // Kein cast erforderlich\n    Console.WriteLine($\"Wert: {i}\");\n}\n
                  "},{"location":"labor/3-felhasznaloi-felulet/","title":"3. A felhaszn\u00e1l\u00f3i fel\u00fclet kialak\u00edt\u00e1sa","text":""},{"location":"labor/3-felhasznaloi-felulet/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                  A gyakorlat c\u00e9lja megismerkedni a vastagkliens alkalmaz\u00e1sok fejleszt\u00e9s\u00e9nek alapjaival a deklarat\u00edv XAML fel\u00fcletle\u00edr\u00f3 technol\u00f3gi\u00e1n kereszt\u00fcl. Az itt tanult alapok az \u00f6sszes XAML dialektusra (WinUI, WPF, UWP, Xamarin.Forms, MAUI) igazak lesznek, vagy nagyon hasonl\u00f3an lehet \u0151ket alkalmazni, mi viszont a mai \u00f3r\u00e1n specifikusan a WinAppSDK / WinUI 3 keretrendszeren kereszt\u00fcl fogjuk haszn\u00e1lni a XAML-t.

                  "},{"location":"labor/3-felhasznaloi-felulet/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                  A labor elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                  • Windows 10 vagy Windows 11 oper\u00e1ci\u00f3s rendszer (Linux \u00e9s macOS nem alkalmas)

                  A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s.

                  Fejleszt\u0151k\u00f6rnyezet WinUI3 fejleszt\u00e9shez

                  A kor\u00e1bbi laborokhoz k\u00e9pest plusz komponensek telep\u00edt\u00e9se sz\u00fcks\u00e9ges. A fenti oldal eml\u00edti, hogy sz\u00fcks\u00e9g van a \".NET desktop development\" Visual Studio Workload telep\u00edt\u00e9s\u00e9re, valamint ugyanitt az oldal alj\u00e1n van egy \"WinUI t\u00e1mogat\u00e1s\" fejezet, az itt megadott l\u00e9p\u00e9seket is mindenk\u00e9ppen meg kell tenni!

                  "},{"location":"labor/3-felhasznaloi-felulet/#megoldas","title":"Megold\u00e1s","text":"A k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se

                  L\u00e9nyeges, hogy a labor sor\u00e1n a laborvezet\u0151t k\u00f6vetve kell dolgozni, tilos (\u00e9s \u00e9rtelmetlen) a k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se. Ugyanakkor az ut\u00f3lagos \u00f6n\u00e1ll\u00f3 gyakorl\u00e1s sor\u00e1n hasznos lehet a k\u00e9sz megold\u00e1s \u00e1ttekint\u00e9se, \u00edgy ezt el\u00e9rhet\u0151v\u00e9 tessz\u00fck.

                  A megold\u00e1s GitHubon \u00e9rhet\u0151 el a megoldas \u00e1gon. A legegyszer\u0171bb m\u00f3d a let\u00f6lt\u00e9s\u00e9re, ha parancssorb\u00f3l a git clone utas\u00edt\u00e1ssal lekl\u00f3nozzuk a g\u00e9p\u00fcnkre a megoldas \u00e1gat:

                  git clone https://github.com/bmeviauab00/lab-xaml-kiindulo -b megoldas

                  Ehhez telep\u00edtve kell legyen a g\u00e9pre a parancssori git, b\u0151vebb inform\u00e1ci\u00f3 itt.

                  "},{"location":"labor/3-felhasznaloi-felulet/#kiindulo-projekt","title":"Kiindul\u00f3 projekt","text":"

                  Az els\u0151 feladatban kialak\u00edtjuk a k\u00f6rnyezetet, amelyben a tov\u00e1bbiakban a XAML nyelv \u00e9s a WinUI keretrendszer m\u0171k\u00f6d\u00e9s\u00e9t vizsg\u00e1ljuk. A kiindul\u00f3 projektet a Visual Studi\u00f3val is legener\u00e1lhatn\u00e1nk (WinUI 3 projekt, Blank App, Packaged (WinUI 3 in Desktop) t\u00edpus), de az \u00f3ra g\u00f6rd\u00fcl\u00e9kenys\u00e9ge \u00e9rdek\u00e9ben az el\u0151re elk\u00e9sz\u00edtett projektet fogjuk haszn\u00e1lni.

                  A projektet a k\u00f6vetkez\u0151 parancs kiad\u00e1s\u00e1val tudjuk lekl\u00f3nozni a g\u00e9p\u00fcnkre:

                  git clone https://github.com/bmeviauab00/lab-xaml-kiindulo.git\n

                  Nyissuk meg a HelloXaml.sln-t.

                  Tekints\u00fck \u00e1t milyen f\u00e1jlokat tartalmaz a projekt:

                  • App
                    • K\u00e9t f\u00e1jl App.xaml \u00e9s App.xaml.cs(k\u00e9s\u0151bb tiszt\u00e1zzuk, k\u00e9t f\u00e1jl tartozik hozz\u00e1)
                    • Alkalmaz\u00e1s bel\u00e9p\u00e9si pontja: OnLaunched fel\u00fcldefini\u00e1lt met\u00f3dus az App.xaml.cs-ben
                    • Eset\u00fcnkben itt inicializ\u00e1ljuk az alkalmaz\u00e1s egyetlen ablak\u00e1t a MainWindow-t
                  • MainWindow
                    • Alkalmaz\u00e1sunk f\u0151ablak\u00e1hoz tartoz\u00f3 .xaml \u00e9s .xaml.cs f\u00e1jlok.
                  Tov\u00e1bbi solution elemek

                  A kiindul\u00f3 VS solution a k\u00f6vetkez\u0151 elemeket tartalmazza m\u00e9g:

                  • Dependencies
                    • Frameworks
                      • Microsoft.AspNetCore.App: .NET SDK metapackage (Microsoft .NET \u00e9s SDK alapcsomagokat hivatkozza be)
                      • Windows specifikus .NET SDK
                    • Packages
                      • Windows SDK Build Tools
                      • WindowsAppSDK
                  • Assets
                    • Alkalmaz\u00e1s log\u00f3i
                  • app.manifest, Package.appxmanifest
                    • Az alkalmaz\u00e1s metaadatait tartalmaz\u00f3 XML \u00e1llom\u00e1ny, melyben t\u00f6bbek k\u00f6z\u00f6tt megadhatjuk a log\u00f3kat, vagy pl. Androidhoz hasonl\u00f3an itt kell jogot k\u00e9rj\u00fcnk a biztons\u00e1gkritikus rendszerer\u0151forr\u00e1sokhoz.

                  Futtassuk az alkalmaz\u00e1st!

                  "},{"location":"labor/3-felhasznaloi-felulet/#xaml-bevezetes","title":"XAML bevezet\u00e9s","text":"

                  A fel\u00fclet le\u00edr\u00e1s\u00e1t egy XML alap\u00fa le\u00edr\u00f3 nyelvben, XAML-ben (ejtsd: zem\u00f6l) fogjuk megadni.

                  Grafikus designer fel\u00fclet

                  Bizonyos XAML dialektusok eset\u00e9ben (pl.: WPF) rendelkez\u00e9s\u00fcnkre \u00e1ll grafikus designer eszk\u00f6z is a fel\u00fclet kialak\u00edt\u00e1s\u00e1hoz, de az \u00e1ltal\u00e1ban kev\u00e9sb\u00e9 hat\u00e9kony XAML le\u00edr\u00e1st szokott gener\u00e1lni. R\u00e1ad\u00e1sul m\u00e1r a Visual Studio is t\u00e1mogatja a Hot Reload m\u0171k\u00f6d\u00e9st XAML esetben, \u00edgy nem sz\u00fcks\u00e9ges le\u00e1ll\u00edtani az alkalmaz\u00e1st a XAML szerkeszt\u00e9se k\u00f6zben, a v\u00e1ltoztat\u00e1sokat pedig azonnal l\u00e1thatjuk a fut\u00f3 alkalmaz\u00e1sban. Ez\u00e9rt WinUI eset\u00e9ben m\u00e1r nem is kapunk designer t\u00e1mogat\u00e1st a Visual Studioban. A tapasztalatok alapj\u00e1n vannak limit\u00e1ci\u00f3i, \"nagyobb\" l\u00e9pt\u00e9k\u0171 v\u00e1ltoztat\u00e1sok eset\u00e9n sz\u00fcks\u00e9g van az alkalmaz\u00e1s \u00fajraind\u00edt\u00e1s\u00e1ra.

                  "},{"location":"labor/3-felhasznaloi-felulet/#xaml-nyelvi-alapok","title":"XAML nyelvi alapok","text":"

                  A XAML nyelv:

                  • Objektump\u00e9ld\u00e1nyos\u00edt\u00f3 nyelv
                  • Szabv\u00e1nyos XML
                  • XML elemek/tagek: objektumokat p\u00e9ld\u00e1nyos\u00edtanak, melyek oszt\u00e1lyai szabv\u00e1nyos .NET oszt\u00e1lyok
                  • XML attrib\u00fatumok: tulajdons\u00e1gokat (dependency property-ket) \u00e1ll\u00edtanak be
                  • Deklarat\u00edv

                  N\u00e9zz\u00fck meg, milyen XAML-t gener\u00e1lt a projekt sablon (MainWindow.xaml). L\u00e1thatjuk, hogy a XAML-ben minden vez\u00e9rl\u0151h\u00f6z l\u00e9trehozott egy XML elemet/taget. A vez\u00e9rl\u0151k tagjein pedig be vannak \u00e1ll\u00edtva a vez\u00e9rl\u0151 tulajdons\u00e1gai. Pl. HorizontalAlignment: igaz\u00edt\u00e1s a kont\u00e9neren (eset\u00fcnkben ablakon) bel\u00fcl. Vez\u00e9rl\u0151k tartalmazhatnak m\u00e1s vez\u00e9rl\u0151ket, \u00edgy vez\u00e9rl\u0151kb\u0151l \u00e1ll\u00f3 fa j\u00f6n l\u00e9tre.

                  N\u00e9zz\u00fck meg r\u00e9szletesebben a MainWindow.xaml-t:

                  • Gy\u00f6k\u00e9r tagen n\u00e9vterek: meghat\u00e1rozz\u00e1k, hogy az XML-ben milyen tageket \u00e9s attrib\u00fatumokat haszn\u00e1lhatunk
                    • Alap\u00e9rtelmezett n\u00e9vt\u00e9r: XAML elemek/vez\u00e9rl\u0151k (pl. Button, TextBox stb.) n\u00e9vtere
                    • x n\u00e9vt\u00e9r: XAML parser n\u00e9vtere (pl.: x:Class, x:Name)
                    • Egy\u00e9b tetsz\u0151leges n\u00e9vterek hivatkozhat\u00f3k
                  • Window gy\u00f6k\u00e9r tag
                    • Az ablakunk/oldalunk alapj\u00e1n egy .NET oszt\u00e1ly j\u00f6n l\u00e9tre, mely a Window oszt\u00e1lyb\u00f3l sz\u00e1rmazik.
                    • A lesz\u00e1rmaztatott oszt\u00e1lyunk nev\u00e9t az x:Class attrib\u00fatum hat\u00e1rozza meg: az x:Class=\"HelloXaml.MainWindow\" alapj\u00e1n egy HelloXaml n\u00e9vt\u00e9rben egy MainWindow nev\u0171 oszt\u00e1ly lesz.
                    • Ez egy partial class, az oszt\u00e1ly \"m\u00e1sik fele\" az ablakhoz/oldalhoz tartoz\u00f3 \u00fan. a code-behind f\u00e1jlban (MainWindow.xaml.cs) tal\u00e1lhat\u00f3. L\u00e1sd k\u00f6vetkez\u0151 pont.
                  • Code-behind f\u00e1jl (MainWindow.xaml.cs):
                    • A partial classunk m\u00e1sik \"fele\": ellen\u0151rizz\u00fck, hogy itt az oszt\u00e1ly neve \u00e9s n\u00e9vtere megegyezik a .xaml f\u00e1jlban megadottal (partial class!).
                    • Esem\u00e9nykezel\u0151 \u00e9s seg\u00e9df\u00fcggv\u00e9nyeket tessz\u00fck ide (t\u00f6bbek k\u00f6z\u00f6tt).
                    • this.InitializeComponent();: a konstruktorban mindig meg kell h\u00edvni, ez olvassa majd be fut\u00e1s k\u00f6zben a XAML-t, ez p\u00e9ld\u00e1nyos\u00edtja, inicializ\u00e1lja az ablak/oldal tartalm\u00e1t (vagyis a XAML-f\u00e1jlban megadott vez\u00e9rl\u0151ket az ott meghat\u00e1rozott tulajdons\u00e1gokkal).

                  T\u00f6r\u00f6lj\u00fck ki a Window tartalm\u00e1t \u00e9s a code-behind f\u00e1jlb\u00f3l az esem\u00e9nykezel\u0151t (myButton_Click f\u00fcggv\u00e9ny). Most k\u00e9zzel fogunk XAML-t \u00edrni \u00e9s ezzel a fel\u00fcletet kialak\u00edtani. Vegy\u00fcnk fel egy Grid-et a Window-ba, mellyel a k\u00e9s\u0151bbiekben egy t\u00e1bl\u00e1zatos elrendez\u00e9st (layout) fogunk tudunk kialak\u00edtani:

                  <?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Window\n    x:Class=\"HelloXaml.MainWindow\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:local=\"using:HelloXaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\">\n\n    <Grid>\n\n    </Grid>\n</Window>\n

                  Futtassuk az alkalmaz\u00e1st (pl. az F5 billenty\u0171vel). A Grid most kit\u00f6lti a teljes ablakot, a sz\u00edne megegyezik az ablak h\u00e1tt\u00e9rsz\u00edn\u00e9vel, ez\u00e9rt szemmel nem tudjuk megk\u00fcl\u00f6nb\u00f6ztetni.

                  A k\u00f6vetkez\u0151 feladatok sor\u00e1n hagyjuk futni az alkalmaz\u00e1st, hogy azonnal l\u00e1thassuk a fel\u00fcleten eszk\u00f6z\u00f6lt m\u00f3dos\u00edt\u00e1sainkat.

                  Hot Reload limit\u00e1ci\u00f3k

                  Tartsuk szem el\u0151tt a Hot Reload limit\u00e1ci\u00f3it: ha egy v\u00e1ltoz\u00e1sunk nem akar a fut\u00f3 alkalmaz\u00e1s fel\u00fclet\u00e9n megjelenni, akkor ind\u00edtsuk majd \u00fajra az alkalmaz\u00e1st!

                  "},{"location":"labor/3-felhasznaloi-felulet/#objektum-peldanyok-es-tulajdonsagaik","title":"Objektum p\u00e9ld\u00e1nyok \u00e9s tulajdons\u00e1gaik","text":"

                  Most azt n\u00e9zz\u00fck meg, hogyan tudunk XAML alapokon objektumokat p\u00e9ld\u00e1nyos\u00edtani \u00e9s ezen objektumok tulajdons\u00e1gait be\u00e1ll\u00edtani.

                  Vegy\u00fcnk fel a Grid belsej\u00e9be egy Button-t. A Content tulajdons\u00e1ggal adhatjuk meg a gomb sz\u00f6veg\u00e9t, pontosabban a tartalm\u00e1t.

                  <Button Content=\"Hello WinUI App!\"/>\n

                  Ez azon a helyen, ahol deklar\u00e1ltuk, fut\u00e1s k\u00f6zben l\u00e9trehoz egy Button objektumot, \u00e9s a Content tulajdons\u00e1g\u00e1t a \"Hello WinUI App!\" sz\u00f6vegre \u00e1ll\u00edtja. Ezt megtehett\u00fck volna a code-behind f\u00e1jlban C# nyelven is k\u00f6vetkez\u0151k\u00e9ppen (de ez kev\u00e9sb\u00e9 olvashat\u00f3 k\u00f3dot eredm\u00e9nyezne):

                  // Pl. a konstruktor v\u00e9g\u00e9re be\u00edrva:\n\nButton b = new Button();\nb.Content = \"Hello WinUI App!\";\nrootGrid.Children.Add(b); \n// Az el\u0151z\u0151 a sorhoz XAML f\u00e1jlban a Gridnek meg kellene adni az x:Name=\"rootGrid\" \n// attrib\u00fatumot, hogy rootGrid n\u00e9ven el\u00e9rhet\u0151 legyen a code-behind f\u00e1jlban\n

                  Ez a p\u00e9lda nagyon j\u00f3l szeml\u00e9lteti, hogy a XAML alapvet\u0151en egy objektump\u00e9ld\u00e1nyos\u00edt\u00f3 nyelv, \u00e9s t\u00e1mogatja objektumok tulajdons\u00e1gainak be\u00e1ll\u00edt\u00e1s\u00e1t.

                  A Content tulajdons\u00e1g k\u00fcl\u00f6nleges, nem csak XML attrib\u00fatumban lehet megadni, hanem tagen (XML elemen) bel\u00fcl is.

                  <Button>Hello WinUI App!</Button>\n

                  S\u0151t! A gombra nem csak feliratot rakhatunk, hanem tetsz\u0151leges m\u00e1s elemet. Pl. rakjunk bele egy piros k\u00f6rt. A k\u00f6r 10 pixel sz\u00e9les, 10 pixel magas, a sz\u00edn (Fill) pedig piros.

                  <Button>\n    <Ellipse Width=\"10\" Height=\"10\" Fill=\"Red\" />\n</Button>\n

                  Ezt kor\u00e1bbi .NET UI technol\u00f3gi\u00e1k eset\u00e9ben (pl. Windows Forms) nem lett volna ilyen egyszer\u0171 megval\u00f3s\u00edtani.

                  Legyen most a piros k\u00f6r mellett a Record felirat (hogy \u00e9rtelme is legyen a piros k\u00f6r\u00f6s gombnak). A gombnak csak egy gyereke lehet, ez\u00e9rt egy layout vez\u00e9rl\u0151be (pl. egy StackPanel-be) kell beraknunk a k\u00f6rt \u00e9s a sz\u00f6veget (TextBlock). Adjunk egy bal oldali marg\u00f3t is a TextBlock-nak, hogy ne \u00e9rjenek \u00f6ssze.

                  <Button>\n    <StackPanel Orientation=\"Horizontal\">\n        <Ellipse Width=\"10\" Height=\"10\" Fill=\"Red\" />\n        <TextBlock Text=\"Record\" Margin=\"10,0,0,0\" />\n    </StackPanel>\n</Button>\n

                  A StackPanel egy egyszer\u0171, vez\u00e9rl\u0151k elrendez\u00e9s\u00e9re szolg\u00e1l\u00f3 layout panel: a tartalmazott vez\u00e9rl\u0151ket Horizontal Orientation megad\u00e1sa eset\u00e9n egym\u00e1s mell\u00e9, Vertical Orientation eset\u00e9n egym\u00e1s al\u00e1 helyezi el. \u00cdgy a p\u00e9ld\u00e1nkban egyszer\u0171en egym\u00e1s mell\u00e9 teszi a k\u00e9t vez\u00e9rl\u0151t.

                  Az eredm\u00e9ny a k\u00f6vetkez\u0151:

                  XAML vektorgrafikus vez\u00e9rl\u0151k

                  L\u00e9nyeges, hogy a XAML vez\u00e9rl\u0151k nagy r\u00e9sze vektorgrafikus. Ez a gomb ugyanolyan \u00e9lesen fog kin\u00e9zni (nem tapasztalunk \"pixelesed\u00e9st\") b\u00e1rmilyen b\u00e1rmilyen DPI ill. nagy\u00edt\u00e1s mellett n\u00e9zz\u00fck.

                  A XAML-ben p\u00e9ld\u00e1nyos\u00edtott vez\u00e9rl\u0151k tulajdons\u00e1gainak megad\u00e1s\u00e1ra h\u00e1rom lehet\u0151s\u00e9g van (ezeket r\u00e9szben haszn\u00e1ltuk is m\u00e1r):

                  • Property ATTRIBUTE syntax
                  • Property ELEMENT syntax
                  • Property CONTENT syntax

                  Tekints\u00fck \u00e1t most r\u00e9szletesebben ezeket a lehet\u0151s\u00e9geket:

                  1. Property ATTRIBUTE syntax. M\u00e1r alkalmaztuk, m\u00e9gpedig a legels\u0151 p\u00e9ld\u00e1nkban:

                    <Button Content=\"Hello WinUI App!\"/>\n

                    Az elnevez\u00e9s onnan ered, hogy a tulajdons\u00e1got XML attrib\u00fatum form\u00e1j\u00e1ban adjuk meg. Seg\u00edts\u00e9g\u00e9vel - mivel XML attrib\u00fatum csak string lehet! - csak sztring form\u00e1ban megadott egyszer\u0171 sz\u00e1m/sztring/stb. \u00e9rt\u00e9k, ill. code-behind f\u00e1jlban defini\u00e1lt tagv\u00e1ltoz\u00f3, esem\u00e9nykezel\u0151 \u00e9rhet\u0151 el. De t\u00edpuskonverterek seg\u00edts\u00e9g\u00e9vel \"\u00f6sszetett\" objektumok is megadhat\u00f3k. Err\u0151l sok sz\u00f3 nem lesz, de a be\u00e9p\u00edtett t\u00edpuskonvertereket sokszor haszn\u00e1ljuk, gyakorlatilag \"\u00f6szt\u00f6n\u00f6sen\". P\u00e9lda:

                    Vegy\u00fcnk fel a Grid-re egy h\u00e1tt\u00e9rsz\u00ednt:

                    <Grid Background=\"Azure\">\n

                    Vagy megadhatjuk hex\u00e1ban is:

                    <Grid Background=\"#FFF0FFFF\">\n

                    A marg\u00f3 (Margin) is egy \u00f6sszetett \u00e9rt\u00e9k, a hozz\u00e1 tartoz\u00f3 t\u00edpuskonveter vessz\u0151vel (vagy sz\u00f3k\u00f6zzel) elv\u00e1lasztva v\u00e1rja a n\u00e9gy oldalra vonatkoz\u00f3 \u00e9rt\u00e9keket (bal, fent, jobb, lent). M\u00e1r haszn\u00e1ltuk is a Record felirat\u00fa TextBlockunk eset\u00e9ben. Megjegyz\u00e9s: marg\u00f3nak egyetlen sz\u00e1m is megadhat\u00f3, akkor mind a n\u00e9gy oldalra ugyanazt fogja alkalmazni.

                  2. Property ELEMENT syntax. Seg\u00edts\u00e9g\u00e9vel egy tulajdons\u00e1got t\u00edpuskonverterek n\u00e9lk\u00fcl tudjuk egy \u00f6sszetett m\u00f3don p\u00e9ld\u00e1nyos\u00edtott/felparam\u00e9terezett objektumra \u00e1ll\u00edtani. N\u00e9zz\u00fck egy p\u00e9ld\u00e1n kereszt\u00fcl.

                    • A fenti p\u00e9ld\u00e1ban Background tulajdons\u00e1g be\u00e1ll\u00edt\u00e1sakor az Azure val\u00f3j\u00e1ban egy SolidColorBrush-t hoz l\u00e9tre, melynek a sz\u00edn\u00e9t vil\u00e1gosk\u00e9kre \u00e1ll\u00edtja. Ezt t\u00edpuskonverter alkalmaz\u00e1sa n\u00e9lk\u00fcl az al\u00e1bbi m\u00f3don lehet megadni:
                    <Grid>\n    <Grid.Background>\n        <SolidColorBrush Color=\"Azure\" />\n    </Grid.Background>\n    ...\n

                    Ez a Grid Background tulajdons\u00e1g\u00e1t \u00e1ll\u00edtja be a megadott SolidColorBrush-ra. Ez az \u00fan. \"property element syntax\" alap\u00fa tulajdons\u00e1gmegad\u00e1s.

                    • A n\u00e9v onnan ered, hogy a tulajdons\u00e1got egy XML elem (\u00e9s pl. nem XML attrib\u00fatum) form\u00e1j\u00e1ban adjuk meg.
                    • Itt a <Grid.Background> elem nem objektump\u00e9ld\u00e1nyt hoz l\u00e9tre, hanem az adott (eset\u00fcnkben Background) property \u00e9rt\u00e9k\u00e9t \u00e1ll\u00edtja be a megfelel\u0151 objektum p\u00e9ld\u00e1ny\u00e1ra (eset\u00fcnkben egy SolidColorBrush-ra). Ezt az XML elem nev\u00e9ben lev\u0151 pont alapj\u00e1n lehet tudni.
                    • Ez \"terjeng\u0151sebb\" forma tulajdons\u00e1g megad\u00e1s\u00e1ra, de teljes rugalmass\u00e1got biztos\u00edt.

                    Cser\u00e9lj\u00fck le a SolidColorBrush-t egy sz\u00edn\u00e1tmenetes Brush-ra (LinearGradientBrush):

                    <Grid>\n    <Grid.Background>\n        <LinearGradientBrush>\n            <LinearGradientBrush.GradientStops>\n                <GradientStop Color=\"Black\" Offset=\"0\" />\n                <GradientStop Color=\"White\" Offset=\"1\" />\n            </LinearGradientBrush.GradientStops>\n        </LinearGradientBrush>\n    </Grid.Background>\n    ...\n

                    LinearGradientBrush-ra nincs t\u00edpuskonverter, ezt csak az element syntax seg\u00edts\u00e9g\u00e9vel tudtuk megadni!

                    K\u00e9rd\u00e9s, hogyan lehets\u00e9ges az, hogy a Grid vez\u00e9rl\u0151 Background tulajdons\u00e1g\u00e1nak SolidColorBrush \u00e9s LinearGradientBrush t\u00edpus\u00fa ecsetet is meg tudtunk adni? A v\u00e1lasz nagyon egyszer\u0171, a polimorfizmus teszi ezt lehet\u0151v\u00e9:

                    • A SolidColorBrush \u00e9s LinearGradientBrush oszt\u00e1lyok a be\u00e9p\u00edtett Brush oszt\u00e1ly lesz\u00e1rmazottai.
                    • A Background tulajdons\u00e1g egy Brush t\u00edpus\u00fa property, \u00edgy a polimorfizmus miatt b\u00e1rmely lesz\u00e1rmazottj\u00e1t lehet haszn\u00e1lni.
                    Note
                    • A fenti p\u00e9ld\u00e1kban a Color (sz\u00edn) megad\u00e1s\u00e1n\u00e1l pl. a Color=\"Azure\" esetben az Azure sz\u00f3b\u00f3l is t\u00edpuskonverter k\u00e9sz\u00edt k\u00e9k Color p\u00e9ld\u00e1nyt. \u00cdgy n\u00e9zne a kor\u00e1bbi, SolidColorBrush alap\u00fa p\u00e9ld\u00e1nk teljesen kifejtve:
                      <Grid>\n    <Grid.Background>\n        <SolidColorBrush>\n            <SolidColorBrush.Color>\n                <Color>#FFF0FFFF</Color>\n            </SolidColorBrush.Color>\n        </SolidColorBrush>\n    </Grid.Background>\n    ...\n
                    • Ahol t\u00e1mogatott, \u00e9rdemes kihaszn\u00e1lni a t\u00edpuskonvertereket, \u00e9s attribute syntaxot haszn\u00e1lni, hogy ne legyen terjeng\u0151s a XAML le\u00edr\u00e1sunk.
                    • \u00c9rt\u00e9kt\u00edpusokn\u00e1l (struct), mint amilyen a Color is, m\u00e1r az objektum p\u00e9ld\u00e1nyos\u00edt\u00e1sakor (\"konstruktor id\u0151ben\") kell megadni az \u00e9rt\u00e9ket, ez\u00e9rt itt nem lehet a propertyket k\u00fcl\u00f6n \u00e1ll\u00edtgatni, musz\u00e1j t\u00edpuskonverterre b\u00edzni magunkat.
                  3. Property CONTENT syntax. Annak \u00e9rdek\u00e9ben, hogy jobban meg\u00e9rts\u00fck, n\u00e9zz\u00fck meg, milyen h\u00e1romf\u00e9le m\u00f3don tudjuk be\u00e1ll\u00edtani egy gomb Content tulajdons\u00e1g\u00e1t valamilyen sz\u00f6vegre (ezt laboron nem kell megtenni, el\u00e9g, ha jelen \u00fatmutat\u00f3ban n\u00e9zz\u00fck k\u00f6z\u00f6sen):

                    • Property attribute syntax (m\u00e1r haszn\u00e1ltuk):
                      <Button Content=\"Hello WinUI App!\"/>\n
                    • \u00c1ll\u00edtsuk be az el\u0151z\u0151 pontban tanult property element syntax alapj\u00e1n:
                      <Button>\n    <Button.Content>\n    Hello WinUI App!\n    </Button.Content>\n</Button>\n
                    • Minden vez\u00e9rl\u0151 meghat\u00e1rozhat mag\u00e1r\u00f3l egy kit\u00fcntetett \"Content\" tulajdons\u00e1got, melyn\u00e9l nem kell ki\u00edrni a nyit\u00f3 \u00e9s csuk\u00f3 tag-eket. Vagyis az el\u0151z\u0151 p\u00e9ld\u00e1ban alkalmazott <Button.Content> nyit\u00f3 \u00e9s z\u00e1r\u00f3 tag-ek enn\u00e9l az egy tulajdons\u00e1gn\u00e1l elhagyhat\u00f3k:
                      <Button>\n    Hello WinUI App!\n</Button>\n
                      Vagy egy sorba \u00edrva:
                      <Button>Hello WinUI App!</Button>\n
                      Ez ismer\u0151s, l\u00e1ttuk a bevezet\u0151 p\u00e9ld\u00e1nkban: ez az \u00fan. Property CONTENT syntax alap\u00fa tulajdons\u00e1gmegad\u00e1s. Az elnevez\u00e9s is sugallja, hogy ezt az egy tulajdons\u00e1got a vez\u00e9rl\u0151 \"tartalmi\" r\u00e9sz\u00e9ben, contentj\u00e9ben is megadhatjuk. Nem minden vez\u00e9rl\u0151 eset\u00e9ben Content ezen kit\u00fcntetett tulajdons\u00e1g neve: StackPanel-n\u00e9l \u00e9s Grid-n\u00e9l Children a neve. Eml\u00e9kezz\u00fcnk vissza, ill. n\u00e9zz\u00fck meg a k\u00f3dot: ezeket m\u00e1r haszn\u00e1ltuk is: ugyanakkor, nem \u00edrtuk ki a StackPanel.Children, ill. Grid.Children XML elemeket a StackPanel, ill. Grid belsej\u00e9nek megad\u00e1sakor (de megtehett\u00fck volna!)

                  \u00cdrjuk vissza a Grid h\u00e1tter\u00e9t valami szimpatikusan egyszer\u0171re, vagy t\u00f6r\u00f6lj\u00fck ki a h\u00e1tt\u00e9rsz\u00edn megad\u00e1s\u00e1t.

                  "},{"location":"labor/3-felhasznaloi-felulet/#esemenykezeles","title":"Esem\u00e9nykezel\u00e9s","text":"

                  A XAML applik\u00e1ci\u00f3k esem\u00e9nyvez\u00e9relt alkalmaz\u00e1sok. Minden felhaszn\u00e1l\u00f3i interakci\u00f3r\u00f3l esem\u00e9nyek seg\u00edts\u00e9g\u00e9vel \u00e9rtes\u00fcl\u00fcnk, ezek hat\u00e1s\u00e1ra friss\u00edthetj\u00fck a fel\u00fcletet.

                  Most kezelj\u00fck le a gombon t\u00f6rt\u00e9n\u0151 kattint\u00e1st.

                  El\u0151k\u00e9sz\u00edt\u0151 l\u00e9p\u00e9sk\u00e9nt adjunk nevet a TextBlock vez\u00e9rl\u0151nknek, hogy a code-behind f\u00e1jlb\u00f3l hivatkozni tudjunk majd r\u00e1 a k\u00e9s\u0151bbiekben:

                  <TextBlock x:Name=\"recordTextBlock\" Text=\"Record\" Margin=\"10,0,0,0\" />\n

                  Az x:Name a XAML parsernek sz\u00f3l, \u00e9s ezen a n\u00e9ven fog l\u00e9trehozni egy tagv\u00e1ltoz\u00f3t az oszt\u00e1lyunkban, mely az adott vez\u00e9rl\u0151 referenci\u00e1j\u00e1t tartalmazza. Gondoljuk \u00e1t: mivel tagv\u00e1ltoz\u00f3 lesz, a code-behind f\u00e1jlban el tudjuk \u00e9rni, hiszen az egy \"partial r\u00e9sze\" ugyanazon oszt\u00e1lynak!

                  Elnevezett vez\u00e9rl\u0151k

                  Ne adjunk nevet azoknak a vez\u00e9rl\u0151knek, melyekre nem akarunk hivatkozni. (Szoktassuk magunkat arra, hogy csak arra hivatkozunk k\u00f6zvetlen\u00fcl, amire nagyon musz\u00e1j. Ebben az adatk\u00f6t\u00e9s is seg\u00edt majd.)

                  Kiv\u00e9tel: Ha nagyon bonyolult a vez\u00e9rl\u0151hierarchi\u00e1nk, seg\u00edthetnek a nevek a k\u00f3d \u00e1tl\u00e1that\u00f3bb\u00e1 t\u00e9tel\u00e9ben, mivel a Live Visual Tree ablakban megjelennek, illetve a gener\u00e1lt esem\u00e9nykezel\u0151-nevek is ehhez igazodnak.

                  Kezelj\u00fck le a gomb Click esem\u00e9ny\u00e9t, majd pr\u00f3b\u00e1ljuk ki a k\u00f3dot.

                  MainWindow.xaml-be
                  <Button Click=\"RecordButton_Click\">\n
                  MainWindow.xaml.cs-be
                  private void RecordButton_Click(object sender, RoutedEventArgs e)\n{\n    recordTextBlock.Text = \"Recording...\";\n}\n

                  Esem\u00e9nykezel\u0151k l\u00e9trehoz\u00e1sa

                  Ha az esem\u00e9nykezel\u0151kn\u00e9l nem a New Event Handler-t v\u00e1lasztjuk, hanem be\u00edrjuk k\u00e9zzel a k\u00edv\u00e1nt nevet, majd F12-t nyomunk, vagy a jobb gomb / Go to Definition-t v\u00e1lasztjuk, az esem\u00e9nykezel\u0151 legener\u00e1l\u00e1sra ker\u00fcl a code-behind f\u00e1jlban.

                  Az esem\u00e9nykezel\u0151nek k\u00e9t param\u00e9tere van: a k\u00fcld\u0151 objektum (object sender) \u00e9s az esem\u00e9ny param\u00e9tereit/k\u00f6r\u00fclm\u00e9nyeit tartalmaz\u00f3 param\u00e9ter (EventArgs e). N\u00e9zz\u00fck ezeket r\u00e9szletesebben:

                  • object sender: Az esem\u00e9ny kiv\u00e1lt\u00f3ja. Eset\u00fcnkben ez maga a gomb, Button-ra kasztolva haszn\u00e1lhatn\u00e1nk is. Ritk\u00e1n haszn\u00e1ljuk ez a param\u00e9tert.
                  • A m\u00e1sodik param\u00e9ter mindig EventArgs t\u00edpus\u00fa, vagy annak lesz\u00e1rmazottja (ez az esem\u00e9ny t\u00edpus\u00e1t\u00f3l f\u00fcgg), melyben az esem\u00e9ny param\u00e9tereit kapjuk meg. A Click esem\u00e9ny eset\u00e9ben ez RoutedEventArgs t\u00edpus\u00fa.

                  Esem\u00e9nyargumentumok

                  N\u00e9h\u00e1ny esem\u00e9nyargumentum t\u00edpus:

                  • RoutedEventArgs: pl. a Click esem\u00e9ny est\u00e9ben haszn\u00e1land\u00f3, ahogy a p\u00e9ld\u00e1nkban is volt. Az OriginalSource tulajdons\u00e1gban megkapjuk azt a vez\u00e9rl\u0151t, melyn\u00e9l el\u0151sz\u00f6r kiv\u00e1lt\u00f3dott az esem\u00e9ny.
                    • Megjegyz\u00e9s: a fenti esetben ez maga a gomb, de ha pl. egy eg\u00e9rlenyom\u00e1s esem\u00e9nyt (nem a Click, hanem PointerPressed) kezeln\u00e9nk pl. a StackPanel-en, akkor lehet, hogy az egyik gyerekelem\u00e9t kapn\u00e1nk meg, ha arra kattintottak.
                  • KeyRoutedEventArgs: pl. KeyDown (billenty\u0171 lenyom\u00e1sa) esem\u00e9ny eset\u00e9ben megkapjuk benne a lenyomott billenty\u0171t.
                  • PointerRoutedEventArgs: pl. PointerPressed (eg\u00e9r/toll lenyom\u00e1sa) esem\u00e9ny eset\u00e9ben haszn\u00e1ljuk, rajta kereszt\u00fcl lek\u00e9rdezhet\u0151k - t\u00f6bbek k\u00f6z\u00f6tt - a kattint\u00e1s koordin\u00e1t\u00e1i.

                  A XAML esem\u00e9nykezel\u0151k teljes eg\u00e9sz\u00e9ben a C# nyelv esem\u00e9nyeire \u00e9p\u00fclnek (event kulcssz\u00f3, l\u00e1sd el\u0151z\u0151 gyakorlat):

                  Pl. a

                  <Button Click=\"RecordButton_Click\">\n

                  erre k\u00e9pz\u0151dik le:

                  Button b = new Button();\nb.Click += RecordButton_Click;\n
                  "},{"location":"labor/3-felhasznaloi-felulet/#layout-elrendezes","title":"Layout, elrendez\u00e9s","text":"

                  A vez\u00e9rl\u0151k elrendez\u00e9s\u00e9t k\u00e9t dolog hat\u00e1rozza meg:

                  1. Layout (panel) vez\u00e9rl\u0151k \u00e9s kapcsolhat\u00f3 tulajdons\u00e1gaik (attached property)
                  2. Sz\u00fcl\u0151 vez\u00e9rl\u0151n bel\u00fcli \u00e1ltal\u00e1nos poz\u00edci\u00f3 tulajdons\u00e1gok (pl. marg\u00f3, igaz\u00edt\u00e1s f\u00fcgg\u0151legesen vagy v\u00edzszintesen)

                  Be\u00e9p\u00edtett layout vez\u00e9rl\u0151k p\u00e9ld\u00e1ul:

                  • StackPanel: elemek egym\u00e1s alatt vagy mellett
                  • Grid: defini\u00e1lhatunk egy r\u00e1csot, melyhez igazodnak az elemek
                  • Canvas: explicit poz\u00edcion\u00e1lhat\u00f3k az elemek az X \u00e9s Y koordin\u00e1t\u00e1juk megad\u00e1s\u00e1val
                  • RelativePanel: elemek egym\u00e1shoz k\u00e9pesti viszony\u00e1t hat\u00e1rozhatjuk meg k\u00e9nyszerekkel

                  A Grid-et fogjuk kipr\u00f3b\u00e1lni (\u00e1ltal\u00e1ban ezt haszn\u00e1ljuk az ablakunk/oldalunk alapelrendez\u00e9s\u00e9nek kialak\u00edt\u00e1s\u00e1ra). Egy olyan fel\u00fcletet k\u00e9sz\u00edt\u00fcnk el, melyen szem\u00e9lyeket lehet egy list\u00e1ba felvenni, nev\u00fck \u00e9s \u00e9letkoruk megad\u00e1s\u00e1val. A k\u00f6vetkez\u0151 elrendez\u00e9s kialak\u00edt\u00e1sa a v\u00e9gs\u0151 c\u00e9lunk:

                  P\u00e1r l\u00e9nyeges viselked\u00e9sbeli megk\u00f6t\u00e9s:

                  • Az ablak \u00e1tm\u00e9retez\u00e9sekor az \u0171rlap fix sz\u00e9less\u00e9g\u0171 legyen, \u00e9s maradjon k\u00f6z\u00e9pre igaz\u00edtva.
                  • Az Age sorban a + gombbal n\u00f6velhet\u0151, a - gombbal cs\u00f6kkenthet\u0151 az \u00e9letkor.
                  • Az Add gombbal a fent meghat\u00e1rozott adatokkal felveszi a szem\u00e9lyt az als\u00f3 list\u00e1ba (az \u00e1br\u00e1n az als\u00f3 list\u00e1ban k\u00e9t szem\u00e9ly adatai l\u00e1that\u00f3k).

                  Defini\u00e1ljunk a gy\u00f6k\u00e9r Grid-en 4 sort \u00e9s 2 oszlopot. Az els\u0151 oszlop\u00e1ba ker\u00fcljenek a c\u00edmk\u00e9k, a m\u00e1sodik oszlopba pedig a beviteli mez\u0151k. A megl\u00e9v\u0151 gombunkat is rakjuk a 3. sorba, \u00e9s \u00edrjuk \u00e1t a tartalm\u00e1t Add-ra, a k\u00f6r helyett pedig vegy\u00fcnk fel egy SymbolIcon-t. A 4. sorban pedig list\u00e1t helyezz\u00fcnk el, ami 2 oszlopot is foglaljon el.

                  <Grid x:Name=\"rootGrid\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n    <Grid.ColumnDefinitions>\n        <ColumnDefinition Width=\"Auto\" />\n        <ColumnDefinition Width=\"*\" />\n    </Grid.ColumnDefinitions>\n\n    <TextBlock Grid.Row=\"0\" Grid.Column=\"0\" Text=\"Name\"/>\n    <TextBox Grid.Row=\"0\" Grid.Column=\"1\" x:Name=\"tbName\"/>\n    <TextBlock Grid.Row=\"1\" Grid.Column=\"0\" Text=\"Age\"/>\n    <TextBox Grid.Row=\"1\" Grid.Column=\"1\" x:Name=\"tbAge\"/>\n\n    <Button Grid.Row=\"2\" Grid.Column=\"1\">\n        <StackPanel Orientation=\"Horizontal\">\n            <SymbolIcon Symbol=\"Add\" />\n            <TextBlock Text=\"Add\" Margin=\"5,0,0,0\"/>\n        </StackPanel>\n    </Button>\n\n    <ListView Grid.Row=\"3\" Grid.Column=\"0\" Grid.ColumnSpan=\"2\"/>\n</Grid>\n

                  A sor- \u00e9s oszlopdefin\u00edci\u00f3k eset\u00e9ben megadhatjuk, hogy az adott sor vegye fel a tartalm\u00e1nak a m\u00e9ret\u00e9t (Auto), vagy t\u00f6ltse ki a marad\u00e9k helyet (*), de ak\u00e1r fix sz\u00e9less\u00e9get is megadhatn\u00e1nk pixelben (Width tulajdons\u00e1g). Ha t\u00f6bb * is szerepel a defin\u00edci\u00f3kban, akkor azok ar\u00e1nyos\u00edthat\u00f3ak pl.: * \u00e9s * 1:1-es ar\u00e1nyt jelent, m\u00edg a * \u00e9s 3* 1:3-at.

                  A Grid.Row, Grid.Column \u00fagynevezett Attached Property-k (csatolt tulajdons\u00e1gok). Ez azt jelenti, hogy a vez\u00e9rl\u0151, melyn\u00e9l alkalmazzuk, nem rendelkezik ilyen tulajdons\u00e1ggal, \u00e9s ezt az inform\u00e1ci\u00f3t csak \u201ehozz\u00e1csatoljuk\u201d. Ez az inform\u00e1ci\u00f3 eset\u00fcnkben a Grid-nek lesz fontos, hogy el tudja helyezni a gyerekeit. A Grid.Row \u00e9s Grid.Column alap\u00e9rtelmezett \u00e9rt\u00e9ke a 0, teh\u00e1t ezt ki sem k\u00e9ne \u00edrnunk.

                  Imperat\u00edv UI le\u00edr\u00e1s

                  M\u00e1s UI keretrendszerekben, ahol imperat\u00edv a fel\u00fclet \u00f6ssze\u00e1ll\u00edt\u00e1sa, ezt egyszer\u0171en megoldj\u00e1k f\u00fcggv\u00e9nyparam\u00e9terekkel \u2013 pl.: myPanel.Add(new TextBox(), 0, 1).

                  M\u00e9g magyar\u00e1zatra szorulhat a ListView-n\u00e1l megadott Grid.ColumnSpan=\"2\" csatolt tulajdons\u00e1g: a ColumnSpan \u00e9s RowSpan azt hat\u00e1rozz\u00e1k meg, h\u00e1ny oszlopon illetve soron \"\u00e1t\u00edvel\u0151en\" helyezkedjen el a vez\u00e9rl\u0151. A p\u00e9ld\u00e1nkban a ListView mindk\u00e9t oszlopot kit\u00f6lti.

                  Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st (ha nem fordul a k\u00f3d, akkor t\u00f6r\u00f6lj\u00fck a code behind f\u00e1jlban a RecordButton_Click esem\u00e9nykezel\u0151t).

                  Jelen \u00e1llapot\u00e1ban a Grid kit\u00f6lti a teljes teret v\u00edzszintesen \u00e9s f\u00fcgg\u0151legesen is. Mi ennek az oka? A vez\u00e9rl\u0151k elrendez\u00e9s\u00e9nek egyik alapil\u00e9re a HorizontalAlignment \u00e9s VerticalAlignment tulajdons\u00e1guk. Ezek azt hat\u00e1rozz\u00e1k meg, hogy v\u00edzszintesen \u00e9s f\u00fcgg\u0151legesen hol helyezkedjen el az adott vez\u00e9rl\u0151 az \u0151t tartalmaz\u00f3 kont\u00e9nerben (vagyis a sz\u00fcl\u0151 vez\u00e9rl\u0151ben). A lehets\u00e9ges \u00e9rt\u00e9kek:

                  • VerticalAlignment: Top, Center, Bottom, Stretch (fel\u00fclre, k\u00f6z\u00e9pre, alulra igaz\u00edtva, vagy t\u00e9r kit\u00f6lt\u00e9se f\u00fcgg\u0151legesen)
                  • HorizontalAlignment: Left, Center, Right, Stretch (balra, k\u00f6z\u00e9pre, jobbra igaz\u00edtva, vagy t\u00e9r kit\u00f6lt\u00e9se v\u00edzszintesen)

                  (Megjegyz\u00e9s: a Stretch eset\u00e9ben sz\u00fcks\u00e9ges, hogy ne legyen a Height ill. Width tujadons\u00e1g megadva a vez\u00e9rl\u0151re.)

                  A Grid-\u00fcnknek nem adtunk meg HorizontalAlignment \u00e9s VerticalAlignment tulajdons\u00e1got, \u00edgy annak \u00e9rt\u00e9ke a Grid eset\u00e9ben alap\u00e9rtelmezett Stretch, emiatt a Grid mindk\u00e9t ir\u00e1nyban kit\u00f6lti a teret a sz\u00fcl\u0151 kont\u00e9ner\u00e9ben, vagyis az ablakban.

                  A fel\u00fclet\u00fcnk m\u00e9g nem \u00fagy n\u00e9z ki, mint amit szeretn\u00e9nk, finom\u00edtsunk kicsit a kin\u00e9zet\u00e9n. Az eszk\u00f6zlend\u0151 v\u00e1ltoz\u00e1sok:

                  • Ne t\u00f6ltse ki az eg\u00e9sz k\u00e9perny\u0151t a t\u00e1bl\u00e1zat, hanem legyen v\u00edzszintesen k\u00f6z\u00e9pen
                    • HorizontalAlignment=\"Center\"
                  • Legyen 300px sz\u00e9les
                    • Width=\"300\"
                  • Legyen a sorok k\u00f6z\u00f6tt 10px, az oszlopok k\u00f6z\u00f6tt 5px t\u00e1vols\u00e1g \u00e9s tartsunk 20px t\u00e1vols\u00e1got a kont\u00e9ner sz\u00e9l\u00e9t\u0151l
                    • RowSpacing=\"5\" ColumnSpacing=\"10\" Margin=\"20\"
                  • Igaz\u00edtsuk a c\u00edmk\u00e9ket (TextBlock) f\u00fcgg\u0151legesen k\u00f6z\u00e9pre
                    • VerticalAlignment=\"Center\"
                  • Igaz\u00edtsuk a gombot jobbra
                    • HorizontalAlignment=\"Right\"
                  • Tegy\u00fck beazonos\u00edthat\u00f3v\u00e1 a list\u00e1t
                    • BorderThickness=\"1\" \u00e9s BorderBrush=\"DarkGray\"
                  <Grid x:Name=\"rootGrid\"\n      Width=\"300\"\n      HorizontalAlignment=\"Center\"\n      Margin=\"20\"\n      RowSpacing=\"5\"\n      ColumnSpacing=\"10\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n    <Grid.ColumnDefinitions>\n        <ColumnDefinition Width=\"Auto\" />\n        <ColumnDefinition Width=\"*\" />\n    </Grid.ColumnDefinitions>\n\n    <TextBlock Grid.Row=\"0\" Grid.Column=\"0\" Text=\"Name\" VerticalAlignment=\"Center\"/>\n    <TextBox Grid.Row=\"0\" Grid.Column=\"1\" x:Name=\"tbName\" />\n    <TextBlock Grid.Row=\"1\" Grid.Column=\"0\" Text=\"Age\" VerticalAlignment=\"Center\"/>\n    <TextBox Grid.Row=\"1\" Grid.Column=\"1\" x:Name=\"tbAge\"/>\n\n    <Button Grid.Row=\"2\" Grid.Column=\"1\" HorizontalAlignment=\"Right\">\n        <StackPanel Orientation=\"Horizontal\">\n            <SymbolIcon Symbol=\"Add\"/>\n            <TextBlock Text=\"Add\" Margin=\"5,0,0,0\" />\n        </StackPanel>\n    </Button>\n\n    <ListView Grid.Row=\"3\"\n              Grid.Column=\"0\"\n              Grid.ColumnSpan=\"2\"\n              BorderThickness=\"1\"\n              BorderBrush=\"DarkGray\"/>\n</Grid>\n

                  B\u0151v\u00edts\u00fck ki m\u00e9g k\u00e9t gombbal az \u0171rlapunkat (\u00b1 gombok az \u00e9letkorhoz, l\u00e1sd kor\u00e1bbi anim\u00e1lt k\u00e9perny\u0151k\u00e9p):

                  • \u2019-\u2019: a TextBox bal oldal\u00e1n
                  • \u2019+\u2019 aTextBox jobb oldal\u00e1n

                  Ehhez vegy\u00fcnk fel a

                  <TextBox Grid.Row=\"1\" Grid.Column=\"1\" x:Name=\"tbAge\"/>\n

                  sor hely\u00e9re (azt kit\u00f6r\u00f6lve) egy 1 soros, 3 oszloppal rendelkez\u0151 Grid-et:

                  <Grid Grid.Row=\"1\" Grid.Column=\"1\" ColumnSpacing=\"5\">\n    <Grid.ColumnDefinitions>\n        <ColumnDefinition Width=\"Auto\" />\n        <ColumnDefinition Width=\"*\" />\n        <ColumnDefinition Width=\"Auto\" />\n    </Grid.ColumnDefinitions>\n\n    <Button Grid.Row=\"0\" Grid.Column=\"0\" Content=\"-\" />\n    <TextBox Grid.Row=\"0\" Grid.Column=\"1\" x:Name=\"tbAge\" />\n    <Button Grid.Row=\"0\" Grid.Column=\"2\" Content=\"+\" />\n</Grid>\n

                  T\u00f6bb layout vez\u00e9rl\u0151 egym\u00e1sba \u00e1gyaz\u00e1sa

                  Feltehetj\u00fck a k\u00e9rd\u00e9st, hogy mi\u00e9rt nem a k\u00fcls\u0151 Grid-ben vett\u00fcnk fel plusz oszlopokat \u00e9s sorokat (a ColumnSpan megfelel\u0151 alkalmaz\u00e1s\u00e1val a megl\u00e9v\u0151 vez\u00e9rl\u0151kre). Helyette egys\u00e9gbez\u00e1r\u00e1s elv\u00e9t k\u00f6vett\u00fck: az \u00fajonnan bevezetett vez\u00e9rl\u0151k alapvet\u0151en egybe tartoz\u00f3 elemek, \u00edgy \u00e1tl\u00e1that\u00f3bb megold\u00e1st kaptunk az\u00e1ltal, hogy k\u00fcl\u00f6n Grid vez\u00e9rl\u0151be tett\u00fck \u0151ket. A k\u00fcls\u0151 Grid b\u0151v\u00edt\u00e9se akkor lenne indokolt, ha sp\u00f3rolni akarn\u00e1nk a vez\u00e9rl\u0151k l\u00e9trehoz\u00e1s\u00e1val, teljes\u00edtm\u00e9nyokok miatt. Eset\u00fcnkben ez nem indokolt.

                  K\u00e9szen is vagyunk az egyszer\u0171 \u0171rlapunk kin\u00e9zet\u00e9nek kialak\u00edt\u00e1s\u00e1val.

                  "},{"location":"labor/3-felhasznaloi-felulet/#adatkotes","title":"Adatk\u00f6t\u00e9s","text":""},{"location":"labor/3-felhasznaloi-felulet/#binding","title":"Binding","text":"

                  A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben azt oldjuk meg, hogy az el\u0151bb elk\u00e9sz\u00edtett kis \u0171rlapon egy szem\u00e9ly adatait lehessen megadni, m\u00f3dos\u00edtani. Ehhez m\u00e1r el\u0151 van k\u00e9sz\u00edtve egy Person oszt\u00e1ly a projekt Models mapp\u00e1j\u00e1ban, n\u00e9zz\u00fcnk ezt meg.

                  public class Person\n{\n    public string Name { get; set; }\n    public int Age { get; set; }\n}\n

                  Azt itt l\u00e9v\u0151 k\u00e9t tulajdons\u00e1got akarjuk a TextBox vez\u00e9rl\u0151kh\u00f6z k\u00f6tni, ehhez adatk\u00f6t\u00e9st fogunk alkalmazni. Az ablakunk code-behind f\u00e1jlj\u00e1ban vezess\u00fcnk be egy propertyt, mely egy Person objektumra hivatkozik, \u00e9s adjunk ennek kezd\u0151\u00e9rt\u00e9ket a konstruktorban:

                  public Person NewPerson { get; set; }\n\npublic MainWindow()\n{\n    InitializeComponent();\n\n    NewPerson = new Person()\n    {\n        Name = \"Eric Cartman\",\n        Age = 8\n    };\n}\n

                  A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben a fenti NewPerson objektum

                  • Name tulajdons\u00e1g\u00e1t k\u00f6ss\u00fck hozz\u00e1 a tbName Textbox Text tulajdons\u00e1g\u00e1hoz
                  • Age tulajdons\u00e1g\u00e1t k\u00f6ss\u00fck hozz\u00e1 a tbAge Textbox Text tulajdons\u00e1g\u00e1hoz , m\u00e9gpedig adatk\u00f6t\u00e9ssel (data binding):

                  Text=\"{x:Bind NewPerson.Name}\"\nText=\"{x:Bind NewPerson.Age}\"\n
                  (a tbName ill. tbAge TextBox-ok soraiba vegy\u00fck fel a fenti 1-1 tulajdons\u00e1g be\u00e1ll\u00edt\u00e1st)

                  Fontos

                  Az adatk\u00f6t\u00e9snek az a l\u00e9nyege, hogy nem k\u00e9zzel, a code-behind f\u00e1jlb\u00f3l \u00e1ll\u00edtgatjuk a fel\u00fcleten megjelen\u0151 vez\u00e9rl\u0151k tulajdons\u00e1gait (eset\u00fcnkben a sz\u00f6veg\u00e9t), hanem \u00f6sszerendelj\u00fck/ \u00f6sszek\u00f6tj\u00fck a tulajdons\u00e1gokat a platform adatk\u00f6t\u00e9s mechanizmus\u00e1val. \u00cdgy azt is el\u00e9rhetj\u00fck, hogyha az egyik tulajdons\u00e1g megv\u00e1ltozik, akkor a m\u00e1sik is automatikusan v\u00e1ltozzon meg!

                  A Text=\"{x:Bind}\" szintaktika az \u00fagynevezett markup extension: ez speci\u00e1lis jelent\u00e9ssel rendelkezik a XAML feldolgoz\u00f3 sz\u00e1m\u00e1ra. Els\u0151sorban emiatt haszn\u00e1lunk XAML \u00e9s nem sima XML-t. Lehet\u0151s\u00e9g\u00fcnk van ak\u00e1r saj\u00e1t Markup Extension-t is k\u00e9sz\u00edteni, de ez nem tananyag.

                  Futtassuk! L\u00e1that\u00f3, hogy az adatk\u00f6t\u00e9s miatt automatikusan beker\u00fclt a k\u00e9t TextBox Text tulajdons\u00e1g\u00e1ba a NewPerson objektum (mint adatforr\u00e1s) Name \u00e9s Age tulajdons\u00e1gaiban megadott n\u00e9v \u00e9s \u00e9letkor.

                  "},{"location":"labor/3-felhasznaloi-felulet/#valtozasertesites","title":"V\u00e1ltoz\u00e1s\u00e9rtes\u00edt\u00e9s","text":"

                  Implement\u00e1ljuk a \u00b1 gombok Click esem\u00e9nykezel\u0151it.

                  <Button Grid.Row=\"1\" Grid.Column=\"0\" Content=\"-\" Click=\"DecreaseButton_Click\"/>\n<!-- ... -->\n<Button Grid.Row=\"1\" Grid.Column=\"2\" Content=\"+\" Click=\"IncreaseButton_Click\"/>\n
                  private void DecreaseButton_Click(object sender, RoutedEventArgs e)\n{\n    NewPerson.Age--;\n}\n\nprivate void IncreaseButton_Click(object sender, RoutedEventArgs e)\n{\n    NewPerson.Age++;\n}\n

                  A kor\u00e1bbi pontban bevezetett adatk\u00f6t\u00e9s miatt azt v\u00e1rn\u00e1nk, hogy ha a NewPerson adatforr\u00e1s Age tulajdons\u00e1g\u00e1t v\u00e1ltoztatjuk a fenti esem\u00e9nykezel\u0151kben, akkor a fel\u00fclet\u00fcnk\u00f6n a tbAge Textbox vez\u00e9rl\u0151nk ezt lek\u00f6veti. Pr\u00f3b\u00e1ljuk ki! Ez m\u00e9g egyel\u0151re nem m\u0171k\u00f6dik, ugyanis ehhez sz\u00fcks\u00e9g van m\u00e9g az INotifyPropertyChanged interf\u00e9sz megval\u00f3s\u00edt\u00e1s\u00e1ra is.

                  1. Implement\u00e1ljuk az INotifyPropertyChanged interf\u00e9szt a Person oszt\u00e1lyunkban. Ha adatk\u00f6t\u00fcnk ehhez az oszt\u00e1lyhoz, akkor a rendszer a PropertyChanged esem\u00e9nyre fog feliratkozni, ennek az esem\u00e9nynek a els\u00fct\u00e9s\u00e9vel tudjuk \u00e9rtes\u00edteni a bindingot, ha egy property megv\u00e1ltozott.

                    public class Person : INotifyPropertyChanged\n{\n    public event PropertyChangedEventHandler PropertyChanged;\n\n    private string name;\n    public string Name\n    {\n        get { return name; }\n        set\n        {\n            if (name != value)\n            {\n                name = value;\n                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));\n            }\n        }\n    }\n\n    private int age;\n    public int Age\n    {\n        get { return age; }\n        set\n        {\n            if (age != value)\n            {\n                age = value;\n                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Age)));\n            }\n        }\n    }\n}\n

                    Terjeng\u0151s a k\u00f3d?

                    A k\u00e9s\u0151bbiekben ezt a logik\u00e1t ki is szervezhetn\u00e9nk egy \u0151soszt\u00e1lyba, de ez m\u00e1r az MVVM mint\u00e1t vezetn\u00e9 el\u0151, mely egy k\u00e9s\u0151bbi tematik\u00e1hoz kapcsol\u00f3dik. Teh\u00e1t ne ijedj\u00fcnk meg ett\u0151l a kiss\u00e9 cs\u00fany\u00e1cska k\u00f3dt\u00f3l.

                  2. Az adatk\u00f6t\u00e9sen kapcsoljuk be a v\u00e1ltoz\u00e1s\u00e9rtes\u00edt\u00e9st a Mode OneWay-re t\u00f6rt\u00e9n\u0151 m\u00f3dos\u00edt\u00e1s\u00e1val, mivel az x:Bind alap\u00e9rtelmezett m\u00f3dja a OneTime, mely csak egyszeri adatk\u00f6t\u00e9st jelent.

                    Text=\"{x:Bind NewPerson.Age, Mode=OneWay}\"\n

                  Pr\u00f3b\u00e1ljuk ki! Az esem\u00e9nykezel\u0151k v\u00e1ltoztatj\u00e1k az adatforr\u00e1st (NewPerson), ennek hat\u00e1s\u00e1ra most m\u00e1r v\u00e1ltozik a fel\u00fclet is a megfelel\u0151en el\u0151k\u00e9sz\u00edtett adatk\u00f6t\u00e9s miatt.

                  "},{"location":"labor/3-felhasznaloi-felulet/#ketiranyu-adatkotes","title":"K\u00e9tir\u00e1ny\u00fa adatk\u00f6t\u00e9s","text":"

                  Az Age mint\u00e1j\u00e1ra, a Name tulajdons\u00e1gra vonatkoz\u00f3 adatk\u00f6t\u00e9st is \u00e1ll\u00edtsuk egyir\u00e1ny\u00fara:

                  Text=\"{x:Bind NewPerson.Name, Mode=OneWay}\"\n

                  Ind\u00edtsuk el az alkalmaz\u00e1st, majd ezt k\u00f6vet\u0151en tegy\u00fcnk egy t\u00f6r\u00e9spontot a Person oszt\u00e1ly Name tulajdons\u00e1g\u00e1nak setter\u00e9be (if (name != value) sor) , \u00e9s pr\u00f3b\u00e1ljuk, hogy vissza ir\u00e1nyba is m\u0171k\u00f6dik-e az adatk\u00f6t\u00e9s: ha megv\u00e1ltoztatjuk az egyik TextBox \u00e9rt\u00e9k\u00e9t, megv\u00e1ltozik-e a NewPerson objektum Name tulajdons\u00e1ga? G\u00e9pelj\u00fcnk valamit a Name-hez tartoz\u00f3 sz\u00f6vegdobozba, majd kattintsunk \u00e1t egy m\u00e1sik mez\u0151be: ekkor a Textbox tartalma \"v\u00e9gleges\u00edt\u0151dik\", tartalma vissza kellene \u00edr\u00f3djon az adatforr\u00e1sba, de m\u00e9gsem t\u00f6rt\u00e9nik meg, nem fut r\u00e1 a k\u00f3d a t\u00f6r\u00e9spontunkra.

                  Ez az\u00e9rt van \u00edgy, mert fentebb OneWay adatk\u00f6t\u00e9st haszn\u00e1ltunk, mely csak az adatforr\u00e1sb\u00f3l a fel\u00fcletre ir\u00e1ny\u00fa adatk\u00f6t\u00e9st jelent. Ha azt szeretn\u00e9nk, hogy az adatk\u00f6t\u00e9s a m\u00e1sik ir\u00e1nyba is m\u0171k\u00f6dj\u00f6n (vez\u00e9rl\u0151b\u0151l adatforr\u00e1sba), ahhoz TwoWay-re kell \u00e1ll\u00edtsuk az adatk\u00f6t\u00e9s m\u00f3dj\u00e1t. Ezt k\u00e9tir\u00e1ny\u0171 adatk\u00f6t\u00e9snek nevezz\u00fck.

                  Text=\"{x:Bind Name, Mode=TwoWay}\"\nText=\"{x:Bind Age, Mode=TwoWay}\"\n

                  Pr\u00f3b\u00e1ljuk ki! \u00cdgy az adatk\u00f6t\u00e9s m\u00e1r mindk\u00e9t ir\u00e1nyba m\u0171k\u00f6dik:

                  • Ha a forr\u00e1stulajdons\u00e1g (pl. NewPerson.Name) v\u00e1ltozik, akkor a vez\u00e9rl\u0151 k\u00f6t\u00f6tt tulajdons\u00e1ga (pl. TextBox.Text) ezzel szinkronban marad.
                  • Ha a c\u00e9l (vez\u00e9rl\u0151) tulajdons\u00e1g v\u00e1ltozik (pl. TextBox.Text), akkor az forr\u00e1stulajdons\u00e1g (pl. NewPerson.Name) ezzel szinkronban marad.
                  "},{"location":"labor/3-felhasznaloi-felulet/#listak","title":"List\u00e1k","text":"

                  A k\u00f6vetkez\u0151kben a list\u00e1s adatk\u00f6t\u00e9s alkalmaz\u00e1s\u00e1t fogjuk gyakorolni. Vegy\u00fck fel a Person-\u00f6k list\u00e1j\u00e1t a n\u00e9zet\u00fcnk code-behind f\u00e1jlj\u00e1ba, a konstruktor elej\u00e9n pedig adjunk neki kezd\u0151\u00e9rt\u00e9ket.

                  public List<Person> People { get; set; }\n\npublic MainWindow()\n{\n    InitializeComponent();\n\n    NewPerson = new Person()\n    {\n        Name = \"Eric Cartman\",\n        Age = 8\n    };\n\n    People = new List<Person>()\n    {\n      new Person() { Name = \"Peter Griffin\", Age = 40 },\n      new Person() { Name = \"Homer Simpson\", Age = 42 },\n    };\n}\n

                  Adatk\u00f6t\u00e9ssel \u00e1ll\u00edtsuk be a ListView vez\u00e9rl\u0151 ItemsSource tulajdons\u00e1g\u00e1n kereszt\u00fcl, milyen adatforr\u00e1sb\u00f3l dolgozzon.

                  <ListView Grid.Row=\"3\" Grid.ColumnSpan=\"2\" ItemsSource=\"{x:Bind People}\"/>\n

                  Pr\u00f3b\u00e1ljuk ki!

                  L\u00e1tjuk, hogy megjelent k\u00e9t elem a list\u00e1ban. Persze nem az van ki\u00edrva, amit mi szeretn\u00e9nk, de ezen k\u00f6nnyen seg\u00edthet\u00fcnk. Alap\u00e9rtelmezetten ugyanis a ListView ToString()-et h\u00edv a listaelemeken, ami ha nem defini\u00e1ljuk fel\u00fcl, akkor az oszt\u00e1ly t\u00edpus\u00e1nak FullName tulajdons\u00e1ga (vagyis a t\u00edpus neve).

                  \u00c1ll\u00edtsunk be a ListView-unk ItemTemplate tulajdons\u00e1g\u00e1t (a m\u00e1r j\u00f3l ismert property element syntax-szal), mely a listaelem megjelen\u00e9s\u00e9t adja meg egy sablon seg\u00edts\u00e9g\u00e9vel: eset\u00fcnkben legyen ez egycell\u00e1s Grid, ahol a TextBlock-ok a Person tulajdons\u00e1gait jelen\u00edtik meg, a nevet balra, az \u00e9letkort jobbra igaz\u00edtva.

                  <ListView Grid.Row=\"3\" Grid.ColumnSpan=\"2\" ItemsSource=\"{x:Bind People}\">\n    <ListView.ItemTemplate>\n        <DataTemplate x:DataType=\"model:Person\">\n            <Grid>\n                <TextBlock Text=\"{x:Bind Name}\" />\n                <TextBlock Text=\"{x:Bind Age}\" HorizontalAlignment=\"Right\" />\n            </Grid>\n        </DataTemplate>\n    </ListView.ItemTemplate>\n</ListView>\n

                  A DataTemplate egy olyan fel\u00fcletsablon, melyet a ListView (he megadjuk az ItemTemplate tulajdons\u00e1g\u00e1nak) minden elem\u00e9re alkalmazni fog a megjelen\u00edt\u00e9s sor\u00e1n.

                  Mivel az x:Bind ford\u00edt\u00e1s idej\u0171 adatk\u00f6t\u00e9s, ez\u00e9rt az adatok t\u00edpus\u00e1t is meg kell adnunk az adatsablonban az x:DataType attrib\u00fatummal. A fenti p\u00e9ld\u00e1ban a model:Person-t adtuk meg, vagyis azt szeretn\u00e9nk, hogy a model prefix a k\u00f3dunk HelloXaml.Models n\u00e9vter\u00e9re k\u00e9pz\u0151dj\u00f6n le (hiszen ebben van a Person oszt\u00e1ly). Ehhez a XAML f\u00e1jlunk elej\u00e9n a Window tag attrib\u00fatumaihoz fel kell vegy\u00fck a k\u00f6vetkez\u0151 n\u00e9vt\u00e9r deklar\u00e1ci\u00f3t is: xmlns:model=\"using:HelloXaml.Models\" (ezt k\u00f6vet\u0151en a model prefix haszn\u00e1lhat\u00f3 lesz). Ezt megtehetj\u00fck k\u00e9zzel, vagy a Visual Studio seg\u00edts\u00e9g\u00e9vel is: csak kattintsunk bele az al\u00e1h\u00fazott (hib\u00e1snak megjel\u00f6lt) model:Personsz\u00f6vegbe, majd kattintsuk a sor elej\u00e9n megjelen\u0151 l\u00e1mp\u00e1csk\u00e1n (vagy Ctrl + . billenty\u0171kombin\u00e1ci\u00f3), \u00e9s v\u00e1lasszuk ki a megjelen\u0151 \"Add xmlns using:HelloXaml.Models\" elemet.

                  Pr\u00f3b\u00e1ljuk ki! Most m\u00e1r j\u00f3l jelennek meg a list\u00e1ban az elemek.

                  Az Add gomb hat\u00e1s\u00e1ra rakjuk bele a list\u00e1ba az \u0171rlapon tal\u00e1lhat\u00f3 szem\u00e9ly adataival egy \u00faj Person m\u00e1solat\u00e1t, majd t\u00f6r\u00f6lj\u00fck ki az \u0171rlap adatait a NewPerson objektumunkban.

                  Ehhez vezess\u00fcnk be egy Click esem\u00e9nykezel\u0151t az Add gombunkra:

                  <Button ... Click=\"AddButton_Click\">\n
                  private void AddButton_Click(object sender, RoutedEventArgs e)\n{\n    People.Add(new Person()\n    { \n        Name = NewPerson.Name,\n        Age = NewPerson.Age,\n    });\n\n    NewPerson.Name = string.Empty;\n    NewPerson.Age = 0;\n}\n

                  Nem jelenik meg a list\u00e1ban az \u00faj elem, mert a ListView nem \u00e9rtes\u00fcl arr\u00f3l, hogy \u00faj elem ker\u00fclt a list\u00e1ba. Ezt k\u00f6nnyen orvosolhatjuk: a List<Persont>-t cser\u00e9lj\u00fck le ObservableCollection<Person>-re:

                  public ObservableCollection<Person> People { get; set; }\n

                  ObservableCollection<T>

                  Fontos, hogy itt nem maga a People tulajdons\u00e1g \u00e9rt\u00e9ke v\u00e1ltozott, hanem a List<Person> objektum tartalma, ez\u00e9rt nem az INotifyPropertyChanged interf\u00e9sz a megold\u00e1s, hanem az INotifyCollectionChanged interf\u00e9sz, melyet az ObservableCollection implement\u00e1l.

                  Teh\u00e1t m\u00e1r k\u00e9t v\u00e1ltoz\u00e1skezel\u00e9st t\u00e1mogat\u00f3 interf\u00e9szt ismer\u00fcnk \u00e9s haszn\u00e1lunk, melyek az adatk\u00f6t\u00e9st t\u00e1mogatj\u00e1k: INotifyPropertyChanged \u00e9s INotifyCollectionChanged.

                  "},{"location":"labor/3-felhasznaloi-felulet/#kitekintes-klasszikus-binding","title":"Kitekint\u00e9s: Klasszikus Binding","text":"

                  Az adatk\u00f6t\u00e9snek a klasszikus form\u00e1j\u00e1t a Binding markup extension jelenti.

                  A legfontosabb k\u00fcl\u00f6nbs\u00e9gek az x:Bind-hoz k\u00e9pest:

                  • A Binding alap\u00e9rtelmezett m\u00f3dja a OneWay \u00e9s nem a OneTime: teh\u00e1t figyeli a v\u00e1ltoz\u00e1sokat alap\u00e9rtelmezetten, m\u00edg az x:Bind-n\u00e9l ezt explicit meg kell adni.
                  • A Binding alap\u00e9rtelmezetten a DataContext-b\u0151l dolgozik, de lehet\u0151s\u00e9g van \u00e1ll\u00edtani az adatk\u00f6t\u00e9s forr\u00e1s\u00e1t. M\u00edg az x:Bind alap\u00e9rtelmezetten a n\u00e9zet\u00fcnk oszt\u00e1ly\u00e1b\u00f3l (xaml.cs) k\u00f6t.
                  • A Binding fut\u00e1sid\u0151ben dolgozik reflection seg\u00edts\u00e9g\u00e9vel, \u00edgy egyr\u00e9szt nem kapunk ford\u00edt\u00e1s idej\u0171 hib\u00e1kat, ha valamit el\u00edrtunk volna, m\u00e1sr\u00e9szt pedig sok adatk\u00f6t\u00e9s (1000-es nagys\u00e1grend) jelent\u0151sen lass\u00edthatja az alkalmaz\u00e1sunkat.
                  • Az x:Bind ford\u00edt\u00e1s idej\u0171, \u00edgy a ford\u00edt\u00f3 ellen\u0151rzi, hogy a megadott tulajdons\u00e1gok l\u00e9teznek-e. Adatsablonokban nyilatkozni kell a DataTemplate megad\u00e1sa sor\u00e1n, hogy az milyen adatokon fog dolgozni az x:DataType attrib\u00fatummal.
                  • Az x:Bind eset\u00e9ben lehet\u0151s\u00e9g van met\u00f3dusokat is k\u00f6tni, m\u00edg a Binding-n\u00e9l csak konvertereket lehet haszn\u00e1lni. F\u00fcggv\u00e9nyek k\u00f6t\u00e9se eset\u00e9n a v\u00e1ltoz\u00e1s\u00e9rtes\u00edt\u00e9s a param\u00e9terek v\u00e1ltoz\u00e1s\u00e1ra is m\u0171k\u00f6dik.

                  Aj\u00e1nl\u00e1s

                  \u00d6k\u00f6lszab\u00e1lyk\u00e9nt elmondhat\u00f3, hogy pr\u00f3b\u00e1ljunk prefer\u00e1ltan x:Bind-ot haszn\u00e1lni, mert gyorsabb, \u00e9s ford\u00edt\u00e1s idej\u0171 hib\u00e1kat kapunk, viszont ha valami\u00e9rt probl\u00e9m\u00e1ba \u00fctk\u00f6zn\u00e9nk az x:Bind-dal, akkor Binding-ra \u00e9rdemes \u00e1tt\u00e9rni.

                  "},{"location":"labor/3-felhasznaloi-felulet/index_ger/","title":"3. Entwurf der Benutzeroberfl\u00e4che","text":""},{"location":"labor/3-felhasznaloi-felulet/index_ger/#das-ziel-der-ubung","title":"Das Ziel der \u00dcbung","text":"

                  Das Ziel der \u00dcbung ist, die Grundlagen der Entwicklung von Thick-Client-Anwendungen unter Verwendung der deklarativen XAML-Oberfl\u00e4chebeschreibungstechnologie zu erlernen. Die hier gelernten Grundlagen gelten f\u00fcr alle XAML-Dialekte (WinUI, WPF, UWP, Xamarin.Forms, MAUI) oder k\u00f6nnen auf sehr \u00e4hnliche Weise angewendet werden, aber wir werden XAML in der heutigen \u00dcbung speziell \u00fcber das WinAppSDK / WinUI 3-Framework verwenden.

                  "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#voraussetzungen","title":"Voraussetzungen","text":"

                  Die f\u00fcr die Durchf\u00fchrung des Labors ben\u00f6tigten Werkzeuge:

                  • Betriebssystem Windows 10 oder Windows 11 (Linux und macOS nicht geeignet)
                  • Visual Studio 2022
                  • Windows Desktop Entwicklung Workload

                  "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#losung","title":"L\u00f6sung","text":"Laden Sie die fertige L\u00f6sung herunter

                  Es ist wichtig, dass Sie sich w\u00e4hrend des Praktikums an die Anleitung halten. Es ist verboten (und sinnlos), die fertige L\u00f6sung herunterzuladen. Allerdings kann es bei der anschlie\u00dfenden Selbstein\u00fcbung n\u00fctzlich sein, die fertige L\u00f6sung zu \u00fcberpr\u00fcfen, daher stellen wir sie zur Verf\u00fcgung.

                  Die L\u00f6sung ist auf GitHub im megoldas-Zweig verf\u00fcgbar. Der einfachste Weg, es herunterzuladen, ist, mit dem git clone-Befehl von der Kommandozeile aus zu klonen:

                  git clone https://github.com/bmeviauab00/lab-xaml-kiindulo -b megoldas

                  Sie m\u00fcssen Git auf Ihrem Rechner installiert haben, weitere Informationen hier.

                  "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#ursprungliches-projekt","title":"Urspr\u00fcngliches Projekt","text":"

                  In der ersten Aufgabe werden wir die Umgebung einrichten, in der wir die Funktionalit\u00e4t der XAML-Sprache und des WinUI-Frameworks untersuchen werden. Das anf\u00e4ngliche Projekt k\u00f6nnte mit Visual Studio erstellt werden (WinUI 3 Projekt, Blank App, Packaged (WinUI 3 in Desktop) type), aber um den Ablauf der \u00dcbung zu vereinfachen, werden wir das vorgefertigte Projekt verwenden.

                  Wir k\u00f6nnen das Projekt auf unseren Rechner klonen, mit dem folgenden Befehl:

                  git clone https://github.com/bmeviauab00/lab-xaml-kiindulo.git\n

                  \u00d6ffnen wir HelloXaml.sln.

                  Schauen wir uns an, welche Dateien in dem Projekt enthalten sind:

                  • App
                    • Zwei Dateien App.xaml und App.xaml.cs(sp\u00e4ter zu kl\u00e4rende zwei Dateien geh\u00f6ren dazu)
                    • Eintrittspunkt in die Anwendung: OnLaunched \u00fcberschriebene Method in App.xaml.cs
                    • In unserem Fall initialisieren wir das einzige Fenster der Anwendung hier MainWindow
                  • MainWindow
                    • Zu dem Hauptfenster unserer Anwendung geh\u00f6rende .xaml und .xaml.cs Dateien.
                  Zus\u00e4tzliche L\u00f6sungselemente

                  Die urspr\u00fcngliche VS-L\u00f6sung enth\u00e4lt auch die folgenden Elemente:

                  • Dependencies
                    • Frameworks
                      • Microsoft.AspNetCore.App: .NET SDK-Metapaket (verweist auf Microsoft .NET und SDK-Basispakete)
                      • Windows-spezifisches .NET SDK
                    • Packages
                      • Windows SDK Build Tools
                      • WindowsAppSDK
                  • Assets
                    • Anwendungslogos
                  • app.manifest, Package.appxmanifest
                    • Eine XML-Datei mit den Metadaten der Anwendung, in der wir, unter anderem, Logos angeben oder, wie bei Android, den Zugriff auf sicherheitskritische Systemressourcen anfordern k\u00f6nnen.

                  Starten wir die Anwendung!

                  "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#xaml-einfuhrung","title":"XAML-Einf\u00fchrung","text":"

                  Die Schnittstelle wird in einer XML-basierten Beschreibungssprache, XAML (ausgesprochen: zem\u00f6l), beschrieben.

                  Grafische Designeroberfl\u00e4che

                  Bei einigen XAML-Dialekten (z.B.: WPF) steht auch ein grafisches Designer-Tool f\u00fcr die Gestaltung der Oberfl\u00e4che zur Verf\u00fcgung, das jedoch in der Regel eine weniger effiziente XAML-Beschreibung erzeugt. Dar\u00fcber hinaus unterst\u00fctzt Visual Studio bereits Hot Reload f\u00fcr XAML, so dass die Anwendung w\u00e4hrend der Bearbeitung der XAML nicht angehalten werden muss und die \u00c4nderungen sofort in der laufenden Anwendung sichtbar sind. Daher gibt es f\u00fcr WinUI keine Designer-Unterst\u00fctzung mehr in Visual Studio. Die Erfahrung hat gezeigt, dass es Grenzen gibt, wobei \"gr\u00f6\u00dfere\" \u00c4nderungen einen Neustart der Anwendung erfordern.

                  "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#grundlagen-der-xaml-sprache","title":"Grundlagen der XAML-Sprache","text":"

                  Die XAML-Sprache:

                  • Sprache f\u00fcr Objektinstanziierung
                  • Standard-XML
                  • XML-Elemente/Tags: instanziieren Objekte, deren Klassen Standard-.NET-Klassen sind
                  • XML-Attribute: Eigenschaften (dependency properties) werden festgelegt
                  • Deklarativ

                  Schauen wir uns die von der Projektvorlage generierte XAML (MainWindow.xaml) an. Wir k\u00f6nnen sehen, dass f\u00fcr jedes Steuerelement in der XAML ein XML-Element/Tag erstellt wurde. Und die Eigenschaften der Steuerelementen werden auf die Tags der Steuerelementen gesetzt. Z.B. HorizontalAlignment: Ausrichtung innerhalb eines Containers (in unserem Fall Fenster). Steuerelemente k\u00f6nnen andere Steuerelemente enthalten, wodurch ein Baum von Steuerelementen entsteht.

                  Schauen wir uns MainWindow.xamlgenauer an:

                  • Namensr\u00e4ume auf dem Root-Tag: definieren, welche Tags und Attribute in XML verwendet werden k\u00f6nnen
                    • Standardnamensraum: Namensraum der XAML-Elemente/Steuerelemente (z. B. Button, TextBox usw.)
                    • x Namensraum: XAML-Parser-Namensraum (z. B.: x:Class, x:Name)
                    • Andere beliebige Namespaces k\u00f6nnen referenziert werden
                  • Window Wurzelelement:
                    • Auf der Grundlage unseres Fensters/unserer Seite erstellen wir eine .NET-Klasse, die von der Klasse Window abgeleitet ist.
                    • Der Name unserer abgeleiteten Klasse wird durch das Attribut x:Class definiert: Auf der Grundlage von x:Class=\"HelloXaml.MainWindow\" wird eine Klasse namens MainWindow im Namensraum HelloXaml erstellt.
                    • Dies ist eine Teilklasse, die \"andere H\u00e4lfte\" der Klasse befindet sich in der Code-Behind-Datei (MainWindow.xaml.cs) f\u00fcr das Fenster/die Seite. Siehe n\u00e4chster Punkt.
                  • Code-Behind-Datei (MainWindow.xaml.cs):
                    • Die andere \"H\u00e4lfte\" unserer partiellen Klasse: \u00dcberpr\u00fcfen wir, ob der Name und der Namensraum der Klasse hier derselbe ist wie in der .xaml-Datei (partielle Klasse!).
                    • Hier werden u.a. Ereignishandler und Hilfsfunktionen untergebracht.
                    • this.InitializeComponent(); muss immer im Konstruktor aufgerufen werden, er liest die XAML zur Laufzeit ein, er initialisiert den Inhalt des Fensters/der Seite (d.h. die in der XAML-Datei angegebenen Controls mit den dort definierten Eigenschaften).

                  L\u00f6schen wir den Inhalt von Window und den Ereignishandler aus der Code-Behind-Datei (FunktionmyButton_Click ). Jetzt werden wir XAML manuell schreiben, um die Oberfl\u00e4che0 zu erstellen. F\u00fcgen wir ein Gridzu Windowhinzu, mit dem wir sp\u00e4ter ein Tabellenlayout erstellen k\u00f6nnen:

                  <?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Window\n    x:Class=\"HelloXaml.MainWindow\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:local=\"using:HelloXaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\">\n\n    <Grid>\n\n    </Grid>\n</Window>\n

                  F\u00fchren wir die Anwendung aus (z. B. mit F5 ). Die Grid f\u00fcllt das gesamte Fenster aus, ihre Farbe ist dieselbe wie die Hintergrundfarbe des Fensters, so dass man sie mit dem Auge nicht mehr unterscheiden kann.

                  In den folgenden Aufgaben lassen wir die Anwendung laufen, damit wir die \u00c4nderungen, die wir an der Schnittstelle vorgenommen haben, sofort sehen k\u00f6nnen.

                  Hot Reload Limitations

                  Beachten wir die Einschr\u00e4nkungen von Hot Reload: Wenn eine \u00c4nderung nicht in der laufenden Anwendung erscheinen soll, m\u00fcssen wir die Anwendung neu starten!

                  "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#objektinstanzen-und-ihre-eigenschaften","title":"Objektinstanzen und ihre Eigenschaften","text":"

                  Sehen wir uns an, wie wir Objekte auf der Grundlage von XAML instanziieren und die Eigenschaften dieser Objekte festlegen k\u00f6nnen.

                  F\u00fcgen wir Buttoninnerhalb der Grid hinzu. Die Eigenschaft Content wird verwendet, um den Text des Knopfs, genauer gesagt seinen Inhalt, anzugeben.

                  <Button Content=\"Hello WinUI App!\"/>\n

                  Dadurch wird zur Laufzeit ein Objekt Button an der Stelle erzeugt, an der es deklariert ist, und die Eigenschaft Content auf \"Hello WinUI App!\" gesetzt. Dies h\u00e4tte in C# in der Code-Behind-Datei wie folgt geschehen k\u00f6nnen (was jedoch zu weniger lesbarem Code f\u00fchren w\u00fcrde):

                  // z.B. am Ende des Konstruktors geschrieben:\n\nButton b = new Button();\nb.Content = \"Hello WinUI App!\";\nrootGrid.Children.Add(b); \n// F\u00fcr die vorherige Zeile sollte das Attribut x:Name=\"rootGrid\" des Grids in der XAML-Datei angegeben\n// werden, um das Grid mit dem Namen rootGrid aus dem Code-Behind-Datai zu erreichen.\n

                  Dieses Beispiel verdeutlicht sehr gut, dass XAML im Grunde eine Objektinstanziirungs-Sprache ist und das Setzen von Eigenschaften von Objekten unterst\u00fctzt.

                  Die Eigenschaft Content ist eine Besonderheit: Sie kann nicht nur in einem XML-Attribut, sondern auch innerhalb eines Tags (XML-Element) angegeben werden.

                  <Button>Hello WinUI App!</Button>\n

                  Sogar! Wir k\u00f6nnen nicht nur eine Beschriftung auf die Taste setzen, sondern auch jedes andere Element, das wir m\u00f6chten. F\u00fcgen wir zum Beispiel einen roten Kreis ein. Der Kreis ist 10 Pixel breit, 10 Pixel hoch und die Farbe (Fill) ist rot.

                  <Button>\n    <Ellipse Width=\"10\" Height=\"10\" Fill=\"Red\" />\n</Button>\n

                  Dies konnte mit fr\u00fcheren .NET UI-Technologien (z. B. Windows Forms) nicht so einfach erreichen.

                  Neben dem roten Kreis steht nun Record (um den Sinn der roten Kreistaste zu verdeutlichen). Die Taste kann nur ein untergeordnetes Element haben, daher m\u00fcssen wir den Kreis und den Text (TextBlock) in ein Layout-Steuerelement (z. B. ein StackPanel) einf\u00fcgen. F\u00fcgen wir au\u00dferdem einen linken Rand zu TextBlockhinzu, damit sie sich nicht ber\u00fchren.

                  <Button>\n    <StackPanel Orientation=\"Horizontal\">\n        <Ellipse Width=\"10\" Height=\"10\" Fill=\"Red\" />\n        <TextBlock Text=\"Record\" Margin=\"10,0,0,0\" />\n    </StackPanel>\n</Button>\n

                  StackPanel ist ein einfaches Layout-Panel f\u00fcr die Anordnung von Steuerelementen: Die darin enthaltenen Steuerelemente werden nebeneinander angeordnet, wenn Horizental Orientation angegeben ist, und untereinander, wenn Vertical Orientation angegeben ist. In unserem Beispiel legen wir also einfach die beiden Steuerelemente nebeneinander.

                  Das Ergebnis ist:

                  XAML-Vektorgrafik-Controller

                  Es ist wichtig zu beachten, dass die meisten XAML-Controller Vektorgrafiken sind. Diese Taste sieht bei jeder DPI oder Vergr\u00f6\u00dferung genauso scharf aus (keine \"Verpixelung\").

                  Es gibt drei Optionen f\u00fcr die Angabe von Eigenschaften von XAML-instanziierten Steuerelementen (von denen wir einige bereits verwendet haben):

                  • Property ATTRIBUTE syntax
                  • Property ELEMENT syntax
                  • Property CONTENT syntax

                  Schauen wir uns diese Optionen nun genauer an:

                  1. Property ATTRIBUTE syntax. Wir haben sie bereits in unserem allerersten Beispiel verwendet:

                    <Button Content=\"Hello WinUI App!\"/>\n

                    Der Name kommt daher, dass die Eigenschaft als XML-Attribut angegeben wird. Da XML-Attribute nur Strings sein k\u00f6nnen, k\u00f6nnen sie nur f\u00fcr den Zugriff auf einfache Zahlen-, String- usw. Werte in Stringform oder auf Mitgliedsvariablen und Ereignishandler, die in einer Code-Behind-Datei definiert sind, verwendet werden. Wir k\u00f6nnen aber auch \"komplexe\" Objekte mit Hilfe von Typkonvertern angeben. Wir werden nicht viel dar\u00fcber reden, aber wir benutzen die eingebauten Typkonverter sehr oft, praktisch \"instinktiv\". Beispiel:

                    F\u00fcgen wir eine Hintergrundfarbe zu Gridhinzu:

                    <Grid Background=\"Azure\">\n

                    Oder wir k\u00f6nnen es in Hexadezimal angeben:

                    <Grid Background=\"#FFF0FFFF\">\n

                    Der Rand (Margin) ist ebenfalls ein zusammengesetzter Wert, wobei der zugeh\u00f6rige Typkonverter durch ein Komma (oder ein Leerzeichen) getrennt ist und Werte f\u00fcr die vier Seiten (links, oben, rechts, unten) erwartet werden. Wir haben es bereits f\u00fcr unseren TextBlock mit Record verwendet. Hinweis: wir k\u00f6nnen eine einzige Zahl f\u00fcr den Rand angeben, die dann f\u00fcr alle vier Seiten gleich ist.

                  2. Property ELEMENT syntax. Es erm\u00f6glicht uns, eine Eigenschaft auf ein komplex instanziiertes/parametrisiertes Objekt zu setzen, ohne Typkonverter zu verwenden. Schauen wir uns das anhand eines Beispiels an.

                    • Im obigen Beispiel wird durch die Einstellung der Eigenschaft Background auf Azure tats\u00e4chlich ein SolidColorBrushmit der Farbe hellblau erstellt. Dies kann ohne Verwendung eines Typkonverters wie folgt angegeben werden:
                    <Grid>\n    <Grid.Background>\n        <SolidColorBrush Color=\"Azure\" />\n    </Grid.Background>\n    ...\n

                    Damit wird die Eigenschaft Grid Background auf die angegebene SolidColorBrushgesetzt. Dabei handelt es sich um die so genannte \"property element syntax\"-basierte Eigenschafts\u00fcbermittlung.

                    • Der Name kommt daher, dass die Eigenschaft in Form eines XML-Elements (und nicht eines XML-Attributs) angegeben wird.
                    • Hier erstellt <Grid.Background> keine Objektinstanz, sondern setzt den Wert der angegebenen Eigenschaft (in diesem Fall Background) auf die entsprechende Objektinstanz (in diesem Fall SolidColorBrush). Sie erkennen dies an dem Punkt im Namen des XML-Elements.
                    • Dadurch erh\u00e4lt man eine \"expansivere\" Formeigenschaft, jedoch mit voller Flexibilit\u00e4t.

                    Ersetzen wir SolidColorBrushdurch eine Brush mit Farb\u00fcbergang (LinearGradientBrush):

                    <Grid>\n    <Grid.Background>\n        <LinearGradientBrush>\n            <LinearGradientBrush.GradientStops>\n                <GradientStop Color=\"Black\" Offset=\"0\" />\n                <GradientStop Color=\"White\" Offset=\"1\" />\n            </LinearGradientBrush.GradientStops>\n        </LinearGradientBrush>\n    </Grid.Background>\n    ...\n

                    F\u00fcr LinearGradientBrush gibt es keinen Typkonverter, er kann nur mit der Elementsyntax angegeben werden!

                    Es ist eine Frage, wie ist es m\u00f6glich, dass die Background Eigenschaft des Grid Steuerelements sowohl SolidColorBrush und LinearGradientBrush Pinsel haben k\u00f6nnte? Die Antwort ist ganz einfach: Polymorphismus macht dies m\u00f6glich:

                    • Die Klassen SolidColorBrush und LinearGradientBrush sind beide aus der eingebauten Klasse Brush abgeleitet.
                    • Die Eigenschaft Background ist eine Eigenschaft des Typs Brush, so dass aufgrund der Polymorphie jeder Nachkomme dieser Eigenschaft verwendet werden kann.
                    Note
                    • Wenn in den obigen Beispielen Color (Farbe) angegeben ist, z. B. Color=\"Azure\", erstellt der Typkonverter auch eine blaue Color -Instanz von Azure. So w\u00fcrde unser vorheriges Beispiel, das auf SolidColorBrush basiert, vollst\u00e4ndig erkl\u00e4rt aussehen:
                      <Grid>\n    <Grid.Background>\n        <SolidColorBrush>\n            <SolidColorBrush.Color>\n                <Color>#FFF0FFFF</Color>\n            </SolidColorBrush.Color>\n        </SolidColorBrush>\n    </Grid.Background>\n    ...\n
                    • Wo unterst\u00fctzt, lohnt es sich, die Vorteile von Typkonvertern zu nutzen und die Attributsyntax zu verwenden, um eine ausf\u00fchrliche XAML-Beschreibung zu vermeiden.
                    • Bei Werttypen (struct), wie z. B. Color, muss der Wert bei der Instanziierung des Objekts (\"Konstruktorzeit\") angegeben werden, d. h. hier k\u00f6nnen wir die Eigenschaften nicht separat festlegen, sondern m\u00fcssen sich auf die Typkonverter verlassen.
                  3. Property CONTENT syntax. Um das besser zu verstehen, schauen wir uns die drei M\u00f6glichkeiten an, die Content Eigenschaft einer Taste auf einen Text zu setzen (wir m\u00fcssen das nicht im Labor machen, schauen wir es sich einfach zusammen in diesem Leitfaden an):

                    • Property attribute syntax (bereits verwendet):
                      <Button Content=\"Hello WinUI App!\"/>\n
                    • Richten wir sie mit der property element syntax ein, die wir im vorigen Abschnitt gelernt haben:
                      <Button>\n    <Button.Content>\n    Hello WinUI App!\n    </Button.Content>\n</Button>\n
                    • Jedes Steuerelement kann f\u00fcr sich selbst eine spezielle Eigenschaft \"Content\" definieren, f\u00fcr die die \u00f6ffnenden und schlie\u00dfenden Tags nicht gedruckt werden m\u00fcssen. Das hei\u00dft, die \u00f6ffnenden und schlie\u00dfenden Tags <Button.Content>, die im vorigen Beispiel verwendet wurden, k\u00f6nnen f\u00fcr diese eine Eigenschaft weggelassen werden:
                      <Button>\n    Hello WinUI App!\n</Button>\n
                      Oder in einer einzigen Zeile geschrieben werden:
                      <Button>Hello WinUI App!</Button>\n
                      Dies ist bekannt, wir haben es in unserem Einf\u00fchrungsbeispiel gesehen: dies ist die so genannte Property CONTENT syntax-basierte Eigenschaftsdeklaration. Der Name deutet auch darauf hin, dass diese eine Eigenschaft im \"Content\"-Teil des Steuerelements angegeben werden kann. Nicht alle Steuerelemente haben Content als Namen f\u00fcr diese besondere Eigenschaft: StackPanelund Gridhaben Children als Namen. Erinnern wir uns, oder schauen wir uns den Code an: wir haben diese bereits verwendet: allerdings haben wir die XML-Elemente StackPanel.Children oder Grid.Children nicht ausgeschrieben, wenn wir das Innere von StackPanel oder Grid angegeben haben (aber wir h\u00e4tten es tun k\u00f6nnen!)

                  \u00c4ndern wir den Hintergrund von Grid wieder in etwas sympathisch Einfaches, oder l\u00f6schen wir die Hintergrundfarbe.

                  "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#ereignis-management","title":"Ereignis-Management","text":"

                  XAML-Anwendungen sind ereignisgesteuerte Anwendungen. Alle Benutzerinteraktionen werden durch Ereignisse gemeldet, die zur Aktualisierung der Oberfl\u00e4che verwendet werden k\u00f6nnen.

                  Jetzt geht es um das Klicken auf die Taste.

                  Als vorbereitenden Schritt geben wir unserem TextBlock Steuerelement einen Namen, damit wir sp\u00e4ter in der Code-Behind-Datei darauf verweisen k\u00f6nnen:

                  <TextBlock x:Name=\"recordTextBlock\" Text=\"Record\" Margin=\"10,0,0,0\" />\n

                  Die x:Name ist f\u00fcr den XAML-Parser und erstellt eine Member-Variable in unserer Klasse mit diesem Namen, die den Verweis auf das angegebene Steuerelement enth\u00e4lt. Denken wir dar\u00fcber nach: da es sich um eine Membervariable ist, k\u00f6nnen wir es in der Code-Behind-Datei erreichen, da es sich einen \"partiellen Teil\" der gleichen Klasse ist!

                  Benannte Steuerelemente

                  Benennen wir keine Steuerelemente, auf die wir nicht verweisen wollen. (Wir sollten uns angew\u00f6hnen, nur auf das zu verweisen, was wir wirklich brauchen. Auch die Datenverkn\u00fcpfung ist hilfreich)

                  Eine Ausnahme: Wenn wir eine sehr komplexe Kontrollhierarchie haben, k\u00f6nnen Namen helfen, den Code transparenter zu machen, da sie im Live Visual Tree-Fenster erscheinen und die generierten Ereignishandlernamen ebenfalls daran ausgerichtet sind.

                  Behandeln wir das Ereignis Click der Taste und probieren wir dann den Code aus.

                  MainWindow.xaml
                  <Button Click=\"RecordButton_Click\">\n
                  MainWindow.xaml.cs
                  private void RecordButton_Click(object sender, RoutedEventArgs e)\n{\n    recordTextBlock.Text = \"Recording...\";\n}\n

                  Erstellen von Ereignishandlern

                  Wenn wir f\u00fcr die Ereignishandler nicht New Event Handler w\u00e4hlen, sondern manuell den gew\u00fcnschten Namen eingeben und F12dr\u00fccken oder Rechtsklick / Go to Definition w\u00e4hlen, wird der Ereignishandler in der Code-Behind-Datei generiert.

                  Der Ereignishandler hat zwei Parameter: das sendende Objekt (object sender) und den Parameter, der die Parameter/Bedingungen des Ereignisses enth\u00e4lt (EventArgs e). Schauen wir uns diese im Detail an:

                  • object sender: Der Ausl\u00f6ser des Ereignisses. In diesem Fall handelt es sich um die Taste selbst, die unter Buttonzu finden ist. Wir verwenden diesen Parameter nur selten.
                  • Der zweite Parameter ist immer vom Typ EventArgs oder dessen Nachkomme (je nach Art des Ereignisses), in dem die Parameter des Ereignisses zur\u00fcckgegeben werden. F\u00fcr das Ereignis Click ist dies der Typ RoutedEventArgs.

                  Ereignisargumente

                  Einige Ereignisargumenttypen:

                  • routedEventArgs\": wird z. B. im Falle des Ereignisses \"Click\" verwendet, wie in unserem Beispiel. In der Eigenschaft \"OriginalSource\" wird das Steuerelement angegeben, in dem das Ereignis zuerst ausgel\u00f6st wurde.
                    • Beachten wir, dass es im obigen Fall die Taste selbst ist, aber wenn wir ein Mausklick-Ereignis (nicht Click, sondern PointerPressed) auf z.B. StackPanel behandeln w\u00fcrden, k\u00f6nnten wir eines seiner Kindelemente erhalten, wenn es angeklickt wird.
                  • keyRoutedEventArgs\": z.B. f\u00fcr ein \"KeyDown\"-Ereignis (Tastendruck), erhalten wir die gedr\u00fcckte Taste darin.
                  • pointerRoutedEventArgs\": wird z.B. f\u00fcr das \"PointerPressed\"-Ereignis (Maus-/Stiftdruck) verwendet und kann u.a. dazu verwendet werden, die Koordinaten des Klicks zu ermitteln.

                  Die XAML-Ereignishandler basieren vollst\u00e4ndig auf C#-Ereignissen (Schl\u00fcsselwortevent, siehe vorherige \u00dcbung):

                  Z.B. eine

                  <Button Click=\"RecordButton_Click\">\n

                  ist daf\u00fcr ausgebildet:

                  Button b = new Button();\nb.Click += RecordButton_Click;\n
                  "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#layout-gestaltung","title":"Layout, Gestaltung","text":"

                  Die Anordnung der Steuerelemente wird durch zwei Faktoren bestimmt:

                  1. Layout-Steuerelemente (panel) und ihre angef\u00fcgte Eigenschaften (attached property)
                  2. Allgemeine Positionseigenschaften innerhalb des \u00fcbergeordneten Steuerelements (z. B. Rand, vertikale oder horizontale Ausrichtung)

                  Eingebaute Layout-Steuerelemente zum Beispiel:

                  • StackPanel: Elemente untereinander oder nebeneinander
                  • Grid: Wir k\u00f6nnen ein Raster festlegen, an dem sich die Elemente ausrichten
                  • Canvas: Wir positionieren die Elemente explizit durch Angabe ihrer X- und Y-Koordinaten
                  • RelativePanel: Die Beziehung der Elemente zueinander kann durch Nebenbedingungen definiert werden

                  Versuchen wir es mit Grid(wir verwenden dies normalerweise, um das grundlegende Layout unseres Fensters/unserer Seite einzurichten). Wir werden eine Oberfl\u00e4che erstellen, \u00fcber die man Personen zu einer Liste hinzuf\u00fcgen kann, indem man ihren Namen und ihr Alter eingeben kann. Unser Ziel ist es, das folgende Layout zu erstellen:

                  Einige wichtige Verhaltensbeschr\u00e4nkungen:

                  • Wenn die Gr\u00f6\u00dfe des Fensters ge\u00e4ndert wird, sollte das Formular eine feste Breite haben und zentriert bleiben.
                  • In der Zeile Alter erh\u00f6ht die Taste + das Alter, die Taste - verringert es.
                  • Die Taste Hinzuf\u00fcgen f\u00fcgt die Person mit den oben angegebenen Daten zur unteren Liste hinzu (die Abbildung zeigt die Daten von zwei Personen in der unteren Liste).

                  Definieren wir die Wurzel Gridals 4 Zeilen und 2 Spalten. Die erste Spalte sollte die Bezeichnungen und die zweite Spalte die Eingabefelder enthalten. Setzen wir unsere vorhandene Taste in Zeile 3 und \u00e4ndern wir ihren Inhalt auf Add, und ersetzen wir den Kreis durch SymbolIcon. Geben wir in Zeile 4 eine Liste ein, die 2 Spalten einnehmen sollte.

                  <Grid x:Name=\"rootGrid\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n    <Grid.ColumnDefinitions>\n        <ColumnDefinition Width=\"Auto\" />\n        <ColumnDefinition Width=\"*\" />\n    </Grid.ColumnDefinitions>\n\n    <TextBlock Grid.Row=\"0\" Grid.Column=\"0\" Text=\"Name\"/>\n    <TextBox Grid.Row=\"0\" Grid.Column=\"1\" />\n    <TextBlock Grid.Row=\"1\" Grid.Column=\"0\" Text=\"Age\"/>\n    <TextBox Grid.Row=\"1\" Grid.Column=\"1\" />\n\n    <Button Grid.Row=\"2\" Grid.Column=\"1\">\n        <StackPanel Orientation=\"Horizontal\">\n            <SymbolIcon Symbol=\"Add\" />\n            <TextBlock Text=\"Add\" Margin=\"5,0,0,0\"/>\n        </StackPanel>\n    </Button>\n\n    <ListView Grid.Row=\"3\" Grid.Column=\"0\" Grid.ColumnSpan=\"2\"/>\n</Grid>\n

                  F\u00fcr die Zeilen- und Spaltendefinitionen k\u00f6nnen wir angeben, ob die Zeile die Gr\u00f6\u00dfe ihres Inhalts einnehmen soll (Auto) oder den verbleibenden Platz ausf\u00fcllen soll (*), oder sogar eine feste Breite in Pixeln (Width Eigenschaft). Wenn es mehrere * in den Definitionen gibt, k\u00f6nnen sie skaliert werden, z.B. * und * haben ein Verh\u00e4ltnis von 1:1, w\u00e4hrend * und 3* ein Verh\u00e4ltnis von 1:3 haben.

                  Die Grid.Row, Grid.Column werden als Attached Properties (angef\u00fcgte Eigneschaften) bezeichnet. Das bedeutet, dass der Controller, auf den sie angewendet wird, diese Eigenschaft nicht besitzt und diese Information nur \"angeh\u00e4ngt\" wird. In unserem Fall sind diese Informationen f\u00fcr Gridwichtig, um Ihre Kinder unterzubringen. Der Standardwert f\u00fcr Grid.Row und Grid.Column ist 0, so dass wir dies gar nicht schreiben sollten.

                  Imperative UI-Beschreibung

                  In anderen UI-Frameworks, in denen die UI imperativ ist, wird dies einfach mit Funktionsparametern gel\u00f6st - z.B.: myPanel.Add(new TextBox(), 0, 1).

                  Die angef\u00fcgte Eigenschaft Grid.ColumnSpan=\"2\" unter ListViewbedarf vielleicht einer Erkl\u00e4rung: ColumnSpan und RowSpan definieren die Anzahl der Spalten und Zeilen, die das Steuerelement \"umspannen\". In unserem Beispiel f\u00fcllt ListView beide Spalten.

                  Probieren wir die Anwendung aus (wenn der Code nicht funktioniert, l\u00f6schen wir den Ereignishandler im Code hinter der Datei RecordButton_Click ).

                  In seinem derzeitigen Zustand f\u00fcllt Grid den gesamten Raum sowohl horizontal als auch vertikal aus. Was ist der Grund daf\u00fcr? Eines der grundlegenden Merkmale des Layouts der Steuerelemente sind ihre Eigenschaften HorizontalAlignment und VerticalAlignment. Diese bestimmen, wo der Controller horizontal und vertikal in dem ihn enthaltenden Container (d. h. dem \u00fcbergeordneten Controller) positioniert werden soll. Die m\u00f6glichen Werte:

                  • VerticalAlignment: Top, Center, Bottom, Stretch(oben, mittig, unten ausgerichtet oder vertikal ausf\u00fcllen)
                  • HorizontalAlignment: Left, Center, Right, Stretch (links-, zentriert-, rechtsb\u00fcndig oder horizontal ausf\u00fcllen)

                  (Hinweis: F\u00fcr Stretch ist es erforderlich, dass die Eigenschaft Height oder Width f\u00fcr den Controller nicht angegeben ist)

                  Unserem Gridwurden die Eigenschaften HorizontalAlignment und VerticalAlignment nicht zugewiesen, so dass sein Wert standardm\u00e4\u00dfig Stretch f\u00fcr das Raster ist, weshalb Grid den Raum im \u00fcbergeordneten Container, dem Fenster, in beide Richtungen f\u00fcllt.

                  Unsere Oberfl\u00e4che sieht nicht so aus, wie wir sie haben wollen, also m\u00fcssen wir sie noch ein wenig optimieren. Die vorzunehmenden \u00c4nderungen:

                  • Die Tabelle muss nicht den ganzen Bildschirm ausf\u00fcllen, sondern sollte horizontal in der Mitte liegen
                    • HorizontalAlignment=\"Center\"
                  • 300px breit machen
                    • Width=\"300\"
                  • Halten wir 10px zwischen den Zeilen, 5px zwischen den Spalten und 20px vom Rand des Containers
                    • RowSpacing=\"5\" ColumnSpacing=\"10\" Margin=\"20\"
                  • Richten wir die Bezeichnungen (TexBlock) vertikal in der Mitte aus
                    • VerticalAlignment=\"Center\"
                  • Richten wir die Taste nach rechts aus
                    • HorizontalAlignment=\"Right\"
                  • Machen wir die Liste identifizierbar
                    • BorderThickness=\"1\" und BorderBrush=\"DarkGray\"
                  <Grid x:Name=\"rootGrid\"\n      Width=\"300\"\n      HorizontalAlignment=\"Center\"\n      Margin=\"20\"\n      RowSpacing=\"5\"\n      ColumnSpacing=\"10\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n    <Grid.ColumnDefinitions>\n        <ColumnDefinition Width=\"Auto\" />\n        <ColumnDefinition Width=\"*\" />\n    </Grid.ColumnDefinitions>\n\n    <TextBlock Grid.Row=\"0\" Grid.Column=\"0\" Text=\"Name\" VerticalAlignment=\"Center\"/>\n    <TextBox Grid.Row=\"0\" Grid.Column=\"1\" x:Name=\"tbName\" />\n    <TextBlock Grid.Row=\"1\" Grid.Column=\"0\" Text=\"Age\" VerticalAlignment=\"Center\"/>\n    <TextBox Grid.Row=\"1\" Grid.Column=\"1\" x:Name=\"tbAge\"/>\n\n    <Button Grid.Row=\"2\" Grid.Column=\"1\" HorizontalAlignment=\"Right\">\n        <StackPanel Orientation=\"Horizontal\">\n            <SymbolIcon Symbol=\"Add\"/>\n            <TextBlock Text=\"Add\" Margin=\"5,0,0,0\" />\n        </StackPanel>\n    </Button>\n\n    <ListView Grid.Row=\"3\"\n              Grid.Column=\"0\"\n              Grid.ColumnSpan=\"2\"\n              BorderThickness=\"1\"\n              BorderBrush=\"DarkGray\"/>\n</Grid>\n

                  Erweitern wir unser Formular um zwei weitere Tasten (\u00b1 Tasten f\u00fcr das Alter, siehe vorheriges animiertes Bildschirmfoto):

                  • -': auf der linken Seite von TextBox
                  • +' auf der rechten Seite vonTextBox

                  Dazu nehmen wir anstatt die Zeile (mit L\u00f6schen)

                  <TextBox Grid.Row=\"1\" Grid.Column=\"1\" x:Name=\"tbAge\"/>\n

                  ein Grid mit 1 Zeile und 3 Spalten :

                  <Grid Grid.Row=\"1\" Grid.Column=\"1\" ColumnSpacing=\"5\">\n    <Grid.ColumnDefinitions>\n        <ColumnDefinition Width=\"Auto\" />\n        <ColumnDefinition Width=\"*\" />\n        <ColumnDefinition Width=\"Auto\" />\n    </Grid.ColumnDefinitions>\n\n    <Button Grid.Row=\"0\" Grid.Column=\"0\" Content=\"-\" />\n    <TextBox Grid.Row=\"0\" Grid.Column=\"1\" x:Name=\"tbAge\" />\n    <Button Grid.Row=\"0\" Grid.Column=\"2\" Content=\"+\" />\n</Grid>\n

                  Verschachtelung mehrerer Layout-Steuerelemente

                  Sie fragen sich vielleicht, warum wir nicht zus\u00e4tzliche Spalten und Zeilen in das externe Grid(durch Anwendung von ColumnSpan auf die vorhandenen Steuerelemente) eingef\u00fcgt haben. Stattdessen folgten wir dem Prinzip der Vereinheitlichung: Die neu eingef\u00fchrten Steuerelemente sind im Wesentlichen ein Element, so dass wir eine transparentere L\u00f6sung erhielten, indem wir sie in ein separates Grid Steuerelement einf\u00fcgten. Die Erweiterung des externen Grid w\u00e4re gerechtfertigt, wenn wir aufgrund von Leistungsproblemen bei der Erstellung von Steuerelementen sparen wollten. In unserem Fall ist dies nicht gerechtfertigt.

                  Wir sind fertig mit dem Aussehen unseres einfachen Formulars.

                  "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#datenverbindung","title":"Datenverbindung","text":""},{"location":"labor/3-felhasznaloi-felulet/index_ger/#binding","title":"Binding","text":"

                  Im n\u00e4chsten Schritt soll es m\u00f6glich sein, die Daten einer Person in das soeben erstellte kleine Formular einzugeben und zu \u00e4ndern. Erstellen wir dazu zun\u00e4chst eine Datenklasse namens Person in einem neu erstellten Ordner Models im Projekt.

                  public class Person\n{\n    public string Name { get; set; }\n    public int Age { get; set; }\n}\n

                  Wir wollen die beiden Eigenschaften hier an die TextBox Steuerelemente binden, also verwenden wir die Datenbindung. F\u00fchren wir in der Code-Behind-Datei unseres Fensters eine Eigenschaft ein, die auf ein Objekt Person verweist, und geben wir ihr im Konstruktor einen Anfangswert:

                  public Person NewPerson { get; set; }\n\npublic MainWindow()\n{\n    InitializeComponent();\n\n    NewPerson = new Person()\n    {\n        Name = \"Eric Cartman\",\n        Age = 8\n    };\n}\n

                  Im n\u00e4chsten Schritt werden die Eigenschaften des oben genannten Objekts NewPerson zu die Text Eigenschaft der geigneten Textfelder gebunden:

                  • die Eigenschaft Name zu die Text Eigenschaft von tbName Textbox
                  • die Eigenschaft Age zu die Text Eigenschaft von tbAge Textbox

                  Wir verwenden Datenverbindung (data binding) daf\u00fcr:

                  Text=\"{x:Bind NewPerson.Name}\"\nText=\"{x:Bind NewPerson.Age}\"\n
                  (f\u00fcgen wir die oben genannten 1-1 Eigenschaftseinstellungen in die Zeilen von tbName und tbAge TextBoxein)

                  Wichtig

                  Bei der Datenverbindung geht es darum, dass anstatt die Eigenschaften (in unserem Fall den Text) der Steuerelemente in der Oberfl\u00e4che von der Code-Behind-Datei aus manuell einstellen, werden die Eigenschaften mit dem Datenverbindungsmechanismus der Plattform zusammengesetzt/verbunden. So k\u00f6nnen wir auch daf\u00fcr sorgen, dass sich bei einer \u00c4nderung einer Eigenschaft die andere automatisch \u00e4ndert!

                  Die Syntax Text=\"{x:Bind}\" wird als Markup Extension bezeichnet: Sie hat eine besondere Bedeutung f\u00fcr den XAML-Prozessor. Dies ist der Hauptgrund, warum wir XAML und nicht einfaches XML verwenden. Es ist auch m\u00f6glich, eine eigene Markup Extension zu erstellen, aber dies ist kein Material des Kurses.

                  Laufen wir die Anwendung! Es ist zu erkennen, dass den Namen und das Alter, die in den Eigenschaften Name und Age des Objekts NewPerson (als Datenquelle) angegeben sind, wegem der Datenverbindung automatisch in die Text Eigenschaften beider TextBox \u00fcbernommen wurden.

                  "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#benachrichtigung-uber-anderungen","title":"Benachrichtigung \u00fcber \u00c4nderungen","text":"

                  Implementieren wir die Click Ereignishandler f\u00fcr die Tasten \u00b1 .

                  <Button Grid.Row=\"1\" Grid.Column=\"0\" Content=\"-\" Click=\"DecreaseButton_Click\"/>\n<!-- ... -->\n<Button Grid.Row=\"1\" Grid.Column=\"2\" Content=\"+\" Click=\"IncreaseButton_Click\"/>\n
                  private void DecreaseButton_Click(object sender, RoutedEventArgs e)\n{\n    NewPerson.Age--;\n}\n\nprivate void IncreaseButton_Click(object sender, RoutedEventArgs e)\n{\n    NewPerson.Age++;\n}\n

                  Aufgrund der Datenverbindung, die im vorherigen Abschnitt eingef\u00fchrt wurde, w\u00fcrden wir erwarten, dass, wenn wir die Eigenschaft Age der Datenquelle NewPerson in den obigen Ereignishandlern \u00e4ndern, unser Steuerelement tbAge Textbox auf unserer Oberfl\u00e4che dies verfolgen w\u00fcrde. Probieren wir es aus! Dies funktioniert noch nicht, da es die Implementierung der Schnittstelle INotifyPropertyChanged erfordert.

                  1. Implementieren wir die Schnittstelle INotifyPropertyChanged in unserer Klasse Person. Wenn wir Daten an diese Klasse binden, abonniert das System das Ereignis PropertyChanged. Durch Ausl\u00f6sen dieses Ereignisses k\u00f6nnen wir die Verbindung benachrichtigen, wenn sich eine Eigenschaft ge\u00e4ndert hat.

                    public class Person : INotifyPropertyChanged\n{\n    public event PropertyChangedEventHandler PropertyChanged;\n\n    private string name;\n    public string Name\n    {\n        get { return name; }\n        set\n        {\n            if (name != value)\n            {\n                name = value;\n                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));\n            }\n        }\n    }\n\n    private int age;\n    public int Age\n    {\n        get { return age; }\n        set\n        {\n            if (age != value)\n            {\n                age = value;\n                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Age)));\n            }\n        }\n    }\n}\n

                    Ist der Code zu viel?

                    In Zukunft k\u00f6nnte diese Logik in einer Klasse von Vorg\u00e4ngern organisiert werden, aber das w\u00fcrde zum MVVM-Muster f\u00fchren, das mit einem sp\u00e4teren Thema verkn\u00fcpft ist. Lassen wir uns also nicht von diesem etwas h\u00e4sslichen Code abschrecken.

                  2. Bei der Datenverbindung schalten wir die \u00c4nderungsbenachrichtigung ein, indem wir sie auf Mode OneWay\u00e4ndern, da der Standardmodus f\u00fcr x:Bind OneTime ist, was eine einmalige Datenbindung darstellt.

                    Text=\"{x:Bind NewPerson.Age, Mode=OneWay}\"\n

                  Probieren wir es aus! Die Ereignishandler \u00e4ndern die Datenquelle (NewPerson), die nun auch die Oberfl\u00e4che aufgrund der richtig vorbereiteten Datenverbindung \u00e4ndert.

                  "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#ruckwartige-datenbindung-vom-controller-zur-datenquelle","title":"R\u00fcckw\u00e4rtige Datenbindung (vom Controller zur Datenquelle)","text":"

                  Wie Age sollte auch die Datenbindung f\u00fcr die Eigenschaft Name auf einseitig eingestellt werden:

                  Text=\"{x:Bind NewPerson.Name, Mode=OneWay}\"\n

                  Starten wir die Anwendung und setzen wir dann einen Haltepunkt im Setter der Eigenschaft Name der Klasse Person (Zeileif (name != value) ), und sehen wir nach, ob die Datenverbindung in umgekehrter Richtung funktioniert: Wenn wir den Wert eines der TextBox \u00e4ndern, \u00e4ndert sich dann die Eigenschaft Name des Objekts NewPerson? Geben wir etwas in das Textfeld ein, das mit dem Namen verkn\u00fcpft ist, und klicken wir dann auf ein anderes Feld: Der Inhalt des Textfelds wird dann \"abgeschlossen\", sein Inhalt sollte in die Datenquelle zur\u00fcckgeschrieben werden, wird aber nicht, der Code l\u00e4uft nicht an unserem Haltepunkt.

                  Das liegt daran, dass wir oben die Datenverbindung OneWay verwendet haben, die nur eine Datenbindung von der Datenquelle zur Oberfl\u00e4che ist. F\u00fcr den Weg zur\u00fcck soll der Datenbindungsmodus auf TwoWay eingestellt werden.

                  Text=\"{x:Bind Name, Mode=TwoWay}\"\nText=\"{x:Bind Age, Mode=TwoWay}\"\n

                  Probieren wir es aus! Auf diese Weise funktioniert die R\u00fcckw\u00e4rts-Datenverbindung: Die angegebene Eigenschaft des Controllers (in unserem Fall Text) und die Datenquelle bleiben bei jeder Richtungs\u00e4nderung synchron.

                  "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#listen","title":"Listen","text":"

                  Im Folgenden werden wir die Listenverbindung \u00fcben. F\u00fcgen wir eine Liste von Person-Objekten in die Code-Behind-Datei unserer Ansicht ein und geben wir ihr am Ende des Konstruktors einen Anfangswert.

                  public List<Person> People { get; set; }\n\npublic MainWindow()\n{\n    InitializeComponent();\n\n    NewPerson = new Person()\n    {\n        Name = \"Eric Cartman\",\n        Age = 8\n    };\n\n    People = new List<Person>()\n    {\n      new Person() { Name = \"Peter Griffin\", Age = 40 },\n      new Person() { Name = \"Homer Simpson\", Age = 42 },\n    };\n}\n

                  Verwenden wir die Datenverbindung, um die Datenquelle des Steuerelements ListView festzulegen. Dazu sollen wir die Eigenschaft ItemsSource des Steuerelements ListView einstellen.

                  <ListView Grid.Row=\"3\" Grid.ColumnSpan=\"2\" ItemsSource=\"{x:Bind People}\"/>\n

                  Probieren wir es aus!

                  Wir sehen, dass zwei Eintr\u00e4ge in der Liste erschienen sind. Nat\u00fcrlich ist es nicht das, was wir wollen, aber das ist leicht zu \u00e4ndern. Standardm\u00e4\u00dfig ruft ListView ToString()bei Listenelementen auf, was die Eigenschaft FullName des Klassentyps (d.h. der Typname) ist, wenn ToString() nicht \u00fcberschrieben wird.

                  Legen wir die Eigenschaft ItemTemplate von ListViewfest (unter Verwendung der bekannten property element syntax), die das Aussehen des Listenelementes unter Verwendung einer Vorlage verleiht: In unserem Fall machen wir daraus ein einzelliges Grid, wobei TextBlocks die Eigenschaften von Person anzeigt, wobei der Name links und das Alter rechts ausgerichtet ist.

                  <ListView Grid.Row=\"3\" Grid.ColumnSpan=\"2\" ItemsSource=\"{x:Bind People}\">\n    <ListView.ItemTemplate>\n        <DataTemplate x:DataType=\"model:Person\">\n            <Grid>\n                <TextBlock Text=\"{x:Bind Name}\" />\n                <TextBlock Text=\"{x:Bind Age}\" HorizontalAlignment=\"Right\" />\n            </Grid>\n        </DataTemplate>\n    </ListView.ItemTemplate>\n</ListView>\n

                  DataTemplate ist eine Oberfl\u00e4chenschablone, die von der ListView (er ist gegeben durch ItemTemplate eigenschaft) auf alle Elemente w\u00e4hrend der Anzeige angewendet wird.

                  Da x:Bind eine Datenverbindung zur \u00dcbersetzungszeit ist, m\u00fcssen wir auch den Datentyp in der Datenvorlage mit dem Attribut x:DataType angeben. Im obigen Beispiel haben wir model:Personangegeben, so dass das Pr\u00e4fix model dem Namensraum HelloXaml.Models unseres Codes zugeordnet werden soll (der die Klasse Person enth\u00e4lt). Dazu m\u00fcssen wir die folgende Namensraumdeklaration zu den Attributen des Tags Window am Anfang unserer XAML-Datei hinzuf\u00fcgen: xmlns:model=\"using:HelloXaml.Models\" (danach wird das Pr\u00e4fix model verwendet). Dies kann manuell oder mit Visual Studio erfolgen: Klicken wir einfach auf den unterstrichenen (als fehlerhaft markierten) model:PersonText, dann auf die Lampe am Anfang der Zeile (oder die Tastenkombination Ctrl + . ) und w\u00e4hlen wir das angezeigte Element \"Add xmlns using:HelloXaml.Models\".

                  Probieren wir es aus! Die Eintr\u00e4ge erscheinen nun gut in der Liste.

                  Klicken wir auf die Taste Add, um eine neue Kopie von Person mit den Daten der Person des Formilar zur Liste hinzuzuf\u00fcgen, und l\u00f6schen wir dann die Formulardaten in unserem Objekt NewPerson.

                  F\u00fcgen wir dazu unserer Taste Add einen Click Ereignishandler hinzu:

                  <Button ... Click=\"AddButton_Click\">\n
                  private void AddButton_Click(object sender, RoutedEventArgs e)\n{\n    People.Add(new Person()\n    { \n        Name = NewPerson.Name,\n        Age = NewPerson.Age,\n    });\n\n    NewPerson.Name = string.Empty;\n    NewPerson.Age = 0;\n}\n

                  Der neue Eintrag erscheint nicht in der Liste, da ListView nicht dar\u00fcber informiert wird, dass ein neuer Eintrag in die Liste aufgenommen wurde. Dies kann leicht behoben werden, indem List<Person>durch ObservableCollection<Person>ersetzt wird:

                  public ObservableCollection<Person> People { get; set; }\n

                  ObservableCollection<T>

                  Es ist wichtig zu beachten, dass sich hier nicht der Wert der Eigenschaft People selbst ge\u00e4ndert hat, sondern der Inhalt des Objekts List<Person>. Die L\u00f6sung ist also nicht die Schnittstelle INotifyPropertyChanged, sondern die Schnittstelle INotifyCollectionChanged, die von ObservableCollection implementiert wird.

                  Wir kennen und verwenden also bereits zwei Schnittstellen, die die Datenverbindung unterst\u00fctzen: INotifyPropertyChanged und INotifyCollectionChanged.

                  "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#ausblick-klassische-bindung","title":"Ausblick: Klassische Bindung","text":"

                  Die klassische Form der Datenverbindung ist die Binding Markup Extension.

                  Die wichtigsten Unterschiede im Vergleich zu x:Bindsind:

                  • Der Standardmodus f\u00fcr Binding ist OneWay und nicht OneTime: Er \u00fcberwacht also standardm\u00e4\u00dfig \u00c4nderungen, w\u00e4hrend dies f\u00fcr x:Bindausdr\u00fccklich angegeben werden muss.
                  • Binding arbeitet standardm\u00e4\u00dfig mit DataContext, aber es ist m\u00f6glich, die Quelle f\u00fcr die Datenbindung festzulegen. W\u00e4hrend x:Bind standardm\u00e4\u00dfig von unserer Ansichtsklasse (xaml.cs) gebunden wird.
                  • Binding arbeitet zur Laufzeit mit Reflection, so dass Sie einerseits keine Kompilierfehler bekommen, wenn Sie etwas falsch schreiben, und andererseits k\u00f6nnen viele Datenbindungen (in der Gr\u00f6\u00dfenordnung von 1000) Ihre Anwendung verlangsamen.
                  • x:Bind ist kompilierbar, d. h. der Compiler pr\u00fcft, ob die angegebenen Eigenschaften vorhanden sind. In Datenvorlagen m\u00fcssen Sie bei der Angabe von DataTemplate mit dem Attribut x:DataType angeben, mit welchen Daten sie arbeiten werden.
                  • F\u00fcr x:Bind ist es m\u00f6glich, Methoden zu binden, w\u00e4hrend f\u00fcr Bindingnur Konverter verwendet werden k\u00f6nnen. Bei gebundenen Funktionen funktioniert die \u00c4nderungsbenachrichtigung auch bei \u00c4nderungen von Parametern.

                  Empfehlung

                  Als Faustregel gilt, dass Sie vorzugsweise x:Bindverwenden sollten, da Sie so schneller und zeitnaher Fehler erhalten. Wenn Sie jedoch aus irgendeinem Grund Probleme mit x:Bindhaben, sollten Sie zu Bindingwechseln.

                  "},{"location":"labor/4-tobbszalu/","title":"4. T\u00f6bbsz\u00e1l\u00fa alkalmaz\u00e1sok k\u00e9sz\u00edt\u00e9se","text":""},{"location":"labor/4-tobbszalu/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                  A gyakorlat c\u00e9lja, hogy megismertesse a hallgat\u00f3kat a t\u00f6bbsz\u00e1las programoz\u00e1s sor\u00e1n k\u00f6vetend\u0151 alapelvekkel. \u00c9rintett t\u00e9mak\u00f6r\u00f6k (t\u00f6bbek k\u00f6z\u00f6tt):

                  • Sz\u00e1lak ind\u00edt\u00e1sa (Thread)
                  • Sz\u00e1lak le\u00e1ll\u00edt\u00e1sa
                  • Sz\u00e1lbiztos (thread safe) oszt\u00e1lyok k\u00e9sz\u00edt\u00e9se a lock kulcssz\u00f3 alkalmaz\u00e1s\u00e1val
                  • ThreadPool haszn\u00e1lata
                  • Jelz\u00e9s \u00e9s jelz\u00e9sre v\u00e1rakoz\u00e1s sz\u00e1l szinkroniz\u00e1ci\u00f3 ManualResetEvent seg\u00edts\u00e9g\u00e9vel (WaitHandle)
                  • WinUI sz\u00e1lkezel\u00e9si saj\u00e1toss\u00e1gok (DispatcherQueue)

                  Term\u00e9szetesen, mivel a t\u00e9mak\u00f6r hatalmas, csak alapszint\u0171 tud\u00e1st fogunk szerezni, de e tud\u00e1s birtok\u00e1ban m\u00e1r k\u00e9pesek lesz\u00fcnk \u00f6n\u00e1ll\u00f3an is elindulni a bonyolultabb feladatok megval\u00f3s\u00edt\u00e1s\u00e1ban.

                  A kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sok: Konkurens (t\u00f6bbsz\u00e1l\u00fa) alkalmaz\u00e1sok fejleszt\u00e9se.

                  "},{"location":"labor/4-tobbszalu/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                  A gyakorlat elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                  • Visual Studio 2022
                    • Windows Desktop Development Workload
                  • Windows 10 vagy Windows 11 oper\u00e1ci\u00f3s rendszer (Linux \u00e9s macOS nem alkalmas)
                  "},{"location":"labor/4-tobbszalu/#megoldas","title":"Megold\u00e1s","text":"A k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se

                  L\u00e9nyeges, hogy a labor sor\u00e1n a laborvezet\u0151t k\u00f6vetve kell dolgozni, tilos (\u00e9s \u00e9rtelmetlen) a k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se. Ugyanakkor az ut\u00f3lagos \u00f6n\u00e1ll\u00f3 gyakorl\u00e1s sor\u00e1n hasznos lehet a k\u00e9sz megold\u00e1s \u00e1ttekint\u00e9se, \u00edgy ezt el\u00e9rhet\u0151v\u00e9 tessz\u00fck.

                  A megold\u00e1s GitHubon \u00e9rhet\u0151 el. A legegyszer\u0171bb m\u00f3d a let\u00f6lt\u00e9s\u00e9re, ha parancssorb\u00f3l a git clone utas\u00edt\u00e1ssal lekl\u00f3nozzuk a g\u00e9p\u00fcnkre a megoldas \u00e1gat:

                  git clone https://github.com/bmeviauab00/lab-tobbszalu-kiindulo -b megoldas

                  Ehhez telep\u00edtve kell legyen a g\u00e9pre a parancssori git, b\u0151vebb inform\u00e1ci\u00f3 itt.

                  "},{"location":"labor/4-tobbszalu/#bevezeto","title":"Bevezet\u0151","text":"

                  A p\u00e1rhuzamosan fut\u00f3 sz\u00e1lak kezel\u00e9se kiemelt fontoss\u00e1g\u00fa ter\u00fclet, melyet minden szoftverfejleszt\u0151nek legal\u00e1bb alapszinten ismernie kell. A gyakorlat sor\u00e1n alapszint\u0171, de kiemelt fontoss\u00e1g\u00fa probl\u00e9m\u00e1kat oldunk meg, ez\u00e9rt t\u00f6rekedn\u00fcnk kell arra, hogy ne csak a v\u00e9geredm\u00e9nyt, hanem az elv\u00e9gzett m\u00f3dos\u00edt\u00e1sok \u00e9rtelm\u00e9t \u00e9s indokait is meg\u00e9rts\u00fck.

                  A feladat sor\u00e1n egyszer\u0171 WinUI alkalmaz\u00e1st fogunk felruh\u00e1zni t\u00f6bbsz\u00e1las k\u00e9pess\u00e9gekkel, egyre komplexebb feladatokat megoldva. Az alapprobl\u00e9ma a k\u00f6vetkez\u0151: van egy f\u00fcggv\u00e9ny\u00fcnk, mely hossz\u00fa ideig fut, s mint l\u00e1tni fogjuk, ennek \u201edirektben\u201d t\u00f6rt\u00e9n\u0151 h\u00edv\u00e1sa a fel\u00fcletr\u0151l kellemetlen k\u00f6vetkezm\u00e9nyekkel j\u00e1r. A megold\u00e1s sor\u00e1n egy megl\u00e9v\u0151 alkalmaz\u00e1st fogunk kieg\u00e9sz\u00edteni saj\u00e1t k\u00f3dr\u00e9szletekkel. Az \u00fajonnan besz\u00farand\u00f3 sorokat az \u00fatmutat\u00f3ban kiemelt h\u00e1tt\u00e9r jelzi.

                  "},{"location":"labor/4-tobbszalu/#0-feladat-ismerkedes-a-kiindulo-alkalmazassal-elokeszites","title":"0. Feladat - Ismerked\u00e9s a kiindul\u00f3 alkalmaz\u00e1ssal, el\u0151k\u00e9sz\u00edt\u00e9s","text":"

                  Kl\u00f3nozzuk le a 4. gyakorlathoz tartoz\u00f3 kiindul\u00f3 alkalmaz\u00e1s repositoryj\u00e1t:

                  • Nyissunk egy command prompt-ot
                  • Navig\u00e1ljunk el egy tetsz\u0151leges mapp\u00e1ba, p\u00e9ld\u00e1ul c:\\work\\NEPTUN
                  • Adjuk ki a k\u00f6vetkez\u0151 parancsot: git clone https://github.com/bmeviauab00/lab-tobbszalu-kiindulo.git
                  • Nyissuk meg a SuperCalculator.sln solutiont Visual Studio-ban.

                  A feladatunk az, hogy egy bin\u00e1ris form\u00e1ban megkapott algoritmus futtat\u00e1s\u00e1hoz WinUI technol\u00f3gi\u00e1val felhaszn\u00e1l\u00f3i fel\u00fcletet k\u00e9sz\u00edts\u00fcnk. A bin\u00e1ris forma .NET eset\u00e9ben egy .dll kiterjeszt\u00e9s\u0171 f\u00e1jlt jelent, ami programoz\u00f3i szemmel egy oszt\u00e1lyk\u00f6nyvt\u00e1r. A f\u00e1jl neve eset\u00fcnkben Algorithms.dll, megtal\u00e1lhat\u00f3 a lekl\u00f3nozott Git repositoryban.

                  A kiindul\u00f3 alkalmaz\u00e1sban a felhaszn\u00e1l\u00f3i fel\u00fclet el\u0151 is van k\u00e9sz\u00edtve. Futtassuk az alkalmaz\u00e1st:

                  Az alkalmaz\u00e1s fel\u00fclet\u00e9n meg tudjuk adni az algoritmus bemen\u0151 param\u00e9tereit (double sz\u00e1mok t\u00f6mbje): a p\u00e9ld\u00e1nkban mindig k\u00e9t double sz\u00e1m param\u00e9terrel h\u00edvjuk az algoritmust, ezt a k\u00e9t fels\u0151 sz\u00f6vegmez\u0151ben lehet megadni. A feladatunk az, hogy a Calculate Result gombra kattint\u00e1s sor\u00e1n futtassuk az algoritmust a megadott param\u00e9terekkel, majd, ha v\u00e9gzett, akkor a Result alatti list\u00e1z\u00f3 mez\u0151 \u00faj sor\u00e1ban jelen\u00edts\u00fck meg a kapott eredm\u00e9nyt a bemen\u0151 param\u00e9terekkel egy\u00fctt.

                  K\u00f6vetkez\u0151 l\u00e9p\u00e9sben ismerkedj\u00fcnk meg a let\u00f6lt\u00f6tt Visual Studio solutionnel:

                  A keretalkalmaz\u00e1s egy WinUI 3 alap\u00fa alkalmaz\u00e1s. A fel\u00fclet alapvet\u0151en k\u00e9sz, defin\u00edci\u00f3ja a MainWindow.xaml f\u00e1jlban tal\u00e1lhat\u00f3. Ez sz\u00e1munkra a gyakorlat c\u00e9lj\u00e1t tekintve kev\u00e9sb\u00e9 izgalmas, de otthon a gyakorl\u00e1s kedv\u00e9\u00e9rt \u00e9rdemes \u00e1ttekinteni.

                  Fel\u00fclet kialak\u00edt\u00e1sa a MainWindow.xaml-ben

                  Az ablakfel\u00fclet kialak\u00edt\u00e1s\u00e1nak alapjai:

                  • A gy\u00f6k\u00e9relem (root) \"szok\u00e1sosan\" egy Grid.
                  • A gy\u00f6k\u00e9r Grid fels\u0151 sor\u00e1ban tal\u00e1lhat\u00f3 a k\u00e9t TextBox-ot \u00e9s a Button-t tartalmaz\u00f3 StackPanel.
                  • A gy\u00f6k\u00e9r Grid als\u00f3 sor\u00e1ban egy m\u00e1sik Grid tal\u00e1lhat\u00f3. A TextBox-szal ellent\u00e9tben a ListBox nem rendelkezik Header tulajdons\u00e1ggal, \u00edgy ezt nek\u00fcnk kellett egy k\u00fcl\u00f6n\u00e1ll\u00f3 \"Result\" sz\u00f6veg\u0171 TextBlock form\u00e1j\u00e1ban bevezetni. Ezt a Grid-et az\u00e9rt vezett\u00fck be (egy \"egyszer\u0171bb\" StackPanel helyett), mert \u00edgy lehetett el\u00e9rni, hogy a fels\u0151 sor\u00e1ban a \"Result\" TextBlock fix magass\u00e1g\u00fa legyen, az als\u00f3 sorban pedig a ListBox t\u00f6ltse ki a teljes marad\u00f3 helyet (a fels\u0151 sor magass\u00e1ga Auto, az als\u00f3 sor magass\u00e1ga *).
                  • A \"Calculate Result\" sz\u00f6veg\u0171 gomb sz\u00e9p p\u00e9lda arra, hogy a Button Content-j\u00e9nek sokszor nemcsak egy egyszer\u0171 sz\u00f6veget adunk meg. A p\u00e9ld\u00e1ban egy SymbolIcon \u00e9s a TextBlock kompoz\u00edci\u00f3ja (StackPanel seg\u00edts\u00e9g\u00e9vel megval\u00f3s\u00edtva), ez\u00e1ltal tudjunk a egy megfelel\u0151 ikont/szimb\u00f3lumot rendelni, mely feldobja a megjelen\u00e9s\u00e9t.
                  • Arra is l\u00e1tunk p\u00e9ld\u00e1t, hogy a ListBox hogyan tehet\u0151 g\u00f6rgethet\u0151v\u00e9, ha m\u00e1r sok elem van benne (vagy t\u00fal sz\u00e9lesek az elemek). Ehhez a ScrollViewer-\u00e9t kell megfelel\u0151en param\u00e9terezni.
                  • A ListBox ItemContainerStyle tulajdons\u00e1g\u00e1val a ListBox elemre adhatunk meg st\u00edlusokat. A p\u00e9ld\u00e1ban a Padding-et vett\u00fck kisebbre az alap\u00e9rtelmezettn\u00e9l, en\u00e9lk\u00fcl a ListBox elemek magass\u00e1ga helypazarl\u00f3an nagy lenne.

                  A MainWindow.xaml.cs forr\u00e1sf\u00e1jl a f\u0151ablakhoz tartoz\u00f3 code behind f\u00e1jl, ezt tekints\u00fck \u00e1t, f\u0151bb elemei a k\u00f6vetkez\u0151k:

                  • Az eredm\u00e9ny \u00e9s a param\u00e9terek ListBox-ba t\u00f6rt\u00e9n\u0151 napl\u00f3z\u00e1s\u00e1hoz tal\u00e1lunk egy ShowResult nev\u0171 seg\u00e9df\u00fcggv\u00e9nyt.
                  • A CalculateResultButton_Click a gomb a Calculate Result gomb kattint\u00e1s\u00e1hoz tartoz\u00f3 esem\u00e9nykezel\u0151. Azt l\u00e1tjuk, hogy a k\u00e9t sz\u00f6vegdobozb\u00f3l kiolvassa a param\u00e9terek \u00e9rt\u00e9k\u00e9t, \u00e9s megpr\u00f3b\u00e1lja sz\u00e1mm\u00e1 alak\u00edtani. Ha siker\u00fcl, akkor itt t\u00f6rt\u00e9nik majd az algoritmus h\u00edv\u00e1sa (ez nincs m\u00e9g megval\u00f3s\u00edtva), illetve, ha nem siker\u00fcl, akkor a DisplayInvalidElementDialog seg\u00edts\u00e9g\u00e9vel egy \u00fczenetablakban t\u00e1j\u00e9koztatja a felhaszn\u00e1l\u00f3t az \u00e9rv\u00e9nytelen param\u00e9terekr\u0151l.
                  • A konstruktorb\u00f3l h\u00edvott AddKeyboardAcceleratorToChangeTheme f\u00fcggv\u00e9ny sz\u00e1munkra nem relev\u00e1ns, a vil\u00e1gos \u00e9s s\u00f6t\u00e9t t\u00e9ma k\u00f6z\u00f6tti v\u00e1lt\u00e1st teszi lehet\u0151v\u00e9 (fut\u00e1s k\u00f6zben \u00e9rdemes kipr\u00f3b\u00e1lni, Ctrl+T billenty\u0171kombin\u00e1ci\u00f3).
                  "},{"location":"labor/4-tobbszalu/#a-dll-ben-levo-kod-felhasznalasa","title":"A DLL-ben lev\u0151 k\u00f3d felhaszn\u00e1l\u00e1sa","text":"

                  A kiindul\u00f3 projektben megtal\u00e1ljuk a Algorithm.dll-t. Ebben leford\u00edtott form\u00e1ban egy Algorithms n\u00e9vt\u00e9rben lev\u0151 SuperAlgorithm nev\u0171 oszt\u00e1ly tal\u00e1lhat\u00f3, melynek egy Calculate nev\u0171 statikus m\u0171velete van. Ahhoz, hogy egy projektben fel tudjuk haszn\u00e1lni a DLL-ben lev\u0151 oszt\u00e1lyokat, a DLL-re a projekt\u00fcnkben egy \u00fan. referenci\u00e1t kell felvegy\u00fcnk.

                  1. Solution Explorerben a projekt\u00fcnk Dependencies node-j\u00e1ra jobbklikkelve v\u00e1lasszuk az Add Project reference opci\u00f3t!

                    K\u00fcls\u0151 referenci\u00e1k

                    Itt val\u00f3j\u00e1ban nem egy m\u00e1sik Visual Studio projektre adunk referenci\u00e1t, de \u00edgy a legegyszer\u0171bb el\u0151hozni ezt az ablakot.

                    Megeml\u00edtend\u0151 m\u00e9g, hogy k\u00fcls\u0151 oszt\u00e1lyk\u00f6nyvt\u00e1rak eset\u00e9ben m\u00e1r nem DLL-eket szoktunk refer\u00e1lni egy rendes projektben, hanem a .NET csomagkezel\u0151 rendeszer\u00e9b\u0151l a NuGet-r\u0151l szok\u00e1s a k\u00fcls\u0151 csomagokat beszerezni. Most az Algorithm.dll eset\u00fcnkben nincs NuGet-en publik\u00e1lva, ez\u00e9rt kell k\u00e9zzel felvegy\u00fck azt.

                  2. Az el\u0151ugr\u00f3 ablak jobb als\u00f3 sarokban tal\u00e1lhat\u00f3 Browse gomb seg\u00edts\u00e9g\u00e9vel keress\u00fck meg \u00e9s v\u00e1lasszuk ki projekt External almapp\u00e1j\u00e1ban tal\u00e1lhat\u00f3 Algorithms.dll f\u00e1jlt, majd hagyjuk j\u00f3v\u00e1 a hozz\u00e1ad\u00e1st az OK gombbal!

                  A Solution Explorerben egy projekt alatti Dependencies csom\u00f3pontot lenyitva l\u00e1thatjuk a hivatkozott k\u00fcls\u0151 f\u00fcgg\u0151s\u00e9geket. Itt most m\u00e1r megjelenik az Assemblyk k\u00f6z\u00f6tt el\u0151bb felvett Algorithms referencia is. A Frameworks kateg\u00f3ri\u00e1ban a .NET keretrendszer csomagjait tal\u00e1ljuk. Az Analyzerek pedig statikus k\u00f3delemz\u0151 eszk\u00f6z\u00f6k ford\u00edt\u00e1s id\u0151ben. Illetve itt lenn\u00e9nek m\u00e9g a projekt vagy a NuGet referenci\u00e1k is.

                  Kattintsunk Algorithms referenci\u00e1n jobb gombbal \u00e9s v\u00e1lasszuk a View in Object Browser funkci\u00f3t. Ekkor megny\u00edlik az Object Browser tabf\u00fcl, ahol megtekinthetj\u00fck, hogy az adott DLL-ben milyen n\u00e9vterek, oszt\u00e1lyok tal\u00e1lhat\u00f3k, illetve ezeknek milyen tagjaik (tagv\u00e1ltoz\u00f3, tagf\u00fcggv\u00e9ny, property, event) vannak. Ezeket a Visual Studio a DLL metaadataib\u00f3l az \u00fan. reflection mechanizmus seg\u00edts\u00e9g\u00e9vel olvassa ki (ilyen k\u00f3dot ak\u00e1r mi is \u00edrhatunk).

                  Az al\u00e1bbi \u00e1br\u00e1nak megfelel\u0151en az Object Browserben baloldalt keress\u00fck ki az Algorithms csom\u00f3pontot, nyissuk le, \u00e9s l\u00e1that\u00f3v\u00e1 v\u00e1lik, hogy egy Algorithms n\u00e9vt\u00e9r van benne, abban pedig egy SuperAlgorithm oszt\u00e1ly. Ezt kiv\u00e1lasztva k\u00f6z\u00e9pen megjelennek az oszt\u00e1ly f\u00fcggv\u00e9nyei, itt egy f\u00fcggv\u00e9nyt kiv\u00e1lasztva pedig az adott f\u00fcggv\u00e9ny pontos szignat\u00far\u00e1ja:

                  "},{"location":"labor/4-tobbszalu/#1-feladat-muvelet-futtatasa-a-foszalon","title":"1. Feladat \u2013 M\u0171velet futtat\u00e1sa a f\u0151sz\u00e1lon","text":"

                  Most m\u00e1r r\u00e1t\u00e9rhet\u00fcnk az algoritmus futtat\u00e1s\u00e1ra. Els\u0151 l\u00e9p\u00e9sben ezt az alkalmaz\u00e1sunk f\u0151 sz\u00e1l\u00e1n tessz\u00fck meg.

                  1. A f\u0151ablakon l\u00e9v\u0151 gomb Click esem\u00e9nykezel\u0151j\u00e9ben h\u00edvjuk meg a sz\u00e1mol\u00f3 f\u00fcggv\u00e9ny\u00fcnket. Ehhez a Solution Explorerben nyissuk meg a MainWindow.xaml.cs code behind f\u00e1jlt, \u00e9s keress\u00fck meg a CalculateResultButton_Click esem\u00e9nykezel\u0151t. Eg\u00e9sz\u00edts\u00fck ki a k\u00f3dot az \u00fajonnan behivatkozott algoritmus megh\u00edv\u00e1s\u00e1val.

                    private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        var result = Algorithms.SuperAlgorithm.Calculate(parameters);\n        ShowResult(parameters, result);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n
                  2. Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st, \u00e9s vegy\u00fck \u00e9szre, hogy az ablak a sz\u00e1mol\u00e1s ideje alatt nem reag\u00e1l a mozgat\u00e1sra, \u00e1tm\u00e9retez\u00e9sre, a fel\u00fclet gyakorlatilag befagy.

                  Az alkalmaz\u00e1sunk esem\u00e9nyvez\u00e9relt, mint minden Windows alkalmaz\u00e1s. Az oper\u00e1ci\u00f3s rendszer a k\u00fcl\u00f6nb\u00f6z\u0151 interakci\u00f3kr\u00f3l (pl. mozgat\u00e1s, \u00e1tm\u00e9retez\u00e9s, eg\u00e9rkattint\u00e1s) \u00e9rtes\u00edti az alkalmaz\u00e1sunkat: mivel a gombnyom\u00e1st k\u00f6vet\u0151en az alkalmaz\u00e1sunk egyetlen sz\u00e1la a kalkul\u00e1ci\u00f3val van elfoglalva, nem tudja azonnal feldolgozni a tov\u00e1bbi felhaszn\u00e1l\u00f3i utas\u00edt\u00e1sokat. Amint a sz\u00e1m\u00edt\u00e1s lefutott (\u00e9s az eredm\u00e9nyek megjelennek a list\u00e1ban) a kor\u00e1bban kapott parancsok is v\u00e9grehajt\u00e1sra ker\u00fclnek.

                  "},{"location":"labor/4-tobbszalu/#2-feladat-vegezzuk-a-szamitast-kulon-szalban","title":"2. Feladat \u2013 V\u00e9gezz\u00fck a sz\u00e1m\u00edt\u00e1st k\u00fcl\u00f6n sz\u00e1lban","text":"

                  K\u00f6vetkez\u0151 l\u00e9p\u00e9sben a sz\u00e1m\u00edt\u00e1s elv\u00e9gz\u00e9s\u00e9re egy k\u00fcl\u00f6n sz\u00e1lat fogunk ind\u00edtani, hogy az ne blokkolja a felhaszn\u00e1l\u00f3i fel\u00fcletet.

                  1. K\u00e9sz\u00edts\u00fcnk egy \u00faj f\u00fcggv\u00e9nyt a MainWindow oszt\u00e1lyban, mely a feldolgoz\u00f3 sz\u00e1l bel\u00e9p\u00e9si pontja lesz.

                    private void CalculatorThread(object arg)\n{\n    var parameters = (double[])arg;\n    var result = Algorithms.SuperAlgorithm.Calculate(parameters);\n    ShowResult(parameters, result);\n}\n
                  2. Ind\u00edtsuk el a sz\u00e1lat a gomb Click esem\u00e9nykezel\u0151j\u00e9ben. Ehhez cser\u00e9lj\u00fck le a kor\u00e1bban hozz\u00e1adott k\u00f3dot:

                    private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        var th = new Thread(CalculatorThread);\n        th.Start(parameters);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n

                    A Thread objektum Start m\u0171velet\u00e9ben \u00e1tadott param\u00e9tert kapja meg a CalculatorThread sz\u00e1lf\u00fcggv\u00e9ny\u00fcnk.

                  3. Futtassuk az alkalmaz\u00e1st F5-tel (most fontos, hogy \u00edgy, a debuggerben futtassuk)! The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD)) hiba\u00fczenetet kapunk a ShowResult met\u00f3dusban, ugyanis nem abb\u00f3l a sz\u00e1lb\u00f3l pr\u00f3b\u00e1lunk hozz\u00e1f\u00e9rni a UI elemhez / vez\u00e9rl\u0151h\u00f6z, amelyik l\u00e9trehozta (a vez\u00e9rl\u0151t). A k\u00f6vetkez\u0151 feladatban ezt a probl\u00e9m\u00e1t analiz\u00e1ljuk \u00e9s oldjuk meg.

                  "},{"location":"labor/4-tobbszalu/#3-feladat-a-dispatcherqueuehasthreadaccess-es-dispatcherqueuetryenqueue-hasznalata","title":"3. Feladat \u2013 a DispatcherQueue.HasThreadAccess \u00e9s DispatcherQueue.TryEnqueue haszn\u00e1lata","text":"

                  Az el\u0151z\u0151 pontban a probl\u00e9m\u00e1t a k\u00f6vetkez\u0151 okozza. WinUI alkalmaz\u00e1sokn\u00e1l \u00e9l az al\u00e1bbi szab\u00e1ly: az ablakok/fel\u00fcletelemek/vez\u00e9rl\u0151elemek alapvet\u0151en nem sz\u00e1lv\u00e9dett (thread safe) objektumok, \u00edgy egy ablakhoz/fel\u00fcletelemhez/vez\u00e9rl\u0151h\u00f6z csak abb\u00f3l a sz\u00e1lb\u00f3l szabad hozz\u00e1f\u00e9rni (pl. propertyj\u00e9t olvasni, \u00e1ll\u00edtani, m\u0171velet\u00e9t megh\u00edvni), amelyik sz\u00e1l az adott ablakot/fel\u00fcletelemet/vez\u00e9rl\u0151t l\u00e9trehozta, m\u00e1sk\u00fcl\u00f6nben kiv\u00e9telt kapunk. Alkalmaz\u00e1sunkban az\u00e9rt kaptunk kiv\u00e9telt, mert a resultListBox vez\u00e9rl\u0151t a f\u0151 sz\u00e1lban hoztuk l\u00e9tre, a ShowResult met\u00f3dusban az eredm\u00e9ny megjelen\u00edt\u00e9sekor viszont egy m\u00e1sik sz\u00e1lb\u00f3l f\u00e9r\u00fcnk hozz\u00e1 (resultListBox.Items.Add m\u0171velet h\u00edv\u00e1sa).

                  K\u00e9rd\u00e9s, hogyan lehet m\u00e9gis valamilyen m\u00f3don ezekhez a fel\u00fcletelemekhez/vez\u00e9rl\u0151kh\u00f6z egy m\u00e1sik sz\u00e1lb\u00f3l hozz\u00e1f\u00e9rni. A megold\u00e1st a DispatcherQueue alkalmaz\u00e1sa jelenti, mely abban ny\u00fajt seg\u00edts\u00e9get, hogy a vez\u00e9rl\u0151kh\u00f6z mindig a megfelel\u0151 sz\u00e1lb\u00f3l t\u00f6rt\u00e9njen a hozz\u00e1f\u00e9r\u00e9s:

                  • DispatcherQueue objektum TryEnqueue f\u00fcggv\u00e9nye a vez\u00e9rl\u0151elemet l\u00e9trehoz\u00f3 sz\u00e1lon futtatja le a sz\u00e1m\u00e1ra param\u00e9terk\u00e9nt megadott f\u00fcggv\u00e9nyt (mely f\u00fcggv\u00e9nyb\u0151l \u00edgy m\u00e1r k\u00f6zvetlen\u00fcl hozz\u00e1f\u00e9rhet\u00fcnk a vez\u00e9rl\u0151h\u00f6z).
                  • A DispatcherQueue objektum HasThreadAccess tulajdons\u00e1ga azt seg\u00edt eld\u00f6nteni, sz\u00fcks\u00e9g van-e egy\u00e1ltal\u00e1n az el\u0151z\u0151 pontban eml\u00edtett TryEnqueue alkalmaz\u00e1s\u00e1ra. Ha a tulajdons\u00e1g \u00e9rt\u00e9ke
                    • igaz, akkor a vez\u00e9rl\u0151h\u00f6z k\u00f6zvetlen\u00fcl is hozz\u00e1f\u00e9rhet\u00fcnk (mert az aktu\u00e1lis sz\u00e1l megegyezik a vez\u00e9rl\u0151t l\u00e9trehoz\u00f3 sz\u00e1llal), ellenben ha
                    • hamis, akkor a vez\u00e9rl\u0151h\u00f6z csak \"ker\u00fcl\u0151 \u00faton\", a DispatcherQueue objektum TryEnqueue seg\u00edts\u00e9g\u00e9vel f\u00e9rhet\u00fcnk hozz\u00e1 (mert az aktu\u00e1lis sz\u00e1l NEM egyezik a vez\u00e9rl\u0151t l\u00e9trehoz\u00f3 sz\u00e1llal).

                  A DispatcherQueue seg\u00edts\u00e9g\u00e9vel teh\u00e1t el tudjuk ker\u00fclni kor\u00e1bbi kiv\u00e9tel\u00fcnket (a vez\u00e9rl\u0151h\u00f6z, eset\u00fcnkben a resultListBox-hoz val\u00f3 hozz\u00e1f\u00e9r\u00e9st a megfelel\u0151 sz\u00e1lra tudjuk \"ir\u00e1ny\u00edtani\"). Ezt fogjuk a k\u00f6vetkez\u0151kben megtenni.

                  Note

                  A DispatcherQueue objektum a Window oszt\u00e1ly lesz\u00e1rmazottakban \u00e9rhet\u0151 el aDispatcherQueue tulajdons\u00e1g\u00e1n kereszt\u00fcl (m\u00e1s oszt\u00e1lyokban pedig a DispatcherQueue.GetForCurrentThread() statikus m\u0171velet seg\u00edts\u00e9g\u00e9vel szerezhet\u0151 meg).

                  M\u00f3dos\u00edtanunk kell a ShowResult met\u00f3dust annak \u00e9rdek\u00e9ben, hogy mell\u00e9ksz\u00e1lb\u00f3l t\u00f6rt\u00e9n\u0151 h\u00edv\u00e1s eset\u00e9n se dobjon kiv\u00e9telt.

                  private void ShowResult(double[] parameters, double result)\n{\n    // Closing the window the DispatcherQueue property may return null, so we have to perform a null check\n    if (this.DispatcherQueue == null)\n        return;\n\n    if (this.DispatcherQueue.HasThreadAccess)\n    {\n        var item = new ListBoxItem()\n        {\n            Content = $\"{parameters[0]} #  {parameters[1]} = {result}\"\n        };\n        resultListBox.Items.Add(item);\n        resultListBox.ScrollIntoView(item);\n    }\n    else\n    {\n        this.DispatcherQueue.TryEnqueue( () => ShowResult(parameters, result) );\n    }\n}\n

                  Pr\u00f3b\u00e1ljuk ki!

                  Ez a megold\u00e1s m\u00e1r m\u0171k\u00f6d\u0151k\u00e9pes, f\u0151bb elemei a k\u00f6vetkez\u0151k:

                  • A DispatcherQueue null vizsg\u00e1lat szerepe: a f\u0151ablak bez\u00e1r\u00e1sa ut\u00e1n a DispatcherQueue m\u00e1r null, nem haszn\u00e1lhat\u00f3.
                  • A DispatcherQueue.HasThreadAccess seg\u00edts\u00e9g\u00e9vel megn\u00e9zz\u00fck, hogy a h\u00edv\u00f3 sz\u00e1l hozz\u00e1f\u00e9rhet-e k\u00f6zvetlen\u00fcl a vez\u00e9rl\u0151kh\u00f6z (eset\u00fcnkben a ListBox-hoz):
                    • Ha igen, minden \u00fagy t\u00f6rt\u00e9nik, mint eddig, a ListBox-ot kezel\u0151 k\u00f3d v\u00e1ltozatlan.
                    • Ha nem, a DispatcherQueue.TryEnqueue seg\u00edts\u00e9g\u00e9vel f\u00e9r\u00fcnk hozz\u00e1 a vez\u00e9rl\u0151h\u00f6z. A k\u00f6vetkez\u0151 tr\u00fckk\u00f6t alkalmazzuk. A TryEnqueue f\u00fcggv\u00e9nynek egy olyan param\u00e9ter n\u00e9lk\u00fcli, egysoros f\u00fcggv\u00e9nyt adunk meg lambda kifejez\u00e9s form\u00e1j\u00e1ban, mellyel a ShowResult f\u00fcggv\u00e9ny\u00fcnket h\u00edvja meg (gyakorlatilag rekurz\u00edvan), a param\u00e9tereket tov\u00e1bb passzolva sz\u00e1m\u00e1ra. Ez nek\u00fcnk az\u00e9rt j\u00f3, mert ez a ShowResult h\u00edv\u00e1s m\u00e1r azon a sz\u00e1lon t\u00f6rt\u00e9nik, mely a vez\u00e9rl\u0151t l\u00e9trehozta (az alkalmaz\u00e1s f\u0151 sz\u00e1la), ebben a HasThreadAccess \u00e9rt\u00e9ke m\u00e1r igaz, \u00e9s hozz\u00e1 tudunk f\u00e9rni k\u00f6zvetlen\u00fcl a ListBox-unkhoz. Ez a rekurz\u00edv megk\u00f6zel\u00edt\u00e9s egy bevett minta a redund\u00e1ns k\u00f3dok elker\u00fcl\u00e9s\u00e9re.

                  Tegy\u00fcnk t\u00f6r\u00e9spontot a ShowResult m\u0171velet els\u0151 sor\u00e1ra, \u00e9s az alkalmaz\u00e1st futtatva gy\u0151z\u0151dj\u00fcnk meg arr\u00f3l, hogy a ShowResult m\u0171velet els\u0151 h\u00edv\u00e1sakor HasThreadAccess m\u00e9g hamis (\u00edgy megt\u00f6rt\u00e9nik a TryEnqueue h\u00edv\u00e1sa), majd ennek hat\u00e1s\u00e1ra m\u00e9g egyszer megh\u00edv\u00f3dik a ShowResult, de ekkor a HasThreadAccess \u00e9rt\u00e9ke m\u00e1r igaz.

                  Vegy\u00fck ki a t\u00f6r\u00e9spontot, \u00edgy futtassuk az alkalmaz\u00e1st: vegy\u00fck \u00e9szre, hogy am\u00edg egy sz\u00e1m\u00edt\u00e1s fut, \u00fajabbakat is ind\u00edthatunk, hiszen a fel\u00fclet\u00fcnk v\u00e9gig reszponz\u00edv maradt (a kor\u00e1bban tapasztalt hiba pedig m\u00e1r nem jelentkezik).

                  "},{"location":"labor/4-tobbszalu/#4-feladat-muvelet-vegzese-threadpool-szalon","title":"4. feladat \u2013 M\u0171velet v\u00e9gz\u00e9se Threadpool sz\u00e1lon","text":"

                  Az el\u0151z\u0151 megold\u00e1s egy jellemz\u0151je, hogy mindig \u00faj sz\u00e1lat hoz l\u00e9tre a m\u0171velethez. Eset\u00fcnkben ennek nincs k\u00fcl\u00f6n\u00f6sebb jelent\u0151s\u00e9ge, de ez a megk\u00f6zel\u00edt\u00e9s egy olyan kiszolg\u00e1l\u00f3 alkalmaz\u00e1s eset\u00e9ben, amely nagysz\u00e1m\u00fa k\u00e9r\u00e9st szolg\u00e1l ki \u00fagy, hogy minden k\u00e9r\u00e9shez k\u00fcl\u00f6n sz\u00e1lat ind\u00edt, m\u00e1r probl\u00e9m\u00e1s lehet. K\u00e9t okb\u00f3l is:

                  • Ha a sz\u00e1lf\u00fcggv\u00e9ny gyorsan lefut (egy kliens kiszolg\u00e1l\u00e1sa gyors), akkor a CPU nagy r\u00e9sz\u00e9t arra pazaroljuk, hogy sz\u00e1lakat ind\u00edtsunk \u00e9s \u00e1ll\u00edtsunk le, ezek ugyanis \u00f6nmagukban is er\u0151forr\u00e1sig\u00e9nyesek.
                  • T\u00fal nagy sz\u00e1m\u00fa sz\u00e1l is l\u00e9trej\u00f6het, ennyit kell \u00fctemeznie az oper\u00e1ci\u00f3s rendszernek, ami feleslegesen pazarolja az er\u0151forr\u00e1sokat.

                  Egy m\u00e1sik probl\u00e9ma jelen megold\u00e1sunkkal: mivel a sz\u00e1m\u00edt\u00e1s \u00fan. el\u0151t\u00e9rsz\u00e1lon fut (az \u00fajonnan l\u00e9trehozott sz\u00e1lak alap\u00e9rtelmez\u00e9sben el\u0151t\u00e9rsz\u00e1lak), hi\u00e1ba z\u00e1rjuk be az alkalmaz\u00e1st, a program tov\u00e1bb fut a h\u00e1tt\u00e9rben mindaddig, am\u00edg v\u00e9gre nem hajt\u00f3dik az utolj\u00e1ra ind\u00edtott sz\u00e1mol\u00e1s is: egy processz fut\u00e1sa ugyanis akkor fejez\u0151dik csak be, ha m\u00e1r nincs fut\u00f3 el\u0151t\u00e9rsz\u00e1la.

                  M\u00f3dos\u00edtsuk a gomb esem\u00e9nykezel\u0151j\u00e9t, hogy \u00faj sz\u00e1l ind\u00edt\u00e1sa helyett threadpool sz\u00e1lon futtassa a sz\u00e1m\u00edt\u00e1st. Ehhez csak a gombnyom\u00e1s esem\u00e9nykezel\u0151j\u00e9t kell ism\u00e9t \u00e1t\u00edrni.

                  private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        ThreadPool.QueueUserWorkItem(CalculatorThread, parameters);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n

                  Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st, \u00e9s vegy\u00fck \u00e9szre, hogy az alkalmaz\u00e1s az ablak bez\u00e1r\u00e1sakor azonnal le\u00e1ll, nem foglalkozik az esetlegesen m\u00e9g fut\u00f3 sz\u00e1lakkal (mert a threadpool sz\u00e1lak h\u00e1tt\u00e9r sz\u00e1lak).

                  "},{"location":"labor/4-tobbszalu/#5-feladat-termelo-fogyaszto-alapu-megoldas","title":"5. Feladat \u2013 Termel\u0151-fogyaszt\u00f3 alap\u00fa megold\u00e1s","text":"

                  Az el\u0151z\u0151 feladatok megold\u00e1sa sor\u00e1n \u00f6nmag\u00e1ban egy j\u00f3l m\u0171k\u00f6d\u0151 komplett megold\u00e1s\u00e1t kaptuk az eredeti probl\u00e9m\u00e1nak, mely lehet\u0151v\u00e9 teszi, hogy ak\u00e1r t\u00f6bb munkasz\u00e1l is p\u00e1rhuzamosan dolgozzon a h\u00e1tt\u00e9rben a sz\u00e1m\u00edt\u00e1son, ha a gombot sokszor egym\u00e1s ut\u00e1n megnyomjuk. A k\u00f6vetkez\u0151kben \u00fagy fogjuk m\u00f3dos\u00edtani az alkalmaz\u00e1sunkat, hogy a gombnyom\u00e1sra ne mindig keletkezzen \u00faj sz\u00e1l, hanem a feladatok beker\u00fcljenek egy feladatsorba, ahonnan t\u00f6bb, a h\u00e1tt\u00e9rben folyamatosan fut\u00f3 sz\u00e1l egym\u00e1s ut\u00e1n fogja kivenni \u0151ket \u00e9s v\u00e9grehajtani. Ez a feladat a klasszikus termel\u0151-fogyaszt\u00f3 probl\u00e9ma, mely a gyakorlatban is sokszor el\u0151fordul, a m\u0171k\u00f6d\u00e9s\u00e9t az al\u00e1bbi \u00e1bra szeml\u00e9lteti.

                  Termel\u0151 fogyaszt\u00f3 vs ThreadPool

                  Ha belegondolunk, a ThreadPool is egy speci\u00e1lis, a .NET \u00e1ltal sz\u00e1munkra biztos\u00edtott termel\u0151-fogyaszt\u00f3 \u00e9s \u00fctemez\u0151 mechanizmus. A k\u00f6vetkez\u0151kben egy m\u00e1s jelleg\u0171 termel\u0151-fogyaszt\u00f3 megold\u00e1st dolgozunk ki annak \u00e9rdek\u00e9ben, hogy bizonyos sz\u00e1lkezel\u00e9ssel kapcsolatos konkurencia probl\u00e9m\u00e1kkal tal\u00e1lkozhassunk.

                  A f\u0151sz\u00e1lunk a termel\u0151, a Calculate result gombra kattintva hoz l\u00e9tre egy \u00faj feladatot. Fogyaszt\u00f3/feldolgoz\u00f3 munkasz\u00e1lb\u00f3l az\u00e9rt ind\u00edtunk majd t\u00f6bbet, mert \u00edgy t\u00f6bb CPU magot is ki tudunk haszn\u00e1lni, valamint a feladatok v\u00e9grehajt\u00e1s\u00e1t p\u00e1rhuzamos\u00edtani tudjuk.

                  A feladatok ideiglenes t\u00e1rol\u00e1s\u00e1ra a kiindul\u00f3 projekt\u00fcnkben m\u00e1r n\u00e9mik\u00e9ppen el\u0151k\u00e9sz\u00edtett DataFifo oszt\u00e1lyt tudjuk haszn\u00e1lni (a Solution Explorerben a Data mapp\u00e1ban tal\u00e1lhat\u00f3). N\u00e9zz\u00fck meg a forr\u00e1sk\u00f3dj\u00e1t. Egy egyszer\u0171 FIFO sort val\u00f3s\u00edt meg, melyben double[] elemeket t\u00e1rol. A Put met\u00f3dus hozz\u00e1f\u0171zi a bels\u0151 lista v\u00e9g\u00e9hez az \u00faj p\u00e1rokat, m\u00edg a TryGet met\u00f3dus visszaadja (\u00e9s elt\u00e1vol\u00edtja) a bels\u0151 lista els\u0151 elem\u00e9t. Amennyiben a lista \u00fcres, a f\u00fcggv\u00e9ny nem tud visszaadni elemet. Ilyenkor a false visszat\u00e9r\u00e9si \u00e9rt\u00e9kkel jelzi ezt.

                  1. M\u00f3dos\u00edtsuk a gomb esem\u00e9nykezel\u0151j\u00e9t, hogy ne ThreadPoolba dolgozzon, hanem a FIFO-ba:

                    private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        _fifo.Put(parameters);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n
                  2. K\u00e9sz\u00edts\u00fck el az \u00faj sz\u00e1lkezel\u0151 f\u00fcggv\u00e9ny na\u00edv implement\u00e1ci\u00f3j\u00e1t az \u0171rlap oszt\u00e1lyunkban:

                    private void WorkerThread()\n{\n    while (true)\n    {\n        if (_fifo.TryGet(out var data))\n        {\n            double result = Algorithms.SuperAlgorithm.Calculate(data);\n            ShowResult(data, result);\n        }\n\n        Thread.Sleep(500);\n    }\n}\n

                    A Thread.Sleep bevezet\u00e9s\u00e9re az\u00e9rt van sz\u00fcks\u00e9g, mert e n\u00e9lk\u00fcl a munkasz\u00e1lak \u00fcres FIFO eset\u00e9n folyamatosan feleslegesen p\u00f6r\u00f6gn\u00e9nek, semmi hasznos m\u0171veletet nem v\u00e9gezve is 100%-ban kiterheln\u00e9nek egy-egy CPU magot. Megold\u00e1sunk nem ide\u00e1lis, k\u00e9s\u0151bb tov\u00e1bbfejlesztj\u00fck.

                  3. Hozzuk l\u00e9tre, \u00e9s ind\u00edtsuk el a feldolgoz\u00f3 sz\u00e1lakat a konstruktorban:

                    new Thread(WorkerThread) { Name = \"Worker thread 1\" }.Start();\nnew Thread(WorkerThread) { Name = \"Worker thread 2\" }.Start();\nnew Thread(WorkerThread) { Name = \"Worker thread 3\" }.Start();\n
                  4. Ind\u00edtsuk el az alkalmaz\u00e1st, majd z\u00e1rjuk is be azonnal an\u00e9lk\u00fcl, hogy a Calculate Result gombra kattintan\u00e1nk. Az tapasztaljuk, hogy az ablakunk bez\u00e1r\u00f3dik ugyan, de a processz\u00fcnk tov\u00e1bb fut, az alkalmaz\u00e1s bez\u00e1r\u00e1s\u00e1ra csak a Visual Studiob\u00f3l, vagy a Task Managerb\u0151l van lehet\u0151s\u00e9g:

                    A feldolgoz\u00f3 sz\u00e1lak el\u0151t\u00e9rsz\u00e1lak, kil\u00e9p\u00e9skor megakad\u00e1lyozz\u00e1k a processz megsz\u0171n\u00e9s\u00e9t. Az egyik megold\u00e1s az lehetne, ha a sz\u00e1lak IsBackground tulajdons\u00e1g\u00e1t true-ra \u00e1ll\u00edtan\u00e1nk a l\u00e9trehoz\u00e1sukat k\u00f6vet\u0151en. A m\u00e1sik megold\u00e1s, hogy kil\u00e9p\u00e9skor gondoskodunk a feldolgoz\u00f3 sz\u00e1lak kil\u00e9ptet\u00e9s\u00e9r\u0151l. Egyel\u0151re tegy\u00fck f\u00e9lre ezt a probl\u00e9m\u00e1t, k\u00e9s\u0151bb visszat\u00e9r\u00fcnk r\u00e1.

                  5. Ind\u00edtsuk el az alkalmaz\u00e1st azt tapasztaljuk, hogy miut\u00e1n kattintunk a Calculate Result gombon (csak egyszer kattintsunk rajta) nagy val\u00f3sz\u00edn\u0171s\u00e9ggel kiv\u00e9telt fogunk kapni. A probl\u00e9ma az, hogy a DataFifo nem sz\u00e1lbiztos, inkonzisztens\u00e9 v\u00e1lt. K\u00e9t ered\u0151 ok is h\u00faz\u00f3dik a h\u00e1tt\u00e9rben:

                  "},{"location":"labor/4-tobbszalu/#problema-1","title":"Probl\u00e9ma 1","text":"

                  N\u00e9zz\u00fck a k\u00f6vetkez\u0151 forgat\u00f3k\u00f6nyvet:

                  1. A sor \u00fcres. A feldolgoz\u00f3 sz\u00e1lak egy while ciklusban folyamatosan pollozz\u00e1k a FIFO-t, vagyis h\u00edvj\u00e1k a TryGet met\u00f3dus\u00e1t.
                  2. A felhaszn\u00e1l\u00f3 egy feladatot tesz a sorba.
                  3. Az egyik feldolgoz\u00f3 sz\u00e1l a TryGet met\u00f3dusban azt l\u00e1tja, van adat a sorban, vagyis if ( _innerList.Count > 0 ) k\u00f3dsor felt\u00e9tele teljes\u00fcl, \u00e9s r\u00e1l\u00e9p a k\u00f6vetkez\u0151 k\u00f3dsorra. Tegy\u00fck fel, hogy ez a sz\u00e1l ebben a pillanatban elveszti a fut\u00e1si jog\u00e1t, m\u00e1r nincs ideje kivenni az adatot a sorb\u00f3l.
                  4. Egy m\u00e1sik feldolgoz\u00f3 sz\u00e1l is \u00e9ppen ekkor ejti meg az if ( _innerList.Count > 0 ) vizsg\u00e1latot, n\u00e1la is teljes\u00fcl a felt\u00e9tel, \u00e9s ez a sz\u00e1l ki is veszi az adatot a sorb\u00f3l.
                  5. Az els\u0151 sz\u00e1lunk \u00fajra \u00fctemez\u00e9sre ker\u00fcl, fel\u00e9bred, \u0151 is megpr\u00f3b\u00e1lja kivenni az adatot a sorb\u00f3l: a sor viszont m\u00e1r \u00fcres, a m\u00e1sik sz\u00e1lunk kivette az egyetlen adatot a sorb\u00f3l az orra el\u0151tt. \u00cdgy az _innerList[0] hozz\u00e1f\u00e9r\u00e9s kiv\u00e9telt eredm\u00e9nyez.

                  Ezt a probl\u00e9m\u00e1t csak \u00fagy tudjuk elker\u00fclni, ha a sor \u00fcress\u00e9g\u00e9nek a vizsg\u00e1lat\u00e1t \u00e9s az elem kiv\u00e9tel\u00e9t \"oszthatatlann\u00e1\" tessz\u00fck: ez azt jelenti, hogy am\u00edg az egyik sz\u00e1l nem v\u00e9gzett mindkett\u0151vel, addig a t\u00f6bbi sz\u00e1lnak v\u00e1rnia kell!

                  Thread.Sleep(500)

                  Az \u00fcress\u00e9gvizsg\u00e1latot figyel\u0151 k\u00f3dsort k\u00f6vet\u0151 Thread.Sleep(500); k\u00f3dsornak csak az a szerepe a p\u00e9ldak\u00f3dunkban, hogy a fenti peches forgat\u00f3k\u00f6nyv bek\u00f6vetkez\u00e9s\u00e9nek a val\u00f3sz\u00edn\u0171s\u00e9g\u00e9t megn\u00f6velje, s \u00edgy a p\u00e9ld\u00e1t szeml\u00e9letesebb\u00e9 tegye (mivel ilyenkor szinte biztos, hogy \u00e1t\u00fctemez\u0151dik a sz\u00e1l). A k\u00e9s\u0151bbiekben ezt ki is fogjuk venni, egyel\u0151re hagyjuk benne.

                  "},{"location":"labor/4-tobbszalu/#problema-2","title":"Probl\u00e9ma 2","text":"

                  A DataFifo oszt\u00e1ly egyid\u0151ben t\u00f6bb sz\u00e1lb\u00f3l is hozz\u00e1f\u00e9rhet a List<double[]> t\u00edpus\u00fa _innerList tagv\u00e1ltoz\u00f3hoz. Ugyanakkor, ha megn\u00e9zz\u00fck a List<T> dokument\u00e1ci\u00f3j\u00e1t, azt tal\u00e1ljuk, hogy az oszt\u00e1ly nem sz\u00e1lbiztos (not thread safe). Ez esetben viszont ezt nem tehetj\u00fck meg, nek\u00fcnk kell a k\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1st z\u00e1rakkal biztos\u00edtanunk: meg kell oldjuk, hogy a sz\u00e1laink egyid\u0151ben csak egy met\u00f3dus\u00e1hoz / tulajdons\u00e1g\u00e1hoz / tagv\u00e1ltoz\u00f3j\u00e1hoz f\u00e9rjenek hozz\u00e1 (pontosabban inkonzisztencia csak egyidej\u0171 \u00edr\u00e1s, illetve egyidej\u0171 \u00edr\u00e1s \u00e9s olvas\u00e1s eset\u00e9n l\u00e9phet fel, de az \u00edr\u00f3kat \u00e9s az olvas\u00f3kat a legt\u00f6bb esetben nem szoktuk megk\u00fcl\u00f6nb\u00f6ztetni, itt sem tessz\u00fck).

                  A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben a DataFifo oszt\u00e1lyunkat sz\u00e1lbiztoss\u00e1 tessz\u00fck, amivel megakad\u00e1lyozzuk, hogy a fenti k\u00e9t probl\u00e9ma bek\u00f6vetkezhessen.

                  "},{"location":"labor/4-tobbszalu/#6-feladat-tegyuk-szabiztossa-a-datafifo-osztalyt","title":"6. feladat \u2013 Tegy\u00fck sz\u00e1biztoss\u00e1 a DataFifo oszt\u00e1lyt","text":"

                  A DataFifo oszt\u00e1ly sz\u00e1lbiztoss\u00e1 t\u00e9tel\u00e9hez sz\u00fcks\u00e9g\u00fcnk van egy objektumra (ez b\u00e1rmilyen referencia t\u00edpus\u00fa objektum lehet), melyet kulcsk\u00e9nt haszn\u00e1lhatunk a z\u00e1rol\u00e1sn\u00e1l. Ezt k\u00f6vet\u0151en a lock kulcssz\u00f3 seg\u00edts\u00e9g\u00e9vel el tudjuk \u00e9rni, hogy egyszerre mindig csak egy sz\u00e1l tart\u00f3zkodjon az adott kulccsal v\u00e9dett blokkokban.

                  1. Vegy\u00fcnk fel egy object t\u00edpus\u00fa mez\u0151t _syncRoot n\u00e9ven a DataFifo oszt\u00e1lyba.

                    private object _syncRoot = new object();\n
                  2. Eg\u00e9sz\u00edts\u00fck ki a Put \u00e9s a TryGet f\u00fcggv\u00e9nyeket a z\u00e1rol\u00e1ssal.

                    public void Put(double[] data)\n{\n    lock (_syncRoot)\n    {\n        _innerList.Add(data); \n    }\n}\n
                    public bool TryGet(out double[] data)\n{\n    lock (_syncRoot)\n    {\n        if (_innerList.Count > 0)\n        {\n            Thread.Sleep(500);\n\n            data = _innerList[0];\n            _innerList.RemoveAt(0);\n            return true;\n        }\n\n        data = null;\n        return false;\n    }\n}\n

                    Surround with

                    Haszn\u00e1ljuk a Visual Studio Surround with funkci\u00f3j\u00e1t a CTRL + K, CTRL + S billenty\u0171 kombin\u00e1ci\u00f3j\u00e1val a k\u00f6r\u00fclvenni k\u00edv\u00e1nt kijel\u00f6lt k\u00f3dr\u00e9szleten.

                  Most m\u00e1r nem szabad kiv\u00e9telt kapnunk.

                  Ki is vehetj\u00fck a TryGet met\u00f3dusb\u00f3l a mesters\u00e9ges k\u00e9sleltet\u00e9st (Thread.Sleep(500); sor).

                  Lockol\u00e1s this-en

                  Felmer\u00fclhet a k\u00e9rd\u00e9s, hogy mi\u00e9rt vezett\u00fcnk be egy k\u00fcl\u00f6n _syncRoot tagv\u00e1ltoz\u00f3t \u00e9s haszn\u00e1ltuk ezt z\u00e1rol\u00e1sra a lock param\u00e9terek\u00e9nt, amikor a this-t is haszn\u00e1lhattuk volna helyette (a DataFifo referencia t\u00edpus, \u00edgy ennek nem lenne akad\u00e1lya). A this alkalmaz\u00e1sa azonban s\u00e9rten\u00e9 az oszt\u00e1lyunk egys\u00e9gbez\u00e1r\u00e1s\u00e1t! Ne feledj\u00fck: a this egy referencia az objektumunkra, de m\u00e1s oszt\u00e1lyoknak is van ugyanerre az objektumra referenci\u00e1juk (pl. eset\u00fcnkben a MainWindow-nak van referenci\u00e1ja a DataFifo-ra), \u00e9s ha ezek a k\u00fcls\u0151 oszt\u00e1lyok z\u00e1rat tesznek a lock seg\u00edts\u00e9g\u00e9vel az objektumra, akkor az \"interfer\u00e1l\" az \u00e1ltalunk az oszt\u00e1lyon bel\u00fck haszn\u00e1lt z\u00e1rol\u00e1ssal (mivel this alkalmaz\u00e1sa miatt a k\u00fcls\u0151 \u00e9s bels\u0151 lock-ok param\u00e9tere ugyanaz lesz). \u00cdgy pl. egy k\u00fcls\u0151 z\u00e1rral teljesen meg lehet \"b\u00e9n\u00edtani\" a TryGet \u00e9s Put m\u0171velet m\u0171k\u00f6d\u00e9s\u00e9t. Ezzel szemben az \u00e1ltalunk v\u00e1lasztott megold\u00e1sban a lock param\u00e9tere, a _syncRoot v\u00e1ltoz\u00f3 priv\u00e1t, ehhez m\u00e1r k\u00fcls\u0151 oszt\u00e1lyok nem f\u00e9rhetnek hozz\u00e1, \u00edgy nem is zavarhatj\u00e1k meg az oszt\u00e1lyunk bels\u0151 m\u0171k\u00f6d\u00e9s\u00e9t.

                  "},{"location":"labor/4-tobbszalu/#7-feladat-hatekony-jelzes-megvalositasa","title":"7. feladat \u2013 Hat\u00e9kony jelz\u00e9s megval\u00f3s\u00edt\u00e1sa","text":""},{"location":"labor/4-tobbszalu/#manualresetevent-hasznalata","title":"ManualResetEvent haszn\u00e1lata","text":"

                  A WorkerThread-ben folyamatosan fut\u00f3 while ciklus \u00fan. akt\u00edv v\u00e1rakoz\u00e1st val\u00f3s\u00edt meg, ami mindig ker\u00fclend\u0151. Ha a Thread.Sleep-et nem tett\u00fck volna a ciklusmagba, akkor ezzel maximumra ki is terheln\u00e9 a processzort. A Thread.Sleep megoldja ugyan a processzor terhel\u00e9s probl\u00e9m\u00e1t, de bevezet egy m\u00e1sikat: ha mindh\u00e1rom munkasz\u00e1lunk \u00e9ppen alv\u00f3 \u00e1llapotba l\u00e9pett, mikor be\u00e9rkezik egy \u00faj adat, akkor feleslegesen v\u00e1runk 500 ms-ot az adat feldolgoz\u00e1s\u00e1nak megkezd\u00e9s\u00e9ig.

                  A k\u00f6vetkez\u0151kben \u00fagy fogjuk m\u00f3dos\u00edtani az alkalmaz\u00e1st, hogy blokkolva v\u00e1rakozzon, am\u00edg adat nem ker\u00fcl a FIFO-ba (amikor viszont adat ker\u00fcl bele, azonnal kezdje meg a feldolgoz\u00e1st). Annak jelz\u00e9s\u00e9re, hogy van-e adat a sorban egy ManualResetEvent-et fogunk haszn\u00e1lni.

                  1. Adjunk hozz\u00e1 egy MaunalResetEvent p\u00e9ld\u00e1nyt a DataFifo oszt\u00e1lyunkhoz _hasData n\u00e9ven.

                    // A false konstruktor param\u00e9ter eredm\u00e9nyek\u00e9ppen kezdetben az esem\u00e9ny nem jelzett (kapu csukva)\nprivate ManualResetEvent _hasData = new ManualResetEvent(false);\n
                  2. A _hasData alkalmaz\u00e1sunkban kapuk\u00e9nt viselkedik. Amikor adat ker\u00fcl a list\u00e1ba \u201ekinyitjuk\u201d, m\u00edg amikor ki\u00fcr\u00fcl a lista \u201ebez\u00e1rjuk\u201d.

                    Az esem\u00e9ny szemantik\u00e1ja \u00e9s elnevez\u00e9se

                    L\u00e9nyeges, hogy j\u00f3 v\u00e1lasszuk meg az esem\u00e9ny\u00fcnk szemantik\u00e1j\u00e1t \u00e9s ezt a v\u00e1ltoz\u00f3nk nev\u00e9vel pontosan ki is fejezz\u00fck. A p\u00e9ld\u00e1nkban a _hasData n\u00e9v j\u00f3l kifejezi, hogy pontosan akkor \u00e9s csak akkor jelzett az esem\u00e9ny\u00fcnk (nyitott a kapu), amikor van feldolgozand\u00f3 adat. Most m\u00e1r \"csak\" az a dolgunk, hogy ezt a szemantik\u00e1t megval\u00f3s\u00edtsuk: jelzettbe tegy\u00fck az esem\u00e9nyt, mikor adat ker\u00fcl a FIFO-ba, \u00e9s jelzetlenbe, amikor ki\u00fcr\u00fcl a FIFO.

                    public void Put(double[] data)\n{\n    lock (_syncRoot)\n    {\n        _innerList.Add(data);\n        _hasData.Set();\n    }\n}\n
                    public bool TryGet(out double[] data)\n{\n    lock (_syncRoot)\n    {\n        if (_innerList.Count > 0)\n        {\n            data = _innerList[0];\n            _innerList.RemoveAt(0);\n            if (_innerList.Count == 0)\n            {\n                _hasData.Reset();\n            }\n\n            return true;\n        }\n\n        data = null;\n        return false;\n    }\n}\n
                  "},{"location":"labor/4-tobbszalu/#jelzesre-varakozas-blokkolo-a-get","title":"Jelz\u00e9sre v\u00e1rakoz\u00e1s (blokkol\u00f3 a Get)","text":"

                  Az el\u0151z\u0151 pontban megoldottuk a jelz\u00e9st, \u00e1m ez \u00f6nmag\u00e1ban nem sokat \u00e9r, hiszen nem v\u00e1rakoznak r\u00e1. Ennek megval\u00f3s\u00edt\u00e1sa j\u00f6n most.

                  1. M\u00f3dos\u00edtsuk a met\u00f3dust az al\u00e1bbiak szerint: sz\u00farjuk be a _hasData esem\u00e9nyre v\u00e1rakoz\u00e1st.

                    public bool TryGet(out double[] data)\n{\n    lock (_syncRoot)\n    {\n        _hasData.WaitOne();\n\n        if (_innerList.Count > 0)\n            // ...\n

                    A WaitOne m\u0171velet visszat\u00e9r\u00e9si \u00e9rt\u00e9ke

                    A WaitOne m\u0171velet egy bool \u00e9rt\u00e9kkel t\u00e9r vissza, mely igaz, ha a WaitOne param\u00e9ter\u00e9ben megadott id\u0151korl\u00e1t el\u0151tt jelzett \u00e1llapotba ker\u00fcl az esem\u00e9ny (ill. ennek megfelel\u0151en hamis, ha lej\u00e1rt az id\u0151korl\u00e1t). A p\u00e9ld\u00e1nkban nem adtunk meg id\u0151korl\u00e1tot param\u00e9terben, mely v\u00e9gtelen id\u0151korl\u00e1t alkalmaz\u00e1s\u00e1t jelenti. Ennek megfelel\u0151en nem is vizsg\u00e1ljuk a visszat\u00e9r\u00e9si \u00e9rt\u00e9k\u00e9t (mert v\u00e9gtelen ideig v\u00e1r jelz\u00e9sre).

                  2. Ezzel a Thread.Sleep a WorkerThread-ben feleslegess\u00e9 v\u00e1lt, kommentezz\u00fck ki!

                    A fenti megold\u00e1s futtat\u00e1sakor azt tapasztaljuk, hogy az alkalmaz\u00e1sunk fel\u00fclete az els\u0151 gombnyom\u00e1st k\u00f6vet\u0151en befagy. Az el\u0151z\u0151 megold\u00e1sunkban ugyanis egy amat\u0151r hib\u00e1t k\u00f6vett\u00fcnk el. A lock-olt k\u00f3dr\u00e9szleten bel\u00fcl v\u00e1rakozunk a _hasData jelz\u00e9s\u00e9re, \u00edgy a f\u0151sz\u00e1lnak lehet\u0151s\u00e9ge sincs arra, hogy a Put m\u0171veletben (egy szint\u00e9n lock-kal v\u00e9dett r\u00e9szen bel\u00fcl) jelz\u00e9st k\u00fcldj\u00f6n _hasData-val. Gyakorlatilag egy holtpont (deadlock) helyzet alakult ki. Fontos, hogy a k\u00f3dot n\u00e9zve gondoljuk \u00e1t r\u00e9szleteiben:

                    • A TryGet-ben az egyik munkasz\u00e1l (mely bejutott a lock blokkba a h\u00e1rom k\u00f6z\u00fcl), a _hasData.WaitOne() sorban arra v\u00e1r, hogy a f\u0151 sz\u00e1l Put-ban a _hasData-t jelzettbe \u00e1ll\u00edtsa.
                    • A Put-ban a lock sorban f\u0151 sz\u00e1l arra v\u00e1r, hogy az el\u0151z\u0151 pontban eml\u00edtett munkasz\u00e1l a TryGet-ben kil\u00e9pjen a lock blokkb\u00f3l.

                    K\u00f6lcs\u00f6n\u00f6sen egym\u00e1sra v\u00e1rnak v\u00e9gtelen ideig, ez a holtpont/deadlock klasszikus esete.

                    Pr\u00f3b\u00e1lkozhatn\u00e1nk egy id\u0151korl\u00e1t megad\u00e1s\u00e1val (ms) a v\u00e1rakoz\u00e1sn\u00e1l (ez nem kell megval\u00f3s\u00edtani):

                    if (_hasData.WaitOne(100))\n

                    Ez \u00f6nmag\u00e1ban sem lenne eleg\u00e1ns megold\u00e1s, r\u00e1ad\u00e1sul a folyamatosan polloz\u00f3 munkasz\u00e1lak jelent\u0151sen ki\u00e9heztetn\u00e9k a Put-ot h\u00edv\u00f3 sz\u00e1lat! Helyette, az eleg\u00e1ns \u00e9s k\u00f6vetend\u0151 minta az, hogy lock-on bel\u00fcl ker\u00fclj\u00fck a blokkolva v\u00e1rakoz\u00e1st.

                    Jav\u00edt\u00e1sk\u00e9nt cser\u00e9lj\u00fck meg a lock-ot \u00e9s a WaitOne-t:

                    public bool TryGet(out double[] data)\n{\n    _hasData.WaitOne();\n\n    lock (_syncRoot)\n    {\n        if (_innerList.Count > 0)\n        {\n            data = _innerList[0];\n            _innerList.RemoveAt(0);\n            if (_innerList.Count == 0)\n            {\n                _hasData.Reset();\n            }\n\n            return true;\n        }\n\n        data = null;\n        return false;\n    }\n}\n

                    Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st, most m\u00e1r j\u00f3l m\u0171k\u00f6dik.

                  3. A lock-on bel\u00fcli \u00fcress\u00e9g-vizsg\u00e1lat szerepe.

                    Az el\u0151z\u0151 l\u00e9p\u00e9sben a TryGet-ben bevezett\u00fcnk _hasData n\u00e9ven egy MaunalResetEvent objektumot. Ez pontosan akkor van jelzett \u00e1llapotban, amikor a FIFO-ban van adat. K\u00e9rd\u00e9s, sz\u00fcks\u00e9g van-e m\u00e9g most is a lock blokkban az sor \u00fcress\u00e9g vizsg\u00e1latra (if (_innerList.Count > 0)). Els\u0151 \u00e9rz\u00e9sre redund\u00e1nsnak gondolhatjuk. De pr\u00f3b\u00e1ljuk ki, az if-ben az \u00fcress\u00e9gvizsg\u00e1lat helyett adjunk meg egy fix true \u00e9rt\u00e9ket, ezzel semleges\u00edtve az if hat\u00e1s\u00e1t (az\u00e9rt dolgozunk \u00edgy, hogy k\u00f6nny\u0171 legyen visszacsin\u00e1lni):

                        ...\n    lock (_syncRoot)\n    {\n        if (true)\n        {\n            data = _innerList[0];\n            ...\n}\n

                    Pr\u00f3b\u00e1ljuk ki. Egy kiv\u00e9telt fogunk kapni, amikor kattintunk a gombon: \u00edgy m\u00e1r nem sz\u00e1lbiztos a megold\u00e1sunk. Vezess\u00fck le, mi\u00e9rt:

                    • Amikor elindul az alkalmaz\u00e1s, mindh\u00e1rom feldolgoz\u00f3 sz\u00e1l a TryGet _hasData.WaitOne(); sor\u00e1n\u00e1l v\u00e1r arra, hogy adat ker\u00fclj\u00f6n a FIFO-ba.
                    • A gombra kattint\u00e1skor a Put m\u0171velet _hasData-t jelzettre \u00e1ll\u00edtja.
                    • A TryGet _hasData.WaitOne(); sor\u00e1n mindh\u00e1rom sz\u00e1l \u00e1tjut (ez egy ManualResetEvent, ha jelezett, minden sz\u00e1l mehet tov\u00e1bb).
                    • A TryGet lock blokkj\u00e1ba egyetlen sz\u00e1l jut be, a m\u00e1sik kett\u0151 itt v\u00e1r (lock blokkban egyszerre egy sz\u00e1l lehet): ez a sz\u00e1l kiveszi az egyetlen elemet az _innerList list\u00e1b\u00f3l, majd elhagyja a lock blokkot.
                    • Most m\u00e1r be tud jutni a lock-n\u00e1l v\u00e1rakoz\u00f3 k\u00e9t sz\u00e1lb\u00f3l (ezek m\u00e1r kor\u00e1bban t\u00faljutottak a hasData.WaitOne() h\u00edv\u00e1son!!!) egy m\u00e1sik is a lock blokkba, az is megpr\u00f3b\u00e1lja a 0. elemet kivenni az _innerList list\u00e1b\u00f3l. De az m\u00e1r nincs ott (az el\u0151z\u0151 l\u00e9p\u00e9sben az els\u0151nek bejut\u00f3 sz\u00e1l elcsente az orra el\u0151l): ebb\u0151l lesz a kiv\u00e9tel.

                    A megold\u00e1s: biztos\u00edtani kell a lock blockban, hogy ha id\u0151k\u00f6zben egy m\u00e1sik sz\u00e1l ki\u00fcr\u00edtette a sort, akkor a sz\u00e1lunk m\u00e1r ne pr\u00f3b\u00e1ljon elemet kivenni bel\u0151le. Vagyis vissza kell tenni a kor\u00e1bbi \u00fcress\u00e9g vizsg\u00e1latot. Tegy\u00fck is ezt meg! A megold\u00e1sunk \u00edgy j\u00f3l m\u0171k\u00f6dik. El\u0151fordulhat ugyan, hogy feleslegesen fordulunk a list\u00e1hoz, de ezzel \u00edgy most megel\u00e9gsz\u00fcnk.

                  \u00d6sszefoglalva:

                  • Az \u00fcress\u00e9g vizsg\u00e1latra a ManualResetEvent bevezet\u00e9se ut\u00e1n is sz\u00fcks\u00e9g van.
                  • A ManualResetEvent az a c\u00e9lja, hogy feleslegesen ne pollozzuk gyakran a sort, ha az \u00fcres, vagyis az \u00fan. akt\u00edv v\u00e1rakoz\u00e1st ker\u00fclj\u00fck el a seg\u00edts\u00e9g\u00e9vel.

                  A konkurens, t\u00f6bbsz\u00e1l\u00fa k\u00f6rnyezetben val\u00f3 programoz\u00e1s neh\u00e9zs\u00e9gei

                  J\u00f3l illusztr\u00e1lja a feladat, hogy milyen alapos \u00e1tgondol\u00e1st ig\u00e9nyel a konkurens, t\u00f6bbsz\u00e1l\u00fa k\u00f6rnyezetben val\u00f3 programoz\u00e1s. Tulajdonk\u00e9ppen m\u00e9g szerencs\u00e9nk is volt az el\u0151z\u0151ekben, mert j\u00f3l reproduk\u00e1lhat\u00f3an el\u0151j\u00f6tt a hiba. A gyakorlatban azonban ez ritk\u00e1n van \u00edgy. Sajnos sokkal gyakoribb, hogy a konkurenciahib\u00e1k id\u0151nk\u00e9nti, nem reproduk\u00e1lhat\u00f3 probl\u00e9m\u00e1kat okoznak. Az ilyen jelleg\u0171 feladatok megold\u00e1s\u00e1t mindig nagyon \u00e1t kell gondolni, nem lehet az \"addig-pr\u00f3b\u00e1lkozom-m\u00edg-j\u00f3-nem-lesz-a-k\u00e9zi-teszt-sor\u00e1n\" elv ment\u00e9n leprogramozni.

                  System.Collections.Concurrent

                  A .NET keretrendszerben t\u00f6bb be\u00e9p\u00edtett sz\u00e1lbiztoss\u00e1gra felk\u00e9sz\u00edtett oszt\u00e1ly is tal\u00e1lhat\u00f3 a System.Collections.Concurrent n\u00e9vt\u00e9rben. A fenti p\u00e9ld\u00e1ban a DataFifo oszt\u00e1lyt a System.Collections.Concurrent.ConcurrentQueue oszt\u00e1llyal kiv\u00e1lthattuk volna.

                  "},{"location":"labor/4-tobbszalu/#8-feladat-kulturalt-leallas","title":"8. feladat \u2013 Kultur\u00e1lt le\u00e1ll\u00e1s","text":"

                  Kor\u00e1bban f\u00e9lretett\u00fck azt a probl\u00e9m\u00e1t, hogy az ablakunk bez\u00e1r\u00e1sakor a processz\u00fcnk \u201eberagad\u201d, ugyanis a feldolgoz\u00f3 munkasz\u00e1lak el\u0151t\u00e9rsz\u00e1lak, kil\u00e9ptet\u00e9s\u00fcket eddig nem oldottuk meg. C\u00e9lunk, hogy a v\u00e9gtelen while ciklust kiv\u00e1ltva a munkasz\u00e1laink az alkalmaz\u00e1s bez\u00e1r\u00e1sakor kultur\u00e1lt m\u00f3don \u00e1lljanak le.

                  1. Egy ManualResetEvent seg\u00edts\u00e9g\u00e9vel jelezz\u00fck a le\u00e1ll\u00edt\u00e1st a FIFO-ban a TryGet-ben t\u00f6rt\u00e9n\u0151 v\u00e1rakoz\u00e1s sor\u00e1n. A FIFO-ban vegy\u00fcnk fel egy \u00faj ManualResetEvent-et, \u00e9s vezess\u00fcnk be egy Release m\u0171veletet, amellyel a v\u00e1rakoz\u00e1sainkat z\u00e1rhatjuk r\u00f6vidre (\u00faj esem\u00e9ny\u00fcnk jelzett \u00e1llapotba \u00e1ll\u00edthat\u00f3).

                    private ManualResetEvent _releaseTryGet = new ManualResetEvent(false);\n\npublic void Release()\n{\n    _releaseTryGet.Set();\n}\n
                  2. A TryGet-ben erre az esem\u00e9nyre is v\u00e1rakozzunk. A WaitAny met\u00f3dus akkor engedi tov\u00e1bb a futtat\u00e1st, ha a param\u00e9terk\u00e9nt megadott WaitHandle t\u00edpus\u00fa objektumok k\u00f6z\u00fcl valamelyik jelzett \u00e1llapotba ker\u00fcl, \u00e9s visszaadja annak t\u00f6mbb\u00e9li index\u00e9t. T\u00e9nyleges adatfeldolgoz\u00e1st pedig csak akkor szeretn\u00e9nk, ha a _hasData jelzett (amikor is a WaitAny 0-val t\u00e9r vissza).

                    public bool TryGet(out double[] data)\n{\n    if (WaitHandle.WaitAny(new[] { _hasData, _releaseTryGet }) == 0)\n    {\n        lock (_syncRoot)\n        {\n
                  3. MainWindow.xaml.cs-ban vegy\u00fcnk fel egy flag tagv\u00e1ltoz\u00f3t a bez\u00e1r\u00e1s jelz\u00e9s\u00e9re:

                    private bool _isClosed = false;\n
                  4. A f\u0151ablak bez\u00e1r\u00e1sakor \u00e1ll\u00edtsuk jelzettre az \u00faj esem\u00e9nyt \u00e9s billents\u00fcnk be a flag-et is: a MainWindow oszt\u00e1ly Closed esem\u00e9ny\u00e9re iratkozzunk fel a konstruktorban, \u00e9s \u00edrjuk meg a megfelel\u0151 esem\u00e9nykezel\u0151 f\u00fcggv\u00e9nyt:

                    public MainWindow()\n{\n    ...\n\n    Closed += MainWindow_Closed;\n}\n\nprivate void MainWindow_Closed(object sender, WindowEventArgs args)\n{\n    _isClosed = true;\n    _fifo.Release();\n}\n
                  5. \u00cdrjuk \u00e1t a while ciklust az el\u0151z\u0151 pontban felvett flag figyel\u00e9s\u00e9re.

                    private void WorkerThread()\n{\n    while (!_isClosed)\n    {\n
                  6. V\u00e9g\u00fcl biztos\u00edtsuk, hogy a m\u00e1r bez\u00e1r\u00f3d\u00f3 ablak eset\u00e9ben ne pr\u00f3b\u00e1ljunk \u00fczeneteket ki\u00edrni

                    private void ShowResult(double[] parameters, double result)\n{\n    if (_isClosed)\n        return;\n
                  7. Futtassuk az alkalmaz\u00e1st, \u00e9s ellen\u0151rizz\u00fck, kil\u00e9p\u00e9skor az processz\u00fcnk val\u00f3ban befejezi-e a fut\u00e1s\u00e1t.

                  "},{"location":"labor/4-tobbszalu/#kitekintes-task-async-await","title":"Kitekint\u00e9s: Task, async, await","text":"

                  A gyakorlat sor\u00e1n az alacsonyabb szint\u0171 sz\u00e1lkezel\u00e9si technik\u00e1kkal k\u00edv\u00e1ntunk megismerkedni. Ugyanakkor megold\u00e1sunkat (legal\u00e1bbis r\u00e9szben) \u00e9p\u00edthett\u00fck volna a .NET aszinkron programoz\u00e1st t\u00e1mogat\u00f3 magasabb szint\u0171 eszk\u00f6zeire \u00e9s mechanizmusaira, \u00fagymint Task/Task<T> oszt\u00e1lyok \u00e9s async/await kulcsszavak.

                  "},{"location":"labor/4-tobbszalu/index_ger/","title":"4. Erstellung von mehrf\u00e4digen Anwendungen","text":""},{"location":"labor/4-tobbszalu/index_ger/#das-ziel-der-ubung","title":"Das Ziel der \u00dcbung","text":"

                  Ziel der \u00dcbung ist, dass die Studenten mit den Grunds\u00e4tzen kennenzulernen, die bei der Programmierung von mehreren Threads beachtet werden m\u00fcssen. Behandelte Themen (unter anderem):

                  • Einen Thread starten (Thread)
                  • Einen Thread beenden
                  • Erstellen von faedensicheren (thread safe) Klassen mit dem Schl\u00fcsselwort lock
                  • ThreadPool verwenden
                  • Signalisieren und Synchronisation von auf Signal wartenden Threads mit der Hilfe von ManualResetEvent (WaitHandle)
                  • Besonderheiten des WinUI-Threadings (DispatcherQueue)

                  Da das Thema sehr umfangreich ist, werden Sie nat\u00fcrlich nur Grundkenntnisse erwerben, aber mit diesem Wissen werden Sie in der Lage sein, komplexere Aufgaben selbst\u00e4ndig zu bearbeiten.

                  Zugeh\u00f6rige Vorlesungen: Entwicklung konkurrierender (meghrf\u00e4digen) Anwendungen.

                  "},{"location":"labor/4-tobbszalu/index_ger/#voraussetzungen","title":"Voraussetzungen","text":"

                  Die f\u00fcr die Durchf\u00fchrung der \u00dcbung ben\u00f6tigten Werkzeuge:

                  • Visual Studio 2022
                    • Windows Desktop Development Workload
                  • Betriebssystem Windows 10 oder Windows 11 (Linux und macOS nicht geeignet)
                  "},{"location":"labor/4-tobbszalu/index_ger/#losung","title":"L\u00f6sung","text":"Laden Sie die fertige L\u00f6sung herunter

                  Es ist wichtig, dass Sie sich w\u00e4hrend des Praktikums an die Anleitung halten. Es ist verboten (und sinnlos), die fertige L\u00f6sung herunterzuladen. Allerdings kann es bei der anschlie\u00dfenden Selbstein\u00fcbung n\u00fctzlich sein, die fertige L\u00f6sung zu \u00fcberpr\u00fcfen, daher stellen wir sie zur Verf\u00fcgung.

                  Die L\u00f6sung ist [verf\u00fcgbar auf GitHub] (https://github.com/bmeviauab00/lab-tobbszalu-kiindulo/tree/megoldas). Der einfachste Weg, es herunterzuladen, ist, den git clone-Zweig von der Kommandozeile aus zu klonen:

                  git clone https://github.com/bmeviauab00/lab-tobbszalu-kiindulo -b solved

                  Sie m\u00fcssen Git auf Ihrem Rechner installiert haben, weitere Informationen hier.

                  "},{"location":"labor/4-tobbszalu/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

                  Die Verwaltung parallel laufender Threads ist ein Bereich mit hoher Priorit\u00e4t, den alle Softwareentwickler zumindest in den Grundlagen kennen sollten. In der \u00dcbung l\u00f6sen wir grundlegende, aber vorrangige Probleme, so dass wir uns bem\u00fchen sollten, nicht nur das Endergebnis, sondern auch die Bedeutung und die Gr\u00fcnde f\u00fcr die von uns vorgenommenen \u00c4nderungen zu verstehen.

                  In dieser \u00dcbung werden wir einer einfachen WinUI-Anwendung mehrf\u00e4dige F\u00e4higkeiten hinzuf\u00fcgen und zunehmend komplexere Aufgaben l\u00f6sen. Das Grundproblem ist folgendes: Wir haben eine Funktion, die lange l\u00e4uft, und wie wir sehen werden, hat der \"direkte\" Aufruf \u00fcber die Benutzeroberfl\u00e4che unangenehme Folgen. W\u00e4hrend dem L\u00f6sen werden wir eine bestehende Anwendung mit eigenen Codezeile erg\u00e4nzen. Neue Zeilen, die eingef\u00fcgt werden sollen, sind in der Anleitung durch einen hervorgehobenen Hintergrund gekennzeichnet.

                  "},{"location":"labor/4-tobbszalu/index_ger/#0-aufgabe-kennenlernen-des-anfangsprojekt-vorbereitung","title":"0. Aufgabe - Kennenlernen des Anfangsprojekt, Vorbereitung","text":"

                  Klonen wir das Repository der urspr\u00fcnglichen Anwendung f\u00fcr \u00dcbung 4:

                  • \u00d6ffnen wir ein command prompt
                  • Navigieren wir zu einem Ordner unserer Wahl, zum Beispiel c:\\work\\NEPTUN
                  • Geben wir den folgenden Befehl ein: git clone https://github.com/bmeviauab00/lab-tobbszalu-kiindulo.git
                  • \u00d6ffnen wir das Solution SuperCalculator.sln in Visual Studio.

                  Unsere Aufgabe ist es, eine Benutzeroberfl\u00e4che unter Verwendung der WinUI-Technologie zu erstellen, um einen in bin\u00e4rer Form erreichbaren Algorithmus auszuf\u00fchren. Die bin\u00e4re Form von .NET ist eine Datei mit der Erweiterung .dll, die in der Programmiersprache eine Klassenbibliothek darstellt. In unserem Fall lautet der Dateiname Algorithms.dll, der sich im geklonten Git-Repository befindet.

                  In der Anfangsprojekt ist die Benutzeroberfl\u00e4che bereits vorbereitet. F\u00fchren wir die Anwendung aus:

                  In der Benutzeroberfl\u00e4che der Anwendung k\u00f6nnen wir die Eingabeparametern des Algorithmus angeben (double array of numbers): in unserem Beispiel rufen wir den Algorithmus immer mit zwei double Zahlenparametern auf, die in den zwei oberen Textfeldern angegeben werden k\u00f6nnen. Unsere Aufgabe ist es, den Algorithmus mit den angegebenen Parametern auszuf\u00fchren, falls wir auf die Taste Calculate Result klicken, und wenn er fertig ist, das Ergebnis mit den Eingabeparametern in einer neuen Zeile des Listenfeldes unterhalb des Results anzuzeigen.

                  In der n\u00e4chsten Schritten schauen wir zuerst das heruntergeladene Visual Studio Solution an:

                  Die Rahmenanwendung ist eine auf WinUI 3 basierte Anwendung. Die Oberfl\u00e4che ist grunds\u00e4tzlich fertig, ihre Definition ist in der Datei MainWindow.xaml zu finden. Dies ist f\u00fcr uns im Hinblick auf den Zweck der \u00dcbung weniger aufregend, aber es lohnt sich, sie zu Hause zu \u00fcben.

                  Gestaltung der Oberfl\u00e4che in MainWindow.xaml

                  Grundlagen der Gestaltung von Fensterfl\u00e4chen:

                  • Die Wurzel (root) ist \"normalerweise\" ein Grid.
                  • In der obersten Zeile des Wurzel-Grid befindet sich das StackPanel, das die zwei Texteingabefelder (TextBox) und die Taste (Button) enth\u00e4lt.
                  • Die unterste Zeile des Wurzel-Grid enth\u00e4lt ein weiteres Grid. Im Gegensatz zur TextBox hat die ListBox keine Header-Eigenschaft, so dass wir diese als separaten TextBlock mit dem Text \"Result\" einf\u00fchren mussten. Dieses Grid wurde eingef\u00fchrt (anstelle eines \"einfacheren\" StackPanel), weil es m\u00f6glich war, den TextBlock in der oberen Zeile mit einer festen H\u00f6he f\u00fcr das \"Result\" und die ListBox in der unteren Zeile so zu haben, dass sie den gesamten verbleibenden Platz ausf\u00fcllt (die H\u00f6he der oberen Zeile ist Auto, die H\u00f6he der unteren Zeile ist *).
                  • Die Taste mit dem Text \"Calculate Result\" ist ein gutes Beispiel daf\u00fcr, dass der Content eines Button Elementes oft nicht nur ein einfacher Text ist. Das Beispiel zeigt eine Komposition aus einem SymbolIcon und einem TextBlock (implementiert mit StackPanel), so dass wir ein geeignetes Icon/Symbol zuweisen k\u00f6nnen, um sein Aussehen zu verbessern.
                  • Wir sehen auch ein Beispiel daf\u00fcr, wie man eine ListBox scrollbar macht, wenn sie bereits viele Elemente enth\u00e4lt (oder die Elemente zu breit sind). Dazu muss der ScrollViewer richtig parametrisiert werden.
                  • Die Eigenschaft ItemContainerStyle der ListBox wird verwendet, um Stile f\u00fcr das Element ListBox festzulegen. Im Beispiel ist Padding auf einen kleineren Wert als den Standardwert eingestellt, da sonst die H\u00f6he der ListBox-Elemente \u00fcberfl\u00fcssig gro\u00df w\u00e4re.

                  Die Quelldatei MainWindow.xaml.cs ist der Code hinter der Datei f\u00fcr das Hauptfenster, lassen wir uns diese \u00fcberpr\u00fcfen, ihre Hauptelemente sind wie folgt:

                  • Um das Ergebnis und die Parameter auf ListBoxzu loggen, gibt es eine Hilfsfunktion namens ShowResult.
                  • CalculateResultButton_Click ist der Ereignishandler f\u00fcr das Anklicken der Taste \" Calculate Result \". Wir sehen, dass er den Wert der Parameter aus den beiden Textfeldern liest und versucht, ihn in eine Zahl umzuwandeln. Wenn er erfolgreich ist, wird der Algorithmus hier aufgerufen (dies ist noch nicht implementiert), oder wenn er fehlschl\u00e4gt, wird der Benutzer \u00fcber DisplayInvalidElementDialog in einem Nachrichtenfenster \u00fcber ung\u00fcltige Parameter informiert.
                  • Die Funktion AddKeyboardAcceleratorToChangeTheme, die vom Konstruktor aufgerufen wird, ist f\u00fcr uns nicht relevant, sie erm\u00f6glicht das Umschalten zwischen hellen und dunklen Themen (Sie sollten es zur Laufzeit ausprobieren, Ctrl+T ).
                  "},{"location":"labor/4-tobbszalu/index_ger/#verwendung-des-codes-in-der-dll","title":"Verwendung des Codes in der DLL","text":"

                  Im urspr\u00fcnglichen Projekt finden wir die Datei Algorithm.dll. In dieser kompilierten Form gibt es eine Klasse SuperAlgorithm im Namensraum Algorithms, die eine statische Operation namens Calculate hat. Um die Klassen einer DLL in einem Projekt verwenden zu k\u00f6nnen, m\u00fcssen wir in unsrem Projekt einen Verweis auf die DLL hinzuf\u00fcgen.

                  1. Klicken wir im Solution Explorer mit der rechten Maustaste auf den Knoten Dependencies unseres Projekts und w\u00e4hlen wir Add Project reference!

                    Externe Referenzen

                    Hier verweisen wir eigentlich nicht auf ein anderes Visual Studio-Projekt, aber dies ist der einfachste Weg, dieses Fenster aufzurufen.

                    Es sollte auch erw\u00e4hnt werden, dass wir f\u00fcr externe Klassenbibliotheken keine DLLs mehr in einem regul\u00e4ren Projekt referenzieren, sondern die externen Pakete aus dem Paketmanager von .NET, aus dem NuGet beziehen. Jetzt ist Algorithm.dll in unserem Fall nicht in NuGet ver\u00f6ffentlicht, so dass wir sie manuell hinzuf\u00fcgen m\u00fcssen.

                  2. Verwenden wir die Taste Browse in der rechten unteren Ecke des Popup-Fensters, w\u00e4hlen wir die Datei Algorithms.dll im Unterordner External unseres Projekts aus und klicken wir auf OK, um das Hinzuf\u00fcgen zu best\u00e4tigen!

                  Im Solution Explorer k\u00f6nnen wir auf den Knoten Dependencies unter einem Projekt klicken, um die referenzierten externen Abh\u00e4ngigkeiten anzuzeigen. Der Verweis auf Algorithmen, der zuvor addiert war, wird auch hier unter Assemblys angezeigt. Die Kategorie Frameworks enth\u00e4lt die .NET Framework-Pakete. Und die Elemente unter Analyzer sind Werkzeuge f\u00fcr die statische Codeanalyse zur Kompilierzeit. Und es g\u00e4be hier auch die Projekt- oder NuGet-Referenzen.

                  Klicken wir mit der rechten Maustaste auf die Referenz Algorithms und w\u00e4hlen wir View in Object Browser. Dies \u00f6ffnet die Registerkarte Object Browser, in der wir sehen k\u00f6nnen, welche Namensr\u00e4ume, Klassen und deren Mitglieder (Membervariable, Memberfunktion, Eigenschaft, Ereignis) in der angegebenen DLL enthalten sind. Visual Studio liest diese aus den DLL-Metadaten mit Hilfe des so genannten Reflection-Mechanismus (wir k\u00f6nnen diesen Code selbst schreiben).

                  Wie in der Abbildung unten dargestellt ist, suchen wir im Object Browser den Knoten Algorithmen auf der linken Seite, \u00f6ffnen ihn und sehen, dass er einen Namensraum Algorithms und eine Klasse SuperAlgorithm enth\u00e4lt. Wenn wir dies ausw\u00e4hlen, werden die Funktionen der Klasse in der Mitte angezeigt, und wenn wir hier eine Funktion ausw\u00e4hlen, wird die genaue Signatur dieser Funktion angezeigt:

                  "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-1-ausfuhren-einer-operation-auf-dem-hauptthread","title":"Aufgabe 1 - Ausf\u00fchren einer Operation auf dem Hauptthread","text":"

                  Jetzt k\u00f6nnen wir mit der Ausf\u00fchrung des Algorithmus fortfahren. Zun\u00e4chst tun wir dies im Hauptthread unserer Anwendung.

                  1. Im Ereignishandler der Taste Click im Hauptfenster rufen wir unsere Z\u00e4hlerfunktion auf. \u00d6ffnen wir dazu die code behind Datei MainWindow.xaml.cs im Solution Explorer und suchen wir nach dem Ereignishandler CalculateResultButton_Click. Vervollst\u00e4ndigen wir den Code durch den Aufruf des neu referenzierten Algorithmus.

                    private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        var result = Algorithms.SuperAlgorithm.Calculate(parameters);\n        ShowResult(parameters, result);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n
                  2. Probieren wir die Anwendung aus und stellen fest, dass das Fenster w\u00e4hrend der Berechnung nicht auf Verschieben oder Gr\u00f6\u00dfen\u00e4nderung reagiert, die Oberfl\u00e4che friert praktisch ein.

                  Unsere Anwendung ist ereignisgesteuert, wie alle Windows-Anwendungen. Das Betriebssystem benachrichtigt unsere Anwendung \u00fcber die verschiedenen Interaktionen (z. B. Verschieben, Gr\u00f6\u00dfen\u00e4nderung, Mausklick): Da der einzige Thread unserer Anwendung nach dem Tastendruck mit der Berechnung besch\u00e4ftigt ist, kann er nicht sofort weitere Benutzeranweisungen verarbeiten. Sobald die Berechnung abgeschlossen ist (und die Ergebnisse in der Liste angezeigt werden), werden die zuvor erhaltenen Befehle ausgef\u00fchrt.

                  "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-2-durchfuhrung-der-berechnung-in-einem-separaten-thread","title":"Aufgabe 2 - Durchf\u00fchrung der Berechnung in einem separaten Thread","text":"

                  Im n\u00e4chsten Schritt werden wir einen separaten Thread starten, um die Berechnung durchzuf\u00fchren, damit die Benutzeroberfl\u00e4che nicht blockiert wird.

                  1. Erstellen wir eine neue Funktion in der Klasse MainWindow, die der Eintrittspunkt f\u00fcr den VerarbeitungsFaden sein wird.

                    private void CalculatorThread(object arg)\n{\n    var parameters = (double[])arg;\n    var result = Algorithms.SuperAlgorithm.Calculate(parameters);\n    ShowResult(parameters, result);\n}\n
                  2. Starten wir den Thread in dem Ereignishandler der Taste Click. Ersetzen wir dazu den Code, den wir zuvor hinzugef\u00fcgt haben:

                    private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        var th = new Thread(CalculatorThread);\n        th.Start(parameters);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n

                    Der in der Operation Start des Fadenobjekts \u00fcbergebene Parameter wird an unsere Fadenfunktion CalculatorThread \u00fcbergeben.

                  3. F\u00fchren wir die Anwendung mit F5 aus (jetzt ist es wichtig, sie so auszuf\u00fchren, im Debugger)! The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD)) Fehlermeldung bekommen wir in der Methode ShowResult, weil wir nicht versuchen, auf das UI-Element/Controller von dem Thread aus zuzugreifen, der es erstellt hat (der Controller). In der n\u00e4chsten \u00dcbung werden wir dieses Problem analysieren und l\u00f6sen.

                  "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-3-verwendung-von-dispatcherqueuehasthreadaccess-und-dispatcherqueuetryenqueue","title":"Aufgabe 3 - Verwendung von DispatcherQueue.HasThreadAccess und DispatcherQueue.TryEnqueue","text":"

                  Das Problem im vorigen Aufgabe hat folgende Ursachen. F\u00fcr WinUI-Anwendungen gilt folgende Regel: Fenster/Oberfl\u00e4chen/Steuerelemente sind standardm\u00e4\u00dfig keine fadensicheren Objekte, so dass auf ein Fenster/Oberfl\u00e4che/Steuerelement nur von dem Thread aus zugegriffen werden darf (z.B. Eigenschaft lesen, einstellen, Operation aufrufen), der das gegebenen Fenster/Oberfl\u00e4che/Steuerelement erstellt hat, sondern gibt es eine Ausnahme. In unserer Anwendung haben wir eine Ausnahme bekommen, weil das resultListBox Steuerelement im Haupt-Thread erstellt wird, aber in der ShowResult Methode, wenn das Ergebnis angezeigt wird, wird von einem anderen Thread aus darauf zugegriffen (Aufruf derresultListBox.Items.Add Methode).

                  Die Frage ist, wie auf diese Oberfl\u00e4chenelemente/Steuerelemente von einem anderen Thread aus noch irgendwie zugegriffen werden kann. Die L\u00f6sung besteht in der Verwendung von DispatcherQueue, um sicherzustellen, dass der Zugriff auf die Steuerelemente immer \u00fcber den richtigen Thread erfolgt:

                  • Die Funktion TryEnqueue des Objekts DispatcherQueue f\u00fchrt die als Parameter angegebene Funktion auf dem Thread aus, der das Steuerelement erstellt (von dem aus man nun direkt auf das Steuerelement zugreifen kann).
                  • Die Eigenschaft HasThreadAccess des Objekts DispatcherQueue hilft bei der Entscheidung, ob es notwendig ist, TryEnqueue zu verwenden, wie im vorherigen Abschnitt erw\u00e4hnt. Wenn der Wert dieser Eigenschaft
                    • wahr ist, kann auf den Controller direkt zugegriffen werden (weil der aktuelle Thread derselbe ist wie der Thread, der den Controller erstellt hat), aber wenn
                    • falsch ist, kann auf den Controller nur \"unter Umgehung\", durch die Funktion TryEnqueue des Objekts DispatcherQueue zugegriffen werden (da der aktuelle Thread NICHT mit dem Thread identisch ist, der den Controller erstellt hat).

                  Mit DispatcherQueue k\u00f6nnen wir also unsere vorherige Ausnahme vermeiden (der Zugriff auf den Controller, in diesem Fall resultListBox, kann an den entsprechenden Thread \"geleitet\" werden). Wir werden dies im Folgenden tun.

                  Hinweis

                  Das Objekt DispatcherQueue ist in Nachkommen der Klasse Window \u00fcber die Eigenschaft DispatcherQueue verf\u00fcgbar (und in anderen Klassen \u00fcber die statische Operation DispatcherQueue.GetForCurrentThread() ).

                  Wir m\u00fcssen die Methode ShowResult so \u00e4ndern, dass sie keine Ausnahme ausl\u00f6st, wenn sie aus einem neuen, separaten Thread aufgerufen wird.

                  private void ShowResult(double[] parameters, double result)\n{\n    // Closing the window the DispatcherQueue property may return null, so we have to perform a null check\n    if (this.DispatcherQueue == null)\n        return;\n\n    if (this.DispatcherQueue.HasThreadAccess)\n    {\n        var item = new ListBoxItem()\n        {\n            Content = $\"{parameters[0]} #  {parameters[1]} = {result}\"\n        };\n        resultListBox.Items.Add(item);\n        resultListBox.ScrollIntoView(item);\n    }\n    else\n    {\n        this.DispatcherQueue.TryEnqueue( () => ShowResult(parameters, result) );\n    }\n}\n

                  Probieren wir es aus!

                  Diese L\u00f6sung ist bereits funktionsf\u00e4hig und ihre wichtigste Elemente sind die folgenden:

                  • Die Rolle der Pr\u00fcfung, ob DispatcherQueue null ist: Nach dem Schlie\u00dfen des Hauptfensters ist DispatcherQueue schon null, es kann nicht verwendet werden.
                  • Die DispatcherQueue.HasThreadAccess wird verwendet, um zu pr\u00fcfen, ob der aufrufende Thread direkt auf die Controller zugreifen kann (in unserem Fall ListBox):
                    • Falls ja, wird alles wie bisher passieren, der Code f\u00fcr ListBoxbleibt unver\u00e4ndert.
                    • Falls nicht, k\u00f6nnen wir durch DispatcherQueue.TryEnqueue auf den Controller zugreifen. Dabei wird der folgende Trick angewendet. Die Funktion TryEnqueue erh\u00e4lt eine parameterlose, einzeilige Funktion in Form eines Lambda-Ausdrucks, der unsere Funktion ShowResult aufruft (praktisch rekursiv) und ihr die Parameter \u00fcbergibt. Das ist gut f\u00fcr uns, weil dieser ShowResult-Aufruf bereits auf dem Thread erfolgt, der den Controller erstellt hat (dem Hauptthread der Anwendung), der Wert von HasThreadAccess ist jetzt wahr, und wir k\u00f6nnen direkt auf unser ListBoxzugreifen. Dieser rekursive Ansatz ist ein oft benutztes Muster, um redundanten Code zu vermeiden.

                  Setzen wir einen Haltepunkt in der ersten Zeile der Operation ShowResult, und f\u00fchren wir die Anwendung aus, um sicherzustellen, dass HasThreadAccess falsch ist, wenn ShowResult zum ersten Mal aufgerufen wird (also wird TryEnqueue aufgerufen), und dann wird ShowResult erneut aufgerufen, aber HasThreadAccess ist wahr.

                  Entfernen wir den Haltepunkt und f\u00fchren wir die Anwendung aus: Beachten wir, dass w\u00e4hrend eine Berechnung l\u00e4uft, eine andere gestartet werden kann, da unsere Benutzeroberfl\u00e4che durchgehend reaktionsf\u00e4hig bleibt (und der Fehler, der zuvor auftrat, nicht mehr auftritt).

                  "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-4-ausfuhren-einer-operation-auf-einem-threadpool-thread","title":"Aufgabe 4 - Ausf\u00fchren einer Operation auf einem Threadpool-Thread","text":"

                  Eine Merkmal der bisherigen L\u00f6sung ist, dass sie immer einen neuen Thread f\u00fcr die Operation erstellt. In unserem Fall ist dies nicht besonders wichtig, aber dieser Ansatz kann f\u00fcr eine Serveranwendung, die eine gro\u00dfe Anzahl von Anfragen bedient, problematisch sein, da f\u00fcr jede Anfrage ein eigener Thread gestartet wird. Aus zwei Gr\u00fcnden:

                  • Wenn die Fadenfunktion schnell l\u00e4uft (um einen Client schnell zu bedienen), dann wird ein gro\u00dfer Teil der CPU f\u00fcr das Starten und Stoppen von Threads verschwendet, was an sich schon ressourcenintensiv ist.
                  • Es k\u00f6nnen zu viele Threads erstellt werden, und das Betriebssystem muss zu viele planen, was unn\u00f6tig Ressourcen verschwendet.

                  Ein weiteres Problem mit unserer derzeitigen L\u00f6sung: Da die Berechnung auf einem so genannten Vordergrundfaden l\u00e4uft (neu erstellte Threads sind standardm\u00e4\u00dfig Vordergrundf\u00e4den), l\u00e4uft das Programm selbst dann im Hintergrund weiter, obwohl wir die Anwendung schlie\u00dfen, solange bis die letzte Berechnung ausgef\u00fchrt wurde: Ein Prozess h\u00f6rt erst auf zu laufen, wenn er keinen Vordergrundfaden mehr hat.

                  \u00c4ndern wir den Ereignishandler der Taste, um die Berechnung in einem Threadpool-Thread auszuf\u00fchren, anstatt einen neuen Thread zu starten. Um dies zu tun, schreiben wir einfach den Ereignishandler f\u00fcr das Dr\u00fccken der Taste um.

                  private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        ThreadPool.QueueUserWorkItem(CalculatorThread, parameters);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n

                  Probieren wir die Anwendung aus und stellen fest, dass die Anwendung sofort anh\u00e4lt, wenn das Fenster geschlossen wird, ohne sich um eventuell noch laufende Threads zu k\u00fcmmern (denn Threadpool-Threads sind Hintergrundf\u00e4den).

                  "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-5-hersteller-verbraucher-basierte-losung","title":"Aufgabe 5 - Hersteller-Verbraucher-basierte L\u00f6sung","text":"

                  Allein durch die L\u00f6sung der vorangegangenen Probleme erhielten wir eine vollst\u00e4ndige und gut funktionierende L\u00f6sung f\u00fcr das urspr\u00fcngliche Problem, die es erm\u00f6glicht, dass mehrere Threads parallel im Hintergrund arbeiten, wenn die Taste mehrmals nacheinander gedr\u00fcckt wird. Im Folgenden werden wir unsere Anwendung so modifizieren, dass ein Tastendruck nicht immer einen neuen Thread erzeugt, sondern die Aufgaben in eine Aufgabenwarteschlange stellt, aus der mehrere im Hintergrund laufende Threads sie nacheinander ausw\u00e4hlen und ausf\u00fchren. Bei dieser Aufgabe handelt es sich um das klassische Hersteller-Verbraucher-Problem, das in der Praxis h\u00e4ufig auftritt und in der folgenden Abbildung dargestellt ist.

                  Hersteller-Verbraucher vs ThreadPool

                  Wenn Sie dar\u00fcber nachdenken, ist ThreadPool auch ein spezieller Hersteller-Verbraucher und Scheduler-Mechanismus, der uns von .NET zur Verf\u00fcgung gestellt wird. Im Folgenden entwickeln wir eine andere Art von Hersteller-Verbraucher-L\u00f6sung, um einige mit der Fadenbehandlung verbundenen Wettbewerbsprobleme anzuschauen.

                  Der Hauptthread ist der Hersteller, der eine neue Aufgabe erstellt, falls die Taste Calculate result geklickt wird. Wir werden mehr Threads in der Verbraucher-/verarbeitenden Threads starten, da wir mehr CPU-Kerne verwenden und die Ausf\u00fchrung von Aufgaben parallelisieren k\u00f6nnen.

                  F\u00fcr die Zwischenspeicherung von Aufgaben k\u00f6nnen wir die Klasse DataFifo (im Ordner Data im Solution Explorer) verwenden, die in unserem urspr\u00fcnglichen Projekt bereits etwas vorbereitet ist. Schauen wir uns den Quellcode an. Es implementiert eine einfache FIFO-Warteschlange, um double[] zu speichern. Die Methode Put h\u00e4ngt die neuen Paare an das Ende der internen Liste an, w\u00e4hrend die Methode TryGet das erste Element der internen Liste zur\u00fcckgibt (und entfernt). Wenn die Liste leer ist, kann die Funktion kein Element zur\u00fcckgeben. In diesem Fall zeigt false dies durch einen R\u00fcckgabewert an.

                  1. \u00c4ndern wir den Ereignishandler der Taste so, dass er nicht in ThreadPool, sondern in FIFO arbeitet:

                    private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        _fifo.Put(parameters);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n
                  2. Erstellen wir eine naive Implementierung der neuen Fadenbehandlungsfunktion in unserer Formularklasse:

                    private void WorkerThread()\n{\n    while (true)\n    {\n        if (_fifo.TryGet(out var data))\n        {\n            double result = Algorithms.SuperAlgorithm.Calculate(data);\n            ShowResult(data, result);\n        }\n\n        Thread.Sleep(500);\n    }\n}\n

                    Der Grund f\u00fcr die Einf\u00fchrung von Thread.Sleep ist, dass sich die Threads sonst unn\u00f6tigerweise die ganze Zeit mit einem leeren FIFO besch\u00e4ftigen w\u00fcrden, ohne irgendeine n\u00fctzliche Operation auszuf\u00fchren, und einen CPU-Kern zu 100% \u00fcberlasten w\u00fcrden. Unsere L\u00f6sung ist nicht ideal, wir werden sie sp\u00e4ter verbessern.

                  3. Erstellen und starten wir die Verarbeitungsf\u00e4den im Konstruktor:

                    new Thread(WorkerThread) { Name = \"Worker thread 1\" }.Start();\nnew Thread(WorkerThread) { Name = \"Worker thread 2\" }.Start();\nnew Thread(WorkerThread) { Name = \"Worker thread 3\" }.Start();\n
                  4. Starten wir die Anwendung und schlie\u00dfen wir sie sofort, ohne auf die Taste Calculate Result zu klicken. Unser Fenster wird geschlossen, aber unser Prozess l\u00e4uft weiter, und die einzige M\u00f6glichkeit, die Anwendung zu schlie\u00dfen, ist \u00fcber Visual Studio oder den Task-Manager:

                    Die Verarbeitungsf\u00e4den sind Vordergrundf\u00e4den, die verhindern das Beenden der Prozess beim Schlie\u00dfen des Fensters. Eine L\u00f6sung k\u00f6nnte darin bestehen, die Eigenschaft IsBackground der Threads auf truezu setzen, nachdem sie erstellt wurden. Die andere L\u00f6sung stellt sicher, dass die Verarbeitungsf\u00e4den beim Beenden beendet werden. Lassen wir dieses Thema erst einmal beiseite, wir kommen sp\u00e4ter darauf zur\u00fcck.

                  5. Starten wir die Anwendung und wir werden feststellen, dass wir nach dem Klicken auf die Taste Calculate Result (nur einmal klicken) h\u00f6chstwahrscheinlich eine Ausnahme erhalten. Das Problem ist, dass DataFifo nicht fadensicher ist, es ist inkonsistent geworden. Hierf\u00fcr gibt es zwei Ursachen:

                  "},{"location":"labor/4-tobbszalu/index_ger/#problem-1","title":"Problem 1","text":"

                  Betrachten wir das folgende Szenario:

                  1. Die Zeile ist leer. Die verarbeitenden Threads fragen den FIFO kontinuierlich in einer while-Schleife ab, d. h. sie rufen die Methode TryGet auf.
                  2. Der Benutzer f\u00fcgt der Warteschlange eine Aufgabe hinzu.
                  3. Einer der Verarbeitungsf\u00e4den in der Methode TryGet stellt fest, dass Daten in der Zeile vorhanden sind, d. h. die Bedingung der Codezeile if ( _innerList.Count > 0 ) ist erf\u00fcllt, und geht zur n\u00e4chsten Codezeile \u00fcber. Angenommen, dieser Thread verliert an dieser Stelle seine Durchf\u00fchrungsrecht, dann hat er keine Zeit mehr, die Daten aus der Warteschlange zu nehmen.
                  4. Ein anderer Verarbeitungsthread l\u00e4sst die Pr\u00fcfung von if ( _innerList.Count > 0 ) zu diesem Zeitpunkt ebenfalls fallen, die Bedingung ist ebenfalls erf\u00fcllt, und dieser Thread nimmt die Daten aus der Warteschlange.
                  5. Der erste Thread wird neu geplant, wacht auf und versucht, die Daten aus der Warteschlange zu nehmen: die Warteschlange ist leer, der andere Thread hat die einzigen Daten aus der Warteschlange vor ihm genommen. Der Zugriff auf _innerList[0] f\u00fchrt daher zu einer Ausnahme.

                  Die einzige M\u00f6glichkeit, dieses Problem zu vermeiden, ist die Pr\u00fcfung der Zeilenleere und die Elementausnahme unteilbar zu machen.

                  Thread.Sleep(500)

                  Die Rolle der Codezeile Thread.Sleep(500);, die auf die Codezeile folgt, die die Leere-Pr\u00fcfung in unserem Beispielcode \u00fcberwacht, besteht nur darin, die Wahrscheinlichkeit zu erh\u00f6hen, dass das obige ungl\u00fcckliche Szenario eintritt, und somit das Beispiel anschaulicher zu machen (da es fast sicher ist, dass der Thread neu geplant wird). Wir werden dies in Zukunft herausnehmen, aber vorl\u00e4ufig lassen wir es drin.

                  "},{"location":"labor/4-tobbszalu/index_ger/#problem-2","title":"Problem 2","text":"

                  Die Klasse DataFifo kann von mehreren Threads gleichzeitig auf die Mitgliedsvariable _innerList mit der Typ List<double[]> zugreifen. Wenn wir uns jedoch die Dokumentation zu List<T> ansehen, werden wir feststellen, dass die Klasse nicht fadensicher (not thread safe) ist. Aber in diesem Fall k\u00f6nnen wir das nicht tun, wir m\u00fcssen Sperren verwenden, um sicherzustellen, dass unser Code nur auf eine Methode/Eigenschaft/Mitgliedsvariable zur gleichen Zeit zugreifen kann (genauer gesagt, kann Inkonsistenz nur im Fall von gleichzeitigen Schreiben und Lesen auftreten, aber wir unterscheiden in den meisten F\u00e4llen nicht zwischen Lesern und Schreibern, und wir tun es hier auch nicht).

                  Der n\u00e4chste Schritt ist, unsere Klasse DataFifo fadensicher zu machen, wodurch die beiden oben genannten Probleme vermieden werden.

                  "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-6-die-datafifo-klasse-fadensicher-machen","title":"Aufgabe 6 - Die DataFifo-Klasse fadensicher machen","text":"

                  Um die Klasse DataFifo fadensicher zu machen, ben\u00f6tigen wir ein Objekt (dies kann ein beliebiges Objekt vom Referenztyp sein), das als Schl\u00fcssel zum Sperren verwendet wird. Mit dem Schl\u00fcsselwort lock k\u00f6nnen wir dann sicherstellen, dass sich jeweils nur ein Thread in den durch diesen Schl\u00fcssel gesch\u00fctzten Bl\u00f6cken aufh\u00e4lt.

                  1. F\u00fcgen wir ein Feld vom Typ object mit dem Namen _syncRoot zur Klasse DataFifo hinzu.

                    private object _syncRoot = new object();\n
                  2. Erg\u00e4nzen wir die Funktionen Put und TryGet mit dem Sperre.

                    public void Put(double[] data)\n{\n    lock (_syncRoot)\n    {\n        _innerList.Add(data); \n    }\n}\n
                    public bool TryGet(out double[] data)\n{\n    lock (_syncRoot)\n    {\n        if (_innerList.Count > 0)\n        {\n            Thread.Sleep(500);\n\n            data = _innerList[0];\n            _innerList.RemoveAt(0);\n            return true;\n        }\n\n        data = null;\n        return false;\n    }\n}\n

                    Surround with

                    Verwenden wir die Funktion \"Surround with\" von Visual Studio, indem Sie STRG + K, STRG + S auf dem ausgew\u00e4hlten Codeschnipsel dr\u00fccken, den wir umschlie\u00dfen m\u00f6chten.

                  Jetzt d\u00fcrfen wir keine Ausnahme bekommen.

                  Wir k\u00f6nnen die k\u00fcnstliche Verz\u00f6gerung auch aus der Methode TryGet entfernen ( ZeileThread.Sleep(500); ).

                  Sperre auf this

                  Es stellt sich die Frage, warum wir eine separate Membervariable _syncRoot eingef\u00fchrt und diese als Sperrparameter f\u00fcr lock verwendet haben, wenn wir stattdessen auch this h\u00e4tten verwenden k\u00f6nnen ( DataFifo ist der Referenztyp, daher w\u00e4re dies kein Problem). Die Verwendung von this w\u00fcrde jedoch gegen die Einkapselung unserer Klasse versto\u00dfen! Erinnern wir uns: this ist ein Verweis auf unser Objekt, aber andere Klassen haben Verweise auf dasselbe Objekt (z.B. in unserem Fall MainWindowhat einen Verweis auf DataFifo), und wenn diese externen Klassen eine Sperre auf das Objekt setzen, indem sie lock verwenden, wird dies die Sperre \"st\u00f6ren\", die wir auf die Klasse darin verwenden (da die Verwendung von this dazu f\u00fchrt, dass die externen und internen lock denselben Parameter haben). Zum Beispiel kann eine externe Sperre verwendet werden, um die Operationen TryGet und Put vollst\u00e4ndig \"lahmzulegen\". Im Gegensatz dazu ist in unserer L\u00f6sung der Parameter lock, die Variable _syncRoot, privat und kann nicht von externen Klassen aufgerufen werden, so dass sie die internen Abl\u00e4ufe unserer Klasse nicht beeintr\u00e4chtigen kann.

                  "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-7-implementierung-einer-effektiven-signalisierung","title":"Aufgabe 7 - Implementierung einer effektiven Signalisierung","text":""},{"location":"labor/4-tobbszalu/index_ger/#verwendung-von-manualresetevent","title":"Verwendung von ManualResetEvent","text":"

                  Die Schleife while, die in WorkerThreadst\u00e4ndig l\u00e4uft, implementiert ein sogenanntes aktives Warten, das immer vermieden werden sollte. Falls Thread.Sleep nicht in den Schleifenkern eingebaut worden w\u00e4re, w\u00e4re der Prozessor \u00fcberlastet gewesen. Thread.Sleep l\u00f6st zwar das Problem der CPU-Belastung, f\u00fchrt aber ein weiteres ein: Wenn sich alle drei Arbeitsf\u00e4den im Ruhezustand befinden, wenn neue Daten empfangen werden, warten wir unn\u00f6tigerweise 500 ms, bevor wir mit der Verarbeitung der Daten beginnen.

                  Im Folgenden wird die Anwendung so ge\u00e4ndert, dass sie in einem blockierten Zustand wartet, bis Daten zum FIFO hinzugef\u00fcgt werden (aber wenn Daten hinzugef\u00fcgt werden, beginnt sie sofort mit der Verarbeitung). Um anzuzeigen, ob sich Daten in der Warteschlange befinden, wird ManualResetEventverwendet.

                  1. F\u00fcgen wir eine Instanz von MaunalResetEvent zu unserer Klasse DataFifo als _hasData hinzu.

                    // Infolge des Konstruktorparameters false wird das Ereignis anf\u00e4nglich nicht signalisiert (Tor geschlossen)\nprivate ManualResetEvent _hasData = new ManualResetEvent(false);\n
                  2. _hasData funktioniert als ein Tor in unserer Anwendung. Wenn der Liste Daten hinzugef\u00fcgt werden, wird sie \"ge\u00f6ffnet\", und wenn die Liste geleert wird, wird sie \"geschlossen\".

                    Semantik und Benennung des Ereignisses

                    Es ist wichtig, die Semantik unseres Ereignisses gut zu w\u00e4hlen und wir im Namen unseres Ereignisses pr\u00e4zise auszudr\u00fccken. In unserem Beispiel dr\u00fcckt der Name _hasData aus, dass unser Ereignis genau dann und nur dann signalisiert wird, wenn es Daten zu verarbeiten gibt (Tor ge\u00f6ffnet). Jetzt m\u00fcssen wir \"nur\" noch diese Semantik implementieren: das Ereignis signalisiert setzen, wenn Daten in den FIFO eingegeben werden, und nicht signalisiert, wenn der FIFO geleert wird.

                    public void Put(double[] data)\n{\n    lock (_syncRoot)\n    {\n        _innerList.Add(data);\n        _hasData.Set();\n    }\n}\n
                    public bool TryGet(out double[] data)\n{\n    lock (_syncRoot)\n    {\n        if (_innerList.Count > 0)\n        {\n            data = _innerList[0];\n            _innerList.RemoveAt(0);\n            if (_innerList.Count == 0)\n            {\n                _hasData.Reset();\n            }\n\n            return true;\n        }\n\n        data = null;\n        return false;\n    }\n}\n
                  "},{"location":"labor/4-tobbszalu/index_ger/#warten-auf-signal-get-blockiert","title":"Warten auf Signal (Get blockiert)","text":"

                  In dem vorherigen Punkt wurde die Signalisierung gel\u00f6st, aber das sich selbst macht nicht viel, weil niemand auf das Signal wartet. Diese Erkenntnis kommt jetzt.

                  1. \u00c4ndern wir die Methode wie folgt: Entfernen wir den Leere-Test und ersetzen wir ihn durch Warten auf das Ereignis.

                    public bool TryGet(out double[] data)\n{\n    lock (_syncRoot)\n    {\n        if (_hasData.WaitOne())\n        {\n            // ...\n

                    Pr\u00fcfung des R\u00fcckgabewerts der Operation WaitOne

                    Die Operation WaitOne gibt den Wert bool zur\u00fcck, der wahr ist, wenn sich das Ereignis vor der im Parameter von WaitOne angegebenen Zeitspanne signalisiert wird (und entsprechend falsch, wenn die Zeitspanne abgelaufen ist). In unserem Beispiel haben wir im Parameter kein Zeitlimit angegeben, was eine unendliche Zeitspanne bedeutet. Dementsprechend ist die Pr\u00fcfung der Bedingung if \u00fcberfl\u00fcssig, da in unserem Fall WaitOne() immer einen wahren Wert liefert. Dies ist der einzige Grund, warum wir dennoch die Konditionstests verwendet haben: Wir erfordern weniger \u00c4nderungen f\u00fcr die n\u00e4chste und eine zuk\u00fcnftige \u00dcbung.

                  2. Dies macht Thread.Sleep in WorkerThread \u00fcberfl\u00fcssig, kommentieren wir es aus!

                    Wenn wir die obige L\u00f6sung ausf\u00fchren, werden wir feststellen, dass die Oberfl\u00e4che unserer Anwendung nach dem ersten Tastendruck einfriert. Bei unserer vorherigen L\u00f6sung haben wir einen Anf\u00e4ngerfehler gemacht. In dem gesperrten Codeschnipsel warten wir darauf, dass _hasData gesendet wird, so dass der Hauptthread keine Gelegenheit hat, _hasData in der Operation Put zu senden (ebenfalls gesch\u00fctzt durch lock). In der Praxis wurde eine Verklemmung (deadlock) gebildet.

                    Wir k\u00f6nnten versuchen, ein Zeitlimit (ms) f\u00fcr die Wartezeit festzulegen:

                    if (_hasData.WaitOne(100))\n

                    Dies w\u00e4re an sich keine elegante L\u00f6sung, au\u00dferdem w\u00fcrden die st\u00e4ndig verschmutzenden Arbeitsf\u00e4den den Thread, der Put aufruft, erheblich aushungern! Stattdessen ist das elegante Muster zu folgen, um zu vermeiden, dass man innerhalb einer Sperre blockiert wartet.

                    Tauschen wir lock und WaitOne um, und entfernen wir die Wartezeitbegrenzung, also den Parameter von WaitOne:

                    public bool TryGet(out double[] data)\n{\n    if (_hasData.WaitOne())\n    {\n        lock (_syncRoot)\n        {\n            data = _innerList[0];\n            _innerList.RemoveAt(0);\n            if (_innerList.Count == 0)\n            {\n                _hasData.Reset();\n            }\n\n            return true; \n        }\n    }\n\n    data = null;\n    return false;\n}\n

                    Probieren wir die App aus. Wenn wir die Taste zum ersten Mal dr\u00fccken, erhalten wir eine Ausnahme. Dadurch wird zwar ein Deadlock vermieden, aber die Fadensicherheit ist verletzt, weiles ist nicht sicher, dass wenn wir in lock eintreten k\u00f6nnen, noch Elemente in der Liste vorhanden sind. Es kann mehrere Threads geben, die mit _hasData.WaitOne() darauf warten, dass ein Element zu der Liste hinzugef\u00fcgt wird. Wenn dies geschieht, wird unser ManualResetEvent Objekt alle durchlassen (au\u00dfer wenn ein Thread schlie\u00dft es schnell, aber das ist nicht garantiert).

                    Die Schwierigkeiten der Programmierung in einer konkurrierenden, mehrf\u00e4digen Umgebung

                    Diese Aufgabe veranschaulicht, wie sorgf\u00e4ltig man bei der Programmierung in einer konkurrierenden, mehrf\u00e4digen Umgebung vorgehen muss. Bei den vorherigen hatten wir sogar noch Gl\u00fcck, denn der Fehler war reproduzierbar. In der Praxis ist dies jedoch selten der Fall. Leider ist es viel h\u00e4ufiger der Fall, dass Konkurenzprobleme gelegentliche, nicht reproduzierbare Probleme verursachen. Die L\u00f6sung einer solchen Aufgabe muss immer sehr sorgf\u00e4ltig durchdacht sein und kann nicht nach dem Motto \"wir-probieren-es-solange-es-wird-gut-im-per-Hand-Test\" programmiert werden.

                  3. Als Korrektur setzen wir den Leertest in lock zur\u00fcck.

                    public bool TryGet(out double[] data)\n{\n    if (_hasData.WaitOne())\n    {\n        lock (_syncRoot)\n        {\n            if (_innerList.Count > 0)\n            {\n                data = _innerList[0];\n                _innerList.RemoveAt(0);\n                if (_innerList.Count == 0)\n                {\n                    _hasData.Reset();\n                }\n\n                return true;  \n            }\n        }\n    }\n\n    data = null;\n    return false;\n}\n

                    Dies funktioniert bereits gut. Es ist m\u00f6glich, dass wir unn\u00f6tigerweise auf die Liste eingehen, aber wir belassen es vorerst dabei.

                    Testen wir die App!

                  System.Collections.Concurrent

                  Im .NET-Framework gibt es mehrere eingebaute fadensichere Klassen im Namensraum System.Collections.Concurrent. In dem obigen Beispiel h\u00e4tte die Klasse DataFifo durch System.Collections.Concurrent.ConcurrentQueue ersetzt werden k\u00f6nnen.

                  "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-8-kulturelle-abschaltung","title":"Aufgabe 8 - Kulturelle Abschaltung","text":"

                  Bisher haben wir das Problem, dass unser Prozess beim Schlie\u00dfen des Fensters \"stecken bleibt\", weil die Verarbeitungsthreads Vordergrundf\u00e4den sind und wir das Problem des Beendens dieser Threads nicht gel\u00f6st haben. Unser Ziel ist es, den unendlichen while-Schleife auszul\u00f6sen, so dass unsere Arbeitsf\u00e4den auf zivilisierte Weise beendet werden, wenn die Anwendung geschlossen wird.

                  1. Ein ManualResetEvent wird verwendet, um das Beenden im FIFO anzuzeigen, w\u00e4hrend in TryGetgewartet wird. F\u00fcgen wir im FIFO ein neues ManualResetEvent hinzu und f\u00fchren wir eine Release-Operation ein, um unsere Wartezeiten zu verk\u00fcrzen (unser neues Ereignis kann auf einen signalisierten Zustand gesetzt werden).

                    private ManualResetEvent _releaseTryGet = new ManualResetEvent(false);\n\npublic void Release()\n{\n    _releaseTryGet.Set();\n}\n
                  2. Warten wir auf diese Ereignis auch in TryGet. Die Methode WaitAny darf die Ausf\u00fchrung fortsetzen, wenn sich eines der als Parameter angegebenen Objekte vom Typ WaitHandle signalisiert ist, und gibt dessen Index innerhalb der Block zur\u00fcck. Und wir wollen die tats\u00e4chliche Verarbeitung nur, wenn _hasData signalisiert ist (wenn WaitAny 0 zur\u00fcckgibt).

                    public bool TryGet(out double[] data)\n{\n    if (WaitHandle.WaitAny(new[] { _hasData, _releaseTryGet }) == 0)\n    {\n        lock (_syncRoot)\n        {\n
                  3. F\u00fcgen wir eine flag Variable in MainWindow.xaml.cs hinzu, um das Beenden anzuzeigen:

                    private bool _isClosed = false;\n
                  4. Wenn das Hauptfenster geschlossen wird, setzen wir das neue Ereignis auf signalisiert und setzen wir auch das Flag auf true: abonnieren wir uns auf das Ereignis Closed der Klasse MainWindow im Konstruktor und schreiben wir die entsprechende Ereignishandler:

                    public MainWindow()\n{\n    ...\n\n    Closed += MainWindow_Closed;\n}\n\nprivate void MainWindow_Closed(object sender, WindowEventArgs args)\n{\n    _isClosed = true;\n    _fifo.Release();\n}\n
                  5. Schreiben wir die while-Schleife so um, dass sie auf das im vorigen Punkt addierte Flag wartet.

                    private void WorkerThread()\n{\n    while (!_isClosed)\n    {\n
                  6. Stellen wir sicher, dass wir nicht versuchen, Nachrichten f\u00fcr ein Fenster zu senden, das bereits geschlossen ist

                    private void ShowResult(double[] parameters, double result)\n{\n    if (_isClosed)\n        return;\n
                  7. F\u00fchren wir die Anwendung aus und \u00fcberpr\u00fcfen wir, ob unser Prozess tats\u00e4chlich beendet wird, wenn wir ihn beenden.

                  "},{"location":"labor/4-tobbszalu/index_ger/#ausblick-task-async-await","title":"Ausblick: Task, async, await","text":"

                  Ziel der \u00dcbung war es, die Techniken f\u00fcr das Management von F\u00e4den auf unterer Ebene kennen zu lernen. Wir h\u00e4tten unsere L\u00f6sung jedoch (zumindest teilweise) auf den \u00fcbergeordneten Werkzeugen und Mechanismen aufbauen k\u00f6nnen, die die asynchrone Programmierung in .NET unterst\u00fctzen, z. B. die Klassen Task/Task<T> und die Schl\u00fcsselw\u00f6rter async/await.

                  "},{"location":"labor/5-mvvm/","title":"5. MVVM","text":""},{"location":"labor/5-mvvm/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                  A labor sor\u00e1n egy recept b\u00f6ng\u00e9sz\u0151 alkalmaz\u00e1st fogunk k\u00e9sz\u00edteni, amelyben alkalmazzuk az MVVM tervez\u00e9si mint\u00e1t.

                  "},{"location":"labor/5-mvvm/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                  A labor elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                  • Windows 10 vagy Windows 11 oper\u00e1ci\u00f3s rendszer (Linux \u00e9s macOS nem alkalmas)
                  • Visual Studio 2022
                    • Windows Desktop Development Workload
                  "},{"location":"labor/5-mvvm/#kiindulo-projekt","title":"Kiindul\u00f3 projekt","text":"

                  Kl\u00f3nozzuk le a kiindul\u00f3 projektet az al\u00e1bbi paranccsal:

                  git clone https://github.com/bmeviauab00/lab-mvvm-kiindulo\n
                  A k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se

                  L\u00e9nyeges, hogy a labor sor\u00e1n a laborvezet\u0151t k\u00f6vetve kell dolgozni, tilos (\u00e9s \u00e9rtelmetlen) a k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se. Ugyanakkor az ut\u00f3lagos \u00f6n\u00e1ll\u00f3 gyakorl\u00e1s sor\u00e1n hasznos lehet a k\u00e9sz megold\u00e1s \u00e1ttekint\u00e9se, \u00edgy ezt el\u00e9rhet\u0151v\u00e9 tessz\u00fck.

                  A megold\u00e1s GitHubon \u00e9rhet\u0151 el a megoldas \u00e1gon. A legegyszer\u0171bb m\u00f3d a let\u00f6lt\u00e9s\u00e9re, ha parancssorb\u00f3l a git clone utas\u00edt\u00e1ssal lekl\u00f3nozzuk a g\u00e9p\u00fcnkre a megoldas \u00e1gat:

                  git clone https://github.com/bmeviauab00/lab-mvvm-kiindulo -b megoldas

                  "},{"location":"labor/5-mvvm/#az-mvvm-mintarol","title":"Az MVVM mint\u00e1r\u00f3l","text":"

                  Az MVVM (Model-View-ViewModel) egy architektur\u00e1lis tervez\u00e9si minta, amelyet a XAML alkalmaz\u00e1sok fejleszt\u00e9se sor\u00e1n haszn\u00e1lhatunk, de gyakran m\u00e1s kliens oldali technol\u00f3gi\u00e1k eset\u00e9ben is megjelenik. Az MVVM minta c\u00e9lja, hogy a felhaszn\u00e1l\u00f3i fel\u00fcletet \u00e9s a m\u00f6g\u00f6tte l\u00e9v\u0151 logik\u00e1t sz\u00e9tv\u00e1lassza, \u00e9s ezzel egy laz\u00e1bb csatol\u00e1s\u00fa alkalmaz\u00e1st hozzon l\u00e9tre, ami n\u00f6veli a tesztelhet\u0151s\u00e9get, a karbantarthat\u00f3s\u00e1got \u00e9s az \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1got.

                  Az MVVM minta h\u00e1rom (+1) f\u0151 r\u00e9szb\u0151l \u00e1ll:

                  • Model: Az alkalmaz\u00e1s \u00fczleti modellj\u00e9t tartalmazza, amelyet a ViewModel-ek haszn\u00e1lhatnak az adatok t\u00e1rol\u00e1s\u00e1ra.
                  • View: A felhaszn\u00e1l\u00f3i fel\u00fclet le\u00edr\u00e1s\u00e1t tartalmazza, \u00e9s a tiszt\u00e1n a n\u00e9zetekhez kapcsol\u00f3d\u00f3 logik\u00e1t (pl.: anim\u00e1ci\u00f3k kezel\u00e9s\u00e9t).
                  • ViewModel: A n\u00e9zet absztrakci\u00f3ja, mely tartalmazza a n\u00e9zet \u00e1llapot\u00e1t \u00e9s a n\u00e9zeten v\u00e9grehajthat\u00f3 m\u0171veleteket, n\u00e9zet f\u00fcggetlen\u00fcl. A laza csatol\u00e1st a ViewModel \u00e9s a n\u00e9zet k\u00f6z\u00f6tt az adatk\u00f6t\u00e9s biztos\u00edtja.
                  • Services (szolg\u00e1ltat\u00e1sok): Az alkalmaz\u00e1s \u00fczleti logik\u00e1j\u00e1t tartalmaz\u00f3 oszt\u00e1lyok, amelyeket a ViewModel-ek haszn\u00e1lnak. Ha minden \u00fczleti logika a ViewModel-ekben lenne, azok t\u00fal bonyolultak \u00e9s \u00e1tl\u00e1thatatlanok lenn\u00e9nek. Ez nem az MVVM minta r\u00e9sze, de itt eml\u00edtj\u00fck meg, mert mi is \u00edgy fogjuk haszn\u00e1lni az alkalmaz\u00e1s architekt\u00far\u00e1j\u00e1t.

                  \u00daj:

                  • Model: Domainspecifikus adatokat fog \u00f6ssze, melyet a ViewModel-ek haszn\u00e1lhatnak az adatok t\u00e1rol\u00e1s\u00e1ra. Pl. Recipe/Product/Order oszt\u00e1ly, egy recept/term\u00e9k/megrendel\u00e9s adatait fogja \u00f6ssze.
                  • View: A felhaszn\u00e1l\u00f3i fel\u00fclet le\u00edr\u00e1s\u00e1t tartalmazza, (\u00e9s a tiszt\u00e1n a n\u00e9zetekhez kapcsol\u00f3d\u00f3 logik\u00e1t, pl. anim\u00e1ci\u00f3k kezel\u00e9s\u00e9t). Tipikusan Window, Page, UserControl lesz\u00e1rmazott oszt\u00e1ly, XAML-beli deklarat\u00edv le\u00edr\u00e1ssal, a code-behind sokszor \u00fcres (mert a logika a ViewModel-ben van).
                  • ViewModel: A n\u00e9zethez tartoz\u00f3 logika van benne: tartalmazza a n\u00e9zet \u00e1llapot\u00e1t \u00e9s a n\u00e9zeten v\u00e9grehajthat\u00f3 m\u0171veleteket. F\u00fcggetlen a n\u00e9zett\u0151l, a laza csatol\u00e1st a ViewModel \u00e9s a n\u00e9zet k\u00f6z\u00f6tt adatk\u00f6t\u00e9s biztos\u00edtja (a n\u00e9zet vez\u00e9rl\u0151i k\u00f6tnek a ViewModel tulajdons\u00e1gaihoz). Unit tesztelhet\u0151!
                  • Services (szolg\u00e1ltat\u00e1sok): Az alkalmaz\u00e1s \u00fczleti/alkalmaz\u00e1s logik\u00e1j\u00e1t tartalmaz\u00f3 oszt\u00e1lyok, amelyeket a ViewModel-ek haszn\u00e1lnak. Ha minden \u00fczleti logika a ViewModel-ekben lenne, azok t\u00fal bonyolultak \u00e9s \u00e1tl\u00e1thatatlanok lenn\u00e9nek. Ez nem az MVVM minta r\u00e9sze, de itt eml\u00edtj\u00fck meg, mert mi is \u00edgy fogjuk fel\u00e9p\u00edteni az alkalmaz\u00e1s architekt\u00far\u00e1j\u00e1t.

                  Mihez k\u00e9sz\u00edt\u00fcnk ViewModel oszt\u00e1lyokat?

                  • Az egyes n\u00e9zetekhez (pl. Window, Page, Dialog, UserControl) mindig k\u00e9sz\u00edt\u00fcnk ViewModel oszt\u00e1lyt, \u00e9s bel\u0151le egy n\u00e9zethez egy objektumot hozunk l\u00e9tre. Pl. MainPage-hez MainPageViewModel, DancerDialog-hoz DancerDialogViewModel. Ezt a gyakorlat sor\u00e1n is alkalmazzuk.
                  • Az egyes modell oszt\u00e1lyokhoz (pl. Recipe, Product, Dancer stb.) opcion\u00e1lisan k\u00e9sz\u00edthet\u00fcnk csomagol\u00f3 ViewModel oszt\u00e1lyokat (pl. RecipeViewModel, ProductViewModel, DancerViewModel), ilyeneket a gyakorlat sor\u00e1n nem fogunk k\u00e9sz\u00edteni. Ez az\u00e9rt van, mert nem a Strict, hanem a Relaxed MVVM mint\u00e1t k\u00f6vetj\u00fck (l\u00e1sd el\u0151ad\u00e1s).
                  "},{"location":"labor/5-mvvm/#0-feladat-projekt-felepitese","title":"0. Feladat - Projekt fel\u00e9p\u00edt\u00e9se","text":"

                  Az alkalmaz\u00e1s v\u00e1za m\u00e1r el\u0151 van k\u00e9sz\u00edtve. Tekints\u00fck \u00e1t a projekt fel\u00e9p\u00edt\u00e9s\u00e9t.

                  Az MvvmLab a futtathat\u00f3 alkalmaz\u00e1s projektje, amely WinUI keretrendszert haszn\u00e1l a megjelen\u00edt\u00e9si r\u00e9teg\u00e9ben a m\u00e1r tanult XAML nyelvvel. Az MvvmLab.Core projekt (class library) a teljesen n\u00e9zet f\u00fcggetlen \u00fczleti logik\u00e1kat tartalmazza.

                  Ami sz\u00e1munkra fontos a kiindul\u00f3 projektben:

                  • App.xaml.cs: Az alkalmaz\u00e1s bel\u00e9p\u00e9si pontja, amely haszn\u00e1lja a modern .NET alkalmaz\u00e1sokban alkalmazott Host Builder \u00e9s Dependency Injection mint\u00e1kat. A f\u00e9l\u00e9vnek ez nem az anyaga, de a f\u00fcgg\u0151s\u00e9g injekt\u00e1l\u00e1sr\u00f3l m\u00e9g a labor sor\u00e1n lesz sz\u00f3.
                  • Views mappa: Az alkalmaz\u00e1s n\u00e9zeteit tartalmazza, jelenleg a MainPage-et
                  • ViewModels mappa: Az alkalmaz\u00e1s ViewModel-jeit tartalmazza, jelenleg a MainPageViewModel-t
                  • INagivationService (Services mapp\u00e1ban): oldalak k\u00f6z\u00f6tti navig\u00e1ci\u00f3hoz haszn\u00e1lt szolg\u00e1ltat\u00e1s

                  MVVM \u00e9s Boilerplate k\u00f6nyvt\u00e1rak

                  MVVM mint\u00e1t ritk\u00e1n szoktunk kiz\u00e1r\u00f3lag a .NET keretrendszerre t\u00e1maszkodva implement\u00e1lni. \u00c9rdemes haszn\u00e1lni valamilyen MVVM k\u00f6nyvt\u00e1rat, amelyek seg\u00edts\u00e9g\u00e9vel a k\u00f3dunk t\u00f6m\u00f6rebb, \u00e1tl\u00e1that\u00f3bb, \u00e9s kevesebb boilerplate k\u00f3dot fog tartalmazni. A k\u00f6nyvt\u00e1rak k\u00f6z\u00fcl a legelterjedtebbek a k\u00f6vetkez\u0151k:

                  • MVVM Toolkit: Microsoft \u00e1ltal gondozott MVVM k\u00f6nyvt\u00e1r
                  • Prism: R\u00e9gen Microsoft gondoz\u00e1s\u00e1ban \u00e1llt \u00e9s nagyon elterjedt volt, de m\u00e1r k\u00fcls\u0151 fejleszt\u0151k tartj\u00e1k karban \u00e9s fizet\u0151s lett id\u0151 k\u00f6zben.
                  • ReactiveUI: A Reactive Extensions (Rx) k\u00f6nyvt\u00e1rakat haszn\u00e1lja a ViewModel \u00e1llapot\u00e1nak kezel\u00e9s\u00e9re, \u00e9s a n\u00e9zet \u00e9s ViewModel k\u00f6z\u00f6tti adatk\u00f6t\u00e9sre. Ez a k\u00f6nyvt\u00e1r ny\u00fajtja a legt\u00f6bb szolg\u00e1ltat\u00e1st, de a legnehezebben tanulhat\u00f3 is.
                  • Uno.Extensions: MVVM Toolkitre \u00e9p\u00fcl, de t\u00f6bb olyan szolg\u00e1ltat\u00e1st is tartalmaz, amelyek a WinUI keretrendszer hi\u00e1nyoss\u00e1gait p\u00f3tolj\u00e1k.

                  A labor sor\u00e1n a Microsoft \u00e1ltal gondozott MVVM Toolkitet fogjuk haszn\u00e1lni.

                  A kiindul\u00f3 projekt pedig a Windows Template Studio Visual Studio kieg\u00e9sz\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel k\u00e9sz\u00fclt.

                  "},{"location":"labor/5-mvvm/#1-feladat-receptek-fooldal","title":"1. Feladat - Receptek f\u0151oldal","text":"

                  A megold\u00e1s sor\u00e1n \"alulr\u00f3l\", az adatok fel\u0151l fogunk \u00e9p\u00edtkezni \u00e9s fokozatosan fogunk eljutni a n\u00e9zetig. Ugyan a val\u00f3 \u00e9letben egy top-bottom fejleszt\u00e9s gyakran hasznosabb, de a labor sor\u00e1n az id\u0151 r\u00f6vids\u00e9ge miatt az alulr\u00f3l \u00e9p\u00edtkez\u00e9s gyorsabb \u00e9s egyszer\u0171bb, mert \u00edgy nem kell az adatokat mockolni. Az al\u00e1bbi \u00e1bra a f\u0151oldalhoz tartoz\u00f3 fontosabb oszt\u00e1lyokat tekinti \u00e1t.

                  A f\u0151oldal MMVM alap\u00fa megval\u00f3s\u00edt\u00e1sa

                  Fontosabb elemek:

                  • MainPage: ez a View, egy Page lesz\u00e1rmazott, a fel\u00fclet XAML alap\u00fa le\u00edr\u00e1sa.
                  • MainPageViewModel: a f\u0151oldalhoz (MainPage) tartoz\u00f3 ViewModel. Egy (gener\u00e1lt) RecipeGroups tulajdons\u00e1gban receptcsoportokat, a receptcsoportokban recepteket tartalmaz. A n\u00e9zet ezen a receptcsoportok fejl\u00e9c\u00e9t, illetve a csoportokban lev\u0151 receptek fejl\u00e9c\u00e9t \u00e9s k\u00e9peit jelen\u00edti meg adatk\u00f6t\u00e9ssel.
                  • RecipeGroup \u00e9s Recipe: a receptcsoportokat \u00e9s a recepteket reprezent\u00e1l\u00f3 modell oszt\u00e1lyok.
                  • RecipeService: alkalmaz\u00e1slogika/adatel\u00e9r\u00e9s a receptek kezel\u00e9s\u00e9hez (egy t\u00e1voli szolg\u00e1ltat\u00e1ssal kommunik\u00e1l), a ViewModel haszn\u00e1lja.
                  "},{"location":"labor/5-mvvm/#11-adateleresi-szolgaltatas","title":"1.1 Adatel\u00e9r\u00e9si szolg\u00e1ltat\u00e1s","text":"

                  Kezdj\u00fck az adatel\u00e9r\u00e9si r\u00e9teggel, amit most tekinthet\u00fcnk az MVVM mint\u00e1ban a modell r\u00e9tegnek is.

                  Az alkalmaz\u00e1sunk adatait egy webszerverr\u0151l k\u00e9rdezi le (\u00fan. REST API-n, HTTP-n kereszt\u00fcl \u00e9ri el). Az ehhez hasonl\u00f3 kliens-szerver architekt\u00far\u00e1j\u00fa alkalmaz\u00e1sok egy kifejezetten gyakori megold\u00e1snak sz\u00e1m\u00edtanak a modern alkalmaz\u00e1sok fejleszt\u00e9se sor\u00e1n. Err\u0151l b\u0151vebben a k\u00f6vetkez\u0151 f\u00e9l\u00e9vben a Mobil \u00e9s Webes szoftverek, illetve az Adatvez\u00e9relt alkalmaz\u00e1sok t\u00e1rgyakban lesz sz\u00f3. Most el\u00e9g annyit tudni, hogy a kliens alkalmaz\u00e1sunk HTTP k\u00e9r\u00e9seket fog k\u00fcldeni a szervernek, amelyekre a szerver v\u00e1laszolni fog, m\u00e9gpedig JSON form\u00e1tumban szolg\u00e1ltat adatokat.

                  Kliens-szerver architekt\u00fara

                  A t\u00e1voli szolg\u00e1ltat\u00e1s a k\u00f6vetkez\u0151 c\u00edmen \u00e9rhet\u0151 el: https://bmecookbook2.azurewebsites.net/api. A szolg\u00e1ltat\u00e1shoz pedig tartozik egy OpenApi alap\u00fa dokument\u00e1ci\u00f3 a https://bmecookbook2.azurewebsites.net/swagger c\u00edmen. Tanulm\u00e1nyozzuk ezt \u00e1t, vagy ak\u00e1r pr\u00f3b\u00e1ljuk ki a v\u00e9gpotokat a Swagger fel\u00fclet\u00e9n kereszt\u00fcl (ehhez \u00edrjuk be az el\u0151z\u0151 \"swagger\" v\u00e9gz\u0151d\u00e9s\u0171 URL-t egy b\u00f6ng\u00e9sz\u0151 c\u00edmsor\u00e1ba). Az els\u0151 feladathoz a /api/Recipes/Groups v\u00e9gpontot fogjuk haszn\u00e1lni, amely a receptek csoportos\u00edt\u00e1s\u00e1t adja vissza.

                  Vegy\u00fcnk fel az MvvmLab.Core projekt Models mapp\u00e1j\u00e1ba egy \u00faj oszt\u00e1lyt RecipeGroup n\u00e9ven.

                  A swagger seg\u00edts\u00e9g\u00e9vel h\u00edvjuk meg az \"api/Recipes/Groups\" v\u00e9gpontot (pontosabban egy http GET k\u00e9r\u00e9st k\u00fcldj\u00fc)

                  • A swagger fel\u00fcleten a \"Get api/Recipes/Groups\" v\u00e9gpont le\u00edr\u00e1st nyissuk le
                  • Kattintsunk az Execute gombon
                  • A szolg\u00e1ltat\u00e1s \u00e1ltal k\u00fcld\u00f6tt JSON v\u00e1lasz a \"Response body\" alatt jelenik meg: itt azt l\u00e1tjuk, hogy a v\u00e1laszban receptcsoportokat kaptunk. Minden csoportnak van egy \"title\"-je (pl. Chinese, Mexican, Italian), \u00e9s a csoportok alatt tal\u00e1lhat\u00f3k [] k\u00f6z\u00f6tt (JSON t\u00f6mb) a csoportban lev\u0151 receptek adatai.
                  • M\u00e1soljunk v\u00e1g\u00f3lapra egy RecipeGroup-nyi JSON adatot. Haszn\u00e1lhatjuk az \"Example Value\" alatti kimenetet is a v\u00e1g\u00f3lapra m\u00e1sol\u00e1skor (de a nyit\u00f3 [ \u00e9s z\u00e1r\u00f3 ] karatereket ne m\u00e1soljuk ki). Ha valami\u00e9rt elakadn\u00e1nk, az al\u00e1bbi leny\u00edl\u00f3 szakaszb\u00f3l is kim\u00e1solhatjuk a v\u00e1g\u00f3lapra a tartalmat:

                    V\u00e1g\u00f3lapra m\u00e1soland\u00f3
                    {\n    \"Title\": \"string\",\n    \"Recipes\": [\n        {\n            \"Id\": 0,\n            \"Title\": \"string\",\n            \"BackgroundImage\": \"string\"\n        }\n    ]\n}\n

                  Visual Studio-ban az Edit men\u00fc Paste Special men\u00fcpontj\u00e1ban a Paste JSON as Classes men\u00fcpontot v\u00e1lasztva illessz\u00fck be a v\u00e1g\u00f3lap tartalm\u00e1t. Ekkor olyan oszt\u00e1lyokat gener\u00e1l a VS, mely megfelel a beillesztett JSON szerkezet\u00e9nek.

                  A kapott oszt\u00e1lyokat \u00e1tnevezhetj\u00fck, hogy a C# k\u00f3dol\u00e1si konvenci\u00f3knak megfeleljenek. A Rootobject oszt\u00e1lyt nevezz\u00fck \u00e1t RecipeGroup-ra, a Recipe oszt\u00e1lyt pedig RecipeHeader-re.

                  public class RecipeGroup\n{\n    public string Title { get; set; }\n    public RecipeHeader[] Recipes { get; set; }\n}\n\npublic class RecipeHeader\n{\n    public int Id { get; set; }\n    public string Title { get; set; }\n    public string BackgroundImage { get; set; }\n}\n

                  List<T> haszn\u00e1lata

                  Eset\u00fcnkben nem volt r\u00e1 sz\u00fcks\u00e9g (mert nem b\u0151vj\u00fck receptgy\u0171jtem\u00e9nyeket), de ha k\u00e9nyelmesebb sz\u00e1munkra, akkor nyugodtan \u00edrjuk \u00e1t a gener\u00e1lt k\u00f3dban a t\u00f6mb\u00f6ket List<T>-re.

                  K\u00e9sz\u00edts\u00fcnk egy IRecipeService interf\u00e9szt az MvvmLab.Core.Services n\u00e9vt\u00e9rbe, amelyen kereszt\u00fcl el fogjuk \u00e9rni a t\u00e1voli szolg\u00e1ltat\u00e1st. Az interf\u00e9szben egy GetRecipeGroupsAsync met\u00f3dust hozzunk l\u00e9tre, amely a recept csoportokat k\u00e9rdezi le \u00e9s adja vissza.

                  public interface IRecipeService\n{\n    public Task<RecipeGroup[]> GetRecipeGroupsAsync();\n}\n

                  Task visszat\u00e9r\u00e9si \u00e9rt\u00e9k

                  Az interf\u00e9szben a t\u00e9nyleges visszat\u00e9r\u00e9si \u00e9rt\u00e9ket (RecipeGroup[]) egy Task<T> objektumba csomagoljuk, mivel a h\u00e1l\u00f3zati m\u0171veleteket aszinkron c\u00e9lszer\u0171 implement\u00e1lni. .NET-ben az aszinkron megval\u00f3s\u00edt\u00e1s legkorszer\u0171bb \u00e9s legegyszer\u0171bb m\u00f3dja a Task-ok alkalmaz\u00e1sa. Az aszinkronit\u00e1s pedig azt biztos\u00edtja itt sz\u00e1munkra, hogy ha a h\u00e1l\u00f3zati k\u00e9r\u00e9s sok\u00e1ig tart, akkor se fagyjon be a felhaszn\u00e1l\u00f3i fel\u00fclet (\u00e9s mindezt k\u00fcl\u00f6n sz\u00e1lak ind\u00edt\u00e1sa n\u00e9lk\u00fcl).

                  Az interf\u00e9sz implement\u00e1ci\u00f3j\u00e1t a MvvmLab.Core.Services n\u00e9vt\u00e9rben hozzuk l\u00e9tre RecipeService n\u00e9ven. A szolg\u00e1ltat\u00e1sunk a HttpClient be\u00e9p\u00edtett .NET oszt\u00e1lyt fogja haszn\u00e1lni a REST API h\u00edv\u00e1sokhoz. A GetFromJsonAsync ind\u00edt egy HTTP GET aszinkron k\u00e9r\u00e9st a megadott c\u00edmre, \u00e9s a v\u00e1laszt JSON form\u00e1tumb\u00f3l deszerializ\u00e1lja a megadott t\u00edpusra.

                  public class RecipeService : IRecipeService\n{\n    private readonly string _baseUrl = \"https://bmecookbook2.azurewebsites.net/api\";\n\n    public async Task<RecipeGroup[]> GetRecipeGroupsAsync()\n    {\n        using var client = new HttpClient();\n        return await client.GetFromJsonAsync<RecipeGroup[]>($\"{_baseUrl}/Recipes/Groups\");\n    }\n}\n

                  A GetFromJsonAsync m\u0171velet aszinkron, \u00edgy Task-kal t\u00e9r vissza, ezt nem blokkol\u00f3 m\u00f3don bev\u00e1rni \u00e9s az eredm\u00e9ny\u00e9t el\u00e9rni az await kulcssz\u00f3val tudjuk.

                  async-await

                  Az async \u00e9s await kulcsszavak a legt\u00f6bb modern nyelvben az aszinkron f\u00fcggv\u00e9nyh\u00edv\u00e1s nyelvi szint\u0171 kezel\u00e9s\u00e9re szolg\u00e1lnak. A m\u0171k\u00f6d\u00e9s\u00e9r\u0151l a f\u00e9l\u00e9v v\u00e9g\u00e9n lesz m\u00e9g sz\u00f3 r\u00e9szletesen, de most a haszn\u00e1lathoz az al\u00e1bbiakat \u00e9rdemes tudni:

                  • Az await kulcssz\u00f3val tudunk bev\u00e1rni aszinkron v\u00e9grehajt\u00e1s\u00fa m\u0171veletet, an\u00e9lk\u00fcl, hogy blokkoln\u00e1nk a h\u00edv\u00f3t.
                  • Az await kulcssz\u00f3t, csak async kulcssz\u00f3val ell\u00e1tott f\u00fcggv\u00e9nyekben haszn\u00e1lhatjuk.
                  • Az async f\u00fcggv\u00e9nyeknek csak Task vagy Task<T> vagy void visszat\u00e9r\u00e9si \u00e9rt\u00e9k\u00fck lehet. (Illetve \"Task szer\u0171\", de ezt nem itt vessz\u00fck.)
                    • Ha egy async f\u00fcggv\u00e9nyt k\u00edv\u00fclr\u0151l be szeretn\u00e9nk v\u00e1rni, akkor az voiddal nem tudjuk megtenni, mindenk\u00e9ppen Task vagy Task<T> visszat\u00e9r\u00e9si \u00e9rt\u00e9kkel kell rendelkeznie.
                    • az async f\u00fcggv\u00e9nyekben a return utas\u00edt\u00e1s szintaktik\u00e1ja megv\u00e1ltozik: nem a Task objektummal kell visszat\u00e9rj\u00fcnk, hanem az \u00e1ltala tartalmazott adattal (Task eset\u00e9ben void, Task<T> eset\u00e9ben T).
                  "},{"location":"labor/5-mvvm/#12-fooldal-viewmodel","title":"1.2 F\u0151oldal ViewModel","text":"

                  K\u00f6vetkez\u0151 l\u00e9p\u00e9sben a f\u0151oldal ViewModelj\u00e9t fogjuk elk\u00e9sz\u00edteni, amely az el\u0151bb elk\u00e9sz\u00edtett szolg\u00e1ltat\u00e1st fogja haszn\u00e1lni a recept csoportok lek\u00e9rdez\u00e9s\u00e9hez, \u00e9s \u00e1llapotk\u00e9nt t\u00e1rolja azokat a n\u00e9zet sz\u00e1m\u00e1ra.

                  "},{"location":"labor/5-mvvm/#dependency-injection","title":"Dependency Injection","text":"

                  Nyissuk meg a MainPageViewModel oszt\u00e1lyt az MvvmLab.ViewModels mapp\u00e1b\u00f3l. A ViewModel-\u00fcnknek sz\u00fcks\u00e9ge lesz egy IRecipeService interf\u00e9szt implement\u00e1l\u00f3 oszt\u00e1lyra, amelyen kereszt\u00fcl le tudja k\u00e9rdezni a recept csoportokat. A MainPageViewModel konstruktor\u00e1ban f\u00fcgg\u0151s\u00e9g injekt\u00e1l\u00e1son kereszt\u00fcl szerezz\u00fck be a sz\u00fcks\u00e9ges f\u00fcgg\u0151s\u00e9get. Eset\u00fcnkben ez annyit tesz, hogy v\u00e1runk egy IRecipeService t\u00edpus\u00fa param\u00e9tert, amelyet majd a ViewModel p\u00e9ld\u00e1nyos\u00edt\u00e1skor fog megkapni, a param\u00e9tert pedig elmentj\u00fck egy priv\u00e1t v\u00e1ltoz\u00f3ba.

                  private readonly IRecipeService _recipeService;\n\npublic MainPageViewModel(IRecipeService recipeService)\n{\n    _recipeService = recipeService;\n}\n
                  F\u00fcgg\u0151s\u00e9g Injekt\u00e1l\u00e1s - Dependency Injection - DI

                  Alapesetben az oszt\u00e1lyok szoros csatol\u00e1st alak\u00edtanak ki a f\u00fcgg\u0151s\u00e9geikkel (referencia, p\u00e9ld\u00e1nyos\u00edt\u00e1s).

                  Er\u0151s csatol\u00e1s DI n\u00e9lk\u00fcl

                  Ez a szoros csatol\u00e1s nehez\u00edti a tesztelhet\u0151s\u00e9get, a karbantarthat\u00f3s\u00e1got \u00e9s az \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1got. Ezen seg\u00edt a Dependency Injection (\u00e9s a Strategy) alkalamaz\u00e1sa. A t\u00e1rgy keret\u00e9ben a tervez\u00e9si mint\u00e1khoz kapcsol\u00f3d\u00f3an tanulunk a Dependency Injection (DI) tervez\u00e9si mint\u00e1r\u00f3l, melyet mindig a Strategy mint\u00e1val egy\u00fctt alkalmazunk. A l\u00e9nyege az, hogy egy oszt\u00e1ly nem maga hozza l\u00e9tre a f\u00fcgg\u0151s\u00e9geit (azon oszt\u00e1lyokat, melyekt\u0151l f\u00fcgg, melyeket felhaszn\u00e1l), hanem k\u00edv\u00fclr\u0151l kapja meg, pl. konstruktor param\u00e9terben. A Strategy mint\u00e1b\u00f3l ad\u00f3d\u00f3an pedig az k\u00f6vetkezik, hogy csak \"interf\u00e9szk\u00e9nt\" f\u00fcgg t\u0151l\u00fck.

                  A mai legt\u00f6bb platform egy plusz szolg\u00e1ltat\u00e1st, \u00fan. DI (m\u00e1s nev\u00e9n IoC) kont\u00e9nert is biztos\u00edt a f\u00fcggg\u0151s\u00e9gek k\u00e9nyelmes kezel\u00e9s\u00e9hez. A f\u00fcgg\u0151s\u00e9gek \u00e9letciklus\u00e1t ez esetben egy kit\u00fcntetett komponens kezeli, a DI kont\u00e9ner. A DI kont\u00e9ner (\u00e1br\u00e1n Builder) felel\u0151s az oszt\u00e1lyok p\u00e9ld\u00e1nyos\u00edt\u00e1s\u00e1\u00e9rt \u00e9s a f\u00fcgg\u0151s\u00e9gek beinjekt\u00e1l\u00e1s\u00e1\u00e9rt rekurz\u00edvan.

                  DI oszt\u00e1lydiagramm

                  Ahhoz, hogy a p\u00e9ld\u00e1nyos\u00edt\u00e1s sor\u00e1n a f\u00fcgg\u0151s\u00e9gi gr\u00e1fot bej\u00e1rva beinjekt\u00e1lja a megfelel\u0151 implement\u00e1ci\u00f3kat a kont\u00e9ner, a DI kont\u00e9nerbe be kell regisztr\u00e1lni a f\u00fcgg\u0151s\u00e9gi lek\u00e9pez\u00e9seket. Alkalmaz\u00e1sunkban ezt az App.xaml.cs f\u00e1jlban a ConfigureServices met\u00f3dusban tessz\u00fck meg. Vegy\u00fck fel az al\u00e1bbi sort, pl. a // Core Services szakasz al\u00e1:

                  services.AddTransient<IRecipeService, RecipeService>();\n

                  Ez azt mondja meg, hogy ahol egy oszt\u00e1lyunk IRecipeService f\u00fcgg\u0151s\u00e9get v\u00e1r (pl. MainPageViewModel konstruktora), a DI keretrendszer egy RecipeService implement\u00e1ci\u00f3t sz\u00far be (\u00e9s mivel itt Tranziens \u00e9lettartam\u00fak\u00e9nt regisztr\u00e1ltuk, minden egyes IRecipeService f\u00fcgg\u0151s\u00e9g ig\u00e9nyt egy \u00faj RecipeService p\u00e9ld\u00e1ny fog kiel\u00e9g\u00edteni).

                  Ahhoz, hogy a Dependency Injection az alkalmaz\u00e1sunkban m\u0171k\u00f6dj\u00f6n, a MainPageViewModel oszt\u00e1lyt is be kell regisztr\u00e1lni a kont\u00e9nerbe, ezt is megtal\u00e1ljuk a ConfigureServices alatt.

                  DI kont\u00e9nerekr\u0151l r\u00e9szletesen

                  A DI kont\u00e9nerek haszn\u00e1lat\u00e1val \u00e9s m\u0171k\u00f6d\u00e9s\u00e9vel Adatvez\u00e9relt rendszerek t\u00e1rgy keret\u00e9ben fogunk k\u00e9s\u0151bb r\u00e9szletesen megismerkedni.

                  "},{"location":"labor/5-mvvm/#viewmodel-allapot","title":"ViewModel \u00e1llapot","text":"

                  K\u00f6vetkez\u0151 l\u00e9p\u00e9sben a ViewModel \u00e1llapot\u00e1nak felt\u00f6lt\u00e9s\u00e9t implement\u00e1ljuk.

                  A c\u00e9lunk az, hogy

                  • a MainPageViewModel-ben legyen RecipeGroupsnev\u0171 tulajdons\u00e1g, melyben receptcsoportok vannak (ezt akarjuk a fel\u00fclethez k\u00f6tni),
                  • a RecipeGroups v\u00e1ltoz\u00e1sait k\u00f6vesse le a fel\u00fclet, melyhez sz\u00fcks\u00e9g van az INotifyPropertyChanged megval\u00f3s\u00edt\u00e1s\u00e1ra \u00e9s a PropertyChanged megfelel\u0151 els\u00fct\u00e9s\u00e9re (ahogy a kor\u00e1bbi laboron/h\u00e1zi feladatban m\u00e1r l\u00e1ttuk).

                  Ehhez viszonylag \"sokat\" kellene dolgoznunk, de az MVVM toolkit leegyszer\u0171s\u00edti az \u00e9let\u00fcnket, mind\u00f6ssze a k\u00f6vetkez\u0151t kell megtenn\u00fcnk:

                  • A MainPageViewModel-ben hozzunk l\u00e9tre egy _recipeGroups nev\u0171 RecipeGroup[] tagv\u00e1ltoz\u00f3t (vagyis nem tulajdons\u00e1got).
                  • A v\u00e1ltoz\u00f3t l\u00e1ssuk el a ObservableProperty attrib\u00fatummal.
                  [ObservableProperty]\nprivate RecipeGroup[] _recipeGroups = Array.Empty<RecipeGroup>();\n

                  K\u00e9sz is vagyunk. De mi t\u00f6rt\u00e9nik ennek hat\u00e1s\u00e1ra?

                  • Ez alapj\u00e1n az MVVM Toolkit automatikusan gener\u00e1lni fog egy RecipeGroups nev\u0171 property-t az oszt\u00e1ly gener\u00e1lt m\u00e1sik (partial) fel\u00e9ben.
                  • Ez a gener\u00e1lt property kihaszn\u00e1lja az INotifyPropertyChanged interf\u00e9szt, \u00edgy a RecipeGroups property \u00e9rt\u00e9k\u00e9nek megv\u00e1ltoz\u00e1sakor a PropertyChanged esem\u00e9nyt kiv\u00e1ltva \u00e9rtes\u00edti a n\u00e9zetet, az adatk\u00f6t\u00e9sek ment\u00e9n.
                  • A MainPageViewModel-\u00fcnk m\u00e1r megval\u00f3s\u00edtja az INotifyPropertyChanged interf\u00e9szt, mert az MVVM Toolkit ObservableObject oszt\u00e1ly\u00e1b\u00f3l sz\u00e1rmazik.

                  A MainPageViewModel-ben implement\u00e1ljuk az el\u0151k\u00e9sz\u00edtett INavigationAware interf\u00e9szt, amelynek seg\u00edts\u00e9g\u00e9vel a n\u00e9zetek k\u00f6z\u00f6tti navig\u00e1ci\u00f3s \u00e9letciklus esem\u00e9nyt tudjuk lekezelni, \u00e9s ak\u00e1r adatokat is tudunk \u00e1tadni a ViewModel-ek k\u00f6z\u00f6tt. A OnNavigatedTo met\u00f3dusban k\u00e9rdezz\u00fck le a recept csoportokat az IRecipeService-en kereszt\u00fcl, majd t\u00e1roljuk el a RecipeGroups v\u00e1ltoz\u00f3ban.

                  public partial class MainPageViewModel : ObservableObject, INavigationAware\n{\n    // ...\n\n    public async void OnNavigatedTo(object parameter)\n    {\n        RecipeGroups = await _recipeService.GetRecipeGroupsAsync();\n    }\n\n    public void OnNavigatedFrom()\n    {\n    }\n}\n
                  "},{"location":"labor/5-mvvm/#13-fooldal-nezet","title":"1.3 F\u0151oldal n\u00e9zet","text":"

                  A MainPage-en k\u00e9sz\u00edts\u00fck el a n\u00e9zetet, amelyen megjelen\u00edtj\u00fck a recept csoportokat.

                  Ahhoz, hogy a csoportos\u00edt\u00e1st kezelni tudja a GridView, sz\u00fcks\u00e9g\u00fcnk van egy olyan list\u00e1ra, mely elv\u00e9gzi a csoportos\u00edt\u00e1st. Ezt a CollectionViewSource oszt\u00e1ly seg\u00edts\u00e9g\u00e9vel tudjuk megval\u00f3s\u00edtani, ami bizonyos szempontb\u00f3l UI specifikus burkol\u00f3 feladatokat l\u00e1t el gy\u0171jtem\u00e9nyeken. A CollectionViewSource-nak meg kell adnunk a csoportos\u00edtand\u00f3 elemeket, valamint azt, hogy a csoportokat milyen property alapj\u00e1n hozza l\u00e9tre. Tov\u00e1bb\u00e1 meg kell adnunk azt is, hogy a csoportokon bel\u00fcl milyen property alapj\u00e1n jelen\u00edtse meg az elemeket.

                  Hozzuk l\u00e9tre az oldal er\u0151forr\u00e1sai k\u00f6z\u00f6tt a CollectionViewSource p\u00e9ld\u00e1nyt (az al\u00e1bbi k\u00f3dot a MainPage.xaml-be, a Grid f\u00f6l\u00e9 tegy\u00fck be, vele egy szintre).

                  <Page.Resources>\n    <CollectionViewSource x:Name=\"RecipeGroupsCollectionSource\"\n                            IsSourceGrouped=\"True\"\n                            ItemsPath=\"Recipes\"\n                            Source=\"{x:Bind ViewModel.RecipeGroups, Mode=OneWay}\" />\n</Page.Resources>\n

                  Note

                  Vegy\u00fck \u00e9szre, hogy az adatk\u00f6t\u00e9s sor\u00e1n a ViewModel tulajdons\u00e1ghoz k\u00f6t\u00fcnk, mely a MainPage.xaml.cs-ben tal\u00e1lhat\u00f3, \u00e9s egyszer\u0171en csak \u00e1tkasztolja a DataContext property-t a ViewModel t\u00edpusunkra.

                  public MainPageViewModel ViewModel => DataContext as MainPageViewModel;\n

                  Az, hogy a vez\u00e9rl\u0151k (oldalak) DataContext tulajdons\u00e1g\u00e1ban a ViewModel-t t\u00e1roljuk tipikus az MVVM mint\u00e1ban. Eset\u00fcnkben ezt a gener\u00e1lt projekt NavigationService oszt\u00e1lya teszi meg nek\u00fcnk.

                  Er\u0151forr\u00e1sok

                  XAML k\u00f6rnyezetben minden vez\u00e9rl\u0151 (fenti p\u00e9ld\u00e1ban Page) \u00e9s az Application oszt\u00e1ly is, rendelkezik egy Resources property-vel, mely egy kulcs \u00e9rt\u00e9k t\u00e1rol\u00f3 (Dictionary<string, object>), alap esetben. Ebbe tudunk t\u00f6bbsz\u00f6r felhaszn\u00e1lhat\u00f3 objektumokat rakni, ak\u00e1r alkalmaz\u00e1s szinten is. Ha ehhez az er\u0151forr\u00e1sok p\u00e9ld\u00e1nyos\u00edt\u00e1sakor megadjuk az x:Key attrib\u00fatumot, akkor az er\u0151forr\u00e1sokat a kulcs alapj\u00e1n tudjuk lek\u00e9rdezni pl.: a {StaticResource Key} markup extensionnel.

                  Mi viszont itt kifejezetten x:Key helyett x:Name-et adtunk meg, mert az x:Bind-ban n\u00e9v szerint szeretn\u00e9nk majd hivatkozni r\u00e1 (eml\u00e9kezz\u00fcnk: az x:Name attrib\u00fatum seg\u00edts\u00e9g\u00e9vel azt tudjuk el\u00e9rni, hogy gener\u00e1l\u00f3dik ilyen n\u00e9ven egy tagv\u00e1ltoz\u00f3 az oszt\u00e1lyunkban, \u00edgy a code behind f\u00e1jlb\u00f3l, vagy x:Bind adatk\u00f6t\u00e9s sor\u00e1n ilyen n\u00e9ven el tudjuk \u00e9rni).

                  A receptek list\u00e1z\u00e1s\u00e1hoz, most egy speci\u00e1lis GridView lesz\u00e1rmazott vez\u00e9rl\u0151t haszn\u00e1ljunk, m\u00e9gpedig az AdaptiveGridView-t a CommunityToolkit csomagb\u00f3l, amely a n\u00e9zet m\u00e9ret\u00e9nek megfelel\u0151en v\u00e1ltoztatja a megjelen\u00edtett elemek sz\u00e1m\u00e1t \u00e9s m\u00e9ret\u00e9t, illetve t\u00e1mogatja a Command-okat az elem kattint\u00e1s eset\u00e9ben. A k\u00fcls\u0151 vez\u00e9rl\u0151k hivatkoz\u00e1s\u00e1hoz vegy\u00fck fel az oldalra a k\u00f6vetkez\u0151 n\u00e9vteret:

                  xmlns:controls=\"using:CommunityToolkit.WinUI.UI.Controls\"\n

                  K\u00e9sz\u00edts\u00fck el a GridView-t, amelynek a ItemsSource property-j\u00e9t a fenti er\u0151forr\u00e1sban l\u00e9v\u0151 RecipeGroupsCollectionSource.View-ra k\u00f6tj\u00fck.

                  A GridView-en bel\u00fcl a megszokott m\u00f3don az ItemTemplate property-n kereszt\u00fcl tudjuk megadni, hogy az egyes elemeket hogyan kell megjelen\u00edteni. Eset\u00fcnkben egy k\u00e9pet \u00e9s egy sz\u00f6veget rakunk ki a receptek c\u00edme alapj\u00e1n egy \"k\u00e1rtya\" szer\u0171 layoutra.

                  A GroupStyle property-n kereszt\u00fcl pedig meg tudjuk adni, hogy a csoportokat hogyan kell megjelen\u00edteni. Eset\u00fcnkben a fejl\u00e9cet akarjuk testreszabni.

                  A MainPage.xaml-ben a <Grid x:Name=\"ContentArea\"> ... grid-et cser\u00e9lj\u00fck le a k\u00f6vetkez\u0151re:

                  <Grid x:Name=\"ContentArea\" Padding=\"10\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n\n    <TextBlock Text=\"Recipes\"\n               Grid.Row=\"0\"\n               Style=\"{StaticResource TitleLargeTextBlockStyle}\" />\n\n    <controls:AdaptiveGridView Grid.Row=\"1\"\n                               DesiredWidth=\"180\"\n                               IsItemClickEnabled=\"True\"\n                               ItemHeight=\"160\"\n                               ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                               SelectionMode=\"None\"\n                               StretchContentForSingleRow=\"False\">\n        <GridView.ItemTemplate>\n            <DataTemplate x:DataType=\"models:RecipeHeader\">\n                <Grid MaxWidth=\"300\">\n                    <Image Source=\"{x:Bind BackgroundImage}\" />\n                    <Border Height=\"40\"\n                            Padding=\"10,0,0,0\"\n                            VerticalAlignment=\"Bottom\"\n                            Background=\"#88000000\">\n                        <TextBlock VerticalAlignment=\"Center\"\n                                   Foreground=\"White\"\n                                   Text=\"{x:Bind Title}\" />\n                    </Border>\n                </Grid>\n            </DataTemplate>\n        </GridView.ItemTemplate>\n        <GridView.GroupStyle>\n            <GroupStyle>\n                <GroupStyle.HeaderTemplate>\n                    <DataTemplate x:DataType=\"models:RecipeGroup\">\n                        <TextBlock Margin=\"0\"\n                                   Style=\"{ThemeResource TitleTextBlockStyle}\"\n                                   Text=\"{x:Bind Title}\" />\n                    </DataTemplate>\n                </GroupStyle.HeaderTemplate>\n            </GroupStyle>\n        </GridView.GroupStyle>\n    </controls:AdaptiveGridView>\n</Grid>\n

                  Vegy\u00fck fel a k\u00f6vetkez\u0151 n\u00e9vteret (ebben vannak a modell oszt\u00e1lyaink):

                  `xmlns:models=\"using:MvvmLab.Core.Models\"`\n

                  Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st! Gy\u0151z\u0151dj\u00fcnk meg r\u00f3la, hogy a recept csoportok megjelennek a f\u0151oldalon.

                  "},{"location":"labor/5-mvvm/#2-feladat-recept-reszletes-oldal","title":"2. Feladat - Recept r\u00e9szletes oldal","text":"

                  A receptek r\u00e9szletes oldal\u00e1nak elk\u00e9sz\u00edt\u00e9se a k\u00f6vetkez\u0151 l\u00e9p\u00e9sekb\u0151l fog \u00e1llni:

                  1. Kieg\u00e9sz\u00edtj\u00fck az IRecipeService interf\u00e9szt egy GetRecipeAsync met\u00f3dussal, \u00e9s l\u00e9trehozzuk a sz\u00fcks\u00e9ges oszt\u00e1lyokat
                  2. L\u00e9trehozzuk a RecipeDetailPageViewModel ViewModel-t, amiben lek\u00e9rdezz\u00fck a recept adatait a RecipeDetailPageViewModel-ben az IRecipeService-en kereszt\u00fcl (a VM az azonos\u00edt\u00f3t kapja meg a navig\u00e1ci\u00f3 sor\u00e1n)
                  3. L\u00e9trehozzuk a RecipeDetailPage n\u00e9zetet, \u00e9p\u00edtve a ViewModel adataira
                  4. Regisztr\u00e1ljuk a ViewModel-t \u00e9s a n\u00e9zetet a Dependency Injection konfigur\u00e1ci\u00f3hoz \u00e9s a navig\u00e1ci\u00f3hoz
                  5. \u00c1tnavig\u00e1lunk a RecipeDetailPage-re a MainPageViewModel-b\u0151l a receptre t\u00f6rt\u00e9n\u0151 kattint\u00e1sra az INavigationService seg\u00edts\u00e9g\u00e9vel, \u00e9s \u00e1tadjuk a kiv\u00e1lasztott recept azonos\u00edt\u00f3j\u00e1t a r\u00e9szletes oldalnak
                  "},{"location":"labor/5-mvvm/#21-recept-lekerdezese","title":"2.1 Recept lek\u00e9rdez\u00e9se","text":"

                  Hozzuk l\u00e9tre a Recipe oszt\u00e1lyt a MvvmLab.Core.Model n\u00e9vt\u00e9rbe, \u00e9s gener\u00e1ljuk le a tartalm\u00e1t a /api/recipes/{id} v\u00e9gpont \u00e1ltal visszaadott p\u00e9lda JSON adatokb\u00f3l, a fent megismert m\u00f3dszerrel (Paste special).

                  public class Recipe\n{\n    public int Id { get; set; }\n    public string BackgroundImage { get; set; }\n    public string Title { get; set; }\n    public string[] ExtraImages { get; set; }\n    public string[] Ingredients { get; set; }\n    public string Directions { get; set; }\n    public Comment[] Comments { get; set; }\n}\n\npublic class Comment\n{\n    public string Name { get; set; }\n    public string Text { get; set; }\n}\n

                  Warning

                  A \"Paste Special\" sor\u00e1n fontos, hogy olyan receptet tegy\u00fcnk el\u0151tte a v\u00e1g\u00f3lapra, melyhez tartozik megjegyz\u00e9s (k\u00fcl\u00f6nben a Comment oszt\u00e1ly nem fog legener\u00e1l\u00f3dni, illetve a Recipe oszt\u00e1lyban a Comments t\u00edpus\u00e1nak object[] t\u00edpus gener\u00e1l\u00f3dik). \u00c9rdemes ehhez a swagger le\u00edr\u00e1s \"Example value\" mez\u0151j\u00e9b\u0151l a v\u00e1g\u00f3lapra m\u00e1solni a mint\u00e1t!

                  A IRecipeService interf\u00e9szt \u00e9s implement\u00e1ci\u00f3j\u00e1t eg\u00e9sz\u00edts\u00fck ki egy GetRecipeAsync met\u00f3dussal, mely egy receptet ad vissza az azonos\u00edt\u00f3ja alapj\u00e1n.

                  IRecipeService
                  public Task<Recipe> GetRecipeAsync(int id);\n
                  RecipeService
                  public async Task<Recipe> GetRecipeAsync(int id)\n{\n    using var client = new HttpClient();\n    return await client.GetFromJsonAsync<Recipe>($\"{_baseUrl}/Recipes/{id}\");\n}\n
                  "},{"location":"labor/5-mvvm/#22-recept-reszletes-viewmodel","title":"2.2 Recept r\u00e9szletes ViewModel","text":"

                  A ViewModel k\u00e9sz\u00edt\u00e9se a f\u0151oldalhoz k\u00e9pest m\u00e1r ujjgyakorlat (alapvet\u0151en annak mint\u00e1j\u00e1ra lehet dolgozni). Hozzuk l\u00e9tre a RecipeDetailPageViewModel oszt\u00e1lyt az MvvmLab.ViewModels mapp\u00e1ban.

                  A ViewModel-nek sz\u00fcks\u00e9ge lesz egy IRecipeService interf\u00e9szt implement\u00e1l\u00f3 oszt\u00e1lyra, amelyen kereszt\u00fcl le tudja k\u00e9rdezni a receptet. A RecipeDetailPageViewModel konstruktor\u00e1ban DI seg\u00edts\u00e9g\u00e9vel szerezz\u00fck be a sz\u00fcks\u00e9ges f\u00fcgg\u0151s\u00e9get.

                  private readonly IRecipeService _recipeService;\n\npublic RecipeDetailPageViewModel(IRecipeService recipeService)\n{\n    _recipeService = recipeService;\n}\n

                  A RecipeDetailPageViewModel-ben hozzunk l\u00e9tre egy _recipe nev\u0171 Recipe t\u00edpus\u00fa v\u00e1ltoz\u00f3t, amelyben t\u00e1rolni fogjuk a receptet. A v\u00e1ltoz\u00f3t attribut\u00e1ljuk fel a ObservableProperty attrib\u00fatummal, mely alapj\u00e1n az MVVM Toolkit automatikusan gener\u00e1lni fogja a Recipe nev\u0171 property-t az oszt\u00e1ly m\u00e1sik gener\u00e1lt partial fel\u00e9ben. Ehhez sz\u00fcks\u00e9ges, hogy az oszt\u00e1ly az ObservableObject oszt\u00e1lyb\u00f3l sz\u00e1rmazzon, publikus legyen \u00e9s a partial kulcssz\u00f3val legyen ell\u00e1tva.

                  public partial class RecipeDetailPageViewModel : ObservableObject\n{\n    // ...\n\n    [ObservableProperty]\n    private Recipe _recipe = new();\n

                  Implement\u00e1ljuk a RecipeDetailPageViewModel-ben az el\u0151k\u00e9sz\u00edtett INavigationAware interf\u00e9szt. Arra k\u00e9sz\u00fcl\u00fcnk, hogy a navig\u00e1ci\u00f3s param\u00e9terk\u00e9nt a megjelen\u00edteni k\u00edv\u00e1nt recept azonos\u00edt\u00f3j\u00e1t fogjuk megkapni. A OnNavigatedTo met\u00f3dusban k\u00e9rdezz\u00fck le a receptet a RecipeService-en kereszt\u00fcl, majd t\u00e1roljuk el a Recipe tulajdons\u00e1gban.

                  public partial class RecipeDetailPageViewModel : ObservableObject, INavigationAware\n{\n    // ...\n\n    public async void OnNavigatedTo(object parameter)\n    {\n        Recipe = await _recipeService.GetRecipeAsync((int)parameter);\n    }\n\n    public void OnNavigatedFrom()\n    {\n    }\n}\n

                  Note

                  A OnNavigatedTo m\u0171velet fejl\u00e9c\u00e9ben haszn\u00e1lni kellett az async kulcssz\u00f3t, mert haszn\u00e1ltuk az await-et a t\u00f6rzs\u00e9ben.

                  "},{"location":"labor/5-mvvm/#23-recept-reszletes-oldal-navigacio","title":"2.3 Recept r\u00e9szletes oldal, navig\u00e1ci\u00f3","text":"

                  Hozzunk l\u00e9tre egy \u00faj oldalt RecipeDetailPage n\u00e9ven a Views mapp\u00e1ba (Views mapp\u00e1n jobb gomb / Add New Item / Blank Page (WinUI 3)), amelyen megjelen\u00edtj\u00fck a receptet. Els\u0151 k\u00f6rben csak a recept c\u00edm\u00e9t jelen\u00edts\u00fck meg egy TextBlock-ban.

                  <Grid x:Name=\"ContentArea\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"48\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n\n    <TextBlock Grid.Row=\"0\"\n               Style=\"{StaticResource PageTitleStyle}\"\n               Text=\"{x:Bind ViewModel.Recipe.Title, Mode=OneWay}\" />\n</Grid>\n

                  Az adatk\u00f6t\u00e9shez vegy\u00fck fel a RecipeDetailPage.xaml.cs-ben a ViewModel property-t a f\u0151oldal mint\u00e1j\u00e1ra.

                  public RecipeDetailPageViewModel ViewModel => (RecipeDetailPageViewModel)DataContext;\n

                  Ford\u00edt\u00e1si hib\u00e1k

                  Ha valami\u00e9rt egzotikus hib\u00e1kat kapn\u00e1nk az \u00faj oldal felv\u00e9tele ut\u00e1n t\u00f6r\u00f6lj\u00fck ki a projekt f\u00e1jlb\u00f3l az al\u00e1bbi sorokat:

                  <ItemGroup>\n    <None Remove=\"Views\\RecipeDetailPage.xaml\" />\n</ItemGroup>\n
                  <Page Update=\"Views\\RecipeDetailPage.xaml\">\n    <Generator>MSBuild:Compile</Generator>\n</Page>\n

                  A navig\u00e1ci\u00f3 t\u00e1mogat\u00e1s\u00e1hoz a Services mapp\u00e1ban l\u00e9v\u0151 PageService-ben regisztr\u00e1ljuk be a RecipeDetailPage-et az al\u00e1bbi 3 l\u00e9p\u00e9sben:

                  1. Vegy\u00fck fel a n\u00e9zet kulcs\u00e1t a Pages oszt\u00e1lyba.

                    public static class Pages\n{\n    public static string Main { get; } = \"Main\";\n    public static string Detail { get; } = \"Detail\";\n}\n
                  2. Regisztr\u00e1ljuk a n\u00e9zetet \u00e9s ViewModel kapcsolatot a PageService-ben.

                    public PageService()\n{\n    Configure<MainPageViewModel, MainPage>(Pages.Main);\n    Configure<RecipeDetailPageViewModel, RecipeDetailPage>(Pages.Detail);\n}\n
                  3. Az App.xaml.cs f\u00e1jlban a ConfigureServices met\u00f3dusban regisztr\u00e1ljuk be a ViewModel-t \u00e9s a n\u00e9zetet a Dependency Injection kont\u00e9nerbe.

                    services.AddTransient<RecipeDetailPage>();\nservices.AddTransient<RecipeDetailPageViewModel>();\n

                  Ezekre az\u00e9rt van sz\u00fcks\u00e9g, mert a projekt sablonban l\u00e9v\u0151 INavigationService alapvet\u0151en egy kulccsal azonos\u00edtja a n\u00e9zeteket, annak \u00e9rdek\u00e9ben, hogy a ViewModel-ben ne legyen sz\u00fcks\u00e9g a n\u00e9zet t\u00edpus\u00e1nak ismeret\u00e9re. A kulcs alapj\u00e1n pedig ki tudja keresni, hogy pontosan melyik Viewt kell megjelen\u00edteni, \u00e9s melyik ViewModel-t kell p\u00e9ld\u00e1nyos\u00edtani a n\u00e9zet DataContext-j\u00e9be a DI kont\u00e9nerb\u0151l.

                  A MainPageViewModel-ben injekt\u00e1ljuk be az INavigationService-t, amelyen kereszt\u00fcl navig\u00e1lni fogunk a RecipeDetailPage-re.

                  private readonly INavigationService _navigationService;\n\npublic MainPageViewModel(IRecipeService recipeService, INavigationService navigationService)\n{\n    _recipeService = recipeService;\n    _navigationService = navigationService;\n}\n
                  "},{"location":"labor/5-mvvm/#command","title":"Command","text":"

                  Eddig az MVVM minta egyik oldal\u00e1val foglalkoztunk: hogyan \u00e9ri el adatk\u00f6t\u00e9ssel \u00e9s jelen\u00edti meg a View a ViewModel-ben lev\u0151 adatokat. Ugyanakkor, a View \u00e9s ViewModel k\u00f6z\u00f6tt \u00e1ltal\u00e1ban van egy m\u00e1sik kapcsolat is: ez arr\u00f3l sz\u00f3l, hogy a View esem\u00e9nyei (pl. kattint\u00e1s) hogyan hatnak vissza a ViewModel-re. Most ezzel fogunk foglalkozni.

                  Eset\u00fcnkben pl. meg kell oldani, hogy a f\u0151oldali n\u00e9zeten egy Recepten t\u00f6rt\u00e9n\u0151 kattint\u00e1s eljusson a MainPageViewModel-hez, \u00e9s az ennek hat\u00e1s\u00e1ra \u00e1tnavig\u00e1ljon az adott recept r\u00e9szletes n\u00e9zet\u00e9re.

                  A ViewModel a v\u00e9grehajthat\u00f3 m\u0171veleteket az MVVM mint\u00e1ban tipikusan ICommand interf\u00e9szt megval\u00f3s\u00edt\u00f3 objektumokon kereszt\u00fcl publik\u00e1lja (amelyek a konkr\u00e9t m\u0171velet v\u00e9grehajt\u00e1s\u00e1n t\u00fal kezelhetik a m\u0171velet v\u00e9grehajt\u00e1s\u00e1nak felt\u00e9teleit is).

                  A MainPageViewModel-ben k\u00e9sz\u00edts\u00fcnk egy Commandot, mely a receptre kattintva fog lefutni. A Command param\u00e9terk\u00e9nt megkapja a kiv\u00e1lasztott recept fejl\u00e9cet, \u00e9s \u00e1tnavig\u00e1l a RecipeDetailPage-re, ahol \u00e1tad\u00e1sra ker\u00fcl a kiv\u00e1lasztott recept azonos\u00edt\u00f3ja.

                  Most l\u00e9tre kellene hozzunk egy \u00fagy, ICommand interf\u00e9szt implement\u00e1l\u00f3 oszt\u00e1lyt, majd ebb\u0151l fel kellene vegy\u00fcnk egy p\u00e9ld\u00e1nyt (tulajdons\u00e1got) a ViewModel-be. Ezt a k\u00e9t l\u00e9p\u00e9st az MVVM toolkit leegyszer\u0171s\u00edti, csak egy [RelayCommand] attrib\u00fatummal ell\u00e1tott f\u00fcggv\u00e9nyt kell felvegy\u00fcnk a ViewModelbe:

                  [RelayCommand]\nprivate void RecipeSelected(RecipeHeader recipe)\n{\n    _navigationService.NavigateTo(Pages.Detail, recipe.Id);\n}\n

                  Ennek hat\u00e1s\u00e1ra a compiler legener\u00e1lja a command oszt\u00e1lyt \u00e9s a tulajdons\u00e1got a ViewModel-be RecipeSelectedCommand n\u00e9ven.

                  A parancs \u00e9s a ViewModel el\u0151 van k\u00e9sz\u00edtve, de a View m\u00e9g semmit nem tud a parancsr\u00f3l. A ViewModel-ben lev\u0151 commandunkat a szok\u00e1sos technik\u00e1kkal r\u00e1 kell k\u00f6ss\u00fck a View megfelel\u0151 esem\u00e9ny\u00e9re. MVVM eset\u00e9n mindig \u00edgy haszn\u00e1ljuk a Command mint\u00e1t! A megk\u00f6zel\u00edt\u00e9s sz\u00e9ps\u00e9ge az, hogy ez teljesen a szok\u00e1sos, View->ViewModel ir\u00e1ny\u00fa adatk\u00f6t\u00e9ssel t\u00f6rt\u00e9nik (amit m\u00e1r eddig is t\u00f6bbsz\u00f6r haszn\u00e1ltunk).

                  Ennek megfelel\u0151en a MainPage-en k\u00f6ss\u00fck a AdaptiveGridView ItemClickCommand tulajdons\u00e1g\u00e1t a RecipeSelectedCommand-ra.

                  ItemClickCommand=\"{x:Bind ViewModel.RecipeSelectedCommand}\"\n

                  Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st! Gy\u0151z\u0151dj\u00fcnk meg r\u00f3la, hogy a receptekre kattintva megjelenik a recept r\u00e9szletes oldala.

                  Kitekint\u00e9s: Ha nincs a haszn\u00e1lni k\u00edv\u00e1nt esem\u00e9nyre Command?

                  Ha a vez\u00e9rl\u0151 bizonyos esem\u00e9nyekre biztos\u00edt Commandot, akkor viszonylag egyszer\u0171 dolgunk van, amire fentebb l\u00e1thattunk egy p\u00e9ld\u00e1t. Azonban, ha a vez\u00e9rl\u0151 nem biztos\u00edt Commandot (pl.: a be\u00e9p\u00edtett GridView.ItemClicked), akkor t\u00f6bb lehet\u0151s\u00e9g\u00fcnk is van:

                  1. Code-Behind \"ragaszt\u00f3 k\u00f3d\": A vez\u00e9rl\u0151 esem\u00e9ny\u00e9t kezelj\u00fck le, \u00e9s a code-behindban (xaml.cs) ViewModel-ben h\u00edvjuk meg a megfelel\u0151 met\u00f3dust/commadot.

                    <controls:AdaptiveGridView x:Name=\"gridView\"\n                            ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                            IsItemClickEnabled=\"True\"\n                            ItemClick=\"GridView_ItemClick\">\n
                    private void GridView_ItemClick(object sender, ItemClickEventArgs e)\n{\n    ViewModel.RecipeSelectedCommand.Execute((RecipeHeader)e.ClickedItem);\n}\n
                  2. x:Bind esem\u00e9ny k\u00f6t\u00e9s: haszn\u00e1ljuk az x:Bind met\u00f3dus k\u00f6t\u00e9si lehet\u0151s\u00e9g\u00e9t, amelynek seg\u00edts\u00e9g\u00e9vel a vez\u00e9rl\u0151 esem\u00e9ny\u00e9t tudjuk k\u00f6tni a ViewModel-ben l\u00e9v\u0151 met\u00f3dusra. A met\u00f3dusnak viszont ilyenkor vagy param\u00e9ter n\u00e9lk\u00fclinek kell lennie, vagy olyan param\u00e9tereket kell fogadnia, amely az esem\u00e9ny szignat\u00far\u00e1j\u00e1ra illeszkedik.

                    View - MainPage.xaml
                    <controls:AdaptiveGridView x:Name=\"gridView\"\n                            ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                            IsItemClickEnabled=\"True\"\n                            ItemClick=\"{x:Bind ViewModel.RecipeSelected\">\n</controls:AdaptiveGridView>\n
                    ViewModel - MainPageViewModel
                    public void RecipeSelected(object sender, ItemClickEventArgs e)\n{\n   ...\n}\n

                    Ennek a m\u00f3dszernek a h\u00e1tr\u00e1nya, hogy a esem\u00e9ny param\u00e9tereivel a ViewModel-be a n\u00e9zet keretrendszer f\u00fcgg\u0151s\u00e9geit is beviszi (esem\u00e9nykezel\u0151 param\u00e9ter t\u00edpusok), pedig az alap gondolatunk az volt, hogy a ViewModel f\u00fcggetlen legyen a n\u00e9zett\u0151l. Term\u00e9szetesen ez a m\u00f3dszer is j\u00f3l tud m\u0171k\u00f6dni, ha r\u00e9szben feladjuk az MVVM minta szigor\u00fa betart\u00e1s\u00e1t.

                  3. A Behavior-\u00f6k seg\u00edts\u00e9g\u00e9vel, azon bel\u00fcl is az EventTriggerBehavior \u00e9s InvokeCommandAction oszt\u00e1lyokkal tudunk Commandot k\u00f6tni tetsz\u0151leges vez\u00e9rl\u0151 esem\u00e9nyre.

                    <controls:AdaptiveGridView x:Name=\"gridView\"\n                            ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                            IsItemClickEnabled=\"True\">\n    <i:Interaction.Behaviors>\n        <c:EventTriggerBehavior EventName=\"ItemClick\">\n            <c:InvokeCommandAction Command=\"{x:Bind ViewModel.RecipeSelectedCommand}\" \n                                   InputConverter=\"{StaticResource ItemClickedInputConverter}\" />\n        </c:EventTriggerBehavior>\n    </i:Interaction.Behaviors>\n

                    Ezzel szinte teljesen deklarat\u00edvv\u00e1 tudjuk tenni hagyni a n\u00e9zetet, de m\u00e9g \u00edgy is k\u00e9sz\u00edten\u00fcnk kell egy ItemClickedInputConverter oszt\u00e1lyt, amely az esem\u00e9ny param\u00e9tereit \u00e1talak\u00edtja a megfelel\u0151 t\u00edpusra az IValueConverter interf\u00e9sz seg\u00edts\u00e9g\u00e9vel.

                    public class ItemClickedInputConverter : IValueConverter\n{\n    public object Convert(object value, Type targetType, object parameter, string language)\n    {\n        return (RecipeHeader)((value as ItemClickEventArgs)?.ClickedItem);\n    }\n\n    public object ConvertBack(object value, Type targetType, object parameter, string language)\n    {\n        throw new NotImplementedException();\n    }\n}\n

                    A behavior-\u00f6k egy\u00e9bk\u00e9nt egy teljesen \u00e1ltal\u00e1nos mechanizmus a XAML vil\u00e1gban, amelyek seg\u00edts\u00e9g\u00e9vel a n\u00e9zetekhez tudunk \u00fajrafelhaszn\u00e1lhat\u00f3 viselked\u00e9st hozz\u00e1adni (b\u0151vebben itt).

                  "},{"location":"labor/5-mvvm/#24-recept-reszletes-nezet","title":"2.4 Recept r\u00e9szletes n\u00e9zet","text":"

                  A recept r\u00e9szletes adatainak megjelen\u00edt\u00e9s\u00e9hez egy Grid-et haszn\u00e1ljunk, amelynek k\u00e9t oszlopa van. Az els\u0151 oszlopban egy ScrollViewer-t helyezz\u00fcnk el, amelybe egy StackPanel ker\u00fcl. A StackPanel-ben helyezz\u00fcnk el egy FlipView-t, amelyben a recept k\u00e9peit fogjuk megjelen\u00edteni. A FlipView egy listak\u00e9nt m\u0171k\u00f6dik, de az elemeit egy lapozhat\u00f3 fel\u00fcleten jelen\u00edti meg.

                  A FlipView alatt lesz tal\u00e1lhat\u00f3 el egy ItemsControl (egyszer\u0171 lista, mely nem t\u00e1mogat g\u00f6rget\u00e9st, kiv\u00e1laszt\u00e1st, kattint\u00e1st stb.), amelyben a recept hozz\u00e1val\u00f3it fogjuk megjelen\u00edteni.

                  Ez al\u00e1 ker\u00fcl egy TextBlock, amelybe a recept elk\u00e9sz\u00edt\u00e9s\u00e9nek l\u00e9p\u00e9sei ker\u00fclnek.

                  A m\u00e1sodik oszlopba helyezz\u00fcnk el egy Grid-et, amelybe kommentek list\u00e1ja \u00e9s beviteli mez\u0151i fognak ker\u00fclni.

                  Az al\u00e1bbi k\u00f3dot a labor sor\u00e1n nyugodtan m\u00e1solhatjuk a RecipeDetailPage.xaml f\u00e1jlba, \u00fajdons\u00e1g ebben a k\u00f3dban nincs az eddigiekhez k\u00e9pest.

                  <?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<Page x:Class=\"MvvmLab.Views.RecipeDetailPage\"\n      xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n      xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n      xmlns:local=\"using:MvvmLab.Views\"\n      xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n      xmlns:models=\"using:MvvmLab.Core.Models\"\n      Background=\"{ThemeResource ApplicationPageBackgroundThemeBrush}\"\n      mc:Ignorable=\"d\">\n\n    <Grid x:Name=\"ContentArea\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n\n        <TextBlock Grid.Row=\"0\" Padding=\"10\"\n                   Style=\"{StaticResource TitleTextBlockStyle}\"\n                   Text=\"{x:Bind ViewModel.Recipe.Title, Mode=OneWay}\" />\n\n        <Grid Grid.Row=\"1\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"3*\" />\n                <ColumnDefinition Width=\"*\" />\n            </Grid.ColumnDefinitions>\n\n            <ScrollViewer Grid.Column=\"0\" Padding=\"20 10 0 20\">\n                <StackPanel Orientation=\"Vertical\">\n                    <StackPanel x:Name=\"images\"\n                                Margin=\"0,0,24,0\"\n                                Orientation=\"Vertical\">\n                        <TextBlock Margin=\"0,0,0,12\"\n                                   Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                                   Text=\"Images\" />\n                        <FlipView x:Name=\"flipView\"\n                                  MaxHeight=\"250\"\n                                  VerticalAlignment=\"Top\"\n                                  ItemsSource=\"{x:Bind ViewModel.Recipe.ExtraImages, Mode=OneWay}\">\n                            <FlipView.ItemTemplate>\n                                <DataTemplate>\n                                    <Image Source=\"{Binding}\" Stretch=\"Uniform\" />\n                                </DataTemplate>\n                            </FlipView.ItemTemplate>\n                        </FlipView>\n                    </StackPanel>\n\n                    <StackPanel x:Name=\"ingredients\"\n                                Margin=\"0,0,24,0\"\n                                Orientation=\"Vertical\">\n                        <TextBlock Margin=\"0,0,0,12\"\n                                   Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                                   Text=\"Ingredients\" />\n                        <ItemsControl HorizontalAlignment=\"Left\" ItemsSource=\"{x:Bind ViewModel.Recipe.Ingredients, Mode=OneWay}\">\n                            <ItemsControl.ItemTemplate>\n                                <DataTemplate>\n                                    <TextBlock Margin=\"0,0,0,10\"\n                                               Text=\"{Binding}\"\n                                               TextWrapping=\"Wrap\" />\n                                </DataTemplate>\n                            </ItemsControl.ItemTemplate>\n                        </ItemsControl>\n                    </StackPanel>\n\n                    <StackPanel x:Name=\"directions\"\n                                Margin=\"0,0,24,0\"\n                                Orientation=\"Vertical\"\n                                RelativePanel.RightOf=\"ingredients\">\n                        <TextBlock Margin=\"0,0,0,12\"\n                                   Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                                   Text=\"Directions\" />\n                        <TextBlock HorizontalAlignment=\"Left\"\n                                   Text=\"{x:Bind ViewModel.Recipe.Directions, Mode=OneWay}\"\n                                   TextWrapping=\"Wrap\" />\n                    </StackPanel>\n                </StackPanel>\n            </ScrollViewer>\n\n            <Grid Grid.Column=\"1\" RowSpacing=\"12\">\n                <Grid.RowDefinitions>\n                    <RowDefinition Height=\"Auto\" />\n                    <RowDefinition Height=\"*\" />\n                    <RowDefinition Height=\"Auto\" />\n                </Grid.RowDefinitions>\n\n                <TextBlock Grid.Row=\"0\"\n                           Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                           Text=\"Comments\" />\n\n                <ListView Grid.Row=\"1\" ItemsSource=\"{x:Bind ViewModel.Recipe.Comments, Mode=OneWay}\">\n                    <ListView.ItemTemplate>\n                        <DataTemplate x:DataType=\"models:Comment\">\n                            <StackPanel Orientation=\"Vertical\" Padding=\"0 5 0 5\">\n                                <TextBlock FontWeight=\"Bold\" Text=\"{x:Bind Name}\" />\n                                <TextBlock Text=\"{x:Bind Text}\" />\n                            </StackPanel>\n                        </DataTemplate>\n                    </ListView.ItemTemplate>\n                </ListView>\n\n                <StackPanel x:Name=\"comments\"\n                            Grid.Row=\"2\"\n                            Margin=\"24,0,24,0\"\n                            Orientation=\"Vertical\">\n                    <!-- TODO input fields for comments -->\n                </StackPanel>\n            </Grid>\n        </Grid>\n    </Grid>\n</Page>\n

                  Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st!

                  "},{"location":"labor/5-mvvm/#3-feladat-kommentek-hozzaadasa","title":"3. Feladat - Kommentek hozz\u00e1ad\u00e1sa","text":"

                  Ha j\u00f3l \u00e1llunk id\u0151vel, k\u00e9sz\u00edts\u00fcnk funkci\u00f3t a kommentek hozz\u00e1ad\u00e1s\u00e1hoz a recept r\u00e9szletes oldal\u00e1n.

                  "},{"location":"labor/5-mvvm/#webszolgaltatas","title":"Webszolg\u00e1ltat\u00e1s","text":"

                  Az IRecipeService interf\u00e9szt \u00e9s implement\u00e1ci\u00f3t eg\u00e9sz\u00edts\u00fck ki egy SendCommentAsync met\u00f3dussal, mely egy kommentet k\u00fcld a szervernek a POST /Recipes/{recipeId}/Comments v\u00e9gpontra.

                  IRecipeService
                  public Task SendCommentAsync(int recipeId, Comment comment);\n
                  RecipeService
                  public async Task SendCommentAsync(int recipeId, Comment comment)\n{\n    using var client = new HttpClient();\n    await client.PostAsJsonAsync($\"{_baseUrl}/Recipes/{recipeId}/Comments\", comment);\n}\n
                  "},{"location":"labor/5-mvvm/#viewmodel","title":"ViewModel","text":"

                  A RecipeDetailPageViewModel-ben hozzunk l\u00e9tre egy NewCommentText nev\u0171 string t\u00edpus\u00fa tulajdons\u00e1got \u00e9s egy NewCommentName string tulajdons\u00e1got, melyekben t\u00e1rolni fogjuk a felhaszn\u00e1l\u00f3 \u00e1ltal megadott komment adatait. Haszn\u00e1ljuk az ObservableProperty attrib\u00fatumot!

                  [ObservableProperty]\nprivate string _newCommentName = string.Empty;\n\n[ObservableProperty]\nprivate string _newCommentText = string.Empty;\n

                  A RecipeDetailPageViewModel-ben hozzunk l\u00e9tre egy SendComment nev\u0171 f\u00fcggv\u00e9nyt, amelyen kereszt\u00fcl a felhaszn\u00e1l\u00f3 \u00e1ltal megadott kommentet tudjuk elk\u00fcldeni a szervernek. A f\u00fcggv\u00e9nyb\u0151l gener\u00e1ltassunk egy Commandot az MVVM Toolkit seg\u00edts\u00e9g\u00e9vel ([RelayCommand]).

                  Az implement\u00e1ci\u00f3 egyszer\u0171: elk\u00fcldj\u00fck a kommentet a szervernek, majd friss\u00edtj\u00fck a receptet.

                  [RelayCommand]\nprivate async Task SendComment()\n{\n    await _recipeService.SendCommentAsync(Recipe.Id, new Comment\n    {\n        Name = NewCommentName,\n        Text = NewCommentText\n    });\n\n    NewCommentName = string.Empty;\n    NewCommentText = string.Empty;\n\n    Recipe = await _recipeService.GetRecipeAsync(Recipe.Id);\n}\n

                  A n\u00e9zeten a k\u00f6vetkez\u0151 elemeket helyezz\u00fck el a kommentek hozz\u00e1ad\u00e1s\u00e1hoz:

                  <StackPanel x:Name=\"comments\"\n            Grid.Row=\"2\"\n            Margin=\"24,0,24,0\"\n            Orientation=\"Vertical\">\n    <TextBox Margin=\"0,0,0,16\"\n             Header=\"Name\"\n             Text=\"{x:Bind ViewModel.NewCommentName, Mode=TwoWay}\" />\n    <TextBox Margin=\"0,0,0,16\"\n             Header=\"Comment\"\n             Text=\"{x:Bind ViewModel.NewCommentText, Mode=TwoWay}\" />\n    <Button Margin=\"0,0,0,16\"\n            HorizontalAlignment=\"Right\"\n            Command=\"{x:Bind ViewModel.SendCommentCommand}\"\n            Content=\"Send\" />\n</StackPanel>\n

                  Vegy\u00fck \u00e9szre, hogy a TextBox-ok Text property-j\u00e9t k\u00e9tir\u00e1ny\u00fa k\u00f6t\u00e9ssel k\u00f6t\u00f6tt\u00fck a ViewModel-ben l\u00e9v\u0151 NewCommentName \u00e9s NewCommentText tulajdons\u00e1gokhoz, \u00e9s a gomb Command-j\u00e1t is a ViewModel-ben l\u00e9v\u0151 SendCommentCommand tulajdons\u00e1ghoz k\u00f6t\u00f6tt\u00fck.

                  "},{"location":"labor/5-mvvm/#kitekintes-commandok-vegrehajtasanak-feltetelei","title":"Kitekint\u00e9s: Commandok v\u00e9grehajt\u00e1s\u00e1nak felt\u00e9telei","text":"

                  A SendCommentCommand Command v\u00e9grehajt\u00e1s\u00e1nak felt\u00e9tele, hogy a NewCommentName \u00e9s a NewCommentText tulajdons\u00e1gok ne legyenek \u00fcresek. A Commandok lehet\u0151s\u00e9get adnak arra, hogy a v\u00e9grehajt\u00e1sukat felt\u00e9telekhez k\u00f6ss\u00fck, amelyeket a CanExecute met\u00f3dusban tudunk megadni. Eset\u00fcnkben egy bool-lal visszat\u00e9r\u0151 met\u00f3dus/property nevet kell megadnunk a Command gener\u00e1tor attrib\u00fatumnak.

                  RecipeDetailPageViewModel-ben:
                  private bool CanExecuteSendComment => !string.IsNullOrEmpty(NewCommentName) && !string.IsNullOrEmpty(NewCommentText);\n\n[RelayCommand(CanExecute = nameof(CanExecuteSendComment))]\nprivate async Task SendComment()\n

                  Pr\u00f3b\u00e1ljuk ki. Azt tapasztaljuk, hogy a gomb nem lesz enged\u00e9lyezve, viszont a TextBox-ok m\u00f3dos\u00edt\u00e1sa ut\u00e1n sem v\u00e1ltozik a gomb \u00e1llapota.

                  A CanExecute met\u00f3dus akkor h\u00edv\u00f3dik meg (akkor h\u00edvj\u00e1k a vez\u00e9rl\u0151k), amikor a Command els\u00fcti a CanExecuteChanged esem\u00e9nyt. Eset\u00fcnkben ezt az esem\u00e9nyt a NewCommentName \u00e9s a NewCommentText tulajdons\u00e1gok PropertyChanged esem\u00e9ny\u00e9nek kiv\u00e1lt\u00e1sakor kell kiv\u00e1ltani. Erre az MVVM Toolkit egy k\u00fcl\u00f6n attrib\u00fatumot biztos\u00edt ([NotifyCanExecuteChangedFor]), amelyet a NewCommentName \u00e9s a NewCommentText tulajdons\u00e1gokra kell r\u00e1rakni.

                  Teh\u00e1t, ha a NewCommentName vagy a NewCommentText tulajdons\u00e1g \u00e9rt\u00e9ke megv\u00e1ltozik, akkor a SendCommentCommand Command CanExecuteChanged esem\u00e9ny\u00e9t is kiv\u00e1ltjuk, ami miatt a CanExecute met\u00f3dus \u00fajra lefut, \u00e9s a gomb \u00e1llapota is friss\u00fcl.

                  [ObservableProperty]\n[NotifyCanExecuteChangedFor(nameof(SendCommentCommand))]\nprivate string _newCommentName = string.Empty;\n\n[ObservableProperty]\n[NotifyCanExecuteChangedFor(nameof(SendCommentCommand))]\nprivate string _newCommentText = string.Empty;\n

                  Pr\u00f3b\u00e1ljuk ki.

                  M\u00e1r csak egy dolog van h\u00e1tra: jelenleg a TextBox \u00e1llapota csak akkor v\u00e1ltozik, ha a felhaszn\u00e1l\u00f3 elhagyja a TextBox-ot. Ezt a viselked\u00e9st az adatk\u00f6t\u00e9s UpdateSourceTrigger tulajdons\u00e1g\u00e1n kereszt\u00fcl tudjuk m\u00f3dos\u00edtani.

                  Text=\"{x:Bind ViewModel.NewCommentName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n\nText=\"{x:Bind ViewModel.NewCommentText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n

                  Pr\u00f3b\u00e1ljuk ki.

                  "},{"location":"labor/5-mvvm/index_ger/","title":"5. MVVM","text":""},{"location":"labor/5-mvvm/index_ger/#das-ziel-der-ubung","title":"Das Ziel der \u00dcbung","text":"

                  In dieser \u00dcbung werden wir eine Rezept-Browser-Anwendung unter Verwendung des MVVM-Entwurfsmusters erstellen.

                  "},{"location":"labor/5-mvvm/index_ger/#voraussetzungen","title":"Voraussetzungen","text":"

                  Die f\u00fcr die Durchf\u00fchrung des Labors ben\u00f6tigten Werkzeuge:

                  • Betriebssystem Windows 10 oder Windows 11 (Linux und macOS nicht geeignet)
                  • Visual Studio 2022
                    • Windows Desktop Development Workload
                  "},{"location":"labor/5-mvvm/index_ger/#ursprungliches-projekt","title":"Urspr\u00fcngliches Projekt","text":"

                  Klonen Sie das urspr\u00fcngliche Projekt mit dem folgenden Befehl:

                  git clone https://github.com/bmeviauab00/lab-mvvm-kiindulo\n
                  Laden Sie die fertige L\u00f6sung herunter

                  Es ist wichtig, dass Sie sich w\u00e4hrend des Praktikums an die Anleitung halten. Es ist verboten (und sinnlos), die fertige L\u00f6sung herunterzuladen. Allerdings kann es bei der anschlie\u00dfenden Selbstein\u00fcbung n\u00fctzlich sein, die fertige L\u00f6sung zu \u00fcberpr\u00fcfen, daher stellen wir sie zur Verf\u00fcgung.

                  Die L\u00f6sung ist [auf GitHub] (https://github.com/bmeviauab00/lab-mvvm-kiindulo) im solve-Zweig verf\u00fcgbar. Der einfachste Weg, es herunterzuladen, ist, den git clone-Zweig von der Kommandozeile aus zu klonen:

                  git clone https://github.com/bmeviauab00/lab-mvvm-kiindulo -b solved

                  "},{"location":"labor/5-mvvm/index_ger/#uber-das-mvvm-muster","title":"\u00dcber das MVVM-Muster","text":"

                  Das MVVM (Model-View-ViewModel) ist ein Architekturentwurfsmuster, das bei der Entwicklung von XAML-Anwendungen eingesetzt werden kann, aber auch h\u00e4ufig in anderen clientseitigen Technologien verwendet wird. Das MVVM-Muster wurde entwickelt, um die Benutzeroberfl\u00e4che und die zugrunde liegende Logik zu entkoppeln und so eine lose gekoppelte Anwendung zu schaffen, die die Testbarkeit, Wartbarkeit und Wiederverwendbarkeit erh\u00f6ht.

                  Das MVVM-Muster besteht aus drei (+1) Hauptteilen:

                  • Modell: Enth\u00e4lt das Gesch\u00e4ftsmodell/Dom\u00e4nenmodell der Anwendung, das ViewModels zum Speichern von Daten verwenden k\u00f6nnen.
                  • View (Ansicht): Sie enth\u00e4lt eine Beschreibung der Benutzeroberfl\u00e4che und der rein auf die Ansichten bezogenen Logik (z.B.: Behandlung von Animationen).
                  • ViewModel (Ansichtsmodell): Eine Abstraktion der Ansicht, die den Zustand der Ansicht und die Operationen enth\u00e4lt, die mit der Ansicht durchgef\u00fchrt werden k\u00f6nnen, unabh\u00e4ngig von der Ansicht. Die lose Kopplung zwischen dem ViewModel und dem View wird durch die Datenverbindung gew\u00e4hrleistet.
                  • Services (Dienstleistungen): Klassen, die die Gesch\u00e4ftslogik der Anwendung enthalten und von ViewModels verwendet werden. W\u00e4re die gesamte Gesch\u00e4ftslogik in ViewModels enthalten, w\u00e4ren diese zu komplex und undurchsichtig. Dies ist nicht Teil des MVVM-Musters, aber wir erw\u00e4hnen es hier, weil wir die Anwendungsarchitektur auf diese Weise nutzen werden.

                  Neu:

                  • Modell: Erfasst dom\u00e4nenspezifische Daten, die ViewModels zum Speichern von Daten verwenden k\u00f6nnen. Z.B. die Klasse Recipe/Product/Order, die die Daten eines Rezepts/Produkts/Bestellung zusammenfasst.
                  • View (Ansicht): Sie enth\u00e4lt eine Beschreibung der Benutzeroberfl\u00e4che (und der rein auf die Ansichten bezogenen Logik, z. B. die Behandlung von Animationen). Typischerweise eine von Window, Page, UserControl abgeleitete Klasse, mit einer deklarativen Beschreibung in XAML, der Code-Behind ist oft leer (weil die Logik im ViewModel ist).
                  • ViewModel (Ansichtsmodell): Sie enth\u00e4lt die Logik f\u00fcr die Ansicht: Sie enth\u00e4lt den Zustand der Ansicht und die Operationen, die mit der Ansicht durchgef\u00fchrt werden k\u00f6nnen. Sie ist unabh\u00e4ngig von der Ansicht, wird eine lose Kopplung zwischen dem ViewModel und der Ansicht durch Datenverbindung erreicht (die Steuerelemente der Ansicht binden an die Eigenschaften des ViewModels). Einheitlich testbar (unit test)!
                  • Services (Dienstleistungen): Klassen, die die Gesch\u00e4fts-/Anwendungslogik enthalten und von ViewModels verwendet werden. W\u00e4re die gesamte Gesch\u00e4ftslogik in ViewModels enthalten, w\u00e4ren diese zu komplex und undurchsichtig. Dies ist nicht Teil des MVVM-Musters, aber wir erw\u00e4hnen es hier, weil wir auf diese Weise die Architektur der Anwendung aufbauen werden.

                  Wozu erstellen wir ViewModel-Klassen?

                  • Wir erstellen immer eine ViewModel-Klasse f\u00fcr jede Ansicht (z.B. Window, Page, Dialog, UserControl) und erzeugen daraus je ein Objekt f\u00fcr jede Ansicht. Z.B. MainPage f\u00fcr MainPageViewModel, DancerDialog f\u00fcr DancerDialogViewModel. Wir wenden dies in der Praxis an.
                  • F\u00fcr jede Modellklasse (z.B. Recipe, Product, Dancer usw.) k\u00f6nnen Sie optional Wrapper-ViewModel-Klassen erstellen (z.B. RecipeViewModel, ProductViewModel, DancerViewModel), aber wir werden sie in dieser \u00dcbung nicht erstellen. Das liegt daran, dass wir nicht dem Strict MVVM-Muster folgen, sondern dem Relaxed MVVM-Muster (siehe Vorlesung).
                  "},{"location":"labor/5-mvvm/index_ger/#aufgabe-0-projektstruktur","title":"Aufgabe 0. - Projektstruktur","text":"

                  Der Anwendungsrahmen ist bereits vorbereitet. Schauen wir uns die Projektstruktur an.

                  MvvmLab ist ein Projekt f\u00fcr eine ausf\u00fchrbare Anwendung, die das WinUI-Framework in seiner Anzeigeschicht mit der bereits erlernten XAML-Sprache verwendet. Das Projekt MvvmLab.Core (Klassenbibliothek) enth\u00e4lt die vollst\u00e4ndig ansichtsunabh\u00e4ngige Gesch\u00e4ftslogik.

                  Was ist f\u00fcr uns in der Anfangsphase des Projekts wichtig?

                  • App.xaml.cs: Ein Anwendungseintrittspunkt, der die in modernen .NET-Anwendungen verwendeten Muster Host Builder und Dependency Injection verwendet. Dies ist nicht das Thema dieses Semesters, aber die Injektion von Abh\u00e4ngigkeit wird im Labor behandelt werden.
                  • Views-Ordner: Enth\u00e4lt Ansichten der Anwendung, derzeit MainPage
                  • ViewModels-Ordner: Enth\u00e4lt die ViewModels der Anwendung, derzeit MainPageViewModel
                  • INagivationService ( im Ordner Services ): Dienst f\u00fcr die Navigation zwischen Seiten

                  MVVM und Boilerplate-Bibliotheken

                  MVVM-Muster wird selten allein auf der Grundlage des .NET-Frameworks implementiert. Es lohnt sich, einige MVVM-Bibliotheken zu verwenden, die Ihren Code kompakter und \u00fcbersichtlicher machen und weniger Boilerplate-Code enthalten. Die am h\u00e4ufigsten verwendeten Bibliotheken sind:

                  • MVVM Toolkit: MVVM-Bibliothek, die von Microsoft gepflegt wird
                  • [Prism] (https://prismlibrary.com/): Fr\u00fcher wurde es von Microsoft gewartet und war sehr weit verbreitet, aber jetzt wird es von externen Entwicklern gewartet und ist mit der Zeit kostenpflichtig geworden.
                  • ReactiveUI: Es verwendet die Reactive Extensions (Rx)-Bibliotheken, um den Zustand des ViewModels zu verwalten und Daten zwischen der Ansicht und dem ViewModel zu binden. Diese Bibliothek bietet die meisten Dienste, ist aber auch am schwierigsten zu erlernen.
                  • Uno.Extensions: Es basiert auf dem MVVM-Toolkit, enth\u00e4lt aber auch mehrere Funktionen, die M\u00e4ngel im WinUI-Framework ausf\u00fcllen.

                  W\u00e4hrend des Praktikums werden wir das MVVM-Toolkit von Microsoft verwenden.

                  Das urspr\u00fcngliche Projekt wurde mit dem Visual Studio Add-on Windows Template Studio erstellt.

                  "},{"location":"labor/5-mvvm/index_ger/#aufgabe-1-rezepte-hauptseite","title":"Aufgabe 1. - Rezepte Hauptseite","text":"

                  Die L\u00f6sung werden wir \"von unten\" aufbauen, von den Daten ausgehend werden wir schrittweise zur Ansicht. Die Entwicklung von oben nach unten ist zwar in der Praxis oft n\u00fctzlicher, aber aufgrund der zeitlichen Beschr\u00e4nkungen im Labor ist die Entwicklung von unten nach oben schneller und einfacher, weil man die Daten so nicht mocken muss. Die folgende Abbildung gibt einen \u00dcberblick \u00fcber die wichtigsten Klassen, die mit der Hauptseite verbunden sind.

                  MMVM-basierte Implementierung der Homepage

                  Schl\u00fcsselelemente:

                  • MainPage: Diese Ansicht, ein Nachkomme der Seite, ist eine XAML-basierte Beschreibung der Benutzeroberfl\u00e4che.
                  • MainPageViewModel: das ViewModel f\u00fcr die Hauptseite (MainPage). Es enth\u00e4lt Rezeptgruppen in einer (generierten) RecipeGroups Eigenschaft, und Rezepte in den Rezeptgruppen. Diese Ansicht zeigt die Kopfzeile der Rezeptgruppen sowie die Kopfzeile und die Bilder der Rezepte in den Gruppen mit Datenverbindung.
                  • RecipeGroup und Recipe: Modellklassen f\u00fcr Rezeptgruppen und Rezepte.
                  • RecipeService: Anwendungslogik/Datenzugriff zur Verwaltung von Rezepten (kommuniziert mit einem entfernten Dienst) unter Verwendung von ViewModel.
                  "},{"location":"labor/5-mvvm/index_ger/#11-datenzugangsdienst","title":"1.1 Datenzugangsdienst","text":"

                  Beginnen wir mit der Datenzugriffsschicht, die nun als Modellschicht im MVVM-Muster betrachtet werden kann.

                  Unsere Anwendung ruft Daten von einem Webserver ab (\u00fcber die sogenannte REST-API, HTTP). Client-Server-Architekturen wie diese sind eine sehr verbreitete L\u00f6sung in der modernen Anwendungsentwicklung. Dies wird im n\u00e4chsten Semester in Mobile und Web Software, und Data Driven Applications ausf\u00fchrlicher behandelt. F\u00fcr den Moment gen\u00fcgt es zu wissen, dass unsere Client-Anwendung HTTP-Anfragen an den Server sendet, der mit der R\u00fcckgabe von Daten im JSON-Format antwortet.

                  Client-Server-Architektur

                  Der Ferndienst ist verf\u00fcgbar unter: https://bmecookbook2.azurewebsites.net/api. Der Dienst umfasst eine OpenApi-basierte Dokumentation \u00fcber die https://bmecookbook2.azurewebsites.net/swagger am. Schauen wir uns dies an oder probieren wir die Endpunkte auch \u00fcber die Oberfl\u00e4che von Swagger aus (indem man die URL mit der Endung \"swagger\" in die Adresszeile eines Browsers eingibt). F\u00fcr die erste Aufgabe werden wir den Endpunkt /api/Recipes/Groups verwenden, der die Gruppierung von Rezepten zur\u00fcckgibt.

                  F\u00fcgen wir eine neue Klasse namens RecipeGroup in den Ordner Models des Projekts MvvmLab.Core ein.

                  Rufen wir mit Swagger den Endpunkt \"api/Recipes/Groups\" auf (genauer gesagt, senden wir eine HTTP-GET-Anfrage)

                  • \u00d6ffnen wir in der Swagger-Oberfl\u00e4che die Beschreibung des Endpunktes \"Get api/Recipes/Groups\".
                  • Klicken wir auf die Taste Execute.
                  • Die vom Dienst gesendete JSON-Antwort wird unter \"Response body\" angezeigt: Hier sehen wir, dass die Antwort Rezeptgruppen enth\u00e4lt. Jede Gruppe hat einen \"Titel\" (z.B. Chinese, Mexican, Italian), und unter jeder Gruppe finden wir zwischen [] (JSON-Array) die Daten der Rezepte in der Gruppe.
                  • Kopieren wir die JSON-Daten von RecipeGroup in die Zwischenablage. Wir k\u00f6nnen auch die Ausgabe unter \"Example Value\" verwenden, wenn wir sie in die Zwischenablage kopieren (kopieren wir jedoch nicht die \u00f6ffnenden [ und schlie\u00dfenden ] Schriftzeichen). Wenn wir aus irgendeinem Grund nicht weiterkommen k\u00f6nnen, k\u00f6nnen wir den Inhalt auch aus das folgende Dropdown-Men\u00fc in die Zwischenablage kopieren:

                    In die Zwischenablage zu kopieren
                    {\n    \"Title\": \"string\",\n    \"Recipes\": [\n        {\n            \"Id\": 0,\n            \"Title\": \"string\",\n            \"BackgroundImage\": \"string\"\n        }\n    ]\n}\n

                  In Visual Studio w\u00e4hlen wir im Men\u00fc Edit / Paste Special / Paste JSON as Classes aus, um den Inhalt der Zwischenablage einf\u00fcgen. VS generiert dann Klassen, die der Struktur des von uns eingef\u00fcgten JSON entsprechen.

                  Die entstehenden Klassen k\u00f6nnen umbenannt werden, um den C#-Codierungskonventionen zu entsprechen. Benennen wir die Klasse Rootobject in RecipeGroup und die Klasse Recipe in RecipeHeader um.

                  public class RecipeGroup\n{\n    public string Title { get; set; }\n    public RecipeHeader[] Recipes { get; set; }\n}\n\npublic class RecipeHeader\n{\n    public int Id { get; set; }\n    public string Title { get; set; }\n    public string BackgroundImage { get; set; }\n}\n

                  Verwenden von List<T>

                  In unserem Fall war es nicht notwendig (weil wir die Rezeptgruppen nicht erweitern), aber wenn es bequemer f\u00fcr uns ist, k\u00f6nnen wir die Bl\u00f6cke in den generierten Code im List<T> umwandeln.

                  Erstellen wir eine Schnittstelle IRecipeService zum Namespace MvvmLab.Core.Services, \u00fcber die auf den Remote-Dienst zugegriffen werden soll. In der Schnittstelle erstellen wir eine Methode GetRecipeGroupsAsync, die die Rezeptgruppen abfragt und zur\u00fcckgibt.

                  public interface IRecipeService\n{\n    public Task<RecipeGroup[]> GetRecipeGroupsAsync();\n}\n

                  Task-R\u00fcckgabewert

                  In der Schnittstelle ist der eigentliche R\u00fcckgabewert (RecipeGroup[]) in ein Objekt Task<T> verpackt, da es vorzuziehen ist, Netzwerkoperationen asynchron zu implementieren. In .NET ist die modernste und einfachste Art, Asynchronit\u00e4t zu implementieren, die Verwendung von Tasks. Und die Asynchronit\u00e4t sorgt daf\u00fcr, dass die Benutzeroberfl\u00e4che nicht einfriert, wenn die Netzwerkanforderung lange dauert (und das alles, ohne separate Threads zu starten).

                  Die Implementierung der Schnittstelle wird im Namespace MvvmLab.Core.Services unter RecipeService erstellt. Unser Dienst wird die integrierte .NET-Klasse HttpClient f\u00fcr REST-API-Aufrufe verwenden. GetFromJsonAsync stellt eine asynchrone HTTP GET-Anfrage an die angegebene Adresse und deserialisiert die Antwort von JSON in den angegebenen Typ.

                  public class RecipeService : IRecipeService\n{\n    private readonly string _baseUrl = \"https://bmecookbook2.azurewebsites.net/api\";\n\n    public async Task<RecipeGroup[]> GetRecipeGroupsAsync()\n    {\n        using var client = new HttpClient();\n        return await client.GetFromJsonAsync<RecipeGroup[]>($\"{_baseUrl}/Recipes/Groups\");\n    }\n}\n

                  Die Operation GetFromJsonAsync ist asynchron, sie gibt also Task zur\u00fcck, wir k\u00f6nnen dies nicht blockierend erwarten und mit dem Schl\u00fcsselwort await auf das Ergebnis zugreifen.

                  async-await

                  Die Schl\u00fcsselw\u00f6rter async und await werden in den meisten modernen Sprachen verwendet, um asynchrone Funktionsaufrufe auf Sprachebene zu behandeln. Wir werden am Ende des Semesters mehr dar\u00fcber sprechen, wie es funktioniert, aber bis dahin m\u00fcssen Sie Folgendes wissen, um es zu nutzen:

                  • Mit dem Schl\u00fcsselwort await k\u00f6nnen wir auf eine asynchrone Ausf\u00fchrung warten, ohne den Aufrufer zu blockieren.
                  • Das Schl\u00fcsselwort await kann nur in Funktionen mit dem Schl\u00fcsselwort async verwendet werden.
                  • async-Funktionen k\u00f6nnen nur den R\u00fcckgabewert Task oder Task<T> oder void haben. (Oder \"Task-\u00e4hnlich\", aber das nehmen wir hier nicht.)
                    • Wenn man eine async-Funktion von au\u00dfen abwarten will, kann man das nicht mit void tun, sondern man muss einen R\u00fcckgabewert von Task oder Task<T> haben.
                    • In async-Funktionen wird die Syntax der return-Anweisung ge\u00e4ndert: es muss nicht das Task-Objekt zur\u00fcckgegeben werden, sondern die darin enthaltenen Daten (void f\u00fcr Task, Task<T> f\u00fcr T).
                  "},{"location":"labor/5-mvvm/index_ger/#12-startseite-viewmodel","title":"1.2 Startseite ViewModel","text":"

                  Im n\u00e4chsten Schritt erstellen wir das ViewModel der Hauptseite, das den soeben erstellten Dienst verwendet, um die Rezeptgruppen abzurufen und sie als Status f\u00fcr die Ansicht zu speichern.

                  "},{"location":"labor/5-mvvm/index_ger/#dependency-injection","title":"Dependency Injection\u00b6","text":"

                  \u00d6ffnen wir die Klasse MainPageViewModel aus dem Ordner MvvmLab.ViewModels. Unser ViewModel ben\u00f6tigt eine Klasse, die die Schnittstelle IRecipeService implementiert, \u00fcber die es die Rezeptgruppen abfragen kann. Im MainPageViewModel Konstruktor erhalten wir die erforderliche Abh\u00e4ngigkeit \u00fcber Dependency Injection. In unserem Fall bedeutet dies, dass wir einen Parameter vom Typ IRecipeService erwarten, der vom ViewModel empfangen wird, wenn es instanziiert wird, und der Parameter wird in einer privaten Variablen gespeichert.

                  private readonly IRecipeService _recipeService;\n\npublic MainPageViewModel(IRecipeService recipeService)\n{\n    _recipeService = recipeService;\n}\n
                  Dependency Injection - DI

                  Standardm\u00e4\u00dfig sind Klassen eng mit ihren Abh\u00e4ngigkeiten gekoppelt (Referenz, Instanziierung).

                  Starke Kopplung ohne DI

                  Diese enge Kopplung erschwert die Pr\u00fcfung, Wartung und Wiederverwendung. Dies wird durch den Einsatz von Dependency Injection (und Strategy) unterst\u00fctzt. In diesem Kurs lernen wir das Dependency Injection (DI) Entwurfmuster kennen, das immer in Verbindung mit dem Strategy-Muster verwendet wird. Die Idee ist, dass eine Klasse ihre Abh\u00e4ngigkeiten (die Klassen, von denen sie abh\u00e4ngt und die sie verwendet) nicht selbst erzeugt, sondern sie von au\u00dfen erh\u00e4lt, z.B. in einem Konstruktorparameter. Das Strategy-Muster impliziert, dass sie nur als \"Schnittstelle\" von ihnen abh\u00e4ngt.

                  Die meisten Plattformen bieten heute auch einen zus\u00e4tzlichen Dienst, einen so genannten DI-Container (auch IoC-Container genannt), zur bequemen Verwaltung von Abh\u00e4ngigkeiten. Der Lebenszyklus von Abh\u00e4ngigkeiten wird dann von einer speziellen Komponente, dem DI-Container, verwaltet. Der DI-Container (dargestellt als Builder) ist f\u00fcr die Instanziierung von Klassen und die rekursive Injektion von Abh\u00e4ngigkeiten zust\u00e4ndig.

                  DI-Klassendiagramm

                  Um die entsprechenden Implementierungen zu injektieren, w\u00e4hrend des Durchlaufens der Abh\u00e4ngigkeitsgraph w\u00e4hrend der Instanziierung, m\u00fcssen die Abh\u00e4ngigkeitszuordnungen im DI-Container registriert werden. In unserer Anwendung tun wir dies in der Datei App.xaml.cs in der Methode ConfigureServices. F\u00fcgen wir die folgende Zeile hinzu, z.B. unter dem Abschnitt // Core Services:

                  services.AddTransient<IRecipeService, RecipeService>();\n

                  Dies sagt uns, dass das DI-Framework eine RecipeService-Implementierung injektiert, wenn eine Klasse eine IRecipeService-Abh\u00e4ngigkeit erwartet (z.B. den Konstruktor von MainPageViewModel). (Da wir sie hier als Transient Lifetime registriert haben, wird jede IRecipeService-Abh\u00e4ngigkeitsanforderung durch eine neue RecipeService-Instanz erf\u00fcllt).

                  Damit Dependency Injection in unserer Anwendung funktioniert, muss die Klasse MainPageViewModel auch im Container registriert sein, der ebenfalls unter ConfigureServices zu finden ist.

                  \u00dcber DI-Container im Detail

                  Die Verwendung und Funktionsweise von DI-Containern wird sp\u00e4ter im Kurs Datengesteuerte Systeme ausf\u00fchrlich behandelt.

                  "},{"location":"labor/5-mvvm/index_ger/#viewmodel-status","title":"ViewModel-Status","text":"

                  Im n\u00e4chsten Schritt werden wir das Hochladen des ViewModel-Status implementieren.

                  Unser Ziel ist, dass

                  • MainPageViewModel hat eine Eigenschaft namens RecipeGroups, die Rezeptgruppen enth\u00e4lt (wir wollen diese an die Oberfl\u00e4che binden),
                  • die \u00c4nderungen von RecipeGroups von der Schnittstelle verfolgt werden, was die Implementierung von INotifyPropertyChanged und das korrekte Ausl\u00f6sen von PropertyChanged erfordert (wie wir bereits in der vorherigen \u00dcbung/Hausaufgabe gesehen haben).

                  Dies w\u00fcrde relativ \"viel\" Arbeit erfordern, aber das MVVM-Toolkit vereinfacht unser Leben, denn wir m\u00fcssen nur das Folgendes tun:

                  • Erstellen wir in MainPageViewModel eine RecipeGroup[] Member-Variable (keine Eigenschaft) mit dem Namen _recipeGroups.
                  • Die Variable wird mit dem Attribut ObservableProperty versehen.
                  [ObservableProperty]\nprivate RecipeGroup[] _recipeGroups = Array.Empty<RecipeGroup>();\n

                  Hier sind wir nun. Aber was passiert dann?

                  • Auf dieser Grundlage erzeugt MVVM Toolkit automatisch eine Eigenschaft namens RecipeGroups in der generierten (partiellen) H\u00e4lfte der Klasse.
                  • Diese generierte Eigenschaft nutzt die Vorteile der Schnittstelle INotifyPropertyChanged. Wenn sich der Wert der Eigenschaft RecipeGroups \u00e4ndert, wird das Ereignis PropertyChanged ausgel\u00f6st, um die Ansicht entlang der Datenverbindungen zu benachrichtigen.
                  • Unser MainPageViewModel implementiert bereits die Schnittstelle INotifyPropertyChanged, da es von der Klasse ObservableObject des MVVM-Toolkits stammt.

                  In MainPageViewModel implementieren wir die vorbereitete Schnittstelle INavigationAware, die es uns erm\u00f6glicht, das Navigations-Lebenszyklus-Ereignis zwischen Ansichten zu handhaben und sogar Daten zwischen ViewModels zu \u00fcbergeben. In der Methode OnNavigatedTo werden die Rezeptgruppen \u00fcber IRecipeService abgefragt und in der Variablen RecipeGroups gespeichert.

                  public partial class MainPageViewModel : ObservableObject, INavigationAware\n{\n    // ...\n\n    public async void OnNavigatedTo(object parameter)\n    {\n        RecipeGroups = await _recipeService.GetRecipeGroupsAsync();\n    }\n\n    public void OnNavigatedFrom()\n    {\n    }\n}\n
                  "},{"location":"labor/5-mvvm/index_ger/#13-ansicht-der-hauptseite","title":"1.3 Ansicht der Hauptseite","text":"

                  Erstellen wir die Ansicht auf MainPage, in der die Rezeptgruppen angezeigt werden.

                  Damit GridView die Gruppierung behandeln kann, brauchen wir eine Liste, die die Gruppierung vornimmt. Wir k\u00f6nnen dies mit der Klasse CollectionViewSource tun, die in gewisser Weise UI-spezifische Wrapping-Aufgaben f\u00fcr Sammlungen \u00fcbernimmt. CollectionViewSource muss die zu gruppierenden Elemente und die Eigenschaft, auf der die Gruppen basieren, angegeben werden. Wir m\u00fcssen auch die Eigenschaft angeben, auf der die Elemente innerhalb der Gruppen angezeigt werden sollen.

                  Erstellen wir die Instanz CollectionViewSource in den Ressourcen der Seite (f\u00fcgen wir den Code unten in MainPage.xaml ein, oberhalb des Grids, auf der gleichen Ebene wo es liegt).

                  <Page.Resources>\n    <CollectionViewSource x:Name=\"RecipeGroupsCollectionSource\"\n                            IsSourceGrouped=\"True\"\n                            ItemsPath=\"Recipes\"\n                            Source=\"{x:Bind ViewModel.RecipeGroups, Mode=OneWay}\" />\n</Page.Resources>\n

                  Note

                  Beachten Sie, dass wir in der Datenverbindung an die Eigenschaft ViewModel binden, die sich in MainPage.xaml.cs befindet, und einfach die Eigenschaft DataContext an unseren ViewModel-Typ \u00fcbergeben.

                  public MainPageViewModel ViewModel => DataContext as MainPageViewModel;\n

                  Die Speicherung des ViewModels in der Eigenschaft DataContext der Steuerelemente (Seiten) ist typisch f\u00fcr das MVVM-Muster. In unserem Fall \u00fcbernimmt die Klasse \"NavigationService\" des generierten Projekts diese Aufgabe f\u00fcr uns.

                  Ressourcen

                  In der XAML-Umgebung hat jedes Steuerelement (im obigen Beispiel die Seite) und die Klasse Application standardm\u00e4\u00dfig eine Eigenschaft Resources, die ein Schl\u00fcssel-Wert-Speicher ist (Dictionary<string, object>). Sie k\u00f6nnen wiederverwendbare Objekte einf\u00fcgen, sogar auf der Anwendungsebene. Wenn Sie bei der Instanziierung von Ressourcen das Attribut x:Key angeben, k\u00f6nnen Sie Ressourcen nach Schl\u00fcsseln abfragen, z.B. mit der Markup-Erweiterung {StaticResource Key}.

                  Aber hier haben wir explizit x:Name anstelle von x:Key angegeben, weil wir uns in x:Bind auf den Namen beziehen wollen (zur Erinnerung: das Attribut x:Name wird verwendet, um eine Mitgliedsvariable in unserer Klasse mit diesem Namen zu erzeugen, so dass wir sie aus dem code behind Datei oder w\u00e4hrend der Verwendung von x:Bind Datenverbindung, mit diesem Namen erreichen k\u00f6nnen).

                  F\u00fcr die Auflistung der Rezepte verwenden wir nun ein spezielles, von GridView abgeleitetes Steuerelement, n\u00e4mlich AdaptiveGridViewaus dem CommunityToolkit-Paket, das die Anzahl und Gr\u00f6\u00dfe der angezeigten Elemente in Abh\u00e4ngigkeit von der Gr\u00f6\u00dfe der Ansicht \u00e4ndert und die Benutzung von Commands f\u00fcr Elementklicks unterst\u00fctzt. Um auf externe Steuerelemente zu verweisen, f\u00fcgen wir zu der Seite den folgenden Namespace hinzu:

                  xmlns:controls=\"using:CommunityToolkit.WinUI.UI.Controls\"\n

                  Erstellen wir die GridView mit der Eigenschaft ItemsSource, die in der obigen Ressource an RecipeGroupsCollectionSource.View gebunden ist.

                  Innerhalb von GridView k\u00f6nnen wir wie gewohnt \u00fcber die Eigenschaft ItemTemplate festlegen, wie jedes Element angezeigt werden soll. In unserem Fall haben wir ein Bild und einen Text, der auf dem Titel des Rezepts basiert, in ein \"karten\u00e4hnliches\" Layout gesetzt.

                  Und \u00fcber die Eigenschaft GroupStyle k\u00f6nnen wir festlegen, wie die Gruppen angezeigt werden sollen. In diesem Fall wollen wir die Kopfzeile anpassen.

                  Ersetzen wir in MainPage.xaml das Gitter <Grid x:Name=\"ContentArea\"> ... durch das folgende:

                  <Grid x:Name=\"ContentArea\" Padding=\"10\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n\n    <TextBlock Text=\"Recipes\"\n               Grid.Row=\"0\"\n               Style=\"{StaticResource TitleLargeTextBlockStyle}\" />\n\n    <controls:AdaptiveGridView Grid.Row=\"1\"\n                               DesiredWidth=\"180\"\n                               IsItemClickEnabled=\"True\"\n                               ItemHeight=\"160\"\n                               ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                               SelectionMode=\"None\"\n                               StretchContentForSingleRow=\"False\">\n        <GridView.ItemTemplate>\n            <DataTemplate x:DataType=\"models:RecipeHeader\">\n                <Grid MaxWidth=\"300\">\n                    <Image Source=\"{x:Bind BackgroundImage}\" />\n                    <Border Height=\"40\"\n                            Padding=\"10,0,0,0\"\n                            VerticalAlignment=\"Bottom\"\n                            Background=\"#88000000\">\n                        <TextBlock VerticalAlignment=\"Center\"\n                                   Foreground=\"White\"\n                                   Text=\"{x:Bind Title}\" />\n                    </Border>\n                </Grid>\n            </DataTemplate>\n        </GridView.ItemTemplate>\n        <GridView.GroupStyle>\n            <GroupStyle>\n                <GroupStyle.HeaderTemplate>\n                    <DataTemplate x:DataType=\"models:RecipeGroup\">\n                        <TextBlock Margin=\"0\"\n                                   Style=\"{ThemeResource TitleTextBlockStyle}\"\n                                   Text=\"{x:Bind Title}\" />\n                    </DataTemplate>\n                </GroupStyle.HeaderTemplate>\n            </GroupStyle>\n        </GridView.GroupStyle>\n    </controls:AdaptiveGridView>\n</Grid>\n

                  Nehmen wir den folgenden Namespace (hier befinden sich unsere Modellklassen) auf:

                  `xmlns:models=\"using:MvvmLab.Core.Models\"`\n

                  Probieren wir die App aus! Achten Sie darauf, dass die Rezeptgruppen auf der Hauptseite erscheinen.

                  "},{"location":"labor/5-mvvm/index_ger/#aufgabe-2-rezept-detailseite","title":"Aufgabe 2. - Rezept-Detailseite","text":"

                  Die Erstellung der detaillierten Rezeptseite erfolgt in folgenden Schritten:

                  1. F\u00fcgen wir der Schnittstelle IRecipeService eine Methode GetRecipeAsync hinzu und erstellen wir die erforderlichen Klassen
                  2. Erstellen wir ein Ansichtsmodell RecipeDetailPageViewModel, in dem wir die Rezeptdaten in RecipeDetailPageViewModel \u00fcber IRecipeService abfragen (die VM erh\u00e4lt die ID bei der Navigation)
                  3. Erstellen wir die Ansicht RecipeDetailPage, die auf den Daten des ViewModel aufbaut
                  4. Registrieren wir das ViewModel und View f\u00fcr Dependency Injection Konfiguration und Navigation
                  5. Navigieren wir von MainPageViewModel zu RecipeDetailPage durch INavigationService, falls es auf das Rezept angeklickt wird und die ID des ausgew\u00e4hlten Rezepts wird an die Detailseite \u00fcbergegeben
                  "},{"location":"labor/5-mvvm/index_ger/#21-abfrage-eines-rezepts","title":"2.1 Abfrage eines Rezepts","text":"

                  Erstellen wir die Klasse Recipe im Namensraum MvvmLab.Core.Model und generieren wir ihren Inhalt aus den JSON-Beispieldaten, die vom Endpunkt /api/recipes/{id} zur\u00fcckgegeben werden, unter Verwendung der oben beschriebenen Methode (Paste special).

                  public class Recipe\n{\n    public int Id { get; set; }\n    public string BackgroundImage { get; set; }\n    public string Title { get; set; }\n    public string[] ExtraImages { get; set; }\n    public string[] Ingredients { get; set; }\n    public string Directions { get; set; }\n    public Comment[] Comments { get; set; }\n}\n\npublic class Comment\n{\n    public string Name { get; set; }\n    public string Text { get; set; }\n}\n

                  Warning

                  W\u00e4hrend des \"Paste Special\" ist es wichtig, ein Rezept in die Zwischenablage zu legen, das einen Kommentar enth\u00e4lt (andernfalls wird die Klasse Comment nicht erzeugt, und die Klasse Recipe erzeugt den Typ object[] des Typs Comments). Es lohnt sich, das Beispiel aus dem Feld \"Example value\" der Swagger-Beschreibung in die Zwischenablage zu kopieren!

                  Die Schnittstelle IRecipeService und ihre Implementierung werden mit einer Methode GetRecipeAsync erweitert, die ein Rezept auf der Grundlage seiner Identifizierungsnummer zur\u00fcckgibt.

                  IRecipeService
                  public Task<Recipe> GetRecipeAsync(int id);\n
                  RecipeService
                  public async Task<Recipe> GetRecipeAsync(int id)\n{\n    using var client = new HttpClient();\n    return await client.GetFromJsonAsync<Recipe>($\"{_baseUrl}/Recipes/{id}\");\n}\n
                  "},{"location":"labor/5-mvvm/index_ger/#22-rezept-detailliertes-viewmodel","title":"2.2 Rezept detailliertes ViewModel","text":"

                  Die Erstellung eines ViewModels ist im Vergleich zur Hauptseite eine Finger\u00fcbung (wir k\u00f6nnen grunds\u00e4tzlich auf seinem Muster arbeiten). Erstellen wir die Klasse RecipeDetailPageViewModel im Ordner MvvmLab.ViewModels.

                  Das ViewModel ben\u00f6tigt eine Klasse, die die Schnittstelle IRecipeService implementiert, \u00fcber die es das Rezept abfragen kann. Im RecipeDetailPageViewModel Konstruktor wird DI verwendet, um die notwendige Abh\u00e4ngigkeit zu erhalten.

                  private readonly IRecipeService _recipeService;\n\npublic RecipeDetailPageViewModel(IRecipeService recipeService)\n{\n    _recipeService = recipeService;\n}\n

                  Erstellen wir in RecipeDetailPageViewModel eine Variable des Typs Recipe mit dem Namen _recipe, in der das Rezept gespeichert werden soll. Die Variable wird mit dem Attribut ObservableProperty versehen, wodurch MVVM Toolkit automatisch die Eigenschaft Recipe in der anderen generierten partiellen H\u00e4lfte der Klasse erzeugen kann. Dies setzt voraus, dass die Klasse von der Klasse ObservableObject abgeleitet ist, \u00f6ffentlich ist und das Schl\u00fcsselwort partial enth\u00e4lt.

                  public partial class RecipeDetailPageViewModel : ObservableObject\n{\n    // ...\n\n    [ObservableProperty]\n    private Recipe _recipe = new();\n

                  Implementieren wir die vorbereitete Schnittstelle INavigationAware in RecipeDetailPageViewModel. Wir bereiten uns darauf vor, dass wir die ID des Rezepts als Navigationsparameter erhalten, das wir anzeigen wollen. In der Methode OnNavigatedTo rufen wir das Rezept \u00fcber RecipeService ab und speichern es in der Eigenschaft Recipe.

                  public partial class RecipeDetailPageViewModel : ObservableObject, INavigationAware\n{\n    // ...\n\n    public async void OnNavigatedTo(object parameter)\n    {\n        Recipe = await _recipeService.GetRecipeAsync((int)parameter);\n    }\n\n    public void OnNavigatedFrom()\n    {\n    }\n}\n

                  Note

                  In der Kopfzeile der Aktion OnNavigatedTo mussten wir das Schl\u00fcsselwort async verwenden, weil wir await in der Wurzel verwendet haben.

                  "},{"location":"labor/5-mvvm/index_ger/#23-rezeptdetailseite-navigation","title":"2.3 Rezeptdetailseite, Navigation","text":"

                  Erstellen wir eine neue Seite mit dem Namen RecipeDetailPage im Ordner Views (Rechtsklick auf den Ordner Views / Add New Item / Blank Page (WinUI 3)), auf der wir das Rezept anzeigen k\u00f6nnen. Zeigen wir zun\u00e4chst nur den Titel des Rezepts in einer TextBlock an.

                  <Grid x:Name=\"ContentArea\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"48\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n\n    <TextBlock Grid.Row=\"0\"\n               Style=\"{StaticResource PageTitleStyle}\"\n               Text=\"{x:Bind ViewModel.Recipe.Title, Mode=OneWay}\" />\n</Grid>\n

                  Zu der Datenverbingung f\u00fcgen wir die Eigenschaft ViewModel in RecipeDetailPage.xaml.cs zur Hauptseite hinzu.

                  public RecipeDetailPageViewModel ViewModel => (RecipeDetailPageViewModel)DataContext;\n

                  \u00dcbersetzungsfehler

                  Wenn Sie aus irgendeinem Grund exotische Fehler erhalten, nachdem Sie eine neue Seite hinzugef\u00fcgt haben, l\u00f6schen Sie die folgenden Zeilen in der Projektdatei:

                  <ItemGroup>\n    <None Remove=\"ViewsRecipeDetailPage.xaml\" />\n</ItemGroup>\n
                  <Page Update=\"ViewsRecipeDetailPage.xaml\">\n    <Generator>MSBuild:Compile</Generator>\n</Page>\n

                  Um die Navigation zu unterst\u00fctzen, registrieren wir RecipeDetailPage in PageService im Ordner Services in den folgenden 3 Schritten:

                  1. Nehmen wir den Ansichtsschl\u00fcssel in die Klasse Pages auf.

                    public static class Pages\n{\n    public static string Main { get; } = \"Main\";\n    public static string Detail { get; } = \"Detail\";\n}\n
                  2. Registrieren wir die Ansicht und ViewModel-Verbindung in PageService.

                    public PageService()\n{\n    Configure<MainPageViewModel, MainPage>(Pages.Main);\n    Configure<RecipeDetailPageViewModel, RecipeDetailPage>(Pages.Detail);\n}\n
                  3. In der Datei App.xaml.cs registrieren wir das ViewModel und den View im Dependency Injection Container in der Methode ConfigureServices.

                    services.AddTransient<RecipeDetailPage>();\nservices.AddTransient<RecipeDetailPageViewModel>();\n

                  Diese werden ben\u00f6tigt, weil die INavigationService in der Projektvorlage die Ansichten grunds\u00e4tzlich mit einem Schl\u00fcssel identifiziert, so dass das ViewModel den Ansichtstyp nicht kennen muss. Und anhand des Schl\u00fcssels kann man genau herausfinden, welche View angezeigt und welches ViewModel in der View DataContext aus dem DI-Container instanziiert werden soll.

                  In das MainPageViewModel injektieren wir den INavigationService, \u00fcber den wir zur RecipeDetailPage navigieren werden.

                  private readonly INavigationService _navigationService;\n\npublic MainPageViewModel(IRecipeService recipeService, INavigationService navigationService)\n{\n    _recipeService = recipeService;\n    _navigationService = navigationService;\n}\n
                  "},{"location":"labor/5-mvvm/index_ger/#command","title":"Command","text":"

                  Bisher haben wir uns mit einem Aspekt des MVVM-Musters besch\u00e4ftigt: wie die View auf die Daten im ViewModel zugreift und diese anzeigt, indem sie Daten bindet. Gleichzeitig besteht in der Regel eine weitere Beziehung zwischen View und ViewModel: Hier geht es darum, wie sich Ereignisse in der View (z.B. Klicks) auf das ViewModel auswirken. Damit werden wir uns jetzt befassen.

                  In unserem Fall m\u00fcssen wir zum Beispiel daf\u00fcr sorgen, dass ein Klick auf ein Rezept in der Hauptseitenansicht zu MainPageViewModel f\u00fchrt und dann zur Detailansicht dieses Rezepts navigiert.

                  Das ViewModel ver\u00f6ffentlicht die ausf\u00fchrbaren Operationen im MVVM-Muster durch Objekte, die typischerweise die Schnittstelle ICommand implementieren (die neben der Ausf\u00fchrung der spezifischen Operation auch die Bedingungen f\u00fcr die Ausf\u00fchrung der Operation verwalten k\u00f6nnen).

                  Erstellen wir unter MainPageViewModel einen Command, der ausgef\u00fchrt wird, wenn wir auf das Rezept klicken. Der Command erh\u00e4lt die Kopfzeile des ausgew\u00e4hlten Rezepts als Parameter und wird an RecipeDetailPage weitergeleitet, wo die ID des ausgew\u00e4hlten Rezepts \u00fcbergeben wird.

                  Jetzt sollten wir eine Klasse erstellen, die die Schnittstelle ICommand implementiert, und dann eine Instanz (Eigenschaft) davon in das ViewModel aufnehmen. Diese beiden Schritte werden durch das MVVM-Toolkit vereinfacht, wir m\u00fcssen nur eine Funktion mit dem Attribut [RelayCommand] zum ViewModel hinzuf\u00fcgen:

                  [RelayCommand]\nprivate void RecipeSelected(RecipeHeader recipe)\n{\n    _navigationService.NavigateTo(Pages.Detail, recipe.Id);\n}\n

                  Dies veranlasst den Compiler, die Commandsklasse und die Eigenschaft im ViewModel als RecipeSelectedCommand zu generieren.

                  Der Befehl und das ViewModel sind vorbereitet, aber die View wei\u00df noch nichts \u00fcber den Befehl. Unser Befehl im ViewModel muss mit den \u00fcblichen Techniken an das entsprechende Ereignis in der View gebunden werden. Verwenden wir f\u00fcr MVVM immer das Command-Muster wie dieses! Das Sch\u00f6ne an diesem Ansatz ist, dass er vollst\u00e4ndig mit der standardm\u00e4\u00dfigen direktionalen Datenverbindung von View->ViewModel durchgef\u00fchrt wird (die wir bereits mehrfach verwendet haben).

                  Binden wir daher auf MainPage die Eigenschaft AdaptiveGridView ItemClickCommand an RecipeSelectedCommand.

                  ItemClickCommand=\"{x:Bind ViewModel.RecipeSelectedCommand}\"\n

                  Probieren wir die App aus! Klicken wir auf die Rezepte, um die Rezeptdetailseite zu sehen.

                  Ausblick: Gibt es keinen Befehl f\u00fcr das Ereignis, das Sie verwenden m\u00f6chten?

                  Wenn der Controller einen Befehl f\u00fcr bestimmte Ereignisse bereitstellt, ist dies relativ einfach zu bewerkstelligen, wie im obigen Beispiel gezeigt. Wenn das Steuerelement jedoch keinen Befehl bereitstellt (z.B. das eingebaute GridView.ItemClicked), haben wir mehrere M\u00f6glichkeiten:

                  1. Code-Behind \"Klebercode\": Behandeln Sie das Ereignis des Controllers und rufen Sie die entsprechende Methode/Befehl des Code-Behind im ViewModel (xaml.cs) auf.

                    <controls:AdaptiveGridView x:Name=\"gridView\"\n                            ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                            IsItemClickEnabled=\"True\"\n                            ItemClick=\"GridView_ItemClick\">\n
                    private void GridView_ItemClick(object sender, ItemClickEventArgs e)\n{\n    ViewModel.RecipeSelectedCommand.Execute((RecipeHeader)e.ClickedItem);\n}\n
                  2. x:Bind-Ereignisbindung: Verwenden Sie die Bindungsoption der Methode x:Bind, um das Ereignis des Steuerelements an die Methode im ViewModel zu binden. Die Methode muss dann entweder parameterlos sein oder einen Parameter annehmen, der der Signatur des Ereignisses entspricht.

                    View - MainPage.xaml
                    <controls:AdaptiveGridView x:Name=\"gridView\"\n                            ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                            IsItemClickEnabled=\"True\"\n                            ItemClick=\"{x:Bind ViewModel.RecipeSelected\">\n</controls:AdaptiveGridView>\n
                    ViewModel - MainPageViewModel
                    public void RecipeSelected(object sender, ItemClickEventArgs e)\n{\n   ...\n}\n

                    Der Nachteil dieser Methode ist, dass sie die Framework-Abh\u00e4ngigkeiten des View (Eventhandler-Parametertypen) mit den Ereignisparametern in das ViewModel einf\u00fchrt, obwohl die Idee war, das ViewModel unabh\u00e4ngig von der View zu machen. Nat\u00fcrlich kann diese Methode auch gut funktionieren, wenn wir die strikte Einhaltung des MVVM-Musters teilweise aufgeben.

                  3. Mit Hilfe von Behavior, ganz konkret EventTriggerBehavior und InvokeCommandAction Klassen, k\u00f6nnen Sie einen Command an ein Ereignis eines beliebigen Steuererelementes binden.

                    <controls:AdaptiveGridView x:Name=\"gridView\"\n                            ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                            IsItemClickEnabled=\"True\">\n    <i:Interaction.Behaviors>\n        <c:EventTriggerBehavior EventName=\"ItemClick\">\n            <c:InvokeCommandAction Command=\"{x:Bind ViewModel.RecipeSelectedCommand}\" \n                                   InputConverter=\"{StaticResource ItemClickedInputConverter}\" />\n        </c:EventTriggerBehavior>\n    </i:Interaction.Behaviors>\n

                    Dies erm\u00f6glicht es uns, die Ansicht fast vollst\u00e4ndig deklarativ zu gestalten, aber wir m\u00fcssen immer noch eine Klasse ItemClickedInputConverter erstellen, die die Ereignisparameter mithilfe der Schnittstelle IValueConverter in den entsprechenden Typ umwandelt.

                    public class ItemClickedInputConverter : IValueConverter\n{\n    public object Convert(object value, Type targetType, object parameter, string language)\n    {\n        return (RecipeHeader)((value as ItemClickEventArgs)?.ClickedItem);\n    }\n\n    public object ConvertBack(object value, Type targetType, object parameter, string language)\n    {\n        throw new NotImplementedException();\n    }\n}\n

                    Behaviors sind in der XAML-Welt weit verbreiteter Mechanismus, um wiederverwendbare Verhaltensweisen zu Views hinzuzuf\u00fcgen (weitere Informationen hier).

                  "},{"location":"labor/5-mvvm/index_ger/#24-rezept-detailansicht","title":"2.4 Rezept-Detailansicht","text":"

                  Um die Details des Rezepts anzuzeigen, verwenden wir eine Grid mit zwei Spalten. Legen wir in die erste Spalte ein ScrollViewer, in das ein StackPanel eingef\u00fcgt wird. Legen wir auf StackPanel eine FlipView, an der die Bilder des Rezepts angezeigt werden sollen. FlipView funktioniert wie eine Liste, zeigt aber ihre Elemente in einer bl\u00e4tterbaren Oberfl\u00e4che an.

                  Unter FlipView finden wir ItemsControl (eine einfache Liste, die kein Scrollen, Ausw\u00e4hlen, Anklicken usw. unterst\u00fctzt), in der die Zutaten des Rezepts angezeigt werden.

                  Darunter befindet sich eine TextBlock, die die Schritte zur Zubereitung des Rezepts enth\u00e4lt.

                  In der zweiten Spalte platzieren wir ein Grid, wo die Liste der Kommentare und ihre Eingabefelder platziert werden.

                  Wir k\u00f6nnen den folgenden Code w\u00e4hrend des Praktikums auf RecipeDetailPage.xaml kopieren. Dieser Code ist im Vergleich zu den vorherigen nicht neu.

                  <?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<Page x:Class=\"MvvmLab.Views.RecipeDetailPage\"\n      xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n      xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n      xmlns:local=\"using:MvvmLab.Views\"\n      xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n      xmlns:models=\"using:MvvmLab.Core.Models\"\n      Background=\"{ThemeResource ApplicationPageBackgroundThemeBrush}\"\n      mc:Ignorable=\"d\">\n\n    <Grid x:Name=\"ContentArea\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n\n        <TextBlock Grid.Row=\"0\" Padding=\"10\"\n                   Style=\"{StaticResource TitleTextBlockStyle}\"\n                   Text=\"{x:Bind ViewModel.Recipe.Title, Mode=OneWay}\" />\n\n        <Grid Grid.Row=\"1\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"3*\" />\n                <ColumnDefinition Width=\"*\" />\n            </Grid.ColumnDefinitions>\n\n            <ScrollViewer Grid.Column=\"0\" Padding=\"20 10 0 20\">\n                <StackPanel Orientation=\"Vertical\">\n                    <StackPanel x:Name=\"images\"\n                                Margin=\"0,0,24,0\"\n                                Orientation=\"Vertical\">\n                        <TextBlock Margin=\"0,0,0,12\"\n                                   Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                                   Text=\"Images\" />\n                        <FlipView x:Name=\"flipView\"\n                                  MaxHeight=\"250\"\n                                  VerticalAlignment=\"Top\"\n                                  ItemsSource=\"{x:Bind ViewModel.Recipe.ExtraImages, Mode=OneWay}\">\n                            <FlipView.ItemTemplate>\n                                <DataTemplate>\n                                    <Image Source=\"{Binding}\" Stretch=\"Uniform\" />\n                                </DataTemplate>\n                            </FlipView.ItemTemplate>\n                        </FlipView>\n                    </StackPanel>\n\n                    <StackPanel x:Name=\"ingredients\"\n                                Margin=\"0,0,24,0\"\n                                Orientation=\"Vertical\">\n                        <TextBlock Margin=\"0,0,0,12\"\n                                   Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                                   Text=\"Ingredients\" />\n                        <ItemsControl HorizontalAlignment=\"Left\" ItemsSource=\"{x:Bind ViewModel.Recipe.Ingredients, Mode=OneWay}\">\n                            <ItemsControl.ItemTemplate>\n                                <DataTemplate>\n                                    <TextBlock Margin=\"0,0,0,10\"\n                                               Text=\"{Binding}\"\n                                               TextWrapping=\"Wrap\" />\n                                </DataTemplate>\n                            </ItemsControl.ItemTemplate>\n                        </ItemsControl>\n                    </StackPanel>\n\n                    <StackPanel x:Name=\"directions\"\n                                Margin=\"0,0,24,0\"\n                                Orientation=\"Vertical\"\n                                RelativePanel.RightOf=\"ingredients\">\n                        <TextBlock Margin=\"0,0,0,12\"\n                                   Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                                   Text=\"Directions\" />\n                        <TextBlock HorizontalAlignment=\"Left\"\n                                   Text=\"{x:Bind ViewModel.Recipe.Directions, Mode=OneWay}\"\n                                   TextWrapping=\"Wrap\" />\n                    </StackPanel>\n                </StackPanel>\n            </ScrollViewer>\n\n            <Grid Grid.Column=\"1\" RowSpacing=\"12\">\n                <Grid.RowDefinitions>\n                    <RowDefinition Height=\"Auto\" />\n                    <RowDefinition Height=\"*\" />\n                    <RowDefinition Height=\"Auto\" />\n                </Grid.RowDefinitions>\n\n                <TextBlock Grid.Row=\"0\"\n                           Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                           Text=\"Comments\" />\n\n                <ListView Grid.Row=\"1\" ItemsSource=\"{x:Bind ViewModel.Recipe.Comments, Mode=OneWay}\">\n                    <ListView.ItemTemplate>\n                        <DataTemplate x:DataType=\"models:Comment\">\n                            <StackPanel Orientation=\"Vertical\" Padding=\"0 5 0 5\">\n                                <TextBlock FontWeight=\"Bold\" Text=\"{x:Bind Name}\" />\n                                <TextBlock Text=\"{x:Bind Text}\" />\n                            </StackPanel>\n                        </DataTemplate>\n                    </ListView.ItemTemplate>\n                </ListView>\n\n                <StackPanel x:Name=\"comments\"\n                            Grid.Row=\"2\"\n                            Margin=\"24,0,24,0\"\n                            Orientation=\"Vertical\">\n                    <!-- TODO input fields for comments -->\n                </StackPanel>\n            </Grid>\n        </Grid>\n    </Grid>\n</Page>\n

                  Probieren wir die App aus!

                  "},{"location":"labor/5-mvvm/index_ger/#aufgabe-3-kommentare-hinzufugen","title":"Aufgabe 3. - Kommentare hinzuf\u00fcgen","text":"

                  Wenn wir einen engen Zeitplan haben, k\u00f6nnen wir eine Funktion zum Hinzuf\u00fcgen von Kommentaren auf der Rezeptdetailseite erstellen.

                  "},{"location":"labor/5-mvvm/index_ger/#webdienst","title":"Webdienst","text":"

                  F\u00fcgen wir der Schnittstelle IRecipeService und der Implementierung eine Methode SendCommentAsync hinzu, die einen Kommentar an den Server unter dem Endpunkt POST /Recipes/{recipeId}/Comments sendet.

                  IRecipeService
                  public Task SendCommentAsync(int recipeId, Comment comment);\n
                  RecipeService
                  public async Task SendCommentAsync(int recipeId, Comment comment)\n{\n    using var client = new HttpClient();\n    await client.PostAsJsonAsync($\"{_baseUrl}/Recipes/{recipeId}/Comments\", comment);\n}\n
                  "},{"location":"labor/5-mvvm/index_ger/#viewmodel","title":"ViewModel","text":"

                  Erstellen wir in RecipeDetailPageViewModeleine Eigenschaft string mit dem Namen NewCommentText und eine Eigenschaft NewCommentName string mit dem Namen, in denen die vom Benutzer bereitgestellten Kommentarinformationen gespeichert werden sollen. Verwenden wir das Attribut ObservableProperty!

                  [ObservableProperty]\nprivate string _newCommentName = string.Empty;\n\n[ObservableProperty]\nprivate string _newCommentText = string.Empty;\n

                  Erstellen wir in RecipeDetailPageViewModel eine Funktion namens SendComment, mit der der Kommentar des Benutzers an den Server gesendet werden kann. Generieren wir einen Befehl aus der Funktion mit dem MVVM Toolkit ([RelayCommand]).

                  Die Umsetzung ist einfach: Wir senden den Kommentar an den Server und aktualisieren dann das Rezept.

                  [RelayCommand]\nprivate async Task SendComment()\n{\n    await _recipeService.SendCommentAsync(Recipe.Id, new Comment\n    {\n        Name = NewCommentName,\n        Text = NewCommentText\n    });\n\n    NewCommentName = string.Empty;\n    NewCommentText = string.Empty;\n\n    Recipe = await _recipeService.GetRecipeAsync(Recipe.Id);\n}\n

                  Die folgenden Elemente werden in der Ansicht platziert, um Kommentare hinzuzuf\u00fcgen:

                  <StackPanel x:Name=\"comments\"\n            Grid.Row=\"2\"\n            Margin=\"24,0,24,0\"\n            Orientation=\"Vertical\">\n    <TextBox Margin=\"0,0,0,16\"\n             Header=\"Name\"\n             Text=\"{x:Bind ViewModel.NewCommentName, Mode=TwoWay}\" />\n    <TextBox Margin=\"0,0,0,16\"\n             Header=\"Comment\"\n             Text=\"{x:Bind ViewModel.NewCommentText, Mode=TwoWay}\" />\n    <Button Margin=\"0,0,0,16\"\n            HorizontalAlignment=\"Right\"\n            Command=\"{x:Bind ViewModel.SendCommentCommand}\"\n            Content=\"Send\" />\n</StackPanel>\n

                  Beachten wir, dass die Eigenschaft Text von TextBox an die Eigenschaften NewCommentName und NewCommentText im ViewModel mit einer bidirektionalen Bindung gebunden ist, und dass die Eigenschaft Command der Taste an die Eigenschaft SendCommentCommand im ViewModel gebunden ist.

                  "},{"location":"labor/5-mvvm/index_ger/#ausblick-bedingungen-fur-die-ausfuhrung-von-befehlen","title":"Ausblick: Bedingungen f\u00fcr die Ausf\u00fchrung von Befehlen","text":"

                  Der Befehl SendCommentCommand erfordert, dass die Eigenschaften NewCommentName und NewCommentText nicht leer sind. Befehle bieten die M\u00f6glichkeit, ihre Ausf\u00fchrung an Bedingungen zu kn\u00fcpfen, die in der Methode CanExecute angegeben werden k\u00f6nnen. In unserem Fall m\u00fcssen wir dem Attribut Command generator einen Methoden-/Eigenschaftsnamen geben, der bool zur\u00fcckgibt.

                  RecipeDetailPageViewModel-ben:
                  private bool CanExecuteSendComment => !string.IsNullOrEmpty(NewCommentName) && !string.IsNullOrEmpty(NewCommentText);\n\n[RelayCommand(CanExecute = nameof(CanExecuteSendComment))]\nprivate async Task SendComment()\n

                  Probieren wir es aus. Wir stellen fest, dass die Taste nicht aktiviert wird, aber nach der \u00c4nderung von TextBox \u00e4ndert sich der Zustand der Taste nicht.

                  Die Methode CanExecute wird aufgerufen (von den Steuerelementen), wenn Command das Ereignis CanExecuteChanged ausl\u00f6st. In unserem Fall soll dieses Ereignis ausgel\u00f6st werden, wenn das Ereignis PropertyChanged der Eigenschaften NewCommentName und NewCommentText ausgel\u00f6st wird. Zu diesem Zweck bietet das MVVM Toolkit ein eigenes Attribut ([NotifyCanExecuteChangedFor]), das zu den Eigenschaften NewCommentName und NewCommentText hinzugef\u00fcgt werden muss.

                  Wenn sich also der Wert der Eigenschaft NewCommentName oder NewCommentText \u00e4ndert, wird auch das Ereignis SendCommentCommand Befehl CanExecuteChanged ausgel\u00f6st, wodurch die Methode CanExecute erneut ausgef\u00fchrt und der Zustand der Taste aktualisiert wird.

                  [ObservableProperty]\n[NotifyCanExecuteChangedFor(nameof(SendCommentCommand))]\nprivate string _newCommentName = string.Empty;\n\n[ObservableProperty]\n[NotifyCanExecuteChangedFor(nameof(SendCommentCommand))]\nprivate string _newCommentText = string.Empty;\n

                  Probieren wir es aus.

                  Es gibt nur noch eine Sache: Derzeit \u00e4ndert sich der Zustand von TextBox nur, wenn der Benutzer TextBox verl\u00e4sst. Dieses Verhalten kann \u00fcber die Eigenschaft UpdateSourceTrigger der Datenverbindung ge\u00e4ndert werden.

                  Text=\"{x:Bind ViewModel.NewCommentName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n\nText=\"{x:Bind ViewModel.NewCommentText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n

                  Probieren wir es aus.

                  "},{"location":"labor/6-tervezesi-mintak/","title":"6. Tervez\u00e9si mint\u00e1k (kiterjeszthet\u0151s\u00e9g)","text":""},{"location":"labor/6-tervezesi-mintak/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                  A gyakorlat c\u00e9ljai (egy \u00f6sszetettebb, \u00e9letszer\u0171 p\u00e9lda alapj\u00e1n):

                  • Kiterjeszthet\u0151s\u00e9get, \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1got, k\u00f3d \u00e1tl\u00e1that\u00f3s\u00e1got \u00e9s karbantarthat\u00f3s\u00e1got seg\u00edt\u0151 n\u00e9h\u00e1ny tervez\u00e9si alapelv gyakorl\u00e1sa: SRP, OPEN-CLOSED, DRY, KISS stb.
                  • N\u00e9h\u00e1ny, a kiterjeszthet\u0151s\u00e9ghez legink\u00e1bb kapcsol\u00f3d\u00f3 tervez\u00e9si minta alkalmaz\u00e1sa (Template Method, Strategy, Dependency Injection).
                  • Kiterjeszthet\u0151s\u00e9get \u00e9s \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1got t\u00e1mogat\u00f3 tov\u00e1bbi technik\u00e1k (pl. delegate/lambda kifejez\u00e9s) gyakorl\u00e1sa \u00e9s kombin\u00e1l\u00e1sa tervez\u00e9si mint\u00e1kkal.
                  • K\u00f3d refaktor\u00e1l\u00e1s gyakorl\u00e1sa.

                  Kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sok:

                  • Tervez\u00e9si mint\u00e1k: kiterjeszthet\u0151s\u00e9ghez kapcsol\u00f3d\u00f3 mint\u00e1k (bevezet\u0151, Template Method, Strategy), valamint a Dependency Injection \"minta\".
                  "},{"location":"labor/6-tervezesi-mintak/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                  A gyakorlat elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                  • Visual Studio 2022

                  Gyakorlat Linuxon vagy macOS alatt

                  A gyakorlat anyag alapvet\u0151en Windowsra \u00e9s Visual Studiora k\u00e9sz\u00fclt, de az elv\u00e9gezhet\u0151 m\u00e1s oper\u00e1ci\u00f3s rendszereken is m\u00e1s fejleszt\u0151eszk\u00f6z\u00f6kkel (pl. VS Code, Rider, Visual Studio for Mac), vagy ak\u00e1r egy sz\u00f6vegszerkeszt\u0151vel \u00e9s CLI (parancssori) eszk\u00f6z\u00f6kkel. Ezt az teszi lehet\u0151v\u00e9, hogy a p\u00e9ld\u00e1k egy egyszer\u0171 Console alkalmaz\u00e1s kontextus\u00e1ban ker\u00fclnek ismertet\u00e9sre (nincsenek Windows specifikus elemek), a .NET 8 SDK pedig t\u00e1mogatott Linuxon \u00e9s macOS alatt. Hello World Linuxon.

                  "},{"location":"labor/6-tervezesi-mintak/#elmeleti-hatter-szemleletmod","title":"Elm\u00e9leti h\u00e1tt\u00e9r, szeml\u00e9letm\u00f3d *","text":"

                  A komplexebb alkalmaz\u00e1sok fejleszt\u00e9se sor\u00e1n sz\u00e1mos tervez\u0151i d\u00f6nt\u00e9st kell meghoznunk, melyek sor\u00e1n t\u00f6bb lehet\u0151s\u00e9g k\u00f6z\u00fcl is v\u00e1laszthatunk. Amennyiben ezen pontokban nem tartjuk szem el\u0151tt az alkalmaz\u00e1sunk k\u00f6nny\u0171 karbantarthat\u00f3s\u00e1g\u00e1t, illetve egyszer\u0171en megval\u00f3s\u00edthat\u00f3 tov\u00e1bbfejleszt\u00e9si lehet\u0151s\u00e9g\u00e9t, k\u00f6nnyen hamar r\u00e9m\u00e1lomm\u00e1 v\u00e1lhat a fejleszt\u00e9s. A megrendel\u0151i v\u00e1ltoztat\u00e1si \u00e9s b\u0151v\u00edt\u00e9si ig\u00e9nyek a k\u00f3d nagym\u00e9rt\u00e9k\u0171 folyamatos \u00e1t\u00edr\u00e1s\u00e1t/m\u00f3dos\u00edt\u00e1s\u00e1t ig\u00e9nylik: ennek sor\u00e1n \u00faj hib\u00e1k sz\u00fcletnek, illetve jelent\u0151s munk\u00e1t kell fektetni a k\u00f3d nagy l\u00e9pt\u00e9k\u0171 \u00fajratesztel\u00e9s\u00e9be is!

                  A c\u00e9lunk az, hogy az ilyen v\u00e1ltoztat\u00e1si \u00e9s b\u0151v\u00edt\u00e9si ig\u00e9nyeket a k\u00f3d p\u00e1r j\u00f3l meghat\u00e1rozott pontj\u00e1ban t\u00f6rt\u00e9n\u0151 b\u0151v\u00edt\u00e9s\u00e9vel - a megl\u00e9v\u0151 k\u00f3d \u00e9rdemi m\u00f3dos\u00edt\u00e1sa n\u00e9lk\u00fcl - meg tudjuk val\u00f3s\u00edtani. A kulcssz\u00f3: m\u00f3dos\u00edt\u00e1ssal szemben b\u0151v\u00edt\u00e9s. Ehhez kapcsol\u00f3d\u00f3an: amennyiben bizonyos logik\u00e1ink kiterjeszthet\u0151k, akkor azok \u00e1ltal\u00e1nosabbak is leszek, t\u00f6bb kontextusban k\u00f6nnyebben is fel tudjuk ezeket haszn\u00e1lni. \u00cdgy hosszabb t\u00e1von gyorsabban haladunk, r\u00f6videbb a k\u00f3d, elker\u00fclj\u00fck a k\u00f3dduplik\u00e1ci\u00f3t (ez\u00e1ltal k\u00f6nnyebben karbantarthat\u00f3 is a k\u00f3d).

                  A tervez\u00e9si mint\u00e1k j\u00f3l bev\u00e1lt megold\u00e1sokat mutatnak bizonyos gyakran el\u0151fordul\u00f3 tervez\u00e9si probl\u00e9m\u00e1kra: ezen megold\u00e1sok abban seg\u00edtenek, hogy k\u00f3dunk k\u00f6nnyebben b\u0151v\u00edthet\u0151, karbantarthat\u00f3 \u00e9s min\u00e9l nagyobb m\u00e9rt\u00e9kben \u00fajrafelhaszn\u00e1lhat\u00f3 legyen. Jelen gyakorlat keret\u00e9ben azon mint\u00e1kra, tervez\u00e9si elvekre \u00e9s n\u00e9h\u00e1ny programoz\u00f3i eszk\u00f6zre f\u00f3kusz\u00e1lunk, melyek a fenti probl\u00e9m\u00e1kon seg\u00edtenek. Ugyanakkor ne ess\u00fcnk \u00e1t a l\u00f3 t\u00faloldal\u00e1ra: csak akkor \u00e9rdemes egy adott tervez\u00e9si mint\u00e1t bevetni, ha adott esetben val\u00f3s el\u0151nyt jelent az alkalmaz\u00e1sa. Ellenkez\u0151 esetben csak a megval\u00f3s\u00edt\u00e1s komplexit\u00e1s\u00e1t n\u00f6veli feleslegesen. Ennek t\u00fckr\u00e9ben nem is c\u00e9lunk (\u00e9s sokszor nincs is r\u00e1 lehet\u0151s\u00e9g\u00fcnk), hogy minden j\u00f6v\u0151beli kiterjeszthet\u0151s\u00e9gi ig\u00e9nyt el\u0151re meg\u00e9rezz\u00fcnk, illetve nagyon el\u0151re \u00e1tgondoljunk. A l\u00e9nyeg az, hogy ak\u00e1r egy egyszer\u0171 megold\u00e1sb\u00f3l kiindulva, az egyes probl\u00e9m\u00e1kat felismerve, a k\u00f3dunkat folyamatosan refaktor\u00e1ljuk \u00fagy, hogy az aktu\u00e1lis (funkcion\u00e1lis \u00e9s nemfunkcion\u00e1lis) k\u00f6vetelm\u00e9nyeknek \u00e9s el\u0151rel\u00e1t\u00e1sunk szerint a megfelel\u0151 pontokban tegy\u00fck k\u00f3dunkat k\u00f6nnyebben kiterjeszthet\u0151v\u00e9 \u00e9s \u00fajrafelhaszn\u00e1lhat\u00f3v\u00e1.

                  Meg kell eml\u00edteni, hogy kapcsol\u00f3d\u00f3 tervez\u00e9si mint\u00e1k \u00e9s nyelvi eszk\u00f6z\u00f6k a k\u00f3dunk egys\u00e9gtesztelhet\u0151v\u00e9 t\u00e9tel\u00e9ben is nagym\u00e9rt\u00e9kben seg\u00edtenek: sok c\u00e9gn\u00e9l egy szoftverterm\u00e9k fejleszt\u00e9se eset\u00e9n (jogos) alapelv\u00e1r\u00e1s a fejleszt\u0151kt\u0151l, hogy nagy k\u00f3dlefedetts\u00e9g\u0171 egys\u00e9gteszteket (unit test) k\u00e9sz\u00edtsenek. Ennek kivitelez\u00e9se viszont gyakorlatilag lehetetlen, ha a k\u00f3dunk egyes egys\u00e9gei/oszt\u00e1lyai t\u00fal szoros csatol\u00e1sban vannak egym\u00e1ssal.

                  "},{"location":"labor/6-tervezesi-mintak/#0-feladat-ismerkedes-a-feladattal-es-a-kiindulo-alkalmazassal","title":"0. Feladat - Ismerked\u00e9s a feladattal \u00e9s a kiindul\u00f3 alkalmaz\u00e1ssal","text":"

                  Kl\u00f3nozzuk le a 6. laborhoz tartoz\u00f3 kiindul\u00f3 alkalmaz\u00e1s repositoryj\u00e1t:

                  • Nyissunk egy command prompt-ot
                  • Navig\u00e1ljunk el egy tetsz\u0151leges mapp\u00e1ba, p\u00e9ld\u00e1ul c:\\work\\NEPTUN
                  • Adjuk ki a k\u00f6vetkez\u0151 parancsot: git clone https://github.com/bmeviauab00/lab-patterns-extensibility-kiindulo.git
                  • Nyissuk meg a Lab-Patterns-Extensibility.sln solutiont Visual Studio-ban.
                  "},{"location":"labor/6-tervezesi-mintak/#a-feladat-ismertetese","title":"A feladat ismertet\u00e9se","text":"

                  A labor sor\u00e1n egy konzol alap\u00fa, adatfeldolgoz\u00f3 (pontosabban anonimiz\u00e1l\u00f3) alkalmaz\u00e1st fogunk a folyamatosan alakul\u00f3 ig\u00e9nyeknek megfelel\u0151en - k\u00fcl\u00f6nb\u00f6z\u0151 pontok ment\u00e9n \u00e9s k\u00fcl\u00f6nb\u00f6z\u0151 technik\u00e1kat alkalmazva - kiterjeszthet\u0151v\u00e9 tenni. Az els\u0151 feladat keret\u00e9ben az anonimiz\u00e1l\u00e1s fogalm\u00e1val is megismerked\u00fcnk.

                  Az alkalmaz\u00e1s bemenete egy CSV sz\u00f6vegf\u00e1jl, mely minden sora egy adott szem\u00e9lyre vonatkoz\u00f3an tartalmaz adatokat. A f\u00e1jlrendszerben nyissuk meg a Data mapp\u00e1ban lev\u0151 us-500.csv f\u00e1jlt (duplakattal, vagy ak\u00e1r a Jegyzett\u00f6mb/Notepad alkalmaz\u00e1sban). Az l\u00e1tjuk, hogy \"\" k\u00f6z\u00f6tt, vessz\u0151vel elv\u00e1lasztva tal\u00e1lhat\u00f3k az egyes szem\u00e9lyekre vonatkoz\u00f3 adatok (ezek nem val\u00f3sak). N\u00e9zz\u00fck az els\u0151 sort:

                  \"James\",\"Rhymes\",\"Benton, John B Jr\",\"6649 N Blue Gum St\",\"New Orleans \",\"Orleans\",\"LA\",\"70116\",\"504-621-8927\",\"504-845-1427\",\"30\",\"65\",\"Heart-related\",\"jRhymes@gmail.com\"\n

                  Az els\u0151 sorban lev\u0151 szem\u00e9lyt James Rhymesnak nevezik, a \"Benton, John B Jr\" c\u00e9gn\u00e9l dolgozik, majd n\u00e9h\u00e1ny c\u00edmre vonatkoz\u00f3 mez\u0151 tal\u00e1lhat\u00f3, 30 \u00e9ves, 65 kg a tests\u00falya. Az ezt k\u00f6vet\u0151 mez\u0151 azt mondja meg, milyen s\u00falyosabb betegs\u00e9ge van (a fenti sorban ez \"Heart-related\"). Az utols\u00f3 oszlop pedig a szem\u00e9ly e-mail c\u00edm\u00e9t tartalmazza.

                  Adatok forr\u00e1sa \u00e9s pontos form\u00e1tuma *

                  Az adatok forr\u00e1sa: https://www.briandunning.com/sample-data/, p\u00e1r oszloppal (kor, s\u00faly, betegs\u00e9g) kieg\u00e9sz\u00edtve. A mez\u0151k sorrendje: First Name, Last Name, Company, Address, City, County (where applicable), State/Province (where applicable), ZIP/Postal Code, Phone 1, Phone 2, Age, Weight, Illness, Email

                  Az alkalmaz\u00e1s alapfeladata, hogy ezeket az adatokat az aktu\u00e1lis ig\u00e9nyeknek megfelel\u0151en anonimiz\u00e1lja, majd egy kimeneti CSV sz\u00f6vegf\u00e1jlba ki\u00edrja. Az anonimiz\u00e1l\u00e1s c\u00e9lja, hogy az adatok \u00e1talak\u00edt\u00e1s\u00e1val adathalmazban lev\u0151 szem\u00e9lyeket beazonos\u00edthatatlann\u00e1 tegye, de olyan m\u00f3don, hogy az adatokb\u00f3l m\u00e9gis lehessen kimutat\u00e1sokat k\u00e9sz\u00edteni. Az anonimiz\u00e1l\u00e1s egy k\u00fcl\u00f6n\u00e1ll\u00f3, nagyon komoly \u00e9s sok kih\u00edv\u00e1st rejt\u0151 adatfeldolgoz\u00e1si szakter\u00fclet. A gyakorlat keret\u00e9ben nem c\u00e9lunk, hogy val\u00f3s k\u00f6rnyezetben is haszn\u00e1lhat\u00f3, vagy ak\u00e1r minden tekintetben \u00e9rtelmes megold\u00e1sokat dolgozzunk ki. Sz\u00e1munkra tulajdonk\u00e9ppen csak egy valamilyen adatfeldolgoz\u00f3 algoritmus \"bevet\u00e9se\" a fontos a mint\u00e1k bemutat\u00e1s\u00e1hoz. Ez tal\u00e1n kicsit \"izgalmasabb\" keretet ad, mint egy egyszer\u0171 adatsz\u0171r\u00e9s/sorrendez\u00e9s/stb. alap\u00fa adatfeldolgoz\u00e1s (melyeket r\u00e1ad\u00e1sul a .NET m\u00e1r eleve be\u00e9p\u00edtve t\u00e1mogat).

                  P\u00e1r gondolat az anonimiz\u00e1l\u00e1sr\u00f3l

                  Azt gondolhatn\u00e1nk, hogy az anonimiz\u00e1l\u00e1s egy egyszer\u0171 probl\u00e9mak\u00f6r. Pl. csak el kell t\u00e1vol\u00edtani, vagy ki kell \"csillagozni\" a szem\u00e9lyek neveit, lakc\u00edm\u00e9b\u0151l az utca-h\u00e1zsz\u00e1mot, telefonsz\u00e1mokat, e-mail c\u00edmet, \u00e9s meg is vagyunk. P\u00e9ld\u00e1ul a bemenet\u00fcnk els\u0151 sor\u00e1ra ez lenne a kimenet:

                  \"***\",\"***\",\"Benton, John B Jr\",\"***\",\"New Orleans \",\"Orleans\",\"LA\",\"70116\",\"***\",\"***\",\"30\",\"65\",\"Heart-related\",\"***\"\n

                  De ez kor\u00e1nt sincs \u00edgy, k\u00fcl\u00f6n\u00f6sen, ha igaz\u00e1n sok adatr\u00f3l van sz\u00f3. Gondoljunk arra, hogy van egy kisebb falu, ahol nem laknak sokan. Tegy\u00fck fel, hogy az egyik fenti m\u00f3don anonimiz\u00e1lt szem\u00e9ly \u00e9letkora 14 \u00e9v, de rendk\u00edv\u00fcl t\u00fals\u00falyos, 95 kg. Ez egy ritka \"kombin\u00e1ci\u00f3\", m\u00e1s szem\u00e9ly j\u00f3 es\u00e9llyel nem \u00e9l ilyen param\u00e9terekkel a faluban. Ha az \u0151 oszt\u00e1lyt\u00e1rsai k\u00f6z\u00fcl (nyolcadikos, hiszen 14 \u00e9ves) valaki megn\u00e9zi az \"anonimiz\u00e1lt\" adatokat, tudni fogja ki \u0151 (nincs m\u00e1s ennyire t\u00fals\u00falyos nyolcadikos az iskol\u00e1ban), beazonos\u00edtja a szem\u00e9lyt. \u00cdgy pl. tudni fogja, milyen betegs\u00e9ge van az illet\u0151nek. Tanuls\u00e1g: az adatok \u00f6sszef\u00fcgg\u00e9sben \u00e1rulkod\u00f3k lehetnek.

                  Mi a megold\u00e1s? A v\u00e1rost, az \u00e9letkort \u00e9s a testt\u00f6meget nem t\u00f6r\u00f6lhetj\u00fck/csillagozhatjuk, mert ezekre vonatkoz\u00f3an kell kimutat\u00e1st k\u00e9sz\u00edteni. Egy tipikus megold\u00e1s: nem pontos \u00e9letkort/tests\u00falyt adunk meg az anonimiz\u00e1l\u00e1st k\u00f6vet\u0151en, hanem s\u00e1vokat (vagyis \u00e1ltal\u00e1nos\u00edtjuk az adatokat): pl. a fenti szem\u00e9ly eset\u00e9ben az \u00e9letkora 10..20 \u00e9v, tests\u00falya 80..100 kg, \u00e9s ezeket adjuk meg erre a szem\u00e9lyre vonatkoz\u00f3an a kimeneti f\u00e1jlban. \u00cdgy m\u00e1r nem lehet beazonos\u00edtani a szem\u00e9lyeket. Ezt a technik\u00e1t mi is fogjuk k\u00e9s\u0151bb alkalmazni.

                  "},{"location":"labor/6-tervezesi-mintak/#kiindulo-kovetelmenyek","title":"Kiindul\u00f3 k\u00f6vetelm\u00e9nyek","text":"

                  Az alkalmaz\u00e1ssal szemben t\u00e1masztott kiindul\u00f3 k\u00f6vetelm\u00e9nyek:

                  1. Egy adott \u00fcgyf\u00e9lt\u0151l kapott f\u00e1jlokat (mindnek ugyanaz a form\u00e1tuma) kell ugyanazzal az anonimiz\u00e1l\u00f3 algoritmussal, ugyanabba a kimeneti form\u00e1tumba konvert\u00e1lni. Az anonimiz\u00e1l\u00e1s egyszer\u0171en a keresztn\u00e9v \u00e9s vezet\u00e9kn\u00e9v \"kicsillagoz\u00e1s\u00e1b\u00f3l\" \u00e1lljon.
                  2. Sz\u00fcks\u00e9g van n\u00e9mi adattiszt\u00edt\u00e1sra. A bemeneti adatokban a v\u00e1rost tartalmaz\u00f3 oszlop elej\u00e9n/v\u00e9g\u00e9n lehetnek felesleges _ \u00e9s # karakterek, ezeket el kell t\u00e1vol\u00edtani (trim m\u0171velet).
                  3. Ki kell \u00edrni minden sor feldolgoz\u00e1sa ut\u00e1n a konzolra, hogy a sor feldolgoz\u00e1sa megt\u00f6rt\u00e9nt, ill. a minden adat feldolgoz\u00e1s ut\u00e1n n\u00e9mi \u00f6sszes\u00edt\u0151 inform\u00e1ci\u00f3t (Summary) is meg kell jelen\u00edteni: h\u00e1ny sort dolgoztunk fel, \u00e9s mennyin\u00e9l kellett a v\u00e1rosnevet trimmelni.
                  4. L\u00e9nyeges szempont: az alkalmaz\u00e1sra csak r\u00f6vid id\u0151re lesz sz\u00fcks\u00e9g, nem a k\u00edv\u00e1njuk k\u00e9s\u0151bbiekben b\u0151v\u00edteni.

                  Megjegyz\u00e9s: annak \u00e9rdek\u00e9ben, hogy a k\u00f3dban kevesebb mez\u0151vel kelljen dolgozni, \u00e9s a kimenet is \u00e1tl\u00e1that\u00f3bb legyen, elhagyunk m\u00e9g n\u00e9h\u00e1ny mez\u0151t a feldolgoz\u00e1s sor\u00e1n.

                  P\u00e9ldak\u00e9nt a bemeneti f\u00e1jlunk els\u0151 sor\u00e1ra a v\u00e1rt kimenet:

                  ***; ***; LA; New Orleans; 30; 65; Heart-related\n
                  "},{"location":"labor/6-tervezesi-mintak/#1-megoldas-minden-egyben-1-startstart","title":"1. Megold\u00e1s - minden egyben (1-Start/Start)","text":"

                  A Visual Studio Solution Explorer\u00e9ben mapp\u00e1kat l\u00e1tunk, 1-t\u0151l 4-ig sz\u00e1mmal kezd\u0151d\u0151 n\u00e9vvel. Ezek az egyes munkaiter\u00e1ci\u00f3khoz tartoz\u00f3 megold\u00e1sokat tartalmazz\u00e1k. Az els\u0151 k\u00f6r\u00f6s megold\u00e1s az \"1-Start\" mapp\u00e1ban, \"Start\" projektn\u00e9v alatt tal\u00e1lhat\u00f3. N\u00e9zz\u00fck meg a projektben tal\u00e1lhat\u00f3 f\u00e1jlokat:

                  • Person.cs - Egy szem\u00e9ly sz\u00e1munkra \u00e9rdekes adatai tartalmazza, ennek objektumaiba olvassuk be egy-egy szem\u00e9ly adatait.
                  • Program.cs - Ennek Main f\u00fcggv\u00e9ny\u00e9ben van megval\u00f3s\u00edtva minden logika, k\u00f3dmegjegyz\u00e9sekkel \"elv\u00e1lasztva\". Amennyiben kicsit is bonyolultabb\u00e1 v\u00e1lik a logika, m\u00e1r egy-k\u00e9t nap (\u00f3ra?) ut\u00e1n mi magunk is csak nehezen fogjuk \u00e1ttekinteni \u00e9s meg\u00e9rteni a saj\u00e1t k\u00f3dunkat. Ezt a megold\u00e1st ne is n\u00e9zz\u00fck.

                  \u00d6sszeg\u00e9sz\u00e9ben minden nagyon egyszer\u0171 a megold\u00e1sban, hiszen a k\u00f3dnak nem j\u00f3solunk hossz\u00fa j\u00f6v\u0151t. De az egy f\u00fcggv\u00e9nybe \u00f6nt\u00f6tt \"szkriptszer\u0171\", \"minden egybe\" megold\u00e1s ekkor sem j\u00f3 ir\u00e1ny, nagyon neh\u00e9zz\u00e9 teszi a k\u00f3d \u00e1tl\u00e1t\u00e1s\u00e1t, meg\u00e9rt\u00e9s\u00e9t. Ne is n\u00e9zz\u00fck ezt tov\u00e1bb.

                  "},{"location":"labor/6-tervezesi-mintak/#2-megoldas-2-organizedtofunctionsorganizedtofunctions-1","title":"2. Megold\u00e1s (2-OrganizedToFunctions/OrganizedToFunctions-1)","text":"

                  T\u00e9rj\u00fcnk \u00e1t Visual Studioban a \"2-OrganizedToFunctions\" mapp\u00e1ban tal\u00e1lhat\u00f3 \"OrganizedToFunctions-1\" projektben tal\u00e1lhat\u00f3 megold\u00e1sra. Ez m\u00e1r sokkal szimpatikusabb, mert f\u00fcggv\u00e9nyekre bontottuk a logik\u00e1t. Tekints\u00fck \u00e1t a k\u00f3dot r\u00f6viden:

                  Anonymizer.cs

                  • A Run f\u00fcggv\u00e9ny a \"gerince\", ez tartalmazza a vez\u00e9rl\u00e9si logik\u00e1t, ez h\u00edvja az egyes l\u00e9p\u00e9sek\u00e9rt felel\u0151s f\u00fcggv\u00e9nyeket.
                  • ReadFromInput m\u0171velet: beolvassa a forr\u00e1sf\u00e1jlt, minden sorhoz k\u00e9sz\u00edt egy Person objektumot, \u00e9s visszat\u00e9r a beolvasott Person objektumok list\u00e1j\u00e1val.
                  • TrimCityNames: Az adattiszt\u00edt\u00e1st v\u00e9gzi (v\u00e1rosnevek trimmel\u00e9se).
                  • Anonymize: Minden egyes beolvasott Person objektummal megh\u00edv\u00e1sra ker\u00fcl, \u00e9s feladata, hogy visszaadjon egy \u00faj Person objektumot, mely m\u00e1r az anonimiz\u00e1lt adatokat tartalmazza.
                  • WriteToOutput: m\u00e1r anonimiz\u00e1lt Person objektumokat ki\u00edrja a kimeneti f\u00e1jlba.
                  • PrintSummary: ki\u00edrja az \u00f6sszes\u00edt\u00e9st a feldolgoz\u00e1s v\u00e9g\u00e9n a konzolra.

                  Program.cs

                  • L\u00e9trehoz egy Anonymizer objektumot \u00e9s a Run h\u00edv\u00e1s\u00e1val futtatja. L\u00e1that\u00f3, hogy az anonimiz\u00e1l\u00e1s sor\u00e1n maszkol\u00e1sra haszn\u00e1lt stringet konstruktor param\u00e9terben kell megadni.

                  Pr\u00f3b\u00e1ljuk ki, futtassuk! Ehhez a \"OrganizedToFunctions-1\" legyen Visual Studioban a startup projekt (jobb katt rajta, \u00e9s Set as Startup Project), majd futtassuk:

                  A kimeneti f\u00e1jt f\u00e1jlkezel\u0151ben tudjuk megn\u00e9zni, az \"OrganizedToFunctions-1\\bin\\Debug\\net8.0\\\" vagy hasonl\u00f3 nev\u0171 mapp\u00e1ban tal\u00e1ljuk, \"us-500.processed.txt\" n\u00e9ven. Nyissuk meg, \u00e9s vess\u00fcnk egy pillant\u00e1st az adatokra.

                  "},{"location":"labor/6-tervezesi-mintak/#a-megoldas-ertekelese","title":"A megold\u00e1s \u00e9rt\u00e9kel\u00e9se","text":"
                  • A megold\u00e1s alapvet\u0151en j\u00f3l struktur\u00e1lt, k\u00f6nnyen meg\u00e9rthet\u0151.
                  • K\u00f6veti a KISS (Keep It Stupid Simple) elvet, nem haszn\u00e1l felesleges bonyol\u00edt\u00e1sokat. Ez \u00edgy j\u00f3, hiszen nem mer\u00fcltek fel potenci\u00e1lis j\u00f6v\u0151beli tov\u00e1bbfejleszt\u00e9si ig\u00e9nyek, nem kell k\u00fcl\u00f6nb\u00f6z\u0151 form\u00e1tumokat, logik\u00e1kat stb. t\u00e1mogatni.
                  • A megold\u00e1sunk ugyanakkor nem k\u00f6veti az egyik legalapvet\u0151bb \u00e9s legh\u00edresebb tervez\u00e9si elvet, mely Single Responsibility Principle (r\u00f6viden SRP) n\u00e9ven k\u00f6zismert. Ez - n\u00e9mi egyszer\u0171s\u00edt\u00e9ssel \u00e9lve - azt v\u00e1rja el, hogy egy oszt\u00e1lynak egy felel\u0151ss\u00e9ge legyen (alapvet\u0151en egy dologgal foglalkozzon).

                    • K\u00e9ts\u00e9gtelen, hogy az Anonymizer oszt\u00e1lyunknak sz\u00e1mos felel\u0151ss\u00e9ge van: bemenet feldolgoz\u00e1sa, adattiszt\u00edt\u00e1s, anonimiz\u00e1l\u00e1s, kimenet el\u0151\u00e1ll\u00edt\u00e1sa stb.
                    • Ez a probl\u00e9ma n\u00e1lunk az\u00e9rt nem felt\u0171n\u0151, illetve az\u00e9rt nem okoz gondot, mert mindegyik felel\u0151ss\u00e9g megval\u00f3s\u00edt\u00e1sa egyszer\u0171, \"belef\u00e9rt\" egy-egy r\u00f6videbb f\u00fcggv\u00e9nybe. De ha b\u00e1rmelyik is \u00f6sszetettebb lenne, t\u00f6bb f\u00fcggv\u00e9nyben lenn\u00e9nek megval\u00f3s\u00edtva, akkor mindenk\u00e9ppen k\u00fcl\u00f6n oszt\u00e1lyba illene szervezni.
                    Mi\u00e9rt probl\u00e9ma, ha egy oszt\u00e1lynak t\u00f6bb felel\u0151ss\u00e9ge van? *
                    • Nehezebb meg\u00e9rteni a m\u0171k\u00f6d\u00e9s\u00e9t, mert nem egy dologra f\u00f3kusz\u00e1l.
                    • Ha b\u00e1rmelyik felel\u0151ss\u00e9g ment\u00e9n is j\u00f6n be v\u00e1ltoz\u00e1si ig\u00e9ny, egy nagy, sok mindennel foglalkoz\u00f3 oszt\u00e1lyt kell v\u00e1ltoztatni \u00e9s \u00fajra tesztelni.
                  • A megold\u00e1shoz lehet \u00edrni automatiz\u00e1lt integr\u00e1ci\u00f3s (input-output) teszteket, de \"igazi\" egys\u00e9gteszteket nem.

                  "},{"location":"labor/6-tervezesi-mintak/#3-megoldas-organizedtofunctions-2-twoalgorithms","title":"3. Megold\u00e1s (OrganizedToFunctions-2-TwoAlgorithms)","text":"

                  A kor\u00e1bbi \"tervekkel\" ellent\u00e9tben \u00faj felhaszn\u00e1l\u00f3i ig\u00e9nyek mer\u00fcltek fel. Az \u00fcgyfel\u00fcnk meggondolta mag\u00e1t, egy m\u00e1sik adathalmazn\u00e1l m\u00e1sf\u00e9le anonimiz\u00e1l\u00f3 algoritmus megval\u00f3s\u00edt\u00e1s\u00e1t k\u00e9ri: a szem\u00e9lyek \u00e9letkor\u00e1t kell s\u00e1vosan menteni, nem der\u00fclhet ki a szem\u00e9lyek pontos \u00e9letkora. Az egyszer\u0171s\u00e9g \u00e9rdek\u00e9ben ez esetben a szem\u00e9lyek nev\u00e9t nem fogjuk anonimiz\u00e1lni, \u00edgy tekints\u00fck ezt egyfajta \"pszeudo\" anonimiz\u00e1l\u00e1snak (ett\u0151l m\u00e9g lehet \u00e9rtelme, csak nem teljesen korrekt ezt anonimiz\u00e1l\u00e1snak nevezni).

                  A megold\u00e1sunkat - mely egyar\u00e1nt t\u00e1mogatja a r\u00e9gi \u00e9s az \u00faj algoritmust (egyszerre csak az egyiket) - a VS solution OrganizedToFunctions-2-TwoAlgorithms nev\u0171 projektj\u00e9ben tal\u00e1ljuk. N\u00e9zz\u00fcnk r\u00e1 az Anonymizer oszt\u00e1lyra, a megold\u00e1s alapelve (ezeket tekints\u00fck \u00e1t a k\u00f3dban):

                  • Bevezett\u00fcnk egy AnonymizerMode enum t\u00edpust, mely meghat\u00e1rozza, hogy melyik \u00fczemm\u00f3dban (algoritmussal) haszn\u00e1ljuk az Anonymizer oszt\u00e1lyt.
                  • Az Anonymizer oszt\u00e1lynak k\u00e9t anonimiz\u00e1l\u00f3 m\u0171velete van: Anonymize_MaskName, Anonymize_AgeRange
                  • Az Anonymizer oszt\u00e1ly a _anonymizerMode tagj\u00e1ban t\u00e1rolja, melyik algoritmust kell haszn\u00e1lni: a k\u00e9t \u00fczemm\u00f3dhoz k\u00e9t k\u00fcl\u00f6n konstruktort vezett\u00fcnk be, ezek \u00e1ll\u00edtj\u00e1k be az _anonymizerMode \u00e9rt\u00e9k\u00e9t.
                  • Az Anonymizer oszt\u00e1ly t\u00f6bb helyen is megvizsg\u00e1lja (pl. Run, GetAnonymizerDescription m\u0171veletek), hogy mi az _anonymizerMode \u00e9rt\u00e9ke, \u00e9s ennek f\u00fcggv\u00e9ny\u00e9ben el\u00e1gazik.
                  • A GetAnonymizerDescription-ben az\u00e9rt kell ezt megtenni, mert ennek a m\u0171veletnek a feladata az anonimiz\u00e1l\u00f3 algoritmusr\u00f3l egy egysoros le\u00edr\u00e1s el\u0151\u00e1ll\u00edt\u00e1sa, melyet a feldolgoz\u00e1s v\u00e9g\u00e9n a \"summary\"-ben megjelen\u00edt. N\u00e9zz\u00fcnk r\u00e1 a PintSummary k\u00f3dj\u00e1ra, ez a m\u0171velet h\u00edvja. Pl. ez jelenik meg a konzolon \u00f6sszefoglal\u00f3k\u00e9nt, ha \u00e9letkor anonimiz\u00e1l\u00f3t haszn\u00e1lunk 20-as range-dzsel:

                    Summary - Anonymizer (Age anonymizer with range size 20): Persons: 500, trimmed: 2

                  "},{"location":"labor/6-tervezesi-mintak/#a-megoldas-ertekelese_1","title":"A megold\u00e1s \u00e9rt\u00e9kel\u00e9se","text":"

                  \u00d6sszeg\u00e9sz\u00e9ben megold\u00e1sunk k\u00f3dmin\u0151s\u00e9g tekintet\u00e9ben a kor\u00e1bbin\u00e1l rosszabb lett. Kor\u00e1bban nem volt probl\u00e9ma, hogy anonimiz\u00e1l\u00f3 algoritmusok tekintet\u00e9ben nem volt kiterjeszthet\u0151, hiszen nem volt r\u00e1 ig\u00e9ny. De ha m\u00e1r egyszer felmer\u00fclt az ig\u00e9ny \u00faj algoritmus bevezet\u00e9s\u00e9re, akkor hiba ebben a tekintetben nem kiterjeszthet\u0151v\u00e9 tenni a megold\u00e1sunkat: ett\u0151l kezdve sokkal ink\u00e1bb sz\u00e1m\u00edtunk arra, hogy \u00fajabb tov\u00e1bbi algoritmusokat kell bevezetni a j\u00f6v\u0151ben.

                  Mi\u00e9rt \u00e1ll\u00edtjuk azt, hogy a k\u00f3dunk nem kiterjeszthet\u0151, amikor \"csak\" egy \u00faj enum \u00e9rt\u00e9ket, \u00e9s egy-egy plusz if/switch \u00e1gat kell a k\u00f3d n\u00e9h\u00e1ny pontj\u00e1ra bevezetni, amikor \u00faj algoritmust kell majd bevezetni?

                  Open/Closed principle Kulcsfontoss\u00e1g\u00fa, hogy egy oszt\u00e1lyt akkor tekint\u00fcnk kiterjeszthet\u0151nek, ha annak b\u00e1rmilyen nem\u0171 m\u00f3dos\u00edt\u00e1sa n\u00e9lk\u00fcl, puszt\u00e1n a k\u00f3d kiterjeszt\u00e9s\u00e9vel/b\u0151v\u00edt\u00e9s\u00e9vel lehet \u00faj viselked\u00e9st (eset\u00fcnkben \u00faj algoritmust) bevezetni. Vagyis eset\u00fcnkben az Anonymizer k\u00f3dj\u00e1hoz nem szabadna hozz\u00e1ny\u00falni, ami egy\u00e9rtelm\u0171en nem teljes\u00fcl. Ez a h\u00edres Open/Closed principle/elv: the class should be Open for Extension, Closed for Modification. A k\u00f3d m\u00f3dos\u00edt\u00e1sa az\u00e9rt probl\u00e9ma, mert annak sor\u00e1n j\u00f3 es\u00e9llyel \u00faj bugokat vezet\u00fcnk be, ill. a m\u00f3dos\u00edtott k\u00f3dot mindig \u00fajra kell tesztelni, ez pedig jelent\u0151s id\u0151/k\u00f6lts\u00e9gr\u00e1ford\u00edt\u00e1si ig\u00e9nyt jelenthet.

                  Mi is a pontos c\u00e9l, \u00e9s hogyan \u00e9rj\u00fck ezt el? Vannak olyan r\u00e9szek az oszt\u00e1lyunkban, melyeket nem szeretn\u00e9nk be\u00e9getni:

                  • Ezek nem adatok, hanem viselked\u00e9sek (k\u00f3d, logika).
                  • Nem if/switch utas\u00edt\u00e1sokkal oldjuk meg: \"kiterjeszt\u00e9si pontokat\" vezet\u00fcnk be, \u00e9s valamilyen m\u00f3don megoldjuk, hogy ezekben \"tetsz\u0151leges\" k\u00f3d lefuthasson.
                  • Ezek v\u00e1ltoz\u00f3/esetf\u00fcgg\u0151 r\u00e9szek k\u00f3dj\u00e1t m\u00e1s oszt\u00e1lyokba tessz\u00fck (az oszt\u00e1lyunk szempontj\u00e1b\u00f3l \"lecser\u00e9lhet\u0151\" m\u00f3don)!

                  Note

                  Ne gondoljunk semmif\u00e9le var\u00e1zslatra, a m\u00e1r ismert eszk\u00f6z\u00f6ket fogjuk erre haszn\u00e1lni: \u00f6r\u00f6kl\u00e9st absztrakt/virtu\u00e1lis f\u00fcggv\u00e9nyekkel, vagy interf\u00e9szeket, vagy delegate-eket.

                  Keress\u00fck meg azokat a r\u00e9szeket, melyek esetf\u00fcgg\u0151, v\u00e1ltoz\u00f3 logik\u00e1k, \u00edgy nem j\u00f3 be\u00e9getni az Anonymizer oszt\u00e1lyba:

                  • Az egyik maga az anonimiz\u00e1l\u00e1si logika: Anonymize_MaskName/Anonymize_AgeRange
                  • A m\u00e1sik a GetAnonymizerDescription

                  Ezeket kell lev\u00e1lasztani az oszt\u00e1lyr\u00f3l, ezekben a pontokban kell kiterjeszthet\u0151v\u00e9 tenni az oszt\u00e1lyt. Az al\u00e1bbi \u00e1bra illusztr\u00e1lja a c\u00e9lt \u00e1ltal\u00e1noss\u00e1g\u00e1ban *:

                  Az \u00e1ltal\u00e1nos megold\u00e1si elv illusztr\u00e1l\u00e1sa

                  A h\u00e1rom konkr\u00e9t tervez\u00e9si mint\u00e1t, ill. technik\u00e1t n\u00e9z\u00fcnk meg a fentiek megval\u00f3s\u00edt\u00e1s\u00e1ra:

                  • Template Method tervez\u00e9si minta
                  • Strategy tervez\u00e9si minta (Dependency Injectionnel egyetemben)
                  • Delegate (opcion\u00e1lisan Lambda kifejez\u00e9ssel)

                  Val\u00f3j\u00e1ban mind haszn\u00e1ltuk m\u00e1r a tanulm\u00e1nyaink sor\u00e1n, de most m\u00e9lyebben megismerked\u00fcnk vel\u00fck, \u00e9s \u00e1tfog\u00f3bban be fogjuk gyakorolni ezek alkalmaz\u00e1s\u00e1t. Az els\u0151 kett\u0151t a labor keret\u00e9ben, a harmadikat pedig majd egy kapcsol\u00f3d\u00f3 h\u00e1zi feladat keret\u00e9ben.

                  "},{"location":"labor/6-tervezesi-mintak/#4-megoldas-3-templatemethodtemplatemethod-1","title":"4. Megold\u00e1s (3-TemplateMethod/TemplateMethod-1)","text":"

                  Ebben a l\u00e9p\u00e9sben a Template Method tervez\u00e9si minta alkalmaz\u00e1s\u00e1val fogjuk a megold\u00e1sunkat a sz\u00fcks\u00e9ges pontokban kiterjeszthet\u0151v\u00e9 tenni.

                  Note

                  A minta neve \"megt\u00e9veszt\u0151\": semmi k\u00f6ze nincs a C++-ban tanult sablonmet\u00f3dusokhoz!

                  Template Method alap\u00fa megold\u00e1s oszt\u00e1lydiagram

                  Az al\u00e1bbi UML oszt\u00e1lydiagram illusztr\u00e1lja a Template Method alap\u00fa megold\u00e1st, a l\u00e9nyegre f\u00f3kusz\u00e1lva:

                  A mint\u00e1ban a k\u00f6vetkez\u0151 elvek ment\u00e9n val\u00f3sul meg a \"v\u00e1ltozatlan\" \u00e9s \"v\u00e1ltoz\u00f3\" r\u00e9szek k\u00fcl\u00f6nv\u00e1laszt\u00e1sa (\u00e9rdemes a fenti oszt\u00e1lydiagram alapj\u00e1n - a p\u00e9ld\u00e1nkra vet\u00edtve - ezeket meg\u00e9rteni):

                  • A \"k\u00f6z\u00f6s/v\u00e1ltozatlan\" r\u00e9szeket egy \u0151soszt\u00e1lyba tessz\u00fck.
                  • Ebben a kiterjeszt\u00e9si pontokat absztrakt/virtu\u00e1lis f\u00fcggv\u00e9nyek bevezet\u00e9se jelenti, ezeket h\u00edvjuk a kiterjeszt\u00e9si pontokban.
                  • Ezek esetf\u00fcgg\u0151 megval\u00f3s\u00edt\u00e1sa a lesz\u00e1rmazott oszt\u00e1lyokba ker\u00fcl.

                  A j\u00f3l ismert \"tr\u00fckk\" a dologban az, hogy amikor az \u0151s megh\u00edvja az absztrakt/virtu\u00e1lis f\u00fcggv\u00e9nyeket, akkor a lesz\u00e1rmazottb\u00e9li, esetf\u00fcgg\u0151 k\u00f3d h\u00edv\u00f3dik meg.

                  A k\u00f6vetkez\u0151kben a kor\u00e1bbi enum, illetve if/switch alap\u00fa megold\u00e1st alak\u00edtjuk \u00e1t Template Method alap\u00fara (ebben m\u00e1r nem lesz enum). Egy \u0151soszt\u00e1lyt \u00e9s k\u00e9t, algoritmusf\u00fcgg\u0151 lesz\u00e1rmazottat vezet\u00fcnk be.

                  Alak\u00edtsuk \u00e1t a k\u00f3dunkat ennek megfelel\u0151en. A VS solution-ben a \"3-TemplateMethod\" mapp\u00e1ban a \"TemplateMethod-0-Begin\" projekt tartalmazza a kor\u00e1bbi megold\u00e1sunk k\u00f3dj\u00e1t (annak \"m\u00e1solat\u00e1t\"), ebben a projektben dolgozzunk:

                  1. Nevezz\u00fck \u00e1t az Anonymizer oszt\u00e1lyt AnonymizerBase-re (pl. az oszt\u00e1ly nev\u00e9re \u00e1llva a forr\u00e1sf\u00e1jlban \u00e9s F2-t nyomva).
                  2. Vegy\u00fcnk fel az projektbe egy NameMaskingAnonymizer \u00e9s egy AgeAnonymizer oszt\u00e1lyt (projekten jobb katt, Add/Class).
                  3. Sz\u00e1rmaztassuk az AnonymizerBase-b\u0151l \u0151ket
                  4. Az AnonymizerBase-b\u0151l mozgassuk \u00e1t a NameMaskingAnonymizer-be az ide tartoz\u00f3 r\u00e9szeket:

                    1. A _mask tagv\u00e1ltoz\u00f3t.
                    2. A string inputFileName, string mask param\u00e9terez\u00e9s\u0171 konstruktort, \u00e1tnevezve NameMaskingAnonymizer-re,
                      1. _anonymizerMode = AnonymizerMode.Name; sort t\u00f6r\u00f6lve,
                      2. a this konstruktorh\u00edv\u00e1s helyett base konstruktorh\u00edv\u00e1ssal.

                        A konstruktor k\u00f3dja
                        public NameMaskingAnonymizer(string inputFileName, string mask): base(inputFileName)\n{\n    _mask = mask;\n}\n
                  5. Az AnonymizerBase-b\u0151l mozgassuk \u00e1t az AgeAnonymizer-be az ide tartoz\u00f3 r\u00e9szeket:

                    1. A _rangeSize tagv\u00e1ltoz\u00f3t.
                    2. A string inputFileName, string rangeSize param\u00e9terez\u00e9s\u0171 konstruktort, \u00e1tnevezve AgeAnonymizer-re,
                      1. _anonymizerMode = AnonymizerMode.Age; sort t\u00f6r\u00f6lve,
                      2. a this konstruktorh\u00edv\u00e1s helyett base konstruktorh\u00edv\u00e1ssal.

                        A konstruktor k\u00f3dja
                        public AgeAnonymizer(string inputFileName, int rangeSize): base(inputFileName)\n{\n    _rangeSize = rangeSize;\n}\n
                  6. Az AnonymizerBase-ben:

                    1. T\u00f6r\u00f6lj\u00fck az AnonymizerMode enum t\u00edpust.
                    2. T\u00f6r\u00f6lj\u00fck a _anonymizerMode tagot.

                  Keress\u00fck meg azokat a r\u00e9szeket, melyek esetf\u00fcgg\u0151, v\u00e1ltoz\u00f3 logik\u00e1k, \u00edgy nem akarjuk be\u00e9getni az \u00fajrafelhaszn\u00e1lhat\u00f3nak sz\u00e1nt AnonymizerBase oszt\u00e1lyba:

                  • Az egyik az Anonymize_MaskName/Anonymize_AgeRange,
                  • a m\u00e1sik a GetAnonymizerDescription.

                  A mint\u00e1t k\u00f6vetve ezekre az \u0151sben absztrakt (vagy esetleg virtu\u00e1lis) f\u00fcggv\u00e9nyeket vezet\u00fcnk be, \u00e9s ezeket h\u00edvjuk, az esetf\u00fcgg\u0151 implement\u00e1ci\u00f3ikat pedig a lesz\u00e1rmazott oszt\u00e1lyokba tessz\u00fck (override):

                  1. Tegy\u00fck az AnonymizerBase oszt\u00e1lyt absztraktt\u00e1 (a class el\u00e9 abstract kulcssz\u00f3).
                  2. Vezess\u00fcnk be az AnonymizerBase-ben egy

                    protected abstract Person Anonymize(Person person);\n

                    m\u0171veletet (ennek feladata lesz az anonimiz\u00e1l\u00e1s v\u00e9grehajt\u00e1sa).

                  3. Az Anonymize_MaskName m\u0171veletet mozgassuk \u00e1t a NameMaskingAnonymizer oszt\u00e1lyba, \u00e9s alak\u00edtsuk \u00e1t a szignat\u00far\u00e1j\u00e1t \u00fagy, hogy override-olja az \u0151sbeli Anonymize absztrakt f\u00fcggv\u00e9nyt:

                    protected override Person Anonymize(Person person)\n{\n    return new Person(_mask, _mask, person.CompanyName,\n        person.Address, person.City, person.State, person.Age, person.Weight, person.Decease);\n}\n

                    A f\u00fcggv\u00e9ny t\u00f6rzs\u00e9t csak annyiban kell \u00e1t\u00edrni, hogy ne a megsz\u00fcntetett mask param\u00e9tert, hanem a _mask tagv\u00e1ltoz\u00f3t haszn\u00e1lja.

                  4. Az el\u0151z\u0151 l\u00e9p\u00e9ssel teljesen anal\u00f3g m\u00f3don az Anonymize_AgeRange m\u0171veletet mozgassuk \u00e1t a AgeAnonymizer oszt\u00e1lyba, \u00e9s alak\u00edtsuk \u00e1t a szignat\u00far\u00e1j\u00e1t \u00fagy, hogy override-olja az \u0151sbeli Anonymize absztrakt f\u00fcggv\u00e9nyt:

                    protected override Person Anonymize(Person person)\n{\n    ...\n}\n

                    A f\u00fcggv\u00e9ny t\u00f6rzs\u00e9t csak annyiban kell \u00e1t\u00edrni, hogy ne a megsz\u00fcntetett rangeSize param\u00e9tert, hanem a _rangeSize tagv\u00e1ltoz\u00f3t haszn\u00e1lja.

                  5. A AnonymizerBase oszt\u00e1ly Run f\u00fcggv\u00e9ny\u00e9ben az if/else kifejez\u00e9sben tal\u00e1lhat\u00f3 Anonymize h\u00edv\u00e1sokat most m\u00e1r le tudjuk cser\u00e9lni egy egyszer\u0171 absztrakt f\u00fcggv\u00e9ny h\u00edv\u00e1sra:

                    Person person;\nif (_anonymizerMode == AnonymizerMode.Name)\n    person = Anonymize_MaskName(persons[i], _mask);\nelse if (_anonymizerMode == AnonymizerMode.Age)\n    person = Anonymize_AgeRange(persons[i], _rangeSize);\nelse\n    throw new NotSupportedException(\"The requested anonymization mode is not supported.\");\n

                    helyett:

                    var person = Anonymize(persons[i]);\n

                  Az egyik kiterjeszt\u00e9si pontunkkal el is k\u00e9sz\u00fclt\u00fcnk. De maradt m\u00e9g egy, a GetAnonymizerDescription, mely kezel\u00e9se szint\u00e9n esetf\u00fcgg\u0151. Ennek \u00e1talak\u00edt\u00e1sa nagyon hasonl\u00f3 az el\u0151z\u0151 l\u00e9p\u00e9ssorozathoz:

                  1. Az AnonymizerBase oszt\u00e1ly GetAnonymizerDescription m\u0171velet\u00e9t m\u00e1soljuk \u00e1t a NameMaskingAnonymizer-be, a szignat\u00far\u00e1ba belev\u00e9ve az override kulcssz\u00f3t, a f\u00fcggv\u00e9ny t\u00f6rzs\u00e9ben csak a NameMaskingAnonymizer-re vonatkoz\u00f3 logik\u00e1t meghagyva:

                    protected override string GetAnonymizerDescription()\n{\n    return $\"NameMasking anonymizer with mask {_mask}\";\n}\n
                  2. A AnonymizerBase GetAnonymizerDescription m\u0171velet\u00e9t m\u00e1soljuk \u00e1t az AgeAnonymizer-be is, a szignat\u00far\u00e1ba belev\u00e9ve az override kulcssz\u00f3t, a f\u00fcggv\u00e9ny t\u00f6rzs\u00e9ben most csak a AgeAnonymizer-re vonatkoz\u00f3 logik\u00e1t meghagyva:

                    protected override string GetAnonymizerDescription()\n{\n    return $\"Age anonymizer with range size {_rangeSize}\";\n}\n
                  3. K\u00e9rd\u00e9s, mi legyen AnonymizerBase-ben a GetAnonymizerDescription m\u0171velettel. Ezt nem absztrakt\u00e1, hanem virtu\u00e1lis f\u00fcggv\u00e9nny\u00e9 alak\u00edtjuk, hiszen itt tudunk \u00e9rtelmes alap\u00e9rtelmezett viselked\u00e9st biztos\u00edtani: egyszer\u0171en visszaadjuk az oszt\u00e1ly nev\u00e9t (mely pl. a NameMaskingAnonymizer oszt\u00e1ly eset\u00e9ben \"NameMaskingAnonymizer\" lenne). Mindenesetre a rugalmatlan switch szerkezett\u0151l ezzel megszabadulunk:

                    protected virtual string GetAnonymizerDescription()\n{\n    return GetType().Name;\n}\n

                    Reflexi\u00f3

                    Az object \u0151sb\u0151l \u00f6r\u00f6k\u00f6lt GetType() m\u0171velettel egy Type t\u00edp\u00fas\u00fa objektumot szerz\u00fcnk az oszt\u00e1lyunkra vonatkoz\u00f3an. Ez a refelexi\u00f3 t\u00e9mak\u00f6rh\u00f6z tartozik, err\u0151l a f\u00e9l\u00e9v v\u00e9g\u00e9n fogunk el\u0151ad\u00e1son r\u00e9szletesebben tanulni.

                  Egy dolog van m\u00e1r csak h\u00e1tra: a Program.cs Main f\u00fcggv\u00e9ny\u00e9ben most az AnonymizerBase \u0151st pr\u00f3b\u00e1ljuk p\u00e9ld\u00e1nyos\u00edtani (a kor\u00e1bbi \u00e1tnevez\u00e9s miatt). Helyette a k\u00e9t lesz\u00e1rmazott valamelyik\u00e9t kellene. Pl.:

                  NameMaskingAnonymizer anonymizer = new(\"us-500.csv\", \"***\");\nanonymizer.Run();\n

                  El is k\u00e9sz\u00fclt\u00fcnk. Pr\u00f3b\u00e1ljuk ki, hogy jobban \"\u00e9rezz\u00fck\", val\u00f3ban m\u0171k\u00f6dnek az kiterjeszt\u00e9si pontok (de ha kev\u00e9s az id\u0151nk a labor sor\u00e1n, ez k\u00fcl\u00f6n\u00f6sebben nem fontos, hasonl\u00f3t m\u00e1r kor\u00e1bbi f\u00e9l\u00e9vekben C++/Java nyelvek kontextus\u00e1ban is csin\u00e1ltunk):

                  • Visual Studioban a TemplateMethod-0-Begin projekt legyen a startup projekt, ha ezt eddig m\u00e9g nem \u00e1ll\u00edtottuk be.
                  • Tegy\u00fcnk egy t\u00f6r\u00e9spontot az AnonymizerBase oszt\u00e1ly var person = Anonymize(persons[i]); sor\u00e1ra.
                  • Amikor fut\u00e1s k\u00f6zben itt meg\u00e1ll a debugger, F11-gyel l\u00e9pj\u00fcnk bele.
                  • Az tapasztaljuk, hogy a lesz\u00e1rmazott AgeAnonymizer m\u0171velete h\u00edv\u00f3dik.

                  Vethet\u00fcnk egy pillant\u00e1st a megold\u00e1s oszt\u00e1lydiagramj\u00e1ra:

                  Template Method alap\u00fa megold\u00e1s oszt\u00e1lydiagram *

                  Az eddigi munk\u00e1nk megold\u00e1sa a 3-TemplateMethod/TemplateMethod-1 projektben megtal\u00e1lhat\u00f3, ha esetleg sz\u00fcks\u00e9g lenne r\u00e1.

                  Mi\u00e9rt Template Method a minta neve *

                  A minta az\u00e9rt kapta a Template Method nevet, mert - alkalmaz\u00e1sunkat p\u00e9ldak\u00e9nt haszn\u00e1lva - a Run \u00e9s a PrintSummary olyan \"sablon met\u00f3dusok\", melyek meghat\u00e1roznak egy sablonszer\u0171 logik\u00e1t, v\u00e1zat, melyben bizonyos l\u00e9p\u00e9sek nincsenek megk\u00f6tve. Ezek \"k\u00f3dj\u00e1t\" absztrakt/virtu\u00e1lis f\u00fcggv\u00e9nyekre b\u00edzzuk, \u00e9s a lesz\u00e1rmazott oszt\u00e1lyok hat\u00e1rozz\u00e1k meg a megval\u00f3s\u00edt\u00e1sukat.

                  "},{"location":"labor/6-tervezesi-mintak/#a-megoldas-ertekelese_2","title":"A megold\u00e1s \u00e9rt\u00e9kel\u00e9se","text":"

                  Ellen\u0151rizz\u00fck a megold\u00e1st, megval\u00f3s\u00edtja-e a c\u00e9ljainkat:

                  • Az AnonymizerBase egy \u00fajrafelhaszn\u00e1lhat\u00f3(bb) oszt\u00e1ly lett.
                  • Ha \u00faj anonimiz\u00e1l\u00f3 logik\u00e1ra van sz\u00fcks\u00e9g a j\u00f6v\u0151ben, csak sz\u00e1rmaztatunk bel\u0151le. Ez nem m\u00f3dos\u00edt\u00e1s, hanem b\u0151v\u00edt\u00e9s.
                  • Ennek megfelel\u0151en teljes\u00fcl az OPEN/CLOSED elv, vagyis a k\u00f3dj\u00e1nak m\u00f3dos\u00edt\u00e1sa n\u00e9lk\u00fcl tudjuk az \u0151sben megadott k\u00e9t pontban a logik\u00e1t testre szabni, kiterjeszteni.

                  Legyen minden pontban kiterjeszthet\u0151 az oszt\u00e1lyunk?

                  Figyelj\u00fck meg, hogy nem tett\u00fcnk az AnonymizerBase minden m\u0171velet\u00e9t virtu\u00e1liss\u00e1 (\u00edgy sok pontban kiterjeszthet\u0151v\u00e9 az oszt\u00e1lyt). Csak ott tett\u00fck meg, ahol azt gondoljuk, hogy a j\u00f6v\u0151ben sz\u00fcks\u00e9g lehet a logika kiterjeszt\u00e9s\u00e9re.

                  "},{"location":"labor/6-tervezesi-mintak/#5-megoldas-3-templatemethodtemplatemethod-2-progress","title":"5. Megold\u00e1s (3-TemplateMethod/TemplateMethod-2-Progress)","text":"

                  T.f.h \u00faj - viszonylag egyszer\u0171 - ig\u00e9ny mer\u00fcl fel:

                  • A NameMaskinAnonimizer eset\u00e9n marad ugyan a kor\u00e1bbi egyszer\u0171 progress kijelz\u00e9s (minden sor ut\u00e1n ki\u00edrjuk, h\u00e1nyadikn\u00e1l tartottunk),

                    Egyszer\u0171 progress illusztr\u00e1l\u00e1sa

                  • de az AgeAnonymizer eset\u00e9n a progress kijelz\u00e9s m\u00e1s kell legyen: azt kell ki\u00edrni - minden sor ut\u00e1n friss\u00edtve -, hogy h\u00e1ny sz\u00e1zal\u00e9kn\u00e1l tart a feldolgoz\u00e1s.

                    Sz\u00e1zal\u00e9kos progress illusztr\u00e1l\u00e1sa

                    (Mivel jelenleg kev\u00e9s az adatunk (mind\u00f6ssze 500 sor), ezt a megold\u00e1sunk v\u00e9g\u00e9n nem \u00edgy l\u00e1tjuk majd, pillanatok alatt 100%-ra ugrik)

                  A megold\u00e1s nagyon egyszer\u0171: a Run m\u0171veletben sz\u00e9lesebb k\u00f6rben alkalmazva a Template Method mint\u00e1t, a progress ki\u00edr\u00e1skor is egy kiterjeszt\u00e9si pontot vezet\u00fcnk be, egy virtu\u00e1lis f\u00fcggv\u00e9nyre b\u00edzzuk a megval\u00f3s\u00edt\u00e1st.

                  Ugorjunk egyb\u0151l a k\u00e9sz megold\u00e1sra (3-TemplateMethod/TemplateMethod-2-Progress projekt):

                  • AnonymizerBase oszt\u00e1lyban \u00faj PrintProgress virtu\u00e1lis f\u00fcggv\u00e9ny (alap\u00e9rtelmez\u00e9sben nem \u00edr ki semmit)
                  • Run-ban ennek h\u00edv\u00e1sa
                  • NameMaskingAnonymizer-ben \u00e9s NameMaskingAnonymizer-ben megfelel\u0151 megval\u00f3s\u00edt\u00e1s (override)

                  Ennek egyel\u0151re k\u00fcl\u00f6n\u00f6sebb tanuls\u00e1ga nincs, de a k\u00f6vetkez\u0151 l\u00e9p\u00e9sben m\u00e1r lesz.

                  "},{"location":"labor/6-tervezesi-mintak/#6-megoldas-3-templatemethodtemplatemethod-3-progressmultiple","title":"6. Megold\u00e1s (3-TemplateMethod/TemplateMethod-3-ProgressMultiple)","text":"

                  \u00daj - \u00e9s teljesen logikus - ig\u00e9ny mer\u00fclt fel: a j\u00f6v\u0151ben b\u00e1rmely anonimiz\u00e1l\u00f3 algoritmust b\u00e1rmely progress megjelen\u00edt\u00e9ssel lehessen haszn\u00e1lni. Ez jelen pillanatban n\u00e9gy keresztkombin\u00e1ci\u00f3t jelent:

                  Anonimiz\u00e1l\u00f3 Progress N\u00e9v anonimiz\u00e1l\u00f3 Egyszer\u0171 progress N\u00e9v anonimiz\u00e1l\u00f3 Sz\u00e1zal\u00e9k progress Kor anonimiz\u00e1l\u00f3 Egyszer\u0171 progress Kor anonimiz\u00e1l\u00f3 Sz\u00e1zal\u00e9k progress

                  Ugorjunk a k\u00e9sz megold\u00e1sra (3-TemplateMethod/TemplateMethod-3-ProgressMultiple projekt). K\u00f3d helyett a Main.cd oszt\u00e1lydiagramot nyissuk meg a projektben, \u00e9s a megold\u00e1st az alapj\u00e1n tekintj\u00fck \u00e1t (vagy n\u00e9zhetj\u00fck a diagramot al\u00e1bb az \u00fatmutat\u00f3ban).

                  Template Method alap\u00fa megold\u00e1s (k\u00e9t aspektus) oszt\u00e1lydiagram

                  \u00c9rezhet\u0151, hogy valami \"baj van\", minden keresztkombin\u00e1ci\u00f3nak k\u00fcl\u00f6n lesz\u00e1rmazottat kellett l\u00e9trehozni. S\u0151t, a k\u00f3dduplik\u00e1ci\u00f3 cs\u00f6kkent\u00e9s\u00e9re m\u00e9g plusz, k\u00f6ztes oszt\u00e1lyok is vannak a hierarchi\u00e1ban. R\u00e1ad\u00e1sul:

                  • Ha a j\u00f6v\u0151ben \u00faj anonimiz\u00e1l\u00f3 algoritmust vezet\u00fcnk be, annyi \u00faj oszt\u00e1lyt kell \u00edrni (legal\u00e1bb), ah\u00e1ny progress t\u00edpust t\u00e1mogatunk.
                  • Ha a j\u00f6v\u0151ben \u00faj progress t\u00edpust vezet\u00fcnk be, annyi \u00faj oszt\u00e1lyt kell \u00edrni (legal\u00e1bb), ah\u00e1ny anonimiz\u00e1l\u00f3 t\u00edpust t\u00e1mogatunk.

                  Mi okozta a probl\u00e9m\u00e1t? Az, hogy az oszt\u00e1lyunk viselked\u00e9s\u00e9t t\u00f6bb aspektus/dimenzi\u00f3 ment\u00e9n (p\u00e9ld\u00e1nkban az anonimiz\u00e1l\u00e1s \u00e9s progress) kell kiterjeszthet\u0151v\u00e9 tenni, \u00e9s ezeket sok keresztkombin\u00e1ci\u00f3ban kell t\u00e1mogatni. Ha \u00fajabb aspektusok ment\u00e9n kellene ezt megtenni (pl. beolvas\u00e1s m\u00f3dja, kimenet gener\u00e1l\u00e1sa), akkor a probl\u00e9ma exponenci\u00e1lisan tov\u00e1bb \"robbanna\". Ilyen esetekben a Template Method tervez\u00e9si minta nem alkalmazhat\u00f3.

                  "},{"location":"labor/6-tervezesi-mintak/#7-megoldas-4-strategystrategy-1","title":"7. Megold\u00e1s (4-Strategy/Strategy-1)","text":"

                  Ebben a l\u00e9p\u00e9sben a Strategy tervez\u00e9si minta alkalmaz\u00e1s\u00e1val fogjuk a kezdeti megold\u00e1sunkat a sz\u00fcks\u00e9ges pontokban kiterjeszthet\u0151v\u00e9 tenni. A mint\u00e1ban a k\u00f6vetkez\u0151 elvek ment\u00e9n val\u00f3sul meg a \"v\u00e1ltozatlan/\u00fajrafelhaszn\u00e1lhat\u00f3\" \u00e9s \"v\u00e1ltoz\u00f3\" r\u00e9szek k\u00fcl\u00f6nv\u00e1laszt\u00e1sa:

                  • A \"k\u00f6z\u00f6s/v\u00e1ltozatlan\" r\u00e9szeket egy adott oszt\u00e1lyba tessz\u00fck (de ez most nem egy \"\u0151soszt\u00e1ly\" lesz).
                  • A Template Methoddal szemben nem \u00f6r\u00f6kl\u00e9st, hanem kompoz\u00edci\u00f3t (tartalmaz\u00e1st) alkalmazunk: interf\u00e9szk\u00e9nt tartalmazott m\u00e1s objektumokra b\u00edzzuk a viselked\u00e9s megval\u00f3s\u00edt\u00e1s\u00e1t a kiterjeszt\u00e9si pontokban (\u00e9s nem absztrakt/virtu\u00e1lis f\u00fcggv\u00e9nyekre).
                  • Mindezt az oszt\u00e1ly viselked\u00e9s\u00e9nek minden olyan aspektus\u00e1ra/dimenzi\u00f3j\u00e1ra, melyet lecser\u00e9lhet\u0151v\u00e9/b\u0151v\u00edthet\u0151v\u00e9 szeretn\u00e9nk tenni, egym\u00e1st\u00f3l f\u00fcggetlen\u00fcl megtessz\u00fck. Mint l\u00e1tni fogjuk, ezzel az el\u0151z\u0151 fejezetben tapasztalt kombinatorikus robban\u00e1s elker\u00fclhet\u0151.

                  Ez sokkal egyszer\u0171bb a gyakorlatban, mint amilyennel le\u00edrva \u00e9rz\u0151dik (m\u00e1r haszn\u00e1ltuk is p\u00e1rszor kor\u00e1bbi tanulm\u00e1nyaink sor\u00e1n). \u00c9rts\u00fck meg a p\u00e9ld\u00e1nkra vet\u00edtve.

                  A k\u00f6vetkez\u0151kben tekints\u00fck \u00e1t a Strategy alap\u00fa megold\u00e1st illusztr\u00e1l\u00f3 oszt\u00e1lydiagramot (a diagramot k\u00f6vet\u0151 magyar\u00e1zatra \u00e9p\u00edtve).

                  Strategy alap\u00fa megold\u00e1s oszt\u00e1lydiagram

                  Az al\u00e1bbi UML oszt\u00e1lydiagram illusztr\u00e1lja a Strategy alap\u00fa megold\u00e1st, a l\u00e9nyegre f\u00f3kusz\u00e1lva:

                  A Strategy minta alkalmaz\u00e1s\u00e1nak els\u0151 l\u00e9p\u00e9se, hogy meghat\u00e1rozzuk, az oszt\u00e1ly viselked\u00e9s\u00e9nek h\u00e1ny k\u00fcl\u00f6nb\u00f6z\u0151 aspektusa van, melyet kiterjeszthet\u0151v\u00e9 szeretn\u00e9nk tenni. A p\u00e9ld\u00e1nkban ebb\u0151l - egyel\u0151re legal\u00e1bbis - kett\u0151 van:

                  • Anonimiz\u00e1l\u00e1shoz k\u00f6t\u0151d\u0151 viselked\u00e9s, melyhez k\u00e9t m\u0171velet tartozik:
                    • Anonimiz\u00e1l\u00f3 logika
                    • Anonimiz\u00e1l\u00f3 logika le\u00edr\u00e1s\u00e1nak meghat\u00e1roz\u00e1sa (description string el\u0151\u00e1ll\u00edt\u00e1sa)
                  • Progress kezel\u00e9s, melyhez egy m\u0171velet tartozik:
                    • Progress megjelen\u00edt\u00e9se

                  A nehez\u00e9vel meg is vagyunk, ett\u0151l kezdve alapvet\u0151en mechanikusan lehet dolgozni a Strategy mint\u00e1t k\u00f6vetve:

                  1. A fenti aspektusok mindegyik\u00e9hez egy-egy strategy interf\u00e9szt kell bevezetni, a fent meghat\u00e1rozott m\u0171veletekkel, \u00e9s ezekhez el kell k\u00e9sz\u00edteni a megfelel\u0151 implement\u00e1ci\u00f3kat.
                  2. Az Anonymizer oszt\u00e1lyba be kell vezetni egy-egy strategy interf\u00e9sz tagv\u00e1ltoz\u00f3t, \u00e9s a kiterjeszt\u00e9si pontokban ezen tagv\u00e1ltoz\u00f3kon kereszt\u00fcl haszn\u00e1lni az aktu\u00e1lisan be\u00e1ll\u00edtott strategy implement\u00e1ci\u00f3s objektumokat.

                  A fenti oszt\u00e1lydiagramon meg is jelennek ezek az elemek. Most t\u00e9rj\u00fcnk \u00e1t a k\u00f3dra. Kiindul\u00f3 k\u00f6rnyezet\u00fcnk a \"4-Strategy\" mapp\u00e1ban a \"Strategy-0-Begin\" projektben tal\u00e1lhat\u00f3, ebben dolgozzunk. Ez ugyanaz, az enum-ot haszn\u00e1l\u00f3 megold\u00e1s, mint amelyet a Template Method minta eset\u00e9ben is kiindul\u00e1sk\u00e9nt haszn\u00e1ltunk.

                  "},{"location":"labor/6-tervezesi-mintak/#anonimizalasi-strategia","title":"Anonimiz\u00e1l\u00e1si strat\u00e9gia","text":"

                  Az anonimiz\u00e1l\u00e1si strat\u00e9gia/aspektus kezel\u00e9s\u00e9vel kezd\u00fcnk. Vezess\u00fck be az ehhez tartoz\u00f3 interf\u00e9szt:

                  1. Hozzunk l\u00e9tre a projektben egy AnonymizerAlgorithms nev\u0171 mapp\u00e1t (jobb katt a \"Strategy-0-Begin\" projekten, majd Add/New Folder men\u00fc). A k\u00f6vetkez\u0151 l\u00e9p\u00e9sekben minden interf\u00e9szt \u00e9s oszt\u00e1lyt egy k\u00fcl\u00f6n, a nev\u00e9nek megfelel\u0151 forr\u00e1sf\u00e1jlba tegy\u00fcnk a szok\u00e1sos m\u00f3don!
                  2. Vegy\u00fcnk fel ebben a mapp\u00e1ban egy IAnonymizerAlgorithm interf\u00e9szt az al\u00e1bbi k\u00f3ddal:

                    IAnonymizerAlgorithm.cs
                    public interface IAnonymizerAlgorithm\n{\n    Person Anonymize(Person person);\n    string GetAnonymizerDescription() => GetType().Name;\n}\n

                    Azt is megfigyelhetj\u00fck a GetAnonymizerDescription m\u0171velet eset\u00e9ben, hogy a modern C# nyelven, amennyiben akarunk, tudunk az egyes interf\u00e9sz m\u0171veleteknek alap\u00e9rtelmezett implement\u00e1ci\u00f3t adni!

                  Most ennek az interf\u00e9sznek a n\u00e9v anonimiz\u00e1l\u00e1shoz tartoz\u00f3 megval\u00f3s\u00edt\u00e1s\u00e1t k\u00e9sz\u00edtj\u00fck el (vagyis egy strategy implement\u00e1ci\u00f3t k\u00e9sz\u00edt\u00fcnk).

                  1. Vegy\u00fcnk fel egy NameMaskingAnonymizerAlgorithm oszt\u00e1lyt ugyenebbe a mapp\u00e1ba.
                  2. Az Anonymizer oszt\u00e1lyb\u00f3l mozgassuk \u00e1t a NameMaskingAnonymizerAlgorithm-be az ide tartoz\u00f3 _mask tagv\u00e1ltoz\u00f3t:
                  3. A NameMaskingAnonymizerAlgorithm-be vegy\u00fck fel a k\u00f6vetkez\u0151 konstruktort:

                    public NameMaskingAnonymizerAlgorithm(string mask)\n{\n    _mask = mask;\n}\n
                  4. Val\u00f3s\u00edtsuk meg a IAnonymizerAlgorithm interf\u00e9szt. Miut\u00e1n az oszt\u00e1ly neve ut\u00e1n be\u00edrjuk a : IAnonymizerAlgorithm interf\u00e9szt, c\u00e9lszer\u0171 a m\u0171veletek v\u00e1z\u00e1t a Visual Studioval legener\u00e1ltatni: tegy\u00fck a kurzort a interf\u00e9sz nev\u00e9re (kattintsunk r\u00e1 a forr\u00e1sk\u00f3dban), haszn\u00e1ljuk a 'ctrl' + '.' billenty\u0171kombin\u00e1ci\u00f3t, majd a megjelen\u0151 men\u00fcben \"Implement interface\" kiv\u00e1laszt\u00e1sa. Megjegyz\u00e9s: mivel a GetAnonymizerDescription m\u0171velethez van alap\u00e9rtelmezett implement\u00e1ci\u00f3 az interf\u00e9szben, csak az Anonymize m\u0171velet gener\u00e1l\u00f3dik le, de ez most nek\u00fcnk egyel\u0151re rendben van \u00edgy.

                  5. Az Anonymizer oszt\u00e1lyb\u00f3l vegy\u00fck \u00e1t a Anonymize_MaskName m\u0171velet t\u00f6rzs\u00e9t a NameMaskingAnonymizerAlgorithm.Anonymize-be. A f\u00fcggv\u00e9ny t\u00f6rzs\u00e9t csak annyiban kell \u00e1t\u00edrni, hogy ne a m\u00e1r nem l\u00e9tez\u0151 mask param\u00e9tert, hanem a _mask tagv\u00e1ltoz\u00f3t haszn\u00e1lja. Az Anonymize oszt\u00e1ly Anonymize_MaskName-et pedig t\u00f6r\u00f6lj\u00fck.
                  6. A stategy interf\u00e9sz GetAnonymizerDescriptionm\u0171velet\u00e9nek megval\u00f3s\u00edt\u00e1s\u00e1ra t\u00e9r\u00fcnk most \u00e1t. Az Anonymizer oszt\u00e1ly GetAnonymizerDescription m\u0171velet\u00e9t m\u00e1soljuk \u00e1t a NameMaskingAnonymizerAlgorithm-be, a f\u00fcggv\u00e9ny t\u00f6rzs\u00e9ben csak a n\u00e9v anonimiz\u00e1l\u00f3ra vonatkoz\u00f3 logik\u00e1t meghagyva, a m\u0171veletet publikuss\u00e1 t\u00e9ve:

                    public string GetAnonymizerDescription()\n{\n    return $\"NameMasking anonymizer with mask {_mask}\";\n}  \n
                  7. Ezzel a n\u00e9v anonimiz\u00e1l\u00e1shoz tartoz\u00f3 strategy implement\u00e1ci\u00f3nk elk\u00e9sz\u00fclt, a teljes k\u00f3dja a k\u00f6vetkez\u0151 lett NameMaskingAnonymizerAlgorithm.cs
                    public class NameMaskingAnonymizerAlgorithm: IAnonymizerAlgorithm\n{\n    private readonly string _mask;\n\n    public NameMaskingAnonymizerAlgorithm(string mask)\n    {\n        _mask = mask;\n    }\n\n    public Person Anonymize(Person person)\n    {\n        return new Person(_mask, _mask, person.CompanyName,\n            person.Address, person.City, person.State, person.Age, person.Weight, person.Decease);\n    }\n\n    public string GetAnonymizerDescription()\n    {\n        return $\"NameMasking anonymizer with mask {_mask}\";\n    }\n}\n

                  A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben az IAnonymizerAlgorithm strategy interf\u00e9sz\u00fcnk \u00e9letkor anonimiz\u00e1l\u00e1shoz tartoz\u00f3 megval\u00f3s\u00edt\u00e1s\u00e1t k\u00e9sz\u00edtj\u00fck el.

                  1. Vegy\u00fcnk fel egy AgeAnonymizerAlgorithm oszt\u00e1lyt ugyenebbe a mapp\u00e1ba (AnonymizerAlgorithms).
                  2. Az Anonymizer oszt\u00e1lyb\u00f3l mozgassuk \u00e1t a AgeAnonymizerAlgorithm-be az ide tartoz\u00f3 _rangeSize tagv\u00e1ltoz\u00f3t:
                  3. A AgeAnonymizerAlgorithm-be vegy\u00fck fel a k\u00f6vetkez\u0151 konstruktort:

                    public AgeAnonymizerAlgorithm(int rangeSize)\n{\n    _rangeSize = rangeSize;\n}\n
                  4. Val\u00f3s\u00edtsuk meg a IAnonymizerAlgorithm interf\u00e9szt. Miut\u00e1n az oszt\u00e1ly neve ut\u00e1n be\u00edrjuk a : IAnonymizerAlgorithm interf\u00e9szt, most is c\u00e9lszer\u0171 az Anonymize m\u0171velet v\u00e1z\u00e1t a Visual Studioval a kor\u00e1bbihoz hasonl\u00f3 m\u00f3don legener\u00e1ltatni.

                  5. Az Anonymizer oszt\u00e1lyb\u00f3l vegy\u00fck \u00e1t az Anonymize_AgeRange m\u0171velet t\u00f6rzs\u00e9t a AgeAnonymizerAlgorithm.Anonymize-be. A f\u00fcggv\u00e9ny t\u00f6rzs\u00e9t csak annyiban kell \u00e1t\u00edrni, hogy ne a m\u00e1r nem l\u00e9tez\u0151 rangeSize param\u00e9tert, hanem a _rangeSize tagv\u00e1ltoz\u00f3t haszn\u00e1lja. Az Anonymize oszt\u00e1ly Anonymize_AgeRange-et pedig t\u00f6r\u00f6lj\u00fck.
                  6. A stategy interf\u00e9sz GetAnonymizerDescriptionm\u0171velet\u00e9nek megval\u00f3s\u00edt\u00e1s\u00e1ra t\u00e9r\u00fcnk most \u00e1t. Az Anonymizer oszt\u00e1ly GetAnonymizerDescription m\u0171velet\u00e9t m\u00e1soljuk \u00e1t az AgeAnonymizerAlgorithm-be, a f\u00fcggv\u00e9ny t\u00f6rzs\u00e9ben csak a kor anonimiz\u00e1l\u00f3ra vonatkoz\u00f3 logik\u00e1t meghagyva, a m\u0171veletet publikuss\u00e1 t\u00e9ve:

                    public string GetAnonymizerDescription()\n{\n    return $\"Age anonymizer with range size {_rangeSize}\";\n} \n
                  7. Ezzel a kor anonimiz\u00e1l\u00e1shoz tartoz\u00f3 strategy implement\u00e1ci\u00f3nk elk\u00e9sz\u00fclt, a teljes k\u00f3dja a k\u00f6vetkez\u0151 lett AgeAnonymizerAlgorithm.cs
                    public class AgeAnonymizerAlgorithm: IAnonymizerAlgorithm\n{\n    private readonly int _rangeSize;\n\n    public AgeAnonymizerAlgorithm(int rangeSize)\n    {\n        _rangeSize = rangeSize;\n    }\n\n    public Person Anonymize(Person person)\n    {\n        // This is whole number integer arithmetics, e.g for 55 / 20 we get 2\n        int rangeIndex = int.Parse(person.Age) / _rangeSize;\n        string newAge = $\"{rangeIndex * _rangeSize}..{(rangeIndex + 1) * _rangeSize}\";\n\n        return new Person(person.FirstName, person.LastName, person.CompanyName,\n            person.Address, person.City, person.State, newAge,\n            person.Weight, person.Decease);\n    }\n\n    public string GetAnonymizerDescription()\n    {\n        return $\"Age anonymizer with range size {_rangeSize}\";\n    }\n}\n

                  Mindenk\u00e9ppen figyelj\u00fck meg, hogy az interf\u00e9sz \u00e9s a megval\u00f3s\u00edt\u00e1sai kiz\u00e1r\u00f3lag az anonimiz\u00e1l\u00e1ssal foglalkoznak, semmif\u00e9le m\u00e1s logika (pl. progress kezel\u00e9s) nincs itt!

                  "},{"location":"labor/6-tervezesi-mintak/#progress-strategia","title":"Progress strat\u00e9gia","text":"

                  A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben vezess\u00fck be a progress kezel\u00e9shez tartoz\u00f3 interf\u00e9szt \u00e9s implement\u00e1ci\u00f3kat:

                  1. Hozzunk l\u00e9tre a projektben egy Progresses nev\u0171 mapp\u00e1t. A k\u00f6vetkez\u0151 l\u00e9p\u00e9sekben minden interf\u00e9szt \u00e9s oszt\u00e1lyt egy k\u00fcl\u00f6n, a nev\u00e9nek megfelel\u0151 forr\u00e1sf\u00e1jlba tegy\u00fcnk a szok\u00e1sos m\u00f3don.
                  2. Vegy\u00fcnk fel ebben a mapp\u00e1ban egy IProgress interf\u00e9szt az al\u00e1bbi k\u00f3ddal:

                    Megold\u00e1s IProgress.cs
                    public interface IProgress\n{\n    void Report(int count, int index);\n}\n
                  3. Vegy\u00fck fel ennek az interf\u00e9sznek az egyszer\u0171 progresshez tartoz\u00f3 megval\u00f3s\u00edt\u00e1s\u00e1t ugyanebbe a mapp\u00e1ba. Az implement\u00e1ci\u00f3 az Anonymizer oszt\u00e1lyunk PrintProgress m\u0171velet\u00e9b\u0151l lett \"levezetve\":

                    Megold\u00e1s SimpleProgress.cs
                    public class SimpleProgress: IProgress\n{\n    public void Report(int count, int index)\n    {\n        Console.WriteLine($\"{index + 1}. person processed\");\n    }\n}\n
                  4. Vegy\u00fck fel ennek az interf\u00e9sznek a sz\u00e1zal\u00e9kos progresshez tartoz\u00f3 megval\u00f3s\u00edt\u00e1s\u00e1t ugyanebbe a mapp\u00e1ba. A k\u00f3d \u00e9rtelmez\u00e9s\u00e9vel ne foglalkozzunk. Erre megold\u00e1s az Anonymizer oszt\u00e1lyunkban nincs, hiszen ezt csak a template method alap\u00fa megold\u00e1sunkn\u00e1l vezett\u00fck be (ott nem n\u00e9zt\u00fck a k\u00f3dj\u00e1t, de azzal gyakorlatilag megegyezik a l\u00e9nyege):

                    Megold\u00e1s PercentProgress.cs
                    public class PercentProgress: IProgress\n{\n    public void Report(int count, int index)\n    {\n        int percentage = (int)((double)(index+1) / count * 100);\n\n        var pos = Console.GetCursorPosition();\n        Console.SetCursorPosition(0, pos.Top);\n\n        Console.Write($\"Processing: {percentage} %\");\n\n        if (index == count - 1)\n            Console.WriteLine();\n    }\n}\n

                  Mindenk\u00e9ppen figyelj\u00fck meg, hogy az interf\u00e9sz \u00e9s a megval\u00f3s\u00edt\u00e1sai kiz\u00e1r\u00f3lag a progress kezel\u00e9ssel foglalkoznak, semmif\u00e9le m\u00e1s logika (pl. anonimiz\u00e1l\u00e1s) nincs itt!

                  "},{"location":"labor/6-tervezesi-mintak/#a-strategiak-alkalmazasa","title":"A strat\u00e9gi\u00e1k alkalmaz\u00e1sa","text":"

                  A k\u00f6vetkez\u0151 fontos l\u00e9p\u00e9s az anonimiz\u00e1l\u00f3 alaposzt\u00e1ly \u00fajrafelhaszn\u00e1lhat\u00f3v\u00e1 \u00e9s kiterjeszthet\u0151v\u00e9 t\u00e9tele a fent bevezetett strategy-k seg\u00edts\u00e9g\u00e9vel. Az Anonymizer.cs f\u00e1jlban:

                  1. T\u00f6r\u00f6lj\u00fck a k\u00f6vetkez\u0151ket:

                    • AnonymizerMode enum t\u00edpus
                    • _anonymizerMode tag (illetve a _mask \u00e9s _rangeSize tagok, ha esetleg itt maradtak kor\u00e1bban)
                  2. Vezess\u00fcnk be egy-egy strategy interf\u00e9sz t\u00edpus\u00fa tagot:

                    private readonly IProgress _progress;\nprivate readonly IAnonymizerAlgorithm _anonymizerAlgorithm;\n
                  3. A f\u00e1jl elej\u00e9re sz\u00farjunk be a megfelel\u0151 usingokat:

                    using Lab_Extensibility.AnonymizerAlgorithms;\nusing Lab_Extensibility.Progresses;\n
                  4. Az el\u0151z\u0151 pontban bevezetett _progress \u00e9s _anonymizerAlgorithm kezd\u0151\u00e9rt\u00e9ke null, a konstruktorban \u00e1ll\u00edtsuk ezeket a referenci\u00e1kat az ig\u00e9nyeinknek megfelel\u0151 implement\u00e1ci\u00f3ra. Pl.:

                    public Anonymizer(string inputFileName, string mask) : this(inputFileName)\n{\n    _progress = new PercentProgress();\n    _anonymizerAlgorithm = new NameMaskingAnonymizerAlgorithm(mask);\n}\n\npublic Anonymizer(string inputFileName, int rangeSize) : this(inputFileName)\n{\n    _progress = new PercentProgress();\n    _anonymizerAlgorithm = new AgeAnonymizerAlgorithm(rangeSize);\n}\n

                  Az Anonymizer oszt\u00e1lyban a jelenleg be\u00e9getett, de anonimiz\u00e1l\u00e1s f\u00fcgg\u0151 logik\u00e1kat b\u00edzzuk a _anonymizerAlgorithm tagv\u00e1ltoz\u00f3 \u00e1ltal hivatkozott strategy implement\u00e1ci\u00f3ra:

                  1. Az oszt\u00e1ly Run f\u00fcggv\u00e9ny\u00e9ben az if/else kifejez\u00e9sben tal\u00e1lhat\u00f3 Anonymize h\u00edv\u00e1sokat most m\u00e1r deleg\u00e1ljuk a _anonymizerAlgorithm objektumnak:

                    Person person;\nif (_anonymizerMode == AnonymizerMode.Name)\n    person = Anonymize_MaskName(persons[i], _mask);\nelse if (_anonymizerMode == AnonymizerMode.Age)\n    person = Anonymize_AgeRange(persons[i], _rangeSize);\nelse\n    throw new NotSupportedException(\"The requested anonymization mode is not supported.\");\n

                    helyett:

                    Person person = _anonymizerAlgorithm.Anonymize(persons[i]);\n
                  2. Ha esetleg kor\u00e1bban nem tett\u00fck meg, t\u00f6r\u00f6lj\u00fck a Anonymize_MaskName \u00e9s Anonymize_AgeRange f\u00fcggv\u00e9nyeket, hiszen ezek k\u00f3dja m\u00e1r a strategy implement\u00e1ci\u00f3kba ker\u00fclt, az oszt\u00e1lyr\u00f3l lev\u00e1lasztva.

                  3. A PrintSummary f\u00fcggv\u00e9ny\u00fcnk a rugalmatlan, switch alapokon m\u0171k\u00f6d\u0151 GetAnonymizerDescription-t h\u00edvja. Ezt a GetAnonymizerDescription h\u00edv\u00e1st cser\u00e9lj\u00fck le, deleg\u00e1ljuk a _anonymizerAlgorithm objektumnak. A PrintSummary f\u00fcggv\u00e9nyben (csak a l\u00e9nyeget kiemelve):

                        ... GetAnonymizerDescription() ...\n

                    helyett:

                        ... _anonymizerAlgorithm.GetAnonymizerDescription() ...\n

                    P\u00e1r sorral lejjebb a GetAnonymizerDescription f\u00fcggv\u00e9nyt t\u00f6r\u00f6lj\u00fck is az oszt\u00e1lyb\u00f3l (ennek k\u00f3dja megfelel\u0151 strategy implement\u00e1ci\u00f3kba bek\u00fclt).

                  Az utols\u00f3 l\u00e9p\u00e9s az Anonymizer oszt\u00e1lyba be\u00e9getett progress kezel\u00e9s lecser\u00e9l\u00e9se:

                  1. Itt is deleg\u00e1ljuk a k\u00e9r\u00e9st, m\u00e9gpedig a kor\u00e1bban bevezetett _progress objektumunknak. A Run f\u00fcggv\u00e9nyben egy sort kell ehhez lecser\u00e9lni:

                    PrintProgress(i);\n

                    helyett:

                    _progress.Report(persons.Count, i);\n
                  2. T\u00f6r\u00f6lj\u00fck a PrintProgress f\u00fcggv\u00e9nyt, hiszen ennek k\u00f3dja m\u00e1r egy megfelel\u0151 strategy implement\u00e1ci\u00f3ba ker\u00fclt, az oszt\u00e1lyr\u00f3l lev\u00e1lasztva.

                  Elk\u00e9sz\u00fclt\u00fcnk, a k\u00e9sz megold\u00e1s a \"4-Strategy/Strategy-1\" projektben meg is tal\u00e1lhat\u00f3 (ha valahol elakadtunk, vagy nem fordul a k\u00f3d, ezzel \u00f6ssze lehet n\u00e9zni).

                  "},{"location":"labor/6-tervezesi-mintak/#a-megoldas-ertekelese_3","title":"A megold\u00e1s \u00e9rt\u00e9kel\u00e9se","text":"

                  A strategy minta bevezet\u00e9s\u00e9vel elk\u00e9sz\u00fclt\u00fcnk. Jelen form\u00e1j\u00e1ban ugyanakkor szinte soha nem haszn\u00e1ljuk. Ellen\u0151rizz\u00fck a megold\u00e1sunkat: val\u00f3ban \u00fajrafelhaszn\u00e1lhat\u00f3, \u00e9s az Anomymizer oszt\u00e1ly m\u00f3dos\u00edt\u00e1sa n\u00e9lk\u00fcl lehet\u0151s\u00e9g van-e az anonimiz\u00e1l\u00f3 algoritmus, illetve a progress kezel\u00e9s megv\u00e1ltoztat\u00e1s\u00e1ra? Ehhez azt kell megn\u00e9zni, b\u00e1rhol az oszt\u00e1lyban van-e olyan k\u00f3d, mely implement\u00e1ci\u00f3 f\u00fcgg\u0151.

                  Sajnos tal\u00e1lunk ilyet. A konstruktorba be van \u00e9getve, milyen algoritmus implement\u00e1ci\u00f3t \u00e9s progress implement\u00e1ci\u00f3t hozunk l\u00e9tre. Ezt mindenk\u00e9ppen n\u00e9zz\u00fck meg a k\u00f3dban! Ha algoritmus vagy progress m\u00f3dot akarunk v\u00e1ltoztatni, ezekben a sorokban \u00e1t kell \u00edrni a new oper\u00e1tor ut\u00e1ni t\u00edpust, mely \u00edgy az oszt\u00e1ly m\u00f3dos\u00edt\u00e1s\u00e1val j\u00e1r.

                  Sokan - teljesen jogosan - ezt jelen form\u00e1j\u00e1ban nem is tekintik igazi Strategy alap\u00fa megold\u00e1snak. A teljes k\u00f6r\u0171 megold\u00e1st a k\u00f6vetkez\u0151 l\u00e9p\u00e9sben val\u00f3s\u00edtjuk meg.

                  "},{"location":"labor/6-tervezesi-mintak/#8-megoldas-4-strategystrategy-2-di","title":"8. Megold\u00e1s (4-Strategy/Strategy-2-DI)","text":"

                  Dependency Injection (DI) A megold\u00e1st a Dependency Injection (r\u00f6viden DI) alkalmaz\u00e1sa jelenti. Ennek l\u00e9nyege az, hogy nem maga az oszt\u00e1ly p\u00e9ld\u00e1nyos\u00edtja a viselked\u00e9sbeli f\u00fcgg\u0151s\u00e9geit (ezek a strategy implement\u00e1ci\u00f3k), hanem ezeket k\u00edv\u00fclr\u0151l adjuk \u00e1t neki, pl. konstruktor param\u00e9terekben, vagy ak\u00e1r property-k vagy setter m\u0171veletek form\u00e1j\u00e1ban. Term\u00e9szetesen interf\u00e9sz t\u00edpusk\u00e9nt hivatkozva!

                  Alak\u00edtsuk \u00e1t ennek megfelel\u0151en az Anonymizer oszt\u00e1lyt \u00fagy, hogy ne maga p\u00e9ld\u00e1nyos\u00edtsa a strategy implement\u00e1ci\u00f3it, hanem konstruktor param\u00e9terekben kapja meg azokat:

                  1. T\u00f6r\u00f6lj\u00fck mindh\u00e1rom konstruktor\u00e1t
                  2. Vegy\u00fck fel a k\u00f6vetkez\u0151 konstruktort:

                    public Anonymizer(string inputFileName, IAnonymizerAlgorithm anonymizerAlgorithm, IProgress progress = null)\n{\n    ArgumentException.ThrowIfNullOrEmpty(inputFileName);\n    ArgumentNullException.ThrowIfNull(anonymizerAlgorithm);\n\n    _inputFileName = inputFileName;\n    _anonymizerAlgorithm = anonymizerAlgorithm;\n    _progress = progress;\n}\n

                    Mint l\u00e1that\u00f3, a progress param\u00e9ter megad\u00e1sa nem k\u00f6telez\u0151, hiszen lehet, hogy az oszt\u00e1ly haszn\u00e1l\u00f3ja nem k\u00edv\u00e1ncsi semmif\u00e9le progress inform\u00e1ci\u00f3ra.

                  3. Mivel a _progress strategy null is lehet, egy null vizsg\u00e1latot be kell vezess\u00fcnk a haszn\u00e1lata sor\u00e1n. A \".\" oper\u00e1tor helyett a \"?.\" oper\u00e1tort haszn\u00e1ljuk:

                    _progress?.Report(persons.Count,i);\n
                  4. Most m\u00e1r elk\u00e9sz\u00fclt\u00fcnk, az Anonymizer oszt\u00e1ly teljesen f\u00fcggetlen lett a strategy implement\u00e1ci\u00f3kt\u00f3l. Lehet\u0151s\u00e9g\u00fcnk van az Anonymizer oszt\u00e1lyt b\u00e1rmilyen anonimiz\u00e1l\u00f3 algoritmus \u00e9s b\u00e1rmilyen progress kezel\u00e9s kombin\u00e1ci\u00f3val haszn\u00e1lni (annak m\u00f3dos\u00edt\u00e1sa n\u00e9lk\u00fcl). Hozzunk is l\u00e9tre h\u00e1rom Anonymizer k\u00fcl\u00f6nb\u00f6z\u0151 kombin\u00e1ci\u00f3kkal a Program.cs f\u00e1jl Main f\u00fcggv\u00e9ny\u00e9ben (a megl\u00e9v\u0151 k\u00f3dot el\u0151tte t\u00f6r\u00f6lj\u00fck a Main f\u00fcggv\u00e9nyb\u0151l):

                    Anonymizer p1 = new(\"us-500.csv\",\n    new NameMaskingAnonymizerAlgorithm(\"***\"),\n    new SimpleProgress());\np1.Run();\n\nConsole.WriteLine(\"--------------------\");\n\nAnonymizer p2 = new(\"us-500.csv\",\n    new NameMaskingAnonymizerAlgorithm(\"***\"),\n    new PercentProgress());\np2.Run();\n\nConsole.WriteLine(\"--------------------\");\n\nAnonymizer p3 = new(\"us-500.csv\",\n    new AgeAnonymizerAlgorithm(20),\n    new SimpleProgress());\np3.Run();\n
                  5. Ahhoz, hogy a k\u00f3d foruljon, sz\u00farjuk be a f\u00e1jl elej\u00e9re a sz\u00fcks\u00e9ges using-okat

                    using Lab_Extensibility.AnonymizerAlgorithms;\nusing Lab_Extensibility.Progresses;\n

                  Elk\u00e9sz\u00fclt\u00fcnk, a k\u00e9sz megold\u00e1s a \"4-Strategy/Strategy-2-DI\" projektben meg is tal\u00e1lhat\u00f3 (ha valahol elakadtunk, vagy nem fordul a k\u00f3d, ezzel \u00f6ssze lehet n\u00e9zni).

                  A m\u0171k\u00f6d\u00e9s ellen\u0151rz\u00e9se

                  A gyakorlat sor\u00e1n erre val\u00f3sz\u00edn\u0171leg nem lesz id\u0151, de aki bizonytalan abban, \"mit\u0151l is m\u0171k\u00f6dik\" a strategy minta, mit\u0151l lesz m\u00e1s a viselked\u00e9s a fenti n\u00e9gy esetre: \u00e9rdemes t\u00f6r\u00e9spontokat tenni a Program.cs f\u00e1jlban a n\u00e9gy Run f\u00fcggv\u00e9nyh\u00edv\u00e1sra, \u00e9s a f\u00fcggv\u00e9nyekbe a debuggerben belel\u00e9pkedve kipr\u00f3b\u00e1lni, hogy mindig a megfelel\u0151 strategy implement\u00e1ci\u00f3 h\u00edv\u00f3dik meg.

                  A projektben tal\u00e1lhat\u00f3 egy oszt\u00e1lydiagram (Main.cd), ezen is megtekinthet\u0151 a k\u00e9sz megold\u00e1s:

                  Strategy alap\u00fa megold\u00e1s oszt\u00e1lydiagram

                  Az al\u00e1bbi UML oszt\u00e1lydiagram illusztr\u00e1lja a Strategy alap\u00fa megold\u00e1sunkat:

                  "},{"location":"labor/6-tervezesi-mintak/#a-megoldas-ertekelese_4","title":"A megold\u00e1s \u00e9rt\u00e9kel\u00e9se","text":"

                  Ellen\u0151rizz\u00fck a megold\u00e1st, megval\u00f3s\u00edtja-e a c\u00e9ljainkat:

                  • Az Anonymizer egy \u00fajrafelhaszn\u00e1lhat\u00f3(bb) oszt\u00e1ly lett.
                  • Ha \u00faj anonimiz\u00e1l\u00f3 logik\u00e1ra van sz\u00fcks\u00e9g a j\u00f6v\u0151ben, csak egy \u00faj IAnonymizerAlgorithm implement\u00e1ci\u00f3t kell bevezetni. Ez nem m\u00f3dos\u00edt\u00e1s, hanem kiterjeszt\u00e9s/b\u0151v\u00edt\u00e9s.
                  • Ha \u00faj progress logik\u00e1ra van sz\u00fcks\u00e9g a j\u00f6v\u0151ben, csak egy \u00faj IProgress implement\u00e1ci\u00f3t kell bevezetni. Ez nem m\u00f3dos\u00edt\u00e1s, hanem b\u0151v\u00edt\u00e9s.
                  • A fenti k\u00e9t pontban teljes\u00fcl az OPEN/CLOSED elv, vagyis az Anonymizer k\u00f3dj\u00e1nak m\u00f3dos\u00edt\u00e1sa n\u00e9lk\u00fcl tudjuk a logik\u00e1j\u00e1t testre szabni, kiterjeszteni.
                  • Itt nem kell tartani a Template Methodn\u00e1l tapasztalt kombinatorikus robban\u00e1st\u00f3l: b\u00e1rmely IAnonymizerAlgorithm implement\u00e1ci\u00f3 b\u00e1rmely IProgress implement\u00e1ci\u00f3val k\u00e9nyelmesen haszn\u00e1lhat\u00f3, nem kell a kombin\u00e1ci\u00f3khoz \u00faj oszt\u00e1lyokat bevezetni (ezt l\u00e1ttuk a Program.cs f\u00e1jlban).

                  Tov\u00e1bbi Strategy el\u0151ny\u00f6k a Template Methoddal szemben *

                  • Fut\u00e1s k\u00f6zben lecser\u00e9lhet\u0151 viselked\u00e9s is megval\u00f3s\u00edthat\u00f3. Ha sz\u00fcks\u00e9g lenne arra, hogy egy adott Anonymizer objektumra vonatkoz\u00f3an a l\u00e9trehoz\u00e1sa ut\u00e1n meg tudjuk v\u00e1ltoztatni az anonimiz\u00e1l\u00f3 vagy progress viselked\u00e9st, akkor azt k\u00f6nnyen meg tudn\u00e1nk tenni (csak egy SetAnonimizerAlgorithm, ill. SetProgress m\u0171veletet kellene bevezetni, melyben a param\u00e9terben megkapott implement\u00e1ci\u00f3ra lehetne \u00e1ll\u00edtani az oszt\u00e1ly \u00e1ltal haszn\u00e1lt strategy-t).
                  • Egys\u00e9gtesztelhet\u0151s\u00e9g t\u00e1mogat\u00e1sa (laboron ezt nem n\u00e9zz\u00fck).
                  "},{"location":"labor/7-adatkezeles/","title":"7. Adatkezel\u00e9s","text":""},{"location":"labor/7-adatkezeles/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                  A gyakorlat c\u00e9lja az ADO.NET programoz\u00e1si modellj\u00e9nek megismer\u00e9se \u00e9s a leggyakoribb adatkezel\u00e9si probl\u00e9m\u00e1k, buktat\u00f3k szeml\u00e9ltet\u00e9se alapvet\u0151 CRUD m\u0171veletek meg\u00edr\u00e1s\u00e1n kereszt\u00fcl.

                  Kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sok: Adatkezel\u00e9s, ADO.NET alapismeretek.

                  "},{"location":"labor/7-adatkezeles/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                  A gyakorlat elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                  • Visual Studio 2022
                  • Windows 10 vagy Windows 11 oper\u00e1ci\u00f3s rendszer
                  • A gyakorlat sor\u00e1n Visual Studio-ban az SQL Server Object Explorer-t fogjuk haszn\u00e1lni az adatb\u00e1zis objektumok k\u00f6z\u00f6tti navig\u00e1l\u00e1s\u00e1ra \u00e9s a lek\u00e9rdez\u00e9sek futtat\u00e1s\u00e1ra. Ehhez sz\u00fcks\u00e9g lehet az SQL Server Data Tools komponensre, melyet legegyszer\u0171bben az Individual Components oldalon tudunk telep\u00edteni a Visual Studio Installer-ben, de a Data Storage and Processing workload is tartalmazza ezt.

                  Gyakorlat Linuxon vagy Macen

                  A gyakorlat anyag alapvet\u0151en Windowsra \u00e9s Visual Studio-ra k\u00e9sz\u00fclt, de - n\u00e9mik\u00e9ppen m\u00e1s \u00faton - elv\u00e9gezhet\u0151 m\u00e1s oper\u00e1ci\u00f3s rendszereken is, mivel a .NET SDK t\u00e1mogatott Linuxon \u00e9s Mac-en is, Linuxon:

                  • Visual Studio helyett, sz\u00f6vegszerkeszt\u0151vel (pl.: VSCode) \u00e9s CLI eszk\u00f6z\u00f6kkel.
                  • Az SQL szervernek van Linuxos v\u00e1ltozata, Mac-en pedig Dockerben futtathat\u00f3 (de Linuxon is tal\u00e1n a Docker legk\u00e9nyelmesebb m\u00f3d a futtat\u00e1s\u00e1ra).
                  • Az adatok vizualiz\u00e1ci\u00f3j\u00e1ra haszn\u00e1lhat\u00f3 a szint\u00e9n keresztplatformos Azure Data Studio eszk\u00f6z.
                  "},{"location":"labor/7-adatkezeles/#megoldas","title":"Megold\u00e1s","text":"A k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se

                  L\u00e9nyeges, hogy a labor sor\u00e1n a laborvezet\u0151t k\u00f6vetve kell dolgozni, tilos (\u00e9s \u00e9rtelmetlen) a k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se. Ugyanakkor az ut\u00f3lagos \u00f6n\u00e1ll\u00f3 gyakorl\u00e1s sor\u00e1n hasznos lehet a k\u00e9sz megold\u00e1s \u00e1ttekint\u00e9se, \u00edgy ezt el\u00e9rhet\u0151v\u00e9 tessz\u00fck.

                  A megold\u00e1s GitHubon \u00e9rhet\u0151 el itt. A legegyszer\u0171bb m\u00f3d a let\u00f6lt\u00e9s\u00e9re, ha parancssorb\u00f3l a git clone utas\u00edt\u00e1ssal lekl\u00f3nozzuk a g\u00e9p\u00fcnkre:

                  git clone https://github.com/bmeviauab00/lab-adatkezeles-megoldas

                  Ehhez telep\u00edtve kell legyen a g\u00e9pre a parancssori git, b\u0151vebb inform\u00e1ci\u00f3 itt.

                  "},{"location":"labor/7-adatkezeles/#bevezeto","title":"Bevezet\u0151","text":"Megjegyz\u00e9s gyakorlatvezet\u0151knek

                  Ezt a fejezetet gyakorlaton nem kell a le\u00edrtaknak megfelel\u0151 r\u00e9szletess\u00e9ggel ismertetni, a fontosabb fogalmakat azonban mindenk\u00e9ppen ismertess\u00fck r\u00f6viden.

                  "},{"location":"labor/7-adatkezeles/#adonet","title":"ADO.NET","text":"

                  Alacsony szint\u0171 adatb\u00e1zis-kezel\u00e9sre a .NET platformon az ADO.NET \u00e1ll rendelkez\u00e9sre, seg\u00edts\u00e9g\u00e9vel rel\u00e1ci\u00f3s adatb\u00e1zisokat tudunk el\u00e9rni.

                  Az ADO.NET haszn\u00e1lata sor\u00e1n k\u00e9t elt\u00e9r\u0151 adathozz\u00e1f\u00e9r\u00e9si modellt alkalmazhatunk:

                  • Kapcsolatalap\u00fa modell
                  • Kapcsolat n\u00e9lk\u00fcli modell

                  Az al\u00e1bbi k\u00e9t blokkot lenyitva \u00e1ttekint\u00e9st kaphatunk a k\u00e9t modell alapelv\u00e9r\u0151l.

                  A Kapcsolatalap\u00fa modell alapelvei

                  L\u00e9nyege az, hogy az adatb\u00e1zis-kapcsolatot v\u00e9gig nyitva tartjuk, am\u00edg az adatokat lek\u00e9rdezz\u00fck, m\u00f3dos\u00edtjuk, majd a v\u00e1ltoztat\u00e1sokat az adatb\u00e1zisba vissza\u00edrjuk. A megold\u00e1sra DataReader objektumokat haszn\u00e1lhatunk (l\u00e1sd k\u00e9s\u0151bb). A megold\u00e1s el\u0151nye az egyszer\u0171s\u00e9g\u00e9ben rejlik (egyszer\u0171bb programoz\u00e1si modell \u00e9s konkurenciakezel\u00e9s). A megold\u00e1s h\u00e1tr\u00e1nya, hogy a folyamatosan fenntartott h\u00e1l\u00f3zati kapcsolat miatt sk\u00e1l\u00e1zhat\u00f3s\u00e1gi probl\u00e9m\u00e1k ad\u00f3dhatnak. Ez azt jelenti, hogy az adatkezel\u0151h\u00f6z t\u00f6rt\u00e9n\u0151 nagysz\u00e1m\u00fa p\u00e1rhuzamos felhaszn\u00e1l\u00f3i hozz\u00e1f\u00e9r\u00e9s eset\u00e9n folyamatosan nagysz\u00e1m\u00fa adatb\u00e1zis kapcsolat \u00e9l, ami adatkezel\u0151 rendszerek eset\u00e9n a teljes\u00edtm\u00e9ny szempontj\u00e1b\u00f3l k\u00f6lts\u00e9ges er\u0151forr\u00e1snak sz\u00e1m\u00edt. \u00cdgy a fejleszt\u00e9s sor\u00e1n c\u00e9lszer\u0171 arra t\u00f6rekedni, hogy az adatb\u00e1zis kapcsolatokat miel\u0151bb z\u00e1rjuk le.

                  A modell el\u0151nyei:

                  • Egyszer\u0171bb a konkurencia kezel\u00e9se
                  • Az adatok mindenhol a legfrissebbek

                  Megjegyz\u00e9s: ezek az el\u0151ny\u00f6k akkor jelentkeznek, ha az adatb\u00e1zis hozz\u00e1f\u00e9r\u00e9shez az adatkezel\u0151 szigor\u00fa z\u00e1rakat haszn\u00e1l \u2013 ezt mi a hozz\u00e1f\u00e9r\u00e9s sor\u00e1n megfelel\u0151 tranzakci\u00f3 izol\u00e1ci\u00f3s szint megad\u00e1s\u00e1val tudjuk szab\u00e1lyozni. (Ennek technik\u00e1i k\u00e9s\u0151bbi tanulm\u00e1nyok sor\u00e1n ker\u00fclnek ismertet\u00e9sre.)

                  H\u00e1tr\u00e1nyok:

                  • Folyamatos h\u00e1l\u00f3zati kapcsolat
                  • Sk\u00e1l\u00e1zhat\u00f3s\u00e1g hi\u00e1nya
                  A Kapcsolat-n\u00e9lk\u00fcli modell alapelvei

                  A kapcsolatalap\u00fa modellel ellent\u00e9tben az adatok megjelen\u00edt\u00e9se \u00e9s mem\u00f3ri\u00e1ban t\u00f6rt\u00e9n\u0151 m\u00f3dos\u00edt\u00e1sa sor\u00e1n nem tartunk fent adatb\u00e1zis kapcsolatot. Ennek megfelel\u0151en a f\u0151bb l\u00e9p\u00e9sek a k\u00f6vetkez\u0151k: a kapcsolat felv\u00e9tel\u00e9t \u00e9s az adatok lek\u00e9rdez\u00e9s\u00e9t k\u00f6vet\u0151en azonnal bontjuk a kapcsolatot. Az adatokat ezt k\u00f6vet\u0151en tipikusan megjelen\u00edtj\u00fck \u00e9s lehet\u0151s\u00e9get biztos\u00edtunk a felhaszn\u00e1l\u00f3nak az adatok m\u00f3dos\u00edt\u00e1s\u00e1ra (rekordok felv\u00e9tele, m\u00f3dos\u00edt\u00e1sa, t\u00f6rl\u00e9se ig\u00e9ny szerint). A m\u00f3dos\u00edt\u00e1sok ment\u00e9se sor\u00e1n \u00fajra felvessz\u00fck az adatkapcsolatot, mentj\u00fck az adatb\u00e1zisba a v\u00e1ltoztat\u00e1sokat \u00e9s z\u00e1rjuk a kapcsolatot. Term\u00e9szetesen a modell megk\u00f6veteli, hogy a lek\u00e9rdez\u00e9se \u00e9s a m\u00f3dos\u00edt\u00e1sok vissza\u00edr\u00e1sa k\u00f6z\u00f6tt \u2013 amikor nincs kapcsolatunk az adatb\u00e1zissal \u2013 az adatokat \u00e9s a v\u00e1ltoztat\u00e1sokat a mem\u00f3ri\u00e1ban nyilv\u00e1ntartsuk. Erre az ADO.NET k\u00f6rnyezetben nagyon k\u00e9nyelmes megold\u00e1st ny\u00fajt a DataSet objektumok alkalmaz\u00e1sa.

                  A modell el\u0151nyei:

                  • Nem sz\u00fcks\u00e9ges folyamatos h\u00e1l\u00f3zati kapcsolat
                  • Sk\u00e1l\u00e1zhat\u00f3s\u00e1g

                  H\u00e1tr\u00e1nyok

                  • Az adatok nem mindig a legfrissebbek
                  • \u00dctk\u00f6z\u00e9sek lehets\u00e9gesek

                  Megjegyz\u00e9s: Sz\u00e1mos lehet\u0151s\u00e9g\u00fcnk van arra, hogy az objektumokat \u00e9s kapcsol\u00f3d\u00f3 v\u00e1ltoz\u00e1sokat nyilv\u00e1ntartsuk a mem\u00f3ri\u00e1ban. A DataSet csak az egyik lehets\u00e9ges technika. De haszn\u00e1lhatunk erre a c\u00e9lra k\u00f6z\u00f6ns\u00e9ges objektumokat, illetve ezek menedzsel\u00e9s\u00e9t megk\u00f6nny\u00edt\u0151 - az ADO.NET-n\u00e9l korszer\u0171bb - .NET technol\u00f3gi\u00e1kat (pl. Entity Framework Core).

                  "},{"location":"labor/7-adatkezeles/#a-kapcsolatalapu-modell","title":"A kapcsolatalap\u00fa modell","text":"

                  A labor keret\u00e9ben a kapcsolatalap\u00fa modellt ismerj\u00fck meg.

                  Az alapfolyamat a k\u00f6vetkez\u0151:

                  1. Kapcsolat l\u00e9trehoz\u00e1sa az alkalmaz\u00e1s, illetve az adatb\u00e1zis kezel\u0151 rendszer k\u00f6z\u00f6tt (Connection objektum felhaszn\u00e1l\u00e1s\u00e1val).
                  2. A futtatand\u00f3 SQL utas\u00edt\u00e1s \u00f6ssze\u00e1ll\u00edt\u00e1sa (Command objektum felhaszn\u00e1l\u00e1s\u00e1val).
                  3. Utas\u00edt\u00e1s futtat\u00e1sa (Command objektum felhaszn\u00e1l\u00e1s\u00e1val).
                  4. Lek\u00e9rdez\u00e9sek eset\u00e9n a visszakapott rekordhalmaz feldolgoz\u00e1sa (DataReader objektum felhaszn\u00e1l\u00e1s\u00e1val). Erre a m\u00f3dos\u00edt\u00f3 parancsok eset\u00e9n \u00e9rtelemszer\u0171en nincs sz\u00fcks\u00e9g.
                  5. Kapcsolat lez\u00e1r\u00e1sa.

                  Mint a fentiekb\u0151l kider\u00fcl, az adatb\u00e1zissal val\u00f3 kommunik\u00e1ci\u00f3nak ebben a modellben h\u00e1rom f\u0151 \u00f6sszetev\u0151je van:

                  • Connection
                  • Command
                  • Data Reader

                  Ezek az \u00f6sszetev\u0151k egy-egy oszt\u00e1lyk\u00e9nt jelennek meg, adatb\u00e1zis-kezel\u0151-f\u00fcggetlen r\u00e9sz\u00fck a BCL System.Data.Common n\u00e9vter\u00e9ben tal\u00e1lhat\u00f3 DbConnection, DbCommand, illetve DbDataReader n\u00e9ven. Ezek absztrakt oszt\u00e1lyok, az adatb\u00e1zis-kezel\u0151k gy\u00e1rt\u00f3inak feladata, hogy ezekb\u0151l lesz\u00e1rmazva meg\u00edrj\u00e1k a konkr\u00e9t adatb\u00e1zis-kezel\u0151ket t\u00e1mogat\u00f3 v\u00e1ltozatokat.

                  Mindh\u00e1rom ADO.NET \u00f6sszetev\u0151 t\u00e1mogatja a Dispose mint\u00e1t, \u00edgy using blokkban haszn\u00e1lhat\u00f3k \u2013 haszn\u00e1ljuk is \u00edgy, amikor csak tudjuk. Az adatb\u00e1zis-kezel\u0151 \u00e1ltal\u00e1ban m\u00e1sik g\u00e9pen tal\u00e1lhat\u00f3, mint ahol a k\u00f3dunk fut (a labor sor\u00e1n pont nem :)), \u00edgy tekints\u00fcnk ezekre, mint t\u00e1voli h\u00e1l\u00f3zati er\u0151forr\u00e1sokra.

                  A Microsoft SQL Server-t t\u00e1mogat\u00f3 v\u00e1ltozat a Microsoft.Data.SqlClient NuGet csomagban, az \u201eSql\u201d prefix\u0171 oszt\u00e1lyokban tal\u00e1lhat\u00f3k (SqlConnection, SqlCommand \u00e9s SqlDataReader).

                  A t\u00f6bbi gy\u00e1rt\u00f3 k\u00fcl\u00f6n dll-(ek)be teszi a saj\u00e1t v\u00e1ltozat\u00e1t, az \u00edgy l\u00e9trej\u00f6tt komponenst data provider-nek nevezik. Teljess\u00e9g ig\u00e9nye n\u00e9lk\u00fcl n\u00e9h\u00e1ny p\u00e9lda:

                  • PostgreSQL
                  • SQLite
                  • Oracle
                  "},{"location":"labor/7-adatkezeles/#connection","title":"Connection","text":"

                  Ez teremti meg a kapcsolatot a programunk, illetve az adatb\u00e1zis-kezel\u0151-rendszer k\u00f6z\u00f6tt. Inicializ\u00e1l\u00e1s\u00e1hoz sz\u00fcks\u00e9g van egy connection string-re, mely a kapcsolat fel\u00e9p\u00edt\u00e9s\u00e9hez sz\u00fcks\u00e9ges adatokat adja meg a driver sz\u00e1m\u00e1ra. Adatb\u00e1zisgy\u00e1rt\u00f3nk\u00e9nt elt\u00e9r\u0151 a bels\u0151 form\u00e1tuma (b\u0151vebben).

                  \u00daj Connection p\u00e9ld\u00e1nyos\u00edt\u00e1sakor nem biztos, hogy t\u00e9nyleg \u00faj kapcsolat fog l\u00e9trej\u00f6nni az adatb\u00e1zis fel\u00e9, a driverek \u00e1ltal\u00e1ban connection pooling-ot alkalmaznak, hasonl\u00f3an, mint a thread pool eset\u00e9ben, \u00fajrahaszn\u00e1lhatj\u00e1k a kor\u00e1bbi (\u00e9ppen nem haszn\u00e1lt) kapcsolatokat.

                  A Connection k\u00fcl\u00f6n\u00f6sen k\u00f6lts\u00e9ges nem fel\u00fcgyelt er\u0151forr\u00e1sokat haszn\u00e1l, \u00edgy kiemelten fontos, hogy a lehet\u0151 leghamarabb gondoskodjunk lez\u00e1r\u00e1s\u00e1r\u00f3l, amikor m\u00e1r nincs r\u00e1 sz\u00fcks\u00e9g (pl. a Dispose() h\u00edv\u00e1s\u00e1val, amit az esetek t\u00f6bbs\u00e9g\u00e9ben legegyszer\u0171bben a using blokk alkalmaz\u00e1s\u00e1val tehet\u00fcnk meg).

                  "},{"location":"labor/7-adatkezeles/#command","title":"Command","text":"

                  Ennek seg\u00edts\u00e9g\u00e9vel vagyunk k\u00e9pesek \u201eutas\u00edt\u00e1sokat\u201d megfogalmazni az adatb\u00e1zis kezel\u0151 sz\u00e1m\u00e1ra. Ezeket SQL nyelven kell megfogalmaznunk. A Command-nak be kell \u00e1ll\u00edtani egy kapcsolatot \u2013 ezen kereszt\u00fcl fog a parancs v\u00e9grehajt\u00f3dni. A parancsnak k\u00fcl\u00f6nb\u00f6z\u0151 eredm\u00e9nye lehet, ennek megfelel\u0151en k\u00fcl\u00f6nb\u00f6z\u0151 f\u00fcggv\u00e9nyekkel futtatjuk a parancsot:

                  • ExecuteReader: Eredm\u00e9nyhalmaz (result set) lek\u00e9rdez\u00e9se
                  • ExecuteScalar: Skal\u00e1r \u00e9rt\u00e9k lek\u00e9rdez\u00e9se
                  • ExecuteNonQuery: Nincs visszat\u00e9r\u00e9si \u00e9rt\u00e9k (Pl: INSERT, UPDATE \u00e9s DELETE), viszont a m\u0171velet k\u00f6vetkezt\u00e9ben \u00e9rintett rekordok sz\u00e1m\u00e1t visszakapjuk
                  "},{"location":"labor/7-adatkezeles/#data-reader","title":"Data Reader","text":"

                  Ha a parancs eredm\u00e9nye eredm\u00e9nyhalmaz, akkor ennek a komponensnek a seg\u00edts\u00e9g\u00e9vel tudjuk az adatokat kiolvasni. Az eredm\u00e9nyhalmaz egy t\u00e1bl\u00e1zatnak tekinthet\u0151, a Data Reader ezen tud soronk\u00e9nt v\u00e9gignavig\u00e1lni (csak egyes\u00e9vel el\u0151refel\u00e9!). A kurzor egyszerre egy soron \u00e1ll, ha a sorb\u00f3l a sz\u00fcks\u00e9ges adatokat kiolvastuk, a kurzort egy sorral el\u0151re l\u00e9ptethetj\u00fck. Csak az aktu\u00e1lis sorb\u00f3l tudunk olvasni. Kezdetben a kurzor nem az els\u0151 soron \u00e1ll, azt egyszer l\u00e9ptetn\u00fcnk kell, hogy az els\u0151 sorra \u00e1lljon.

                  Megjegyz\u00e9s: navig\u00e1l\u00e1s kliens oldalon t\u00f6rt\u00e9nik a mem\u00f3ri\u00e1ban, nincs k\u00f6ze az egyes adatkezel\u0151k \u00e1ltal t\u00e1mogatott kiszolg\u00e1l\u00f3 oldali kurzorokhoz.

                  "},{"location":"labor/7-adatkezeles/#1-feladat-adatbazis-elokeszitese","title":"1. Feladat \u2013 Adatb\u00e1zis el\u0151k\u00e9sz\u00edt\u00e9se","text":"

                  Els\u0151k\u00e9nt sz\u00fcks\u00e9g\u00fcnk van egy adatb\u00e1zis-kezel\u0151re. Ezt val\u00f3s k\u00f6rnyezetben dedik\u00e1lt szerveren fut\u00f3, adatb\u00e1zis adminisztr\u00e1torok \u00e1ltal fel\u00fcgyelt, teljes-\u00e9rt\u00e9k\u0171 adatb\u00e1zis-kezel\u0151k jelentik. Fejleszt\u00e9si id\u0151ben, lok\u00e1lis tesztel\u00e9shez azonban k\u00e9nyelmesebb egy fejleszt\u0151i adatb\u00e1zis-kezel\u0151 haszn\u00e1lata. A Visual Studio telep\u00edt\u00e9s\u00e9nek r\u00e9szek\u00e9nt kapunk is egy ilyen adatb\u00e1zismotort, ez a LocalDB, mely a teljes-\u00e9rt\u00e9k\u0171 SQL Server egyszer\u0171s\u00edtett v\u00e1ltozata. F\u0151bb tulajdons\u00e1gai:

                  • nem csak a Visual Studio-val, hanem k\u00fcl\u00f6n is telep\u00edthet\u0151,
                  • az adatb\u00e1zismotor szinte teljes m\u00e9rt\u00e9kben kompatibilis a teljes-\u00e9rt\u00e9k\u0171 Microsoft SQL Server-rel,
                  • alapvet\u0151en arr\u00f3l a g\u00e9pr\u0151l \u00e9rhet\u0151 el, melyre telep\u00edtett\u00fck,
                  • t\u00f6bb p\u00e9ld\u00e1ny is l\u00e9trehozhat\u00f3 ig\u00e9ny szerint, a p\u00e9ld\u00e1nyok alapvet\u0151en a l\u00e9trehoz\u00f3 oper\u00e1ci\u00f3s rendszer felhaszn\u00e1l\u00f3ja sz\u00e1m\u00e1ra \u00e9rhet\u0151k el (ig\u00e9ny eset\u00e9n megoszthat\u00f3 egy p\u00e9ld\u00e1ny a felhaszn\u00e1l\u00f3k k\u00f6z\u00f6tt),
                  • a saj\u00e1t p\u00e9ld\u00e1nyok kezel\u00e9se (l\u00e9trehoz\u00e1s, t\u00f6rl\u00e9s stb.) nem ig\u00e9nyel adminisztr\u00e1tori jogokat.
                  ssqllocaldb parancssori eszk\u00f6z

                  A gyakorlat sor\u00e1n nincs sz\u00fcks\u00e9g\u00fcnk erre, de a p\u00e9ld\u00e1nyok kezel\u00e9s\u00e9re az sqllocaldb parancssori eszk\u00f6z haszn\u00e1lhat\u00f3. N\u00e9h\u00e1ny parancs, melyet az sqllocaldb ut\u00e1n be\u00edrva alkalmazhatunk:

                  Paracs Le\u00edr\u00e1s info az aktu\u00e1lis felhaszn\u00e1l\u00f3 sz\u00e1m\u00e1ra l\u00e1that\u00f3 p\u00e9ld\u00e1nyok list\u00e1ja create \u201elocdb\u201d \u00faj p\u00e9ld\u00e1ny l\u00e9trehoz\u00e1sa \u201elocdb\u201d n\u00e9vvel delete \u201elocdb\u201d \u201elocdb\u201d nev\u0171 p\u00e9ld\u00e1ny t\u00f6rl\u00e9se start \u201elocdb\u201d \u201elocdb\u201d nev\u0171 p\u00e9ld\u00e1ny ind\u00edt\u00e1sa stop \u201elocdb\u201d \u201elocdb\u201d nev\u0171 p\u00e9ld\u00e1ny le\u00e1ll\u00edt\u00e1sa

                  A Visual Studio is telep\u00edt, illetve ind\u00edt LocalDB p\u00e9ld\u00e1nyokat, ez\u00e9rt \u00e9rdemes megn\u00e9zni, hogy a Visual Studio alapesetben milyen p\u00e9ld\u00e1nyokat l\u00e1t.

                  1. Ind\u00edtsuk el a Visual Studio-t, a View men\u00fcb\u0151l v\u00e1lasszuk az SQL Server Object Explorer-t (SSOE).
                  2. Nyissuk ki az SQL Server csom\u00f3pontot, ha alatta l\u00e1tunk tov\u00e1bbi csom\u00f3pontokat, akkor nyert \u00fcgy\u00fcnk van, nyissuk ki valamelyiket (ilyenkor indul el a p\u00e9ld\u00e1ny, ha nincs elind\u00edtva, \u00edgy lehet, hogy v\u00e1rni kell kicsit).
                  3. Ha nem jelent meg semmi, akkor parancssorb\u00f3l az mssqllocaldb info parancs megadja a l\u00e9tez\u0151 p\u00e9ld\u00e1nyokat. V\u00e1lasszuk az SQL Server csom\u00f3ponton jobbklikkelve az Add SQL Server opci\u00f3t, majd adjuk meg valamelyik l\u00e9tez\u0151 p\u00e9ld\u00e1nyt, pl.: (localdb)\\MSSQLLocalDB
                  4. A megjelen\u0151 Databases csom\u00f3ponton v\u00e1lasszuk a New Database opci\u00f3t, itt adjunk meg egy adatb\u00e1zisnevet. (Laboron, mivel t\u00f6bb hallgat\u00f3 is haszn\u00e1lhatja ugyanazt az oper\u00e1ci\u00f3s rendszer felhaszn\u00e1l\u00f3t, javasolt a Neptun k\u00f3d, mint n\u00e9v haszn\u00e1lata).
                  5. Az \u00faj adatb\u00e1zis csom\u00f3pontj\u00e1n jobbklikkelve v\u00e1lasszuk a New Query opci\u00f3t, ami egy \u00faj query ablakot nyit.
                  6. Nyissuk meg vagy t\u00f6lts\u00fck le a Northwind adatb\u00e1zis inicializ\u00e1l\u00f3 szkriptet.
                  7. M\u00e1soljuk be a teljes szkriptet a query ablakba.
                  8. Futtassuk le a szkriptet a kis z\u00f6ld ny\u00edllal (Execute). Figyelj\u00fcnk oda, hogy j\u00f3 adatb\u00e1zis (melyet fenti 4. l\u00e9p\u00e9sben hoztunk l\u00e9tre) legyen kiv\u00e1lasztva a query ablak tetej\u00e9n a leg\u00f6rd\u00fcl\u0151ben!.
                  9. Ellen\u0151rizz\u00fck, hogy az adatb\u00e1zisunkban megjelentek-e t\u00e1bl\u00e1k, n\u00e9zetek.
                  10. Fedezz\u00fck fel az SSOE legfontosabb funkci\u00f3it (t\u00e1bl\u00e1k adatainak, s\u00e9m\u00e1j\u00e1nak lek\u00e9rdez\u00e9se stb.).

                  MSSQL menedzsment eszk\u00f6z\u00f6k

                  A Visual Studio-ban k\u00e9t eszk\u00f6zzel is kezelhet\u00fcnk adatb\u00e1zisokat: a Server Explorer-rel \u00e9s az SQL Server Object Explorer-rel is. El\u0151bbi egy \u00e1ltal\u00e1nosabb eszk\u00f6z, mely nem csak adatb\u00e1zis, hanem egy\u00e9b szerver er\u0151forr\u00e1sok (pl. Azure szerverek) kezel\u00e9s\u00e9re is alkalmas, m\u00edg a m\u00e1sik kifejezetten csak adatb\u00e1zis-kezel\u00e9sre van kihegyezve. Mindkett\u0151 el\u00e9rhet\u0151 a View men\u00fcb\u0151l \u00e9s mindkett\u0151 hasonl\u00f3 funkci\u00f3kat ad adatb\u00e1zis-kezel\u00e9shez, ez\u00e9rt ebben a m\u00e9r\u00e9sben csak az egyiket (SQL Server Object Explorer) haszn\u00e1ljuk.

                  Amikor nem \u00e1ll rendelkez\u00e9s\u00fcnkre a Visual Studio fejleszt\u0151k\u00f6rnyezet, akkor az adatb\u00e1zisunk menedzsel\u00e9s\u00e9re az (ingyenes) SQL Server Management Studio-t vagy a szint\u00e9n ingyenes \u00e9s multiplatform Azure Data Studio-t tudjuk haszn\u00e1lni.

                  "},{"location":"labor/7-adatkezeles/#2-feladat-lekerdezes-adonet-sqldatareader-rel","title":"2. Feladat \u2013 Lek\u00e9rdez\u00e9s ADO.NET SqlDataReader-rel","text":"

                  A feladat egy olyan C# nyelv\u0171 konzol alkalmaz\u00e1s elk\u00e9sz\u00edt\u00e9se, amely haszn\u00e1lja a Northwind adatb\u00e1zis Shippers t\u00e1bl\u00e1j\u00e1nak rekordjait.

                  1. Hozzunk l\u00e9tre egy C# nyelv\u0171 konzolos alkalmaz\u00e1st. A projekt t\u00edpusa Console App legyen, \u00e9s NE a Console App (.NET Framework):

                    • A projekt neve legyen AdoExample
                    • A Target Framework legyen .NET 8
                    • Pip\u00e1ljuk be a Do not use top-level statements kapcsol\u00f3t
                  2. Keress\u00fck ki a connection string-et az SSOE-b\u0151l: jobbklikk az adatb\u00e1zis-kapcsolatunkon (pirossal jel\u00f6lve az al\u00e1bbi \u00e1br\u00e1n) / Properties.

                  3. M\u00e1soljuk a Properties ablakb\u00f3l a Connection String tulajdons\u00e1g \u00e9rt\u00e9k\u00e9t egy v\u00e1ltoz\u00f3ba, a Program oszt\u00e1lyba.

                    private const string ConnString = @\"Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=neptun;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False\";\n

                    SQL Server connection string form\u00e1tuma

                    MSSQL eset\u00e9ben a connection string kulcs \u00e9rt\u00e9kp\u00e1rokat tartalmaz pontosvessz\u0151vel elv\u00e1lasztva. A Data Source kulcs alatt az SQL szerver p\u00e9ld\u00e1ny neve, azInitial Catalog kulcs alatt pedig az adatb\u00e1zis neve szerepel. Az Integrated Security=true kapcsol\u00f3 pedig a Windows autentik\u00e1ci\u00f3t jelenti.

                    @-os string (C# verbatim string)

                    A @ egy speci\u00e1lis karakter (verbatim identifier), amit itt arra haszn\u00e1lunk, hogy a connection string-ben megjelen\u0151 backslash karakter (\\) ne felold\u00f3jelk\u00e9nt (escape character) ker\u00fclj\u00f6n \u00e9rtelmez\u00e9sre.

                  4. Vegy\u00fck fel a projektbe a Microsoft.Data.SqlClient NuGet csomagot. Ezt k\u00e9tf\u00e9lek\u00e9ppen tehetj\u00fck meg:

                    • A) Visual Studio NuGet kezel\u0151ben:
                      1. Projekten jobb gomb / Manage NuGet Packages..., a megjelen\u0151 oldalon Browse oldalra v\u00e1lt\u00e1s.
                      2. A keres\u0151be Microsoft.Data.SqlClient be\u00edr\u00e1sa.
                      3. A Version mez\u0151ben az 5.0.1 kiv\u00e1laszt\u00e1sa (laboron az\u00e9rt v\u00e1lasztjuk ki ezt a verzi\u00f3t, mert ez szerepel a g\u00e9peken a NuGet cache-ben, otthoni gyakorl\u00e1s sor\u00e1n v\u00e1lasszuk ink\u00e1bb a Latest stable-t).
                    • B) Bem\u00e1soljuk az al\u00e1bbi csomag referenci\u00e1t a a projektf\u00e1jlba:

                      <ItemGroup>\n    <PackageReference Include=\"Microsoft.Data.SqlClient\" Version=\"5.0.1\" />\n</ItemGroup>\n

                    NuGet csomagkezel\u0151

                    A NuGet egy olyan online csomagkezel\u0151 rendszer, ahonnan .NET alap\u00fa projektjeinkbe tudunk k\u00fcls\u0151 f\u00fcgg\u0151s\u00e9geket, oszt\u00e1lyk\u00f6nyvt\u00e1rakat egyszer\u0171en, verzi\u00f3zott form\u00e1ban behivatkozni. B\u0151vebben az els\u0151 el\u0151ad\u00e1son szerepel.

                  5. \u00cdrjunk lek\u00e9rdez\u0151 f\u00fcggv\u00e9nyt, mely lek\u00e9rdezi az \u00f6sszes sz\u00e1ll\u00edt\u00f3t:

                    private static void GetShippers()\n{\n    using (var conn = new SqlConnection(ConnString))\n    using (var command = new SqlCommand(\"SELECT ShipperID, CompanyName, Phone FROM Shippers\", conn))\n    {\n        conn.Open();\n        Console.WriteLine(\"{0,-10}{1,-20}{2,-20}\", \"ShipperID\", \"CompanyName\", \"Phone\");\n        Console.WriteLine(new string('-', 60));\n        using (SqlDataReader reader = command.ExecuteReader())\n        {\n            while (reader.Read())\n            {\n                Console.WriteLine(\n                    $\"{reader[\"ShipperID\"],-10}\" +\n                    $\"{reader[\"CompanyName\"],-20}\" +\n                    $\"{reader[\"Phone\"],-20}\");\n            }\n        }\n    }\n}\n

                    A kapcsolat alap\u00fa modell folyamata:

                    • Kapcsolat, parancs inicializ\u00e1l\u00e1sa
                    • Kapcsolat megnyit\u00e1sa
                    • Parancs futtat\u00e1sa
                    • Eredm\u00e9ny feldolgoz\u00e1sa
                    • Kapcsolat bont\u00e1sa, takar\u00edt\u00e1s

                    N\u00e9h\u00e1ny megjegyz\u00e9s a k\u00f3dhoz

                    • A DataReader-t a parancs futtat\u00e1s\u00e1nak eredm\u00e9nyek\u00e9nt kapjuk meg, nem pedig k\u00f6zvetlen\u00fcl p\u00e9ld\u00e1nyos\u00edtjuk
                    • A parancs futtat\u00e1sa el\u0151tt meg kell nyitnunk a kapcsolatot
                    • A DbConnection p\u00e9ld\u00e1nyos\u00edt\u00e1sakor nem nyit\u00f3dik meg a kapcsolat (nem t\u00f6rt\u00e9nik h\u00e1l\u00f3zati kommunik\u00e1ci\u00f3)
                    • A DataReader.Read() f\u00fcggv\u00e9nye mutatja, hogy van-e m\u00e9g adat az eredm\u00e9nyhalmazban
                    • A DataReader-t az eredm\u00e9nyhalmazban tal\u00e1lhat\u00f3 oszlopok nev\u00e9vel indexelhetj\u00fck \u2013 az eredm\u00e9ny object lesz, \u00edgy, ha konkr\u00e9tabb t\u00edpusra van sz\u00fcks\u00e9g\u00fcnk cast-olni kell
                    • A ford\u00edt\u00f3 nem \u00e9rtelmezi az SQL parancs sz\u00f6veg\u00e9t (az csak egy string), hanem majd csak az adatb\u00e1zis, \u00edgy hib\u00e1s SQL eset\u00e9n csak fut\u00e1si idej\u0171 kiv\u00e9telt kapunk
                    • Figyelj\u00fck meg, hogy az adatb\u00e1zis s\u00e9ma v\u00e1ltoz\u00e1sa eset\u00e9n, pl. egy oszlop \u00e1tnevez\u00e9se ut\u00e1n, h\u00e1ny helyen kell k\u00e9zzel \u00e1t\u00edrni string-eket a k\u00f3dban
                    • $-ral prefixelve string interpol\u00e1ci\u00f3t alkalmazhatunk, azaz k\u00f6zvetlen\u00fcl a string-be \u00e1gyazhatunk ki\u00e9rt\u00e9kelend\u0151 kifejez\u00e9seket (C# 6-os k\u00e9pess\u00e9g). A $@ seg\u00edts\u00e9g\u00e9vel t\u00f6bbsoros string interpol\u00e1ci\u00f3s kifejez\u00e9seket \u00edrhatunk (a sort\u00f6r\u00e9st a {}-k k\u00f6z\u00f6tt kell betenn\u00fcnk, k\u00fcl\u00f6nben a kimeneten is megjelenik). \u00c9rdekess\u00e9g: C# 8-t\u00f3l f\u00f6lfele b\u00e1rmilyen sorrendben \u00edrhatjuk a $ \u00e9s @ karaktereket, teh\u00e1t a $@ \u00e9s a @$ is helyesnek sz\u00e1m\u00edt.
                    • A using kulcssz\u00f3 blokk utas\u00edt\u00e1s helyett egysoros kifejez\u00e9sk\u00e9nt is haszn\u00e1lhat\u00f3. Ilyen esetben a using blokk v\u00e9ge a tartalmaz\u00f3 blokkig tart (eset\u00fcnkben a f\u00fcggv\u00e9ny v\u00e9g\u00e9ig). Ezzel cs\u00f6kkenthet\u0151 a beh\u00faz\u00e1sok sz\u00e1ma, de ne legyen automatikus reflex a haszn\u00e1lata, mert el\u0151fordulhat, hogy hamarabb c\u00e9lszer\u0171 kik\u00e9nyszer\u00edteni az er\u0151forr\u00e1sok felszabad\u00edt\u00e1s\u00e1t, mint a tartalmaz\u00f3 blokk v\u00e9ge.

                      private static void GetShippers()\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\"SELECT ShipperID, CompanyName, Phone FROM Shippers\", conn);\n\n    conn.Open();\n\n    Console.WriteLine(\"{0,-10}{1,-20}{2,-20}\",\"ShipperID\", \"CompanyName\", \"Phone\");\n    Console.WriteLine(new string('-', 60));\n\n    using var reader = command.ExecuteReader();\n    while (reader.Read())\n    {\n        Console.WriteLine(\n            $\"{reader[\"ShipperID\"],-10}\" +\n            $\"{reader[\"CompanyName\"],-20}\" +\n            $\"{reader[\"Phone\"],-20}\");\n    }\n}\n

                      A tov\u00e1bbiakban ezt a m\u00f3dszert haszn\u00e1ljuk a beh\u00faz\u00e1sok \u00e9s z\u00e1r\u00f3jelek megsp\u00f3rol\u00e1sa \u00e9rdek\u00e9ben.

                  6. H\u00edvjuk meg \u00faj f\u00fcggv\u00e9ny\u00fcnket a Main f\u00fcggv\u00e9nyb\u0151l.

                    private static void Main(string[] args)\n{\n    GetShippers();\n}\n
                  7. Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st. Rontsuk el az SQL-t, \u00e9s \u00fagy is pr\u00f3b\u00e1ljuk ki.

                  "},{"location":"labor/7-adatkezeles/#3-feladat-beszuras-sql-utasitassal","title":"3. Feladat \u2013 Besz\u00far\u00e1s SQL utas\u00edt\u00e1ssal","text":"
                  1. \u00cdrjunk f\u00fcggv\u00e9nyt, mely \u00faj sz\u00e1ll\u00edt\u00f3t sz\u00far be az adatb\u00e1zisba:

                    private static void InsertShipper(string companyName, string phone)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\n        \"INSERT INTO Shippers(CompanyName, Phone) VALUES(@name,@phone)\", conn);\n    command.Parameters.AddWithValue(\"@name\", companyName);\n    command.Parameters.AddWithValue(\"@phone\", phone);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n\n    Console.WriteLine($\"{affectedRows} rows affected\");\n}\n

                    Itt olyan SQL-t kell \u00edrnunk, melynek az \u00f6ssze\u00e1ll\u00edt\u00e1s\u00e1n\u00e1l k\u00edv\u00fclr\u0151l kapott v\u00e1ltoz\u00f3k \u00e9rt\u00e9keit is felhaszn\u00e1ltuk. A string \u00f6sszerak\u00e1s\u00e1hoz egyszer\u0171en a string \u00f6sszef\u0171z\u00e9s oper\u00e1tort, string interpol\u00e1ci\u00f3t vagy string.Format-ot is haszn\u00e1lhattunk volna, de ez biztons\u00e1gi kock\u00e1zatot (SQL Injection \u2013 b\u0151vebben l\u00e1sd lentebb) rejt \u2013 SOHA!!! ne rakjuk \u00f6ssze az SQL-t string m\u0171velettel. Helyette \u00edrjuk meg \u00fagy az SQL-t, hogy ahov\u00e1 a v\u00e1ltoz\u00f3k \u00e9rt\u00e9keit \u00edrn\u00e1nk, oda param\u00e9terhivatkoz\u00e1sokat tesz\u00fcnk. SQL Server eset\u00e9ben a hivatkoz\u00e1s szintaxisa: @param\u00e9tern\u00e9v.

                    A parancs futtat\u00e1s\u00e1hoz a param\u00e9terek \u00e9rt\u00e9keit is \u00e1t kell adnunk az adatb\u00e1zisnak, ugyanis az fogja elv\u00e9gezni a param\u00e9terek hely\u00e9re az \u00e9rt\u00e9kek behelyettes\u00edt\u00e9s\u00e9t.

                    A besz\u00far\u00e1si parancs kimenete nem eredm\u00e9nyhalmaz, \u00edgy az ExecuteNonQuery m\u0171velettel kell futtatnuk, mely visszaadja besz\u00fart sorok sz\u00e1m\u00e1t.

                  2. H\u00edvjuk meg \u00faj f\u00fcggv\u00e9ny\u00fcnket a Main f\u00fcggv\u00e9nyb\u0151l.

                    GetShippers();\nInsertShipper(\"Super Shipper\",\"49-98562\");\nGetShippers();\n
                  3. Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st, ellen\u0151rizz\u00fck a konzolban \u00e9s az SSOE-ben is, hogy beker\u00fclt-e az \u00faj sor. Az SSOE-ben val\u00f3 gyors \u00e9s k\u00e9nyelmes ellen\u0151rz\u00e9shez a Shippers t\u00e1bla context men\u00fcj\u00e9b\u0151l v\u00e1lasszuk a View Data lehet\u0151s\u00e9get.

                  "},{"location":"labor/7-adatkezeles/#4-feladat-modositas-tarolt-eljarassal","title":"4. Feladat - M\u00f3dos\u00edt\u00e1s t\u00e1rolt elj\u00e1r\u00e1ssal","text":"
                  1. Tanulm\u00e1nyozzuk SSOE-ben a Product_Update t\u00e1rolt elj\u00e1r\u00e1s k\u00f3dj\u00e1t. Ehhez nyissuk le a Programmability alatt tal\u00e1lhat\u00f3 Stored Procedures csom\u00f3pontot, majd a Product_Update t\u00e1rolt elj\u00e1r\u00e1s context men\u00fcj\u00e9b\u0151l v\u00e1lasszuk a View Code lehet\u0151s\u00e9get.

                    Programk\u00f3d az adatb\u00e1zisban

                    A nagyobb adatkezel\u0151 rendszerek lehet\u0151s\u00e9get biztos\u00edtanak arra, hogy programk\u00f3dot defini\u00e1ljunk mag\u00e1ban az adatkezel\u0151 adatb\u00e1zis\u00e1ban. Ezeket t\u00e1rol elj\u00e1r\u00e1soknak (stored procedure) nevezz\u00fck. A nyelve adatkezel\u0151 f\u00fcgg\u0151, de MSSQL eset\u00e9ben ez T-SQL.

                    Manaps\u00e1g m\u00e1r egyre ink\u00e1bb kezd kikopni az a gyakorlat az iparb\u00f3l, hogy komolyabb \u00fczleti logik\u00e1t az adatb\u00e1zisban helyezz\u00fcnk el, mivel ezeknek az SQL dialektusoknak az eszk\u00f6zk\u00e9szlete ma m\u00e1r j\u00f3val korl\u00e1tosabb, mint egy magas szint\u0171 programoz\u00e1si nyelv\u00e9 (C#, Java). R\u00e1ad\u00e1sul a rendszer tesztelhet\u0151s\u00e9g\u00e9t nagyban rontja a t\u00e1rolt elj\u00e1r\u00e1sok haszn\u00e1lata. Ennek ellen\u00e9re n\u00e9ha indokolt lehet az adatb\u00e1zisban tartani valamennyi logik\u00e1t, amikor ki szeretn\u00e9nk azt haszn\u00e1lni, hogy az adatokhoz k\u00f6zel futnak a programk\u00f3djaink, pl. ha nem akarjuk megutaztatni a h\u00e1l\u00f3zaton az adatot egy egyszer\u0171 t\u00f6meges adatkarbantart\u00e1s \u00e9rdek\u00e9ben.

                  2. \u00cdrjunk f\u00fcggv\u00e9nyt, mely ezt a t\u00e1rolt elj\u00e1r\u00e1st h\u00edvja

                    private static void UpdateProduct(int productID, string productName, decimal price)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\"Product_Update\", conn);\n\n    command.CommandType = CommandType.StoredProcedure;\n\n    command.Parameters.AddWithValue(\"@ProductID\", productID);\n    command.Parameters.AddWithValue(\"@ProductName\", productName);\n    command.Parameters.AddWithValue(\"@UnitPrice\", price);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n\n    Console.WriteLine($\"{affectedRows} rows affected\");\n}\n

                    A Command-nak a t\u00e1rolt elj\u00e1r\u00e1s nev\u00e9t kellett megadni, \u00e9s a parancs t\u00edpus\u00e1t kellett \u00e1t\u00e1ll\u00edtani, egy\u00e9bk\u00e9nt szerkezetileg hasonl\u00edt a kor\u00e1bbi besz\u00far\u00f3 k\u00f3dra.

                  3. H\u00edvjuk meg az \u00faj f\u00fcggv\u00e9ny\u00fcnket a Main f\u00fcggv\u00e9nyb\u0151l, p\u00e9ld\u00e1ul az al\u00e1bbi param\u00e9terez\u00e9ssel:

                    UpdateProduct(1, \"MyProduct\", 50);\n
                  4. Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st, ellen\u0151rizz\u00fck a konzolban \u00e9s az SSOE-ben is, hogy m\u00f3dosult-e az 1-es azonos\u00edt\u00f3j\u00fa term\u00e9k.

                  "},{"location":"labor/7-adatkezeles/#5-feladat-sql-injection","title":"5. Feladat - SQL Injection","text":"
                  1. \u00cdrjuk meg a besz\u00far\u00f3 f\u00fcggv\u00e9nyt \u00fagy, hogy string interpol\u00e1ci\u00f3val rakja \u00f6ssze az SQL-t.

                    private static void InsertShipper2(string companyName, string phone)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\n        $\"INSERT INTO Shippers(CompanyName, Phone) VALUES('{companyName}','{phone}')\",\n        conn);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n    Console.WriteLine($\"{affectedRows} row(s) inserted\");\n}\n
                  2. H\u00edvjuk meg \u00faj f\u00fcggv\u00e9ny\u00fcnket a Main f\u00fcggv\u00e9nyb\u0151l \u201especi\u00e1lisan\u201d param\u00e9terezve.

                    InsertShipper2(\"Super Shipper\", \"49-98562'); DELETE FROM Shippers;--\");\n

                    \u00dagy \u00e1ll\u00edtottuk \u00f6ssze a m\u00e1sodik param\u00e9tert, hogy az lez\u00e1rja az eredeti utas\u00edt\u00e1st, ezut\u00e1n tetsz\u0151leges (!!!) SQL-t \u00edrhatunk, v\u00e9g\u00fcl kikommentezz\u00fck az eredeti utas\u00edt\u00e1s marad\u00e9k\u00e1t (--).

                  3. Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st, hib\u00e1t kell kapjunk, mely arra utal, hogy valamelyik sz\u00e1ll\u00edt\u00f3 nem t\u00f6r\u00f6lhet\u0151 idegen kulcs hivatkoz\u00e1s miatt.

                    Teh\u00e1t a DELETE FROM is lefutott! N\u00e9zz\u00fck meg debugger-rel (pl. a conn.Open utas\u00edt\u00e1son \u00e1llva), hogy mi a v\u00e9gleges SQL (command.CommandText).

                    Tanuls\u00e1gok:

                    • SOSE f\u0171zz\u00fcnk \u00f6ssze programozottan SQL-t (semmilyen m\u00f3dszerrel), mert azzal kitessz\u00fck a k\u00f3dunkat SQL Injection alap\u00fa t\u00e1mad\u00e1snak.
                    • Az adatb\u00e1zis \u00e1ll\u00edtsa \u00f6ssze a v\u00e9gleges SQL-t az SQL param\u00e9terek alapj\u00e1n, mert ilyenkor biztos\u00edtott, hogy a param\u00e9ter \u00e9rt\u00e9kek nem fognak SQL-k\u00e9nt \u00e9rtelmez\u0151dni (hi\u00e1ba \u00edrunk be SQL-t). Haszn\u00e1ljunk param\u00e9terezett SQL-t vagy t\u00e1rolt elj\u00e1r\u00e1st.
                    • Haszn\u00e1ljunk adatb\u00e1zis k\u00e9nyszereket, pl. a v\u00e9letlen t\u00f6rl\u00e9s ellen is v\u00e9d.
                    • Konfigur\u00e1ljunk adatb\u00e1zisban felhaszn\u00e1l\u00f3kat k\u00fcl\u00f6nb\u00f6z\u0151 jogosults\u00e1gokkal, a programunk connection string-j\u00e9ben megadott felhaszn\u00e1l\u00f3 csak a m\u0171k\u00f6d\u00e9shez sz\u00fcks\u00e9ges minim\u00e1lis jogokkal rendelkezzen. A mi eset\u00fcnkben nem adtunk meg felhaszn\u00e1l\u00f3t, a windows-os felhaszn\u00e1l\u00f3k\u00e9nt fogunk csatlakozni.
                  4. H\u00edvjuk meg az eredeti (vagyis a biztons\u00e1gos, SQL param\u00e9tereket haszn\u00e1l\u00f3) besz\u00far\u00f3 f\u00fcggv\u00e9nyt a \u201especi\u00e1lis\u201d param\u00e9terez\u00e9ssel, hogy l\u00e1ssuk, m\u0171k\u00f6dik-e a v\u00e9delem:

                    InsertShipper(\"Super Shipper\", \"49-98562'); DELETE FROM Shippers;--\");\nInsertShipper(\"XXX');DELETE FROM Shippers;--\", \"49-98562\");\n

                    Az els\u0151n\u00e9l nem f\u00e9r\u00fcnk bele a m\u00e9retkorl\u00e1tba, a m\u00e1sodik lefut, de csak egy \u201efurcsa\u201d nev\u0171 sz\u00e1ll\u00edt\u00f3 ker\u00fclt be. A param\u00e9ter \u00e9rt\u00e9ke t\u00e9nyleg \u00e9rt\u00e9kk\u00e9nt \u00e9rtelmez\u0151d\u00f6tt nem pedig SQL-k\u00e9nt. Nem \u00fagy mint itt:

                  "},{"location":"labor/7-adatkezeles/#6-feladat-torles","title":"6. Feladat - T\u00f6rl\u00e9s","text":"
                  1. \u00cdrjunk egy \u00faj f\u00fcggv\u00e9nyt, mely kit\u00f6r\u00f6l egy adott sz\u00e1ll\u00edt\u00f3t.

                    private static void DeleteShipper(int shipperID)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\"DELETE FROM Shippers WHERE ShipperID = @ShipperID\", conn);\n    command.Parameters.AddWithValue(\"@ShipperID\", shipperID);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n\n    Console.WriteLine($\"{affectedRows} row(s) affected\");\n}\n
                  2. H\u00edvjuk meg \u00faj f\u00fcggv\u00e9ny\u00fcnket a Main f\u00fcggv\u00e9nyb\u0151l, pl. 1-gyel param\u00e9terezve.

                  3. Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st. Val\u00f3sz\u00edn\u0171leg kiv\u00e9telt kapunk, ugyanis van hivatkoz\u00e1s (idegen kulcs k\u00e9nyszerrel) az adott rekordra.
                  4. SSOE-b\u0151l n\u00e9zz\u00fck ki az azonos\u00edt\u00f3j\u00e1t egy olyan sz\u00e1ll\u00edt\u00f3nak, melyet mi vett\u00fcnk fel. Adjuk \u00e1t ezt az azonos\u00edt\u00f3t a t\u00f6rl\u0151 f\u00fcggv\u00e9nynek \u2013 ezt m\u00e1r ki tudja t\u00f6r\u00f6lni, hiszen nincs r\u00e1 hivatkoz\u00e1s.

                  T\u00f6rl\u00e9si strat\u00e9gi\u00e1k

                  L\u00e1that\u00f3, hogy a t\u00f6rl\u00e9s igen kock\u00e1zatos \u00e9s kisz\u00e1m\u00edthatatlan m\u0171velet az idegen kulcs k\u00e9nyszerek miatt. N\u00e9h\u00e1ny m\u00f3dszer a t\u00f6rl\u00e9s kezel\u00e9s\u00e9re:

                  • nem enged\u00e9lyezz\u00fck a t\u00f6rl\u00e9st: Ha hivatkoznak a t\u00f6rlend\u0151 rekordra, az adatb\u00e1zis hib\u00e1val t\u00e9r vissza (ahogy fent is l\u00e1thattuk).
                  • kaszk\u00e1d t\u00f6rl\u00e9s \u2013 az idegen kulcs k\u00e9nyszeren be\u00e1ll\u00edthat\u00f3, hogy a hivatkozott rekord t\u00f6rl\u00e9sekor a hivatkoz\u00f3 rekord is t\u00f6rl\u0151dj\u00f6n. Gyakran ez oda vezet, hogy minden idegen kulcs k\u00e9nyszer\u00fcnk ilyen lesz, \u00e9s egy (v\u00e9letlen) t\u00f6rl\u00e9ssel v\u00e9gigt\u00f6r\u00f6lhetj\u00fck ak\u00e1r a teljes adatb\u00e1zist, azaz nehezen j\u00f3solhat\u00f3 a t\u00f6rl\u00e9s hat\u00e1sa.
                  • hivatkoz\u00e1s NULL-oz\u00e1sa \u2013 az idegen kulcs k\u00e9nyszeren be\u00e1ll\u00edthat\u00f3, hogy a hivatkozott rekord t\u00f6rl\u00e9sekor a hivatkoz\u00f3 rekord idegen kulcs mez\u0151je NULL \u00e9rt\u00e9k\u0171 legyen. Csak akkor alkalmazhat\u00f3, ha a modell\u00fcnkben az adott idegen kulcs mez\u0151 NULL-ozhat\u00f3.
                  • logikai t\u00f6rl\u00e9s (soft delete) \u2013 t\u00f6rl\u00e9s m\u0171velet helyett csak egy flag oszlopot (pl. IsDeleted) \u00e1ll\u00edtunk be. El\u0151nye, hogy nem kell az idegen kulcs k\u00e9nyszerekkel foglalkoznunk, a t\u00f6r\u00f6lt adat rendelkez\u00e9sre \u00e1ll, ha sz\u00fcks\u00e9g lenne r\u00e1 (pl. undelete m\u0171velet). \u00c1m a m\u0171k\u00f6d\u00e9s bonyol\u00f3dik, mert foglalkozni kell azzal, hogy hogyan \u00e9s mikor sz\u0171rj\u00fck a t\u00f6r\u00f6lt rekordokat (pl. hogy ne jelenjenek meg a fel\u00fcleten, statisztik\u00e1kban), vagy hogyan kezelj\u00fck, ha egy nem t\u00f6r\u00f6lt rekord t\u00f6r\u00f6lt rekordra hivatkozik.
                  "},{"location":"labor/7-adatkezeles/#kitekintes","title":"Kitekint\u00e9s","text":"

                  A fenti ADO.NET alapm\u0171veleteket ebben az itt l\u00e1tott alapform\u00e1ban ritk\u00e1n haszn\u00e1lj\u00e1k k\u00e9t okb\u00f3l kifoly\u00f3an (m\u00e9g akkor is, ha ez a megk\u00f6zel\u00edt\u00e9s adja a legjobb teljes\u00edtm\u00e9nyt):

                  • Gyenge t\u00edpusoss\u00e1g (egy rekord adatait beolvasni egy oszt\u00e1ly property-jeibe igen k\u00f6r\u00fclm\u00e9nyes, cast-olni kell stb.)
                  • String-be k\u00f3dolt SQL (az elg\u00e9pel\u00e9sb\u0151l ered\u0151 hib\u00e1k csak fut\u00e1si id\u0151ben der\u00fclnek ki)

                  Az el\u0151bbire megold\u00e1st jelenthetnek a k\u00fcl\u00f6nb\u00f6z\u0151 ADO.NET-et kieg\u00e9sz\u00edt\u0151 komponensek, pl.:

                  • Dapper
                  • PetaPoco

                  Ezek a megold\u00e1sok egy minim\u00e1lis teljes\u00edtm\u00e9nyvesztes\u00e9g\u00e9rt cser\u00e9be nagyobb k\u00e9nyelmet k\u00edn\u00e1lnak.

                  Mindk\u00e9t probl\u00e9m\u00e1ra megold\u00e1st jelentenek az ORM (Object-Relational-Mapping) rendszerek, cser\u00e9be ezek nagyobb overheaddel j\u00e1rnak, mint az el\u0151bb eml\u00edtett megold\u00e1sok. Az ORM-ek lek\u00e9pez\u00e9st alak\u00edtanak ki az adatb\u00e1zis \u00e9s az OO oszt\u00e1lyaink k\u00f6z\u00f6tt, \u00e9s ennek a lek\u00e9pez\u00e9snek a seg\u00edts\u00e9g\u00e9vel egyszer\u0171s\u00edtik az adatb\u00e1zis m\u0171veleteket. Az oszt\u00e1lyainkon v\u00e9gzett, t\u00edpusos k\u00f3ddal le\u00edrt m\u0171veleteinket automatikusan \u00e1tford\u00edtj\u00e1k a megfelel\u0151 adatb\u00e1zis m\u0171veletekre, \u00edgy a mem\u00f3riabeli objektummodell\u00fcnket szinkronban tartj\u00e1k az adatb\u00e1zissal. Az ORM-ek ebb\u0151l k\u00f6vetkez\u0151en kapcsolat n\u00e9lk\u00fcli modellt haszn\u00e1lnak. Ismertebb .NET-es ORM-ek:

                  • ADO.NET DataSet \u2013 els\u0151 gener\u00e1ci\u00f3s ORM, ma m\u00e1r nagyon ritk\u00e1n haszn\u00e1ljuk
                  • Entity Framework 6.x \u2013 (r\u00e9gi) .NET Framework leggyakrabban haszn\u00e1lt ORM keretrendszere
                  • Entity Framework Core (EF Core) \u2013 a jelenleg els\u0151dlegesen haszn\u00e1lt .NET ORM (open source)
                  • NHibernate \u2013 a Java-s Hibernate .NET-es portja (open source)

                  Az Entity Framework Core-ral r\u00e9szletesebben foglalkozunk az Adatvez\u00e9relt rendszerek specializ\u00e1ci\u00f3 t\u00e1rgyban illetve a Szoftverfejleszt\u00e9s .NET platformon v\u00e1laszthat\u00f3 t\u00e1rgyban.

                  "},{"location":"labor/7-adatkezeles/index_ger/","title":"7. Verwaltung der Daten","text":""},{"location":"labor/7-adatkezeles/index_ger/#das-ziel-der-ubung","title":"Das Ziel der \u00dcbung","text":"

                  Ziel der \u00dcbung ist es, das ADO.NET-Programmiermodell zu erlernen und die h\u00e4ufigsten Datenverwaltungsprobleme und Fallstricke durch das Schreiben grundlegender CRUD-Operationen zu veranschaulichen.

                  Verwandte Pr\u00e4sentationen: Datenverwaltung, ADO.NET-Grundlagen.

                  "},{"location":"labor/7-adatkezeles/index_ger/#voraussetzungen","title":"Voraussetzungen","text":"

                  Die f\u00fcr die Durchf\u00fchrung der \u00dcbung ben\u00f6tigten Werkzeuge:

                  • Visual Studio 2022
                  • Betriebssystem Windows 10 oder Windows 11
                  • In dieser \u00dcbung werden wir den SQL Server Object Explorer in Visual Studio verwenden, um zwischen Datenbankobjekten zu navigieren und Abfragen auszuf\u00fchren. Dazu ist m\u00f6glicherweise die Komponente SQL Server Data Tools erforderlich, die am einfachsten auf der Seite Individuelle Komponenten im Visual Studio Installer installiert wird, aber auch im Workload Datenspeicherung und -verarbeitung enthalten ist.

                  \u00dcbung unter Linux oder Mac

                  Das \u00dcbungsmaterial ist grunds\u00e4tzlich f\u00fcr Windows und Visual Studio gedacht, kann aber - in leicht abgewandelter Form - auch auf anderen Betriebssystemen durchgef\u00fchrt werden, da das .NET SDK auch unter Linux und Mac sowie Linux unterst\u00fctzt wird:

                  • Verwenden Sie anstelle von Visual Studio einen Texteditor (z. B.: VSCode) und CLI-Tools.
                  • Es gibt eine Linux-Version von SQL Server, und auf dem Mac kann er in Docker ausgef\u00fchrt werden (aber Docker ist wahrscheinlich der bequemste Weg, um ihn unter Linux auszuf\u00fchren).
                  • Zur Visualisierung der Daten kann das ebenfalls plattform\u00fcbergreifende Tool Azure Data Studio verwendet werden.
                  "},{"location":"labor/7-adatkezeles/index_ger/#losung","title":"L\u00f6sung","text":"Laden Sie die fertige L\u00f6sung herunter

                  Es ist wichtig, dass Sie sich w\u00e4hrend des Praktikums an die Anleitung halten. Es ist verboten (und sinnlos), die fertige L\u00f6sung herunterzuladen. Allerdings kann es bei der anschlie\u00dfenden Selbstein\u00fcbung n\u00fctzlich sein, die fertige L\u00f6sung zu \u00fcberpr\u00fcfen, daher stellen wir sie zur Verf\u00fcgung.

                  Die L\u00f6sung ist auf GitHub [hier] verf\u00fcgbar (https://github.com/bmeviauab00/lab-adatkezeles-megoldas). Der einfachste Weg, es herunterzuladen, ist, es von der Kommandozeile aus mit dem Befehl git clone auf Ihren Computer zu klonen:

                  git clone https://github.com/bmeviauab00/lab-adatkezeles-megoldas

                  Sie m\u00fcssen Git auf Ihrem Rechner installiert haben, weitere Informationen hier.

                  "},{"location":"labor/7-adatkezeles/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"Hinweis f\u00fcr Praktiker

                  Dieses Kapitel muss in einer Praxis nicht ausf\u00fchrlich genug erkl\u00e4rt werden, aber die wichtigsten Begriffe sollten kurz erl\u00e4utert werden.

                  "},{"location":"labor/7-adatkezeles/index_ger/#adonet","title":"ADO.NET","text":"

                  F\u00fcr die Low-Level-Datenbankverwaltung auf der .NET-Plattform ist ADO.NET f\u00fcr den Zugriff auf relationale Datenbanken verf\u00fcgbar.

                  Bei der Verwendung von ADO.NET k\u00f6nnen Sie zwei verschiedene Datenzugriffsmodelle verwenden:

                  • Switch-basiertes Modell
                  • Unverbundenes Modell

                  Wenn Sie auf die beiden Bl\u00f6cke unten klicken, k\u00f6nnen Sie sich einen \u00dcberblick \u00fcber die Grunds\u00e4tze der beiden Modelle verschaffen.

                  Principles of the Connection-based Model

                  Die Idee ist, die Datenbankverbindung die ganze Zeit \u00fcber offen zu halten, w\u00e4hrend die Daten abgefragt und ge\u00e4ndert werden und die \u00c4nderungen dann in die Datenbank zur\u00fcckgeschrieben werden. DataReader-Objekte k\u00f6nnen zur L\u00f6sung dieses Problems verwendet werden (siehe sp\u00e4ter). Der Vorteil dieser L\u00f6sung liegt in ihrer Einfachheit (einfacheres Programmiermodell und Wettbewerbsmanagement). Der Nachteil dieser L\u00f6sung ist, dass aufgrund der st\u00e4ndig aufrechtzuerhaltenden Netzverbindung Skalierbarkeitsprobleme auftreten k\u00f6nnen. Dies bedeutet, dass bei einer gro\u00dfen Anzahl von gleichzeitigen Benutzerzugriffen auf den Data Controller eine gro\u00dfe Anzahl von Datenbankverbindungen st\u00e4ndig aktiv ist, was eine kostspielige Ressource in Bezug auf die Leistung von Data Controller-Systemen darstellt. Daher ist es ratsam, w\u00e4hrend der Entwicklung zu versuchen, die Datenbankverbindungen so bald wie m\u00f6glich zu schlie\u00dfen.

                  Vorteile des Modells:

                  • Leichtere Verwaltung des Wettbewerbs
                  • Die Daten sind \u00fcberall auf dem neuesten Stand

                  Hinweis: Diese Vorteile gelten nur, wenn der Datenbankzugriff strengen Sperren unterliegt, die von dem f\u00fcr die Datenverarbeitung Verantwortlichen verwendet werden - wir k\u00f6nnen dies kontrollieren, indem wir den entsprechenden Transaktionsisolierungsgrad w\u00e4hrend des Zugriffs einstellen. (Die Techniken werden in sp\u00e4teren Studien beschrieben.)

                  Benachteiligungen:

                  • Kontinuierliche Netzwerkverbindung
                  • Mangelnde Skalierbarkeit
                  Principles of the Connectionless Model

                  Im Gegensatz zum verbindungsbasierten Modell wird keine Datenbankverbindung aufrechterhalten, wenn Daten im Speicher angezeigt und ge\u00e4ndert werden. Die wichtigsten Schritte sind demnach folgende: Nach dem Verbindungsaufbau und dem Abruf der Daten wird die Verbindung sofort wieder beendet. Die Daten werden dann in der Regel angezeigt, und der Benutzer hat die M\u00f6glichkeit, die Daten zu \u00e4ndern (Datens\u00e4tze hinzuzuf\u00fcgen, zu \u00e4ndern oder zu l\u00f6schen, je nach Bedarf). Wenn wir \u00c4nderungen speichern, stellen wir die Datenverbindung wieder her, speichern die \u00c4nderungen in der Datenbank und schlie\u00dfen die Verbindung. Nat\u00fcrlich setzt das Modell voraus, dass Sie zwischen der Abfrage und dem Festschreiben von \u00c4nderungen - wenn Sie nicht mit der Datenbank verbunden sind - die Daten und \u00c4nderungen im Speicher halten. Eine sehr bequeme L\u00f6sung daf\u00fcr ist in der ADO.NET-Umgebung die Verwendung von DataSet -Objekten.

                  Vorteile des Modells:

                  • Keine st\u00e4ndige Netzwerkverbindung erforderlich
                  • Skalierbarkeit

                  Benachteiligungen

                  • Die Daten sind nicht immer auf dem neuesten Stand
                  • Zusammenst\u00f6\u00dfe sind m\u00f6glich

                  Kommentar: Es gibt mehrere M\u00f6glichkeiten, Objekte und damit verbundene \u00c4nderungen im Ged\u00e4chtnis zu speichern. Das DataSet ist nur eine der m\u00f6glichen Techniken. Sie k\u00f6nnen aber auch gew\u00f6hnliche Objekte und .NET-Technologien (z. B. Entity Framework Core) verwenden, die die Verwaltung dieser Objekte erleichtern und fortschrittlicher sind als ADO.NET.

                  "},{"location":"labor/7-adatkezeles/index_ger/#das-beziehungsorientierte-modell","title":"Das beziehungsorientierte Modell","text":"

                  Im Labor werden wir das beziehungsbasierte Modell kennenlernen.

                  Das grundlegende Verfahren ist wie folgt:

                  1. Erstellen Sie eine Verbindung zwischen der Anwendung und dem Datenbankmanagementsystem (mit dem ObjektConnection ).
                  2. Erstellen Sie die auszuf\u00fchrende SQL-Anweisung (unter Verwendung des ObjektsCommand ).
                  3. F\u00fchren Sie einen Befehl aus (unter Verwendung des ObjektsCommand ).
                  4. Verarbeitung des zur\u00fcckgegebenen Datensatzes f\u00fcr Abfragen (unter Verwendung desDataReader Objekts). F\u00fcr Modifikatorbefehle ist dies nat\u00fcrlich nicht notwendig.
                  5. Schlie\u00dfen einer Verbindung.

                  Wie oben zu sehen ist, hat die Kommunikation mit der Datenbank in diesem Modell drei Hauptkomponenten:

                  • Verbindung
                  • Befehl
                  • Datenleser

                  Diese Komponenten werden als Klasse dargestellt, deren datenbankunabh\u00e4ngiger Teil im BCL-Namensraum System.Data.Common unter DbConnection, DbCommand bzw. DbDataReader zu finden ist. Es handelt sich um abstrakte Klassen, und es ist die Aufgabe der Anbieter von Datenbankmanagern, Versionen zu schreiben, die bestimmte von ihnen abgeleitete Datenbankmanager unterst\u00fctzen.

                  Alle drei ADO.NET-Komponenten unterst\u00fctzen das Dispose-Muster, so dass sie im using -Block verwendet werden k\u00f6nnen - lassen Sie uns sie auf diese Weise verwenden, wann immer wir k\u00f6nnen. Der Datenbankmanager befindet sich in der Regel auf einem anderen Rechner als der, auf dem unser Code l\u00e4uft (nicht im Labor :)), also betrachten Sie sie als entfernte Netzwerkressourcen.

                  Die Version, die Microsoft SQL Server unterst\u00fctzt, finden Sie im NuGet-Paket Microsoft.Data.SqlClient in Klassen mit dem Pr\u00e4fix \"Sql\" (SqlConnection, SqlCommand und SqlDataReader).

                  Andere Anbieter packen ihre eigene Version in eine separate DLL(s), die daraus resultierende Komponente wird als Datenanbieter bezeichnet. Einige Beispiele ohne Anspruch auf Vollst\u00e4ndigkeit:

                  • PostgreSQL
                  • SQLite
                  • Oracle
                  "},{"location":"labor/7-adatkezeles/index_ger/#verbindung","title":"Verbindung","text":"

                  Dies ist die Verbindung zwischen unserem Programm und dem Datenbankmanagementsystem. Um sie zu initialisieren, ben\u00f6tigen wir einen Verbindungsstring, der dem Treiber die notwendigen Informationen zum Aufbau der Verbindung gibt. Das interne Format variiert von Datenbankanbieter zu Datenbankanbieter(weitere Informationen).

                  Wenn eine neue Connection instanziiert wird, ist nicht garantiert, dass tats\u00e4chlich eine neue Verbindung zur Datenbank hergestellt wird. Die Treiber verwenden in der Regel Connection Pooling, \u00e4hnlich wie Thread Pooling, um fr\u00fchere (derzeit nicht verwendete) Verbindungen wieder zu verwenden.

                  Connection ist eine besonders teure, nicht verwaltete Ressource, daher muss sichergestellt werden, dass sie so schnell wie m\u00f6glich geschlossen wird, wenn sie nicht mehr ben\u00f6tigt wird (z. B. durch den Aufruf von Dispose(), was in den meisten F\u00e4llen am einfachsten mit dem using -Block geschieht).

                  "},{"location":"labor/7-adatkezeles/index_ger/#befehl","title":"Befehl","text":"

                  So k\u00f6nnen wir \"Anweisungen\" f\u00fcr den Datenbankmanager formulieren. Diese m\u00fcssen wir in SQL formulieren. Commandmuss einen Link-Set haben - hier wird der Befehl ausgef\u00fchrt. Der Befehl kann verschiedene Ergebnisse haben, also f\u00fchren wir den Befehl mit verschiedenen Funktionen aus:

                  • ExecuteReader: Abfrage einer Ergebnismenge
                  • ExecuteScalar: Abfrage des Einzelwerts
                  • ExecuteNonQuery: Kein R\u00fcckgabewert (z.B: INSERT, UPDATE und DELETE), aber die Anzahl der von der Operation betroffenen Datens\u00e4tze wird zur\u00fcckgegeben
                  "},{"location":"labor/7-adatkezeles/index_ger/#datenleser","title":"Datenleser","text":"

                  Wenn das Ergebnis des Befehls eine Ergebnismenge ist, k\u00f6nnen wir diese Komponente verwenden, um die Daten zu lesen. Die Ergebnismenge kann als Tabelle angezeigt werden, Data Reader kann Zeile f\u00fcr Zeile (eine nach der anderen!) durch sie navigieren. Der Cursor befindet sich jeweils in einer Zeile. Sobald die gew\u00fcnschten Daten aus der Zeile gelesen wurden, kann der Cursor eine Zeile weiterbewegt werden. Wir k\u00f6nnen nur aus der aktuellen Zeile lesen. Zu Beginn steht der Cursor nicht in der ersten Zeile, Sie m\u00fcssen ihn einmal bewegen, um ihn in die erste Zeile zu setzen.

                  Hinweis: Die clientseitige Navigation erfolgt im Speicher, sie hat nichts mit den serverseitigen Cursors zu tun, die von jedem Controller unterst\u00fctzt werden.

                  "},{"location":"labor/7-adatkezeles/index_ger/#1-aufgabe-vorbereitung-der-datenbank","title":"1. Aufgabe - Vorbereitung der Datenbank","text":"

                  Zun\u00e4chst brauchen wir einen Datenbankmanager. Dies wird durch voll funktionsf\u00e4hige Datenbankmanager erreicht, die in einer realen Umgebung auf speziellen Servern laufen und von Datenbankadministratoren \u00fcberwacht werden. W\u00e4hrend der Entwicklungszeit, f\u00fcr lokale Tests, ist es jedoch bequemer, einen Datenbankmanager f\u00fcr Entwickler zu verwenden. Als Teil der Visual Studio-Installation erhalten Sie eine solche Datenbank-Engine, LocalDB, die eine vereinfachte Version des voll funktionsf\u00e4higen SQL Servers ist. Seine Hauptmerkmale sind:

                  • kann nicht nur mit Visual Studio, sondern auch separat installiert werden,
                  • die Datenbank-Engine ist fast vollst\u00e4ndig kompatibel mit dem vollwertigen Microsoft SQL Server,
                  • ist grunds\u00e4tzlich auf dem Rechner verf\u00fcgbar, auf dem es installiert ist,
                  • mehrere Instanzen k\u00f6nnen bei Bedarf erstellt werden, die Instanzen stehen im Wesentlichen dem Benutzer des erstellenden Betriebssystems zur Verf\u00fcgung (eine Instanz kann bei Bedarf von mehreren Benutzern gemeinsam genutzt werden),
                  • f\u00fcr die Verwaltung Ihrer eigenen Kopien (Erstellen, L\u00f6schen usw.) sind keine Administratorrechte erforderlich.
                  ssqllocaldb command line tool

                  In der Praxis brauchen wir das nicht, aber wir k\u00f6nnen das sqllocaldb command line tool verwenden, um Instanzen zu verwalten. Einige Befehle, die durch Eingabe nach sqllocaldb verwendet werden k\u00f6nnen:

                  Paracs Beschreibung info Liste der Instanzen, die f\u00fcr den aktuellen Benutzer sichtbar sind create \"locdb\" create a new instance named \"locdb\" l\u00f6schen \"locdb\" l\u00f6schen \"locdb\" \"locdb\" starten eine Instanz von \"locdb\" starten Stopp \"locdb\" Stopp \"locdb\"

                  Visual Studio installiert und startet auch LocalDB-Instanzen, so dass es sich lohnt, zu \u00fcberpr\u00fcfen, was Visual Studio standardm\u00e4\u00dfig sieht.

                  1. Starten Sie Visual Studio und w\u00e4hlen Sie SQL Server Object Explorer (SSOE) aus dem Men\u00fc Ansicht.
                  2. \u00d6ffnen Sie den SQL Server-Knoten. Wenn Sie andere Knoten darunter sehen, haben Sie einen erfolgreichen Fall, \u00f6ffnen Sie einen davon (dadurch wird die Instanz gestartet, falls sie noch nicht gestartet ist, Sie m\u00fcssen also m\u00f6glicherweise etwas warten).
                  3. Wenn nichts erscheint, gibt der Befehl mssqllocaldb info in der Befehlszeile die vorhandenen Instanzen zur\u00fcck. Klicken Sie mit der rechten Maustaste auf den Knoten SQL Server und w\u00e4hlen Sie SQL Server hinzuf\u00fcgen, dann geben Sie eine vorhandene Instanz an, z. B. (localdb)MSSQLLocalDB
                  4. W\u00e4hlen Sie im erscheinenden Knoten Datenbanken die Option Neue Datenbank und geben Sie einen Datenbanknamen ein. (Da in Praktika mehrere Sch\u00fcler denselben Betriebssystembenutzer verwenden k\u00f6nnen, empfiehlt es sich, den Neptun-Code als Namen zu verwenden).
                  5. Klicken Sie mit der rechten Maustaste auf den neuen Datenbankknoten und w\u00e4hlen Sie Neue Abfrage, wodurch sich ein neues Abfragefenster \u00f6ffnet.
                  6. \u00d6ffnen Sie das Skriptzur Initialisierung der Northwind-Datenbank oder laden Sie es herunter.
                  7. Kopieren Sie das vollst\u00e4ndige Skript in das Abfragefenster.
                  8. F\u00fchren Sie das Skript aus, indem Sie auf den kleinen gr\u00fcnen Pfeil*(Ausf\u00fchren*) klicken. Vergewissern Sie sich, dass eine gute Datenbank (die in Schritt 4 oben erstellt wurde) oben im Abfragefenster in der Dropdown-Liste ausgew\u00e4hlt ist!
                  9. Pr\u00fcfen Sie, ob Tabellen und Ansichten in unserer Datenbank ver\u00f6ffentlicht worden sind.
                  10. Sehen wir uns die wichtigsten Funktionen von SSOE an (Abruf von Daten aus Tabellen, Schemata usw.).

                  MSSQL-Verwaltungstools

                  In Visual Studio k\u00f6nnen Sie Datenbanken mit zwei Tools verwalten: dem Server Explorer und dem SQL Server Object Explorer. Ersteres ist ein allgemeineres Tool, das nicht nur Datenbanken, sondern auch andere Serverressourcen (z. B. Azure-Server) verwalten kann, w\u00e4hrend letzteres speziell auf die Datenbankverwaltung ausgerichtet ist. Auf beide kann \u00fcber das Men\u00fc Ansicht zugegriffen werden, und beide bieten \u00e4hnliche Datenbankverwaltungsfunktionen, weshalb wir in dieser Messung nur einen (SQL Server Object Explorer) verwenden werden.

                  Wenn Sie nicht \u00fcber die Visual Studio-Entwicklungsumgebung verf\u00fcgen, k\u00f6nnen Sie das (kostenlose) SQL Server Management Studio oder das kostenlose und plattform\u00fcbergreifende Azure Data Studio verwenden, um Ihre Datenbank zu verwalten.

                  "},{"location":"labor/7-adatkezeles/index_ger/#2-aufgabe-abfrage-mit-adonet-sqldatareader","title":"2. Aufgabe - Abfrage mit ADO.NET SqlDataReader","text":"

                  Die Aufgabe besteht darin, eine C#-Konsolenanwendung zu erstellen, die die Datens\u00e4tze der Northwind-Datenbanktabelle Shippers verwendet.

                  1. Erstellen Sie eine Konsolenanwendung in C#. Der Projekttyp sollte Console App und NICHT Console App (.NET Framework) sein:

                    • Der Projektname sollte AdoExample lauten
                    • Das Ziel-Framework sollte .NET 8 sein
                    • Aktivieren Sie die Option Keine Top-Level-Anweisungen verwenden
                  2. Suchen Sie die Verbindungszeichenfolge aus der SSOE: Klicken Sie mit der rechten Maustaste auf unsere Datenbankverbindung (in der Abbildung unten rot markiert) / Eigenschaften.

                  3. Kopieren Sie die Eigenschaft Connection String aus dem Fenster Properties in eine Variable der Klasse Program.

                    private const string ConnString = @\"Data Source=(localdb)MSSQLLocalDB;Initial Catalog=neptun;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False\";\n

                    SQL Server-Verbindungsstringformat

                    Bei MSSQL enth\u00e4lt der Schl\u00fcssel des Verbindungsstrings durch Semikolon getrennte Wertepaare. Unter dem Schl\u00fcssel Data Source steht der Name der SQL-Server-Instanz und unter dem Schl\u00fcsselInitial Catalog der Name der Datenbank. Die Option Integrated Security=true steht f\u00fcr die Windows-Authentifizierung.

                    @- string (C# verbatim string)

                    @ ist ein Sonderzeichen (verbatim identifier), das hier verwendet wird, um zu vermeiden, dass das Backslash-Zeichen (``) in der Verbindungszeichenfolge als Escape-Zeichen interpretiert wird.

                  4. F\u00fcgen Sie das NuGet-Paket Microsoft.Data.SqlClient zum Projekt hinzu. Es gibt zwei M\u00f6glichkeiten, dies zu tun:

                    • A) Visual Studio in NuGet:
                      1. Projekte rechte Taste / NuGet-Pakete verwalten..., auf der erscheinenden Seite wechseln Sie zu Durchsuchen.
                      2. Geben Sie in das Suchfeld Microsoft.Data.SqlClient.
                      3. W\u00e4hlen Sie im Feld Version die Version 5.0.1 aus (im Labor w\u00e4hlen wir diese Version, weil sie sich im NuGet-Cache auf den Rechnern befindet; in der Praxis bevorzugen wir die Version Latest stable).
                    • B) Wir kopieren den folgenden Paketverweis in die Projektdatei:

                      <ItemGroup>\n    <PackageReference Include=\"Microsoft.Data.SqlClient\" Version=\"5.0.1\" />\n</ItemGroup>\n

                    NuGet Package Manager

                    NuGet ist ein Online-Paketverwaltungssystem, mit dem Sie externe Abh\u00e4ngigkeiten und Klassenbibliotheken in versionierter Form mit Ihren .NET-basierten Projekten verkn\u00fcpfen k\u00f6nnen. Lesen Sie mehr \u00fcber die erste Pr\u00e4sentation.

                  5. Schreiben Sie eine Abfragefunktion, die alle Lieferanten abfragt:

                    private static void GetShippers()\n{\n    using (var conn = new SqlConnection(ConnString))\n    using (var command = new SqlCommand(\"SELECT ShipperID, CompanyName, Phone FROM Shippers\", conn))\n    {\n        conn.Open();\n        Console.WriteLine(\"{0,-10}{1,-20}{2,-20}\", \"ShipperID\", \"CompanyName\", \"Phone\");\n        Console.WriteLine(new string('-', 60));\n        using (SqlDataReader reader = command.ExecuteReader())\n        {\n            while (reader.Read())\n            {\n                Console.WriteLine(\n                    $\"{reader[\"ShipperID\"],-10}\" +\n                    $\"{reader[\"CompanyName\"],-20}\" +\n                    $\"{reader[\"Phone\"],-20}\");\n            }\n        }\n    }\n}\n

                    Der beziehungsorientierte Modellprozess:

                    • Verbindung initialisieren, Befehl
                    • Einen Kontakt \u00f6ffnen
                    • Ausf\u00fchren eines Befehls
                    • Verarbeitung des Ergebnisses
                    • Abschaltung, Reinigung

                    Einige Hinweise zum Code

                    • DataReadererh\u00e4lt man als Ergebnis der Ausf\u00fchrung des Befehls, nicht durch direktes Kopieren
                    • Sie m\u00fcssen die Verbindung \u00f6ffnen, bevor Sie den Befehl ausf\u00fchren
                    • Wenn DbConnection kopiert wird, wird die Verbindung nicht ge\u00f6ffnet (keine Netzwerkkommunikation)
                    • Die Funktion DataReader.Read() zeigt an, ob noch Daten in der Ergebnismenge vorhanden sind
                    • Sie k\u00f6nnen DataReadermit den Namen der Spalten in der Ergebnismenge indizieren - das Ergebnis wird object sein, wenn Sie also einen spezifischeren Typ ben\u00f6tigen, m\u00fcssen Sie einen Cast durchf\u00fchren
                    • Der Compiler interpretiert den SQL-Befehlstext nicht (es ist nur ein String), nur die Datenbank tut dies, daher erhalten Sie im Falle eines SQL-Fehlers eine Laufzeitausnahme
                    • Beachten Sie, dass, wenn sich das Datenbankschema \u00e4ndert, z.B. wenn sich das Datenbankschema \u00e4ndert, z. B. nach der Umbenennung einer Spalte, m\u00fcssen Sie an vielen Stellen im Code Strings manuell umschreiben
                    • Mit $k\u00f6nnen Sie String-Interpolation verwenden, d. h. Ausdr\u00fccke einbetten, die direkt im String ausgewertet werden (C# 6-F\u00e4higkeit). $@ erm\u00f6glicht es Ihnen, mehrzeilige String-Interpolationsausdr\u00fccke zu schreiben (Sie m\u00fcssen den Zeilenumbruch zwischen -k einf\u00fcgen, sonst wird er in der Ausgabe angezeigt). Interessante Tatsache: Ab C# 8 k\u00f6nnen Sie $- und @-Zeichen in beliebiger Reihenfolge schreiben, daher sind auch $@ und @$ korrekt.
                    • Das using-Schl\u00fcsselwort kann als einzeiliger Ausdruck anstelle einer Blockanweisung verwendet werden. In diesem Fall reicht das Ende des using-Blocks bis zum enthaltenden Block (in unserem Fall das Ende der Funktion). Dies reduziert die Anzahl der Einr\u00fcckungen, sollte aber kein automatischer Reflex sein, da es sinnvoll sein kann, die Freigabe von Ressourcen fr\u00fcher als am Ende des enthaltenden Blocks zu erzwingen.

                      private static void GetShippers()\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\"SELECT ShipperID, CompanyName, Phone FROM Shippers\", conn);\n\n    conn.Open();\n\n    Console.WriteLine(\"{0,-10}{1,-20}{2,-20}\",\"ShipperID\", \"CompanyName\", \"Phone\");\n    Console.WriteLine(new string('-', 60));\n\n    using var reader = command.ExecuteReader();\n    while (reader.Read())\n    {\n        Console.WriteLine(\n            $\"{reader[\"ShipperID\"],-10}\" +\n            $\"{reader[\"CompanyName\"],-20}\" +\n            $\"{reader[\"Phone\"],-20}\");\n    }\n}\n

                      Diese Methode wird im Folgenden verwendet, um Einz\u00fcge und Klammern zu speichern.

                  6. Rufen Sie unsere neue Funktion von Main aus auf.

                    private static void Main(string[] args)\n{\n    GetShippers();\n}\n
                  7. Probieren wir die App aus. Wir sollten SQL zerst\u00f6ren und es auf diese Weise versuchen.

                  "},{"location":"labor/7-adatkezeles/index_ger/#3-aufgabe-einfugen-mit-sql-anweisung","title":"3. Aufgabe - Einf\u00fcgen mit SQL-Anweisung","text":"
                  1. Schreiben Sie eine Funktion zum Einf\u00fcgen eines neuen Lieferanten in die Datenbank:

                    private static void InsertShipper(string companyName, string phone)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\n        \"INSERT INTO Shippers(CompanyName, Phone) VALUES(@name,@phone)\", conn);\n    command.Parameters.AddWithValue(\"@name\", companyName);\n    command.Parameters.AddWithValue(\"@phone\", phone);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n\n    Console.WriteLine($\"{affectedRows} rows affected\");\n}\n

                    Hier m\u00fcssen wir SQL schreiben, das unter Verwendung von Variablenwerten kompiliert wurde, die wir von au\u00dfen erhalten haben. Um die Zeichenkette zusammenzusetzen, h\u00e4tten wir einfach den Operator f\u00fcr die Zeichenkettenverkettung, die Zeichenketteninterpolation oder string.Formatverwenden k\u00f6nnen, aber das birgt ein Sicherheitsrisiko (SQL Injection - siehe unten f\u00fcr weitere Einzelheiten) - NIEMALS!!! SQL mit einer Zeichenkettenoperation zusammensetzen. Stattdessen sollten wir SQL so schreiben, dass wir an die Stelle der Werte von Variablen Parameterreferenzen setzen. Bei SQL Server lautet die Syntax des Verweises @parametername.

                    Um den Befehl auszuf\u00fchren, m\u00fcssen wir auch die Werte der Parameter an die Datenbank \u00fcbergeben, da diese die Ersetzung der Werte f\u00fcr die Parameter vornimmt.

                    Die Ausgabe des Einf\u00fcgebefehls ist keine Ergebnismenge, daher muss er mit ExecuteNonQuery ausgef\u00fchrt werden, das die Anzahl der eingef\u00fcgten Zeilen zur\u00fcckgibt.

                  2. Rufen Sie unsere neue Funktion von Main aus auf.

                    GetShippers();\nInsertShipper(\"Super Shipper\",\"49-98562\");\nGetShippers();\n
                  3. Probieren wir die Anwendung aus und pr\u00fcfen wir in der Konsole und in der SSOE, ob die neue Zeile eingef\u00fcgt wurde. F\u00fcr eine schnelle und bequeme \u00dcberpr\u00fcfung in SSOE w\u00e4hlen Sie Daten anzeigen aus dem Kontextmen\u00fc der Tabelle Shippers.

                  "},{"location":"labor/7-adatkezeles/index_ger/#4-aufgabe-modifikation-durch-gespeicherte-prozedur","title":"4. Aufgabe - Modifikation durch gespeicherte Prozedur","text":"
                  1. Studieren Sie den in SSOE Product_Update gespeicherten Verfahrenscode. \u00d6ffnen Sie dazu den Knoten \"Gespeicherte Prozeduren\" unter \"Programmierbarkeit\" und w\u00e4hlen Sie dann \" Code anzeigen \" aus dem Kontextmen\u00fc der gespeicherten Prozedur unter Product_Update.

                    Programmcode in der Datenbank

                    Die gro\u00dfen Datenverwaltungssysteme bieten die M\u00f6glichkeit, Programmcode in der Datenbank des Datenverwalters selbst zu definieren. Diese werden als gespeicherte Verfahren bezeichnet. Die Sprache ist abh\u00e4ngig von der Datensteuerung, aber f\u00fcr MSSQL ist es T-SQL.

                    Heutzutage wird die Praxis, ernsthafte Gesch\u00e4ftslogik in die Datenbank zu packen, immer mehr aus der Industrie verdr\u00e4ngt, da der Werkzeugsatz dieser SQL-Dialekte nun viel begrenzter ist als der einer h\u00f6heren Programmiersprache (C#, Java). Dar\u00fcber hinaus wird die Testbarkeit des Systems durch die Verwendung von gespeicherten Prozeduren stark beeintr\u00e4chtigt. Dennoch kann es manchmal sinnvoll sein, einen Teil der Logik in der Datenbank zu belassen, wenn wir den Vorteil nutzen wollen, dass unser Code in der N\u00e4he der Daten l\u00e4uft, z. B. wenn wir f\u00fcr eine einfache Massenpflege von Daten nicht \u00fcber das Netz gehen wollen.

                  2. Schreiben Sie eine Funktion, die diese gespeicherte Prozedur aufruft

                    private static void UpdateProduct(int productID, string productName, decimal price)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\"Product_Update\", conn);\n\n    command.CommandType = CommandType.StoredProcedure;\n\n    command.Parameters.AddWithValue(\"@ProductID\", productID);\n    command.Parameters.AddWithValue(\"@ProductName\", productName);\n    command.Parameters.AddWithValue(\"@UnitPrice\", price);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n\n    Console.WriteLine($\"{affectedRows} rows affected\");\n}\n

                    Der Commandmusste der Name der gespeicherten Prozedur gegeben werden und der Typ des Befehls musste ge\u00e4ndert werden, ansonsten ist er strukturell \u00e4hnlich wie der vorherige Einf\u00fcgecode.

                  3. Rufen Sie unsere neue Funktion von Main aus auf, z. B. mit den folgenden Parametern:

                    UpdateProduct(1, \"MyProduct\", 50);\n
                  4. Probieren wir die Anwendung aus und pr\u00fcfen in der Konsole und in SSOE, ob das Produkt mit der ID 1 ge\u00e4ndert wurde.

                  "},{"location":"labor/7-adatkezeles/index_ger/#5-aufgabe-sql-injektion","title":"5. Aufgabe - SQL-Injektion","text":"
                  1. Schreiben wir die Einf\u00fcgefunktion, um SQL mit Hilfe der String-Interpolation zu kompilieren.

                    private static void InsertShipper2(string companyName, string phone)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\n        $\"INSERT INTO Shippers(CompanyName, Phone) VALUES('{companyName}','{phone}')\",\n        conn);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n    Console.WriteLine($\"{affectedRows} row(s) inserted\");\n}\n
                  2. Rufen Sie unsere neue Funktion von Main mit \"speziellen\" Parametern auf.

                    InsertShipper2(\"Super Shipper\", \"49-98562'); DELETE FROM Shippers;--\");\n

                    Der zweite Parameter wird so gesetzt, dass die urspr\u00fcngliche Anweisung geschlossen wird, dann werden alle (!!!) Wir k\u00f6nnen SQL schreiben und schlie\u00dflich den Rest der urspr\u00fcnglichen Anweisung auskommentieren (--).

                  3. Versuchen Sie die Anwendung, sollten Sie einen Fehler erhalten, der angibt, dass ein Lieferant aufgrund eines Fremdschl\u00fcsselverweises nicht gel\u00f6scht werden kann.

                    Also DELETE FROM ist auch gelaufen! Pr\u00fcfen wir mit dem Debugger (z. B. durch Anhalten bei der Anweisung conn.Open ), wie das endg\u00fcltige SQL (command.CommandText) lautet.

                    Lektionen gelernt:

                    • Splei\u00dfen Sie SQL NICHT programmatisch (egal mit welcher Methode), da dies Ihren Code f\u00fcr SQL-Injection-basierte Angriffe anf\u00e4llig macht.
                    • Die Datenbank sollte das endg\u00fcltige SQL auf der Grundlage der SQL-Parameter kompilieren, denn dann ist gew\u00e4hrleistet, dass die Parameterwerte nicht als SQL interpretiert werden (selbst wenn SQL eingegeben wird). Verwenden Sie parametrisiertes SQL oder gespeicherte Prozeduren.
                    • Verwenden Sie Datenbankbeschr\u00e4nkungen, z. B. zum Schutz vor versehentlichem L\u00f6schen.
                    • Konfigurieren Sie Benutzer in der Datenbank mit unterschiedlichen Rechten. Der in der Verbindungszeichenfolge Ihres Programms angegebene Benutzer sollte nur die f\u00fcr den Betrieb erforderlichen Mindestrechte haben. In unserem Fall haben wir keinen Benutzer angegeben, wir werden uns als Windows-Benutzer verbinden.
                  4. Rufen wir nun die urspr\u00fcngliche (d.h. die sichere, mit SQL-Parametern versehene) Einf\u00fcgefunktion mit der \"speziellen\" Parametrisierung auf, um zu sehen, ob der Schutz funktioniert:

                    InsertShipper(\"Super Shipper\", \"49-98562'); DELETE FROM Shippers;--\");\nInsertShipper(\"XXX');DELETE FROM Shippers;--\", \"49-98562\");\n

                    Der erste passt nicht in die Gr\u00f6\u00dfenbeschr\u00e4nkung, der zweite l\u00e4uft, aber nur ein \"seltsam\" benannter Anbieter ist enthalten. Der Parameterwert wurde tats\u00e4chlich als Wert und nicht als SQL interpretiert. Nicht so wie hier:

                  "},{"location":"labor/7-adatkezeles/index_ger/#6-aufgabe-loschen","title":"6. Aufgabe - L\u00f6schen","text":"
                  1. Schreiben Sie eine neue Funktion zum L\u00f6schen eines bestimmten Lieferanten.

                    private static void DeleteShipper(int shipperID)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\"DELETE FROM Shippers WHERE ShipperID = @ShipperID\", conn);\n    command.Parameters.AddWithValue(\"@ShipperID\", shipperID);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n\n    Console.WriteLine($\"{affectedRows} row(s) affected\");\n}\n
                  2. Rufen wir unsere neue Funktion von Main auf, parametrisiert mit, sagen wir, 1.

                  3. Probieren wir die App aus. Sie werden wahrscheinlich eine Ausnahme erhalten, da ein Verweis (mit Fremdschl\u00fcssel-Beschr\u00e4nkung) auf den Datensatz besteht.
                  4. In SSOE suchen wir nach der ID eines Lieferanten, den wir beauftragt haben. \u00dcbergeben wir diesen Bezeichner an die L\u00f6schfunktion - sie kann ihn l\u00f6schen, da es keinen Verweis auf ihn gibt.

                  L\u00f6schstrategien

                  Es zeigt sich, dass das L\u00f6schen aufgrund der Fremdschl\u00fcssel-Beschr\u00e4nkungen eine sehr riskante und unvorhersehbare Operation ist. Einige M\u00f6glichkeiten, die L\u00f6schung zu verwalten:

                  • Wir erlauben keine L\u00f6schung: Wenn der zu l\u00f6schende Datensatz referenziert ist, gibt die Datenbank einen Fehler zur\u00fcck (wie oben gezeigt).
                  • L\u00f6schkaskade - die Fremdschl\u00fcssel-Beschr\u00e4nkung kann so eingestellt werden, dass der referenzierte Datensatz gel\u00f6scht wird, wenn der referenzierte Datensatz gel\u00f6scht wird. Dies f\u00fchrt oft dazu, dass alle unsere Fremdschl\u00fcssel-Beschr\u00e4nkungen so aussehen, und eine (versehentliche) L\u00f6schung kann die gesamte Datenbank ausl\u00f6schen, d.h. die Auswirkungen der L\u00f6schung sind schwer vorherzusagen.
                  • NULL der Referenz - die Fremdschl\u00fcssel-Beschr\u00e4nkung kann so eingestellt werden, dass das Fremdschl\u00fcsselfeld des referenzierten Datensatzes auf NULL gesetzt wird, wenn der referenzierte Datensatz gel\u00f6scht wird. Nur anwendbar, wenn das Fremdschl\u00fcsselfeld in Ihrem Modell auf NULL gesetzt werden kann.
                  • logisches L\u00f6schen (soft delete) - anstelle eines L\u00f6schvorgangs wird nur eine Flaggenspalte (z.B. IsDeleted) gesetzt. Der Vorteil ist, dass Sie sich nicht mit Fremdschl\u00fcssel-Beschr\u00e4nkungen befassen m\u00fcssen und die gel\u00f6schten Daten bei Bedarf verf\u00fcgbar sind (z. B. beim R\u00fcckg\u00e4ngigmachen des L\u00f6schvorgangs). Der Vorgang ist jedoch kompliziert, da man sich damit befassen muss, wie und wann gel\u00f6schte Datens\u00e4tze gefiltert werden sollen (z. B. damit sie nicht in der Schnittstelle oder in der Statistik erscheinen) oder wie man damit umgeht, wenn ein nicht gel\u00f6schter Datensatz auf einen gel\u00f6schten Datensatz verweist.
                  "},{"location":"labor/7-adatkezeles/index_ger/#screenshots","title":"Screenshots","text":"

                  Die oben genannten grundlegenden ADO.NET-Operationen in der hier gezeigten Form werden aus zwei Gr\u00fcnden selten verwendet (auch wenn dieser Ansatz die beste Leistung bietet):

                  • Schlechte Typisierung (das Einlesen der Daten eines Datensatzes in die Eigenschaften einer Klasse ist sehr umst\u00e4ndlich, Cast usw.)
                  • SQL kodiert in Strings (Fehler aufgrund von Tippfehlern werden erst zur Laufzeit erkannt)

                  Ersteres kann durch verschiedene Komponenten gel\u00f6st werden, die ADO.NET erg\u00e4nzen, wie z.B.:

                  • Dapper
                  • PetaPoco

                  Diese L\u00f6sungen bieten mehr Komfort bei minimalen Leistungseinbu\u00dfen.

                  Beide Probleme werden durch ORM-Systeme (Object-Relational-Mapping) gel\u00f6st, die jedoch einen h\u00f6heren Overhead haben als die oben genannten L\u00f6sungen. ORMs erstellen ein Mapping zwischen der Datenbank und unseren OO-Klassen und verwenden dieses Mapping, um Datenbankoperationen zu vereinfachen. Unsere in Typcode geschriebenen Operationen mit unseren Klassen werden automatisch in die entsprechenden Datenbankoperationen \u00fcbersetzt, so dass unser In-Memory-Objektmodell mit der Datenbank synchronisiert wird. ORMs verwenden daher ein verbindungsloses Modell. Besser bekannte .NET ORMs:

                  • ADO.NET DataSet - ORM der ersten Generation, jetzt sehr selten verwendet
                  • Entity Framework 6.x - das am h\u00e4ufigsten verwendete ORM-Framework im (alten) .NET Framework
                  • Entity Framework Core (EF Core) - derzeit das wichtigste .NET ORM (Open Source)
                  • NHibernate - die .NET-Portierung von Hibernate f\u00fcr Java (Open Source)

                  Das Entity Framework Core wird in der Spezialisierung Data Driven Systems und im Wahlfach Software Development on .NET platform ausf\u00fchrlicher behandelt.

                  "},{"location":"labor/old-6-doc-view/","title":"6. Document-View architekt\u00fara","text":""},{"location":"labor/old-6-doc-view/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                  A gyakorlat c\u00e9ljai:

                  • UML alap\u00fa tervez\u00e9s \u00e9s n\u00e9h\u00e1ny tervez\u00e9si minta alkalmaz\u00e1sa
                  • A Document-View architekt\u00fara alkalmaz\u00e1sa a gyakorlatban
                  • UserControl szerep\u00e9nek bemutat\u00e1sa Window Forms alkalmaz\u00e1sokban, Document-View architekt\u00fara eset\u00e9n
                  • A grafikus megjelen\u00edt\u00e9s elveinek gyakorl\u00e1sa Window Forms alkalmaz\u00e1sokban (Paint esem\u00e9ny, Invalidate, Graphics haszn\u00e1lata)

                  A kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sok \u00e9s kor\u00e1bbi gyakorlatok anyaga:

                  • UML alap\u00fa modellez\u00e9s (1. gyakorlat)
                  • Windows Forms alkalmaz\u00e1sfejleszt\u00e9s
                  • Szoftverarchitekt\u00far\u00e1k (Document-View architekt\u00fara)
                  "},{"location":"labor/old-6-doc-view/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                  A gyakorlat elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                  • Visual Studio 2022
                  • Windows 10 vagy Windows 11 oper\u00e1ci\u00f3s rendszer (Linux \u00e9s macOS nem alkalmas)
                  "},{"location":"labor/old-6-doc-view/#a-gyakorlat-menete","title":"A gyakorlat menete","text":"

                  Az al\u00e1bbiak szerint fogunk dolgozni:

                  • A feladat/c\u00e9lok r\u00f6vid ismertet\u00e9se: egy interakt\u00edv fonteditor (bet\u0171t\u00edpus-szerkeszt\u0151) megtervez\u00e9se
                  • A k\u00e9sz alkalmaz\u00e1st futtatva a feladat (a k\u00e9sz alkalmaz\u00e1s m\u0171k\u00f6d\u00e9s\u00e9nek) ismertet\u00e9se
                  • Az alkalmaz\u00e1s architekt\u00far\u00e1j\u00e1nak megtervez\u00e9se (oszt\u00e1lydiagram elk\u00e9sz\u00edt\u00e9se)
                  • A k\u00e9sz alkalmaz\u00e1s forr\u00e1sk\u00f3dj\u00e1nak alapj\u00e1n n\u00e9h\u00e1ny fontosabb forgat\u00f3k\u00f6nyv megval\u00f3s\u00edt\u00e1s\u00e1nak \u00e1ttekint\u00e9se
                  Megjegyz\u00e9s gyakorlatvezet\u0151k sz\u00e1m\u00e1ra

                  A gyakorlat elej\u00e9n t\u00f6lts\u00fck le a k\u00e9sz alkalmaz\u00e1st (innen kl\u00f3nozzuk ki: https://github.com/bmeviauab00/lab-docview-megoldas). A hallgat\u00f3k ekkor m\u00e9g ne t\u00f6lts\u00e9k le, ne ezt kattintgass\u00e1k, majd csak a gyakorlat m\u00e1sodik r\u00e9sz\u00e9ben. A gyakorlatvezet\u0151knek viszont sz\u00fcks\u00e9ge lesz r\u00e1, mert ennek seg\u00edts\u00e9g\u00e9vel t\u00f6rt\u00e9nik a feladat bemutat\u00e1sa.

                  "},{"location":"labor/old-6-doc-view/#1-feladat-a-feladat-ismertetese","title":"1. Feladat - A feladat ismertet\u00e9se","text":"

                  Interakt\u00edv FontEditor (bet\u0171t\u00edpus szerkeszt\u0151) k\u00e9sz\u00edt\u00e9se, amelyben lehet szerkeszteni a karaktereket, \u00e9s az aktu\u00e1lis bet\u0171k\u00e9szlet alapj\u00e1n tetsz\u0151leges p\u00e9ldasz\u00f6veg megjelen\u00edthet\u0151. Az alkalmaz\u00e1s felhaszn\u00e1l\u00f3i fel\u00fclete fut\u00e1s k\u00f6zben:

                  A k\u00f6vetkez\u0151 funkci\u00f3kat kell t\u00e1mogatnia:

                  • T\u00f6bb bet\u0171t\u00edpus egyidej\u0171 szerkeszt\u00e9se. Ez egyes bet\u0171t\u00edpusok k\u00fcl\u00f6n tab oldalakon szerkeszthet\u0151k (MDI \u2013 Multiple Document Interface).
                  • \u00daj bet\u0171t\u00edpus a File/New men\u00fcelem kiv\u00e1laszt\u00e1s\u00e1val hozhat\u00f3 l\u00e9tre (meg kell adni a nev\u00e9t).
                  • Ez egyes bet\u0171t\u00edpusok elmenthet\u0151k (File/Save), bet\u00f6lthet\u0151k (File/Open), \u00e9s az aktu\u00e1lis dokumentum bez\u00e1rhat\u00f3 (File/Close). Ezek helye megvan az alkalmaz\u00e1sban, de nincsenek r\u00e9szleteiben implement\u00e1lva (a f\u00fcggv\u00e9nyek t\u00f6rzse nincs kit\u00f6ltve \u2013 opcion\u00e1lis HF).
                  • A felhaszn\u00e1l\u00f3i fel\u00fclet fel\u00e9p\u00edt\u00e9se
                    • Az oldal tetej\u00e9n (Sample text) egy mintasz\u00f6veg adhat\u00f3 meg, melyet az aktu\u00e1lis bet\u0171t\u00edpussal az alkalmaz\u00e1s megjelen\u00edt.
                    • Az oldalak k\u00f6zep\u00e9n egy karakters\u00e1v tal\u00e1lhat\u00f3. Egy adott karakteren dupl\u00e1n kattintva alatta megjelenik egy, az adott karakterhez tartoz\u00f3 szerkeszt\u0151n\u00e9zet.
                    • Az oldal alj\u00e1n egym\u00e1s mellett az eddig szerkeszt\u00e9sre megnyitott karakterek szerkeszt\u0151n\u00e9zetei l\u00e1that\u00f3k. Egy karakter t\u00f6bbsz\u00f6r is megnyithat\u00f3 szerkeszt\u00e9sre, ez esetben t\u00f6bb szerkeszt\u0151n\u00e9zet j\u00f6n l\u00e9tre hozz\u00e1. Ennek az az \u00e9rtelme, hogy ugyanazt a karaktert k\u00fcl\u00f6nb\u00f6z\u0151 nagy\u00edt\u00e1ssal is l\u00e1thatjuk/szerkeszthetj\u00fck.
                  • A szerkeszt\u0151n\u00e9zetek fel\u00e9p\u00edt\u00e9se
                    • Nagy r\u00e9sze (eltekintve a fels\u0151 s\u00e1v) a szerkeszt\u0151fel\u00fclet, ahol fekete h\u00e1tt\u00e9ren s\u00e1rg\u00e1val jelennek meg az akt\u00edv pixelek. Egy adott pixelen az eg\u00e9rrel kattintva a pixel invert\u00e1l\u00f3dik.
                    • Bal fels\u0151 sarokban a megjelen\u00edtett karakter l\u00e1that\u00f3
                    • \u2019c\u2019 gomb: Clear, minden akt\u00edv pixelt t\u00f6r\u00f6l
                    • \u2019+\u2019 gomb: nagy\u00edt\u00e1s
                    • \u2019-\u2019 gomb: kicsiny\u00edt\u00e9s

                  Futtassuk az alkalmaz\u00e1st, \u00e9s vizsg\u00e1ljuk meg a m\u0171k\u00f6d\u00e9s\u00e9t a fentieknek megfelel\u0151en. Azt mindenk\u00e9ppen n\u00e9zz\u00fck meg, hogy ha egy karakter szerepel a mintasz\u00f6vegben, valamint t\u00f6bbsz\u00f6r megnyitjuk szerkeszt\u00e9sre, akkor az egyik n\u00e9zetben v\u00e1ltoztatva (egy pixelt invert\u00e1lva) valamennyi n\u00e9zete friss\u00fcl.

                  Az alkalmaz\u00e1s a k\u00f3dmennyis\u00e9g minim\u00e1lis \u00e9rt\u00e9ken tart\u00e1sa \u00e9rdek\u00e9ben minimalisztikus, pl. a hibakezel\u00e9s nincs \u00e1ltal\u00e1noss\u00e1g\u00e1ban kidolgozva, hi\u00e1nyoznak ellen\u0151rz\u00e9sek. Ugyanakkor k\u00f3dmegjegyz\u00e9sekkel el van l\u00e1tva, mely seg\u00edti a k\u00f3d ut\u00f3lagos meg\u00e9rt\u00e9s\u00e9t.

                  "},{"location":"labor/old-6-doc-view/#2-feladat-az-alkalmazas-megtervezese","title":"2. Feladat - Az alkalmaz\u00e1s megtervez\u00e9se","text":"

                  A c\u00e9l az, hogy l\u00e1ssuk, milyen folyamatot k\u00f6vetve, milyen l\u00e9p\u00e9sekben dolgozunk, mikor milyen tervez\u0151i l\u00e9p\u00e9seket kell meghoznunk. T\u00f6rekedj\u00fcnk oktat\u00f3i \u00e9s hallgat\u00f3i r\u00e9szr\u0151l is az interaktivit\u00e1sra, k\u00f6z\u00f6sen hozzuk meg a d\u00f6nt\u00e9seket.

                  Hozzunk l\u00e9tre egy \u00faj C# nyelv\u0171 \u201eWindow Form App\u201d projektet (.NET 6-osat), legyen a neve FontEditor. Vegy\u00fcnk fel egy oszt\u00e1lydiagramot: projekten jobb katt, Add / New Item, majd a megjelen\u0151 ablakban Class Diagram kiv\u00e1laszt\u00e1sa, a neve maradhat az alap\u00e9rtelmezett. \u00c1ll\u00edtsuk be, hogy a diagram mutassa majd a m\u0171veletek szignat\u00far\u00e1it is (pl. jobb katt a h\u00e1tt\u00e9ren, Change Members Format / Display Full Signature). A gyakorlat nagy r\u00e9sz\u00e9ben ezt a diagramot fogjuk szerkeszteni.

                  A k\u00e9sz oszt\u00e1lydiagram a k\u00f6vetkez\u0151, eddig fogunk fokozatosan eljutni:

                  "},{"location":"labor/old-6-doc-view/#document-view-architektura","title":"Document-View architekt\u00fara","text":"

                  Az els\u0151 tervez\u0151i d\u00f6nt\u00e9s: architekt\u00far\u00e1t kell v\u00e1lasztani. A Document-View eset\u00fcnkben egy\u00e9rtelm\u0171 v\u00e1laszt\u00e1s: dokumentumokkal dolgozunk, \u00e9s t\u00f6bb n\u00e9zettel, melyeket szinkronban kell tartani. Az al\u00e1bbi \u00e1bra ismerteti a m\u0171k\u00f6d\u00e9st. A n\u00e9zetek az observerek, a document pedig a subject, melynek v\u00e1ltoz\u00e1saira az egyes n\u00e9zetek fel vannak iratkozva.

                  A D-V architekt\u00far\u00e1b\u00f3l ad\u00f3d\u00f3an sz\u00fcks\u00e9g\u00fcnk lesz dokumentum oszt\u00e1lyra, amely a dokumentum adatait t\u00e1rolja (tagv\u00e1ltoz\u00f3kban), mint pl. a n\u00e9v, el\u00e9r\u00e9si \u00fat, pixelm\u00e1trix. Tegy\u00fck fel, hogy a k\u00e9s\u0151bbiekben t\u00f6bb dokumentum t\u00edpust is t\u00e1mogatni kell majd: pl. megnyithatunk egy olyan tabf\u00fclet, melyen a BKK j\u00e1rm\u0171vekhez tudjuk rendelni a bet\u0171t\u00edpusokat (elektronikus kijelz\u0151). Vannak olyan dokumentum adatok, melyek minden dokumentum t\u00edpusban megjelennek (pl. n\u00e9v, el\u00e9r\u00e9si \u00fat). Az egyes dokumentum t\u00edpusoknak a k\u00f6z\u00f6s tulajdons\u00e1gait/m\u0171veleteit c\u00e9lszer\u0171 egy Document \u0151soszt\u00e1lyba kiszervezni, hogy ne legyenek duplik\u00e1lva az egyes dokumentum t\u00edpusokat reprezent\u00e1l\u00f3 dokumentum oszt\u00e1lyokban.

                  • Vegy\u00fck fel a Document oszt\u00e1lyt (ez az absztrakt \u0151s).
                  • Vegy\u00fcnk fel bele egy string Name property-t (ez jelenik meg a tabf\u00fcleken).

                  A Document-View architekt\u00far\u00e1b\u00f3l ad\u00f3d\u00f3an sz\u00fcks\u00e9g van egy n\u00e9zet interf\u00e9szre (egy Update m\u0171velettel a n\u00e9zet \u00e9rtes\u00edt\u00e9s\u00e9hez), valamint a dokumentumoknak nyilv\u00e1n kell tartaniuk egy list\u00e1ban a n\u00e9zeteiket:

                  • Vegy\u00fck fel az IView interf\u00e9szt.
                  • Vegy\u00fcnk fel bele egy Update m\u0171veletet.
                  • A Document oszt\u00e1lyba vegy\u00fcnk fel egy List<IView> views mez\u0151t (a Fields-n\u00e9l). Jobb gombbal kattintsunk a mez\u0151 nev\u00e9n a diagramon, \u00e9s a men\u00fcb\u0151l Show as collection association kiv\u00e1laszt\u00e1sa.
                  • A Document oszt\u00e1lyba vegy\u00fcnk fel a void AttachView(IView view) m\u0171veletet, mellyel \u00faj n\u00e9zetet lehet beregisztr\u00e1lni.
                  • V\u00e9g\u00fcl vegy\u00fcnk fel egy void DetachView(IView view)-t, mert n\u00e9zetet bez\u00e1rni is lehet.

                  T\u00e1mogatnunk kell az egyes dokumentumok tartalm\u00e1nak perziszt\u00e1l\u00e1s\u00e1t (ment\u00e9s/bet\u00f6lt\u00e9s). Ezekhez vegy\u00fcnk fel a Document \u0151sbe a megfelel\u0151 m\u0171veleteket:

                  • Document-be LoadDocument(string path) felv\u00e9tele.
                  • Document-be SaveDocument(string path) felv\u00e9tele.
                  • Mindkett\u0151 legyen absztrakt, hiszen csak az egyes dokumentum lesz\u00e1rmazottakban tudunk implement\u00e1ci\u00f3t megadni: szelekt\u00e1ljuk ki a k\u00e9t m\u0171veletet, \u00e9s a Properties ablakban az Inheritence modifier legyen Abstract.

                  Az egyes dokumentumoknak t\u00e1mogatniuk kell a n\u00e9zeteik friss\u00edt\u00e9s\u00e9t, ez minden dokumentum t\u00edpusra k\u00f6z\u00f6s:

                  • A Document-be vegy\u00fck fel az UpdateAllViews()-t (ez felel meg az Observer minta Notify m\u0171velet\u00e9nek).
                  "},{"location":"labor/old-6-doc-view/#konkret-dokumentum-es-adatai","title":"Konkr\u00e9t dokumentum \u00e9s adatai","text":"

                  Sz\u00fcks\u00e9g van egy olyan dokumentum t\u00edpusra, ami a bet\u0171t\u00edpusok szerkeszt\u00e9s\u00e9hez tartozik, amely a tagv\u00e1ltoz\u00f3iban nyilv\u00e1ntartja a sz\u00fcks\u00e9ges adatokat: legyen a neve FontEditorDocument.

                  • Vegy\u00fck fel a FontEditorDocument oszt\u00e1lyt.
                  • Sz\u00e1rmaztassuk a Document-b\u0151l (Toolbox \u2013 Inheritence kapcsolat).
                  • Ekkor a LoadDocument \u00e9s SaveDocument m\u0171veletekre automatikusan megsz\u00fcletik az override-ol\u00f3 m\u0171velet. Ha m\u00e9gsem lenne \u00edgy
                    • Jel\u00f6lj\u00fck ki az \u0151sben a k\u00e9t m\u0171veletet.
                    • Copy
                    • Jel\u00f6lj\u00fck ki a FontEditorDocument oszt\u00e1lyt.
                    • Paste
                    • Jel\u00f6lj\u00fck itt ki a k\u00e9t m\u0171veletet, \u00e9s a Properties ablakban a Instance Modifier legyen override.

                  A dokumentumunk tagv\u00e1ltoz\u00f3kban t\u00e1rolja az adatokat. Gondoljuk \u00e1t, hogy ezt hogyan c\u00e9lszer\u0171 megval\u00f3s\u00edtani. Lehetne egy h\u00e1romdimenzi\u00f3s t\u00f6mb (karakter \u2013 x \u2013 y), de ink\u00e1bb emelj\u00fck ki egy k\u00fcl\u00f6n oszt\u00e1lyba az egy adott karakter pixeleinek t\u00e1rol\u00e1s\u00e1t/menedzsel\u00e9s\u00e9t: vezess\u00fck be a CharDef oszt\u00e1lyt.

                  Pixel t\u00f6mb helyett

                  Az\u00e9rt nem a pixelt\u00f6mb\u00f6t haszn\u00e1ljuk k\u00f6zvetlen\u00fcl, mert csak egy \u00faj oszt\u00e1ly bevezet\u00e9s\u00e9vel van lehet\u0151s\u00e9g\u00fcnk kifejezetten ide tartoz\u00f3 m\u0171veletek bevezet\u00e9s\u00e9re, vagyis az egys\u00e9gbez\u00e1r\u00e1s korrekt megval\u00f3s\u00edt\u00e1s\u00e1ra.

                  • Vegy\u00fck fel a CharDef oszt\u00e1lyt.
                  • CharDef-be bool[,] Pixels tulajdons\u00e1g felv\u00e9tele.

                    t\u00f6bbdimenzo\u00f3s t\u00f6mb\u00f6k C#-ban

                    A fenti p\u00e9ld\u00e1ban egy t\u00f6bbdimenzi\u00f3s t\u00f6mb\u00f6t haszn\u00e1ltunk bool[,] \u00e9s nem t\u00f6mb\u00f6k t\u00f6mbj\u00e9t bool[][], mivel ezt nyelvi szinten is t\u00e1mogatja a C# \u00e9s jobb teljes\u00edtm\u00e9nyt ny\u00fajt, mint a t\u00f6mb\u00f6k t\u00f6mbje, mert egy objektumk\u00e9nt t\u00f6rol\u00f3dik a heapen.

                  • CharDef-be char Character felv\u00e9tele: az egyes CharDef oszt\u00e1lyok t\u00e1rolj\u00e1k magukr\u00f3l, hogy mely karakter pixeleit reprezent\u00e1lj\u00e1k.

                  A dokumentumnak lesz egy gy\u0171jtem\u00e9nye CharDef objektumokb\u00f3l: minden karakterhez pontosan egy darab. Gondoljuk \u00e1t, hogy a legc\u00e9lszer\u0171bb ezt megval\u00f3s\u00edtani. Az egyes karakterdefin\u00edci\u00f3kat a karakterk\u00f3djukkal akarjuk c\u00edmezni, \u00edgy a Dictionary<char, CharDef> ide\u00e1lis v\u00e1laszt\u00e1s: a karakterk\u00f3d a kulcs, az hozz\u00e1 tartoz\u00f3 CharDef pedig az \u00e9rt\u00e9k.

                  • FontEditorDocument-be: Dictionary<char, CharDef> charDefs mez\u0151 felv\u00e9tele. Jobb katt, Show as collection association.
                  "},{"location":"labor/old-6-doc-view/#dokumentumok-menedzselese-app-singleton-osztaly","title":"Dokumentumok menedzsel\u00e9se - App Singleton oszt\u00e1ly","text":"

                  Az alkalmaz\u00e1sban nyilv\u00e1n kell tartani a megnyitott dokumentumok list\u00e1j\u00e1t. Mely oszt\u00e1ly felel\u0151ss\u00e9ge legyen? Vezess\u00fcnk be r\u00e1 egy alkalmaz\u00e1sszint\u0171 oszt\u00e1lyt: legyen a neve App (Windows Forms alatt m\u00e1r van Application, nem c\u00e9lszer\u0171 ezt a nevet v\u00e1lasztani). Ez lesz az alkalmaz\u00e1sunk \u201egy\u00f6k\u00e9roszt\u00e1lya\u201d.

                  • Vegy\u00fck fel az App oszt\u00e1lyt.
                  • App-ba List<FontEditorDocument> documents mez\u0151 felv\u00e9tele, majd Show as collection association.

                  Gondoljuk v\u00e9gig, hogyan t\u00f6rt\u00e9nik majd egy \u00faj dokumentum l\u00e9trehoz\u00e1sa (mi t\u00f6rt\u00e9nik a File/New men\u00fcelem kiv\u00e1laszt\u00e1sakor): be kell k\u00e9rni a felhaszn\u00e1l\u00f3t\u00f3l a dokumentum nev\u00e9t, l\u00e9tre kell hozni egy FontEditorDocument objektumot, fel kell venni a megnyitott dokumentumok list\u00e1j\u00e1ba stb. Ezt a logik\u00e1t ne tegy\u00fck a GUI-ba (men\u00fcelem click esem\u00e9nykezel\u0151): tegy\u00fck abba az oszt\u00e1lyba, melynek a felel\u0151ss\u00e9ge a megnyitott dokumentumok menedzsel\u00e9se, amely t\u00e1rolja a sz\u00fcks\u00e9ges adatokat hozz\u00e1 (dokumentum lista). \u00cdgy legyen ez az App oszt\u00e1lyunk feladata, benne vegy\u00fck fel a sz\u00fcks\u00e9ges m\u0171veleteket:

                  • App-ba NewDocument \u00e9s OpenDocument m\u0171veletek felv\u00e9tele.

                  Most a dokumentum ment\u00e9st gondoljuk v\u00e9gig: a File/Save mindig az akt\u00edv dokumentumra vonatkozik. Valakinek nyilv\u00e1n kell tartani, melyik az akt\u00edv dokumentum: legyen ez az App, hiszen \u0151 t\u00e1rolja a dokumentumok list\u00e1j\u00e1t is.

                  • A Toolbox-on v\u00e1lasszuk ki az Association kapcsolatot. Az App-b\u00f3l h\u00fazzunk egy nyilat a FontEditorDocument-be. V\u00e1lasszuk ki az \u00fajonnan l\u00e9trehozott kapcsolatot, \u00e9s nevezz\u00fck \u00e1t ActiveDocument-re.
                  • App-ba void SaveActiveDocument() felv\u00e9tele.
                  • App-ba void CloseActiveDocument\u00e1() felv\u00e9tele.

                  Konkr\u00e9t dokumentumra vagy absztrakt \u0151sre hivatkozzunk?

                  Mivel az App oszt\u00e1lyunk alkalmaz\u00e1s specifikus funkci\u00f3kat l\u00e1t el, nyugodtan hivatkozhat a konkr\u00e9t dokumentum t\u00edpusra, \u00e9s felesleges az absztrakt \u0151st\u0151l f\u00fcggen\u00fcnk, mert az csak nem k\u00edv\u00e1nt castol\u00e1sokhoz vezetne.

                  Az App objektumb\u00f3l \u00e9rtelemszer\u0171en csak egyet kell/szabad l\u00e9trehozni, amely a fut\u00f3 alkalmaz\u00e1st reprezent\u00e1lja. Van m\u00e9g egy probl\u00e9m\u00e1nk: a File/Save stb. men\u00fcelem click esem\u00e9nykezel\u0151ben el kell \u00e9rj\u00fck ezt az egy objektumot. Illetve, majd t\u00f6bb m\u00e1s helyen is. J\u00f3 lenne, ha nem kellene minden oszt\u00e1lyban k\u00fcl\u00f6n el\u00e9rhet\u0151v\u00e9 tenni (tagv\u00e1ltoz\u00f3 vagy f\u00fcggv\u00e9nyparam\u00e9ter form\u00e1j\u00e1ban), hanem b\u00e1rhonnan egyszer\u0171en el\u00e9rhet\u0151 lenne. Erre ny\u00fajt megold\u00e1st a Singleton tervez\u00e9si minta. Egy oszt\u00e1lyb\u00f3l csak egy objektumot enged l\u00e9trehozni, \u00e9s ahhoz glob\u00e1lis hozz\u00e1f\u00e9r\u00e9st biztos\u00edt, m\u00e9gpedig az oszt\u00e1ly nev\u00e9n \u00e9s egy statikus Instance property-n kereszt\u00fcl, pl. \u00edgy: App.Instance.SaveDocument stb. Nem val\u00f3s\u00edtjuk meg teljes \u00e9rt\u00e9k\u0171en, de tegy\u00fck meg az al\u00e1bbiakat:

                  • App-ba App Instance property felv\u00e9tele. Properties ablakban static: true.
                  • App-ba priv\u00e1t konstruktor felv\u00e9tele.

                  Az App-oszt\u00e1llyal v\u00e9gezt\u00fcnk.

                  "},{"location":"labor/old-6-doc-view/#nezetek","title":"N\u00e9zetek","text":"

                  A n\u00e9zetekkel eddig nem foglalkoztunk, ez a k\u00f6vetkez\u0151 l\u00e9p\u00e9s. Futtassuk a k\u00e9sz alkalmaz\u00e1st, \u00e9s n\u00e9zz\u00fck meg, hogy h\u00e1ny t\u00edpus\u00fa n\u00e9zetre van sz\u00fcks\u00e9g, melyikb\u0151l h\u00e1ny p\u00e9ld\u00e1ny lesz:

                  • K\u00e9t t\u00edpus\u00fa n\u00e9zetre van sz\u00fcks\u00e9g: az egyik a mintasz\u00f6veget jelen\u00edti meg, a m\u00e1sik egy adott karakter szerkeszt\u00e9s\u00e9t teszi lehet\u0151v\u00e9.
                  • Legyen az el\u0151z\u0151 neve SampleTextView, az ut\u00f3bbi\u00e9 FontEditorView.
                  • SampleTextView-b\u00f3l mindig egy van (egy adott dokumentumra vonatkoz\u00f3an), a FontEditorView objektumok ig\u00e9ny szerint j\u00f6nnek l\u00e9tre, 0..n p\u00e9ld\u00e1ny l\u00e9tezhet.
                  • Vegy\u00fck fel a k\u00e9t oszt\u00e1lyt.
                  • Implement\u00e1ltassuk vel\u00fck az IView interf\u00e9szt (Toolbox / Inheritence kapcsolat). Az Update m\u0171velet automatikusan implement\u00e1lva lesz.

                  Az egyes n\u00e9zetek a dokumentumukb\u00f3l \u201et\u00e1pl\u00e1lkoznak\u201d, a a dokumentumukban t\u00e1rolt adatokat jelen\u00edtik meg, azokat m\u00f3dos\u00edtj\u00e1k. Ehhez, a D-V architekt\u00far\u00e1nak megfelel\u0151en el kell \u00e9rj\u00e9k a dokumentumukat.

                  • A SampleTextView \u00e9s FontEditorView-ban vegy\u00fcnk fel egy FontEditorDocument t\u00edpus\u00fa document nev\u0171 mez\u0151t (ha felvett\u00fck az egyikben, lehet copy-paste-tel m\u00e1solni a m\u00e1sikba), majd \"Show as Association\". Megjegyz\u00e9s: az\u00e9rt nem c\u00e9lszer\u0171 \u00e1ltal\u00e1nos Document t\u00edpus\u00fat felvenni (\u00e9s az interf\u00e9szbe felvinni), mert a view-knak a konkr\u00e9t dokumentum adatait (l\u00e1sd al\u00e1bb) el kell \u00e9rni\u00fck.

                  Gondoljuk v\u00e9gig, milyen adattagokkal rendelkeznek az egyes n\u00e9zetek. Ehhez futtassuk az alkalmaz\u00e1st, \u00e9s n\u00e9zz\u00fck meg ism\u00e9t a felhaszn\u00e1l\u00f3i fel\u00fclet\u00e9t.

                  • A SampleTextView t\u00e1rolja a mintasz\u00f6veget, melyet meg kell jelen\u00edteni. Vegy\u00fcnk fel egy sampleText:string mez\u0151t. Ha el kellene menteni a mintasz\u00f6veget is, akkor a FontEditorDocument-ben kellene t\u00e1rolni (\u00e9s onnan mindig lek\u00e9rdezni), mert az adatok ment\u00e9s\u00e9\u00e9rt a dokumentum oszt\u00e1lyunk a felel\u0151s.
                  • A FontEditorView k\u00e9t dolgot t\u00e1rol:
                    • A karakter k\u00f3dja, melynek pixeleit megjelen\u00edti. Vegy\u00fcnk fel egy editedChar: char mez\u0151t.
                    • A nagy\u00edt\u00e1si t\u00e9nyez\u0151t (zoom: double felv\u00e9tele)

                  A n\u00e9zetek maguk felel\u0151sek a kirajzol\u00e1suk\u00e9rt:

                  • Draw (g:Graphics) felv\u00e9tele mindk\u00e9t n\u00e9zetbe.
                  "},{"location":"labor/old-6-doc-view/#fonteditordocument-muveletek","title":"FontEditorDocument m\u0171veletek","text":"

                  A FontEditorDocument-ben egy priv\u00e1t list\u00e1ban van egyel\u0151re jelen a CharDef-ek list\u00e1ja. A n\u00e9zetek \u00edgy nem tudj\u00e1k el\u00e9rni, pedig a megjelen\u00edt\u00e9shez sz\u00fcks\u00e9g\u00fck lenne r\u00e1. A dokumentumunkban be kell vezess\u00fcnk olyan m\u0171veleteket, melyek a dokumentum \u00e1ltal t\u00e1rolt adatokat a n\u00e9zetek sz\u00e1m\u00e1ra el\u00e9rhet\u0151v\u00e9 teszik, \u00e9s lehet\u0151s\u00e9get biztos\u00edtanak a m\u00f3dos\u00edt\u00e1sra is.

                  • Mindk\u00e9t n\u00e9zet el kell \u00e9rje a megjelen\u00edtett karakterek pixeleit t\u00e1rol\u00f3 CharDef objektumokat. Ehhez vezess\u00fck be a FontEditorDocument-ben a GetCharDef(c:char):CharDef m\u0171veletet. Ezt hossz\u00fa t\u00e1von majd \u00fagy lesz c\u00e9lszer\u0171 megval\u00f3s\u00edtani, hogy a GetCharDef nem az eredeti objektumot adja vissza, hanem annak egy m\u00e1solat\u00e1t (clone). Ha az eredetit adn\u00e1 vissza, akkor a n\u00e9zetek K\u00d6ZVETLEN\u00dcL tudn\u00e1k m\u00f3dos\u00edtani a pixelek \u00e9rt\u00e9k\u00e9t, ezt mi nem akarjuk (b\u00e1r a funkci\u00f3k b\u0151v\u00edt\u00e9s\u00e9vel r\u00e1k\u00e9nyszer\u00fclhet\u00fcnk).
                  • A FontEditorView-nak k\u00e9pesnek kell lennie egy adott CharDef adott koordin\u00e1t\u00e1ban lev\u0151 pixel \u00e9rt\u00e9k\u00e9t invert\u00e1lni (eg\u00e9r kattint\u00e1skor). Ehhez vezess\u00fck be a FontEditorDocument-ben az InvertCharDefPixel(c:char, x: int, y: int) m\u0171veletet.
                  "},{"location":"labor/old-6-doc-view/#a-tervezes-zarasa","title":"A tervez\u00e9s z\u00e1r\u00e1sa","text":"

                  Eljutottunk oda, hogy megtervezt\u00fck az architekt\u00far\u00e1t, minden igaz\u00e1n l\u00e9nyeges d\u00f6nt\u00e9st meghoztunk. Az UML diagram alapj\u00e1n megsz\u00fcletett az oszt\u00e1lyok v\u00e1za. Ezt term\u00e9szetesen jelent\u0151sen b\u0151v\u00edteni kell, m\u00e9g sz\u00fcletnek \u00faj oszt\u00e1lyok is (pl. Form-ok, vez\u00e9rl\u0151k).

                  "},{"location":"labor/old-6-doc-view/#3-feladat-a-kesz-alkalmazas-attekintese","title":"3. Feladat - A k\u00e9sz alkalmaz\u00e1s \u00e1ttekint\u00e9se","text":"

                  Id\u0151 hi\u00e1ny\u00e1ban nem val\u00f3s\u00edtjuk meg az alkalmaz\u00e1st, hanem a k\u00e9sz megold\u00e1st n\u00e9zz\u00fck \u00e1t (laboron kb. 15 percben), annak is csak n\u00e9h\u00e1ny l\u00e9nyeges haszn\u00e1lati eset\u00e9t.

                  T\u00f6lts\u00fck le a k\u00e9sz megold\u00e1st. Ehhez parancssorban navig\u00e1ljunk a c:\\work\\ mapp\u00e1ba (ha a laborban dolgozunk), \u00e9s adjuk ki a k\u00f6vetkez\u0151 parancsot:

                  git clone https://github.com/bmeviauab00/lab-docview-megoldas

                  Nyissuk meg a k\u00e9sz solution-t, futtassuk \u00e9s pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1s alapfunkci\u00f3it.

                  "},{"location":"labor/old-6-doc-view/#nezetek-megvalositasa","title":"N\u00e9zetek megval\u00f3s\u00edt\u00e1sa","text":"

                  Nyissuk meg a FontEditorView-t, el\u0151sz\u00f6r a k\u00f3dot n\u00e9zz\u00fck. A FontEditorView egyr\u00e9szt implement\u00e1lja az IView interf\u00e9szt, m\u00e1sr\u00e9szt a UserControl-b\u00f3l sz\u00e1rmazik. M\u00e9gpedig az\u00e9rt, mert \u00edgy a tervez\u0151ben (designer) tudjuk kialak\u00edtani a felhaszn\u00e1l\u00f3i fel\u00fclet\u00e9t, pont \u00fagy, mint egy \u0171rlapnak. A Visual Studio designer fel\u00fclet\u00e9n ak\u00e1r bele is m\u00f3dos\u00edthatn\u00e1nk a layoutba \u00e9s a vez\u00e9rl\u0151k tulajdons\u00e1gaiba. Ha k\u00edv\u00e1ncsiak vagyunk, ki is pr\u00f3b\u00e1lhatjuk ezt (pl. a nagy\u00edt\u00e1s \u00e9s a kicsiny\u00edt\u00e9s gombok hely\u00e9nek megv\u00e1ltoztat\u00e1s\u00e1val).

                  A SampleTextView is UserControl lesz\u00e1rmazott, b\u00e1r annak egyszer\u0171 a fel\u00fclete (nincsenek rajta m\u00e1s vez\u00e9rl\u0151k), \u00edgy lehetett volna k\u00f6z\u00f6ns\u00e9ges Control lesz\u00e1rmazott is.

                  Vonjuk le a tanuls\u00e1got: Windows Forms k\u00f6rnyezetben a n\u00e9zeteket tipikusan UserControl-k\u00e9nt (esetleg Control-k\u00e9nt) c\u00e9lszer\u0171 megval\u00f3s\u00edtani.

                  "},{"location":"labor/old-6-doc-view/#egy-oldal-tab-elrendezese","title":"Egy oldal (tab) elrendez\u00e9se","text":"

                  Futtassuk az alkalmaz\u00e1st. Valahogy ki kell alak\u00edtsuk egy adott oldal (tabpage) elrendez\u00e9s\u00e9t. Lehet\u0151leg tervez\u0151i n\u00e9zetben, \u00e9s nem fut\u00e1s k\u00f6zben, k\u00f3db\u00f3l poz\u00edcion\u00e1lva az elemeket (legal\u00e1bbis ahol nem musz\u00e1j). A UserControl-ok alkalmaz\u00e1sa jelenti sz\u00e1munkra a megold\u00e1st. Nyissuk meg a FontDocumentControl-t tervez\u0151i n\u00e9zetben. Ez egy olyan vez\u00e9rl\u0151, amely egy taboldalra ker\u00fcl fel, azt t\u00f6lti ki teljesen. Az oldalt a m\u00e1r ismert layout technik\u00e1kkal alak\u00edtottuk ki (Label, TextBox, Panel-ek Dock-kolva). Ha van id\u0151nk, akkor n\u00e9zz\u00fck meg a Document Outline ablakban. Az igazi \u00e9rdekess\u00e9g pedig az, hogy a SampleTextView-t is a Toolbox-r\u00f3l drag&drop-pal ker\u00fclt felhelyez\u00e9sre (pont \u00fagy, mintha egy be\u00e9p\u00edtett vez\u00e9rl\u0151 lenne). Annyit n\u00e9zz\u00fcnk meg, hogy a SampleTextView val\u00f3ban ott van a Toolbox tetej\u00e9n.

                  "},{"location":"labor/old-6-doc-view/#forgatokonyv-1-egy-pixel-invertalasa-nezetek-szinkronizalasa","title":"Forgat\u00f3k\u00f6nyv 1 \u2013 Egy pixel invert\u00e1l\u00e1sa, n\u00e9zetek szinkroniz\u00e1l\u00e1sa","text":"

                  Ez egy kiemelt jelent\u0151s\u00e9g\u0171 forgat\u00f3k\u00f6nyv, mert ezt illusztr\u00e1lja a D-V architekt\u00fara alapmechanizmus\u00e1t, a n\u00e9zetek friss\u00edt\u00e9s\u00e9t \u00e9s konzisztensen tart\u00e1s\u00e1t. Keress\u00fck meg azt a f\u00fcggv\u00e9nyt, ahol az eg\u00e9sz pixel invert\u00e1l\u00e1s folyamat elindul. A FontEditorView.FontEditorView_MouseClick a kiindul\u00f3pont. Itt az al\u00e1bb kiemelt sor a l\u00e9nyeg:

                  private void FontEditorView_MouseClick(object sender, MouseEventArgs e)\n{\n    int x = e.X / zoom;\n    int y = (e.Y - offsetY) / zoom;\n    if (x >= CharDef.FontSize.Width)\n        return;\n\n    document.InvertCharDefPixel(editedChar, x, y);\n}\n

                  N\u00e9zz\u00fck meg a FontEditorDocument.InvertCharDefPixel-t. Az invert\u00e1lja a megfelel\u0151 CharDef pixel\u00e9t, de a l\u00e9nyeg az utols\u00f3 sor:

                  public void InvertCharDefPixel(char c, int x, int y)\n{\n    var charDef = GetCharDefCore(c);\n    if (charDef == null)\n        return;\n\n    charDef.Pixels[x, y] = !charDef.Pixels[x, y];\n\n    UpdateAllViews();\n}\n

                  Az UpdateAllViews a Document \u0151sben van, Update-et h\u00edv minden n\u00e9zetre. Ami \u00e9rdekes, hogy az Update hogyan van meg\u00edrva az egyes n\u00e9zetekben. N\u00e9zz\u00fck meg pl. a FontEditView-t:

                  public void Update()\n{\n    Invalidate();\n}\n

                  Az Update hat\u00e1s\u00e1ra a n\u00e9zetek \u00fajra kell rajzolj\u00e1k magukat az aktu\u00e1lis dokumentum \u00e1llapot alapj\u00e1n. De az Update-ben nem tudunk rajzolni, csak az OnPaint-ben. \u00cdgy itt az Invalidate h\u00edv\u00e1ssal kiv\u00e1ltjuk a Paint esem\u00e9nyt. Ez megint egy tanuls\u00e1g: Windows Forms alkalmaz\u00e1sokban a n\u00e9zetek Update f\u00fcggv\u00e9ny\u00e9ben tipikusan egy Invalidate h\u00edv\u00e1s szokott lenni.

                  Z\u00e1r\u00e1sk\u00e9ppen n\u00e9zz\u00fck meg a FontEditView.OnPaint megval\u00f3s\u00edt\u00e1s\u00e1t. Egyetlen l\u00e9nyeges dolog van itt: a megjelen\u00edt\u00e9shez le kell k\u00e9rni a dokumentumt\u00f3l az aktu\u00e1lis CharDef-et (mert a n\u00e9zet a D-V architekt\u00fara alapelveinek megfelel\u0151en nem t\u00e1rolja), majd ki kell azt rajzolni.

                  protected override void OnPaint(PaintEventArgs e)\n{\n    base.OnPaint(e);\n\n    var editedCharDef = document.GetCharDef(editedChar);\n\n    CharDefViewModel.DrawFont(e.Graphics, editedCharDef, 0, offsetY, zoom);\n}\n

                  Kirajzol\u00e1s logik\u00e1ja

                  Mivel a kirajzol\u00e1s logik\u00e1ja a FontEditorView-ban \u00e9s a SampleTextView-ban is azonosan m\u0171k\u00f6dik a Graphics oszt\u00e1ly haszn\u00e1lat\u00e1val, kiszervezt\u00fck ezt egy CharDefViewModel seg\u00e9doszt\u00e1lyba az \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1g kedv\u00e9\u00e9rt.

                  A CharDef-be nem c\u00e9lszer\u0171 rakni ezt a logik\u00e1t, mivel az egy n\u00e9zet f\u00fcggetlen adatreprezent\u00e1ci\u00f3, \u00e9s sokkal ink\u00e1bb a dokumentumhoz tartozik, mint a n\u00e9zethez.

                  "},{"location":"labor/old-6-doc-view/#forgatokonyv-2-uj-dokumentum-letrehozasa-opcionalis","title":"Forgat\u00f3k\u00f6nyv 2 \u2013 \u00daj dokumentum l\u00e9trehoz\u00e1sa (opcion\u00e1lis)","text":"

                  Azt n\u00e9zz\u00fck meg, hogyan t\u00f6rt\u00e9nik egy \u00faj dokumentum l\u00e9trehoz\u00e1sa, vagyis mi t\u00f6rt\u00e9nik a File/New men\u00fcelem kiv\u00e1laszt\u00e1sakor.

                  Nyissuk meg a MainForm-ot tervez\u0151i n\u00e9zetben, v\u00e1laszuk a File/New men\u00fcelemet, majd ugorjunk el a Click esem\u00e9nykezel\u0151h\u00f6z. Arra l\u00e1tunk p\u00e9ld\u00e1t, hogy az App oszt\u00e1ly, mint Singleton, hogy \u00e9rhet\u0151 el:

                  App.Instance.NewDocument();\n

                  Az \u00f6sszes t\u00f6bbi men\u00fcelem esem\u00e9nykezel\u0151je hasonl\u00f3, nincs semmi logika a GUI-ban, csak egyszer\u0171 tov\u00e1bbh\u00edv\u00e1s az App-ba.

                  Tekints\u00fck \u00e1t az App.NewDocument t\u00f6rzs\u00e9t, \u00e9s egy-egy mondatban fussuk \u00e1t a fontosabb l\u00e9p\u00e9seket.

                  1. NewDocForm n\u00e9zet megnyit\u00e1sa \u00e9s v\u00e1rakoz\u00e1s a v\u00e1laszra.
                  2. Sikeres v\u00e1lasz eset\u00e9n \u00faj FontEditorDocument l\u00e9trehoz\u00e1sa \u00e9s felv\u00e9tele a dokumentumok k\u00f6z\u00e9, valamint akt\u00edvv\u00e1 t\u00e9tele.
                  3. \u00daj tab l\u00e9trehoz\u00e1sa a n\u00e9zetekkel.
                  public void NewDocument()\n{\n    // Bek\u00e9rj\u00fckk az \u00faj font t\u00edpus (dokumentum) nev\u00e9t a\n    // felhaszn\u00e1l\u00f3t\u00f3l egy mod\u00e1lis dial\u00f3gs ablakban.\n    var form = new NewDocForm(GetDocumentNames());\n    if (form.ShowDialog() != DialogResult.OK)\n        return;\n\n    // \u00daj dokumentum objektum l\u00e9trehoz\u00e1sa \u00e9s felv\u00e9tele a dokumentum list\u00e1ba.\n    var doc = new FontEditorDocument(form.FontName);\n    documents.Add(doc);\n\n    // Az \u00faj tab lesz az akt\u00edv, az activeDocument tagv\u00e1ltoz\u00f3t erre kell \u00e1ll\u00edtani.\n    UpdateActiveDocument(doc.Name);\n\n    CreateTabForNewDocument(doc);\n}\n

                  App oszt\u00e1ly felel\u0151ss\u00e9gi k\u00f6re

                  Az egyszer\u0171s\u00e9g \u00e9rdek\u00e9ben az App oszt\u00e1ly most t\u00f6bb felel\u0151ss\u00e9ggel is rendelkezik, de ide\u00e1lis esetben sz\u00e9t lenne szedve pl. a k\u00f6vetkez\u0151 oszt\u00e1lyokra a felel\u0151ss\u00e9gi k\u00f6r\u00f6knek megfelel\u0151en:

                  • DocumentManager: a megjelen\u00edt\u00e9st\u0151l f\u00fcggetlen\u00fcl a dokumentumokat t\u00e1roln\u00e1.
                  • ViewManager: feladata a n\u00e9zetek menedzsel\u00e9se, tabcontrolokhoz hozz\u00e1ad\u00e1sa stb. lenne.

                  Az App.OpenDocument m\u0171velet t\u00f6rzse nincs implement\u00e1lva, de a l\u00e9p\u00e9sek k\u00f3dmegjegyz\u00e9sek form\u00e1j\u00e1ban adottak, remek otthoni gyakorl\u00e1si lehet\u0151s\u00e9g a m\u0171velet t\u00e9nyleges megval\u00f3s\u00edt\u00e1sa.

                  "},{"location":"labor/old-7-tervezesi-mintak/","title":"7. Tervez\u00e9si mint\u00e1k","text":""},{"location":"labor/old-7-tervezesi-mintak/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                  A gyakorlat c\u00e9ljai:

                  • Egy \u00f6sszetettebb p\u00e9lda alapj\u00e1n n\u00e9h\u00e1ny tervez\u00e9si minta gyakorlati alkalmaz\u00e1sa (els\u0151dlegesen Singleton, Command Processor \u00e9s Memento).
                  • A Document-View minta tov\u00e1bbi gyakorl\u00e1sa, illetve annak demonstr\u00e1l\u00e1sa, hogy a mint\u00e1nak t\u00f6bb vari\u00e1nsa l\u00e9tezik.
                  • Alapszint\u0171 betekint\u00e9st nyerni az \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1got t\u00e1mogat\u00f3 oszt\u00e1lyk\u00f6nyvt\u00e1rak/keretrendszerek fejleszt\u00e9s\u00e9nek vil\u00e1g\u00e1ba.
                  • Jelent\u0151s\u00e9g\u00fcknek megfelel\u0151en tov\u00e1bb gyakoroljuk az objektumorient\u00e1lt paradigma legfontosabb koncepci\u00f3it (pl. felel\u0151ss\u00e9gek k\u00fcl\u00f6nv\u00e1laszt\u00e1sa).

                  Kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sok:

                  • Tervez\u00e9si mint\u00e1k
                  • Szoftver architekt\u00far\u00e1k t\u00e9mak\u00f6rb\u0151l a Document-View architekt\u00fara
                  • Windows Forms alkalmaz\u00e1sok fejleszt\u00e9se
                  "},{"location":"labor/old-7-tervezesi-mintak/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                  A gyakorlat elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                  • Visual Studio 2022
                  • Windows 10 vagy Windows 11 oper\u00e1ci\u00f3s rendszer (Linux \u00e9s macOS nem alkalmas)
                  "},{"location":"labor/old-7-tervezesi-mintak/#megoldas","title":"Megold\u00e1s","text":"A k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se

                  L\u00e9nyeges, hogy a labor sor\u00e1n a laborvezet\u0151t k\u00f6vetve kell dolgozni, tilos (\u00e9s \u00e9rtelmetlen) a k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se. Ugyanakkor az ut\u00f3lagos \u00f6n\u00e1ll\u00f3 gyakorl\u00e1s sor\u00e1n hasznos lehet a k\u00e9sz megold\u00e1s \u00e1ttekint\u00e9se, \u00edgy ezt el\u00e9rhet\u0151v\u00e9 tessz\u00fck.

                  A megold\u00e1s GitHubon \u00e9rhet\u0151 el itt. A legegyszer\u0171bb m\u00f3d a let\u00f6lt\u00e9s\u00e9re, ha parancssorb\u00f3l a git clone utas\u00edt\u00e1ssal lekl\u00f3nozzuk a g\u00e9p\u00fcnkre:

                  git clone https://github.com/bmeviauab00/lab-designpattern-kiindulo -b megoldas-refactor-elott

                  Ehhez telep\u00edtve kell legyen a g\u00e9pre a parancssori git, b\u0151vebb inform\u00e1ci\u00f3 itt.

                  "},{"location":"labor/old-7-tervezesi-mintak/#bevezeto","title":"Bevezet\u0151","text":""},{"location":"labor/old-7-tervezesi-mintak/#elmeleti-hatter","title":"Elm\u00e9leti h\u00e1tt\u00e9r","text":"

                  A komplexebb alkalmaz\u00e1sok fejleszt\u00e9se sor\u00e1n sz\u00e1mos tervez\u0151i d\u00f6nt\u00e9st kell meghoznunk, melyek sor\u00e1n t\u00f6bb lehet\u0151s\u00e9g k\u00f6z\u00fcl is v\u00e1laszthatunk. Amennyiben ezen pontokban olyan d\u00f6nt\u00e9seket hozunk, melyek nem k\u00f6vetik az objektumorient\u00e1lt szeml\u00e9letm\u00f3d alapelveit, nem tartjuk szem el\u0151tt az alkalmaz\u00e1sunk k\u00f6nny\u0171 karbantarthat\u00f3s\u00e1g\u00e1t, illetve egyszer\u0171en megval\u00f3s\u00edthat\u00f3 tov\u00e1bbfejleszt\u00e9si lehet\u0151s\u00e9g\u00e9t, k\u00f6nnyen hamar r\u00e9m\u00e1lomm\u00e1 v\u00e1lhat a fejleszt\u00e9s. Az egyes hib\u00e1k jav\u00edt\u00e1sa folyamatosan \u00faj hib\u00e1kat sz\u00fcl. Ezen fel\u00fcl a megrendel\u0151i v\u00e1ltoztat\u00e1si \u00e9s b\u0151v\u00edt\u00e9si ig\u00e9nyek a k\u00f3d nagym\u00e9rt\u00e9k\u0171 folyamatos \u00e1t\u00edr\u00e1s\u00e1t ig\u00e9nylik ahelyett, hogy a k\u00f3d p\u00e1r j\u00f3l meghat\u00e1rozott pontj\u00e1ban t\u00f6rt\u00e9n\u0151 b\u0151v\u00edt\u00e9s\u00e9vel - a megl\u00e9v\u0151 k\u00f3d jelent\u0151s m\u00f3dos\u00edt\u00e1sa n\u00e9lk\u00fcl - el tudn\u00e1nk ezt \u00e9rni. A tervez\u00e9si mint\u00e1k j\u00f3l bev\u00e1lt megold\u00e1sokat mutatnak bizonyos gyakran el\u0151fordul\u00f3 tervez\u00e9si probl\u00e9m\u00e1kra: ezen megold\u00e1sok abban seg\u00edtenek, hogy k\u00f3dunk k\u00f6nnyebben b\u0151v\u00edthet\u0151, karbantarthat\u00f3 \u00e9s min\u00e9l nagyobb m\u00e9rt\u00e9kben \u00fajrafelhaszn\u00e1lhat\u00f3 legyen. Ugyanakkor ne ess\u00fcnk \u00e1t a l\u00f3 t\u00faloldal\u00e1ra: csak akkor \u00e9rdemes egy adott tervez\u00e9si mint\u00e1t bevetni, ha adott esetben val\u00f3s el\u0151nyt jelent az alkalmaz\u00e1sa. Ellenkez\u0151 esetben csak a megval\u00f3s\u00edt\u00e1s komplexit\u00e1s\u00e1t n\u00f6veli feleslegesen.

                  "},{"location":"labor/old-7-tervezesi-mintak/#a-feladat-ismertetese","title":"A feladat ismertet\u00e9se","text":"

                  A feladatunk egy vektorgrafikus rajzol\u00f3program kifejleszt\u00e9se:

                  • Az alkalmaz\u00e1sban vektorgrafikus alakzatokat lehet l\u00e9trehozni, \u00fagymint t\u00e9glalap, ellipszis stb.
                  • A m\u00e1r l\u00e9trehozott alakzatokat egy grafikus fel\u00fcleten meg kell jelen\u00edteni (ki kell rajzolni).
                  • A m\u00e1r l\u00e9trehozott alakzatok fontosabb param\u00e9tereit, \u00fagymint koordin\u00e1t\u00e1k, befoglal\u00f3 t\u00e9glalap meg kell jelen\u00edteni egy list\u00e1ban egy inform\u00e1ci\u00f3s panelen.
                  • Windows Forms technol\u00f3gi\u00e1ra \u00e9p\u00edtve dolgozunk.
                  • Document-View architekt\u00far\u00e1t k\u00f6vetj\u00fck, de egyszerre csak egy dokumentum lehet megnyitva (nincsenek dokumentumonk\u00e9nt tabf\u00fclek vagy ablakok).
                  • Egy adott pontig el\u0151k\u00e9sz\u00edtett k\u00f6rnyezetet visz\u00fcnk tov\u00e1bb. A munka mennyis\u00e9g\u00e9nek kezelhet\u0151 szinten tart\u00e1sa v\u00e9gett csak bizonyos pontig vissz\u00fck tov\u00e1bb a fejleszt\u00e9st, nem val\u00f3s\u00edtjuk meg a teljes \u00e9rt\u00e9k\u0171 megold\u00e1st. Az \u00fajonnan besz\u00farand\u00f3 sorokat az \u00fatmutat\u00f3ban kiemelt h\u00e1tt\u00e9r jelzi.
                  "},{"location":"labor/old-7-tervezesi-mintak/#1-feladat-a-kiindulasi-kornyezet-megismerese","title":"1. Feladat - A kiindul\u00e1si k\u00f6rnyezet megismer\u00e9se","text":"

                  Kl\u00f3nozzuk le a gyakorlathoz tartoz\u00f3 kiindul\u00f3 alkalmaz\u00e1s repositoryj\u00e1t:

                  • Nyissunk egy command prompt-ot,
                  • Navig\u00e1ljunk el egy tetsz\u0151leges mapp\u00e1ba, p\u00e9ld\u00e1ul c:\\work\\NEPTUN
                  • Adjuk ki a k\u00f6vetkez\u0151 parancsot: git clone https://github.com/bmeviauab00/lab-designpattern-kiindulo
                  • Nyissuk meg a DesignPatternApp.sln solutiont Visual Studio-ban.

                  Futtassuk az alkalmaz\u00e1st, az al\u00e1bbihoz hasonl\u00f3 fel\u00fcletet l\u00e1tunk (amennyiben a File/New men\u00fcelemet kiv\u00e1lasztjuk):

                  Ismerkedj\u00fcnk meg m\u0171k\u00f6d\u00e9s\u00e9nek n\u00e9h\u00e1ny aspektus\u00e1val:

                  • A legt\u00f6bb funkci\u00f3 nincs m\u00e9g megval\u00f3s\u00edtva.
                  • A File/New men\u00fcelem valamint a toolbar els\u0151 gombja egy \u00faj dokumentumot hoz l\u00e9tre. Ez m\u00e1r m\u0171k\u00f6dik, pr\u00f3b\u00e1ljuk ki.
                  • Mivel \u00faj alakzatot jelen pillanatban m\u00e9g nem tudunk l\u00e9trehozni, a dokumentum a l\u00e9trej\u00f6tt\u00e9t k\u00f6vet\u0151en nem \u00fcres, tartalmaz n\u00e9mi tesztel\u00e9st szolg\u00e1l\u00f3 adatot (k\u00e9t t\u00e9glalapot \u00e9s egy ellipszist).
                  • Az alakzatok kirajzol\u00e1sa is meg van val\u00f3s\u00edtva. Ezen fel\u00fcl a jobb oldali inform\u00e1ci\u00f3s panelen l\u00e1thatjuk a m\u00e1r l\u00e9tez\u0151 alakzatok param\u00e9tereit.
                  • Az alakzatok k\u00f6z\u00fcl egy ki lehet v\u00e1lasztva: ez piros sz\u00ednnel \u00e9s szaggatott k\u00e9k kerettel ker\u00fcl kirajzol\u00e1sra, illetve az inform\u00e1ci\u00f3s panelen ki is van v\u00e1lasztva az alakzathoz tartoz\u00f3 sor. \u00daj alakzat kijel\u00f6l\u00e9s\u00e9re az inform\u00e1ci\u00f3s panelen a megfelel\u0151 sor kiv\u00e1laszt\u00e1s\u00e1val van m\u00f3d. Ezt pr\u00f3b\u00e1ljuk is ki. Azt tapasztaljuk, hogy v\u00e1ltoztat\u00e1skor a bal oldali grafikus fel\u00fclet is friss\u00fcl, a kiv\u00e1lasztott alakzat sz\u00edne piros lesz. Hangs\u00falyozzuk, hogy ez bizony a klasszikus dokumentum-n\u00e9zet architekt\u00fara alap\u00fa megk\u00f6zel\u00edt\u00e9s ig\u00e9ny\u00e9t veti fel: a dokumentumunkhoz k\u00e9t n\u00e9zet kapcsol\u00f3dik, melyeket konzisztensen kell tartani. Megjegyz\u00e9s: a teljes \u00e9rt\u00e9k\u0171 megold\u00e1sban a bal oldali grafikus n\u00e9zetben is megval\u00f3s\u00edthatn\u00e1nk az eg\u00e9rkattint\u00e1sra t\u00f6rt\u00e9n\u0151 kijel\u00f6l\u00e9st. Ez jelent\u0151sen komplexebb\u00e9 tenn\u00e9 a k\u00e9s\u0151bbi feladataink megval\u00f3s\u00edt\u00e1s\u00e1t, \u00edgy ezt sz\u00e1nd\u00e9kosan kihagyjuk.
                  "},{"location":"labor/old-7-tervezesi-mintak/#megvalositando-funkciok","title":"Megval\u00f3s\u00edtand\u00f3 funkci\u00f3k","text":"

                  A k\u00f6vetkez\u0151 funkci\u00f3kat fogjuk a gyakorlat sor\u00e1n megval\u00f3s\u00edtani:

                  • \u00daj t\u00e9glalap \u00e9s \u00faj ellipszis l\u00e9trehoz\u00e1sa v\u00e9letlen poz\u00edci\u00f3ban. A funkci\u00f3k az eszk\u00f6zs\u00e1von (toolbar) \u00e9s a Tools men\u00fc alatt is el\u00e9rhet\u0151k.
                  • Dokumentum tartalm\u00e1nak t\u00f6rl\u00e9se. Minden alakzatot elt\u00e1vol\u00edt a dokumentumb\u00f3l. File/Clear men\u00fcvel el\u00e9rhet\u0151.
                  • Visszavon\u00e1s (Undo). Az utols\u00f3 parancs visszavon\u00e1sa, ak\u00e1rh\u00e1ny l\u00e9p\u00e9sig visszamen\u0151en. Visszavonja az utols\u00f3 parancsot, legyen az valamilyen \u00faj alakzat l\u00e9trehoz\u00e1sa, vagy a dokumentum tartalm\u00e1nak t\u00f6rl\u00e9se. Az eszk\u00f6zs\u00e1von \u00e9s \u00e9s az Edit/Undo men\u00fcvel is el\u00e9rhet\u0151.
                  "},{"location":"labor/old-7-tervezesi-mintak/#a-solution-felepitese","title":"A solution fel\u00e9p\u00edt\u00e9se","text":"

                  A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben a kiindul\u00f3 k\u00f3db\u00e1zissal fogunk megismerkedni. Gyakorlatilag az architekt\u00far\u00e1nk komponens n\u00e9zet\u00e9t tekintj\u00fck \u00e1t ebben a l\u00e9p\u00e9sben. A Visual Studio Solution Explorer ablak\u00e1ban megfigyelhet\u0151, hogy solution\u00fcnk k\u00e9t projektet is tartalmaz.

                  • AppFx: Egy oszt\u00e1lyk\u00f6nyvt\u00e1r (egy DLL a kimenete). Az ebben tal\u00e1lhat\u00f3 oszt\u00e1lyok \u00e1ltal\u00e1nos dokumentumkezel\u00e9si \u00e9s parancskezel\u00e9si szolg\u00e1ltat\u00e1sokat val\u00f3s\u00edtanak meg, melyek ak\u00e1r t\u00f6bb alkalmaz\u00e1sban is felhaszn\u00e1lhat\u00f3k. Az oszt\u00e1lyk\u00f6nyvt\u00e1r bevezet\u00e9s\u00e9vel az els\u0151dleges c\u00e9lunk teh\u00e1t az \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1g el\u00e9r\u00e9se.
                  • DesignPatternApp: A futtathat\u00f3 (.exe) alkalmaz\u00e1sunk projektje, mely \u00e9p\u00edt az AppFx oszt\u00e1lyk\u00f6nyvt\u00e1rra.

                  F\u00fcgg\u0151s\u00e9g a projektek k\u00f6z\u00f6tt

                  Visual Studio projektek k\u00f6z\u00f6tt mindig csak egyir\u00e1ny\u00fa f\u00fcgg\u0151s\u00e9g lehet. Jelen esetben a DesignPatternApp \u00e9p\u00edt az AppFx projektre. Ezt a gyakorlatban \u00fagy val\u00f3s\u00edtottuk meg, hogy a DesignPatternApp projektben felvett\u00fcnk egy referenci\u00e1t az AppFx projektre. Ett\u0151l kezdve az DesignPatternApp-ban el\u00e9rhet\u0151k az AppFx (publikus) oszt\u00e1lyai. Ford\u00edtva viszont nem igaz az \u00e1ll\u00edt\u00e1s, \u00e9s ennek el\u00e9r\u00e9s\u00e9re nincs is m\u00f3d.

                  "},{"location":"labor/old-7-tervezesi-mintak/#document-view-architektura","title":"Document-View architekt\u00fara","text":"

                  Az alkalmaz\u00e1sunk a Document-View architekt\u00far\u00e1ra \u00e9p\u00fcl, annak n\u00e9mik\u00e9ppen tov\u00e1bbfejlesztett koncepci\u00f3j\u00e1t val\u00f3s\u00edtja meg: ahelyett, hogy a n\u00e9zeteknek egy Update m\u0171velete lenne, amelyen kereszt\u00fcl \u00e1ltal\u00e1nos v\u00e1ltoz\u00e1s \u00e9rtes\u00edt\u00e9st kapnak a dokumentumukt\u00f3l, a dokumentumok k\u00fcl\u00f6nb\u00f6z\u0151 v\u00e1ltoz\u00e1si esem\u00e9nyeket publik\u00e1lhatnak: minden n\u00e9zet arra az esem\u00e9nyre fizet el\u0151, \u00e9s arr\u00f3l kap \u00e9rtes\u00edt\u00e9st, mely sz\u00e1m\u00e1ra \u00e9rdekes.

                  • Az AppFx projekt DocView mapp\u00e1j\u00e1ban tal\u00e1lhat\u00f3 egy Document oszt\u00e1ly \u00e9s egy IView interf\u00e9sz. N\u00e9zz\u00fck meg \u0151ket sorban (nyissuk is meg a forr\u00e1sf\u00e1jlt):
                    • Document: K\u00fcl\u00f6nb\u00f6z\u0151 dokumentum t\u00edpusok \u0151soszt\u00e1lyak\u00e9nt szolg\u00e1lhat. T\u00f6bbek k\u00f6z\u00f6tt van egy n\u00e9zet list\u00e1ja. (Megjegyz\u00e9s: mivel alkalmaz\u00e1sunk a Document-View architekt\u00fara egy speci\u00e1lis vari\u00e1ns\u00e1t haszn\u00e1lja, a n\u00e9zet list\u00e1t az \u0151sb\u0151l el is hagyhattuk volna).
                    • IView: K\u00fcl\u00f6nb\u00f6z\u0151 n\u00e9zet implement\u00e1ci\u00f3k k\u00f6z\u00f6s interf\u00e9sze. Nincs Update m\u0171velet, helyette egy SetDocumentAndRegisterToDocEvents m\u0171veletet tal\u00e1lunk (ebben kell a n\u00e9zetnek a dokumentum megfelel\u0151 esem\u00e9nyeire beregisztr\u00e1lnia).

                  A k\u00f6vetkez\u0151kben a DesignPatternApp projekt kapcsol\u00f3d\u00f3 oszt\u00e1lyait tekintj\u00fck \u00e1t:

                  • DrawingDocument oszt\u00e1ly

                    • Egy shapes nev\u0171 list\u00e1ban t\u00e1rolja az alakzatokat.
                    • A selectedShape az aktu\u00e1lisan kiv\u00e1lasztott alakzatra mutat.
                    • A dokumentum adatai a Shapes, SelectedShape \u00e9s SelectedShapeIndex tulajdons\u00e1gokon kereszt\u00fcl \u00e9rhet\u0151k el a k\u00fclvil\u00e1g (pl. n\u00e9zetek) sz\u00e1m\u00e1ra.
                    • A kor\u00e1bban ismertetett koncepci\u00f3nknak megfelel\u0151en a dokumentumunk k\u00e9t esem\u00e9nyt is publik\u00e1l, melyek C# esem\u00e9nyk\u00e9nt vannak megval\u00f3s\u00edtva:
                      • ShapesChanged: azt jelzi, hogy az alakzatok list\u00e1ja megv\u00e1ltozott, pl. \u00faj alakzattal b\u0151v\u00fclt, vagy kiker\u00fclt egy alakzat a list\u00e1b\u00f3l, vagy ak\u00e1r egy alakzat adatai v\u00e1ltoztak meg a list\u00e1ban.
                      • SelectionChanged: azt jelzi, hogy egy kor\u00e1bbit\u00f3l elt\u00e9r\u0151 alakzat ker\u00fclt kiv\u00e1laszt\u00e1sra (mely piros sz\u00ednnel jelenik meg rajzol\u00e1skor).
                    • A CreateRect \u00e9s CreateEllipse m\u0171veletek l\u00e9trehoznak egy megfelel\u0151 alakzatot, amit a dokumentum el is t\u00e1rol (\u00e9s term\u00e9szetesen el is s\u00fcti a ShapesChanged esem\u00e9nyt).
                  • ViewBase oszt\u00e1ly

                    • A n\u00e9zeteink k\u00f6z\u00f6s \u0151soszt\u00e1lya, a k\u00f3dduplik\u00e1ci\u00f3 elker\u00fcl\u00e9s\u00e9re vezett\u00fck be. Implement\u00e1lja az IView interf\u00e9szt.
                    • UserControl-b\u00f3l sz\u00e1rmazik (hasonl\u00f3 koncepci\u00f3t m\u00e1r l\u00e1ttunk a megel\u0151z\u0151 gyakorlat FontEditor p\u00e9ld\u00e1j\u00e1ban).
                    • A document tagv\u00e1ltoz\u00f3ban t\u00e1rolja a n\u00e9zetet.
                    • A dokumentum megfelel\u0151 esem\u00e9nyeire val\u00f3 fel/leiratkoz\u00e1shoz bevezeti a RegisterToDocEvents \u00e9s UnRegisterToDocEvents virtu\u00e1lis m\u0171veleteket, a lesz\u00e1rmazottakban ig\u00e9ny szerint kell implement\u00e1lni.
                  • GraphicsView oszt\u00e1ly
                    • Az alkalmaz\u00e1sunk bal oldali, grafikus n\u00e9zet\u00e9nek implement\u00e1ci\u00f3ja.
                    • A ViewBase-b\u0151l sz\u00e1rmazik, \u00edgy k\u00f6zvetve ezen oszt\u00e1lyunk is egy UserControl.
                    • A RegisterToDocEvents m\u0171velet\u00e9ben a dokumentum mindk\u00e9t esem\u00e9ny\u00e9re (ShapesChanged \u00e9s SelectionChanged) el\u0151fizet, ugyanazt a DocumentOnShapesChanged esem\u00e9nykezel\u0151 f\u00fcggv\u00e9nyt regisztr\u00e1lja be. Az esem\u00e9nykezel\u0151ben egy egyszer\u0171 Invalidate h\u00edv\u00e1st tal\u00e1lunk, mely kik\u00e9nyszer\u00edti a n\u00e9zet\u00fcnk \u00fajrarajzol\u00e1s\u00e1t.
                    • Az OnPaint megval\u00f3s\u00edt\u00e1s\u00e1nak alapelve: minden alakzatra megh\u00edvjuk a Draw m\u0171veletet, mely gondoskodik a t\u00e9nyleges megjelen\u00edt\u00e9sr\u0151l.
                  • InfoPanel oszt\u00e1ly
                    • Az alkalmaz\u00e1sunk jobb oldali inform\u00e1ci\u00f3s panel n\u00e9zet\u00e9nek implement\u00e1ci\u00f3ja.
                    • Szint\u00e9n a ViewBase-b\u0151l sz\u00e1rmazik, \u00edgy k\u00f6zvetve ezen oszt\u00e1lyunk is egy UserControl.
                    • Az inform\u00e1ci\u00f3k megjelen\u00edt\u00e9s\u00e9re egy ListBox vez\u00e9rl\u0151t haszn\u00e1l.
                    • A RegisterToDocEvents m\u0171velet\u00e9ben \u0151 is feliratkozik a dokumentum mindk\u00e9t esem\u00e9ny\u00e9re:
                      • Amikor a dokumentum ShapesChanged esem\u00e9nye s\u00fcl el, az InfoPanel Document_ShapesChanged m\u0171velete h\u00edv\u00f3dik meg: ebben friss\u00edti a listbox tartalm\u00e1t a dokumentum aktu\u00e1lis \u00e1llapot\u00e1nak megfelel\u0151en.
                      • Amikor a dokumentum SelectionChanged esem\u00e9nye s\u00fcl el, a Document_SelectionChanged h\u00edv\u00f3dik: ebben a listbox megfelel\u0151 elem\u00e9t \u00e1ll\u00edtjuk be kiv\u00e1lasztottnak.
                    • Amikor a felhaszn\u00e1l\u00f3 egy \u00faj elemet v\u00e1laszt ki a listboxban, a listBox_SelectedIndexChanged esem\u00e9nykezel\u0151 h\u00edv\u00f3dik: ebben az App.Instance.SetSelectedShape() h\u00edv\u00e1ssal az aktu\u00e1lis dokumentumunkban \u00e1ll\u00edtjuk \u00e1t a kiv\u00e1lasztott alakzatot a listbox felhaszn\u00e1l\u00f3 \u00e1ltal kiv\u00e1lasztott sor\u00e1nak megfelel\u0151en.
                  • Shape, Rect, Ellipse oszt\u00e1lyok
                    • A Shape a k\u00f6z\u00f6s \u0151s, az egyes alakzatok ennek lesz\u00e1rmazottai.
                    • A gyakorlat sor\u00e1n ezek k\u00f3dj\u00e1ra \u00e9ppen csak n\u00e9zz\u00fcnk r\u00e1, ezek implement\u00e1ci\u00f3s r\u00e9szletei sz\u00e1munkra most kev\u00e9sb\u00e9 izgalmasak. Megjegyz\u00e9s: Az egyik feladat megval\u00f3s\u00edt\u00e1sa sor\u00e1n m\u00e1solatot k\u00e9sz\u00edt\u00fcnk majd az alakzatokr\u00f3l. Annak \u00e9rdek\u00e9ben, hogy az alakzatot \u00e9s a m\u00e1solatait \u00f6ssze tudjuk \u201etal\u00e1ltatni\u201d, az egyes alakzatokhoz egy sz\u00e1mazonos\u00edt\u00f3t rendel\u00fcnk (Id tag), mely az alakzat \u00e9s m\u00e1solatai eset\u00e9ben ugyanazt az \u00e9rt\u00e9ket veszi fel.
                  • App oszt\u00e1ly
                    • Az alkalmaz\u00e1sunk \u201eroot\u201d oszt\u00e1lya, mag\u00e1t az alkalmaz\u00e1st reprezent\u00e1lja. Szerepe hasonl\u00f3, mint az el\u0151z\u0151 gyakorlat FontEditor p\u00e9ld\u00e1j\u00e1ban.
                    • Megval\u00f3s\u00edt\u00e1sa a Singleton tervez\u00e9si minta fontosabb elveit k\u00f6veti: egy p\u00e9ld\u00e1ny l\u00e9tezhet bel\u0151le, mely egy statikus Instance nev\u0171 tulajdons\u00e1gon kereszt\u00fcl \u00e9rhet\u0151 el, a konstruktora pedig v\u00e9dett.
                    • Mivel alkalmaz\u00e1sunkban egyszerre egy dokumentum lehet megnyitva (\u00fan. SDI, Single Document Interface application), egy jelent\u0151s egyszer\u0171s\u00edt\u00e9ssel \u00e9lhett\u00fcnk: a dokumentumunkb\u00f3l \u00e9s mindk\u00e9t n\u00e9zet t\u00edpusunkb\u00f3l egy-egy objektumra van csak sz\u00fcks\u00e9g. Ezekre az App oszt\u00e1lyunkban el is t\u00e1rolunk egy-egy hivatkoz\u00e1st az al\u00e1bbi tagv\u00e1ltoz\u00f3kban:
                          private DrawingDocument document;\n    private GraphicsView graphicsView;\n    private InfoPanel infoPanel;\n
                    • Az Initialize m\u0171veletben lev\u0151 CommandBindingManager h\u00edv\u00e1sokra k\u00e9s\u0151bb t\u00e9r\u00fcnk vissza.
                  "},{"location":"labor/old-7-tervezesi-mintak/#command-binding","title":"Command Binding","text":"

                  A komplex felhaszn\u00e1l\u00f3 fel\u00fclettel rendelkez\u0151 alkalmaz\u00e1sok eset\u00e9n gyakran el\u0151fordul, hogy ugyanazt a parancsot k\u00fcl\u00f6nb\u00f6z\u0151 felhaszn\u00e1l\u00f3i fel\u00fcletelemekhez is hozz\u00e1 szeretn\u00e9nk k\u00f6tni. P\u00e9ld\u00e1ul New/Open/Close/Cut/Copy/stb. parancsok a legt\u00f6bb alkalmaz\u00e1sban egyar\u00e1nt el\u00e9rhet\u0151k men\u00fcb\u0151l \u00e9s eszk\u00f6zs\u00e1vr\u00f3l is. Vagyis a parancs \u00e9s a kiv\u00e1lt\u00f3 fel\u00fcletelemek k\u00f6z\u00f6tt egy-t\u00f6bb kapcsolat van. Ilyen esetben egy adott parancs vonatkoz\u00e1s\u00e1ban a k\u00f6vetkez\u0151ket kell megval\u00f3s\u00edtani:

                  • Parancs futtat\u00e1sa. Ak\u00e1rmelyik fel\u00fcletelemet is aktiv\u00e1lja a felhaszn\u00e1l\u00f3, a parancshoz tartoz\u00f3 ugyanazon \u201eesem\u00e9nykezel\u0151\u201d k\u00f3dot kell futtatni. Ez a gyakorlatban egy adott f\u00fcggv\u00e9ny megh\u00edv\u00e1s\u00e1t jelenti.
                  • Fel\u00fcletelemek \u00e1llapotkezel\u00e9se. Adott parancs vonatkoz\u00e1s\u00e1ban valamennyi fel\u00fcletelem \u00e1llapot\u00e1t konzisztensen kell tartani. Vagyis ha pl. letiltjuk az Undo men\u00fct, mert nincs visszavonhat\u00f3 m\u0171velet, akkor a visszavon\u00e1sra szolg\u00e1l\u00f3 Undo toolbar gombot is le kell tiltani. Hasonl\u00f3k\u00e9ppen, ha egy men\u00fcelemet el akarunk rejteni, akkor a kapcsol\u00f3d\u00f3 toolbar gombot is rejteni kell. De m\u00e9g fel\u00fcletelemek kijel\u00f6lts\u00e9gi \u00e1llapot\u00e1t is akarhatjuk szab\u00e1lyozni, erre is \u00e9l a szab\u00e1lyunk.

                  N\u00e9zz\u00fcnk erre p\u00e9ld\u00e1kat az alkalmaz\u00e1sunkban:

                  1. Ind\u00edtsuk el az alkalmaz\u00e1st. Figyelj\u00fck meg, hogy a File men\u00fc alatt a Save/Save As/Close men\u00fcelemek \u00e9s az ezeknek megfelel\u0151 toolbar gombok (a Close a harmadik gomb a toolbaron) is le vannak tiltva: am\u00edg nem hoztunk l\u00e9tre vagy t\u00f6lt\u00f6tt\u00fcnk be dokumentumot, ezen parancsok futtat\u00e1s\u00e1nak nincs \u00e9rtelme.
                  2. Hozzunk l\u00e9tre egy \u00faj dokumentumot (File/New men\u00fc). Ekkor valamennyi, az el\u0151z\u0151 pontban eml\u00edtett fel\u00fcletelem konzisztens m\u00f3don enged\u00e9lyezett lesz.
                  3. Ha bez\u00e1rjuk a dokumentumot (File/Close men\u00fc), ism\u00e9t valamennyi fel\u00fcletelem tiltott lesz.

                  Egy komplex alkalmaz\u00e1sban a fenti probl\u00e9mak\u00f6r egyszer\u0171 kezel\u00e9s\u00e9re c\u00e9lszer\u0171 egy k\u00f6zponti megold\u00e1st bevezetni. Ezt sz\u00e1mos m\u00f3don lehet implement\u00e1lni, a legt\u00f6bb k\u00f6rnyezet Command Binding n\u00e9ven hivatkozik a koncepci\u00f3ra. Sajnos az elnevez\u00e9s tekintet\u00e9ben nincs egys\u00e9gess\u00e9g: van olyan technol\u00f3gia, mely Command n\u00e9ven neves\u00edti ezt a technik\u00e1t. Mi Command minta alatt \u2013 a tervez\u00e9si mint\u00e1k klasszikus nevez\u00e9ktan\u00e1t k\u00f6vetve \u2013 m\u00e1st fogunk \u00e9rteni, egy k\u00e9s\u0151bbi feladatban t\u00e9r\u00fcnk majd r\u00e1.

                  A Command Binding minta alapelvei:

                  • Minden felhaszn\u00e1l\u00f3i parancshoz egy k\u00f6zponti (CommandBinding) objektumot hozunk l\u00e9tre.
                  • Ehhez hozz\u00e1k\u00f6tj\u00fck a parancs aktiv\u00e1l\u00e1sakor futtatand\u00f3 esem\u00e9nykezel\u0151t.
                  • Hozz\u00e1k\u00f6tj\u00fck valamennyi aktiv\u00e1l\u00f3 fel\u00fcletelemet (men\u00fc, toolbar gomb stb.)
                  • Seg\u00e9dm\u0171veleteket vezet\u00fcnk be a parancsok tilt\u00e1s\u00e1ra/enged\u00e9lyez\u00e9s\u00e9re/elrejt\u00e9s\u00e9re/megjelen\u00edt\u00e9s\u00e9re.
                  • A parancsokat a k\u00f6nny\u0171 azonos\u00edt\u00e1s \u00e9rdek\u00e9ben valamilyen egyszer\u0171 m\u00f3don, tipikusan stringgel azonos\u00edtjuk.

                  A solution\u00fcnk AppFx projektj\u00e9ben tal\u00e1lunk t\u00e1mogat\u00e1st a Command Binding megval\u00f3s\u00edt\u00e1s\u00e1ra (CommandBinding mappa). A megval\u00f3s\u00edt\u00e1s r\u00e9szletei sz\u00e1munkra teljesen \u00e9rdektelenek, gyakorlaton ne is n\u00e9zz\u00fck a k\u00f3dj\u00e1t, ink\u00e1bb a felhaszn\u00e1l\u00e1s\u00e1nak m\u00f3dj\u00e1t tekints\u00fck \u00e1t r\u00f6viden DesignPatternApp projekt\u00fcnkben:

                  • N\u00e9zz\u00fck meg a CommandName oszt\u00e1lyt: minden parancshoz egy string nevet vezett\u00fcnk be, mely a parancsot azonos\u00edtja (pl. \"Open\", \"Undo\" stb.).
                  • Inicializ\u00e1l\u00e1s: a CommandBinding objektumokat a MainForm oszt\u00e1ly initCommandBindings m\u0171veletben hozzuk l\u00e9tre \u00e9s k\u00f6tj\u00fck hozz\u00e1 az egyes men\u00fcelemekhez/toolbar gombokhoz. El\u00e9g, ha itt egy p\u00e9ld\u00e1t megn\u00e9z\u00fcnk a sok el\u0151fordul\u00e1s k\u00f6z\u00fcl.
                  • Ezt k\u00f6vet\u0151en adott parancshoz tartoz\u00f3 fel\u00fcletelemek \u00e1llapotai a CommandBindingManager oszt\u00e1ly \u00e1llapot\u00e1ll\u00edt\u00f3 seg\u00e9df\u00fcggv\u00e9nyeivel b\u00e1rmikor k\u00e9nyelmesen \u00e1ll\u00edthat\u00f3k (pl. EnableCommandBinding tilt\u00e1shoz/enged\u00e9lyez\u00e9shez). N\u00e9zz\u00fck meg, hogyan t\u00f6rt\u00e9nik ez a Save/Save As/Close parancsok vonatkoz\u00e1s\u00e1ban:
                    • Amikor az alkalmaz\u00e1s elindul, a parancsokat az App oszt\u00e1ly Initialize m\u0171velet\u00e9ben tiltjuk (a false m\u00e1sodik param\u00e9ter jelzi, hogy tiltani akarjuk a vez\u00e9rl\u0151t):
                      CommandBindingManager.Instance.EnableCommandBinding(\n    CommandName.CloseDocument, false);\nCommandBindingManager.Instance.EnableCommandBinding(\n    CommandName.SaveDocument, false);\nCommandBindingManager.Instance.EnableCommandBinding(\n    CommandName.SaveAsDocument, false);\n
                    • A parancsok enged\u00e9lyez\u00e9s\u00e9re az App.NewDocument-ben l\u00e1tunk p\u00e9ld\u00e1t. Ez a m\u0171velet egy parancs esem\u00e9nykezel\u0151je, a t\u00f6bbi esem\u00e9nykezel\u0151vel egy\u00fctt az App.CommandHandlers.cs f\u00e1jlban tal\u00e1lhat\u00f3 (az App oszt\u00e1ly \u201epartial\u201d, t\u00f6bb f\u00e1jlban van meg\u00edrva).
                      CommandBindingManager.Instance.EnableCommandBinding(\n    CommandName.CloseDocument, true);\nCommandBindingManager.Instance.EnableCommandBinding(\n    CommandName.SaveDocument, true);\nCommandBindingManager.Instance.EnableCommandBinding(\n    CommandName.SaveAsDocument, true);\n

                  A tov\u00e1bbi feladatok megval\u00f3s\u00edt\u00e1sa sor\u00e1n is a CommandBindingManager oszt\u00e1lyunkat fogjuk haszn\u00e1lni a parancsok tilt\u00e1s\u00e1hoz \u00e9s enged\u00e9lyez\u00e9s\u00e9hez.

                  "},{"location":"labor/old-7-tervezesi-mintak/#2-feladat-command-processor-minta","title":"2. Feladat - Command Processor minta","text":"

                  A feladat sor\u00e1n a Command, pontosabban annak tov\u00e1bbfejlesztett v\u00e1ltozata, a Command Processor tervez\u00e9si minta megval\u00f3s\u00edt\u00e1s\u00e1t fogjuk gyakorolni. Mindk\u00e9t minta elm\u00e9leti h\u00e1tter\u00e9t a kapcsol\u00f3d\u00f3 el\u0151ad\u00e1s ismerteti r\u00e9szletesen, UML diagramokkal illusztr\u00e1lva. A gyakorlat sor\u00e1n, \u00e9s \u00edgy jelen \u00fatmutat\u00f3ban is csak az elm\u00e9leti h\u00e1tt\u00e9r legfontosabb elemeire t\u00e9r\u00fcnk ki. L\u00e9nyeges, hogy a mint\u00e1t ne keverj\u00fck a m\u00e1r kor\u00e1bban ismertetett Command Binding mint\u00e1val, mert att\u00f3l elt\u00e9r\u0151 probl\u00e9m\u00e1ra mutat megold\u00e1st.

                  "},{"location":"labor/old-7-tervezesi-mintak/#a-command-processor-minta-koncepcioja","title":"A Command Processor minta koncepci\u00f3ja","text":"

                  A minta alapelve az, hogy minden felhaszn\u00e1l\u00f3i k\u00e9r\u00e9st egy k\u00fcl\u00f6n parancs (Command) objektumk\u00e9nt z\u00e1r egys\u00e9gbe. Ezen t\u00falmen\u0151en a v\u00e9grehajtott parancs objektumok elt\u00e1rol\u00e1sra ker\u00fclnek, ami lehet\u0151v\u00e9 teszi a kor\u00e1bban v\u00e9grehajtott parancsok visszavon\u00e1s\u00e1t. A k\u00f6vetkez\u0151kben \u00e1ttekintj\u00fck a minta m\u0171k\u00f6d\u00e9s\u00e9t. Els\u0151 l\u00e9p\u00e9sben m\u00e9g nem val\u00f3s\u00edtjuk meg, csak az alapelveire koncentr\u00e1lunk (b\u00e1r hogy k\u00f6nnyebben meg\u00e9rthet\u0151 legyen, a mint\u00e1t az alkalmaz\u00e1sunkra vet\u00edtve mutatjuk be). K\u00f6vetkezzen egy \u00e1bra, majd a hozz\u00e1 kapcsol\u00f3d\u00f3 gondolatok.

                  • Bevezet\u00fcnk egy Command \u0151soszt\u00e1lyt vagy interf\u00e9szt, melynek van egy Execute \u00e9s egy UnExecute absztrakt m\u0171velete (vagy nevezhetj\u00fck Do \u00e9s Undo-nak is \u0151ket, ha \u00fagy tartja kedv\u00fcnk).
                  • Az egyes felhaszn\u00e1l\u00f3i parancsokhoz bevezet\u00fcnk egy Command lesz\u00e1rmazott oszt\u00e1lyt.
                    • Els\u0151 k\u00f6rben a New Rect \u00e9s New Ellipse parancsokra vonatkoz\u00f3an k\u00edv\u00e1nunk Undo t\u00e1mogat\u00e1st bevezetni, \u00edgy ezekhez vezet\u00fcnk majd r\u00f6videsen be egy-egy \u00faj oszt\u00e1lyt, pl. NewRectCommand \u00e9s NewEllipseCommand n\u00e9ven.
                    • Ezen oszt\u00e1lyokban a parancsspecifikusan meg\u00edrjuk az Execute m\u0171veletet (pl. a NewRectCommand.Execute-ban felvesz\u00fcnk a dokumentumunkban egy \u00faj t\u00e9glalapot), az UnExecute-ban pedig visszacsin\u00e1ljuk a m\u0171velet hat\u00e1s\u00e1t.
                    • A Command lesz\u00e1rmazott oszt\u00e1lyok sokszor nem maguk val\u00f3s\u00edtj\u00e1k meg funkci\u00f3jukat, hanem deleg\u00e1lj\u00e1k azt egy vagy t\u00f6bb m\u00e1sik oszt\u00e1lynak. Ezt az oszt\u00e1lyt az UML diagramon Receiver n\u00e9ven t\u00fcntett\u00fck fel. A gyakorlatban nem \u00edgy szoktuk h\u00edvni. Alkalmaz\u00e1sunkban a Command-ok tipikusan az App oszt\u00e1lyba h\u00edvnak tov\u00e1bb, vagyis eset\u00fcnkben az App felel meg legt\u00f6bb esetben az \u00e1br\u00e1n szerepl\u0151 Receiver oszt\u00e1lynak.
                  • Bevezet\u00fcnk egy k\u00f6zponti CommandProcessor oszt\u00e1lyt k\u00e9t m\u0171velettel:
                    • ExecuteCommand: v\u00e9grehajtja a param\u00e9ter\u00fcl kapott parancsot (megh\u00edvja az Execute m\u0171velet\u00e9t), majd elt\u00e1rolja egy bels\u0151 stack gy\u0171jtem\u00e9nyben.
                    • UnExecuteLastCommand: kiveszi az utolj\u00e1ra v\u00e9grehajtott parancsot a command stack-b\u0151l, \u00e9s megh\u00edvja annak UnExecute m\u0171velet\u00e9t. Ezzel gyakorlatilag a parancs visszavon\u00e1s funkci\u00f3j\u00e1t (Undo) val\u00f3s\u00edtja meg.

                  L\u00e9nyeges, hogy a Command Binding mint\u00e1val ellent\u00e9tben itt a parancsok minden egyes futtat\u00e1s\u00e1hoz \u00faj Command objektumot hozunk l\u00e9tre, vagyis ha pl. h\u00e1romszor \u201efuttatjuk\" a NewRectCommand parancsot, akkor h\u00e1rom NewRectCommand objektumot hozunk ehhez l\u00e9tre. Ennek oka az, hogy a CommandProcessor command stack-j\u00e9ben h\u00e1rom parancsobjektumot kell elt\u00e1rolni (hiszen ezeket egym\u00e1st\u00f3l f\u00fcggetlen\u00fcl akarjuk visszavonni Undo eset\u00e9n).

                  "},{"location":"labor/old-7-tervezesi-mintak/#a-command-processor-minta-megvalositasa-alkalmazasunkban","title":"A Command Processor minta megval\u00f3s\u00edt\u00e1sa alkalmaz\u00e1sunkban","text":"

                  K\u00f6vess\u00fck az al\u00e1bbi l\u00e9p\u00e9seket:

                  1. Az AppFx projekt Command mapp\u00e1j\u00e1ban m\u00e1r l\u00e9tezik egy absztrakt Command oszt\u00e1ly, \u00edgy ezzel nincs teend\u0151nk.
                  2. A Command mapp\u00e1ban vegy\u00fck fel a parancsok menedzsel\u00e9s\u00e9\u00e9rt felel\u0151s az \u00e1ltal\u00e1nos CommandProcessor oszt\u00e1lyt:
                    public class CommandProcessor\n{\n    Stack<Command> commands = new Stack<Command>();\n\n    public void ExecuteCommand(Command cmd)\n    {\n        cmd.Execute();\n        commands.Push(cmd);\n    }\n\n    public void UnExecuteLastCommand()\n    {\n        // Ha \u00fcres, nem csin\u00e1lunk semmit\n        if (!commands.Any())\n            return;\n\n        Command lastCommand = commands.Pop();\n        lastCommand.UnExecute();\n    }\n\n    public void Clear()\n    {\n      commands.Clear();\n    }\n\n    public bool HasAny { get { return commands.Any(); } }\n}\n
                    A megval\u00f3s\u00edt\u00e1s sor\u00e1n a .NET be\u00e9p\u00edtett Stack<T> oszt\u00e1ly\u00e1t haszn\u00e1ljuk a command stack megval\u00f3s\u00edt\u00e1s\u00e1ra. A met\u00f3dusok implement\u00e1ci\u00f3ja egyszer\u0171, a kor\u00e1bban ismertetett logik\u00e1t k\u00f6veti.
                  3. Integr\u00e1ljuk be a CommandProcessor oszt\u00e1lyt az alkalmaz\u00e1sunkba. Vegy\u00fcnk fel egy tagv\u00e1ltoz\u00f3t az App oszt\u00e1lyba:
                    readonly CommandProcessor commandProcessor = new CommandProcessor();\n
                    Annak \u00e9rdek\u00e9ben, hogy ez forduljon, a forr\u00e1sf\u00e1jlban az AppFx.Command n\u00e9vteret \u201eusing-olni\u201d kell.
                  4. Az App.CommandHandlers.cs-be a CloseDocument v\u00e9g\u00e9re vegy\u00fck fel ezt a sort:
                    commandProcessor.Clear();\n
                    Ennek az a szerepe, hogy amikor bez\u00e1rjuk a dokumentumot, kipucoljuk az undo sort, hiszen a benne lev\u0151 elemek egy m\u00e1r bez\u00e1rt, nem l\u00e9tez\u0151 dokumentumra vonatkoznak.
                  5. Amikor egy parancsot futtatunk vagy visszavonunk, az Undo men\u00fct \u00e9s toolbar gombot is megfelel\u0151en tiltani vagy enged\u00e9lyezni kell: ha van legal\u00e1bb egy command a command stack-en, akkor enged\u00e9lyezz\u00fck, egy\u00e9bk\u00e9nt tiltjuk. Vezess\u00fcnk be egy-egy seg\u00e9df\u00fcggv\u00e9nyt az App oszt\u00e1lyba a parancsok futtat\u00e1s\u00e1hoz \u00e9s visszavon\u00e1s\u00e1hoz, melyek a kor\u00e1bban ismertetett CommandBindingManager oszt\u00e1lyunk seg\u00edts\u00e9g\u00e9vel gondoskodnak az Undo tilt\u00e1s\u00e1r\u00f3l/enged\u00e9lyez\u00e9s\u00e9r\u0151l is:
                    void executeCommand(Command cmd)\n{\n    commandProcessor.ExecuteCommand(cmd);\n    CommandBindingManager.Instance.EnableCommandBinding(\n                    CommandName.Undo, commandProcessor.HasAny);\n}\n
                    void unexecuteLastCommand()\n{\n    commandProcessor.UnExecuteLastCommand();\n    CommandBindingManager.Instance.EnableCommandBinding(\n                    CommandName.Undo, commandProcessor.HasAny);\n}\n
                    Az unexecuteLastCommand m\u0171veletet akkor kell megh\u00edvni, amikor a felhaszn\u00e1l\u00f3 az Undo funkci\u00f3t aktiv\u00e1lja. Az App.CommandHandlers.cs f\u00e1jlban lev\u0151 UndoLast met\u00f3dus egy CommandBinding seg\u00edts\u00e9g\u00e9vel m\u00e1r hozz\u00e1 van k\u00f6tve a fel\u00fcletelemekhez (Undo men\u00fc \u00e9s toolbar gomb), \u00edgy aktiv\u00e1l\u00e1sukkor meg is h\u00edv\u00f3dik. M\u00e1r csak az a dolgunk, hogy \u00e1t\u00edrjuk az UndoLast t\u00f6rzs\u00e9t:
                    public void UndoLast()\n{\n    unexecuteLastCommand();\n}\n
                  6. A k\u00f6vetkez\u0151 l\u00e9p\u00e9sekben Command lesz\u00e1rmazott oszt\u00e1lyokat hozunk l\u00e9tre az egyes alkalmaz\u00e1sspecifikus parancsokhoz. A DesignPatternApp projektben vegy\u00fcnk fel egy Commands nev\u0171 mapp\u00e1t (jobb katt a projekten, Add/New Folder men\u00fc), ebbe fogjuk az ide tartoz\u00f3 oszt\u00e1lyokat \u00f6sszegy\u0171jteni.
                  7. Ebbe a Commands mapp\u00e1ba vegy\u00fcnk fel egy NewRectCommand oszt\u00e1lyt a \u201eNew Rect\u201d funkci\u00f3 megval\u00f3s\u00edt\u00e1s\u00e1hoz, a k\u00f6vetkez\u0151 k\u00f3ddal:
                    using AppFx.Command;\n\u2026\n\nclass NewRectCommand : Command\n{\n    private int shapeId;\n\n    public override void Execute()\n    {\n        shapeId = App.Instance.CreateRandomRect().Id;\n    }\n\n    public override void UnExecute()\n    {\n        App.Instance.RemoveShape(shapeId);\n    }\n}\n
                    Az Execute m\u0171velet megh\u00edvja az App singleton CreateRandomRect m\u0171velet\u00e9t, amely felvesz egy \u00faj Rectangle objektumot a dokumentumban, v\u00e9letlenszer\u0171en gener\u00e1lt befoglal\u00f3 t\u00e9glalapban, \u00e9s visszat\u00e9r vele. Az \u00fajonnan l\u00e9trehozott Rect objektumra a NewRectCommand elt\u00e1rolja az alakzat azonos\u00edt\u00f3j\u00e1t a shapeId tagv\u00e1ltoz\u00f3ban. (Jelen pillanatban egy referencia t\u00e1rol\u00e1sa is el\u00e9g lenne, de mikor k\u00e9s\u0151bb a Memento megval\u00f3s\u00edt\u00e1sa sor\u00e1n m\u00e1solatot k\u00e9sz\u00edt\u00fcnk az alakzat objektumokr\u00f3l, a referencia haszn\u00e1lata m\u00e1r nem jelentene megold\u00e1st.) Az UnExecute m\u0171veletben az App singleton RemoveShape m\u0171velet\u00e9nek seg\u00edts\u00e9g\u00e9vel elt\u00e1vol\u00edtjuk a parancs \u00e1ltal l\u00e9trehozott alakzatot, \u00edgy visszavonjuk annak hat\u00e1s\u00e1t (n\u00e9zz\u00fck meg a k\u00f3dban, hogyan van megval\u00f3s\u00edtva).
                  8. Vegy\u00fcnk fel a Commands mapp\u00e1ba egy NewEllipseCommand oszt\u00e1lyt, \u00e9s implement\u00e1ljuk a NewRectCommand-hoz hasonl\u00f3 elveknek megfelel\u0151en:
                    using AppFx.Command;\n\nclass NewEllipseCommand : Command\n{\n    private int shapeId;\n\n    public override void Execute()\n    {\n        shapeId = App.Instance.CreateRandomEllipse().Id;\n    }\n\n    public override void UnExecute()\n    {\n        App.Instance.RemoveShape(shapeId);\n    }\n}\n
                  9. A NewRectCommand \u00e9s NewEllipseCommand oszt\u00e1lyainkat m\u00e9g nem haszn\u00e1ljuk sehol, most ezek bevet\u00e9se k\u00f6vetkezik. Amikor a felhaszn\u00e1l\u00f3 ak\u00e1r men\u00fcb\u0151l, ak\u00e1r toolbarr\u00f3l aktiv\u00e1lja a New Rect funkci\u00f3t, l\u00e9tre kell hozzunk egy NewRectCommand objektumot, \u00e9s futtatni kell seg\u00e9dm\u0171veleteink felhaszn\u00e1l\u00e1s\u00e1val. Keress\u00fck meg az App.CommandHandlers.cs f\u00e1jlban a NewRect met\u00f3dust. Ez egy CommandBinding seg\u00edts\u00e9g\u00e9vel m\u00e1r r\u00e1 van k\u00f6tve a megfelel\u0151 men\u00fcre/toolbar gombra, csak a t\u00f6rzs\u00e9ben lev\u0151 showNotImplemented() h\u00edv\u00e1st kell lecser\u00e9lni:
                    using DesignPatternApp.Commands;\n\u2026\npublic void NewRect()\n{\n    executeCommand( new NewRectCommand() );\n}\n
                    Ehhez hasonl\u00f3an alak\u00edtsuk \u00e1t a NewRect mellett tal\u00e1lhat\u00f3 NewEllipse m\u0171veletet is:
                    public void NewEllipse()\n{\n    executeCommand( new NewEllipseCommand() );\n}\n
                  10. Mostant\u00f3l tudunk \u00faj alakzatokat l\u00e9trehozni, \u00edgy \u00faj dokumentum l\u00e9trehoz\u00e1sakor tesztadatok automatikus felv\u00e9tel\u00e9re nincs sz\u00fcks\u00e9g: az App.NewDocument m\u0171veletben kommentezz\u00fck ki az addTestData h\u00edv\u00e1s\u00e1t.

                  Elk\u00e9sz\u00fclt\u00fcnk, tesztelj\u00fck a megold\u00e1sunkat:

                  1. Futtassuk az alkalmaz\u00e1st, \u00e9s hozzunk l\u00e9tre egy dokumentumot.
                  2. Figyelj\u00fck meg, hogy az Undo parancs (toolbar \u00e9s men\u00fc is) tiltva van.
                  3. A New Rect paranccsal hozzunk l\u00e9tre egy \u00faj t\u00e9glalapot. A t\u00e9glalap megjelenik, \u00e9s az Undo parancs enged\u00e9lyezett lesz.
                  4. Hozzunk l\u00e9tre n\u00e9h\u00e1ny tov\u00e1bbi alakzatot, t\u00e9glalapot \u00e9s ellipszist vegyesen.
                  5. Az Undo funkci\u00f3 haszn\u00e1lat\u00e1val vonjuk vissza a m\u0171veleteket mindaddig, am\u00edg nem marad alakzat: ekkor az Undo parancs letilt\u00e1sra ker\u00fcl.

                  Amennyiben a gyakorlat sor\u00e1n j\u00f3l \u00e1llunk id\u0151vel, a k\u00f3dot l\u00e9p\u00e9senk\u00e9nt futtatva is n\u00e9zz\u00fck vissza megold\u00e1sunk m\u0171k\u00f6d\u00e9s\u00e9t:

                  1. Tegy\u00fcnk egy t\u00f6r\u00e9spontot az App.CommandHandlers.cs-ben tal\u00e1lhat\u00f3 NewRect \u00e9s UndoLast m\u0171veletek t\u00f6rzs\u00e9be (mindk\u00e9t m\u0171velet egysoros).
                  2. Ind\u00edtsuk el debug m\u00f3dban az alkalmaz\u00e1st (F5).
                  3. Hozzunk l\u00e9tre egy dokumentumot, majd egy t\u00e9glalapot. A NewRect k\u00f3dj\u00e1b\u00f3l kiindulva az F11 billenty\u0171vel az executeCommand \u00e9s a CommandProcessor m\u0171veleteibe belel\u00e9pve \u201e\u00e9rtelmezz\u00fck\u201d megold\u00e1sunkat.
                  4. Ezt k\u00f6vet\u0151en vonjuk vissza az utols\u00f3 m\u0171veletet. Ekkor az UndoLast m\u0171veletb\u0151l kiindulva l\u00e9pkedj\u00fcnk v\u00e9gig a k\u00f3dunkon.
                  "},{"location":"labor/old-7-tervezesi-mintak/#3-feladat-memento-minta","title":"3. Feladat \u2013 Memento minta","text":"

                  A feladatban a Memento minta megval\u00f3s\u00edt\u00e1s\u00e1t gyakoroljuk. A minta teljes elm\u00e9leti h\u00e1ttere \u2013 UML diagramokkal illusztr\u00e1lva - el\u0151ad\u00e1son ker\u00fcl ismertet\u00e9sre, itt a minta legfontosabb elemeire koncentr\u00e1lunk.

                  "},{"location":"labor/old-7-tervezesi-mintak/#a-memento-minta-koncepcioja","title":"A Memento minta koncepci\u00f3ja","text":"

                  El\u0151z\u0151 feladatunkban a New Rect \u00e9s New Ellipse parancsok visszavon\u00e1s\u00e1t k\u00f6nnyen meg tudtuk val\u00f3s\u00edtani: mind\u00f6ssze el kellett t\u00e1vol\u00edtani a parancs \u00e1ltal l\u00e9trehozott alakzatot a dokumentum alakzatlist\u00e1j\u00e1b\u00f3l. A command objektumainkban ehhez el\u00e9g volt egy azonos\u00edt\u00f3t elt\u00e1rolni az \u00fajonnan l\u00e9trehozott alakzatra.

                  Az alkalmaz\u00e1sok t\u00f6bbs\u00e9g\u00e9n\u00e9l azonban sz\u00e1mos olyan parancs felbukkanhat, mely a dokumentum \u00e1llapot\u00e1t jelent\u0151s m\u00e9rt\u00e9kben befoly\u00e1solja. Ilyenkor a parancsnak a v\u00e9grehajt\u00e1s el\u0151tt a dokumentum \u00e1llapot\u00e1nak jelent\u0151s r\u00e9sz\u00e9hez, vagy ak\u00e1r a teljes \u00e1llapot\u00e1hoz is hozz\u00e1 kell f\u00e9rnie, hogy eltudja azt menteni az UnExecute megval\u00f3s\u00edt\u00e1s\u00e1hoz. Ez \u00fagy lehets\u00e9ges, ha a dokumentum teljes \u00e1llapot\u00e1t publikuss\u00e1 tessz\u00fck. Ez viszont nem szerencs\u00e9s, mert ellentmond az egys\u00e9gbez\u00e1r\u00e1s elv\u00e9nek. Nem szeretn\u00e9nk a teljes \u00e1llapotot \u2013 r\u00e1ad\u00e1sul m\u00f3dos\u00edt\u00e1sra vonatkoz\u00f3an is \u2013 hozz\u00e1f\u00e9rhet\u0151v\u00e9 tenni a k\u00fclvil\u00e1g sz\u00e1m\u00e1ra, csak a visszavon\u00e1s kedv\u00e9\u00e9rt. Erre a probl\u00e9m\u00e1ra ny\u00fajt megold\u00e1st a Memento tervez\u00e9si minta.

                  Alapelve egy mondatban: dokumentumunk \u00e1llapot\u00e1t egy \u00fan. Memento objektumba csomagoljuk be, hogy az k\u00e9s\u0151bb a visszavon\u00e1s sor\u00e1n vissza\u00e1ll\u00edthat\u00f3 legyen.

                  K\u00f6vetkezzen egy \u00e1bra, majd a hozz\u00e1 kapcsol\u00f3d\u00f3 gondolatok.

                  Alapelve r\u00e9szletesebben: - Az Originator azon oszt\u00e1ly, melynek az \u00e1llapot\u00e1hoz hozz\u00e1 szeretn\u00e9nk f\u00e9rni. Eset\u00fcnkben ez a DrawingDocument oszt\u00e1ly t\u00f6lti be az Originator szerep\u00e9t. Az \u00e1llapotot \u00f6sszefog\u00f3an az \u00e1bra a state:State taggal jel\u00f6li. Eset\u00fcnkben ez a shapes lista, valamint a selectedShape tag lesz. A k\u00f6vetkez\u0151 l\u00e9p\u00e9sekt\u0151l a mint\u00e1t az alkalmaz\u00e1sunkra vet\u00edtj\u00fck. - A dokumentumunk \u00e1llapot\u00e1t (eset\u00fcnkben ez a shapes lista, valamit a selectedShape tag) NEM tessz\u00fck publikuss\u00e1. - A dokumentumunkban bevezet\u00fcnk egy CreateMemento m\u0171veletet, mely egy \u00fan. Memento objektumot hoz l\u00e9tre. A Memento tagv\u00e1ltoz\u00f3iban a dokumentum \u00e1llapot\u00e1nak pillanatnyi k\u00e9p\u00e9t tartalmazza (vagyis tulajdonk\u00e9ppen egy csomagol\u00f3 objektum a dokumentum aktu\u00e1lis \u00e1llapot\u00e1hoz). - A dokumentum \u00e1llapot\u00e1nak vissza\u00e1ll\u00edt\u00e1s\u00e1ra bevezet\u00fcnk a dokumentumban egy RestoreFromMemento m\u0171veletet, mely param\u00e9terk\u00e9nt egy Memento objektumot kap. A dokumentum ebben a m\u0171veletben vissza\u00e1ll\u00edtja saj\u00e1t \u00e1llapot\u00e1t a param\u00e9terk\u00e9nt kapott Memento objektum alapj\u00e1n.

                  "},{"location":"labor/old-7-tervezesi-mintak/#a-memento-minta-megvalositasa-alkalmazasunkban","title":"A Memento minta megval\u00f3s\u00edt\u00e1sa alkalmaz\u00e1sunkban","text":"

                  Alkalmaz\u00e1sunkban a Clear funkci\u00f3t val\u00f3s\u00edtjuk meg a Memento mint\u00e1ra \u00e9p\u00edtve. A Clear parancs t\u00f6rli a dokumentumb\u00f3l az \u00f6sszes alakzatot. Annak \u00e9rdek\u00e9ken, hogy ez visszavonhat\u00f3 legyen, a dokumentumunk teljes \u00e1llapot\u00e1t el kell menteni a parancs v\u00e9grehajt\u00e1sa el\u0151tt. Ehhez a DrawingDocument oszt\u00e1lyunk \u00e1llapot\u00e1t jelent\u0151 shapes tagot NEM fogjuk publikuss\u00e1 tenni. M\u00e9g k\u00f6zvetve, property-n/m\u0171veleten kereszt\u00fcl sem tessz\u00fck m\u00f3dos\u00edthat\u00f3v\u00e1!

                  Warning

                  Amennyiben kev\u00e9s id\u0151 maradt gyakorlaton, nyissuk meg a k\u00e9sz megold\u00e1st, \u00e9s abban mutassuk be a megval\u00f3s\u00edt\u00e1s r\u00e9szleteit!

                  • A dokumentum \u00e1llapot\u00e1t t\u00e1rol\u00f3 Memento oszt\u00e1lyt egy a DrawingDocument-be be\u00e1gyazott oszt\u00e1lyk\u00e9nt val\u00f3s\u00edtjuk meg, ezzel is hangs\u00falyozva, hogy Memento oszt\u00e1lyunk nagyon szorosan kapcsol\u00f3dik a dokumentumhoz. Forr\u00e1sk\u00f3d szintj\u00e9n viszont igyeksz\u00fcnk lev\u00e1lasztani, \u00edgy a DrawingDocument oszt\u00e1lyt partial class-ra alak\u00edtva k\u00fcl\u00f6n f\u00e1jlban dolgozunk.

                    • Alak\u00edtsuk a DrawingDocument-et partial class-\u00e1:
                      public partial class DrawingDocument\n
                    • Vegy\u00fcnk fel egy DrawingDocument.Memento.cs f\u00e1jlt a DesignPatternApp projektbe (jobb katt a projekten, Add/New Item, \u00e9s a megjelen\u0151 ablakban a Code File-t v\u00e1lasszuk ki).
                    • Illessz\u00fck be az al\u00e1bbi k\u00f3dr\u00e9szletet a f\u00e1jlba:

                      DrawingDocument.Memento.cs
                      using System.Collections.Generic;\n\nnamespace DesignPatternApp\n{\n    public partial class DrawingDocument\n    {\n        public class Memento\n        {\n            private List<Shape> shapes = new List<Shape>();\n            private Shape selectedShape;\n\n            public Memento(List<Shape> shapes, Shape selectedShape)\n            {\n                // Deep copyra van sz\u00fcks\u00e9g\u00fcnk!\n                foreach (Shape shape in shapes)\n                    this.shapes.Add(shape.CreateCopy());\n\n                // Be kell \u00e1ll\u00edtsuk selectedShape-nek. Az \u00faj Shape list\u00e1ban kell a megfelel\u00f5\n                // elemre hivatkoznia, nem az eredetiben. Be kell \u00e1ll\u00edtsuk.\n                this.selectedShape = null;\n                for (int i = 0; i < shapes.Count; ++i)\n                    if (shapes[i] == selectedShape)\n                    {\n                        this.selectedShape = this.shapes[i];\n                        break;\n                    }\n            }\n\n            public void GetState(out List<Shape> shapes, out Shape selectedShape)\n            {\n                shapes = this.shapes;\n                selectedShape = this.selectedShape;\n            }\n        }\n\n    }\n}\n
                  • A Memento oszt\u00e1lyunk legfontosabb aspektusai:

                    • Pontosan olyan tagv\u00e1ltoz\u00f3i vannak, mint a dokumentum oszt\u00e1lyunknak: \u00edgy tudja annak teljes \u00e1llapot\u00e1t elt\u00e1rolni.
                    • Konstruktor\u00e1ban a dokumentum \u00e1llapotv\u00e1ltoz\u00f3it v\u00e1rja (shapes \u00e9s selectedShape). L\u00e9nyeges, hogy a shapes list\u00e1r\u00f3l deep-copy m\u00e1solatot k\u00e9sz\u00edt: ha csak referenci\u00e1kat t\u00e1rolna a dokumentumban lev\u0151 objektumokra, akkor a dokumentum v\u00e1ltoz\u00e1s\u00e1val a memento objektumunk \u00e1llapota is v\u00e1ltozna. Nek\u00fcnk viszont az aktu\u00e1lis \u00e1llapot meg\u0151rz\u00e9se a c\u00e9lunk.
                    • A GetState-ben k\u00e9t out param\u00e9terben visszaadja az elmentett \u00e1llapotot. Az Undo m\u0171velet sor\u00e1n fogjuk ezt haszn\u00e1lni.
                  • Emelj\u00fck be az al\u00e1bbi k\u00f3dr\u00e9szletet a DrawingDocument.cs f\u00e1jlba a DrawingDocument oszt\u00e1lyba:

                    DrawingDocument.cs f\u00e1jlba
                    public Memento CreateMemento()\n{\n    return new Memento(shapes, selectedShape);\n}\n\npublic void RestoreFromMemento(Memento m)\n{\n    m.GetState(out shapes, out selectedShape);\n    fireShapesChanged();\n    fireSelectionChanged();\n}\n

                    A CreateMemento m\u0171velet a mint\u00e1nak megfelel\u0151en legy\u00e1rt egy Memento objektumot a dokumentum \u00e1llapot\u00e1r\u00f3l. A RestoreFromMemento pedig a param\u00e9ter\u00fcl kapott Memento objektum alapj\u00e1n vissza\u00e1ll\u00edtja a dokumentum \u00e1llapot\u00e1t.

                  Ezzel a Memento t\u00e1mogat\u00e1s be\u00e9p\u00edt\u00e9s\u00e9vel v\u00e9gezt\u00fcnk. Ugyanakkor jelen pillanatban egyetlen parancsunk sem haszn\u00e1lja ezt a szolg\u00e1ltat\u00e1st. Mint kor\u00e1bban eml\u00edtett\u00fck, a Clear funkci\u00f3t val\u00f3s\u00edtjuk meg a Memento mint\u00e1ra \u00e9p\u00edtve.

                  • Vegy\u00fcnk fel egy ClearCommand oszt\u00e1lyt a DesignPatternApp projekt Commands mapp\u00e1j\u00e1ban.
                  • Emelj\u00fck be az al\u00e1bbi k\u00f3dr\u00e9szletet az \u00faj ClearCommand.cs f\u00e1jlba:

                    ClearCommand.cs
                    class ClearCommand: Command\n{\n    DrawingDocument.Memento memento = null;\n\n    public override void Execute()\n    {\n        if (App.Instance.Document == null)\n            return;\n\n        memento = App.Instance.Document.CreateMemento();\n        App.Instance.Document.Clear();\n    }\n\n    public override void UnExecute()\n    {\n        if (App.Instance.Document == null)\n            return;\n\n        App.Instance.Document.RestoreFromMemento(memento);\n    }\n}\n
                  • Vegy\u00fck fel a f\u00e1jl elej\u00e9re ez al\u00e1bbi sort:

                    using AppFx.Command;\n

                  • Az App.CommandHandlers.cs f\u00e1jlban a ClearDocument m\u0171veletet \u00edrjuk \u00e1t, hogy most m\u00e1r az \u00fajonnan l\u00e9trehozott ClearCommand parancsunkat \u201efuttassa\u201d:
                    public void ClearDocument()\n{\n    executeCommand(new ClearCommand());\n}\n

                  Tesztelj\u00fck megold\u00e1sunkat:

                  • Futtassuk az alkalmaz\u00e1st,
                  • Hozzunk l\u00e9tre p\u00e1r alakzatot,
                  • A File/Clear men\u00fcb\u0151l futtassuk a Clear parancsot: az alakzataink elt\u0171nnek.
                  • Az Undo paranccsal vonjuk vissza a parancsot: az alakzatok \u00fajra megjelennek.

                  L\u00e9p\u00e9senk\u00e9nt futtatva is tesztelj\u00fck a megold\u00e1st:

                  • Tegy\u00fcnk egy t\u00f6r\u00e9spontot a ClearCommand.Execute m\u0171velet els\u0151 sor\u00e1ra.
                  • Ind\u00edtsuk ez az alkalmaz\u00e1st, hozzunk l\u00e9tre p\u00e1r alakzatot, majd a File/Clear men\u00fcb\u0151l futtassuk a Clear parancsot.
                  • Mikor a k\u00f3dunk meg\u00e1ll a t\u00f6r\u00e9spontn\u00e1l, l\u00e9pkedj\u00fck el a CreateMemento h\u00edv\u00e1s\u00e1ig, \u00e9s l\u00e9pj\u00fcnk is \u00e1t rajta. A CreateMemento \u00e1ltal visszaadott memento objektum bels\u0151 \u00e1llapot\u00e1t n\u00e9zz\u00fck meg vagy a Watch ablakban, vagy tooltipben r\u00e1\u00e1llva. Azt l\u00e1tjuk, hogy val\u00f3ban \u201etartalmazza\u201d a dokumentum pillanatnyi \u00e1llapot\u00e1t a shapes \u00e9s selectedShape tagv\u00e1ltoz\u00f3j\u00e1ban. A ClearCommand ezt el is t\u00e1rolja a tagv\u00e1ltoz\u00f3j\u00e1ban, amit az UnExecute m\u0171veletben haszn\u00e1l fel a dokumentum \u00e1llapot\u00e1nak vissza\u00e1ll\u00edt\u00e1s\u00e1ra.

                  P\u00e9ld\u00e1nkban a Memento minta arra \u00e9p\u00edt, hogy a dokumentum teljes \u00e1llapot\u00e1r\u00f3l m\u00e1solatot k\u00e9sz\u00edt\u00fcnk. Sok alkalmaz\u00e1s, illetve nagym\u00e9ret\u0171 dokumentum eset\u00e9ben ennek nagyon nagy lehet a mem\u00f3riaig\u00e9nye. Milyen megold\u00e1sokban gondolkozhatunk a probl\u00e9ma elker\u00fcl\u00e9s\u00e9re?

                  • A kisebb v\u00e1ltoz\u00e1sok hat\u00e1s\u00e1t ink\u00e1bb \u201einverz\u201d m\u0171velettel pr\u00f3b\u00e1ljuk visszacsin\u00e1lni. Ezt alkalmaztuk pl. a New Rect parancs eset\u00e9ben.
                  • A Memento-ba nem mentj\u00fck bele a teljes \u00e1llapotot, hanem csak m\u00f3dosult \u00e1llapotot. Sajnos ez nem mindig tehet\u0151 meg, valamint nehezebben karbantarthat\u00f3 megold\u00e1st eredm\u00e9nyez.
                  • Korl\u00e1tozzuk a visszavonhat\u00f3 l\u00e9p\u00e9sek sz\u00e1m\u00e1t.
                  "}]} \ No newline at end of file +{"config":{"lang":["hu"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Szoftvertechnik\u00e1k (2024-t\u0151l)","text":"

                  Gyakorlati anyagok \u00e9s h\u00e1zi feladatok a BMEVIAUAB00 Szoftvertechnik\u00e1k c. t\u00e1rgyhoz, 2024 \u00e9vt\u0151l kezd\u0151d\u0151en. A kor\u00e1bbi \u00e9vek anyag\u00e1nak megtekint\u00e9s\u00e9hez az oldal fejl\u00e9c\u00e9ben tal\u00e1lhat\u00f3 leny\u00edl\u00f3 mez\u0151ben a megfelel\u0151 \u00e9vet kell kiv\u00e1lasztani (pl. \"2023-ig\").

                  Jav\u00edt\u00e1s az anyagban

                  A t\u00e1rgy hallgat\u00f3inak a jegyzet anyag\u00e1ban t\u00f6rt\u00e9n\u0151 jav\u00edt\u00e1s\u00e9rt, kieg\u00e9sz\u00edt\u00e9s\u00e9rt plusz pontot adunk! Ha hib\u00e1t tal\u00e1lsz a jegyzet b\u00e1rmely r\u00e9sz\u00e9ben, vagy kieg\u00e9sz\u00edten\u00e9d azt, nyiss egy pull request-et! A repository linkj\u00e9t a jobb fels\u0151 sarokban tal\u00e1lod.

                  Felhaszn\u00e1l\u00e1si felt\u00e9telek

                  Az itt tal\u00e1lhat\u00f3 oktat\u00e1si seg\u00e9danyagok a BMEVIAUAB00 t\u00e1rgy hallgat\u00f3inak k\u00e9sz\u00fcltek. Az anyagok oly m\u00f3d\u00fa felhaszn\u00e1l\u00e1sa, amely a t\u00e1rgy oktat\u00e1s\u00e1hoz nem szorosan kapcsol\u00f3dik, csak a szerz\u0151(k) enged\u00e9ly\u00e9vel \u00e9s a forr\u00e1s megjel\u00f6l\u00e9s\u00e9vel t\u00f6rt\u00e9nhet.

                  Az anyagok a t\u00e1rgy keret\u00e9ben oktatott kontextusban \u00e9rtelmezhet\u0151ek. Az anyagok\u00e9rt egy\u00e9b felhaszn\u00e1l\u00e1s eset\u00e9n a szerz\u0151(k) felel\u0151ss\u00e9get nem v\u00e1llalnak.

                  "},{"location":"egyeb/interfesz-es-absztrakt-os/","title":"Interf\u00e9sz \u00e9s absztrakt (\u0151s)oszt\u00e1ly","text":"

                  Utols\u00f3 m\u00f3dos\u00edt\u00e1s ideje: 2022.10.15 Kidolgozta: Benedek Zolt\u00e1n

                  A fejezet nem tartalmaz feladatot, a hallgat\u00f3k sz\u00e1m\u00e1ra ismerteti a kapcsol\u00f3d\u00f3 elm\u00e9letet.

                  "},{"location":"egyeb/interfesz-es-absztrakt-os/#absztrakt-osztaly","title":"Absztrakt oszt\u00e1ly","text":"

                  A fogalmak kor\u00e1bbi t\u00e1rgyak keret\u00e9ben m\u00e1r ismertet\u00e9sre ker\u00fcltek, \u00edgy most csak a legfontosabbakat foglaljuk \u00f6ssze, illetve a C# vonatkoz\u00e1s\u00e1ra t\u00e9r\u00fcnk ki. Absztrakt oszt\u00e1ly Olyan oszt\u00e1ly, mely nem p\u00e9ld\u00e1nyos\u00edthat\u00f3. C# nyelven az oszt\u00e1lydefin\u00edci\u00f3ban az abstract kulcssz\u00f3t kell ki\u00edrni, pl.:

                  abstract class Shape { \u2026 }\n

                  Absztrakt oszt\u00e1lyoknak lehetnek absztrakt met\u00f3dusaik, melyeknek nem adjuk meg a t\u00f6rzs\u00e9t, ezekn\u00e9l is az abstract kulcssz\u00f3t kell haszn\u00e1lni:

                  \u2026\nabstract void Draw();\n\u2026\n

                  Absztrakt oszt\u00e1lyok haszn\u00e1lat\u00e1nak k\u00e9t c\u00e9lja lehet:

                  • Egy oszt\u00e1lyhierarchi\u00e1ban a lesz\u00e1rmazottakra k\u00f6z\u00f6s k\u00f3dot fel tudjuk vinni egy absztrakt k\u00f6z\u00f6s \u0151sbe, \u00edgy elker\u00fclj\u00fck a k\u00f3dduplik\u00e1ci\u00f3t.
                  • Egys\u00e9gesen tudjuk absztrakt \u0151sk\u00e9nt hivatkozva a lesz\u00e1rmazottakat kezelni (pl. heterog\u00e9n kollekci\u00f3k).

                  .NET k\u00f6rnyezetben, csak\u00fagy, mint Java nyelven, egy oszt\u00e1lynak csak egy \u0151soszt\u00e1lya lehet.

                  "},{"location":"egyeb/interfesz-es-absztrakt-os/#interfesz","title":"Interf\u00e9sz","text":"

                  Az interf\u00e9sz nem m\u00e1s, mint egy m\u0171velethalmaz. Tulajdonk\u00e9ppen egy olyan absztrakt oszt\u00e1lynak felel meg, melynek minden m\u0171velete absztrakt.

                  C# nyelven az interface kulcssz\u00f3val tudunk interf\u00e9szt defini\u00e1lni:

                  public interface ISerializable \n{\n   void WriteToStream(Stream s);\n   void LoadFromStream(Stream s);\n}\n\npublic interface IComparable \n{\n   int CompareTo(Object obj);\n}\n

                  M\u00edg egy oszt\u00e1lynak csak egy \u0151se lehet, ak\u00e1rh\u00e1ny interf\u00e9szt implement\u00e1lhat:

                  public class Rect : Shape, ISerializable, IComparable\n{\n    \u2026\n}\n

                  Ebben a p\u00e9ld\u00e1ban Rect oszt\u00e1ly a Shape oszt\u00e1lyb\u00f3l sz\u00e1rmazik, valamint az ISerializable \u00e9s IComparable interf\u00e9szeket implement\u00e1lja (k\u00f6telez\u0151en az \u0151soszt\u00e1lyt kell el\u0151sz\u00f6r megadni). Az interf\u00e9szt implement\u00e1l\u00f3 oszt\u00e1lyban annak valamennyi m\u0171velet\u00e9t meg kell val\u00f3s\u00edtani, vagyis meg kell \u00edrni a t\u00f6rzs\u00e9t (kiv\u00e9ve azt a ritka esetet, amikor absztrakt m\u0171velettel val\u00f3s\u00edtjuk meg). Interf\u00e9szek haszn\u00e1lat\u00e1nak egy f\u0151 c\u00e9lja van. Interf\u00e9szk\u00e9nt hivatkozva egys\u00e9gesen tudjuk az interf\u00e9szt implement\u00e1l\u00f3 valamennyi oszt\u00e1lyt kezelni (pl. heterog\u00e9n kollekci\u00f3). Ennek egy k\u00f6vetkezm\u00e9nye: az interf\u00e9szek lehet\u0151v\u00e9 teszik sz\u00e9les k\u00f6rben haszn\u00e1lhat\u00f3 oszt\u00e1lyok \u00e9s f\u00fcggv\u00e9nyek meg\u00edr\u00e1s\u00e1t. Pl. tudunk \u00edrni egy univerz\u00e1lis Sort sorrendez\u0151 f\u00fcggv\u00e9nyt, mely b\u00e1rmilyen oszt\u00e1llyal haszn\u00e1lhat\u00f3, mely implement\u00e1lja az IComparable interf\u00e9szt.

                  Az interf\u00e9sz alkalmaz\u00e1s\u00e1nak el\u0151nyei m\u00e9g:

                  • A kliensnek el\u00e9g a kiszolg\u00e1l\u00f3 objektum interf\u00e9sz\u00e9t ismernie, \u00edgy egyszer\u0171en tudja a kiszolg\u00e1l\u00f3t haszn\u00e1lni.
                  • Ha a kliens csak az interf\u00e9szen kereszt\u00fcl haszn\u00e1lja a kiszolg\u00e1l\u00f3t, \u00edgy a kiszolg\u00e1l\u00f3 bels\u0151 implement\u00e1ci\u00f3ja megv\u00e1ltozhat, a klienst nem kell m\u00f3dos\u00edtani (\u00fajra sem kell ford\u00edtani). Ennek megfelel\u0151en az interf\u00e9sz egy szerz\u0151d\u00e9s is a kiszolg\u00e1l\u00f3 \u00e9s a kliens k\u00f6z\u00f6tt: am\u00edg a kiszolg\u00e1l\u00f3 garant\u00e1lja az interf\u00e9sz t\u00e1mogat\u00e1s\u00e1t, a klienst nem kell v\u00e1ltoztatni.
                  "},{"location":"egyeb/interfesz-es-absztrakt-os/#absztrakt-os-es-interfesz-osszehasonlitasa","title":"Absztrakt \u0151s \u00e9s interf\u00e9sz \u00f6sszehasonl\u00edt\u00e1sa","text":"

                  Az absztrakt \u0151s el\u0151nye az interf\u00e9sszel szemben, hogy adhatunk meg a m\u0171veletekre vonatkoz\u00f3an alap\u00e9rtelmezett implement\u00e1ci\u00f3t, illetve vehet\u00fcnk fel tagv\u00e1ltoz\u00f3kat.

                  Az interf\u00e9szek el\u0151nye az absztrakt \u0151ssel szemben, hogy egy oszt\u00e1ly ak\u00e1rh\u00e1ny interf\u00e9szt implement\u00e1lhat, m\u00edg \u0151se maximum egy lehet.

                  Az interf\u00e9szek haszn\u00e1lat\u00e1nak van m\u00e9g egy k\u00f6vetkezm\u00e9nye, ami bizonyos esetben kellemetlens\u00e9geket okozhat. Amikor az interf\u00e9szbe \u00faj m\u0171veletet vesz\u00fcnk fel, akkor valamennyi implement\u00e1l\u00f3 oszt\u00e1lyt szint\u00e9n b\u0151v\u00edteni kell, k\u00fcl\u00f6nben a k\u00f3d nem fordul. Absztrakt \u0151s b\u0151v\u00edt\u00e9se eset\u00e9n ez nincs \u00edgy: amennyiben \u00faj m\u0171veletet vesz\u00fcnk fel, lehet\u0151s\u00e9g\u00fcnk van azt virtu\u00e1lis f\u00fcggv\u00e9nyk\u00e9nt felvenni, \u00e9s \u00edgy az \u0151sben alap\u00e9rtelmezett implement\u00e1ci\u00f3t adni r\u00e1. Ez esetben az lesz\u00e1rmazottak ig\u00e9ny szerint tudj\u00e1k ezt fel\u00fcldefini\u00e1lni, erre nincsenek r\u00e1k\u00e9nyszer\u00edtve. Az interf\u00e9szek ezen tulajdons\u00e1ga k\u00fcl\u00f6n\u00f6sen oszt\u00e1lyk\u00f6nyvt\u00e1rak/keretrendszerek eset\u00e9n lehet kellemetlen. Tegy\u00fck fel, hogy a .NET \u00faj verzi\u00f3j\u00e1nak kiad\u00e1skor a keretrendszer egyik interf\u00e9sz\u00e9be \u00faj m\u0171veletet vesznek fel. Ekkor valamennyi alkalmaz\u00e1sban valamennyi implement\u00e1l\u00f3 oszt\u00e1lyt m\u00f3dos\u00edtani kell, k\u00fcl\u00f6nben nem fordul a k\u00f3d. Ezt k\u00e9tf\u00e9lek\u00e9ppen lehet elker\u00fclni. Vagy \u0151soszt\u00e1ly haszn\u00e1lat\u00e1val, vagy ha m\u00e9gis interf\u00e9szt kellene b\u0151v\u00edteni, akkor ink\u00e1bb \u00faj interf\u00e9szt bevezet\u00e9s\u00e9vel, amely m\u00e1r az \u00faj m\u0171veletet is tartalmazza. B\u00e1r itt az els\u0151 megk\u00f6zel\u00edt\u00e9s (\u0151soszt\u00e1ly alkalmaz\u00e1sa) t\u0171nik els\u0151 \u00e9rz\u00e9sre vonz\u00f3bbnak, ennek is van h\u00e1tr\u00e1nya: ha az alkalmaz\u00e1s fejleszt\u00e9sekor egy keretrendszerbeli \u0151sb\u0151l sz\u00e1rmaztatunk, akkor oszt\u00e1lyunknak m\u00e1r nem lehet m\u00e1s \u0151se, \u00e9s ez bizony sok esetben f\u00e1jdalmas megk\u00f6t\u00e9st jelent.

                  \u00c9rdemes tudni, hogy C# 8-t\u00f3l (illetve .NET vagy .NET Core runtime is kell hozz\u00e1, .NET Framework alatt nem t\u00e1mogatott) kezdve interf\u00e9sz m\u0171veleteknek is lehet alap\u00e9rtelmezett implement\u00e1ci\u00f3t adni (default interface methods), \u00edgy a fenti probl\u00e9ma megold\u00e1s\u00e1hoz nincs sz\u00fcks\u00e9g absztrakt oszt\u00e1lyra, de interf\u00e9sznek tov\u00e1bbiakban sem lehet tagv\u00e1ltoz\u00f3ja. B\u0151vebben inform\u00e1ci\u00f3 itt: default interface methods.

                  Mivel mind az interf\u00e9szek, mind az absztrakt \u0151soszt\u00e1lyok alkalmaz\u00e1sa j\u00e1rhat negat\u00edv k\u00f6vetkezm\u00e9nyekkel is, sz\u00e1mos esetben a kett\u0151 egy\u00fcttes haszn\u00e1lat\u00e1val tudjuk kihozni megold\u00e1sunkb\u00f3l a maximumot (vagyis lesz a k\u00f3dunk k\u00f6nnyen b\u0151v\u00edthet\u0151 \u00fagy, hogy nem, vagy csak minim\u00e1lis m\u00e9rt\u00e9kben tartalmaz k\u00f3dduplik\u00e1ci\u00f3t).

                  "},{"location":"egyeb/interfesz-es-absztrakt-os/index_eng/","title":"Interface and abstract (base) class","text":"

                  Last modified date: 2022.10.15 Edited by Zolt\u00e1n Benedek

                  The chapter does not contain an exercise, it introduces the related theory to students.

                  "},{"location":"egyeb/interfesz-es-absztrakt-os/index_eng/#abstract-class","title":"Abstract class","text":"

                  The concepts have been covered in previous topics, so for now we will just summarize the most important ones and focus on the C# aspect. Abstract class A class that cannot be instantiated. In C#, in the class definition, the abstract keyword must be written out, e.g.:

                  abstract class Shape { ... }\n

                  Abstract classes may have abstract methods that do not have a root, and for these abstract methods the abstract keyword should be used:

                  ...\nabstract void Draw();\n...\n

                  There are two purposes for using abstract classes:

                  • In a class hierarchy, we can map code common to descendants into an abstract common base class, thus avoiding code duplication.
                  • We can uniformly refer to descendants as abstract base classes (e.g. heterogeneous collections).

                  in .NET, as in Java, a class can have only one base class class.

                  "},{"location":"egyeb/interfesz-es-absztrakt-os/index_eng/#interface","title":"Interface","text":"

                  An interface is nothing more than a set of operations. In fact, it corresponds to an abstract class whose all operations are abstract.

                  In C# you can define an interface with the interface keyword:

                  public interface ISerializable \n{\n   void WriteToStream(Stream s);\n   void LoadFromStream(Stream s);\n}\n\npublic interface IComparable \n{\n   int CompareTo(Object obj);\n}\n

                  While a class can have only one base class, it can implement any number of interfaces:

                  public class Rect : Shape, ISerializable, IComparable\n{\n    ...\n}\n

                  In this example, the Rect class is derived from the Shape class and implements the ISerializable and IComparable interfaces (mandatory to specify the base class first). In a class implementing an interface, all its operations must be implemented, i.e., its trunk must be written (except in the rare case where it is implemented by an abstract operation). There is one main purpose for using interfaces. Referenced as an interface, we can uniformly manage all the classes that implement the interface (e.g., a heterogeneous collection). One consequence of this is that interfaces allow us to write classes and functions that can be used in a wide variety of ways. For example, we can write a universal Sort ordering function that can be used with any class that implements the IComparable interface.

                  Other benefits of using the interface include:

                  • The client only needs to know the interface of the server object to be able to use the server easily.
                  • If the client only uses the server through the interface, so the internal implementation of the server may change, the client does not need to be modified (nor recompiled). Accordingly, the interface is also a contract between the server and the client: as long as the server guarantees support for the interface, the client does not need to change.
                  "},{"location":"egyeb/interfesz-es-absztrakt-os/index_eng/#comparison-of-abstract-base-class-and-interface","title":"Comparison of abstract base class and interface","text":"

                  The advantage of the abstract base class over the interface is that you can specify a default implementation for the operations and include member variables.

                  The advantage of interfaces over abstract ancestors is that a class can implement any number of interfaces, while its base class can implement at most one.

                  There is another consequence of using interfaces, which can cause inconvenience in some cases. When a new operation is added to the interface, all implementing classes must also be extended, otherwise the code will not compile. This is not the case when extending an abstract base class: if you add a new operation, you have the option to add it as a virtual function, and thus give it a default implementation in the ancestor. In this case, the descendants can redefine this as they wish, they are not forced to do so. This feature of interfaces can be particularly inconvenient for class libraries/framework systems. Suppose a new version of .NET is released and a new operation is added to one of the interfaces of the framework. All implementing classes in all applications must then be modified, otherwise the code will not compile. There are two ways to avoid this. Either by using a legacy class, or, if an interface should be extended, by introducing a new interface that already contains the new operation. Although the first approach (using an base class class) seems more attractive at first sight, it also has a drawback: if you derive from an ancestor in the framework when developing your application, your class can have no other ancestor, and this is a painful constraint in many cases.

                  It's worth knowing that starting from C# 8 (or .NET or .NET Core runtime, not supported under .NET Framework), interface operations can be given a default implementation (default interface methods), so no abstract class is needed to solve the above problem, but an interface can no longer have a member variable. More information here: default interface methods.

                  Since using both interfaces and abstract classes can have negative consequences, in many cases we can get the most out of our solution by using both (i.e., our code can be easily extended with no or minimal code duplication).

                  "},{"location":"egyeb/interfesz-es-absztrakt-os/index_ger/","title":"Schnittstelle und abstrakte (angestammte) Klasse","text":"

                  Letztes \u00c4nderungsdatum: 2022.10.15 Er hat trainiert: Zolt\u00e1n Benedek

                  Das Kapitel enth\u00e4lt keine \u00dcbung, sondern bietet den Studierenden eine Einf\u00fchrung in die entsprechende Theorie.

                  "},{"location":"egyeb/interfesz-es-absztrakt-os/index_ger/#abstrakte-klasse","title":"Abstrakte Klasse","text":"

                  Die Konzepte wurden bereits in fr\u00fcheren Themen behandelt, so dass wir jetzt nur die wichtigsten zusammenfassen und uns auf den C#-Aspekt konzentrieren werden. Abstrakte Klasse Eine Klasse, die nicht instanziiert werden kann. In C# sollte in der Klassendefinition das abstrakte Schl\u00fcsselwort geschrieben werden, z.B.:

                  abstract class Shape { ... }\n

                  Abstrakte Klassen k\u00f6nnen abstrakte Methoden haben, die keine Wurzel haben, und f\u00fcr diese abstrakten Methoden sollte das Schl\u00fcsselwort abstract verwendet werden:

                  ..\nabstract void Draw();\n..\n

                  Es gibt zwei Gr\u00fcnde f\u00fcr die Verwendung abstrakter Klassen:

                  • In einer Klassenhierarchie k\u00f6nnen wir Code, der allen Nachkommen gemeinsam ist, auf einen abstrakten gemeinsamen Vorfahren abbilden und so Code-Duplikation vermeiden.
                  • Wir k\u00f6nnen uns einheitlich auf Nachkommen als abstrakte Vorfahren beziehen (z. B. heterogene Sammlungen).

                  in .NET, wie auch in Java, kann eine Klasse nur eine Vorg\u00e4ngerklasse haben.

                  "},{"location":"egyeb/interfesz-es-absztrakt-os/index_ger/#schnittstelle","title":"Schnittstelle","text":"

                  Eine Schnittstelle ist nichts anderes als eine Reihe von Operationen. Sie entspricht in der Tat einer abstrakten Klasse, deren s\u00e4mtliche Operationen abstrakt sind.

                  In C# k\u00f6nnen Sie eine Schnittstelle mit dem Schl\u00fcsselwort interface definieren:

                  public interface ISerializable \n{\n   void WriteToStream(Stream s);\n   void LoadFromStream(Stream s);\n}\n\npublic interface IComparable \n{\n   int CompareTo(Object obj);\n}\n

                  W\u00e4hrend eine Klasse nur einen Vorfahren haben kann, kann sie eine beliebige Anzahl von Schnittstellen implementieren:

                  public class Rect : Shape, ISerializable, IComparable\n{\n    ..\n}\n

                  In diesem Beispiel ist die Klasse Rect von der Klasse Shape abgeleitet und implementiert die Schnittstellen ISerializable und IComparable (die Vorg\u00e4ngerklasse muss zuerst angegeben werden). In der Klasse, die die Schnittstelle implementiert, m\u00fcssen alle ihre Operationen implementiert werden, d. h. ihr Stamm muss geschrieben werden (au\u00dfer in dem seltenen Fall, dass sie durch eine abstrakte Operation implementiert wird). Die Verwendung von Schnittstellen hat vor allem einen Zweck. Als Schnittstelle referenziert, k\u00f6nnen wir alle Klassen, die die Schnittstelle implementieren, einheitlich verwalten (z. B. heterogene Sammlung). Eine Folge davon ist, dass Schnittstellen es Ihnen erm\u00f6glichen, Klassen und Funktionen zu schreiben, die auf vielf\u00e4ltige Weise verwendet werden k\u00f6nnen. Wir k\u00f6nnen zum Beispiel eine universelle Sortierfunktion schreiben, die mit jeder Klasse verwendet werden kann, die die Schnittstelle IComparable implementiert.

                  Weitere Vorteile der Nutzung der Schnittstelle sind:

                  • Der Client muss nur die Schnittstelle des Serverobjekts kennen, um den Server problemlos nutzen zu k\u00f6nnen.
                  • Wenn der Client den Server nur \u00fcber die Schnittstelle nutzt, so dass sich die interne Implementierung des Servers \u00e4ndern kann, muss der Client nicht ge\u00e4ndert (und auch nicht neu kompiliert) werden. Dementsprechend ist die Schnittstelle auch ein Vertrag zwischen dem Server und dem Client: Solange der Server die Unterst\u00fctzung f\u00fcr die Schnittstelle garantiert, braucht der Client nicht zu wechseln.
                  "},{"location":"egyeb/interfesz-es-absztrakt-os/index_ger/#vergleich-von-abstraktem-vorfahren-und-schnittstelle","title":"Vergleich von abstraktem Vorfahren und Schnittstelle","text":"

                  Der Vorteil des abstrakten Vorg\u00e4ngers gegen\u00fcber der Schnittstelle besteht darin, dass Sie eine Standardimplementierung f\u00fcr die Operationen angeben und Membervariablen einschlie\u00dfen k\u00f6nnen.

                  Der Vorteil von Schnittstellen gegen\u00fcber abstrakten Vorfahren besteht darin, dass eine Klasse eine beliebige Anzahl von Schnittstellen implementieren kann, w\u00e4hrend ihr Vorfahre h\u00f6chstens eine implementieren kann.

                  Die Verwendung von Schnittstellen hat noch eine weitere Konsequenz, die in einigen F\u00e4llen zu Unannehmlichkeiten f\u00fchren kann. Wenn eine neue Operation zur Schnittstelle hinzugef\u00fcgt wird, m\u00fcssen alle implementierenden Klassen ebenfalls erweitert werden, sonst l\u00e4sst sich der Code nicht kompilieren. Dies ist bei der Erweiterung eines abstrakten Vorg\u00e4ngers nicht der Fall: Wenn Sie eine neue Operation hinzuf\u00fcgen, haben Sie die M\u00f6glichkeit, sie als virtuelle Funktion hinzuzuf\u00fcgen und ihr somit eine Standardimplementierung im Vorg\u00e4nger zu geben. In diesem Fall k\u00f6nnen die Nachkommen dies nach Belieben umdefinieren, sie sind nicht dazu gezwungen. Diese Eigenschaft von Schnittstellen kann f\u00fcr Klassenbibliotheken/Framework-Systeme besonders unangenehm sein. Angenommen, eine neue Version von .NET wird ver\u00f6ffentlicht und eine neue Operation wird zu einer der Schnittstellen des Frameworks hinzugef\u00fcgt. Alle implementierenden Klassen in allen Anwendungen m\u00fcssen dann ge\u00e4ndert werden, da der Code sonst nicht kompiliert werden kann. Es gibt zwei M\u00f6glichkeiten, dies zu vermeiden. Entweder durch Verwendung einer Legacy-Klasse oder, wenn eine Schnittstelle erweitert werden soll, durch Einf\u00fchrung einer neuen Schnittstelle, die die neue Operation bereits enth\u00e4lt. Obwohl der erste Ansatz (Verwendung einer Vorg\u00e4ngerklasse) auf den ersten Blick attraktiver erscheint, hat er auch einen Nachteil: Wenn Sie bei der Entwicklung Ihrer Anwendung von einem Vorg\u00e4nger im Framework ableiten, kann Ihre Klasse keinen weiteren Vorg\u00e4nger haben, und das ist in vielen F\u00e4llen eine schmerzhafte Einschr\u00e4nkung.

                  Es ist wichtig zu wissen, dass ab C# 8 (oder .NET oder .NET Core Runtime, nicht unterst\u00fctzt unter .NET Framework), Schnittstellenoperationen eine Standardimplementierung (Standardschnittstellenmethoden) gegeben werden kann, so dass keine abstrakte Klasse ben\u00f6tigt wird, um das obige Problem zu l\u00f6sen, aber eine Schnittstelle kann nicht mehr eine Mitgliedsvariable haben. Weitere Informationen finden Sie hier: Standardschnittstellenmethoden.

                  Da sowohl die Verwendung von Schnittstellen als auch von abstrakten Klassen negative Folgen haben kann, k\u00f6nnen wir in vielen F\u00e4llen das meiste aus unserer L\u00f6sung herausholen, wenn wir beides verwenden (d. h. unser Code kann ohne oder mit nur minimaler Code-Duplizierung leicht erweitert werden).

                  "},{"location":"egyeb/uml-kod-kapcsolata/","title":"Az UML oszt\u00e1lydiagram \u00e9s a k\u00f3d kapcsolat\u00e1nak elm\u00e9lete","text":"

                  Utols\u00f3 m\u00f3dos\u00edt\u00e1s ideje: 2022.10.15 Kidolgozta: Benedek Zolt\u00e1n

                  A fejezet nem tartalmaz feladatot, a hallgat\u00f3k sz\u00e1m\u00e1ra ismerteti a kapcsol\u00f3d\u00f3 elm\u00e9letet.

                  "},{"location":"egyeb/uml-kod-kapcsolata/#bevezeto","title":"Bevezet\u0151","text":"

                  A fejezet egy r\u00f6vid, v\u00e1zlatos \u00e1ttekint\u00e9st ad az UML oszt\u00e1lydiagram \u00e9s a forr\u00e1sk\u00f3d k\u00f6z\u00f6tti lek\u00e9pez\u00e9s alapjair\u00f3l, a megel\u0151z\u0151 f\u00e9l\u00e9vben Szoftvertechnol\u00f3gia t\u00e1rgyb\u00f3l m\u00e1r tanultak ism\u00e9tl\u00e9sek\u00e9nt.

                  Napjainkban sz\u00e1mos szoftverfejleszt\u00e9si m\u00f3dszertan l\u00e9tezik. Ezek k\u00fcl\u00f6nb\u00f6z\u0151 m\u00e9rt\u00e9kben \u00e9p\u00edtenek arra, illetve k\u00f6vetelik meg, hogy a szoftver elk\u00e9sz\u00edt\u00e9se sor\u00e1n modellez\u00e9st alkalmazzunk. Az azonban k\u00e9ts\u00e9gtelen, hogy m\u00e9g a legagilisabb, legink\u00e1bb \u201ek\u00f3dcentrikus\u201d szeml\u00e9letm\u00f3dok k\u00f6vet\u0151i is hasznosnak \u00edt\u00e9lik a szoftver fontosabb/komplexebb komponenseinek \u00e9s szerkezeti elemeinek vizu\u00e1lis modellez\u00e9s\u00e9t annak grafikus volt\u00e1b\u00f3l ad\u00f3d\u00f3 nagyobb kifejez\u0151 ereje miatt.

                  Tegy\u00fck fel, hogy feladatunk egy alkalmaz\u00e1s, vagy annak adott modulj\u00e1nak elk\u00e9sz\u00edt\u00e9se. A v\u00e1lasztott m\u00f3dszertanunkat k\u00f6vetve \u2013 j\u00f3 es\u00e9llyel t\u00f6bb iter\u00e1ci\u00f3ban \u2013 a k\u00f6vetelm\u00e9ny elemz\u00e9s, anal\u00edzis, tervez\u00e9s, implement\u00e1ci\u00f3 \u00e9s tesztel\u00e9s l\u00e9p\u00e9seit fogjuk \u00e9rinteni. Koncentr\u00e1ljunk most a tervez\u00e9si f\u00e1zisra. Ennek sor\u00e1n elk\u00e9sz\u00fcl a rendszer (legal\u00e1bbis bizonyos r\u00e9szeinek) r\u00e9szletes terve, mely kimenete a r\u00e9szletes/ implement\u00e1ci\u00f3s terv, illetve modell. Ezen a szinten a modellben szerepl\u0151 bizonyos elemek (pl. oszt\u00e1lyok) egy\u00e9rtelm\u0171en lek\u00e9pezhet\u0151k az adott alrendszer implement\u00e1ci\u00f3j\u00e1ul v\u00e1lasztott programoz\u00e1si nyelv elemeire. Ha j\u00f3 a fejleszt\u0151/modellez\u0151 eszk\u00f6z\u00fcnk, akkor az le tudja gener\u00e1lni az oszt\u00e1lyok v\u00e1z\u00e1t (pl. C++, Java, C# oszt\u00e1lyok). A feladatunk ezt k\u00f6vet\u0151en a gener\u00e1lt k\u00f3dban szerepl\u0151 a met\u00f3dusok t\u00f6rzs\u00e9nek kit\u00f6lt\u00e9se.

                  "},{"location":"egyeb/uml-kod-kapcsolata/#fogalmak","title":"Fogalmak","text":"
                  • Forward engineering: modellb\u0151l k\u00f3d gener\u00e1l\u00e1sa. A r\u00e9szletes tervb\u0151l a modellez\u0151 eszk\u00f6z le tudja gener\u00e1lni a programv\u00e1zat. El\u0151nye, hogy kevesebbet kell k\u00f3dolni.
                  • Reverse engineering: k\u00f3db\u00f3l modell gener\u00e1l\u00e1sa. A m\u00e1r k\u00e9sz k\u00f3d meg\u00e9rt\u00e9s\u00e9t seg\u00edti.
                  • Round-trip engineering: az el\u0151z\u0151 kett\u0151 egy\u00fcttes alkalmaz\u00e1sa. A l\u00e9nyeg: a modell \u00e9s a k\u00f3d v\u00e9gig szinkronban van. Ha a k\u00f3dban v\u00e1ltoztatunk, a v\u00e1ltoz\u00e1s megjelenik a modellben, ha a modellben v\u00e1ltoztatunk, a v\u00e1ltoz\u00e1s megjelenik a k\u00f3dban.

                  Ahhoz, hogy a k\u00f3dgener\u00e1l\u00e1s el\u0151nyeivel \u00e9lni tudjunk, a k\u00f6vetkez\u0151kkel kell tiszt\u00e1ban legy\u00fcnk: ismern\u00fcnk kell, hogy az adott modellez\u0151 eszk\u00f6z az egyes modell elemeket hogyan k\u00e9pezi le az adott programoz\u00e1si nyelv elemeire. A lek\u00e9pez\u00e9s f\u00fcgg a nyelvt\u0151l \u00e9s a modellez\u0151 eszk\u00f6zt\u0151l is, nincs r\u00e1 univerz\u00e1lis szabv\u00e1ny. A lek\u00e9pez\u00e9sek \u00e1ltal\u00e1ban magukt\u00f3l \u00e9rtet\u0151d\u0151ek, t\u00fal nagy elt\u00e9r\u00e9s nem szokott lenni.

                  A k\u00f6vetkez\u0151kben azt tekintj\u00fck \u00e1t, hogy az UML oszt\u00e1lydiagram egyes modellelemei hogyan k\u00e9pz\u0151dnek le forr\u00e1sk\u00f3dra, \u00e9s viszont.

                  "},{"location":"egyeb/uml-kod-kapcsolata/#osztalyok-lekepezese","title":"Oszt\u00e1lyok lek\u00e9pez\u00e9se","text":"

                  Mondhatni trivi\u00e1lisan egyszer\u0171:

                  • UML oszt\u00e1ly -> oszt\u00e1ly
                  • UML attrib\u00fatum -> tagv\u00e1ltoz\u00f3
                  • UML m\u0171velet -> m\u0171velet/met\u00f3dus

                  Egy p\u00e9lda:

                  , mely a k\u00f6vetkez\u0151 k\u00f3dnak felel meg C# nyelven:

                  public abstract class Shape\n{\n    private int x;\n    private int y;\n    public Shape(int x, int y) { this.x = x; this.y = y; }\n    public abstract void Draw(Graphics gr);\n}\n

                  A l\u00e1that\u00f3s\u00e1g kapcs\u00e1n a lek\u00e9pez\u00e9s:

                  • +: public
                  • -: private
                  • #: protected

                  Enn\u00e9l izgalmasabb k\u00e9rd\u00e9sk\u00f6r, hogy milyen m\u00f3don t\u00f6rt\u00e9nik az oszt\u00e1lyok k\u00f6z\u00f6tti kapcsolatok lek\u00e9pez\u00e9se, ezt a k\u00f6vetkez\u0151 fejezetek ismertetik.

                  "},{"location":"egyeb/uml-kod-kapcsolata/#i-altalanositas-specializacio-kapcsolat","title":"I. \u00c1ltal\u00e1nos\u00edt\u00e1s, specializ\u00e1ci\u00f3 kapcsolat","text":"

                  C# lek\u00e9pez\u00e9s:

                  public class Base\n{ };\npublic class Derived : Base\n{ };\n
                  "},{"location":"egyeb/uml-kod-kapcsolata/#ii-asszociacio","title":"II. Asszoci\u00e1ci\u00f3","text":"

                  Ez a kapcsolatt\u00edpus mindig kommunik\u00e1ci\u00f3t jelent az oszt\u00e1lyok objektumai k\u00f6z\u00f6tt. Egy adott oszt\u00e1ly ig\u00e9nybe veszi egy m\u00e1sik oszt\u00e1ly szolg\u00e1ltat\u00e1sait.

                  "},{"location":"egyeb/uml-kod-kapcsolata/#a-lekepezes-01-multiplicitasu-asszociacios-kapcsolat-eseten","title":"A) Lek\u00e9pez\u00e9s 0..1 multiplicit\u00e1s\u00fa asszoci\u00e1ci\u00f3s kapcsolat eset\u00e9n","text":"

                  Ebben az esetben egy pointert vagy referenci\u00e1t tartalmaz a kliens oszt\u00e1ly, melyen kereszt\u00fcl ig\u00e9nybe tudja venni a c\u00e9loszt\u00e1ly szolg\u00e1ltat\u00e1sait (meg tudja h\u00edvni annak m\u0171veleteit). P\u00e9lda:

                  C++ lek\u00e9pez\u00e9s:

                  class Application\n{\n   WindowManager* windowManager;\n};\n\nclass WindowManager\n{\n};\n

                  C# lek\u00e9pez\u00e9s (nincsenek pointerek, csak referenci\u00e1k):

                  class Application\n{\n   WindowManager windowManager;\n};\n\nclass WindowManager\n{\n};\n

                  Mink\u00e9t esetben azt l\u00e1tjuk, hogy a kliens oszt\u00e1lyba felvesz\u00fcnk egy pointer vagy referencia tagv\u00e1ltoz\u00f3t, melynek t\u00edpusa megegyezik az asszoci\u00e1ci\u00f3ban hivatkozott c\u00e9loszt\u00e1ly t\u00edpus\u00e1val, illetve a tagv\u00e1ltoz\u00f3 neve az asszoci\u00e1ci\u00f3s kapcsolatra a c\u00e9loszt\u00e1lyra megadott szereppel (role), mely a p\u00e9ld\u00e1ban a windowManager. A lek\u00e9pez\u00e9s logikus, hiszen a kliens ezen pointeren/referenci\u00e1n kereszt\u00fcl tudja a c\u00e9lobjektumot b\u00e1rmely m\u0171velet\u00e9b\u0151l el\u00e9rni \u00e9s met\u00f3dusait megh\u00edvni.

                  Megjegyz\u00e9s. El\u0151fordulhat, hogy az asszoci\u00e1ci\u00f3 k\u00e9tir\u00e1ny\u00fa, mindk\u00e9t oszt\u00e1ly ig\u00e9nybe veszi a m\u00e1sik szolg\u00e1ltat\u00e1sait. Ilyenkor sokszor nem tessz\u00fck ki az asszoci\u00e1ci\u00f3 mindk\u00e9t v\u00e9g\u00e9re a nyilat, hanem mindk\u00e9t v\u00e9g\u00e9r\u0151l elhagyjuk azt. Ilyen k\u00e9tir\u00e1ny\u00fa kapcsolat eset\u00e9n a szerepet (role) a kapcsolat mindk\u00e9t v\u00e9g\u00e9n meg kell adni. A lek\u00e9pez\u00e9s sor\u00e1n mindk\u00e9t oszt\u00e1lyba felvesz\u00fcnk egy pointert/referenci\u00e1t a m\u00e1sikra.

                  "},{"location":"egyeb/uml-kod-kapcsolata/#b-lekepezes-0n-multiplicitasu-asszociacios-kapcsolat-eseten","title":"B) Lek\u00e9pez\u00e9s 0..n multiplicit\u00e1s\u00fa asszoci\u00e1ci\u00f3s kapcsolat eset\u00e9n","text":"

                  Ebben az esetben egy kliensoldali objektum t\u00f6bb c\u00e9loldali objektummal van kapcsolatban. P\u00e9lda:

                  Egy WindowManager objektum t\u00f6bb Window objektumot menedzsel. A lek\u00e9pez\u00e9s sor\u00e1n a kliens oszt\u00e1lyba a c\u00e9loszt\u00e1lybeli objektumok valamilyen gy\u0171jtem\u00e9ny\u00e9t vessz\u00fck fel. Ez lehet t\u00f6mb, lista stb., ami a c\u00e9lunknak az adott helyzetben legink\u00e1bb megfelel.

                  Egy lek\u00e9pz\u00e9si lehet\u0151s\u00e9g a fenti p\u00e9ld\u00e1ra C++ nyelven:

                  class WindowManager\n{\n  vector<Window*> windows;\n};\n

                  Illetve C# nyelven:

                  class WindowManager\n{\n  List<Window> windows; \n};\n
                  "},{"location":"egyeb/uml-kod-kapcsolata/#iii-aggregacio-tartalmazas-resz-egesz-viszony","title":"III. Aggreg\u00e1ci\u00f3 (tartalmaz\u00e1s, r\u00e9sz-eg\u00e9sz viszony)","text":"

                  \u00c1ltal\u00e1ban a lek\u00e9pez\u00e9se pontosan \u00fagy t\u00f6rt\u00e9nik, mint az asszoci\u00e1ci\u00f3 eset\u00e9ben.

                  "},{"location":"egyeb/uml-kod-kapcsolata/#iv-fuggoseg-dependency","title":"IV. F\u00fcgg\u0151s\u00e9g (dependency)","text":"

                  A leglaz\u00e1bb kapcsolatot jelenti oszt\u00e1lyok k\u00f6z\u00f6tt. P\u00e9lda:

                  A jelent\u00e9se: a Window oszt\u00e1ly f\u00fcgg a Graphics oszt\u00e1lyt\u00f3l. Vagyis, ha a Graphics oszt\u00e1ly megv\u00e1ltozik, akkor lehet, hogy a Window oszt\u00e1lyt is meg kell v\u00e1ltoztatni. Ezt a kapcsolatt\u00edpust akkor szoktuk haszn\u00e1lni, ha a f\u00fcgg\u0151s\u00e9gi kapcsolat elej\u00e9n lev\u0151 oszt\u00e1ly met\u00f3dusai param\u00e9terlist\u00e1j\u00e1ban/visszat\u00e9r\u00e9si \u00e9rt\u00e9k\u00e9ben szerepel a kapcsolat v\u00e9g\u00e9n lev\u0151 oszt\u00e1ly. A p\u00e9ld\u00e1ban a Window oszt\u00e1ly onDraw m\u0171velete param\u00e9terk\u00e9nt megkapja a Graphics oszt\u00e1ly egy objektum\u00e1t, \u00edgy f\u00fcgg t\u0151le, hiszen a met\u00f3dus t\u00f6rzs\u00e9ben \u00edgy meg tudja h\u00edvni a Graphics oszt\u00e1ly met\u00f3dusait. Ha pl. a Graphics oszt\u00e1ly FillRect met\u00f3dus\u00e1nak nev\u00e9t megv\u00e1ltoztatjuk, akkor ezt a v\u00e1ltoz\u00e1st \u00e1t kell vezetni a h\u00edv\u00e1sok hely\u00e9n, vagyis a Window oszt\u00e1ly onDraw met\u00f3dus\u00e1nak t\u00f6rzs\u00e9ben is.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/","title":"Theory of the relationship between the UML class diagram and code","text":"

                  Last modified date: 2022.10.15 Edited by Zolt\u00e1n Benedek

                  The chapter does not contain an exercise, it introduces the related theory to students.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#introduction","title":"Introduction","text":"

                  The chapter gives a brief, sketchy overview of the basics of mapping between the UML class diagram and the source code, as a review of what has already been learned in Software Engineering in the previous semester.

                  Today, there are many software development methodologies. They rely on, or require, modelling to varying degrees in the construction of the software. However, there is no doubt that even the most agile, \"code-centric\" followers of the most \"code-centric\" approaches find it useful to visually model the more important/complex components and structural elements of software, because of the greater expressive power of the graphical nature of the software.

                  Let's say you have to build an application or a specific module of an application. Following our chosen methodology, we will cover the steps of requirements analysis, analysis, design, implementation and testing, probably in several iterations. Let's now focus on the design phase. This will result in a detailed design of the system (at least parts of it), resulting in a detailed/implementation plan or model. At this level, certain elements of the model (e.g. classes) can be explicitly mapped to elements of the programming language chosen to implement the subsystem. If you have a good development/modeling tool, it can generate the class skeleton (e.g. C++, Java, C# classes). Our task is then to fill in the root of the methods in the generated code.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#concepts","title":"Concepts","text":"
                  • Forward engineering: generating code from a model. From the detailed plan, the modelling tool can generate the program framework. The advantage is that less coding is needed.
                  • Reverse engineering: generating a model from code. It helps you understand the code you already have.
                  • Round-trip engineering: a combination of the previous two. The point is: the model and the code are in sync all the time. If you change the code, the change appears in the model, if you change the model, the change appears in the code.

                  In order to take advantage of code generation, you need to be aware of the following: you need to know how a given modelling tool maps each model element to elements of a given programming language. The mapping depends on the language and the modelling tool, there is no universal standard. The mappings are usually self-explanatory, there is not usually too much variation.

                  In the following we will look at how each model element of the UML class diagram is mapped to source code, and vice versa.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#mapping-of-classes","title":"Mapping of classes","text":"

                  It's trivially simple:

                  • UML class -> class
                  • UML attribute -> member variable
                  • UML operation -> operation/method

                  An example:

                  Shape class

                  , which corresponds to the following code in C#:

                  public abstract class Shape\n{\n    private int x;\n    private int y;\n    public Shape(int x, int y) { this.x = x; this.y = y; }\n    public abstract void Draw(Graphics gr);\n}\n

                  In the context of visibility, mapping:

                  • +: public
                  • -: private
                  • #: protected

                  A more exciting question is how the relationships between classes are mapped, and this is discussed in the following chapters.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#i-generalisation-specialisation-link","title":"I. Generalisation, specialisation link","text":"

                  Generalisation, specialisation

                  C# mapping:

                  public class Base\n{ };\npublic class Derived : Base\n{ };\n
                  "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#ii-association","title":"II. Association","text":"

                  This relationship type always implies communication between objects of classes. A department uses the services of another department.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#a-building-a-01-multiplicity-association-relation","title":"A) Building a 0..1 multiplicity association relation","text":"

                  In this case, the client class contains a pointer or reference through which it can use the services of the target class (call its operations). Example:

                  Generalisation, specialisation, single contact

                  C++ mapping:

                  class Application\n{\n   WindowManager* windowManager;\n};\n\nclass WindowManager\n{\n};\n

                  C# mapping (no pointers, only references):

                  class Application\n{\n   WindowManager windowManager;\n};\n\nclass WindowManager\n{\n};\n

                  In both cases, we see that a pointer or reference member variable is added to the client class, whose type is the same as the type of the target class referenced in the association, and the name of the member variable is the role given to the target class for the association relationship, which in the example is . The mapping is logical, since the client can access the target object from any of its operations and call its methods through this pointer/reference.

                  Comment. Sometimes the association is two-way, with each class using the services of the other. Often, instead of putting an arrow at both ends of the association, we leave it at both ends. In such a two-way relationship, the role must be specified at both ends of the relationship. During the mapping, we add a pointer/reference to each class to the other.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#b-derivation-for-an-association-relation-with-multiplicity-0n","title":"B) Derivation for an association relation with multiplicity 0..n","text":"

                  In this case, a client-side object is related to several target-side objects. Example:

                  Generalisation, specialisation, multiple links

                  One WindowManager object manages several Window objects. The mapping takes some collection of objects in the target class into the client class. This can be an array, list, etc., whichever best suits our purpose in the situation.

                  A mapping to the above example in C++:

                  class WindowManager\n{\n  vector<Window*> windows;\n};\n

                  Or in C#:

                  class WindowManager\n{\n  List<Window> windows; \n};\n
                  "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#iii-aggregation-inclusion-part-part-relationship","title":"III. Aggregation (inclusion, part-part relationship)","text":"

                  In general, the mapping is exactly the same as for association.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_eng/#iv-dependency-dependency","title":"IV. Dependency (dependency)","text":"

                  It represents the loosest link between departments. Example:

                  Dependency

                  Meaning: the Window class depends on the Graphics class. That is, if the Graphics class is changed, the Window class may also need to be changed. This connection type is used when the parameter list/return value of the methods of the class at the beginning of the dependency connection contains the class at the end of the connection. In the example, the onDraw operation of the Window class receives an object of the Graphics class as a parameter, and thus depends on it, since it can call the methods of the Graphics class in the method's trunk. If, for example, the name of the FillRect method of the Graphics class is changed, this change must be reflected in the call location, i.e., in the trunk of the onDraw method of the Window class.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/","title":"Theorie der Beziehung zwischen dem UML-Klassendiagramm und dem Code","text":"

                  Letztes \u00c4nderungsdatum: 2022.10.15 Ausgearbeitet von: Zolt\u00e1n Benedek

                  Das Kapitel enth\u00e4lt keine \u00dcbung, sondern bietet den Studierenden eine Einf\u00fchrung in die entsprechende Theorie.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

                  Das Kapitel gibt einen kurzen \u00dcberblick \u00fcber die Grundlagen des Mappings zwischen dem UML-Klassendiagramm und dem Quellcode, als Wiederholung dessen, was bereits im vorherigen Semester in Softwarechnologien gelernt wurde.

                  Heutzutage gibt es viele Softwareentwicklungsmethoden. Sie st\u00fctzen sich bei der Erstellung der Software in unterschiedlichem Ma\u00dfe auf die Modellierung bzw. erfordern diese. Es besteht jedoch kein Zweifel daran, dass selbst die Anh\u00e4nger der agilsten, \"code-zentrierten\" Ans\u00e4tze es f\u00fcr n\u00fctzlich halten, die wichtigeren/komplexeren Komponenten und Strukturelemente der Software visuell zu modellieren, da deren grafische Natur eine gr\u00f6\u00dfere Ausdruckskraft hat.

                  Nehmen wir an, man muss eine Anwendung oder ein bestimmtes Modul einer Anwendung erstellen. Nach der von sich gew\u00e4hlten Methodik wird man die Schritte Anforderungsanalyse, Analyse, Entwurf, Implementierung und Test durchf\u00fchren, wahrscheinlich in mehreren Iterationen. Konzentrieren wir uns nun auf die Entwurfsphase. Das Ergebnis ist ein detaillierter Entwurf des Systems (zumindest von Teilen davon), der in einen detaillierten Plan oder ein Modell f\u00fcr die Umsetzung m\u00fcndet. Auf dieser Ebene k\u00f6nnen bestimmte Elemente des Modells (z. B. Klassen) explizit auf Elemente der f\u00fcr die Implementierung des Teilsystems gew\u00e4hlten Programmiersprache abgebildet werden. Wenn man \u00fcber ein gutes Entwicklungs-/Modellierungswerkzeug verf\u00fcgt, kann dieses das Klassenskelett (z. B. C++, Java-, C#-Klassen) generieren. Unsere Aufgabe besteht nun darin, die Wurzel der Methoden in den generierten Code einzutragen.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#konzepte","title":"Konzepte","text":"
                  • Forward Engineering: Generierung von Code aus einem Modell. Aus dem detaillierten Plan kann das Modellierungswerkzeug das Programmger\u00fcst erstellen. Der Vorteil ist, dass weniger Kodierung erforderlich ist.
                  • Reverse Engineering: Generierung eines Modells aus Code. Es hilft Ihnen, den bereits vorhandenen Code zu verstehen.
                  • Round-Trip-Engineering: eine Kombination der beiden vorgenannten Verfahren. Der springende Punkt ist, dass das Modell und der Code st\u00e4ndig synchronisiert sind. Wenn Sie den Code \u00e4ndern, erscheint die \u00c4nderung im Modell, wenn Sie das Modell \u00e4ndern, erscheint die \u00c4nderung im Code.

                  Um die Vorteile der Codegenerierung nutzen zu k\u00f6nnen, muss man Folgendes wissen: man muss wissen, wie ein bestimmtes Modellierungswerkzeug jedes Modellelement auf Elemente einer bestimmten Programmiersprache abbildet. Das Mapping h\u00e4ngt von der Sprache und dem Modellierungswerkzeug ab, es gibt keinen universellen Standard. Die Zuordnungen sind in der Regel selbsterkl\u00e4rend, es gibt in der Regel nicht allzu viele Variationen.

                  Im Folgenden werden wir uns ansehen, wie jedes Modellelement des UML-Klassendiagramms auf den Quellcode abgebildet wird und umgekehrt.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#zuordnung-von-klassen","title":"Zuordnung von Klassen","text":"

                  Es ist trivial einfach:

                  • UML-Klasse -> Klasse
                  • UML-Attribut -> Mitgliedsvariable
                  • UML-Operation -> Funktion/Method

                  Ein Beispiel:

                  , was folgendem Code in C# entspricht:

                  public abstract class Shape\n{\n    private int x;\n    private int y;\n    public Shape(int x, int y) { this.x = x; this.y = y; }\n    public abstract void Draw(Graphics gr);\n}\n

                  Im Zusammenhang mit der Sichtbarkeit, Kartierung:

                  • +: public
                  • -: private
                  • # : protected

                  Eine spannendere Frage ist, wie die Beziehungen zwischen den Klassen abgebildet werden, und dies wird in den folgenden Kapiteln diskutiert.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#i-generalisierung-und-spezialisierung","title":"I. Generalisierung und Spezialisierung","text":"

                  C#-Zuordnung:

                  public class Base\n{ };\npublic class Derived : Base\n{ };\n
                  "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#ii-assoziation","title":"II. Assoziation","text":"

                  Dieser Beziehungstyp impliziert immer eine Kommunikation zwischen Objekten von Klassen. Eine Abteilung nimmt die Dienste einer anderen Abteilung in Anspruch.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#a-aufbau-einer-01-multiplizitats-assoziationsbeziehung","title":"A) Aufbau einer 0..1-Multiplizit\u00e4ts-Assoziationsbeziehung","text":"

                  In diesem Fall enth\u00e4lt die Client-Klasse einen Zeiger oder Verweis, \u00fcber den sie die Dienste der Zielklasse nutzen (ihre Operationen aufrufen) kann. Beispiel:

                  C++-Zuordnung:

                  klasse Bewerbung\n{\n   WindowManager* windowManager;\n};\n\nclass WindowManager\n{\n};\n

                  C#-Zuordnung (keine Zeiger, nur Referenzen):

                  class Application\n{\n   WindowManager windowManager;\n};\n\nclass WindowManager\n{\n};\n

                  In beiden F\u00e4llen sehen wir, dass wir der Client-Klasse eine Zeiger- oder Referenz-Member-Variable hinzuf\u00fcgen, die vom gleichen Typ ist wie die Zielklasse, auf die in der Assoziation verwiesen wird, und der Name der Member-Variable ist die Rolle, die der Zielklasse f\u00fcr die Assoziationsbeziehung gegeben wurde, die in diesem Beispiel windowManagerist. Die Zuordnung ist logisch, da der Client auf das Zielobjekt aus jeder seiner Operationen zugreifen und seine Methoden \u00fcber diesen Zeiger/Verweis aufrufen kann.

                  Kommentar. Manchmal ist die Assoziation in beide Richtungen, wobei jede Klasse die Dienste der anderen nutzt. Anstatt einen Pfeil an beiden Enden der Assoziation anzubringen, lassen wir ihn oft an beiden Enden stehen. In einer solchen wechselseitigen Beziehung muss die Rolle an beiden Enden der Beziehung angegeben werden. W\u00e4hrend des Mappings f\u00fcgen wir einen Zeiger/Referenz auf jede Klasse der anderen hinzu.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#b-ableitung-fur-eine-assoziationsbeziehung-mit-der-multiplizitat-0n","title":"B) Ableitung f\u00fcr eine Assoziationsbeziehung mit der Multiplizit\u00e4t 0..n","text":"

                  In diesem Fall ist ein Objekt auf der Client-Seite mit mehreren Objekten auf der Zielseite verbunden. Beispiel:

                  Ein WindowManager Objekt verwaltet mehrere Window Objekte. Das Mapping \u00fcbernimmt eine Sammlung von Objekten der Zielklasse in die Client-Klasse. Dabei kann es sich um ein Array, eine Liste usw. handeln, je nachdem, was f\u00fcr unsere Zwecke in der jeweiligen Situation am besten geeignet ist.

                  Eine Abbildung des obigen Beispiels in C++:

                  class WindowManager\n{\n  vector<Window*> windows;\n};\n

                  Oder in C#:

                  class WindowManager\n{\n  List<Window> windows; \n};\n
                  "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#iii-aggregation-einbeziehung-teil-ganzes-beziehung","title":"III. Aggregation (Einbeziehung, Teil-Ganzes-Beziehung)","text":"

                  Im Allgemeinen ist die Zuordnung genau die gleiche wie bei der Assoziation.

                  "},{"location":"egyeb/uml-kod-kapcsolata/index_ger/#iv-abhangigkeit-dependenz","title":"IV. Abh\u00e4ngigkeit (Dependenz)","text":"

                  Sie stellt die lockerste Verbindung zwischen den Abteilungen dar. Beispiel:

                  Das bedeutet: Die Klasse Window h\u00e4ngt von der Klasse Graphics ab. Das hei\u00dft, wenn die Klasse Graphics ge\u00e4ndert wird, muss m\u00f6glicherweise auch die Klasse Window ge\u00e4ndert werden. Diese Art der Beziehung wird verwendet, wenn die Parameterliste/R\u00fcckgabewerte der Methoden der Klasse am Anfang der Abh\u00e4ngigkeitsbeziehung die Klasse am Ende der Beziehung enth\u00e4lt. Im Beispiel erh\u00e4lt die Operation onDraw der Klasse Window ein Objekt der Klasse Graphics als Parameter und ist somit von dieser abh\u00e4ngig, da sie die Methoden der Klasse Graphics im Stamm der Methode aufrufen kann. Wird z.B. der Name der Methode FillRect der Klasse Graphics ge\u00e4ndert, muss sich diese \u00c4nderung in der Aufrufstelle, d.h. im Stamm der Methode onDraw der Klasse Window widerspiegeln.

                  "},{"location":"hazi/","title":"\u00d6n\u00e1ll\u00f3/h\u00e1zi feladatok","text":"

                  Valamennyi h\u00e1zi feladat elk\u00e9sz\u00edt\u00e9se k\u00f6telez\u0151. A megold\u00e1sok bead\u00e1sa GitHub Classroom seg\u00edts\u00e9g\u00e9vel t\u00f6rt\u00e9nik (b\u0151vebben itt). Az \u00f6n\u00e1ll\u00f3/h\u00e1zi feladatokra vonatkoz\u00f3 pontos k\u00f6vetelm\u00e9nyek Moodle-ben, a T\u00e1rgyk\u00f6vetelm\u00e9nyek alatt olvashat\u00f3k (\"\u00d6n\u00e1ll\u00f3/h\u00e1zi feladatok\" fejezet).

                  "},{"location":"hazi/#a-feladatok","title":"A feladatok","text":"
                  • 1. HF - A modell \u00e9s a k\u00f3d kapcsolata
                  • 2. HF - Nyelvi eszk\u00f6z\u00f6k
                  • 3. HF - Felhaszn\u00e1l\u00f3i fel\u00fclet kialak\u00edt\u00e1sa
                  • 4. HF - T\u00f6bbsz\u00e1l\u00fa alkalmaz\u00e1sok fejleszt\u00e9se
                  • 5. HF - MVVM
                  • 6. HF - Tervez\u00e9si mint\u00e1k
                  • IMSc HF - Liftrendszer:
                  "},{"location":"hazi/#a-feladatok-beadasa","title":"A feladatok bead\u00e1sa","text":"

                  Minden h\u00e1zi feladat megold\u00e1s\u00e1t egy szem\u00e9lyre sz\u00f3l\u00f3 git repository-ban kell beadni. Ennek pontos folyamat\u00e1t l\u00e1sd itt. K\u00e9r\u00fcnk, alaposan olvasd v\u00e9gig a le\u00edr\u00e1st!

                  FONTOS

                  A h\u00e1zik elk\u00e9sz\u00edt\u00e9se \u00e9s bead\u00e1s sor\u00e1n az itt le\u00edrtak szerint kell elj\u00e1rnod. A nem ilyen form\u00e1ban beadott h\u00e1zi feladatokat nem \u00e9rt\u00e9kelj\u00fck.

                  Bizonyos h\u00e1zi feladatokhoz automata el\u0151ellen\u0151rz\u0151 is tartozik, err\u0151l itt olvashatsz b\u0151vebben.

                  "},{"location":"hazi/#kepernyokepek","title":"K\u00e9perny\u0151k\u00e9pek","text":"

                  Bizonyos h\u00e1zi feladatok k\u00e9rik, hogy k\u00e9sz\u00edts k\u00e9perny\u0151k\u00e9pet a megold\u00e1s egy-egy r\u00e9sz\u00e9r\u0151l, mert ezzel bizony\u00edtod, hogy a megold\u00e1sod saj\u00e1t magad k\u00e9sz\u00edtetted. A k\u00e9perny\u0151k\u00e9pek elv\u00e1rt tartalm\u00e1t a feladat minden esetben pontosan megnevezi.

                  A k\u00e9perny\u0151k\u00e9peket a megold\u00e1s r\u00e9szek\u00e9nt kell beadni, \u00edgy felker\u00fclnek a git repository tartalm\u00e1val egy\u00fctt. Mivel a repository priv\u00e1t, azt az oktat\u00f3kon k\u00edv\u00fcl m\u00e1s nem l\u00e1tja. Amennyiben olyan tartalom ker\u00fcl a k\u00e9perny\u0151k\u00e9pre, amit nem szeretn\u00e9l felt\u00f6lteni, kitakarhatod a k\u00e9pr\u0151l.

                  "},{"location":"hazi/#szukseges-eszkozok","title":"Sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k","text":"

                  A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezet alapvet\u0151en a Visual Studio 2022, err\u0151l itt tal\u00e1lhat\u00f3 b\u0151vebb le\u00edr\u00e1s.

                  "},{"location":"hazi/VisualStudio/","title":"Visual Studio & .NET SDK telep\u00edt\u00e9se","text":"

                  COMING SOON

                  "},{"location":"hazi/meghirdetes-elott/","title":"H\u00e1zi feladat","text":"

                  Ez a h\u00e1zi feladat ebben a f\u00e9l\u00e9vben m\u00e9g nem ker\u00fclt meghirdet\u00e9sre, \u00edgy a le\u00edr\u00e1sa k\u00e9s\u0151bb lesz el\u00e9rhet\u0151 a f\u00e9l\u00e9v folyam\u00e1n.

                  "},{"location":"hazi/meghirdetes-elott_ger/","title":"Hausaufgaben","text":"

                  Diese Hausarbeit wurde in diesem Semester noch nicht angek\u00fcndigt, so dass die Beschreibung erst im Laufe des Semesters verf\u00fcgbar sein wird.

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/","title":"1. HF - A modell \u00e9s a k\u00f3d kapcsolata","text":""},{"location":"hazi/1-model-es-kod-kapcsolata/#bevezetes","title":"Bevezet\u00e9s","text":"

                  A feladathoz nem kapcsol\u00f3dik el\u0151ad\u00e1s. A feladatok elm\u00e9leti \u00e9s gyakorlati h\u00e1tter\u00e9\u00fcl az \"1. A modell \u00e9s a k\u00f3d kapcsolata\" vezetett laborgyakorlat szolg\u00e1l:

                  • Ezt a laborgyakorlatot a hallgat\u00f3k a gyakorlatvezet\u0151 \u00fatmutat\u00e1s\u00e1val, a gyakorlatvezet\u0151vel k\u00f6z\u00f6sen vezetett m\u00f3don v\u00e9gzik/v\u00e9gezt\u00e9k el.
                  • A laborgyakorlathoz \u00fatmutat\u00f3 tartozik, mely r\u00e9szletekbe men\u0151en bemutatja az elm\u00e9leti h\u00e1tteret, valamint l\u00e9p\u00e9senk\u00e9nt ismerteti a megold\u00e1s elk\u00e9sz\u00edt\u00e9s\u00e9t: 1. A modell \u00e9s a k\u00f3d kapcsolata

                  Erre \u00e9p\u00edtve jelen \u00f6n\u00e1ll\u00f3 gyakorlat feladatai a feladatle\u00edr\u00e1st k\u00f6vet\u0151 r\u00f6videbb ir\u00e1nymutat\u00e1s seg\u00edts\u00e9g\u00e9vel elv\u00e9gezhet\u0151k.

                  Az \u00f6n\u00e1ll\u00f3 gyakorlat c\u00e9lja:

                  • Egy egyszer\u0171 .NET alkalmaz\u00e1s elk\u00e9sz\u00edt\u00e9se, C# alapok gyakorl\u00e1sa
                  • Az UML \u00e9s a k\u00f3d kapcsolat\u00e1nak szeml\u00e9ltet\u00e9se
                  • Az interf\u00e9sz \u00e9s az absztrakt \u0151soszt\u00e1ly alkalmaz\u00e1stechnik\u00e1j\u00e1nak gyakorl\u00e1sa

                  A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s.

                  C# 12-es (\u00e9s \u00fajabb) nyelvi elemek haszn\u00e1lata

                  A h\u00e1zi feladat megold\u00e1sa sor\u00e1n C# 12-es, \u00e9s ann\u00e1l \u00fajabb nyelvi elemek, (pl. primary constructor) nem haszn\u00e1lhat\u00f3k, ugyanis a GitHub-on fut\u00f3 ellen\u0151rz\u0151 ezeket m\u00e9g nem t\u00e1mogatja.

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/#a-kiindulasi-keret-letoltese-az-elkeszult-megoldas-feltoltese","title":"A kiindul\u00e1si keret let\u00f6lt\u00e9se, az elk\u00e9sz\u00fclt megold\u00e1s felt\u00f6lt\u00e9se","text":"

                  A h\u00e1zi feladat kiindul\u00e1si k\u00f6rnyezet\u00e9nek publik\u00e1l\u00e1sa, valamint a megold\u00e1s bead\u00e1sa Git, GitHub \u00e9s GitHub Classroom seg\u00edts\u00e9g\u00e9vel t\u00f6rt\u00e9nik. F\u0151bb l\u00e9p\u00e9sek:

                  1. GitHub Classroom seg\u00edts\u00e9g\u00e9vel hozz l\u00e9tre magadnak egy repository-t. A megh\u00edv\u00f3 URL-t Moodle-ben tal\u00e1lod (a t\u00e1rgy nyit\u00f3oldal\u00e1n a \"GitHub classroom hivatkoz\u00e1sok a h\u00e1zi feladatokhoz\" hivatkoz\u00e1sra kattintva megjelen\u0151 oldalon l\u00e1that\u00f3).
                  2. Kl\u00f3nozd le az \u00edgy elk\u00e9sz\u00fclt repository-t. Ez tartalmazni fogja a megold\u00e1s elv\u00e1rt szerkezet\u00e9t.
                  3. A feladatok elk\u00e9sz\u00edt\u00e9se ut\u00e1n commit-old \u00e9s push-old a megold\u00e1sod.

                  Ezekhez itt tal\u00e1lhat\u00f3 r\u00e9szletesebb le\u00edr\u00e1s:

                  • Git, GitHub, GitHub Classroom
                  • H\u00e1zi feladat munkafolyamat \u00e9s a Git/GitHub haszn\u00e1lata
                  "},{"location":"hazi/1-model-es-kod-kapcsolata/#a-hazi-feladat-eloellenorzese-es-hivatalos-ertekelese","title":"A h\u00e1zi feladat el\u0151ellen\u0151rz\u00e9se \u00e9s hivatalos \u00e9rt\u00e9kel\u00e9se","text":"

                  Minden egyes alkalommal, miut\u00e1n a GitHub-ra push-olt\u00e1l k\u00f3dot, a GitHub-on automatikusan lefut a felt\u00f6lt\u00f6tt k\u00f3d (el\u0151)ellen\u0151rz\u00e9se, \u00e9s meg lehet n\u00e9zni a kimenet\u00e9t! Err\u0151l b\u0151vebb inform\u00e1ci\u00f3 itt tal\u00e1lhat\u00f3 (mindenk\u00e9ppen olvasd el): A h\u00e1zi feladat el\u0151ellen\u0151rz\u00e9se \u00e9s hivatalos \u00e9rt\u00e9kel\u00e9se.

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/#feladat-1-egy-egyszeru-net-konzol-alkalmazas-elkeszitese","title":"Feladat 1 \u2013 Egy egyszer\u0171 .NET konzol alkalmaz\u00e1s elk\u00e9sz\u00edt\u00e9se","text":""},{"location":"hazi/1-model-es-kod-kapcsolata/#kiindulo-projekt","title":"Kiindul\u00f3 projekt","text":"

                  A kiindul\u00e1si k\u00f6rnyezet a Feladat1 mapp\u00e1ban tal\u00e1lhat\u00f3, az ebben lev\u0151 MusicApp.sln f\u00e1jlt nyissuk meg Visual Studioban \u00e9s ebben a solutionben dolgozzunk.

                  Figyelem!

                  \u00daj solution \u00e9s/vagy projektf\u00e1jl l\u00e9trehoz\u00e1sa, vagy a projekt m\u00e1s/\u00fajabb .NET verzi\u00f3kra targetel\u00e9se tilos.

                  A Feladat1\\Input mapp\u00e1ban tal\u00e1lhat\u00f3 egy music.txt f\u00e1jl, mely a feladat bemenetek\u00e9nt haszn\u00e1land\u00f3.

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/#feladat","title":"Feladat","text":"

                  Egy sz\u00f6vegf\u00e1ljban zeneszerz\u0151k/el\u0151ad\u00f3k/egy\u00fcttesek sz\u00e1mainak c\u00edmeit t\u00e1roljuk a k\u00f6vetkez\u0151 form\u00e1tumban.

                  • Minden szerz\u0151h\u00f6z k\u00fcl\u00f6n sor tartozik.
                  • Minden sorban el\u0151sz\u00f6r a szerz\u0151 neve szerepel, majd ;-t k\u00f6vetve ;-vel elv\u00e1lasztva sz\u00e1mok c\u00edmei.
                  • A f\u00e1jl tartalma \u00e9rv\u00e9nyesnek tekintend\u0151, ha \u00fcres, vagy csak whitespace (space, tab) karaktereket tartalmaz\u00f3 sorok is vannak.

                  A mell\u00e9kelt music.txt f\u00e1jl tartalma a k\u00f6vetkez\u0151h\u00f6z hasonl\u00f3:

                  Adele; Hello; Rolling in the Deep; Skyfall\nEnnio Morricone;    A Fistful Of Dollars; Man with a Harmonica\nAC/DC; Thunderstruck; T.N.T\n

                  Olvassuk be a f\u00e1jlt Song oszt\u00e1lybeli objektumok list\u00e1j\u00e1ba. Egy Song objektum egy dal adatait t\u00e1rolja (szerz\u0151 \u00e9s c\u00edm). A beolvas\u00e1st k\u00f6vet\u0151en \u00edrjuk ki form\u00e1zott m\u00f3don az objektumok adatait a szabv\u00e1nyos kimenetre az al\u00e1bbi form\u00e1ban:

                  szerz\u01511: szerz\u01511_dalc\u00edm1\nszerz\u01511: szerz\u01511_dalc\u00edm2\n...\nszerz\u01512: szerz\u01512_dalc\u00edm1\n...\nstb.\n

                  A mintaf\u00e1jlunk eset\u00e9ben a k\u00f6vetkez\u0151 (a f\u00e1jl tartalm\u00e1nak f\u00fcggv\u00e9ny\u00e9ben lehet elt\u00e9r\u00e9s) kimenetet szeretn\u00e9nk l\u00e1tni:

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/#a-megvalositas-lepesei","title":"A megval\u00f3s\u00edt\u00e1s l\u00e9p\u00e9sei","text":"

                  Vegy\u00fcnk fel egy Song nev\u0171 oszt\u00e1lyt a projektbe (jobb katt a Solution Explorerben a projekten, a men\u00fcben Add / Class).

                  Vegy\u00fck fel a sz\u00fcks\u00e9ges tagokat \u00e9s egy ezekhez passzol\u00f3 konstruktort:

                  public class Song\n{\n    public readonly string Artist;\n    public readonly string Title;\n\n    public Song(string artist, string title)\n    {\n        Artist = artist;\n        Title = title;\n    }\n}\n

                  Property

                  A tagv\u00e1ltoz\u00f3kat readonly-k\u00e9nt vett\u00fck fel, mert nem akartuk, hogy ezek ut\u00f3lag, a konstruktor lefut\u00e1s\u00e1t k\u00f6vet\u0151en megv\u00e1ltoztathat\u00f3k legyenek. Alternat\u00edva lehetne a csak olvashat\u00f3 tulajdons\u00e1g (property) alkalmaz\u00e1sa a readonly tagv\u00e1ltoz\u00f3k helyett (ez k\u00e9s\u0151bbi tanagyag).

                  A k\u00f6vetkez\u0151kben a Song oszt\u00e1lyunkban defini\u00e1ljuk fel\u00fcl az implicit System.Object \u0151sb\u0151l \u00f6r\u00f6k\u00f6lt ToString m\u0171veletet, hogy az az el\u0151\u00edrt form\u00e1ban adja vissza objektum adatait. A megold\u00e1sban sztring interpol\u00e1ci\u00f3t haszn\u00e1ljunk (ezt m\u00e1r alkalmaztuk az els\u0151 labor keret\u00e9ben):

                  public override string ToString()\n{\n    return $\"{Artist}: {Title}\";\n}\n

                  Sz\u00f6vegf\u00e1jl feldolgoz\u00e1s\u00e1ra legk\u00e9nyelmesebben a System.IO n\u00e9vt\u00e9rben lev\u0151 StreamReader oszt\u00e1lyt tudjuk haszn\u00e1lni.

                  A Main f\u00fcggv\u00e9ny\u00fcnkben olvassuk fel soronk\u00e9nt a f\u00e1jlt, hozzuk l\u00e9tre a Song objektumokat, \u00e9s tegy\u00fck be egy List<Song> dinamikusan ny\u00fajt\u00f3zkod\u00f3 t\u00f6mbbe. Figyelj\u00fcnk arra, hogy a f\u00e1jlban a ;-vel elv\u00e1lasztott elemek el\u0151tt/ut\u00e1n whitespace karakterek (space, tab) lehetnek, ezekt\u0151l szabaduljunk meg!

                  A k\u00f6vetkez\u0151 k\u00f3d egy lehets\u00e9ges megold\u00e1st mutat, a megold\u00e1s r\u00e9szleteit a k\u00f3dkommentek magyar\u00e1zz\u00e1k. A f\u00e9l\u00e9v sor\u00e1n ez az els\u0151 \u00f6n\u00e1ll\u00f3 feladat, valamint a hallgat\u00f3k t\u00f6bbs\u00e9g\u00e9nek ez els\u0151 .NET/C# alkalmaz\u00e1sa, \u00edgy itt m\u00e9g adunk mintamegold\u00e1st, de a rutinosabb hallgat\u00f3k \u00f6n\u00e1ll\u00f3an is pr\u00f3b\u00e1lkozhatnak.

                  Megold\u00e1s
                  namespace MusicApp;\n\npublic class Program\n{\n    // A Main f\u00fcggv\u00e9ny a Program oszt\u00e1lyon bel\u00fcl tal\u00e1lhat\u00f3, ezt itt nem jel\u00fclj\u00fck\n    public static void Main(string[] args)\n    {\n        // Ebben t\u00e1roljuk a dal objektumokat\n        List<Song> songs = new List<Song>();\n\n        // F\u00e1jl beolvas\u00e1sa soronk\u00e9nt, songs lista felt\u00f6lt\u00e9se\n        StreamReader sr = null;\n        try\n        {\n            // A @ jelent\u00e9se a string konstans el\u0151tt:\n            // kikapcsolja a string escape-el\u00e9st,\n            // \u00edgy nem kell a '\\' helyett '\\\\'-t \u00edrni.\n            sr = new StreamReader(@\"C:\\temp\\music.txt\");\n            string line;\n            while ((line = sr.ReadLine()) != null)\n            {\n                // Ha \u00fcres volt a sor\n                if (string.IsNullOrWhiteSpace(line))\n                    continue;\n\n                // A line v\u00e1ltoz\u00f3ban benne van az eg\u00e9sz sor,\n                // a Split-tel a ;-k ment\u00e9n feldaraboljuk\n                string[] lineItems = line.Split(';');\n\n                // Els\u0151 elem, amiben az szerz\u0151 nev\u00e9t v\u00e1rjuk\n                // A Trim elt\u00e1vol\u00edtja a vezet\u0151 \u00e9s z\u00e1r\u00f3 whitespace karaktereket\n                string artist = lineItems[0].Trim();\n\n                // Menj\u00fcnk v\u00e9gig a dalokon, \u00e9s vegy\u00fck fel a list\u00e1ba\n                for (int i = 1; i < lineItems.Length; i++)\n                {\n                    Song song = new Song(artist, lineItems[i].Trim());\n                    songs.Add(song);\n                }\n            }\n        }\n        catch (Exception e)\n        {\n            Console.WriteLine(\"A f\u00e1jl feldolgoz\u00e1sa sikertelen.\");\n            // Az e.Message csak a kiv\u00e9tel sz\u00f6veg\u00e9t tartalmazza. \n            // Ha minden kiv\u00e9tel inform\u00e1ci\u00f3t ki szeretn\u00e9nk \u00edrni (pl. stack trace), \n            // akkor az e.ToString()-et \u00edrjuk ki.\n            Console.WriteLine(e.Message);\n        }\n        finally\n        {\n            // L\u00e9nyeges, hogy finally blokkban z\u00e1rjuk le a f\u00e1jlt, \n            // hogy egy esetleges kiv\u00e9tel eset\u00e9n se maradjon m\u00f6g\u00f6tt\u00fcnk lez\u00e1ratlan \u00e1llom\u00e1ny.\n            // try-finally helyett haszn\u00e1lhattunk volna using blokkot,\n            // azt egyel\u0151re nem kell tudni (a f\u00e9l\u00e9v derek\u00e1n tanuljuk).\n            if (sr != null)\n                sr.Close();\n        }\n\n        // A songs lista elemeinek ki\u00edr\u00e1sa a konzolra\n        foreach (Song song in songs)\n            Console.WriteLine(song.ToString());\n    }\n}\n

                  A c:\\temp mapp\u00e1ba m\u00e1soljuk ki a music.txt f\u00e1jlt, \u00e9s futtassuk az alkalmaz\u00e1st. A megval\u00f3s\u00edt\u00e1s sor\u00e1n az egyszer\u0171s\u00e9gre t\u00f6rekedve mindent bele\u00f6nt\u00f6tt\u00fcnk a main f\u00fcggv\u00e9nybe, \u201e\u00e9les\u201d k\u00f6rnyezetben mindenk\u00e9pp c\u00e9lszer\u0171 a k\u00f3dot egy k\u00fcl\u00f6n feldolgoz\u00f3 oszt\u00e1lyba kiszervezni.

                  A fenti p\u00e9ld\u00e1ban j\u00f3p\u00e1r .NET/C# alaptechnika bemutat\u00e1sra ker\u00fcl, mindenk\u00e9pen \u00e9rdemes a fenti k\u00f3dba sz\u00fart megjegyz\u00e9sek alapj\u00e1n ezeket \u00e9rtelmezni \u00e9s megtanulni, a f\u00e9l\u00e9v sor\u00e1n ezekre \u00e9p\u00edteni fogunk.

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/#feladat-2-az-uml-es-a-kod-kapcsolata-interfesz-es-absztrakt-os-alkalmazastechnikaja","title":"Feladat 2 - Az UML \u00e9s a k\u00f3d kapcsolata, interf\u00e9sz \u00e9s absztrakt \u0151s alkalmaz\u00e1stechnik\u00e1ja","text":""},{"location":"hazi/1-model-es-kod-kapcsolata/#kiindulo-kornyezet","title":"Kiindul\u00f3 k\u00f6rnyezet","text":"

                  A kiindul\u00e1si k\u00f6rnyezet a Feladat2 mapp\u00e1ban tal\u00e1lhat\u00f3, az ebben lev\u0151 Shapes.sln f\u00e1jlt nyissuk meg Visual Studioban, \u00e9s ebben a solutionben dolgozzunk.

                  Figyelem!

                  \u00daj solution \u00e9s/vagy projektf\u00e1jl l\u00e9trehoz\u00e1sa, vagy a projekt m\u00e1s/\u00fajabb .NET verzi\u00f3kra targetel\u00e9se tilos.

                  A Feladat2\\Shapes mapp\u00e1ban tal\u00e1lhat\u00f3 egy Controls.dll f\u00e1jl, ezt a feladat megold\u00e1sa sor\u00e1n kell majd felhaszn\u00e1lni.

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/#beadando-a-forraskodon-tulmenoen","title":"Beadand\u00f3 (a forr\u00e1sk\u00f3don t\u00falmen\u0151en)","text":"

                  K\u00e9t-h\u00e1rom bekezd\u00e9sben a Feladat 2 megold\u00e1sa sor\u00e1n hozott tervez\u0151i d\u00f6nt\u00e9sek, a megold\u00e1s legfontosabb alapelveinek r\u00f6vid sz\u00f6veges \u00f6sszefoglal\u00e1sa, indokl\u00e1sa. Ezt a kiindul\u00f3 keret Feladat2 mapp\u00e1j\u00e1ban m\u00e1r megtal\u00e1lhat\u00f3 readme.md sz\u00f6vegf\u00e1jlba kell bele\u00edrni tetsz\u0151leges markdown form\u00e1tumban, vagy egyszer\u0171 nyers sz\u00f6vegk\u00e9nt. Fontos, hogy a Feladat2 mapp\u00e1ban lev\u0151 f\u00e1jlba dolgozz (akkor is, ha esetleg a gy\u00f6k\u00e9rmapp\u00e1ban is van egy azonos nev\u0171 f\u00e1jl).

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/#feladat_1","title":"Feladat","text":"

                  Egy s\u00edkbeli vektorgrafikus alakzatokat kezelni k\u00e9pes CAD tervez\u0151alkalmaz\u00e1s els\u0151 v\u00e1ltozat\u00e1nak kifejleszt\u00e9s\u00e9vel b\u00edznak meg benn\u00fcnket. B\u0151vebben:

                  • K\u00fcl\u00f6nb\u00f6z\u0151 t\u00edpus\u00fa alakzatokat kell tudni kezelni. Kezdetben a Square (n\u00e9gyzet), Circle (k\u00f6r) \u00e9s TextArea t\u00edpus\u00fa alakzatokat kell t\u00e1mogatni, de a k\u00f3d legyen k\u00f6nnyen b\u0151v\u00edthet\u0151 \u00faj t\u00edpusokkal. A TextArea egy szerkeszthet\u0151 sz\u00f6vegdoboz.

                    Elnevez\u00e9sek

                    Az oszt\u00e1lyokat mindenk\u00e9ppen a fentieknek megfelel\u0151en nevezz\u00fck el!

                  • Az alakzatokhoz tartoz\u00f3 adatok: x \u00e9s y koordin\u00e1ta, valamint olyan adatok, melyek a megjelen\u00edt\u00e9shez \u00e9s az alakzatok ter\u00fclet\u00e9nek kisz\u00e1m\u00edt\u00e1s\u00e1hoz sz\u00fcks\u00e9gesek. Pl. n\u00e9gyzet eset\u00e9ben oldalhossz\u00fas\u00e1g, TextArea eset\u00e9ben sz\u00e9less\u00e9g \u00e9s magass\u00e1g, k\u00f6r eset\u00e9ben a sug\u00e1r.

                  • Minden alakzatnak biztos\u00edtania kell m\u0171veleteket t\u00edpus\u00e1nak, koordin\u00e1t\u00e1i \u00e9s ter\u00fclet\u00e9nek lek\u00e9rdez\u00e9s\u00e9hez. A t\u00edpus lek\u00e9rdez\u0151 m\u0171velet string-gel t\u00e9rjen vissza, illetve a be\u00e9p\u00edtett Type oszt\u00e1ly GetType m\u0171velete nem haszn\u00e1lhat\u00f3 a megval\u00f3s\u00edt\u00e1s sor\u00e1n.

                  • List\u00e1zni kell tudni a mem\u00f3ri\u00e1ban nyilv\u00e1ntartott alakzatokat a szabv\u00e1nyos kimenetre (konzolra). Ennek sor\u00e1n a k\u00f6vetkez\u0151 adatokat \u00edrjuk ki: alakzat t\u00edpusa (pl. n\u00e9gyzet eset\u00e9n Square stb.), a k\u00e9t koordin\u00e1ta, alakzat ter\u00fclete. A be\u00e9p\u00edtett Type oszt\u00e1ly GetType m\u0171velete nem haszn\u00e1lhat\u00f3 a t\u00edpus ki\u00edr\u00e1s sor\u00e1n.

                  • A TextArea oszt\u00e1lynak k\u00f6telez\u0151en a jelen feladathoz tartoz\u00f3 Controls.dll oszt\u00e1lyk\u00f6nyvt\u00e1r Textbox oszt\u00e1ly\u00e1b\u00f3l kell sz\u00e1rmaznia. A Controls.dll egy .NET szerelv\u00e9ny, leford\u00edtott form\u00e1ban tartalmaz oszt\u00e1lyokat.

                    Interf\u00e9szben alap\u00e9rtelmezett implement\u00e1ci\u00f3

                    B\u00e1r C# 8-t\u00f3l t\u00e1mogatott .NET interf\u00e9szben alap\u00e9rtelmezett implement\u00e1ci\u00f3 megad\u00e1sa. Ez sokszor hasznos technika, de a megold\u00e1sban nem alkalmazhat\u00f3, enn\u00e9l \"klasszikusabb\" megk\u00f6zel\u00edt\u00e9st kell v\u00e1lasztani.

                  • A megval\u00f3s\u00edt\u00e1s sor\u00e1n t\u00f6rekedjen egys\u00e9gbez\u00e1r\u00e1sra: pl. az alakzatok menedzsel\u00e9se legyen egy erre dedik\u00e1lt oszt\u00e1ly feladata.

                    Failure

                    Az nem elfogadhat\u00f3, ha a Main f\u00fcggv\u00e9nyben egy helyben l\u00e9trehozott egyszer\u0171 list\u00e1ba ker\u00fclnek az alakzatok t\u00e1rol\u00e1sra! Ezen fel\u00fcl a menedzsel\u00e9s\u00e9rt felel\u0151s oszt\u00e1ly NE sz\u00e1rmazzon a be\u00e9p\u00edtett List vagy hasonl\u00f3 oszt\u00e1lyb\u00f3l, hanem tartalmazza azt. Az adatok szabv\u00e1nyos kimentre t\u00f6rt\u00e9n\u0151 list\u00e1z\u00e1s\u00e1\u00e9rt ez az oszt\u00e1ly legyen a felel\u0151s.

                  • A megval\u00f3s\u00edt\u00e1s sor\u00e1n t\u00f6rekedjen a k\u00f6nny\u0171 b\u0151v\u00edthet\u0151s\u00e9gre, karbantarthat\u00f3s\u00e1gra, ker\u00fclje el a k\u00f3dduplik\u00e1ci\u00f3t (tagv\u00e1ltoz\u00f3k, m\u0171veletek, konstruktorok eset\u00e9ben egyar\u00e1nt). A megold\u00e1s elfogad\u00e1s\u00e1nak ezek kiemelt szempontjai!

                  • A Main f\u00fcggv\u00e9nyben mutasson p\u00e9ld\u00e1t az oszt\u00e1lyok haszn\u00e1lat\u00e1ra.

                  • Legk\u00e9s\u0151bb a megval\u00f3s\u00edt\u00e1s v\u00e9g\u00e9re k\u00e9sz\u00edtsen a Visual Studio solutionben egy oszt\u00e1lydiagramot, melyen a solution oszt\u00e1lyait j\u00f3l \u00e1ttekinthet\u0151 form\u00e1ban rendezze el. Az asszoci\u00e1ci\u00f3s kapcsolatokat asszoci\u00e1ci\u00f3 form\u00e1j\u00e1ban jelen\u00edtse meg, ne tagv\u00e1ltoz\u00f3k\u00e9nt (Show as Association ill. Show as Collection Association, l\u00e1sd 1. labor \u00fatmutat\u00f3ja).

                    Class Diagram komponens

                    A Visual Studio 2022 nem teszi fel minden esetben a Class Designer komponenst a telep\u00edt\u00e9s sor\u00e1n. Ha nem lehet Class Diagram-ot felvenni a Visual Studio projektbe (mert a Class Diagram nem szerepel a list\u00e1ban az Add / New Item parancs sor\u00e1n megjelen\u0151 ablak list\u00e1j\u00e1ban), akkor a Class Diagram komponenst ut\u00f3lag kell telep\u00edteni. Err\u0151l b\u0151vebben jelen \u00fatmutat\u00f3 Fejleszt\u0151k\u00f6rnyezet oldal\u00e1n lehet olvasni.

                  A megval\u00f3s\u00edt\u00e1s sor\u00e1n jelent\u0151s egyszer\u0171s\u00edt\u00e9ssel \u00e9l\u00fcnk:

                  • Az alakzatok kirajzol\u00e1s\u00e1t nem val\u00f3s\u00edtjuk meg (az ehhez sz\u00fcks\u00e9ges ismeretek a f\u00e9l\u00e9v sor\u00e1n k\u00e9s\u0151bb szerepelnek).
                  • Az alakzatokat csak a mem\u00f3ri\u00e1ban kell nyilv\u00e1ntartani.
                  "},{"location":"hazi/1-model-es-kod-kapcsolata/#osztalykonyvtarak-hasznalata","title":"Oszt\u00e1lyk\u00f6nyvt\u00e1rak haszn\u00e1lata","text":"

                  A megold\u00e1s az 1. A modell \u00e9s a k\u00f3d kapcsolata laborgyakorlat mint\u00e1j\u00e1ra kidolgozhat\u00f3. Jelen feladat egy l\u00e9nyeges r\u00e9szlet\u00e9ben k\u00fcl\u00f6nb\u00f6zik t\u0151le: m\u00edg abban csak sz\u00f3ban k\u00f6t\u00f6tt\u00fck ki, hogy a DisplayBase \u0151soszt\u00e1ly forr\u00e1sk\u00f3dja nem megv\u00e1ltoztat\u00f3, jelen esetben a Textbox \u0151soszt\u00e1lyunk eset\u00e9ben ez adott, hiszen csak egy leford\u00edtott dll form\u00e1j\u00e1ban \u00e1ll rendelkez\u00e9sre.

                  Note

                  T\u00f6bbkomponens\u0171 alkalmaz\u00e1sok fejleszt\u00e9s\u00e9r\u0151l, szerelv\u00e9ny \u00e9s projekt referencia alkalmaz\u00e1s\u00e1r\u00f3l az els\u0151 el\u0151ad\u00e1son volt sz\u00f3, ha nem eml\u00e9kszel erre a t\u00e9mak\u00f6rre, c\u00e9lszer\u0171 \u00e1tism\u00e9telni.

                  A k\u00f6vetkez\u0151kben n\u00e9zz\u00fck meg, milyen l\u00e9p\u00e9sekben lehet egy ilyen dll-ben lev\u0151 oszt\u00e1lyokat a k\u00f3dunkban felhaszn\u00e1lni:

                  1. A Visual Studio Solution Explorer ablak\u00e1ban jobb gombbal kattintsunk a Dependencies elemen, \u00e9s v\u00e1lasszuk az Add Reference-t vagy Add Project Reference-t (amelyik l\u00e9tezik).
                  2. A megjelen\u0151 ablak bal oldal\u00e1n v\u00e1lasszuk ki a Browse elemet,
                  3. Ha az ablak k\u00f6zep\u00e9n a list\u00e1ban megjelenik a Controls.dll, pip\u00e1ljuk ki az elemet.
                  4. Ha nem jelenik meg, akkor kattintsunk az ablakunk jobb als\u00f3 r\u00e9sz\u00e9ben lev\u0151 Browse... gombon. 1. A megjelen\u0151 f\u00e1jlb\u00f6ng\u00e9sz\u0151 ablakban navig\u00e1ljunk el a Controls.dll f\u00e1jlhoz, \u00e9s kattintsunk rajta dupl\u00e1n, ami bez\u00e1rja az ablakot. 2. A Reference Manager ablakunk k\u00f6z\u00e9ps\u0151 r\u00e9sz\u00e9n a Controls.dll l\u00e1that\u00f3 kipip\u00e1lva, az OK gombbal z\u00e1rjuk be az ablakot.
                  5. Az OK gombbal z\u00e1rjuk be az ablakot.
                  Ha esetleg 'Reference is invalid or unsupported' hiba\u00fczenetet kapsz

                  Nagyon ritk\u00e1n, de el\u0151fordulhat, hogy a fenti l\u00e9p\u00e9sek sor\u00e1n a Visual Studio a \"Reference is invalid or unsupported\" hiba\u00fczenetet jelzi. Ilyenkor az esetek t\u00f6bbs\u00e9g\u00e9ben a Visual Studio \u00fajratelep\u00edt\u00e9se seg\u00edt.

                  Ezzel a projekt\u00fcnkben felvett\u00fcnk egy referenci\u00e1t a Controls.dll-re, \u00edgy a benne lev\u0151 oszt\u00e1lyok haszn\u00e1lhat\u00f3k (pl. lehet p\u00e9ld\u00e1nyos\u00edtani \u0151ket, vagy lehet bel\u0151l\u00fck sz\u00e1rmaztatni). A Solution Explorer-ben a Dependencies majd Assemblies csom\u00f3pontot lenyitva a Controls megjelenik:

                  A Textbox oszt\u00e1ly, melyb\u0151l a TextArea oszt\u00e1lyunkat sz\u00e1rmaztatni kell, a Controls n\u00e9vt\u00e9rben tal\u00e1lhat\u00f3. A TextBox oszt\u00e1lynak egy konstruktora van, melynek n\u00e9gy param\u00e9tere van, az x \u00e9s y koordin\u00e1t\u00e1k, valamint a sz\u00e9less\u00e9g \u00e9s a magass\u00e1g. Amennyiben sz\u00fcks\u00e9g lenne r\u00e1, a t\u00f6bbi m\u0171velet felder\u00edt\u00e9s\u00e9ben az Object Browser seg\u00edt. Az Object Browser a View men\u00fcb\u0151l az Object Browser men\u00fc kiv\u00e1laszt\u00e1s\u00e1val nyithat\u00f3 meg. Az Object Browser egy \u00faj tabf\u00fcl\u00f6n jelenik meg.

                  Ha \u00fcres az Object Browser n\u00e9zet

                  A Visual Studio 2022 hajlamos arra, hogy mindaddig, am\u00edg nincs egy forr\u00e1sf\u00e1jl megnyitva, az Object Browserben nem jelen\u00edt meg semmit (csak egy \"No information\" kezdet\u0171 sz\u00f6veg l\u00e1tszik). Ha azt tapasztaljuk, hogy \u00fcres az Object Browser n\u00e9zet, csak nyissuk meg a Program.cs f\u00e1jl a Solution Explorerben, majd v\u00e1ltsunk vissza az Object Browser tabf\u00fclre, ahol \u00edgy m\u00e1r megjelennek a komponensek.

                  Az Object Browserben a Controls komponenst lenyitogatva az egyes csom\u00f3pontokat kiv\u00e1lasztva (n\u00e9vt\u00e9r, oszt\u00e1ly) az adott csom\u00f3pont jellemz\u0151i jelennek meg: pl. az oszt\u00e1ly nev\u00e9n \u00e1llva az oszt\u00e1ly tagjait l\u00e1tjuk.

                  Most m\u00e1r minden inform\u00e1ci\u00f3 rendelkez\u00e9s\u00fcnkre \u00e1ll a feladat megval\u00f3s\u00edt\u00e1s\u00e1hoz.

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/#beadas","title":"Bead\u00e1s","text":"

                  Ellen\u0151rz\u0151lista ism\u00e9tl\u00e9sk\u00e9ppen:

                  • A repository gy\u00f6k\u00e9rmapp\u00e1j\u00e1ban tal\u00e1lhat\u00f3 neptun.txt f\u00e1jlba \u00edrd bele a Neptun k\u00f3dod, csupa nagybet\u0171vel. A f\u00e1jlban csak ez a hat karakter legyen, semmi m\u00e1s.
                  • A GitHub-r\u00f3l le\u00f6lt\u00f6tt kiindul\u00f3 solutionben/projektekben kell dolgozni, nem \u00fajonnan l\u00e9trehozottban.
                  • Am\u00edg nem vagy rutinos a Visual Studio Git szolg\u00e1ltat\u00e1sainak haszn\u00e1lat\u00e1ban, a push-t k\u00f6vet\u0151en (legk\u00e9s\u0151bb akkor, amikor a h\u00e1zi feladatot beadottnak tekintj\u00fck) c\u00e9lszer\u0171 ellen\u0151rizni a GitHub webes fel\u00fclet\u00e9n a repository-ban a f\u00e1jlokra val\u00f3 r\u00e1pillant\u00e1ssal, hogy val\u00f3ban minden v\u00e1ltoztat\u00e1st felt\u00f6lt\u00f6tt\u00e9l-e.
                  • A GitHub fel\u00fclet\u00e9n ellen\u0151rizd a push-t k\u00f6vet\u0151en, hogy a GitHub Action alap\u00fa el\u0151ellen\u0151rz\u0151 hiba n\u00e9lk\u00fcl lefutott-e.
                  • L\u00e9nyeges, hogy a feladatok csak akkor ker\u00fclnek elfogad\u00e1sra, ha teljesen elk\u00e9sz\u00fclnek, \u00e9s minden tekintetben teljes\u00edtik a k\u00f6vetelm\u00e9nyeket. Nem fordul\u00f3 k\u00f3d, illetve r\u00e9szleges megold\u00e1s elfogad\u00e1s\u00e1ban nem \u00e9rdemes b\u00edzni.
                  • Term\u00e9szetesen saj\u00e1t munk\u00e1t kell beadni (hiszen \u00e9rt\u00e9kel\u00e9sre ker\u00fcl).

                  • A 2. feladat sor\u00e1n ne felejtsd el a readme.md-ben a megold\u00e1sod bemutatni.

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/","title":"1. HF - Beziehung zwischen Modell und Code","text":""},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

                  Die \u00dcbung ist nicht mit einer Pr\u00e4sentation verbunden. Den theoretischen und praktischen Hintergrund f\u00fcr die \u00dcbungen liefert das Kapitel \"1. Die Beziehung zwischen Modell und Code\" wird als angeleitete Labor\u00fcbung dienen:

                  • Diese Labor\u00fcbung wird von den Studierenden unter Anleitung des Tutors gemeinsam durchgef\u00fchrt.
                  • Die Labor\u00fcbung wird von einem Leitfaden begleitet, der einen detaillierten theoretischen Hintergrund und eine Schritt-f\u00fcr-Schritt-Anleitung f\u00fcr die Herstellung einer L\u00f6sung enth\u00e4lt: 1. Beziehung zwischen dem Modell und dem Code

                  Darauf aufbauend k\u00f6nnen die Aufgaben dieser Selbst\u00fcbung mit Hilfe der k\u00fcrzeren Leitf\u00e4den, die der Aufgabenbeschreibung folgen, durchgef\u00fchrt werden.

                  Das Ziel der unabh\u00e4ngigen \u00dcbung:

                  • Erstellen einer einfachen .NET-Anwendung, \u00dcben der C#-Grundlagen
                  • Veranschaulichung der Beziehung zwischen UML und Code
                  • Praktische Anwendung der Schnittstelle und der abstrakten primitiven Klasse

                  Die erforderliche Entwicklungsumgebung wird hier beschrieben.

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#laden-sie-den-ausgangsrahmen-herunter-laden-sie-die-fertige-losung-hoch","title":"Laden Sie den Ausgangsrahmen herunter, laden Sie die fertige L\u00f6sung hoch","text":"

                  Die urspr\u00fcngliche Hausaufgabenumgebung wird ver\u00f6ffentlicht und die L\u00f6sung wird \u00fcber Git, GitHub und GitHub Classroom eingereicht. Die wichtigsten Schritte:

                  1. Erstellen Sie mit GitHub Classroom ein Repository f\u00fcr sich selbst. Sie finden die Einladungs-URL in Moodle (Sie k\u00f6nnen sie sehen, indem Sie auf den Link*\"GitHub classroom links for homework*\" auf der Startseite des Fachs klicken).
                  2. Klonen Sie das resultierende Repository. Dazu geh\u00f6rt auch die erwartete Struktur der L\u00f6sung.
                  3. Nachdem Sie die Aufgaben erledigt haben, \u00fcbergeben Sie Ihre L\u00f6sung alt und dr\u00fccken Sie sie alt.

                  Diese werden hier ausf\u00fchrlicher beschrieben:

                  • Git, GitHub, GitHub Classroom
                  • Arbeitsablauf bei Hausaufgaben und Verwendung von Git/GitHub
                  "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#vorabkontrolle-und-formale-bewertung-der-hausaufgaben","title":"Vorabkontrolle und formale Bewertung der Hausaufgaben","text":"

                  Jedes Mal, wenn Sie Code auf GitHub hochladen, f\u00fchrt GitHub automatisch eine (Vor-)Pr\u00fcfung des hochgeladenen Codes durch und Sie k\u00f6nnen das Ergebnis sehen! Weitere Informationen dazu finden Sie hier (lesen Sie sie unbedingt): Vorabkontrolle und formale Bewertung der Hausaufgaben.

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#aufgabe-1-erstellen-einer-einfachen-net-konsolenanwendung","title":"Aufgabe 1 - Erstellen einer einfachen .NET-Konsolenanwendung","text":""},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#ursprungliches-projekt","title":"Urspr\u00fcngliches Projekt","text":"

                  Die anf\u00e4ngliche Umgebung befindet sich im Ordner Feladat1, \u00f6ffnen Sie die Datei MusicApp.sln in Visual Studio und arbeiten Sie in dieser L\u00f6sung.

                  Achtung!

                  Das Erstellen einer neuen Projektmappe und/oder Projektdatei oder die Ausrichtung des Projekts auf andere/neuere .NET-Versionen ist verboten.

                  Im Ordner Feladat1\\Input befindet sich eine Datei music.txt, die als Eingabe f\u00fcr die Aufgabe verwendet werden soll.

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#verfasst-am","title":"Verfasst am","text":"

                  In einem Textstring speichern wir die Titel der Lieder von Komponisten/Interpreten/Ensembles im folgenden Format.

                  • Jeder Autor hat eine eigene Zeile.
                  • Jede Zeile beginnt mit dem Namen des Autors, gefolgt von ;, gefolgt von den Titeln der Nummern, getrennt durch ;.
                  • Der Inhalt der Datei wird als g\u00fcltig angesehen, wenn Leerzeilen oder Zeilen, die nur Leerzeichen (Leerzeichen, Tabulator) enthalten, vorhanden sind.

                  Der Inhalt der beigef\u00fcgten Datei music.txt ist \u00e4hnlich wie der folgende:

                  Adele; Hello; Rolling in the Deep; Skyfall\nEnnio Morricone;    A Fistful Of Dollars; Mann mit der Mundharmonika\nAC/DC; Thunderstruck; T.N.T\n

                  Lesen Sie die Datei in die Liste der Klassenobjekte Song. Ein Objekt Song speichert die Daten (Autor und Titel) eines Liedes. Nach dem Scannen schreiben Sie die formatierten Daten der Objekte in folgendem Format auf die Standardausgabe:

                  autor1: Autor1_Titel1\nautor1: Autor1_Titel2\n...\nautor2: Autor2_Songtitel1\n...\nusw.\n

                  F\u00fcr unsere Beispieldatei m\u00f6chten wir die folgende Ausgabe sehen (die je nach Inhalt der Datei variieren kann):

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#schritte-der-umsetzung","title":"Schritte der Umsetzung","text":"

                  F\u00fcgen Sie dem Projekt eine Klasse mit dem Namen Song hinzu (Rechtsklick auf das Projekt im Solution Explorer, Men\u00fc Hinzuf\u00fcgen / Klasse).

                  F\u00fcgen Sie die erforderlichen Mitglieder und einen passenden Konstruktor ein:

                  public class Song\n{\n    public readonly string Artist;\n    public readonly string Title;\n\n    public Song(string artist, string title)\n    {\n        Artist = artist;\n        Title = title;\n    }\n}\n

                  Property

                  Die Mitgliedsvariablen wurden als readonlyeingef\u00fcgt, weil wir nicht wollten, dass sie nach Ausf\u00fchrung des Konstruktors ge\u00e4ndert werden k\u00f6nnen. Eine Alternative w\u00e4re die Verwendung von schreibgesch\u00fctzten Eigenschaften anstelle von schreibgesch\u00fctzten Mitgliedsvariablen (dies ist ein sp\u00e4terer Kern).

                  Im Folgenden werden wir die Operation ToString, die vom impliziten Vorfahren System.Object geerbt wurde, in unserer Klasse Song umdefinieren, um Objektdaten in der gew\u00fcnschten Form zur\u00fcckzugeben. Verwenden Sie die String-Interpolation in der L\u00f6sung (wir haben dies bereits in der ersten \u00dcbung verwendet):

                  public override string ToString()\n{\n    return $\"{Artist}: {Title}\";\n}\n

                  Die geeignetste Klasse zur Verarbeitung einer Textdatei ist StreamReader im Namensraum System.IO.

                  In unserer Funktion Main lesen wir die Datei Zeile f\u00fcr Zeile ein, erstellen die Song Objekte und legen sie in ein List<Song> dynamisch dehnbares Array. Bitte beachten Sie, dass in der Datei vor/nach den durch ;getrennten Elementen Leerzeichen (Space, Tab) stehen k\u00f6nnen, entfernen Sie diese!

                  Der folgende Code zeigt eine m\u00f6gliche L\u00f6sung, deren Einzelheiten in den Codekommentaren erl\u00e4utert werden. Dies ist die erste eigenst\u00e4ndige Aufgabe des Semesters und f\u00fcr die meisten Studenten die erste Anwendung von .NET/C#, daher geben wir Ihnen eine Musterl\u00f6sung, aber erfahrenere Studenten k\u00f6nnen es auch selbst versuchen.

                  L\u00f6sung
                  namespace MusicApp;\n\npublic class Program\n{\n    // Die Funktion Main befindet sich innerhalb der Klasse Program, die hier nicht gezeigt wird\n    public static void Main(string[] args)\n    {\n        // Hier werden die Liedobjekte gespeichert\n        Liste<Song> songs = new List<Song>();\n\n        // Datei zeilenweise durchsuchen, Liederliste hochladen\n        StreamReader sr = null;\n        try\n        {\n            // @ steht f\u00fcr @ vor der Zeichenkettenkonstante:\n            // Deaktiviert String Escape,\n            // damit Sie nicht '\\\\' statt '\\\\' schreiben m\u00fcssen.\n            sr = new StreamReader(@\"C:\\temp\\music.txt\");\n            string line;\n            while ((line = sr.ReadLine()) != null)\n            {\n                // Wenn die Warteschlange leer war\n                if (string.IsNullOrWhiteSpace(line))\n                    continue;\n\n                // Die Zeilenvariable enth\u00e4lt die gesamte Zeile,\n                // geteilt entlang der ;- mit Split\n                string[] lineItems = line.Split(';');\n\n                // Erstes Element, in dem wir den Namen des Autors erwarten\n                // Trim entfernt f\u00fchrende und nachfolgende Wei\u00dfraumzeichen\n                string artist = lineItems[0].Trim();\n\n                // Gehen Sie die Lieder durch und f\u00fcgen Sie sie der Liste hinzu\n                for (int i = 1; i < lineItems.Length; i++)\n                {\n                    Song song = new Song(artist, lineItems[i].Trim());\n                    songs.Add(song);\n                }\n            }\n        }\n        catch (Exception e)\n        {\n            Console.WriteLine(\"Die Datei konnte nicht verarbeitet werden.\");\n            // e.Message enth\u00e4lt nur den Text der Ausnahme. \n            // Wenn Sie alle Ausnahmeinformationen (z.B. Stacktrace) ausgeben m\u00f6chten, \n            // dann wird e.ToString() gedruckt.\n            Console.WriteLine(e.Message);\n        }\n        finally\n        {\n            // Es ist wichtig, dass die Datei abschlie\u00dfend in einem Block geschlossen wird, \n            // um sicherzustellen, dass wir im Falle einer Ausnahme keine offene Datei haben.\n            // Wir h\u00e4tten einen using-Block anstelle von try-finally verwenden k\u00f6nnen,\n            // Das brauchen Sie noch nicht zu wissen (wir werden es in der Mitte des Semesters lernen).\n            if (sr != null)\n                sr.Close();\n        }\n\n        // Ausgabe der Lieder in der Liste auf der Konsole\n        foreach (Song song in songs)\n            Console.WriteLine(song.ToString());\n    }\n}\n

                  Kopieren Sie die Datei \"music.txt\" in den Ordner \"c:\\temp\" und starten Sie die Anwendung. Der Einfachheit halber haben wir alles in die Funktion main aufgenommen, aber in einer \"Live\"-Umgebung ist es ratsam, den Code in eine separate Verarbeitungsklasse auszulagern.

                  Im obigen Beispiel werden eine Reihe grundlegender .NET/C#-Techniken vorgestellt. Es lohnt sich auf jeden Fall, sie zu interpretieren und aus den Notizen im obigen Code zu lernen, und wir werden im Laufe des Semesters auf ihnen aufbauen.

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#aufgabe-2-beziehung-zwischen-uml-und-code-schnittstellen-und-abstrakten-anwendungstechniken","title":"Aufgabe 2 - Beziehung zwischen UML und Code, Schnittstellen und abstrakten Anwendungstechniken","text":""},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#ursprungliche-umgebung","title":"Urspr\u00fcngliche Umgebung","text":"

                  Die anf\u00e4ngliche Umgebung befindet sich im Ordner Feladat2, \u00f6ffnen Sie die Datei Shapes.sln in Visual Studio und arbeiten Sie in dieser L\u00f6sung.

                  Achtung!

                  Das Erstellen einer neuen Projektmappe und/oder Projektdatei oder die Ausrichtung des Projekts auf andere/neuere .NET-Versionen ist verboten.

                  Es gibt eine Datei Controls.dll im Ordner Feladat2\\Shapes, die Sie zur L\u00f6sung des Problems verwenden m\u00fcssen.

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#einzureichen-zusatzlich-zum-quellcode","title":"Einzureichen (zus\u00e4tzlich zum Quellcode)","text":"

                  In zwei bis drei Abs\u00e4tzen eine kurze textliche Zusammenfassung der bei der L\u00f6sung von Aufgabe 2 getroffenen Entwurfsentscheidungen, der wichtigsten Grunds\u00e4tze der L\u00f6sung und der Begr\u00fcndung daf\u00fcr. Dies sollte in die Textdatei readme.md geschrieben werden, die sich bereits im Ordner Feladat2 des urspr\u00fcnglichen Frames befindet, in einem beliebigen Markdown-Format oder als einfacher Text. Es ist wichtig, in der Datei im Ordner Feladat2 zu arbeiten (auch wenn es eine Datei mit demselben Namen im Stammordner gibt).

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#verfasst-am_1","title":"Verfasst am","text":"

                  Wir haben die Aufgabe, die erste Version einer CAD-Anwendung zu entwickeln, die fl\u00e4chige Vektorgrafiken verarbeiten kann. Lesen Sie mehr:

                  • Sie m\u00fcssen in der Lage sein, verschiedene Arten von Formen zu bearbeiten. Zun\u00e4chst sollten Square (Quadrat), Circle (Kreis) und TextArea unterst\u00fctzt werden, aber der Code sollte leicht um neue Typen erweiterbar sein. TextArea ist ein editierbares Textfeld.

                    Namen

                    Achten Sie darauf, dass Sie die Klassen entsprechend den obigen Angaben benennen!

                  • Die mit den Formen verbundenen Daten: x- und y-Koordinaten sowie Daten, die f\u00fcr die Visualisierung und die Berechnung des Fl\u00e4cheninhalts der Formen erforderlich sind. Zum Beispiel Seitenl\u00e4nge f\u00fcr ein Quadrat, Breite und H\u00f6he f\u00fcr TextArea, Radius f\u00fcr einen Kreis.

                  • Jede Form muss Operationen zur Abfrage ihres Typs, ihrer Koordinaten und ihrer Fl\u00e4che bieten. Die Typabfrageoperation sollte stringzur\u00fcckgeben, und die Operation GetType der eingebauten Klasse Type sollte in der Implementierung nicht verwendet werden.

                  • Sie m\u00fcssen in der Lage sein, die im Speicher abgelegten Formen auf der Standardausgabe (Konsole) aufzulisten. Die folgenden Daten werden geschrieben: Art der Form (z. B. f\u00fcr ein Quadrat Square usw.), die beiden Koordinaten, Fl\u00e4che der Form. Die Operation GetType der eingebauten Klasse Type kann nicht in der Typdeklaration verwendet werden.

                  • Die Klasse TextArea muss aus der Klasse Textbox der Klassenbibliothek Controls.dll f\u00fcr diese Aufgabe stammen. Controls.dll ist eine .NET-Assembly, die kompiliert wurde, um Klassen zu enthalten.

                    Standardimplementierung in Schnittstelle

                    Geben Sie die Standardimplementierung in der .NET-Schnittstelle an, die in C# 8 und h\u00f6her unterst\u00fctzt wird. Dies ist oft eine n\u00fctzliche Technik, die aber bei der L\u00f6sung nicht anwendbar ist; es sollte ein eher \"klassischer\" Ansatz gew\u00e4hlt werden.

                  • Bei der Umsetzung ist eine Vereinheitlichung anzustreben: z.B. sollte die Verwaltung der Formen in die Zust\u00e4ndigkeit einer eigenen Abteilung fallen.

                    Failure

                    Es ist nicht zul\u00e4ssig, Formen in einer lokal erzeugten einfachen Liste in der Funktion Main zu speichern! Au\u00dferdem sollte die Klasse, die f\u00fcr die Verwaltung zust\u00e4ndig ist, NICHT von der eingebauten Klasse List oder einer \u00e4hnlichen Klasse abgeleitet werden, sondern sie sollte diese enthalten. Diese Abteilung sollte f\u00fcr die Auflistung der Daten in einer Standardausgabe zust\u00e4ndig sein.

                  • Streben Sie bei der Implementierung nach einfacher Erweiterbarkeit, Wartbarkeit und Vermeidung von doppeltem Code (f\u00fcr Mitgliedsvariablen, Operationen, Konstruktoren). Dies sind die wichtigsten Kriterien f\u00fcr die Annahme der L\u00f6sung!

                  • Zeigen Sie ein Beispiel f\u00fcr die Verwendung von Klassen in der Funktion Main.

                  • Sp\u00e4testens am Ende der Implementierung erstellen Sie in Visual Studio Solution ein Klassendiagramm, in dem Sie die Klassen der L\u00f6sung \u00fcbersichtlich anordnen k\u00f6nnen. Zeigen Sie Assoziationsbeziehungen als Assoziation, nicht als Mitgliedsvariable*(Als Assoziation anzeigen* oder*Als Assoziation* anzeigen). Als Sammlungsverband anzeigen, siehe Laboranleitung 1).

                    Klassendiagrammkomponente

                    Visual Studio 2022 f\u00fcgt die Klassendesignerkomponente bei der Installation nicht immer hinzu. Wenn es nicht m\u00f6glich ist, ein Klassendiagramm zum Visual Studio-Projekt hinzuzuf\u00fcgen (weil das Klassendiagramm nicht in der Liste des Fensters aufgef\u00fchrt ist, das w\u00e4hrend des Befehls Hinzuf\u00fcgen / Neues Element erscheint), muss die Komponente Klassendiagramm nachtr\u00e4glich installiert werden. Weitere Informationen hierzu finden Sie auf der Seite Entwicklungsumgebung in diesem Handbuch.

                  Wir nehmen erhebliche Vereinfachungen bei der Umsetzung vor:

                  • Formen werden nicht gezeichnet (die notwendigen F\u00e4higkeiten werden sp\u00e4ter im Semester behandelt).
                  • Die Formen sollten nur im Speicher aufgezeichnet werden.
                  "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#verwendung-von-klassenbibliotheken","title":"Verwendung von Klassenbibliotheken","text":"

                  Die L\u00f6sung ist 1. Die Beziehung zwischen dem Modell und dem Code kann auf der Grundlage einer Labor\u00fcbung entwickelt werden. Die vorliegende Aufgabe unterscheidet sich in einem wichtigen Detail: W\u00e4hrend wir nur verbal feststellten, dass der Quellcode der Vorfahrenklasse DisplayBase nicht ver\u00e4nderbar ist, ist dies im Fall unserer Vorfahrenklasse Textbox eine Selbstverst\u00e4ndlichkeit, da sie nur als kompilierte DLL verf\u00fcgbar ist.

                  Note

                  Die Entwicklung von Mehrkomponentenanwendungen, die Zusammenstellung und die Projektreferenz wurden in der ersten Vorlesung behandelt; wenn Sie sich nicht an dieses Thema erinnern, lohnt es sich, es zu wiederholen.

                  Im Folgenden werden wir uns die Schritte zur Verwendung der Klassen in einer solchen DLL in unserem Code ansehen:

                  1. Klicken Sie im Fenster Visual Studio Solution Explorer mit der rechten Maustaste auf Abh\u00e4ngigkeiten und w\u00e4hlen Sie Verweis hinzuf\u00fcgen oder Projektverweis hinzuf\u00fcgen(je nachdem, was vorhanden ist).
                  2. W\u00e4hlen Sie auf der linken Seite des angezeigten Fensters Browse,
                  3. Wenn Controls.dll in der Liste in der Mitte des Fensters erscheint, deaktivieren Sie das Kontrollk\u00e4stchen.
                  4. Wenn sie nicht angezeigt wird, klicken Sie auf die Schaltfl\u00e4che Durchsuchen... unten rechts im Fenster. 1. Navigieren Sie im angezeigten Dateibrowser-Fenster zur Datei Controls.dll und doppelklicken Sie darauf, um das Fenster zu schlie\u00dfen. 2. In der Mitte des Referenzmanager-Fensters sehen Sie das H\u00e4kchen bei Controls.dll. Klicken Sie auf OK, um das Fenster zu schlie\u00dfen.
                  5. Klicken Sie auf OK, um das Fenster zu schlie\u00dfen.
                  Sehr selten, aber es kann vorkommen, dass Visual Studio eine Fehlermeldung

                  Referenz ist ung\u00fcltig oder wird nicht unterst\u00fctzt\" anzeigt, wenn Sie die oben genannten Schritte ausf\u00fchren. In den meisten F\u00e4llen hilft eine Neuinstallation von Visual Studio.

                  Damit haben wir in unserem Projekt einen Verweis auf Controls.dllhinzugef\u00fcgt, so dass die darin enthaltenen Klassen verwendet werden k\u00f6nnen (z. B. k\u00f6nnen sie instanziiert oder von ihnen abgeleitet werden). Wenn Sie im Projektmappen-Explorer auf Abh\u00e4ngigkeiten und dann auf Baugruppen klicken, werden Steuerelemente angezeigt:

                  Die Klasse Textbox, von der unsere Klasse TextArea abgeleitet werden soll, befindet sich im Namespace Controls. Die Klasse TextBox hat einen Konstruktor mit vier Parametern, den x- und y-Koordinaten sowie der Breite und H\u00f6he. Bei Bedarf kann der Object Browser *Ihnen helfen, andere Operationen zu entdecken. Der *Object Browser *kann durch Auswahl des Men\u00fcs *Object Browser *aus dem Men\u00fc *Ansicht ge\u00f6ffnet werden. Der *Object Browser *wird in einer neuen Registerkarte angezeigt.

                  Wenn die Objektbrowser-Ansicht leer ist

                  Visual Studio 2022 zeigt im Objektbrowser nichts an (nur den Text \"Keine Informationen\"), solange keine Quelldatei ge\u00f6ffnet ist. Wenn Sie feststellen, dass die Object Browser-Ansicht leer ist, \u00f6ffnen Sie einfach die Datei Program.cs im Projektmappen-Explorer und wechseln Sie zur\u00fcck zur Registerkarte Object Browser, wo die Komponenten nun angezeigt werden.

                  Wenn Sie im *Object Browser *auf die Komponente Controls klicken und jeden Knoten (Namensraum, Klasse) ausw\u00e4hlen, werden die Attribute dieses Knotens angezeigt: Wenn Sie z. B. auf den Klassennamen klicken, werden die Mitglieder der Klasse angezeigt.

                  Wir haben nun alle Informationen, die wir zur Erf\u00fcllung der Aufgabe ben\u00f6tigen.

                  "},{"location":"hazi/1-model-es-kod-kapcsolata/index_ger/#vorlegen-bei","title":"Vorlegen bei","text":"

                  Checkliste f\u00fcr Wiederholungen:

                  • Geben Sie in der Datei neptun.txt im Stammverzeichnis des Repositorys Ihren Neptun-Code in Gro\u00dfbuchstaben ein. Die Datei sollte nur diese sechs Zeichen enthalten und nichts anderes.
                  • Sie sollten in den urspr\u00fcnglichen L\u00f6sungen/Projekten arbeiten, die Sie von GitHub heruntergeladen haben, und nicht in neu erstellten Projekten.
                  • Solange Sie nicht mit Visual Studio Git vertraut sind, sollten Sie nach dem Push (sp\u00e4testens wenn die Hausarbeit als eingereicht gilt) \u00fcberpr\u00fcfen, ob Sie alle \u00c4nderungen hochgeladen haben, indem Sie sich die Dateien im Repository auf der GitHub-Weboberfl\u00e4che ansehen.
                  • \u00dcberpr\u00fcfen Sie in der GitHub-Schnittstelle nach dem Push, ob der GitHub Action-basierte Pre-Validator fehlerfrei gelaufen ist.
                  • Es ist wichtig, dass Aufgaben nur angenommen werden, wenn sie vollst\u00e4ndig abgeschlossen sind und den Anforderungen in jeder Hinsicht entsprechen. Nicht rotierenden Codes oder Teill\u00f6sungen sollte man nicht trauen.
                  • Nat\u00fcrlich m\u00fcssen Sie Ihre eigene Arbeit einreichen (da sie bewertet wird).

                  • Vergessen Sie bei Aufgabe 2 nicht, Ihre L\u00f6sung unter readme.mdeinzureichen.

                  "},{"location":"hazi/2-nyelvi-eszkozok/","title":"2. HF - Nyelvi eszk\u00f6z\u00f6k","text":""},{"location":"hazi/2-nyelvi-eszkozok/#bevezetes","title":"Bevezet\u00e9s","text":"

                  Az \u00f6n\u00e1ll\u00f3 feladat a 2. el\u0151ad\u00e1son \u00e9s a 3. el\u0151ad\u00e1s els\u0151 fel\u00e9ben elhangzottakra \u00e9p\u00edt (ezek a \"El\u0151ad\u00e1s 02 - Nyelvi eszk\u00f6z\u00f6k\" el\u0151ad\u00e1sanyagban szerepelnek). Gyakorlati h\u00e1tter\u00e9\u00fcl a 2. labor - Nyelvi eszk\u00f6z\u00f6k laborgyakorlat szolg\u00e1l.

                  A fentiekre \u00e9p\u00edtve, jelen \u00f6n\u00e1ll\u00f3 gyakorlat feladatai a feladatle\u00edr\u00e1st k\u00f6vet\u0151 r\u00f6videbb ir\u00e1nymutat\u00e1s seg\u00edts\u00e9g\u00e9vel elv\u00e9gezhet\u0151k.

                  Az \u00f6n\u00e1ll\u00f3 gyakorlat c\u00e9lja:

                  • Tulajdons\u00e1gok (property) haszn\u00e1lat\u00e1nak gyakorl\u00e1sa
                  • Deleg\u00e1tok (delegate) \u00e9s esem\u00e9nyek (event) alkalmaz\u00e1sa
                  • .NET attrib\u00fatumok haszn\u00e1lat\u00e1nak gyakorl\u00e1sa
                  • Alapvet\u0151 gy\u0171jtem\u00e9nyt\u00edpusok haszn\u00e1lat\u00e1nak gyakorl\u00e1sa
                  • Lambda kifejez\u00e9sek gyakorl\u00e1sa

                  A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s.

                  C# 12-es (\u00e9s \u00fajabb) nyelvi elemek haszn\u00e1lata

                  A h\u00e1zi feladat megold\u00e1sa sor\u00e1n C# 12-es, \u00e9s ann\u00e1l \u00fajabb nyelvi elemek, (pl. primary constructor) nem haszn\u00e1lhat\u00f3k, ugyanis a GitHub-on fut\u00f3 ellen\u0151rz\u0151 ezeket m\u00e9g nem t\u00e1mogatja.

                  "},{"location":"hazi/2-nyelvi-eszkozok/#beadas-menete-eloellenorzo","title":"Bead\u00e1s menete, el\u0151ellen\u0151rz\u0151","text":"

                  A bead\u00e1s menete megegyezik az els\u0151 h\u00e1zi feladat\u00e9val (r\u00e9szletes le\u00edr\u00e1s a szok\u00e1sos helyen, l\u00e1sd H\u00e1zi feladat munkafolyamat \u00e9s a Git/GitHub haszn\u00e1lata):

                  1. GitHub Classroom seg\u00edts\u00e9g\u00e9vel hozz l\u00e9tre magadnak egy repository-t. A megh\u00edv\u00f3 URL-t Moodle-ben tal\u00e1lod (a t\u00e1rgy nyit\u00f3oldal\u00e1n a \"GitHub classroom hivatkoz\u00e1sok a h\u00e1zi feladatokhoz\" hivatkoz\u00e1sra kattintva megjelen\u0151 oldalon l\u00e1that\u00f3). Fontos, hogy a megfelel\u0151, ezen h\u00e1zi feladathoz tartoz\u00f3 megh\u00edv\u00f3 URL-t haszn\u00e1ld (minden h\u00e1zi feladathoz m\u00e1s URL tartozik).
                  2. Kl\u00f3nozd le az \u00edgy elk\u00e9sz\u00fclt repository-t. Ez tartalmazni fogja a megold\u00e1s elv\u00e1rt szerkezet\u00e9t.
                  3. A feladatok elk\u00e9sz\u00edt\u00e9se ut\u00e1n commit-old \u00e9s push-old a megold\u00e1sod.

                  Az el\u0151ellen\u0151rz\u0151 is a szok\u00e1sos m\u00f3don m\u0171k\u00f6dik. R\u00e9szletes le\u00edr\u00e1s: A h\u00e1zi feladat el\u0151ellen\u0151rz\u00e9se \u00e9s hivatalos \u00e9rt\u00e9kel\u00e9se.

                  "},{"location":"hazi/2-nyelvi-eszkozok/#feladat-1-baljos-arnyak","title":"Feladat 1 \u2013 Balj\u00f3s \u00e1rnyak","text":""},{"location":"hazi/2-nyelvi-eszkozok/#feladat","title":"Feladat","text":"

                  Amint az k\u00f6zismert, a jedi lovagok erej\u00e9t a sejtjeikben \u00e9l\u0151 kis \u00e9letform\u00e1k, a midi-chlorianok adj\u00e1k. Az eddigi legmagasabb midi-chlorian szintet (20.000 f\u00f6l\u00f6tti \u00e9rt\u00e9ket) Anakin Skywalkern\u00e9l m\u00e9rt\u00e9k.

                  K\u00e9sz\u00edts egy oszt\u00e1lyt Jedi n\u00e9ven mely egy string t\u00edpus\u00fa Name \u00e9s egy int t\u00edpus\u00fa MidiChlorianCount tulajdons\u00e1ggal rendelkezik. Ut\u00f3bbi eset\u00e9ben figyelj r\u00e1, hogy a MidiChlorianCount \u00e9rt\u00e9k\u00e9t ne lehessen 35-re, vagy ann\u00e1l kisebb \u00e9rt\u00e9kre \u00e1ll\u00edtani, ha ezzel pr\u00f3b\u00e1lkozik valaki, az oszt\u00e1lynak kiv\u00e9telt kell dobnia. A valid\u00e1ci\u00f3 sor\u00e1n a lehet\u0151 legegyszer\u0171bb, legletisztultabb megold\u00e1st v\u00e1laszd: a property setterben egyszer\u0171 if-et haszn\u00e1lj \u00e9s dobj kiv\u00e9telt, ne legyen az if-nek else \u00e1ga, valamint nincs sz\u00fcks\u00e9g a return haszn\u00e1lat\u00e1ra sem.

                  "},{"location":"hazi/2-nyelvi-eszkozok/#megoldas","title":"Megold\u00e1s","text":"

                  A feladat megold\u00e1sa a 2. labor 1. feladat\u00e1val anal\u00f3g m\u00f3don k\u00e9sz\u00edthet\u0151 el. A MidiChlorianCount tulajdons\u00e1g setter\u00e9ben \u00e9rv\u00e9nytelen \u00e9rt\u00e9k eset\u00e9n dobj kiv\u00e9telt. Ezt p\u00e9ld\u00e1ul a k\u00f6vetkez\u0151 utas\u00edt\u00e1ssal tehet\u0151 meg:

                  throw new ArgumentException(\"You are not a true jedi!\");\n
                  "},{"location":"hazi/2-nyelvi-eszkozok/#feladat-2-a-klonok-tamadasa","title":"Feladat 2 \u2013 A kl\u00f3nok t\u00e1mad\u00e1sa","text":""},{"location":"hazi/2-nyelvi-eszkozok/#feladat_1","title":"Feladat","text":"

                  Eg\u00e9sz\u00edtsd ki az 1. feladatban elk\u00e9sz\u00edtett oszt\u00e1lyt attrib\u00fatumokkal \u00fagy, hogy amennyiben az XmlSerializer oszt\u00e1ly seg\u00edts\u00e9g\u00e9vel, XML form\u00e1tum\u00fa adatf\u00e1jlba \u00edrunk/soros\u00edtunk ki egy Jedi objektumot, a tulajdons\u00e1gai egy-egy XML attrib\u00fatum form\u00e1j\u00e1ban, magyarul jelenjenek meg! Ezt k\u00f6vet\u0151en \u00edrj egy f\u00fcggv\u00e9nyt, mely a Jedi oszt\u00e1ly egy p\u00e9ld\u00e1ny\u00e1t egy sz\u00f6vegf\u00e1jlba soros\u00edtja, majd onnan visszaolvassa egy \u00faj objektumba (ezzel tulajdonk\u00e9ppen kl\u00f3nozva az eredeti objektumot).

                  XML soros\u00edt\u00f3 attrib\u00fatumai

                  Az XML soros\u00edt\u00e1st szab\u00e1lyoz\u00f3 attrib\u00fatumokat ne tagv\u00e1ltoz\u00f3k, hanem a property-k felett helyezd el!

                  A Jedi oszt\u00e1ly legyen publikus

                  Az XML soros\u00edt\u00f3 csak publikus oszt\u00e1lyokon tud dolgozni, ennek megfelel\u0151en a Jedi oszt\u00e1ly legyen publikus:

                  public class Jedi { ...}\n

                  Fontos

                  A ment\u00e9st \u00e9s bet\u00f6lt\u00e9st v\u00e9gz\u0151/demonstr\u00e1l\u00f3 k\u00f3dot \u00edrd egy k\u00f6z\u00f6s, erre dedik\u00e1lt f\u00fcggv\u00e9nybe, a f\u00fcggv\u00e9nyt pedig l\u00e1sd el a [Description(\"Feladat2\")] C# attrib\u00fatummal (a f\u00fcggv\u00e9ny el\u0151tti sorba kell be\u00edrni). A mentett/bet\u00f6lt\u00f6tt objektum lok\u00e1lis v\u00e1ltoz\u00f3k\u00e9nt legyen ebben a f\u00fcggv\u00e9nyben megval\u00f3s\u00edtva. Az oszt\u00e1ly/f\u00fcggv\u00e9ny neve b\u00e1rmi lehet (pl. ker\u00fclhet a Program oszt\u00e1lyba is). A f\u00fcggv\u00e9ny nem szorosan a feladathoz tartoz\u00f3 k\u00f3dot ne tartalmazzon, \u00edgy m\u00e1s (r\u00e9sz)feladathoz tartoz\u00f3t sem. A f\u00fcggv\u00e9nyt h\u00edvd meg a Program oszt\u00e1ly Main f\u00fcggv\u00e9ny\u00e9b\u0151l. A fenti attrib\u00fatum haszn\u00e1lat\u00e1hoz using-olni kell a System.ComponentModel n\u00e9vteret.

                  L\u00e9nyeges, hogy

                  • az attrib\u00fatumot f\u00fcggv\u00e9ny, \u00e9s NE oszt\u00e1ly f\u00f6l\u00e9 \u00edrd,
                  • az attrib\u00fatumot ne a logik\u00e1t megval\u00f3s\u00edt\u00f3, hanem a tesztel\u00e9st v\u00e9gz\u0151 f\u00fcggv\u00e9ny f\u00f6l\u00e9 \u00edrd,
                  • az attrib\u00fatum csak egyetlen f\u00fcggv\u00e9ny f\u00f6l\u00f6tt szerepelhet.
                  "},{"location":"hazi/2-nyelvi-eszkozok/#megoldas_1","title":"Megold\u00e1s","text":"

                  A feladat megold\u00e1sa a 2. labor 4. feladat\u00e1val anal\u00f3g m\u00f3don k\u00e9sz\u00edthet\u0151 el. A megold\u00e1shoz az al\u00e1bbi seg\u00edts\u00e9geket adjuk:

                  • A soros\u00edt\u00e1st k\u00f6vet\u0151en az XML f\u00e1jlnak ehhez hasonl\u00f3an kell kin\u00e9znie:

                    <?xml version=\"1.0\"?>\n<Jedi xmlns:xsi=\"...\" Nev=\"Obi-Wan\" MidiChlorianSzam=\"15000\" />\n

                    L\u00e9nyeges, hogy az egyes Jedik Jedi XML elemk\u00e9nt, nev\u00fck Nev, a midichloriansz\u00e1muk MidiChlorianSzam XML attrib\u00fatumk\u00e9nt jelenjen meg.

                  • A soros\u00edtott objektumok visszat\u00f6lt\u00e9s\u00e9re a labor sor\u00e1n nem n\u00e9zt\u00fcnk p\u00e9ldak\u00f3dot, ez\u00e9rt ezt itt megadjuk:

                    var serializer = new XmlSerializer(typeof(Jedi));\nvar stream = new FileStream(\"jedi.txt\", FileMode.Open);\nvar clone = (Jedi)serializer.Deserialize(stream);\nstream.Close();\n

                    Az el\u0151z\u0151 m\u0171veletsor el\u0151sz\u00f6r l\u00e9trehoz egy soros\u00edt\u00f3t (serializer), mellyel majd a beolvas\u00e1st k\u00e9s\u0151bb elv\u00e9gezz\u00fck. A beolvas\u00e1st egy jedi.txt nev\u0171 f\u00e1jlb\u00f3l fogjuk v\u00e9gezni, amelyet a m\u00e1sodik sorban olvas\u00e1sra nyitunk meg (figyelj\u00fck meg, hogy ha \u00edrni akartuk volna, akkorFileMode.Create-et kellett volna megadni).

                  "},{"location":"hazi/2-nyelvi-eszkozok/#feladat-3-a-sith-ek-bosszuja","title":"Feladat 3 \u2013 A Sith-ek bossz\u00faja","text":""},{"location":"hazi/2-nyelvi-eszkozok/#feladat_2","title":"Feladat","text":"

                  A Jeditan\u00e1csban az ut\u00f3bbi id\u0151ben nagy a fluktu\u00e1ci\u00f3. Hogy a v\u00e1ltoz\u00e1sokat k\u00f6nnyebben nyomon k\u00f6vethess\u00fck, k\u00e9sz\u00edts egy oszt\u00e1lyt, mely k\u00e9pes nyilv\u00e1ntartani a tan\u00e1cs tagjait \u00e9s minden v\u00e1ltoz\u00e1sr\u00f3l egy esem\u00e9ny form\u00e1j\u00e1ban sz\u00f6veges \u00e9rtes\u00edt\u00e9st k\u00fcldeni! A lista manipul\u00e1ci\u00f3j\u00e1t k\u00e9t f\u00fcggv\u00e9nnyel lehessen v\u00e9gezni. Az Add f\u00fcggv\u00e9ny egy \u00faj jedi lovagot regisztr\u00e1ljon a tan\u00e1csba, m\u00edg a Remove f\u00fcggv\u00e9ny t\u00e1vol\u00edtsa el a legutolj\u00e1ra felvett tan\u00e1cstagot. K\u00fcl\u00f6n \u00e9rtes\u00edt\u00e9s jelezze, ha a tan\u00e1cs teljesen ki\u00fcr\u00fcl (ehhez ugyanazt az esem\u00e9nyt haszn\u00e1ld, mint a t\u00f6bbi v\u00e1ltoz\u00e1s eset\u00e9n, csak m\u00e1s sz\u00f6veggel jelezze).

                  A tan\u00e1cstagok (members) nyilv\u00e1ntart\u00e1s\u00e1t egy List<Jedi> t\u00edpus\u00fa tagv\u00e1ltoz\u00f3ban t\u00e1roljuk, az Add f\u00fcggv\u00e9ny ehhez a list\u00e1hoz f\u0171zze hozz\u00e1 az \u00faj elemeket, m\u00edg a Remove f\u00fcggv\u00e9ny generikus lista RemoveAt utas\u00edt\u00e1s\u00e1val mindig a legutolj\u00e1ra felvett tagot t\u00e1vol\u00edtsa el (az utols\u00f3 elem index\u00e9t a lista hossza alapj\u00e1n tudjuk meghat\u00e1rozni, melyet a Count property ad vissza).

                  Az \u00e9rtes\u00edt\u00e9s egy C# esem\u00e9nyen (C# event) kereszt\u00fcl t\u00f6rt\u00e9njen. Az esem\u00e9nyhez tartoz\u00f3 delegate t\u00edpus param\u00e9terk\u00e9nt egy egyszer\u0171 string-et kapjon. Az \u00faj tag hozz\u00e1ad\u00e1s\u00e1t, az egyes tagok elt\u00e1vol\u00edt\u00e1s\u00e1t, illetve az utols\u00f3 tag elt\u00e1vol\u00edt\u00e1s\u00e1t m\u00e1s-m\u00e1s sz\u00f6veg\u0171 \u00fczenet jelezze. Az esem\u00e9ny els\u00fct\u00e9s\u00e9t k\u00f6zvetlen\u00fcl az Add \u00e9s a Remove m\u0171veletekben v\u00e9gezd el (ne vezess be erre seg\u00e9df\u00fcggv\u00e9nyt).

                  Az esem\u00e9ny t\u00edpus\u00e1nak ne haszn\u00e1lj be\u00e9p\u00edtett delegate t\u00edpust, hanem vezess be egy saj\u00e1tot.

                  Fontos

                  A Jeditan\u00e1cs objektumot l\u00e9trehoz\u00f3 \u00e9s azt tesztel\u0151 (C# esem\u00e9ny\u00e9re val\u00f3 feliratkoz\u00e1s, Add \u00e9s Remove h\u00edv\u00e1sa) k\u00f3d ker\u00fclj\u00f6n egy k\u00f6z\u00f6s, \u00f6n\u00e1ll\u00f3 f\u00fcggv\u00e9nybe, ezt a f\u00fcggv\u00e9nyt pedig l\u00e1sd el a [Description(\"Feladat3\")] C# attrib\u00fatummal. Az oszt\u00e1ly/f\u00fcggv\u00e9ny neve b\u00e1rmi lehet. A f\u00fcggv\u00e9ny nem szorosan a feladathoz tartoz\u00f3 k\u00f3dot ne tartalmazzon, \u00edgy m\u00e1s (r\u00e9sz)feladathoz tartoz\u00f3t sem. A f\u00fcggv\u00e9nyt h\u00edvd meg a Program oszt\u00e1ly Main f\u00fcggv\u00e9ny\u00e9b\u0151l.

                  L\u00e9nyeges, hogy

                  • az attrib\u00fatumot f\u00fcggv\u00e9ny, \u00e9s NE oszt\u00e1ly f\u00f6l\u00e9 \u00edrd,
                  • az attrib\u00fatumot ne a logik\u00e1t megval\u00f3s\u00edt\u00f3, hanem a tesztel\u00e9st v\u00e9gz\u0151 f\u00fcggv\u00e9ny f\u00f6l\u00e9 \u00edrd,
                  • az attrib\u00fatum csak egyetlen f\u00fcggv\u00e9ny f\u00f6l\u00f6tt szerepelhet.
                  "},{"location":"hazi/2-nyelvi-eszkozok/#megoldas_2","title":"Megold\u00e1s","text":"

                  A feladat megold\u00e1sa a 2. labor t\u00f6bb r\u00e9szlet\u00e9re is \u00e9p\u00edt. Az \u00faj esem\u00e9ny bevezet\u00e9s\u00e9t a 2. \u00e9s a 3. feladatban le\u00edrt m\u00f3don tudjuk elv\u00e9gezni, m\u00edg a tan\u00e1cs tagjait egy list\u00e1ban tudjuk nyilv\u00e1ntartani.

                  A fenti inform\u00e1ci\u00f3k alapj\u00e1n pr\u00f3b\u00e1ld meg \u00f6n\u00e1ll\u00f3an megoldani a feladatot, majd ha k\u00e9szen vagy, a k\u00f6vetkez\u0151 kinyithat\u00f3 blokkban folytasd az \u00fatmutat\u00f3 olvas\u00e1s\u00e1t \u00e9s vesd \u00f6ssze a megold\u00e1sodat a lenti referencia megold\u00e1ssal! Sz\u00fcks\u00e9g szerint korrig\u00e1ld a saj\u00e1t megold\u00e1sod!

                  Publikus l\u00e1that\u00f3s\u00e1g

                  A p\u00e9lda \u00e9p\u00edt arra, hogy a r\u00e9sztvev\u0151 oszt\u00e1lyok, tulajdons\u00e1gok, delegate-ek publikus l\u00e1that\u00f3s\u00e1g\u00faak. Amennyiben fura ford\u00edt\u00e1si hib\u00e1val tal\u00e1lkozol, vagy az XmlSerializer fut\u00e1sid\u0151ben hib\u00e1t dob, els\u0151 k\u00f6rben azt ellen\u0151rizd, hogy minden \u00e9rintett helyen megfelel\u0151en be\u00e1ll\u00edtottad-e a publikus l\u00e1that\u00f3s\u00e1got.

                  Referencia megold\u00e1s

                  A referencia megold\u00e1s l\u00e9p\u00e9sei a k\u00f6vetkez\u0151k:

                  1. Hozzunk l\u00e9tre egy \u00faj oszt\u00e1lyt, JediCouncil n\u00e9ven.
                  2. Vegy\u00fcnk fel egy List<Jedi> t\u00edpus\u00fa mez\u0151t \u00e9s inicializ\u00e1ljuk egy \u00fcres list\u00e1val.
                  3. Val\u00f3s\u00edtsuk meg az Add \u00e9s a Remove f\u00fcggv\u00e9nyeket.

                    A fenti l\u00e9p\u00e9seket k\u00f6vet\u0151en az al\u00e1bbi k\u00f3dot kapjuk:

                    public class JediCouncil\n{\n    List<Jedi> members = new List<Jedi>();\n\n    public void Add(Jedi newJedi)\n    {\n        members.Add(newJedi);\n    }\n\n    public void Remove()\n    {\n        // Elt\u00e1vol\u00edtja a lista utols\u00f3 elem\u00e9t\n        members.RemoveAt(members.Count - 1);\n    }\n}\n

                    K\u00f6vetkez\u0151 l\u00e9p\u00e9sk\u00e9nt val\u00f3s\u00edtsuk meg az esem\u00e9nykezel\u00e9st.

                  4. Defini\u00e1ljunk egy \u00faj deleg\u00e1t t\u00edpust (az oszt\u00e1lyon k\u00edv\u00fcl, mivel ez is egy t\u00edpus), mely az \u00e9rtes\u00edt\u00e9sek sz\u00f6veg\u00e9t adja majd \u00e1t:

                    public delegate void CouncilChangedDelegate(string message);\n
                  5. Eg\u00e9sz\u00edts\u00fck ki a JediCouncil oszt\u00e1lyt az esem\u00e9nykezel\u0151vel:

                    public class JediCouncil\n{\n    public event CouncilChangedDelegate CouncilChanged;\n\n    // ...\n}\n
                  6. S\u00fcss\u00fck el az esem\u00e9nyt, amikor \u00faj tan\u00e1cstagot vesz\u00fcnk fel. Ehhez az Add met\u00f3dust kell kieg\u00e9sz\u00edten\u00fcnk.

                    public void Add(Jedi newJedi)\n{\n    members.Add(newJedi);\n\n    // TODO: Itt s\u00fcsd el az esem\u00e9nyt.\n    // Figyelj arra, hogy csak akkor tedd meg, ha van legal\u00e1bb egy feliratkoz\u00f3/el\u0151fizet\u0151.\n    // Ennek sor\u00e1n ne a terjeng\u0151sebb null ellen\u0151rz\u00e9st, hanem a modernebb, ?.Invoke-ot haszn\u00e1ld.\n}\n
                  7. S\u00fcss\u00fck el az esem\u00e9nyt, amikor egy tan\u00e1cstag t\u00e1vozik! K\u00fcl\u00f6nb\u00f6ztess\u00fck meg azt az esetet, amikor a tan\u00e1cs teljesen ki\u00fcr\u00fcl. Ehhez a Remove met\u00f3dust kell kieg\u00e9sz\u00edten\u00fcnk.

                    public void Remove()\n{\n    // Elt\u00e1vol\u00edtja a lista utols\u00f3 elem\u00e9t\n    members.RemoveAt(members.Count - 1);\n\n    // TODO: Itt s\u00fcsd el az esem\u00e9nyt.\n    // Figyelj arra, hogy csak akkor tedd meg, ha van legal\u00e1bb egy feliratkoz\u00f3/el\u0151fizet\u0151.\n}\n
                  8. Megold\u00e1sunk tesztel\u00e9s\u00e9hez vegy\u00fcnk fel egy MessageReceived f\u00fcggv\u00e9nyt abba az oszt\u00e1lyba, ahol az esem\u00e9nyre val\u00f3 feliratkoz\u00e1st \u00e9s az esem\u00e9ny kezel\u00e9s\u00e9t tesztelni szeretn\u00e9nk (pl. a Program oszt\u00e1lyba). Ezt a f\u00fcggv\u00e9nyt fogjuk feliratkoztatni a JediCouncil \u00e9rtes\u00edt\u00e9seire.

                    Program.cs
                    private static void MessageReceived(string message)\n{\n    Console.WriteLine(message);\n}\n
                  9. V\u00e9gezet\u00fcl tesztelj\u00fck az \u00faj oszt\u00e1lyunkat egy erre a c\u00e9lra dedik\u00e1lt f\u00fcggv\u00e9ny meg\u00edr\u00e1s\u00e1val (ez t\u00f6rt\u00e9nhet pl. a Program oszt\u00e1lyban), a f\u00fcggv\u00e9ny f\u00f6l\u00e9 tegy\u00fck oda a [Description(\"Feladat3\")] attrib\u00fatumot! A f\u00fcggv\u00e9ny v\u00e1za:

                    // Tan\u00e1cs l\u00e9trehoz\u00e1sa\nvar council = new JediCouncil();\n\n// TODO: Itt iratkozz fel a council CouncilChanged esem\u00e9ny\u00e9re\n\n// TODO Itt adj hozz\u00e1 k\u00e9t Jedi objektumot a council objektumhoz az Add h\u00edv\u00e1s\u00e1val\n\ncouncil.Remove();\ncouncil.Remove();\n
                  10. Ha j\u00f3l v\u00e9gezt\u00fck a dolgunkat, a program futtat\u00e1s\u00e1t k\u00f6vet\u0151en a k\u00f6vetkez\u0151 kimenetet kell kapnunk:

                    \u00daj taggal b\u0151v\u00fclt\u00fcnk\n\u00daj taggal b\u0151v\u00fclt\u00fcnk\nZavart \u00e9rzek az er\u0151ben\nA tan\u00e1cs elesett!\n

                  Esem\u00e9nyek null vizsg\u00e1lata

                  Amennyiben a JediCouncil.Add m\u0171veletben null vizsg\u00e1lattal v\u00e9gezted annak ellen\u0151rz\u00e9s\u00e9t, hogy van-e legal\u00e1bb egy feliratkoz\u00f3 az esem\u00e9nyre, ezt alak\u00edtsd \u00e1t korszer\u0171bb megold\u00e1sra (?.Invoke alkalmaz\u00e1sa, mely t\u00f6m\u00f6rebb form\u00e1ban szint\u00e9n elv\u00e9gzi az ellen\u0151rz\u00e9st, de null vizsg\u00e1lat n\u00e9lk\u00fcl \u2013 err\u0151l a kapcsol\u00f3d\u00f3 el\u0151ad\u00e1son \u00e9s laboron is volt sz\u00f3). Ezt el\u00e9g a JediCouncil.Add kapcs\u00e1n megtenni, a JediCouncil.Remove eset\u00e9ben mindk\u00e9t megold\u00e1s elfogadhat\u00f3 most.

                  "},{"location":"hazi/2-nyelvi-eszkozok/#feladat-4-delegatok","title":"Feladat 4 \u2013 Deleg\u00e1tok","text":""},{"location":"hazi/2-nyelvi-eszkozok/#feladat_3","title":"Feladat","text":"

                  Eg\u00e9sz\u00edtsd ki a JediCouncil oszt\u00e1lyt egy olyan param\u00e9ter n\u00e9lk\u00fcli f\u00fcggv\u00e9nnyel (a f\u00fcggv\u00e9nyn\u00e9v v\u00e9gz\u0151dj\u00f6n _Delegate-re, ez k\u00f6telez\u0151), mely visszat\u00e9r\u00e9si \u00e9rt\u00e9k\u00e9ben visszaadja a Jedi tan\u00e1cs \u00f6sszes olyan tagj\u00e1t, melynek a midi-chlorian sz\u00e1ma 530 alatt van!

                  • F\u00fcggv\u00e9nyt haszn\u00e1lj, ne tulajdons\u00e1got a lek\u00e9rdez\u00e9sre.
                  • A f\u00fcggv\u00e9nyen bel\u00fcl a tagok kikeres\u00e9s\u00e9re haszn\u00e1ld a List<Jedi> oszt\u00e1ly FindAll() f\u00fcggv\u00e9ny\u00e9t.
                  • Ebben a feladatban m\u00e9g NEM haszn\u00e1lhatsz lambda kifejez\u00e9st!

                  \u00cdrj egy dedik\u00e1lt \u201etesztel\u0151\u201d f\u00fcggv\u00e9nyt is (pl. a Program oszt\u00e1lyba), mely megh\u00edvja a fenti f\u00fcggv\u00e9ny\u00fcnket \u00e9s ki\u00edrja a visszaadott jedi lovagok neveit! Ez a f\u00fcggv\u00e9ny nem szorosan a feladathoz tartoz\u00f3 k\u00f3dot ne tartalmazzon, \u00edgy m\u00e1s (r\u00e9sz)feladathoz tartoz\u00f3t sem.

                  Fontos

                  Ezt a \u201etesztel\u0151\u201d f\u00fcggv\u00e9nyt l\u00e1sd el a [Description(\"Feladat4\")] C# attrib\u00fatummal. A f\u00fcggv\u00e9nyt h\u00edvd meg a Program oszt\u00e1ly Main f\u00fcggv\u00e9ny\u00e9b\u0151l.

                  L\u00e9nyeges, hogy

                  • az attrib\u00fatumot f\u00fcggv\u00e9ny, \u00e9s NE oszt\u00e1ly f\u00f6l\u00e9 \u00edrd,
                  • az attrib\u00fatumot ne a logik\u00e1t megval\u00f3s\u00edt\u00f3, hanem a tesztel\u00e9st v\u00e9gz\u0151 f\u00fcggv\u00e9ny f\u00f6l\u00e9 \u00edrd,
                  • az attrib\u00fatum csak egyetlen f\u00fcggv\u00e9ny f\u00f6l\u00f6tt szerepelhet.

                  Inicializ\u00e1ci\u00f3 kiszervez\u00e9se

                  A megval\u00f3s\u00edt\u00e1s sor\u00e1n vezess be egy k\u00fcl\u00f6n statikus met\u00f3dust (pl. a Program oszt\u00e1lyba), mely param\u00e9terk\u00e9nt egy Jeditan\u00e1cs objektumot kap, abba legal\u00e1bb h\u00e1rom felparam\u00e9terezett Jedi objektumot az Add h\u00edv\u00e1s\u00e1val felvesz. A c\u00e9lunk ezzel az, hogy egy olyan inicializ\u00e1l\u00f3 met\u00f3dusunk legyen, mely a k\u00e9s\u0151bbi feladat(ok) sor\u00e1n is felhaszn\u00e1lhat\u00f3, ne kelljen a kapcsol\u00f3d\u00f3 inicializ\u00e1l\u00f3 k\u00f3dot duplik\u00e1lni.

                  "},{"location":"hazi/2-nyelvi-eszkozok/#megoldas_3","title":"Megold\u00e1s","text":"

                  A feladat megold\u00e1s\u00e1hoz a 2. labor 6. feladat\u00e1t haszn\u00e1lhatjuk referenciak\u00e9nt. Seg\u00edts\u00e9gk\u00e9nt megadjuk a k\u00f6vetkez\u0151ket:

                  • a f\u00fcggv\u00e9ny\u00fcnk ak\u00e1r t\u00f6bb tal\u00e1latot is visszaadhat, ez\u00e9rt a visszat\u00e9r\u00e9si \u00e9rt\u00e9k t\u00edpusa List<Jedi>,
                  • a FindAll param\u00e9terk\u00e9nt az eset\u00fcnkben egy bool F\u00fcggv\u00e9nyn\u00e9v(Jedi j) szignat\u00far\u00e1j\u00fa sz\u0171r\u0151f\u00fcggv\u00e9nyt v\u00e1r el.
                  "},{"location":"hazi/2-nyelvi-eszkozok/#feladat-5-lambda-kifejezesek","title":"Feladat 5 \u2013 Lambda kifejez\u00e9sek","text":"

                  A feladat megfelel az el\u0151z\u0151nek, csak most lambda kifejez\u00e9s seg\u00edts\u00e9g\u00e9vel fogunk dolgozni. Ez a t\u00e9mak\u00f6r szerepelt el\u0151ad\u00e1son \u00e9s laboron is (2. labor 6. feladat).

                  Eg\u00e9sz\u00edtsd ki a JediCouncil oszt\u00e1lyt egy olyan param\u00e9ter n\u00e9lk\u00fcli f\u00fcggv\u00e9nnyel (a f\u00fcggv\u00e9nyn\u00e9v v\u00e9gz\u0151dj\u00f6n _Lambda-ra, ez k\u00f6telez\u0151), mely visszat\u00e9r\u00e9si \u00e9rt\u00e9k\u00e9ben visszaadja a Jedi tan\u00e1cs \u00f6sszes olyan tagj\u00e1t, melynek a midi-chlorian sz\u00e1ma 1000 alatt van!

                  • F\u00fcggv\u00e9nyt haszn\u00e1lj, ne tulajdons\u00e1got a lek\u00e9rdez\u00e9sre.
                  • A f\u00fcggv\u00e9nyen bel\u00fcl a tagok kikeres\u00e9s\u00e9re haszn\u00e1ld a List<Jedi> oszt\u00e1ly FindAll() f\u00fcggv\u00e9ny\u00e9t.
                  • Ebben a feladatban k\u00f6telez\u0151en lambda kifejez\u00e9st kell haszn\u00e1lj (az mindegy, hogy statement vagy expression lambd\u00e1t)!

                  \u00cdrj egy dedik\u00e1lt \u201etesztel\u0151\u201d f\u00fcggv\u00e9nyt is (pl. a Program oszt\u00e1lyba), mely megh\u00edvja a fenti f\u00fcggv\u00e9ny\u00fcnket \u00e9s ki\u00edrja a visszaadott jedi lovagok neveit! Ez a f\u00fcggv\u00e9ny nem szorosan a feladathoz tartoz\u00f3 k\u00f3dot ne tartalmazzon, \u00edgy m\u00e1s (r\u00e9sz)feladathoz tartoz\u00f3t sem.

                  Fontos

                  Ezt a \u201etesztel\u0151\u201d f\u00fcggv\u00e9nyt l\u00e1sd el a [Description(\"Feladat5\")] C# attrib\u00fatummal. A f\u00fcggv\u00e9nyt h\u00edvd meg a Program oszt\u00e1ly Main f\u00fcggv\u00e9ny\u00e9b\u0151l.

                  L\u00e9nyeges, hogy

                  • az attrib\u00fatumot f\u00fcggv\u00e9ny, \u00e9s NE oszt\u00e1ly f\u00f6l\u00e9 \u00edrd,
                  • az attrib\u00fatumot ne a logik\u00e1t megval\u00f3s\u00edt\u00f3, hanem a tesztel\u00e9st v\u00e9gz\u0151 f\u00fcggv\u00e9ny f\u00f6l\u00e9 \u00edrd,
                  • az attrib\u00fatum csak egyetlen f\u00fcggv\u00e9ny f\u00f6l\u00f6tt szerepelhet.
                  "},{"location":"hazi/2-nyelvi-eszkozok/#feladat-6-actionfunc-hasznalata","title":"Feladat 6 \u2013 Action/Func haszn\u00e1lata","text":"

                  Ez a feladat a 3. el\u0151ad\u00e1s anyag\u00e1ra \u00e9p\u00edt, laboron (id\u0151 hi\u00e1ny\u00e1ban) nem szerepelt. Ett\u0151l f\u00fcggetlen\u00fcl ez egy l\u00e9nyeges alapt\u00e9mak\u00f6r a t\u00e1rgyban.

                  A projektbe vegy\u00e9l fel egy Person \u00e9s egy ReportPrinter oszt\u00e1lyt (egy-egy, az oszt\u00e1ly nev\u00e9vel egyez\u0151 f\u00e1jlba, az alap\u00e9rtelmezett, ModernLangToolsApp n\u00e9vt\u00e9rbe), a k\u00f6vetkez\u0151 tartalommal:

                  Person \u00e9s ReportPrinter oszt\u00e1lyok
                  class Person\n{\n    public Person(string name, int age)\n    {\n        Name = name;\n        Age = age;\n    }\n\n    public string Name { get; set; }\n    public int Age { get; set; }\n}\n
                  class ReportPrinter\n{\n    private readonly IEnumerable<Person> people;\n    private readonly Action headerPrinter;\n\n    public ReportPrinter(IEnumerable<Person> people, Action headerPrinter)\n    {\n        this.people = people;\n        this.headerPrinter = headerPrinter;\n    }\n\n    public void PrintReport()\n    {\n        headerPrinter();\n        Console.WriteLine(\"-----------------------------------------\");\n        int i = 0;\n        foreach (var person in people)\n        {\n            Console.Write($\"{++i}. \");\n            Console.WriteLine(\"Person\");\n        }\n        Console.WriteLine(\"--------------- Summary -----------------\");\n        Console.WriteLine(\"Footer\");\n    }\n}\n

                  Ez a ReportPrinter oszt\u00e1ly arra haszn\u00e1lhat\u00f3, hogy a konstruktor\u00e1ban megadott szem\u00e9lyek adatair\u00f3l form\u00e1zott riportot \u00edrjon ki a konzolra fejl\u00e9c/adatok/l\u00e1bl\u00e9c h\u00e1rmas bont\u00e1sban. A Program.cs f\u00e1jlba vedd fel az al\u00e1bbi f\u00fcggv\u00e9nyt a ReportPrinter kipr\u00f3b\u00e1l\u00e1s\u00e1ra, \u00e9s ezt h\u00edvd is meg a Main f\u00fcggv\u00e9nyb\u0151l:

                  ReportPrinter tesztel\u00e9se
                  [Description(\"Feladat6\")]\nstatic void test6()\n{\n    var employees = new Person[] { new Person(\"Joe\", 20), new Person(\"Jill\", 30) };\n\n    ReportPrinter reportPrinter = new ReportPrinter(\n        employees,\n        () => Console.WriteLine(\"Employees\")\n        );\n\n    reportPrinter.PrintReport();\n}\n

                  Futtassuk az alkalmaz\u00e1st. Az al\u00e1bbi kimenetet kapjuk a konzolon:

                  Employees\n-----------------------------------------\n1. Person\n2. Person\n--------------- Summary -----------------\nFooter\n

                  Az els\u0151 sorban \"----\" felett tal\u00e1lhat\u00f3 a fejl\u00e9c. Alatta az egye szem\u00e9lyekhez egy-egy \"Person\" be\u00e9getett sz\u00f6veg, majd a \"----\" alatt a l\u00e1bl\u00e9c, egyel\u0151re csak egy be\u00e9getett \"Footer\" sz\u00f6veggel.

                  A megold\u00e1sban l\u00e1that\u00f3, hogy a fejl\u00e9c sz\u00f6vege a ReportPrinter oszt\u00e1lyba nincs be\u00e9getve. Ezt ReportPrinter felhaszn\u00e1l\u00f3ja adja meg konstruktor param\u00e9terben egy delegate, eset\u00fcnkben egy lambda kifejez\u00e9s form\u00e1j\u00e1ban. A delegate t\u00edpusa a .NET be\u00e9p\u00edtett Action t\u00edpusa.

                  A feladatok a k\u00f6vetkez\u0151k:

                  Warning

                  A megold\u00e1s sor\u00e1n NEM haszn\u00e1lhatsz saj\u00e1t delegate t\u00edpust (a .NET be\u00e9p\u00edtett delegate t\u00edpusaival dolgozz, a megold\u00e1s csak ekkor elfogadhat\u00f3).

                  1. Alak\u00edtsd \u00e1t a ReportPrinter oszt\u00e1lyt \u00fagy, hogy az oszt\u00e1ly haszn\u00e1l\u00f3ja ne csak a fejl\u00e9cet, hanem a l\u00e1bl\u00e9cet is meg tudja adni egy delegate form\u00e1j\u00e1ban a konstruktorban.

                  2. Alak\u00edtsd tov\u00e1bb a ReportPrinter oszt\u00e1lyt \u00fagy, hogy az egyes szem\u00e9lyek ki\u00edr\u00e1sakor ne a fix \"Person\" sz\u00f6veg jelenjen meg, hanem a ReportPrinter oszt\u00e1ly haszn\u00e1l\u00f3ja tudja az egyes szem\u00e9lyek adatait az ig\u00e9nyeinek megfelel\u0151en ki\u00edrni a konzolra egy konstruktorban megadott delegate seg\u00edts\u00e9g\u00e9vel (a fix \"Person\" helyett). L\u00e9nyeges, hogy a sorsz\u00e1m a sor elej\u00e9n mindig meg kell jelenjen, ez nem lehet a ReportPrinter haszn\u00e1l\u00f3ja \u00e1ltal megv\u00e1ltoztathat\u00f3 (vagyis ezt a tov\u00e1bbiakban is a ReportPrinter oszt\u00e1lynak kell ki\u00edrnia)!

                    Tipp a megold\u00e1shoz

                    Hasonl\u00f3 megk\u00f6zel\u00edt\u00e9sben gondolkozz, mint a fejl\u00e9c \u00e9s l\u00e1bl\u00e9c eset\u00e9ben, de itt ehhez a ReportPrinter felhaszn\u00e1l\u00f3j\u00e1nak meg kell kapnia a szem\u00e9ly objektumot ahhoz, hogy azt form\u00e1zottan ki tudja \u00edrni a konzolra.

                  3. A Program.cs f\u00e1jlban a ReportPrinter haszn\u00e1lat\u00e1t alak\u00edtsd \u00fagy (megfelel\u0151 lambda kifejez\u00e9sek megad\u00e1s\u00e1val), hogy a kimenet a konzolon a k\u00f6vetkez\u0151 legyen:

                    Employees\n-----------------------------------------\n1. Name: Joe (Age: 20)\n2. Name: Jill (Age: 30)\n--------------- Summary -----------------\nNumber of Employees: 2\n

                    L\u00e1bl\u00e9cben a dolgoz\u00f3k sz\u00e1m\u00e1nak ki\u00edr\u00e1sa

                    Ahhoz, hogy a l\u00e1bl\u00e9cben a dolgoz\u00f3k sz\u00e1m\u00e1nak ki\u00edr\u00e1s\u00e1t eleg\u00e1ns m\u00f3don meg tudd tenni, sz\u00fcks\u00e9g van a \"variable capturing\" t\u00e9mak\u00f6r ismeret\u00e9re (l\u00e1sd 3. el\u0151ad\u00e1s \"Variable capturing, closure\" fejezet).

                    H\u00e1zi feladat ellen\u0151rz\u00e9se

                    A \"Feladat 6\" feladatot, vagyis azt, hogy a ReportPrinter-t \u00e9s annak haszn\u00e1lat\u00e1t j\u00f3l alak\u00edtottad-e \u00e1t, a GitHub-os automata ellen\u0151rz\u0151 NEM ellen\u0151rzi. Teszteld a megold\u00e1sod alaposan, hogy ne csak a hat\u00e1rid\u0151 ut\u00e1n ut\u00f3lag, a h\u00e1zi feladatok manu\u00e1lis ellen\u0151rz\u00e9se sor\u00e1n der\u00fclj\u00f6n ki, hogy a megold\u00e1s nem elfogadhat\u00f3. (Kieg\u00e9sz\u00edt\u00e9s: 2024.03.13 reggelt\u0151l kezdve m\u00e1r erre is van r\u00e9szleges automata ellen\u0151rz\u00e9s)

                  4. A k\u00f6vetkez\u0151 feladat opcion\u00e1lis, a be\u00e9p\u00edtett Func delegate-ek gyakorl\u00e1s\u00e1ra ad j\u00f3 lehet\u0151s\u00e9get. A ReportPrinter oszt\u00e1lynak van egy komolyabb h\u00e1tr\u00e1nya: a kimeneti riportot csak a konzolon tudjuk a seg\u00edts\u00e9g\u00e9vel megjelen\u00edteni. Rugalmasabb megold\u00e1s lenne, ha nem \u00edrna a konzolra, hanem egy string form\u00e1j\u00e1ban lehetne a seg\u00edts\u00e9g\u00e9vel a riportot el\u0151\u00e1ll\u00edtani. Ezt a stringet m\u00e1r \u00fagy haszn\u00e1lhatn\u00e1nk fel, ahogy csak szeretn\u00e9nk (pl. \u00edrhatn\u00e1nk f\u00e1jlba is).

                    A feladat a k\u00f6vetkez\u0151: vezess be egy ReportBuilder oszt\u00e1lyt a m\u00e1r megl\u00e9v\u0151 ReportPrinter mint\u00e1j\u00e1ra, de ez ne a konzolra \u00edrjon, hanem egy a teljes riportot tartalmaz\u00f3 stringet \u00e1ll\u00edtson el\u0151, melyet egy \u00fajonnan bevezetett, GetResult() m\u0171velettel lehessen t\u0151le lek\u00e9rdezni.

                    Bead\u00e1s

                    Ha beadod a feladatot, a ReportBuilder-t p\u00e9ld\u00e1nyos\u00edt\u00f3/tesztel\u0151 k\u00f3dot ne a fenti, test6 f\u00fcggv\u00e9nybe tedd, hanem vezess be egy test6b nev\u0171 f\u00fcggv\u00e9nyt, \u00e9s l\u00e1sd el a [Description(\"Feladat6b\")] attrib\u00fatummal.

                    Tippek a megold\u00e1shoz

                    • C\u00e9lszer\u0171 az oszt\u00e1lyba egy StringBuilder tagv\u00e1ltoz\u00f3t bevezetni, \u00e9s ennek seg\u00edts\u00e9g\u00e9vel dolgozni. Ez nagys\u00e1grenddel hat\u00e9konyabb, mint a stringek \"+\"-szal val\u00f3 \u00f6sszef\u0171z\u00f6get\u00e9se.
                    • A ReportBuilder oszt\u00e1ly haszn\u00e1l\u00f3ja itt m\u00e1r ne a konzolra \u00edrjon, hanem megfelel\u0151 be\u00e9p\u00edtett t\u00edpus\u00fa delegate-ek (itt az Action nem lesz megfelel\u0151) seg\u00edts\u00e9g\u00e9vel adja vissza a ReportBuilder sz\u00e1m\u00e1ra azokat a stringeket, melyeket bele kell f\u0171znie a kimenetbe. A tesztel\u00e9s sor\u00e1n most is lambda kifejez\u00e9seket haszn\u00e1lj!
                  "},{"location":"hazi/2-nyelvi-eszkozok/#feladat-7-imsc-beepitett-funcaction-generikus-delegate-tipusok-hasznalata","title":"Feladat 7 (IMSc) \u2013 be\u00e9p\u00edtett Func/Action generikus delegate t\u00edpusok haszn\u00e1lata","text":"

                  A feladat megold\u00e1sa nem k\u00f6telez\u0151, de er\u0151sen aj\u00e1nlott: alapanyag, \u00edgy ZH-n/vizsg\u00e1n szerepelhet. Laboron nem volt, csak el\u0151ad\u00e1son.

                  A megold\u00e1s\u00e9rt +2 IMSc pont is j\u00e1r.

                  "},{"location":"hazi/2-nyelvi-eszkozok/#feladat_4","title":"Feladat","text":"

                  B\u0151v\u00edtsd ki a JediCouncil oszt\u00e1lyt.

                  • K\u00e9sz\u00edts egy Count nev\u0171 int visszat\u00e9r\u00e9si \u00e9rt\u00e9k\u0171 property-t (tulajdons\u00e1got), amely minden lek\u00e9rdez\u00e9skor a tan\u00e1csban aktu\u00e1lisan tal\u00e1lhat\u00f3 Jedi-k sz\u00e1m\u00e1t adja vissza. \u00dcgyelj arra, hogy ezt az \u00e9rt\u00e9ket csak lek\u00e9rdezni lehessen (be\u00e1ll\u00edtani nem).

                    Tipp

                    A JediCouncil-ban tal\u00e1lhat\u00f3 members nev\u0171 tagv\u00e1ltoz\u00f3nak van egy Count nev\u0171 property-je, a megold\u00e1s \u00e9p\u00edtsen erre.

                  • K\u00e9sz\u00edts egy CountIf nev\u0171 f\u00fcggv\u00e9nyt, amely szint\u00e9n a tan\u00e1cstagok megsz\u00e1ml\u00e1l\u00e1s\u00e1ra val\u00f3, de csak bizonyos felt\u00e9telnek eleget tev\u0151 tan\u00e1cstagokat vesz figyelembe. A f\u00fcggv\u00e9ny visszat\u00e9r\u00e9si \u00e9rt\u00e9ke int, \u00e9s a felt\u00e9telt, amelynek megfelel\u0151 tan\u00e1cstagok sz\u00e1m\u00e1t visszaadja, egy delegate seg\u00edts\u00e9g\u00e9vel kapja meg param\u00e9terk\u00e9nt (teh\u00e1t a CountIf-nek kell legyen param\u00e9tere).

                    Delegate t\u00edpusa

                    A delegate t\u00edpusa k\u00f6telez\u0151en a be\u00e9p\u00edtett generikus Action / Func delegate t\u00edpusok k\u00f6z\u00fcl a megfelel\u0151 kell legyen (vagyis saj\u00e1t delegate t\u00edpus, ill. a be\u00e9p\u00edtett Predicate t\u00edpus nem haszn\u00e1lhat\u00f3).

                    Emiatt a list\u00e1n NEM haszn\u00e1lhatod a be\u00e9p\u00edtett FindAll m\u0171velet\u00e9t, mivel az \u00e1ltalunk haszn\u00e1lt delegate t\u00edpus nem lenne kompatibilis a FindAll \u00e1ltal v\u00e1rt param\u00e9terrel. A tagokon egy foreach ciklusban v\u00e9gigiter\u00e1lva dolgozz!

                  • A property \u00e9s a f\u00fcggv\u00e9ny m\u0171k\u00f6d\u00e9s\u00e9t demonstr\u00e1ld egy erre dedik\u00e1lt k\u00f6z\u00f6s f\u00fcggv\u00e9nyben, amit l\u00e1ss el a [Description(\"Feladat7\")] attrib\u00fatummal. Ez a f\u00fcggv\u00e9ny nem szorosan a feladathoz tartoz\u00f3 k\u00f3dot ne tartalmazzon, viszont a Jeditan\u00e1cs felt\u00f6lt\u00e9s\u00e9hez az el\u0151z\u0151 feladatban bevezetett seg\u00e9df\u00fcggv\u00e9nyt h\u00edvd. A f\u00fcggv\u00e9nyt h\u00edvd meg a Program oszt\u00e1ly Main f\u00fcggv\u00e9ny\u00e9b\u0151l.

                    Fontos

                    A [Description(\"Feladat7\")] attrib\u00fatum csak egyetlen f\u00fcggv\u00e9ny f\u00f6l\u00f6tt szerepelhet.

                  "},{"location":"hazi/2-nyelvi-eszkozok/#megoldas_4","title":"Megold\u00e1s","text":"
                  • A Count nev\u0171 property eset\u00e9ben csak a get \u00e1gnak van \u00e9rtelme, ez\u00e9rt a set \u00e1gat meg se \u00edrjuk. Ez egy csak olvashat\u00f3 tulajdons\u00e1g legyen.
                  • A CountIf f\u00fcggv\u00e9ny meg\u00edr\u00e1s\u00e1ban a 4-es feladat ny\u00fajt seg\u00edts\u00e9get. A k\u00fcl\u00f6nbs\u00e9g, hogy a CountIf nem a tan\u00e1cstagokat, csak a darabsz\u00e1mot adja vissza.
                    • A CountIf f\u00fcggv\u00e9ny a felt\u00e9telt param\u00e9terk\u00e9nt egy bool F\u00fcggv\u00e9nyn\u00e9v(Jedi jedi) szignat\u00far\u00e1j\u00fa sz\u0171r\u0151f\u00fcggv\u00e9nyt v\u00e1rjon.
                  "},{"location":"hazi/2-nyelvi-eszkozok/#beadas","title":"Bead\u00e1s","text":"

                  Ellen\u0151rz\u0151lista ism\u00e9tl\u00e9sk\u00e9ppen:

                  • A repository gy\u00f6k\u00e9rmapp\u00e1j\u00e1ban tal\u00e1lhat\u00f3 neptun.txt f\u00e1jlba \u00edrd bele a Neptun k\u00f3dod, csupa nagybet\u0171vel. A f\u00e1jlban csak ez a hat karakter legyen, semmi m\u00e1s.
                  • A GitHub-r\u00f3l le\u00f6lt\u00f6tt kiindul\u00f3 solutionben/projektekben kell dolgozni, nem \u00fajonnan l\u00e9trehozottban.
                  • Am\u00edg nem vagy rutinos a Visual Studio Git szolg\u00e1ltat\u00e1sainak haszn\u00e1lat\u00e1ban, a push-t k\u00f6vet\u0151en (legk\u00e9s\u0151bb akkor, amikor a h\u00e1zi feladatot beadottnak tekintj\u00fck) c\u00e9lszer\u0171 ellen\u0151rizni a GitHub webes fel\u00fclet\u00e9n a repository-ban a f\u00e1jlokra val\u00f3 r\u00e1pillant\u00e1ssal, hogy val\u00f3ban minden v\u00e1ltoztat\u00e1st felt\u00f6lt\u00f6tt\u00e9l-e.
                  • A GitHub fel\u00fclet\u00e9n ellen\u0151rizd a push-t k\u00f6vet\u0151en, hogy a GitHub Action alap\u00fa el\u0151ellen\u0151rz\u0151 hiba n\u00e9lk\u00fcl lefutott-e.
                  • L\u00e9nyeges, hogy a feladatok csak akkor ker\u00fclnek elfogad\u00e1sra, ha teljesen elk\u00e9sz\u00fclnek, \u00e9s minden tekintetben teljes\u00edtik a k\u00f6vetelm\u00e9nyeket. Nem fordul\u00f3 k\u00f3d, illetve r\u00e9szleges megold\u00e1s elfogad\u00e1s\u00e1ban nem \u00e9rdemes b\u00edzni.
                  • Term\u00e9szetesen saj\u00e1t munk\u00e1t kell beadni (hiszen \u00e9rt\u00e9kel\u00e9sre ker\u00fcl).
                  "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/","title":"2. HF - Sprachwerkzeuge","text":""},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

                  Die eigenst\u00e4ndige Aufgabe baut auf den Vorlesungen der Vorlesung 2 und der ersten H\u00e4lfte der Vorlesung 3 auf (diese sind im Vorlesungsmaterial \"Vorlesung 02 - Sprachliche Mittel\" enthalten). Labor 2 - Sprachwerkzeuge liefert den praktischen Hintergrund f\u00fcr die Labor\u00fcbung.

                  Aufbauend auf den obigen Ausf\u00fchrungen k\u00f6nnen die Aufgaben in dieser Selbst\u00fcbung unter Verwendung der k\u00fcrzeren Richtlinien, die auf die Aufgabenbeschreibung folgen, erledigt werden.

                  Das Ziel der unabh\u00e4ngigen \u00dcbung:

                  • Praktische Nutzung von Eigentum
                  • Delegierte und Ereignisse verwenden
                  • \u00fcben Sie die Verwendung von .NET-Attributen
                  • \u00dcben der Verwendung grundlegender Sammlungstypen
                  • \u00dcbung Lambda-Terme

                  Die erforderliche Entwicklungsumgebung wird hier beschrieben.

                  Using C# 12 (and newer) language elements

                  C# 12 und neuere Sprachelemente (z.B. prim\u00e4rer Konstruktor) k\u00f6nnen in dieser Hausaufgabe nicht verwendet werden, da der Checker auf GitHub sie noch nicht unterst\u00fctzt.

                  "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#einreichungsverfahren-pre-checker","title":"Einreichungsverfahren, Pre-Checker","text":"

                  Der Einreichungsprozess ist derselbe wie bei der ersten Hausaufgabe (siehe Arbeitsablauf bei Hausaufgaben und Verwendung von Git/GitHub f\u00fcr eine detaillierte Beschreibung an der \u00fcblichen Stelle):

                  1. Erstellen Sie mit GitHub Classroom ein Repository f\u00fcr sich selbst. Sie finden die Einladungs-URL in Moodle (Sie k\u00f6nnen sie sehen, indem Sie auf den Link*\"GitHub classroom links for homework*\" auf der Startseite des Fachs klicken). Es ist wichtig, dass Sie die richtige Einladungs-URL f\u00fcr diese Hausaufgabe verwenden (jede Hausaufgabe hat eine andere URL).
                  2. Klonen Sie das resultierende Repository. Dazu geh\u00f6rt auch die erwartete Struktur der L\u00f6sung.
                  3. Nachdem Sie die Aufgaben erledigt haben, \u00fcbergeben Sie Ihre L\u00f6sung alt und dr\u00fccken Sie sie alt.

                  Auch der Pre-Checker funktioniert wie gewohnt. Ausf\u00fchrliche Beschreibung: Vorabkontrolle und formale Bewertung der Hausaufgaben.

                  "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#aufgabe-1-ominose-schatten","title":"Aufgabe 1 - Omin\u00f6se Schatten","text":""},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#verfasst-am","title":"Verfasst am","text":"

                  Die Macht der Jedi-Ritter kommt bekanntlich von den winzigen Lebensformen, die in ihren Zellen leben, den Midi-Chlorianern. Der h\u00f6chste jemals gemessene Midi-Chlor-Wert (\u00fcber 20.000) wurde bei Anakin Skywalker gemessen.

                  Erstellen Sie eine Klasse mit dem Namen Jedi, die eine Eigenschaft Name vom Typ string und eine Eigenschaft MidiChlorianCount vom Typ int hat. Bei letzterem ist darauf zu achten, dass der Wert von MidiChlorianCount nicht auf 35 oder weniger gesetzt werden kann. W\u00e4hlen Sie f\u00fcr die Validierung die einfachste und sauberste L\u00f6sung, die m\u00f6glich ist: Verwenden Sie ein einfaches ifim Property Setter und l\u00f6sen Sie eine Exception aus, keine else Verzweigung von if, und keine Notwendigkeit, return zu verwenden.

                  "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#losung","title":"L\u00f6sung","text":"

                  Die L\u00f6sung dieser Aufgabe kann auf \u00e4hnliche Weise vorbereitet werden wie in Labor 2, Aufgabe 1. L\u00f6sen Sie im Setter der Eigenschaft MidiChlorianCount eine Ausnahme f\u00fcr einen ung\u00fcltigen Wert aus. Dies kann zum Beispiel mit dem folgenden Befehl geschehen:

                  throw new ArgumentException(\"You are not a true jedi!\");\n
                  "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#aufgabe-2-angriff-auf-die-klone","title":"Aufgabe 2 - Angriff auf die Klone","text":""},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#verfasst-am_1","title":"Verfasst am","text":"

                  F\u00fcgen Sie der Klasse, die Sie in \u00dcbung 1 erstellt haben, Attribute hinzu, so dass, wenn Sie ein Objekt Jedi mit der Klasse XmlSerializer in eine XML-Datendatei schreiben/zuweisen, seine Eigenschaften in Englisch als XML-Attribute angezeigt werden Schreiben Sie dann eine Funktion, die eine Instanz der Klasse Jedi in eine Textdatei sortiert und sie in ein neues Objekt zur\u00fcckliest (und damit das urspr\u00fcngliche Objekt klont).

                  XML-Sortierattribute

                  Platzieren Sie die Attribute, die die XML-Sortierung steuern, \u00fcber den Eigenschaften, nicht \u00fcber den Mitgliedsvariablen!

                  Die Jedi-Klasse sollte \u00f6ffentlich sein

                  Der XML-Sorter kann nur mit \u00f6ffentlichen Klassen arbeiten, daher sollte die Jedi-Klasse \u00f6ffentlich sein: csharp public class Jedi { ...}

                  Wichtig

                  Schreiben Sie den Code zum Speichern und Laden/Demonstrieren in eine gemeinsame dedizierte Funktion, und verweisen Sie auf die Funktion mit dem C#-Attribut [Description(\"Task2\")] (das in der Zeile vor der Funktion eingegeben werden muss). Das gespeicherte/geladene Objekt sollte in dieser Funktion als lokale Variable implementiert werden. Der Name der Klasse/Funktion kann beliebig sein (z. B. kann er in der Klasse Program stehen). Die Funktion sollte keinen Code enthalten, der nicht strikt mit der Aufgabe und somit auch nicht mit einer anderen (Unter-)Aufgabe zusammenh\u00e4ngt. Rufen Sie die Funktion \u00fcber die Funktion Main der Klasse Program auf. Um das oben genannte Attribut zu verwenden, m\u00fcssen Sie den Namespace System.ComponentModel verwenden.

                  Es ist wichtig, dass

                  • attribut \u00fcber Funktion und NE-Klasse,
                  • schreiben Sie das Attribut nicht \u00fcber die Funktion, die die Logik implementiert, sondern \u00fcber die Funktion, die sie testet,
                  • das Attribut kann nur \u00fcber einer einzigen Funktion erscheinen.
                  "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#losung_1","title":"L\u00f6sung","text":"

                  Die L\u00f6sung dieser Aufgabe kann auf \u00e4hnliche Weise wie in Labor 2, Aufgabe 4, vorbereitet werden. Die folgende Hilfe wird angeboten:

                  • Nach der Sortierung sollte die XML-Datei etwa so aussehen:

                    <?xml version=\"1.0\"?>\n<Jedi xmlns:xsi=\"...\" Nev=\"Obi-Wan\" MidiChlorianSzam=\"15000\" />\n

                    Es ist wichtig, dass jeder Jedi als XML-Element Jedi erscheint, sein Name Name, seine Midichlorian-Nummer MidiChlorianCount als XML-Attribut.

                  • Wir haben uns im Labor keinen Beispielcode f\u00fcr die R\u00fcckgabe sortierter Objekte angesehen, daher stellen wir ihn hier zur Verf\u00fcgung:

                    var serializer = new XmlSerializer(typeof(Jedi));\nvar stream = new FileStream(\"jedi.txt\", FileMode.Open);\nvar clone = (Jedi)serializer.Deserialize(stream);\nstream.Close();\n

                    In der vorherigen Zeile wird zun\u00e4chst eine Sortiertabelle (serializer) erstellt, die sp\u00e4ter zur Durchf\u00fchrung der Suche verwendet wird. Gelesen wird aus einer Datei namens jedi.txt, die in der zweiten Zeile zum Lesen ge\u00f6ffnet wird (wenn wir schreiben wollten, h\u00e4tten wirFileMode.Createangeben m\u00fcssen).

                  "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#herausforderung-3-die-rache-der-sith","title":"Herausforderung 3 - Die Rache der Sith","text":""},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#verfasst-am_2","title":"Verfasst am","text":"

                  Im Rat der Jedi hat es in letzter Zeit eine hohe Fluktuation gegeben. Um den \u00dcberblick \u00fcber \u00c4nderungen zu behalten, erstellen Sie eine Klasse, die Vorstandsmitglieder registrieren und eine Textbenachrichtigung \u00fcber \u00c4nderungen in Form eines Ereignisses senden kann! Die Liste kann mit zwei Funktionen bearbeitet werden. Die Funktion Add nimmt einen neuen Jedi-Ritter in den Rat auf, w\u00e4hrend die Funktion Remove das zuletzt aufgenommene Ratsmitglied wieder entfernt. Separate Benachrichtigung, wenn der Rat komplett leer ist (verwenden Sie dasselbe Ereignis wie f\u00fcr andere \u00c4nderungen, nur mit anderem Text).

                  Die Liste der Vorstandsmitglieder (members) wird in einer Mitgliedsvariablen des Typs List<Jedi> gespeichert, die Funktion Add f\u00fcgt dieser Liste neue Mitglieder hinzu, w\u00e4hrend die Funktion Remove immer das letzte durch die generische Liste RemoveAt hinzugef\u00fcgte Mitglied entfernt (der Index des letzten Mitglieds wird durch die L\u00e4nge der Liste bestimmt, die durch die Eigenschaft Count zur\u00fcckgegeben wird).

                  Die Benachrichtigung sollte \u00fcber ein C#-Ereignis erfolgen. Der Delegatentyp f\u00fcr das Ereignis sollte ein einfacher stringsein. Das Hinzuf\u00fcgen eines neuen Mitglieds, das Entfernen jedes Mitglieds und das Entfernen des letzten Mitglieds sollte durch einen anderen Nachrichtentext angezeigt werden. Das Ausl\u00f6sen von Ereignissen sollte direkt in Add und Remove erfolgen (f\u00fchren Sie keine Hilfsfunktion ein).

                  Verwenden Sie keinen eingebauten Delegatentyp f\u00fcr den Ereignistyp, sondern f\u00fchren Sie einen eigenen ein.

                  Wichtig

                  Der Code, der das Jeditan\u00e1cs-Objekt erstellt und testet (Abonnieren eines C#-Ereignisses, Aufrufen von Add und Remove ), sollte in einer gemeinsamen, separaten Funktion untergebracht werden, und diese Funktion sollte durch das C#-Attribut [Description(\"Task3\")] dargestellt werden. Der Name der Klasse/Funktion kann beliebig sein. Die Funktion sollte keinen Code enthalten, der nicht strikt mit der Aufgabe und somit auch nicht mit einer anderen (Unter-)Aufgabe zusammenh\u00e4ngt. Rufen Sie die Funktion \u00fcber die Funktion Main der Klasse Program auf.

                  Es ist wichtig, dass

                  • attribut \u00fcber Funktion und NE-Klasse,
                  • schreiben Sie das Attribut nicht \u00fcber die Funktion, die die Logik implementiert, sondern \u00fcber die Funktion, die sie testet,
                  • das Attribut kann nur \u00fcber einer einzigen Funktion erscheinen.
                  "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#losung_2","title":"L\u00f6sung","text":"

                  Die L\u00f6sung dieses Problems baut auf mehreren Details aus Labor 2 auf. Die Einf\u00fchrung einer neuen Veranstaltung kann wie in den \u00dcbungen 2 und 3 beschrieben erfolgen, wobei die Mitglieder des Gremiums in einer Liste eingetragen werden k\u00f6nnen.

                  Versuchen Sie anhand der obigen Informationen, das Problem selbst zu l\u00f6sen. Wenn Sie fertig sind, lesen Sie die Anleitung im n\u00e4chsten zu \u00f6ffnenden Block weiter und vergleichen Sie Ihre L\u00f6sung mit der Referenzl\u00f6sung unten Korrigieren Sie gegebenenfalls Ihre eigene L\u00f6sung!

                  \u00d6ffentliche Sichtbarkeit

                  Das Beispiel baut auf der Tatsache auf, dass die beteiligten Klassen, Eigenschaften und Delegierten \u00f6ffentlich sichtbar sind. Wenn Sie auf einen seltsamen \u00dcbersetzungsfehler sto\u00dfen oder XmlSerializer zur Laufzeit einen Fehler ausl\u00f6st, \u00fcberpr\u00fcfen Sie zun\u00e4chst, ob Sie die \u00f6ffentliche Sichtbarkeit auf allen relevanten Websites korrekt eingestellt haben.

                  Referenzl\u00f6sung

                  Die Schritte der Referenzl\u00f6sung sind wie folgt:

                  1. Erstelle eine neue Klasse mit dem Namen JediCouncil.
                  2. Man nehme ein Feld vom Typ \"Liste\" und initialisiere es mit einer leeren Liste.
                  3. Machen Sie die Funktionen \"Hinzuf\u00fcgen\" und \"Entfernen\" g\u00fcltig.

                    Nach den obigen Schritten erhalten wir den folgenden Code:

                    public class JediCouncil\n{\n    Liste<Jedi> members = new List<Jedi>();\n\n    public void Add(Jedi newJedi)\n    {\n        members.Add(newJedi);\n    }\n\n    public void Remove()\n    {\n        // Entfernt den letzten Eintrag in der Liste\n        members.RemoveAt(members.Count - 1);\n    }\n}\n

                    Der n\u00e4chste Schritt ist die Implementierung der Ereignisbehandlung.

                  4. Definieren Sie einen neuen Delegatentyp (au\u00dferhalb der Klasse, da es sich ebenfalls um einen Typ handelt), der den Benachrichtigungstext \u00fcbergeben wird:

                    public delegate void CouncilChangedDelegate(string message);\n
                  5. F\u00fcgen Sie die Klasse \"JediCouncil\" zum Ereignis-Handler hinzu:

                    public class JediCouncil\n{\n    public event CouncilChangedDelegate CouncilChanged;\n\n    // ...\n}\n
                  6. Lassen Sie uns das Ereignis feiern, wenn wir ein neues Vorstandsmitglied aufnehmen. Zu diesem Zweck m\u00fcssen wir die Methode \"Hinzuf\u00fcgen\" hinzuf\u00fcgen.

                    public void Add(Jedi newJedi)\n{\n    members.Add(newJedi);\n\n    // TODO: Fry die Veranstaltung hier.\n    // Beachten Sie, dass Sie dies nur tun sollten, wenn Sie mindestens einen Teilnehmer haben.\n    // Verwenden Sie dabei das modernere ?.Invoke und nicht die h\u00e4ufigere Nullpr\u00fcfung.\n}\n
                  7. Braten Sie das Ereignis, wenn ein Ratsmitglied geht! Unterscheiden Sie den Fall, dass der Rat v\u00f6llig leer ist. Dazu m\u00fcssen wir die Methode Remove hinzuf\u00fcgen.

                    public void Remove()\n{\n    // Entfernt den letzten Eintrag in der Liste\n    members.RemoveAt(members.Count - 1);\n\n    // TODO: Fry die Veranstaltung hier.\n    // Beachten Sie, dass Sie dies nur tun sollten, wenn Sie mindestens einen Teilnehmer haben.\n}\n
                  8. Um unsere L\u00f6sung zu testen, f\u00fcgen Sie eine Funktion MessageReceived zu der Klasse hinzu, in der wir das Ereignisabonnement und die Ereignisbehandlung testen wollen (z.B. die Klasse Program). Diese Funktion wird verwendet, um `JediCouncil'-Benachrichtigungen zu abonnieren.

                    Programm.cs
                    private static void MessageReceived(string message)\n{\n    Console.WriteLine(Nachricht);\n}\n
                  9. Testen Sie schlie\u00dflich die neue Klasse, indem Sie eine eigene Funktion schreiben (dies kann in der Klasse Programm geschehen) und f\u00fcgen Sie das Attribut [Description(\"Task3\")] oberhalb der Funktion hinzu Das Grundger\u00fcst der Funktion:

                    // Einrichtung des Rates\nvar council = new JediCouncil();\n\n// TODO: Melden Sie sich hier f\u00fcr die CouncilChanged-Veranstaltung an\n\n// TODO Hier f\u00fcgen Sie zwei Jedi-Objekte zum Ratsobjekt hinzu, indem Sie Add\n\ncouncil.Remove();\ncouncil.Remove();\n
                  10. Wenn wir unsere Arbeit gut gemacht haben, sollten wir nach der Ausf\u00fchrung des Programms die folgende Ausgabe erhalten:

                    ``Text Wir haben ein neues Mitglied Wir haben ein neues Mitglied Ich sp\u00fcre eine St\u00f6rung in der Kraft Der Rat ist gefallen! ```

                  11. Nullpr\u00fcfung von Ereignissen

                    Wenn Sie null in der Operation JediCouncil.Add verwendet haben, um zu pr\u00fcfen, ob es mindestens einen Abonnenten des Ereignisses gibt, konvertieren Sie dies in eine modernere L\u00f6sung (unter Verwendung von?.Invoke, die die Pr\u00fcfung auch in einer pr\u00e4gnanteren Form durchf\u00fchrt, aber ohne null Pr\u00fcfung - dies wurde in der zugeh\u00f6rigen Pr\u00e4sentation und im Labor besprochen). F\u00fcr JediCouncil.Add ist dies ausreichend, f\u00fcr JediCouncil.Remove sind beide L\u00f6sungen vorerst akzeptabel.

                    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#aufgabe-4-delegierte","title":"Aufgabe 4 - Delegierte","text":""},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#verfasst-am_3","title":"Verfasst am","text":"

                    Erg\u00e4nzen Sie die Klasse JediCouncil um eine parameterlose Funktion**(der Funktionsname muss ** mit** _Delegateenden , das ist zwingend erforderlich**), die alle Mitglieder des Jedi-Rates mit einer Midi-Chlorzahl unter 530 zur\u00fcckgibt

                    • Verwenden Sie zur Abfrage eine Funktion, keine Eigenschaft.
                    • Um die Mitglieder innerhalb der Funktion zu finden, verwenden Sie die Funktion FindAll() der Klasse List<Jedi>.
                    • In dieser \u00dcbung k\u00f6nnen Sie lambda noch NICHT verwenden!

                    Schreibe auch eine eigene \"Tester\"-Funktion (z.B. in der Klasse Program ), die unsere obige Funktion aufruft und die Namen der zur\u00fcckgegebenen Jedi-Ritter ausgibt! Diese Funktion sollte keinen Code enthalten, der nicht strikt mit der Aufgabe und somit auch nicht mit einer anderen (Unter-)Aufgabe zusammenh\u00e4ngt.

                    Danger

                    Gefahr \"Wichtig\" Siehe diese \"Tester\"-Funktion mit dem [Description(\"Task4\")] C#-Attribut. Rufen Sie die Funktion \u00fcber die Funktion Main der Klasse Program auf.

                    Es ist wichtig, dass

                    • attribut \u00fcber Funktion und NE-Klasse,
                    • schreiben Sie das Attribut nicht \u00fcber die Funktion, die die Logik implementiert, sondern \u00fcber die Funktion, die sie testet,
                    • das Attribut kann nur \u00fcber einer einzigen Funktion erscheinen.

                    Initialisierung auslagern

                    F\u00fchren Sie bei der Implementierung eine eigene statische Methode ein (z.B. in der Klasse Program ), die ein Jeditan\u00e1cs-Objekt als Parameter annimmt und durch Aufruf von Add mindestens drei parametrisierte Jedi -Objekte hinzuf\u00fcgt. Unser Ziel ist es, eine Initialisierungsmethode zu haben, die in der/den sp\u00e4teren Aufgabe(n) verwendet werden kann, ohne dass der entsprechende Initialisierungscode dupliziert werden muss.

                    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#losung_3","title":"L\u00f6sung","text":"

                    Zur L\u00f6sung dieser Aufgabe k\u00f6nnen Sie Labor 2 Labor 6 als Referenz verwenden. Um Sie zu unterst\u00fctzen, bieten wir Folgendes an:

                    • unsere Funktion kann mehrere Treffer zur\u00fcckgeben, daher ist der R\u00fcckgabetyp List<Jedi>,
                    • erwartet in unserem Fall eine Filterfunktion mit bool F\u00fcggv\u00e9nyn\u00e9v(Jedi j) als Parameter FindAll.
                    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#ubung-5-lambda-ausdrucke","title":"\u00dcbung 5 - Lambda-Ausdr\u00fccke","text":"

                    Die \u00dcbung ist dieselbe wie die vorhergehende, nur dass wir diesmal mit Lambda-Ausdr\u00fccken arbeiten werden. Dieses Thema wurde sowohl in der Vorlesung als auch im Labor (Labor 2, \u00dcbung 6) behandelt.

                    F\u00fcge der Klasse JediCouncil eine Funktion ohne Parameter hinzu**(der Funktionsname muss ** mit** _Lambdaenden , das ist obligatorisch**), die alle Mitglieder des Jedi-Rates mit einer Midi-Chlorianzahl unter 1000 zur\u00fcckgibt

                    • Verwenden Sie zur Abfrage eine Funktion, keine Eigenschaft.
                    • Um die Mitglieder innerhalb der Funktion zu finden, verwenden Sie die Funktion FindAll() der Klasse List<Jedi>.
                    • In dieser \u00dcbung m\u00fcssen Sie einen Lambda-Ausdruck verwenden (es spielt keine Rolle, ob Sie Anweisungs- oder Ausdrucks-Lambda verwenden)!

                    Schreibe auch eine eigene \"Tester\"-Funktion (z.B. in der Klasse Program ), die unsere obige Funktion aufruft und die Namen der zur\u00fcckgegebenen Jedi-Ritter ausgibt! Diese Funktion sollte keinen Code enthalten, der nicht strikt mit der Aufgabe und somit auch nicht mit einer anderen (Unter-)Aufgabe zusammenh\u00e4ngt.

                    Wichtig

                    Siehe diese \"Tester\"-Funktion mit dem [Description(\"Task5\")] C#-Attribut. Rufen Sie die Funktion \u00fcber die Funktion Main der Klasse Program auf.

                    Es ist wichtig, dass

                    • attribut \u00fcber Funktion und NE-Klasse,
                    • schreiben Sie das Attribut nicht \u00fcber die Funktion, die die Logik implementiert, sondern \u00fcber die Funktion, die sie testet,
                    • das Attribut kann nur \u00fcber einer einzigen Funktion erscheinen.
                    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#aufgabe-6-actionfunc-verwenden","title":"Aufgabe 6 - Action/Func verwenden","text":"

                    Diese \u00dcbung baut auf dem Stoff der Vorlesung 3 auf und war (aus Zeitgr\u00fcnden) nicht Bestandteil des Praktikums. Dennoch handelt es sich um ein wesentliches Kernthema des Fachs.

                    F\u00fcgen Sie dem Projekt eine Klasse Person und eine Klasse ReportPrinter (jeweils in einer Datei mit dem gleichen Namen wie die Klasse) mit folgendem Inhalt hinzu:

                    Person und ReportPrinter Klassen
                    class Person\n{\n    public Person(string name, int age)\n    {\n        Name = name;\n        Age = age;\n    }\n\n    public string Name { get; set; }\n    public int Age { get; set; }\n}\n
                    class ReportPrinter\n{\n    private readonly IEnumerable<Person> people;\n    private readonly Action headerPrinter;\n\n    public ReportPrinter(IEnumerable<Person> people, Action headerPrinter)\n    {\n        this.people = people;\n        this.headerPrinter = headerPrinter;\n    }\n\n    public void PrintReport()\n    {\n        headerPrinter();\n        Console.WriteLine(\"-----------------------------------------\");\n        int i = 0;\n        foreach (var person in people)\n        {\n            Console.Write($\"{++i}. \");\n            Console.WriteLine(\"Person\");\n        }\n        Console.WriteLine(\"--------------- Summary -----------------\");\n        Console.WriteLine(\"Footer\");\n    }\n}\n

                    Diese Klasse ReportPrinter kann verwendet werden, um einen formatierten Bericht \u00fcber die Daten der in ihrem Konstruktor angegebenen Personen in die Konsole zu schreiben, und zwar in einer Dreifachaufteilung von Kopfzeile/Daten/Fu\u00dfzeile. F\u00fcgen Sie die folgende Funktion zu Program.cs hinzu, um ReportPrinter zu testen, und rufen Sie sie von Main aus auf:

                    Test ReportPrinter
                    [Description(\"Task6\")]\nstatic void test6()\n{\n    var employees = new Person[] { new Person(\"Joe\", 20), new Person(\"Jill\", 30) };\n\n    ReportPrinter reportPrinter = new ReportPrinter(\n        employees,\n        () => Console.WriteLine(\"Employees\")\n        );\n\n    reportPrinter.PrintReport();\n}\n

                    F\u00fchren Sie die Anwendung aus. Die Ausgabe auf der Konsole sieht wie folgt aus:

                    Employees\n-----------------------------------------\n1. Person\n2. Person\n--------------- Summary -----------------\nFooter\n

                    Die erste Zeile \u00fcber \"----\" ist die Kopfzeile. Unter jeder Person befindet sich ein eingebrannter \"Person\"-Text, dann unter \"----\" die Fu\u00dfzeile, vorerst nur mit einem eingebrannten \"Footer\"-Text.

                    In der L\u00f6sung k\u00f6nnen Sie sehen, dass der \u00dcberschriftentext nicht in die Klasse ReportPrinter eingebrannt wird. Diese wird vom Benutzer von ReportPrinter in einem Konstruktorparameter in Form eines Delegaten, in unserem Fall eines Lambda-Ausdrucks, angegeben. Der Delegatentyp ist der in .NET integrierte Typ Action.

                    Die Aufgaben sind:

                    Warning

                    Sie k\u00f6nnen NICHT Ihren eigenen Delegattyp in der L\u00f6sung verwenden (arbeiten Sie mit .NET eingebauten Delegattypen, die L\u00f6sung ist nur dann akzeptabel).

                    1. Umstrukturierung der Klasse ReportPrinter, so dass der Benutzer der Klasse nicht nur die Kopfzeile, sondern auch die Fu\u00dfzeile in Form eines Delegaten angeben kann.

                    2. \u00c4ndern Sie die Klasse ReportPrinter so, dass der feste Text \"Person\" nicht angezeigt wird, wenn jede Person hinzugef\u00fcgt wird, sondern der Benutzer der Klasse ReportPrinter die Daten jeder Person nach Bedarf \u00fcber einen Delegaten hinzuf\u00fcgen kann (anstelle des festen Texts \"Person\"). Es ist wichtig, dass die Zeilennummer immer am Anfang der Zeile steht, sie kann vom Benutzer von ReportPrinter nicht ge\u00e4ndert werden!

                      Tipp f\u00fcr die L\u00f6sung

                      Denken Sie an einen \u00e4hnlichen Ansatz wie f\u00fcr die Kopf- und Fu\u00dfzeile, aber hier muss der Benutzer von ReportPrinter das Personenobjekt erhalten, um es formatiert in die Konsole schreiben zu k\u00f6nnen.

                    3. \u00c4ndern Sie in der Datei Program.cs die Verwendung von ReportPrinter (mit den entsprechenden Lambda-Ausdr\u00fccken), so dass die Ausgabe auf der Konsole lautet:

                      Employees\n-----------------------------------------\n1. Name: Joe (Age: 20)\n2. Name: Jill (Age: 30)\n--------------- Summary -----------------\nAnzahl der Mitarbeiter: 2\n

                      Hausaufgabenpr\u00fcfung

                      Die Aufgabe \"Aufgabe 6\", d.h. ob Sie ReportPrinterund dessen Verwendung korrekt konvertiert haben, wird NICHT vom automatischen GitHub-Checker gepr\u00fcft. Testen Sie Ihre L\u00f6sung gr\u00fcndlich, damit Sie nicht erst nach dem Abgabetermin bei der manuellen Kontrolle Ihrer Hausaufgaben feststellen, dass sie nicht akzeptabel ist.

                    4. Die n\u00e4chste \u00dcbung ist optional und bietet Ihnen eine gute Gelegenheit, die eingebauten Func Delegierten zu \u00fcben. Die Klasse ReportPrinter hat einen gro\u00dfen Nachteil: Der Ausgabebericht kann nur auf der Konsole angezeigt werden. Eine flexiblere L\u00f6sung w\u00e4re, nicht in die Konsole zu schreiben, sondern einen String zu verwenden, um den Bericht zu erstellen. Diese Zeichenkette kann auf beliebige Weise verwendet werden (z. B. in eine Datei schreiben).

                      Die Aufgabe besteht darin, eine Klasse ReportBuilder einzuf\u00fchren, die auf der bestehenden ReportPrinter basiert, aber nicht in die Konsole schreibt, sondern eine Zeichenkette mit dem vollst\u00e4ndigen Bericht erzeugt, der durch eine neu eingef\u00fchrte Operation GetResult() abgerufen werden kann.

                      Tipps f\u00fcr die L\u00f6sung

                      • Es ist eine gute Idee, eine StringBuilder Mitgliedsvariable in die Klasse einzuf\u00fchren und mit ihr zu arbeiten. Dies ist um Gr\u00f6\u00dfenordnungen effizienter als die Verkettung von Zeichenketten mit \"+\".
                      • In diesem Fall sollte der Benutzer der Klasse ReportBuilder nicht mehr in die Konsole schreiben, sondern die an die Ausgabe anzuh\u00e4ngenden Zeichenketten an ReportBuilder zur\u00fcckgeben und dabei die entsprechenden eingebauten Typdelegierten verwenden ( Action ist hier nicht geeignet). Verwenden Sie jetzt Lambda-Terme in der Pr\u00fcfung!
                    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#aufgabe-7-imsc-verwendung-eingebauter-funcaction-generischer-delegatentypen","title":"Aufgabe 7 (IMSc) - Verwendung eingebauter Func/Action generischer Delegatentypen","text":"

                    Das L\u00f6sen der Aufgabe ist nicht obligatorisch, aber sehr empfehlenswert: Es handelt sich um einen Grundstoff, der in die ZH/Pr\u00fcfung aufgenommen werden kann. Nicht in einem Labor, nur in einer Vorlesung.

                    Die L\u00f6sung bringt au\u00dferdem +2 IMSc-Punkte ein.

                    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#verfasst-am_4","title":"Verfasst am","text":"

                    Erweitern Sie die Klasse JediCouncil.

                    • Erstellen Sie eine Eigenschaft Count mit dem R\u00fcckgabewert int, die bei jeder Abfrage die aktuelle Anzahl der Jedi im Rat zur\u00fcckgibt. Achten Sie darauf, dass dieser Wert nur abgefragt (nicht gesetzt) werden kann.

                      Tipp

                      Die Membervariable members in JediCouncilhat eine Eigenschaft Count, die L\u00f6sung baut darauf auf.

                    • Erstellen Sie eine Funktion namens CountIf, die ebenfalls die Anzahl der Ratsmitglieder z\u00e4hlt, aber nur die Ratsmitglieder ber\u00fccksichtigt, die bestimmte Bedingungen erf\u00fcllen. Der R\u00fcckgabewert der Funktion ist int, und die Bedingung, f\u00fcr die sie die entsprechende Anzahl von Ratsmitgliedern zur\u00fcckgibt, wird als Parameter \u00fcber einen Delegaten zur\u00fcckgegeben ( CountIfmuss also einen Parameter haben).

                      Delegatentyp

                      Der Delegatentyp muss der richtige der eingebauten generischen Action / Func Delegatentypen sein (d.h. Sie k\u00f6nnen nicht Ihren eigenen Delegatentyp oder den eingebauten Predicate Typ verwenden).

                      Aus diesem Grund k\u00f6nnen Sie die eingebaute Operation FindAll f\u00fcr die Liste NICHT verwenden, da der von uns verwendete Delegatentyp nicht mit dem von FindAll erwarteten Parameter kompatibel w\u00e4re. Bearbeite die Tags, indem du eine `foreach'-Schleife durchl\u00e4ufst!

                    • Zeigen Sie die Eigenschaft und die Funktion in einer eigenen gemeinsamen Funktion, die Sie mit dem Attribut [Description(\"Task7\")] bereitstellen k\u00f6nnen. Diese Funktion sollte keinen Code enthalten, der nicht unmittelbar mit der Aufgabe zusammenh\u00e4ngt. Um den Jedi-Rat zu laden, rufen Sie die in der vorherigen Aufgabe vorgestellte Hilfsfunktion auf. Rufen Sie die Funktion \u00fcber die Funktion Main der Klasse Program auf.

                      Wichtig

                      Das Attribut [Description(\"Task7\")] kann nur oberhalb einer einzigen Funktion verwendet werden.

                    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#losung_4","title":"L\u00f6sung","text":"
                    • Bei einer Eigenschaft namens Count ist nur der Zweig get sinnvoll, der Zweig set wird also nicht geschrieben. Diese Eigenschaft sollte schreibgesch\u00fctzt sein.
                    • \u00dcbung 4 hilft Ihnen, die Funktion CountIf zu schreiben. Der Unterschied besteht darin, dass CountIf nicht die Anzahl der Ratsmitglieder, sondern nur die Anzahl der St\u00fccke angibt.
                      • Die Funktion CountIf sollte eine Filterfunktion mit der Signatur bool F\u00fcggv\u00e9nyn\u00e9v(Jedi jedi) als Bedingungsparameter erwarten.
                    "},{"location":"hazi/2-nyelvi-eszkozok/index_ger/#vorlegen-bei","title":"Vorlegen bei","text":"

                    Checkliste f\u00fcr Wiederholungen:

                    • Geben Sie in der Datei neptun.txt im Stammverzeichnis des Repositorys Ihren Neptun-Code in Gro\u00dfbuchstaben ein. Die Datei sollte nur diese sechs Zeichen enthalten und nichts anderes.
                    • Sie sollten in den urspr\u00fcnglichen L\u00f6sungen/Projekten arbeiten, die Sie von GitHub heruntergeladen haben, und nicht in neu erstellten Projekten.
                    • Solange Sie nicht mit Visual Studio Git vertraut sind, sollten Sie nach dem Push (sp\u00e4testens wenn die Hausarbeit als eingereicht gilt) \u00fcberpr\u00fcfen, ob Sie alle \u00c4nderungen hochgeladen haben, indem Sie sich die Dateien im Repository auf der GitHub-Weboberfl\u00e4che ansehen.
                    • \u00dcberpr\u00fcfen Sie in der GitHub-Schnittstelle nach dem Push, ob der GitHub Action-basierte Pre-Validator fehlerfrei gelaufen ist.
                    • Es ist wichtig, dass Aufgaben nur angenommen werden, wenn sie vollst\u00e4ndig abgeschlossen sind und den Anforderungen in jeder Hinsicht entsprechen. Nicht rotierenden Codes oder Teill\u00f6sungen sollte man nicht trauen.
                    • Nat\u00fcrlich m\u00fcssen Sie Ihre eigene Arbeit einreichen (da sie bewertet wird).
                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/","title":"3. HF - Felhaszn\u00e1l\u00f3i fel\u00fclet kialak\u00edt\u00e1sa","text":""},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#bevezetes","title":"Bevezet\u00e9s","text":"

                    A h\u00e1zi feladatban elk\u00e9sz\u00edtend\u0151 kis szoftver egy egyszer\u0171 feladatkezel\u0151 alkalmaz\u00e1s, amelyben a felhaszn\u00e1l\u00f3k feladatokat tudnak list\u00e1zni l\u00e9trehozni, m\u00f3dos\u00edtani.

                    Az \u00f6n\u00e1ll\u00f3 feladat a XAML el\u0151ad\u00e1sokon elhangzottakra \u00e9p\u00edt. A feladatok gyakorlati h\u00e1tter\u00e9\u00fcl a 3. labor \u2013 Felhaszn\u00e1l\u00f3i fel\u00fcletek kialak\u00edt\u00e1sa laborgyakorlat szolg\u00e1l.

                    A fentiekre \u00e9p\u00edtve, jelen \u00f6n\u00e1ll\u00f3 gyakorlat feladatai a feladatle\u00edr\u00e1st k\u00f6vet\u0151 r\u00f6videbb ir\u00e1nymutat\u00e1s seg\u00edts\u00e9g\u00e9vel (n\u00e9ha alap\u00e9rtelmezetten \u00f6sszecsukva) \u00f6n\u00e1ll\u00f3an elv\u00e9gezhet\u0151k.

                    Az \u00f6n\u00e1ll\u00f3 gyakorlat c\u00e9lja:

                    • XAML fel\u00fcletle\u00edr\u00f3 nyelv haszn\u00e1lat\u00e1nak gyakorl\u00e1sa
                    • Alapvet\u0151 vez\u00e9rl\u0151k (t\u00e1bl\u00e1zat, gomb, sz\u00f6vegdoboz, list\u00e1k) haszn\u00e1lat\u00e1nak gyakorl\u00e1sa
                    • Fel\u00fcleti interakci\u00f3k kezel\u00e9se esem\u00e9nyvez\u00e9relten
                    • Adatok megjelen\u00edt\u00e9se a fel\u00fcleten adatk\u00f6t\u00e9ssel

                    A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s.

                    Fejleszt\u0151k\u00f6rnyezet WinUI3 fejleszt\u00e9shez

                    A kor\u00e1bbi laborokhoz k\u00e9pest plusz komponensek telep\u00edt\u00e9se sz\u00fcks\u00e9ges. A fenti oldal eml\u00edti, hogy sz\u00fcks\u00e9g van a \".NET desktop development\" Visual Studio Workload telep\u00edt\u00e9s\u00e9re, valamint ugyanitt az oldal alj\u00e1n van egy \"WinUI t\u00e1mogat\u00e1s\" fejezet, az itt megadott l\u00e9p\u00e9seket is mindenk\u00e9ppen meg kell tenni!

                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#a-beadas-menete","title":"A bead\u00e1s menete","text":"

                    B\u00e1r az alapok hasonl\u00f3k, vannak l\u00e9nyeges, a folyamatra \u00e9s k\u00f6vetelm\u00e9nyekre vonatkoz\u00f3 elt\u00e9r\u00e9sek a kor\u00e1bbi h\u00e1zi feladatokhoz k\u00e9pest, \u00edgy mindenk\u00e9ppen figyelmesen olvasd el a k\u00f6vetkez\u0151ket.

                    • Az alapfolyamat megegyezik a kor\u00e1bbiakkal. GitHub Classroom seg\u00edts\u00e9g\u00e9vel hozz l\u00e9tre magadnak egy repository-t. A megh\u00edv\u00f3 URL-t Moodle-ben tal\u00e1lod (a t\u00e1rgy nyit\u00f3oldal\u00e1n a \"GitHub classroom hivatkoz\u00e1sok a h\u00e1zi feladatokhoz\" hivatkoz\u00e1sra kattintva megjelen\u0151 oldalon l\u00e1that\u00f3). Fontos, hogy a megfelel\u0151, ezen h\u00e1zi feladathoz tartoz\u00f3 megh\u00edv\u00f3 URL-t haszn\u00e1ld (minden h\u00e1zi feladathoz m\u00e1s URL tartozik). Kl\u00f3nozd le az \u00edgy elk\u00e9sz\u00fclt repository-t. Ez tartalmazni fogja a megold\u00e1s elv\u00e1rt szerkezet\u00e9t. A feladatok elk\u00e9sz\u00edt\u00e9se ut\u00e1n commit-old \u00e9s push-old a megold\u00e1sod.
                    • A kikl\u00f3nozott f\u00e1jlok k\u00f6z\u00f6tt a TodoXaml.sln-t megnyitva kell dolgozni.
                    • A feladatok k\u00e9rik, hogy k\u00e9sz\u00edts k\u00e9perny\u0151k\u00e9pet a megold\u00e1s egy-egy r\u00e9sz\u00e9r\u0151l, mert ezzel bizony\u00edtod, hogy a megold\u00e1sod saj\u00e1t magad k\u00e9sz\u00edtetted. A k\u00e9perny\u0151k\u00e9pek elv\u00e1rt tartalm\u00e1t a feladat minden esetben pontosan megnevezi. A k\u00e9perny\u0151k\u00e9peket a megold\u00e1s r\u00e9szek\u00e9nt kell beadni, a repository-d gy\u00f6k\u00e9rmapp\u00e1j\u00e1ba tedd (a neptun.txt mell\u00e9). A k\u00e9perny\u0151k\u00e9pek \u00edgy felker\u00fclnek GitHub-ra a git repository tartalm\u00e1val egy\u00fctt. Mivel a repository priv\u00e1t, azt az oktat\u00f3kon k\u00edv\u00fcl m\u00e1s nem l\u00e1tja. Amennyiben olyan tartalom ker\u00fcl a k\u00e9perny\u0151k\u00e9pre, amit nem szeretn\u00e9l felt\u00f6lteni, kitakarhatod a k\u00e9pr\u0151l.
                    • Ehhez a feladathoz \u00e9rdemi el\u0151ellen\u0151rz\u0151 nem tartozik: minden push ut\u00e1n lefut ugyan, de csak a neptun.txt kit\u00f6lt\u00f6tts\u00e9g\u00e9t ellen\u0151rzi. Az \u00e9rdemi ellen\u0151rz\u00e9st a hat\u00e1rid\u0151 lej\u00e1rta ut\u00e1n a laborvezet\u0151k teszik majd meg.
                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#kikotesek","title":"Kik\u00f6t\u00e9sek","text":"

                    MVVM minta - ne alkalmazd! Jelen h\u00e1zi feladatban az MVVM mint\u00e1t m\u00e9g NE haszn\u00e1ld (egyik k\u00e9s\u0151bbi r\u00e9szfeladatn\u00e1l sem), ViewModel oszt\u00e1lyt NE vezess be. Az MVVM egy k\u00e9s\u0151bb h\u00e1zi feladatnak lesz a t\u00e1rgya.

                    Layout - egyszer\u0171s\u00e9g Mint \u00e1ltal\u00e1ban, a jelen h\u00e1zi feladat keret\u00e9ben elk\u00e9sz\u00edtend\u0151 feladatra is igaz, hogy az oldal alapelrendez\u00e9s\u00e9t Grid-del c\u00e9lszer\u0171 kialak\u00edtani. Ugyanakkor az egyes bels\u0151 r\u00e9szek elrendez\u00e9s\u00e9nek kialak\u00edt\u00e1sakor t\u00f6rekedj az egyszer\u0171s\u00e9gre: ahol az StackPanel-t is lehet haszn\u00e1lni, ne haszn\u00e1lj Grid-et.

                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#1-feladat-modell-kialakitasa-es-tesztadatok","title":"1. feladat - Modell kialak\u00edt\u00e1sa \u00e9s tesztadatok","text":"

                    A projekten bel\u00fcl hozzunk l\u00e9tre egy Models mapp\u00e1t (VS Solution Exporerben), majd a mapp\u00e1ba az al\u00e1bbi \u00e1br\u00e1n l\u00e1that\u00f3 oszt\u00e1lyt \u00e9s enum t\u00edpust. A TodoItem oszt\u00e1ly fogja tartalmazni a teend\u0151k adatait, a priorit\u00e1shoz egy felsorolt t\u00edpust hozunk l\u00e9tre.

                    Mindk\u00e9t t\u00edpus legyen publikus (\u00edrjuk a class \u00e9s az enum el\u00e9 a public kulcssz\u00f3t), k\u00fcl\u00f6nben \"Inconsistent accessibility\" hib\u00e1t kapn\u00e1nk a k\u00e9s\u0151bbiekben a ford\u00edt\u00e1s sor\u00e1n.

                    A MainPage oldal fogja a teend\u0151k list\u00e1j\u00e1t megjelen\u00edteni. Most mem\u00f3ri\u00e1ban l\u00e9v\u0151 tesztadatokat haszn\u00e1ljunk, melyeket a Views mapp\u00e1ban tal\u00e1lhat\u00f3 MainPage.xaml.cs-ben hozzunk l\u00e9tre: itt Todos n\u00e9ven vezess\u00fcnk be egy List<TodoItem> tulajdons\u00e1got (melyet k\u00e9s\u0151bb a fel\u00fcleten elhelyezett ListView vez\u00e9rl\u0151h\u00f6z k\u00f6t\u00fcnk adatk\u00f6t\u00e9ssel). Ez a lista TodoItem objektumokat tartalmaz.

                    MainPage.xaml.cs
                    public List<TodoItem> Todos { get; set; } = new()\n{\n    new TodoItem()\n    {\n        Id = 3,\n        Title = \"Add Neptun code to neptun.txt\",\n        Description = \"NEPTUN\",\n        Priority = Priority.Normal,\n        IsDone = false,\n        Deadline = new DateTime(2024, 11, 08)\n    },\n    new TodoItem()\n    {\n        Id = 1,\n        Title = \"Buy milk\",\n        Description = \"Should be lactose and gluten free!\",\n        Priority = Priority.Low,\n        IsDone = true,\n        Deadline = DateTimeOffset.Now + TimeSpan.FromDays(1)\n    },\n    new TodoItem()\n    {\n        Id = 2,\n        Title = \"Do the Computer Graphics homework\",\n        Description = \"Ray tracing, make it shiny and gleamy! :)\",\n        Priority = Priority.High,\n        IsDone = false,\n        Deadline = new DateTime(2024, 11, 08)\n    },\n};\n
                    A fenti k\u00f3d magyar\u00e1zata

                    A fenti k\u00f3dr\u00e9szletben t\u00f6bb modern C# nyelvi elemet kombin\u00e1ltunk:

                    • Ez egy auto-implement\u00e1lt tulajdons\u00e1g (l\u00e1sd 2. labor).
                    • Kedz\u0151\u00e9rt\u00e9ket adtunk neki.
                    • A new ut\u00e1n nem adtuk meg a t\u00edpust, mert a ford\u00edt\u00f3 ki tudja k\u00f6vetkeztetni (l\u00e1sd 2. labor \"Target-typed new expressions\").
                    • A gy\u0171jtem\u00e9ny elemeit {} k\u00f6z\u00f6tt soroljuk fel (l\u00e1sd 2. labor \"Collection initializer szintaxis\").

                    MainPage oszt\u00e1ly

                    A h\u00e1zi feladat sor\u00e1n a be\u00e9p\u00edtett Page oszt\u00e1lyb\u00f3l sz\u00e1rmaz\u00f3 MainPage oszt\u00e1lyban dolgozunk. A Page oszt\u00e1ly az ablakon bel\u00fcli oldalak k\u00f6z\u00f6tti navig\u00e1ci\u00f3t seg\u00edti. B\u00e1r jelen feladatban ezt nem haszn\u00e1ljuk ki, \u00e9rdemes megszokni a haszn\u00e1lat\u00e1t. Mivel alkalmaz\u00e1sunk egyetlen oldalb\u00f3l \u00e1ll, a f\u0151ablakban egyszer\u0171en csak p\u00e9ld\u00e1nyos\u00edtunk egy MainPage objektumot (\u00e9rdemes a MainWindow.xaml f\u00e1jlban ezt megtekinteni).

                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#2-feladat-oldal-elrendezese-layout-lista-megjelenitese","title":"2. feladat - Oldal elrendez\u00e9se (layout), lista megjelen\u00edt\u00e9se","text":""},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#layout","title":"Layout","text":"

                    A MainPage.xaml-ben hozzuk l\u00e9tre a fel\u00fcletet, amelyen a teend\u0151k list\u00e1j\u00e1t megjelen\u00edtj\u00fck.

                    K\u00e9sz\u00edtend\u0151 alkalmaz\u00e1s list\u00e1z\u00f3 fel\u00fclettel

                    Mint a fenti \u00e1bra a h\u00e1rom teend\u0151vel mutatja, a teend\u0151k adatait egym\u00e1s alatt kell megjelen\u00edteni, a teend\u0151k priorit\u00e1s\u00e1t sz\u00ednek jelzik, a k\u00e9sz teend\u0151k mellett azok jobb oldal\u00e1n egy pipa jelenik meg.

                    A fel\u00fcleten a k\u00f6vetkez\u0151 strukt\u00far\u00e1ban helyezkednek el az elemek:

                    • A MainPage-en bel\u00fcl egy Grid-et haszn\u00e1ljunk, amelyben k\u00e9t sorban \u00e9s k\u00e9t oszlopban helyezkednek el az elemek. Az els\u0151 oszlop fix sz\u00e9les legyen (pl.: 300 px), a m\u00e1sodik pedig a marad\u00e9k helyet foglalja el.
                    • Az els\u0151 oszlop els\u0151 sor\u00e1ban egy CommandBar vez\u00e9rl\u0151 ker\u00fclj\u00f6n, melyben egy c\u00edm \u00e9s egy gomb helyezkedik el. Ehhez az al\u00e1bbi p\u00e9lda szolg\u00e1l seg\u00edts\u00e9g\u00fcl:

                      <CommandBar VerticalContentAlignment=\"Center\"\n            Background=\"{ThemeResource AppBarBackgroundThemeBrush}\"\n            DefaultLabelPosition=\"Right\">\n    <CommandBar.Content>\n        <TextBlock Margin=\"12,0,0,0\"\n                   Style=\"{ThemeResource SubtitleTextBlockStyle}\"\n                   Text=\"To-Dos\" />\n    </CommandBar.Content>\n\n    <AppBarButton Icon=\"Add\"\n                  Label=\"Add\" />\n</CommandBar>\n

                      Vil\u00e1gos/s\u00f6t\u00e9t megjelen\u00e9s

                      A Windows be\u00e1ll\u00edtasainak f\u00fcggv\u00e9ny\u00e9ben (light/dark mode) lehets\u00e9ges, hogy s\u00f6t\u00e9t h\u00e1tt\u00e9ren vil\u00e1gos sz\u00ednekkel jelenik meg a fel\u00fclet, ez is teljesen rendben van. A WinUI alkalmaz\u00e1sok alap\u00e9rtelemezett esetben alkalmazkodnak az oper\u00e1ci\u00f3s rendszer be\u00e1ll\u00edt\u00e1s\u00e1hoz, ebb\u0151l ered ez a viselked\u00e9s.

                      ThemeResource

                      A p\u00e9ld\u00e1ban szerepl\u0151 ThemeResource-okat haszn\u00e1lhatjuk a sz\u00ednek \u00e9s st\u00edlusok be\u00e1ll\u00edt\u00e1s\u00e1ra, melyek a fel\u00fclet t\u00e9m\u00e1j\u00e1t\u00f3l f\u00fcgg\u0151en v\u00e1ltoznak. P\u00e9ld\u00e1ul a AppBarBackgroundThemeBrush a fel\u00fclet t\u00e9m\u00e1j\u00e1t\u00f3l (vil\u00e1gos/s\u00f6t\u00e9t) f\u00fcgg\u0151en a megfelel\u0151 sz\u00edn\u0171 h\u00e1tt\u00e9r lesz.

                      R\u00e9szletek\u00e9rt l\u00e1sd a dokument\u00e1ci\u00f3t \u00e9s a WinUI 3 Gallery App Colors p\u00e9ld\u00e1it.

                    Ha j\u00f3l dolgoztunk, az alkalmaz\u00e1st futtatva, CommandBar-nak a megfelel\u0151 helyen meg kell jelennie.

                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#lista-megjelenitese","title":"Lista megjelen\u00edt\u00e9se","text":"

                    A CommandBar alatti cell\u00e1ban egy list\u00e1ba (ListView) ker\u00fcljenek a teend\u0151k a k\u00f6vetkez\u0151 tartalommal egym\u00e1s alatt. Az adatok adatk\u00f6t\u00e9sen kereszt\u00fcl hassanak a fel\u00fclet megjelen\u00edt\u00e9s\u00e9re (a kor\u00e1bban bevezetett Todos list\u00e1b\u00f3l jelenjenek meg adatk\u00f6t\u00e9ssel az elemek).

                    • Teend\u0151 c\u00edme
                      • F\u00e9lk\u00f6v\u00e9r (SemiBold) bet\u0171t\u00edpussal
                      • Priorit\u00e1s alapj\u00e1n sz\u00ednezve
                        • Magas priorit\u00e1s: piros egy \u00e1rnyalata
                        • Norm\u00e1l priorit\u00e1s: be\u00e9p\u00edtett el\u0151t\u00e9rsz\u00edn
                        • Alacsony priorit\u00e1s: k\u00e9k egy \u00e1rnyalata
                    • A teend\u0151 c\u00edm\u00e9vel egy sorban jobbra rendezve egy pipa ikon, ha a teend\u0151 el van v\u00e9gezve
                    • Teend\u0151 le\u00edr\u00e1sa
                    • Teend\u0151 hat\u00e1rideje yyyy.MM.dd form\u00e1tumban
                    • A ListView h\u00e1ttere legyen azonos a CommandBar-\u00e9val, \u00edgy baloldalt egy egybef\u00fcgg\u0151 s\u00e1vot alkotnak.
                    Elemek a list\u00e1ban

                    Mindig gondoljuk \u00e1t, hogy egy objektumhoz t\u00f6rt\u00e9n\u0151, vagy list\u00e1s adatk\u00f6t\u00e9sr\u0151l van-e sz\u00f3, \u00e9s ennek megfelel\u0151 technik\u00e1t alkalmazzunk! Jelen h\u00e1zi feladatban nem biztos, olyan sorrendben j\u00f6nnek ezek el\u0151, mint ahogy laboron szerepeltek!\"

                    Felt\u00e9teles sz\u00ednez\u00e9s

                    A c\u00edm sz\u00ednez\u00e9s\u00e9re haszn\u00e1lhatunk konvertert vagy x:Bind alap\u00fa f\u00fcggv\u00e9ny k\u00f6t\u00e9st is.

                    • x:Bind alap\u00fa f\u00fcggv\u00e9ny k\u00f6t\u00e9s p\u00e9lda:

                      Foreground=\"{x:Bind local:MainPage.GetForeground(Priority)}\"\n

                      Itt a GetForeground egy publikus statikus f\u00fcggv\u00e9ny a MainPage oszt\u00e1lyban, amely a Priority felsorolt t\u00edpus alapj\u00e1n visszaadja a megfelel\u0151 sz\u00edn\u0171 Brush objektumot. Alap esetben nem lenne fontos a f\u00fcggv\u00e9nynek statikusnak lennie, de mivel itt egy DataTemplate-ben haszn\u00e1ljuk az adatk\u00f6t\u00e9st, ez\u00e9rt az x:Bind kontextusa nem az oldal p\u00e9ld\u00e1nya lesz, hanem a listaelem.

                    • Converter haszn\u00e1lat\u00e1ra p\u00e9lda:

                      Hozzunk l\u00e9tre egy konverter oszt\u00e1lyt egy Converters mapp\u00e1ba, ami megval\u00f3s\u00edtja az IValueConverter interf\u00e9szt.

                      public class PriorityBrushConverter : IValueConverter\n{\n    public object Convert(object value, Type targetType, object parameter, string language)\n    {\n        // TODO return a SolidColorBrush instance\n    }\n\n    public object ConvertBack(object value, Type targetType, object parameter, string language)\n    {\n        throw new NotImplementedException();\n    }\n}\n

                      P\u00e9ld\u00e1nyos\u00edtsuk a konvertert a MainPage er\u0151forr\u00e1sai k\u00f6z\u00f6tt.

                      xmlns:c=\"using:TodoXaml.Converters\"\n\n<Page.Resources>\n    <c:PriorityBrushConverter x:Key=\"PriorityBrushConverter\" />\n</Page.Resources>\n

                      Haszn\u00e1ljuk az adatk\u00f6t\u00e9sben statikus er\u0151forr\u00e1sk\u00e9nt a konvertert

                      Foreground=\"{x:Bind Priority, Converter={StaticResource PriorityBrushConverter}}\"\n

                    A Brushok p\u00e9ld\u00e1nyos\u00edt\u00e1s\u00e1hoz haszn\u00e1ljuk a SolidColorBrush oszt\u00e1lyt, vagy haszn\u00e1lhatunk be\u00e9p\u00edtett ecseteket is C#-k\u00f3db\u00f3l (mint fentebb a ThemeResource-szal).

                    new SolidColorBrush(Colors.Red);\n\n(Brush)App.Current.Resources[\"ApplicationForegroundThemeBrush\"]\n
                    F\u00e9lk\u00f6v\u00e9r bet\u0171t\u00edpus

                    A bet\u0171jellemz\u0151ket a \"Font...\" nev\u0171 tulajdons\u00e1gok hat\u00e1rozz\u00e1k meg: FontFamily, FontSize, FontStyle, FontStretch \u00e9s FontWeight.

                    Pipa ikon l\u00e1that\u00f3s\u00e1ga

                    A pipa ikonhoz haszn\u00e1ljunk egy SymbolIcon-t, aminek az Symbol tulajdons\u00e1g\u00e1t \u00e1ll\u00edtsuk be Accept \u00e9rt\u00e9kre.

                    A pipa ikon megjelen\u00edt\u00e9sekor egy igaz-hamis \u00e9rt\u00e9ket kell \u00e1talak\u00edtani Visibility t\u00edpus\u00fara. Erre ugyan haszn\u00e1lhatn\u00e1nk konvertert is, de ez a konverzi\u00f3 annyira gyakori, hogy az x:Bind adatk\u00f6t\u00e9s be\u00e9p\u00edtetten konvert\u00e1lja a bool \u00e9rt\u00e9ket Visibility-re.

                    Pipa ikon igaz\u00edt\u00e1sa

                    A teend\u0151 c\u00edme \u00e9s a pipa ikon egy sorban kell elhelyezkedjenek (egyik balra, m\u00e1sik jobbra igaz\u00edtva). Ehhez egy tipp: pl. be lehet vetni egy egycell\u00e1s Grid-et. Grid-ben lehet olyat csin\u00e1lni, hogy egy cell\u00e1ba t\u00f6bb vez\u00e9rl\u0151t tesz\u00fcnk \"egym\u00e1sra\", melyek igaz\u00edt\u00e1sa k\u00fcl\u00f6n szab\u00e1lyozhat\u00f3. A m\u00e1sodik laboron \u00edgy oldottuk meg a ListView DataTemplate-ben a n\u00e9v \u00e9s a kor megjelen\u00edt\u00e9s\u00e9t.

                    D\u00e1tumok form\u00e1z\u00e1sa

                    A hat\u00e1rid\u0151 d\u00e1tum form\u00e1z\u00e1s\u00e1ra haszn\u00e1lhatunk szint\u00e9n konvertert vagy x:Bind alap\u00fa f\u00fcggv\u00e9ny k\u00f6t\u00e9st is, ahol a DateTime.ToString f\u00fcggv\u00e9ny\u00e9t k\u00f6tj\u00fck ki param\u00e9terezve.

                    Text=\"{x:Bind Deadline.ToString('yyyy.MM.dd', x:Null)}\"\n

                    A x:Null az\u00e9rt kell, mert a ToString f\u00fcggv\u00e9nynek a m\u00e1sodik param\u00e9ter\u00e9t is meg kell adni, de az lehet null is ebben az esetben.

                    Listaelemek k\u00f6z\u00f6tti hely

                    Az \u00fatmutat\u00f3 k\u00e9perny\u0151ment\u00e9s\u00e9n l\u00e1tszik, hogy a listaelemek k\u00f6z\u00f6tt f\u00fcgg\u0151legesen van kihagyott hely, a listaelemek \u00edgy j\u00f3l elk\u00fcl\u00f6n\u00fclnek. Alapesetben ez nincs \u00edgy. Szerencs\u00e9re a megold\u00e1s sor\u00e1n \u00fagyis kell DataTemplate-et alkalmazni az elemek megjelen\u00edt\u00e9s\u00e9re, \u00edgy ennek kicsi hangol\u00e1s\u00e1val (tipp: egyetlen Margin/Padding megad\u00e1sa) k\u00f6nnyed\u00e9n el\u00e9rhetj\u00fck, hogy a listaelemek k\u00f6z\u00f6tt legyen n\u00e9mi hely a jobb olvashat\u00f3s\u00e1g \u00e9rdek\u00e9ben.

                    2. feladat BEADAND\u00d3

                    Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol az egyik teend\u0151nek a list\u00e1ban a neve vagy le\u00edr\u00e1sa a NEPTUN k\u00f3dod legyen! (f2.png)

                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#3-feladat-uj-teendo-hozzaadasa","title":"3. feladat - \u00daj teend\u0151 hozz\u00e1ad\u00e1sa","text":"

                    A grid jobb oldal\u00e1n az 1. sorban a \"To-Do item\" sz\u00f6veg legyen l\u00e1that\u00f3, 25-\u00f6s bet\u0171m\u00e9rettel, v\u00edzszintesen balra, f\u00fcgg\u0151legesen pedig k\u00f6z\u00e9pre igaz\u00edtva, baloldalon 20 pixelnyi \u00fcres hellyel.

                    A fel\u00fcleten a Hozz\u00e1ad\u00e1s gombra kattintva jelenjen a 2. sorban egy \u0171rlap, ahol \u00faj teend\u0151t lehet felvenni.

                    Az \u0171rlap kin\u00e9zete legyen a k\u00f6vetkez\u0151:

                    Teend\u0151 szerkeszt\u0151 \u0171rlap

                    Az \u0171rlapban a k\u00f6vetkez\u0151 elemek legyenek egym\u00e1s alatt.

                    • C\u00edm: sz\u00f6veges beviteli mez\u0151
                    • Le\u00edr\u00e1s: magasabb sz\u00f6veges beviteli mez\u0151, fogadjon el sort\u00f6r\u00e9st (enter) is (AcceptsReturn=\"True\")
                    • Hat\u00e1rid\u0151: d\u00e1tumv\u00e1laszt\u00f3 (DatePicker) (Megj.: Ez\u00e9rt a vez\u00e9rl\u0151 miatt haszn\u00e1lunk a modellben DateTimeOffset t\u00edpust.)
                    • Priorit\u00e1s: leg\u00f6rd\u00fcl\u0151 lista (ComboBox), melyben a Priority felsorolt t\u00edpus \u00e9rt\u00e9kei szerepelnek
                    • K\u00e9sz\u00fclts\u00e9g: jel\u00f6l\u0151n\u00e9gyzet (CheckBox)
                    • Ment\u00e9s: gomb be\u00e9p\u00edtett accent st\u00edlussal (Style=\"{StaticResource AccentButtonStyle}\")

                    Az \u0171rlaphoz nem kell speci\u00e1lis, egyedi vez\u00e9rl\u0151t (pl. UserControl k\u00e9sz\u00edteni): egyszer\u0171en haszn\u00e1ljuk valamelyik, a feladathoz j\u00f3l illeszked\u0151 layout panel t\u00edpust.

                    N\u00e9h\u00e1ny fenti \u00e9s al\u00e1bb meghat\u00e1rozott k\u00f6vetelm\u00e9ny megval\u00f3s\u00edt\u00e1sa kapcs\u00e1n lentebb g\u00f6rgetve leny\u00edl\u00f3 mez\u0151kben n\u00e9mi ir\u00e1nymutat\u00e1st ad az \u00fatmutat\u00f3.

                    Tov\u00e1bbi funkcion\u00e1lis k\u00f6vetelm\u00e9nyek:

                    • Az \u0171rlap csak akkor legyen l\u00e1that\u00f3, ha a Hozz\u00e1ad\u00e1s gombra kattintottak, \u00e9s t\u0171nj\u00f6n el, ha a teend\u0151 ment\u00e9sre ker\u00fcl.
                    • A Ment\u00e9s gombra kattintva a felvitt adatok ker\u00fcljenek a list\u00e1ba, \u00e9s az \u0171rlap t\u0171nj\u00f6n el.
                    • A Hozz\u00e1ad\u00e1s gombra kattintva a list\u00e1ban \u00fcr\u00edts\u00fck ki az aktu\u00e1lisan kiv\u00e1lasztott elem jel\u00f6l\u00e9s\u00e9t (SelectedItem)
                    • Opcion\u00e1lis feladat: Az \u0171rlap legyen g\u00f6rgethet\u0151, ha a tartalma nem f\u00e9r ki a k\u00e9perny\u0151re (ScrollViewer haszn\u00e1lata).

                    Az \u0171rlap elrendez\u00e9se

                    • A TextBox, ComboBox \u00e9s DatePicker vez\u00e9rl\u0151k rendelkeznek egy Header tulajdons\u00e1ggal, melyben a vez\u00e9rl\u0151 feletti fejl\u00e9csz\u00f6veg megadhat\u00f3. A fejl\u00e9csz\u00f6vegek megad\u00e1s\u00e1hoz ezt haszn\u00e1ljuk, ne k\u00fcl\u00f6n TextBlock-ot!
                    • Az \u0171rlapon az elemek ne legyenek t\u00fal s\u0171r\u0171n egym\u00e1s alatt, legyen k\u00f6z\u00f6tt\u00fck kb. 15 pixel extra hely (erre remek\u00fcl alkalmazhat\u00f3 pl. a StackPanel Spacing tulajdons\u00e1ga).
                    • Az \u0171rlapnak \u00e1ll\u00edtsunk be egy j\u00f3l l\u00e1that\u00f3 keretet. Ezt nem az\u00e9rt tessz\u00fck, hogy szebb legyen a fel\u00fclet\u00fcnk, hanem az\u00e9rt, hogy j\u00f3l l\u00e1that\u00f3 legyen, pontosan hol helyezkedik el az \u0171rlapunk (alternat\u00edva lehetne a h\u00e1tt\u00e9rsz\u00edn\u00e9nek a megv\u00e1ltoztat\u00e1sa). Ezt a \"tr\u00fckk\u00f6t\" ideiglenesen is szoktuk alkalmazni a fel\u00fcletkialak\u00edt\u00e1s sor\u00e1n, ha nem egy\u00e9rtelm\u0171, pontosan mi hol helyezkedik el a fel\u00fcleten. Ehhez az \u0171rlap kont\u00e9ner BorderThickness tulajdons\u00e1g\u00e1t \u00e1ll\u00edtsuk 1-re, valamint a keret sz\u00edn\u00e9t (BorderBrush tulajdons\u00e1g) valamilyen j\u00f3l l\u00e1that\u00f3 sz\u00ednre (pl. LightGray-re).
                    • Az \u0171rlap baloldal\u00e1n, jobboldal\u00e1n, \u00e9s alj\u00e1n haszn\u00e1ljunk 8-as, tetej\u00e9n pedig 0-\u00e1s marg\u00f3t (ekkora hely legyen az \u0171rlap kerete \u00e9s a tartalmaz\u00f3ja k\u00f6z\u00f6tt, ak\u00e1rmekkor\u00e1ra is m\u00e9retezi a felhaszn\u00e1l\u00f3 fut\u00e1s k\u00f6zben az ablakot).
                    • Az \u0171rlap kerete, \u00e9s a benne lev\u0151 vez\u00e9rl\u0151k sz\u00e9le k\u00f6z\u00f6tt legyen alul \u00e9s fel\u00fcl 15, bal \u00e9s jobb oldalt 10 pixel szabad hely minden ir\u00e1nyban. Ehhez ne az \u0171rlapban lev\u0151 vez\u00e9rl\u0151k marg\u00f3it \u00e1ll\u00edtsuk egyes\u00e9vel, hanem az \u0171rlap kont\u00e9ner egy megfelel\u0151 tulajdons\u00e1g\u00e1t \u00e1ll\u00edtsuk be (mely azt szab\u00e1lyozza, mennyi hely van a sz\u00e9le, a bels\u0151 tartalma k\u00f6z\u00f6tt)!
                    • Az el\u0151z\u0151 k\u00e9t pont azt is jelenti, hogy az \u0171rlapnak, \u00e9s benne a sz\u00f6vegdobozoknak automatikusan m\u00e9retez\u0151dni\u00fck kell az ablakkal, ezt az al\u00e1bbi lenyithat\u00f3 szekci\u00f3 alatt megjelen\u0151 k\u00e9pek illusztr\u00e1lj\u00e1k.

                      Az \u0171rlap viselked\u00e9s\u00e9nek \u00e9s elv\u00e1rt m\u00e9retek illusztr\u00e1l\u00e1sa

                    Ment\u00e9s megval\u00f3s\u00edt\u00e1s\u00e1nak l\u00e9p\u00e9sei
                    1. Az \u0171rlapban l\u00e9v\u0151 adatokat egy \u00faj TodoItem objektumba gy\u0171jts\u00fck \u00f6ssze, melynek tulajdons\u00e1gait adatk\u00f6tj\u00fck (k\u00e9t ir\u00e1ny\u00faan!) a fel\u00fcleten. Vezess\u00fcnk be egy tulajdons\u00e1got ehhez EditedTodo n\u00e9ven. Ett\u0151l a pontt\u00f3l kezdve k\u00e9t megk\u00f6zel\u00edt\u00e9ssel dolgozhatunk:
                      1. Az EditedTodo alapesetben null. Amikor a felhaszn\u00e1l\u00f3 \u00faj to-do elem felv\u00e9tel\u00e9t kezdem\u00e9nyezi, akkor hozzuk l\u00e9tre az \u00faj EditedTodo objektumot, mely az adott \u00faj elem adatait t\u00e1rolja. Ment\u00e9skor ezt az objektumot tessz\u00fck bele a list\u00e1ba. \u00cdgy minden \u00faj elem felv\u00e9telekor az EditedTodo egy \u00faj objektumra hivatkozik.
                      2. Egy k\u00f6z\u00f6s EditedTodo objektumot haszn\u00e1lunk minden to-do elem felv\u00e9telekor. Ezt m\u00e1r az oldal l\u00e9trehoz\u00e1skor p\u00e9ld\u00e1nyos\u00edtjuk. Amikor a felhaszn\u00e1l\u00f3 \u00faj to-do elem felv\u00e9tel\u00e9t kezdem\u00e9nyezi (vagy a ment\u00e9s v\u00e9g\u00e9n), akkor gondoskodni kell az EditedTodo alap\u00e9rtelmezett \u00e9rt\u00e9kekkel val\u00f3 felt\u00f6lt\u00e9s\u00e9r\u0151l. Ment\u00e9skor egy m\u00e1solatot kell k\u00e9sz\u00edteni r\u00f3la \u00e9s ezt kell a k\u00f6z\u00f6s list\u00e1ba beletenni.
                    2. A k\u00f6vezkez\u0151kben a fenti 1. megk\u00f6zel\u00edt\u00e9s l\u00e9p\u00e9seire adunk ir\u00e1nymutat\u00e1st, de mindenk\u00e9ppen \u00e9rdemes el\u0151sz\u00f6r \u00f6n\u00e1l\u00f3an pr\u00f3b\u00e1lkozni.
                    3. Az EditedTodo kezd\u0151\u00e9rt\u00e9ke legyen null, illetve a Hozz\u00e1ad\u00e1s gombra kattintva legyen p\u00e9ld\u00e1nyos\u00edtva az EditedTodo.
                    4. A ment\u00e9s sor\u00e1n a Todos list\u00e1hoz adjuk hozz\u00e1 a szerkesztett teend\u0151 objektumot. Gondoljunk arra, hogy az adatk\u00f6t\u00e9seknek friss\u00fclni\u00fck kell a fel\u00fcleten a lista tartalm\u00e1nak v\u00e1ltoz\u00e1sa sor\u00e1n (ehhez az adataink t\u00e1rol\u00e1s\u00e1n kell v\u00e1ltoztatni).
                    5. A ment\u00e9s sor\u00e1n az EditedTodo property-t nullozzuk ki. Ezt annak \u00e9rdek\u00e9ben, tessz\u00fck, hogy a k\u00f6vetkez\u0151 to-do elem felv\u00e9telekor az adatk\u00f6t\u00e9s miatt \u00fcresek legyenek az \u0171rlapon a vez\u00e9rl\u0151k, ne a kor\u00e1bbi to-do elem adatai legyenek rajta. Gondoljuk \u00e1t, ez el\u00e9g lesz-e a megold\u00e1shoz? Pr\u00f3b\u00e1ljuk is ki a megold\u00e1sunkat! Amikor az EditedTodo tulajdons\u00e1got \u00e1ll\u00edtjuk, a k\u00f6t\u00f6tt vez\u00e9rl\u0151knek friss\u00fclni\u00fck kell. Mire van ehhez sz\u00fcks\u00e9g? (Tipp: itt most nem az \u00e9rdekel minket, hogy az EditedTodo \u00e1ltal hivatkozott TodoItem tulajdons\u00e1gai, pl. Title, Description v\u00e1ltoznak, hanem a MainPage oszt\u00e1ly EditedTodo tulajdons\u00e1ga v\u00e1ltozik: ennek megfelel\u0151en az EditedTodo-t tartalmaz\u00f3 oszt\u00e1lyban kell a megfelel\u0151 interf\u00e9szt megval\u00f3s\u00edtani).
                    Az \u0171rlap l\u00e1that\u00f3s\u00e1g szab\u00e1lyoz\u00e1sa

                    Ha a fentieknek megfelel\u0151en dolgoztunk, az \u0171rlapunk pontosan akkor kell l\u00e1that\u00f3 legyen, amikor az EditedTodo \u00e9rt\u00e9ke nem null (gondoljuk \u00e1t, hogy val\u00f3ban \u00edgy van). Erre \u00e9p\u00edtve t\u00f6bb megold\u00e1st is kidolgozhatunk. A legegyszer\u0171bb a klasszikus x:Bind tulajdons\u00e1g alap\u00fa adatk\u00f6t\u00e9s alkalmaz\u00e1sa:

                    1. Vezess\u00fcnk be egy \u00faj tulajdons\u00e1got a MainPage oszt\u00e1lyunkban (pl. IsFormVisible n\u00e9ven, bool t\u00edpussal).
                    2. Ez pontosan akkor legyen igaz, amikor az EditedTodo nem null. Ennek a karbantart\u00e1sa a mi feladatunk, pl. az EditedTodo setter\u00e9ben.
                    3. Ezt a tulajdons\u00e1got lehet adatk\u00f6tni az \u0171rlapunkat reprezent\u00e1l\u00f3 kont\u00e9ner l\u00e1that\u00f3s\u00e1g\u00e1hoz (Visibility tulajdons\u00e1g). Igaz, hogy a t\u00edpusuk nem egyezik, de WinUI alatt van automatikus konverzi\u00f3 a bool \u00e9s Visibility t\u00edpusok k\u00f6z\u00f6tt.
                    4. Gondoljunk arra is, hogy amikor a forr\u00e1s tulajdons\u00e1g (IsFormVisible) v\u00e1ltozik, a hozz\u00e1 k\u00f6t\u00f6tt c\u00e9l tulajdons\u00e1got (vez\u00e9rl\u0151 l\u00e1that\u00f3s\u00e1g) eset\u00fcnkben mindig friss\u00edteni kell. Mire van ehhez sz\u00fcks\u00e9g? (Tipp: a tulajdons\u00e1got k\u00f6zvetlen\u00fcl tartalmaz\u00f3 oszt\u00e1lynak - gondoljuk \u00e1t, eset\u00fcnkben ez melyik oszt\u00e1ly - egy megfelel\u0151 interf\u00e9szt meg kell val\u00f3s\u00edtania stb.)
                    Alternat\u00edv lehet\u0151s\u00e9gek a megold\u00e1sra

                    Egy\u00e9b alternat\u00edv\u00e1k alkalmaz\u00e1sa is lehets\u00e9ges (csak \u00e9rdekess\u00e9gk\u00e9ppen, de ne ezeket alkalmazzuk a megold\u00e1s sor\u00e1n):

                    1. F\u00fcggv\u00e9ny alap\u00fa adatk\u00f6t\u00e9s megval\u00f3s\u00edt\u00e1sa, de eset\u00fcnkben ez k\u00f6r\u00fclm\u00e9nyesebb lenne.
                      • A x:Bind alapon k\u00f6t\u00f6tt f\u00fcggv\u00e9nynek a megjelen\u00edt\u00e9s \u00e9s elrejt\u00e9shez az EditedTodo property null vagy nem null \u00e9rt\u00e9k\u00e9t kell konvert\u00e1lni Visibility-re.
                      • Az adatk\u00f6t\u00e9s sor\u00e1n a FallbackValue='Collapsed' be\u00e1ll\u00edt\u00e1st is haszn\u00e1lnunk kell, mert sajnos az x:Bind alap\u00e9rtelmezetten nem h\u00edvja meg a f\u00fcggv\u00e9nyt, ha az \u00e9rt\u00e9k null.
                      • A k\u00f6t\u00f6tt f\u00fcggv\u00e9nynek param\u00e9terben meg kell adni azt a tulajdons\u00e1got, melynek v\u00e1ltoz\u00e1sa eset\u00e9n az adatk\u00f6t\u00e9st friss\u00edteni kell, illetve a tulajdons\u00e1gra vonatkoz\u00f3 v\u00e1ltoz\u00e1s\u00e9rtes\u00edt\u00e9st itt is meg kell val\u00f3s\u00edtani.
                    2. Konverter alkalmaz\u00e1sa.
                    Priorit\u00e1sok list\u00e1ja

                    A ComboBox-ban a Priority felsorolt t\u00edpus \u00e9rt\u00e9keit jelen\u00edts\u00fck meg. Ehhez haszn\u00e1lhatjuk a Enum.GetValues f\u00fcggv\u00e9nyt, amihez k\u00e9sz\u00edts\u00fcnk egy tulajdons\u00e1got a MainPage.xaml.cs-ben.

                    public List<Priority> Priorities { get; } = Enum.GetValues(typeof(Priority)).Cast<Priority>().ToList();\n

                    A ComboBox ItemsSource tulajdons\u00e1g\u00e1hoz k\u00f6ss\u00fck az Priorities list\u00e1t.

                    <ComboBox ItemsSource=\"{x:Bind Priorities}\" />\n

                    A fenti p\u00e9ld\u00e1ban az ItemsSource csak azt hat\u00e1rozza meg, hogy milyen elemek jelenjenek meg a ComboBox list\u00e1j\u00e1ban. De ez semmit nem mond arr\u00f3l, hogy a ComboBox kiv\u00e1lasztott elem\u00e9t mihez kell k\u00f6tni. Ehhez sz\u00fcks\u00e9g van m\u00e9g egy adatk\u00f6t\u00e9sre. Laboron ez nem szerepelt, el\u0151ad\u00e1sanyagban pl. a SelectedItem-re \u00e9rdemes r\u00e1keresni (minden el\u0151fordul\u00e1s\u00e1t \u00e9rdemes megn\u00e9zni).

                    N\u00e9h\u00e1ny fontosabb vez\u00e9rl\u0151 tulajdons\u00e1g
                    • A CheckBox vez\u00e9rl\u0151 IsChecked (\u00e9s nem a Checked!) tulajdons\u00e1ga. A mellette jobbra megjelen\u0151 sz\u00f6veg a Content tulajdons\u00e1g\u00e1val adhat\u00f3 meg.
                    • DatePicker vez\u00e9rl\u0151 Date tulajdons\u00e1ga
                    Furcsa, adatk\u00f6t\u00e9shez kapcsol\u00f3d\u00f3 NullReferenceException

                    Ha egy \"megfoghatatlannak\" t\u0171n\u0151 NullReferenceException-t kapsz az \u00faj elem felv\u00e9telekor, akkor ellen\u0151rizd, hogy a ComboBox eset\u00e9ben a SelectedValue-t k\u00f6t\u00f6tted-e esetleg a SelectedItem helyett (a SelectedItem haszn\u00e1land\u00f3).

                    3. feladat BEADAND\u00d3

                    Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol az \u00faj teend\u0151 felv\u00e9tele l\u00e1that\u00f3 m\u00e9g ment\u00e9s el\u0151tt! (f3.1.png)

                    Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol az el\u0151z\u0151 k\u00e9pen l\u00e9v\u0151 teend\u0151 a list\u00e1ba ker\u00fclt \u00e9s elt\u0171nt az \u0171rlap! (f3.2.png)

                    Fontos krit\u00e9riumok

                    Az al\u00e1bbiakban megadunk n\u00e9h\u00e1ny fontos krit\u00e9riumot, melyek mindenk\u00e9ppen felt\u00e9telei a h\u00e1zi feladat elfogad\u00e1s\u00e1nak:

                    • A feladatki\u00edr\u00e1s kik\u00f6t\u00f6tte, hogy a list\u00e1ban \u00e9s az \u0171rlapon lev\u0151 vez\u00e9l\u0151k eset\u00e9ben is adatk\u00f6t\u00e9ssel kell dolgozni. Olyan megold\u00e1s nem elfogadhat\u00f3, mely ezt megker\u00fcli. \u00cdgy p\u00e9ld\u00e1ul nem lehet a code behind f\u00e1jlban (MainPage.xaml.cs) olyan k\u00f3d, mely az \u0171rlapokon lev\u0151 vez\u00e9rl\u0151k tulajdons\u00e1gait (pl. TextBox Text tulajdons\u00e1ga) k\u00f6zvetlen\u00fcl k\u00e9rdezi le vagy \u00e1ll\u00edtja.
                    • Az el\u0151z\u0151 pont al\u00f3l k\u00e9t kiv\u00e9tel van:
                      • A ListView SelectedItem tulajdons\u00e1ga k\u00f6zvetlen\u00fcl \u00e1ll\u00edtand\u00f3.
                      • Az \u0171rlap l\u00e1that\u00f3s\u00e1g\u00e1nak szab\u00e1lyoz\u00e1sa adatk\u00f6t\u00e9s n\u00e9lk\u00fcl is elfogadhat\u00f3 (b\u00e1r nem a legszebb megold\u00e1s, \u00e9s a gyakorl\u00e1s kedv\u00e9\u00e9rt is \u00e9rdemesebb adatk\u00f6t\u00e9ssel dolgozni).
                    • Amikor egy \u00faj to-do elem felv\u00e9tele t\u00f6rt\u00e9nik, \u00e9s kor\u00e1bban m\u00e1r t\u00f6rt\u00e9nt egy ilyen elem felv\u00e9tele, akkor a kor\u00e1bbi elem adatai NEM lehetnek benne az \u0171rlap vez\u00e9rl\u0151iben.

                    Opcion\u00e1lis gyakorl\u00f3 feladatok

                    Opcion\u00e1lis gyakorl\u00f3 feladat 1 - \u0170rlap g\u00f6rgethet\u0151v\u00e9 t\u00e9tele

                    Ehhez mind\u00f6ssze be kell csomagolni az \u0171rlapot egy ScrollViewer vez\u00e9rl\u0151be (illetve ne feledkezz\u00fcnk meg arr\u00f3l, hogy \u00edgy m\u00e1r ez lesz a legk\u00fcls\u0151 elem a grid cell\u00e1ban, \u00edgy r\u00e1 vonatkoz\u00f3an kell megadni a gridbeli poz\u00edci\u00f3t). Ha ezt megval\u00f3s\u00edtod, benne lehet a beadott megold\u00e1sodban.

                    Opcion\u00e1lis gyakorl\u00f3 feladat 2 - Fix sz\u00e9less\u00e9g\u0171 \u0171rlap

                    Jelen megold\u00e1sunkban az \u0171rlap automatikusan m\u00e9retez\u0151dik az ablakkal. J\u00f3 gyakorl\u00e1si lehet\u0151s\u00e9g ennek olyan \u00e1talak\u00edt\u00e1sa, mely esetben az \u0171rlap fix sz\u00e9less\u00e9g\u0171 (pl. 500 pixel) \u00e9s olyan magass\u00e1g\u00fa, mint a benne lev\u0151 elemek \u00f6ssz magass\u00e1ga. Ha az \u0171rlap eset\u00e9n StackPanellel dolgozt\u00e1l, ehhez mind\u00f6ssze h\u00e1rom attrib\u00fatumot kell felvenni vagy megv\u00e1ltoztatni. Ezt a viselked\u00e9st az al\u00e1bbi anim\u00e1lt k\u00e9p illusztr\u00e1lja. L\u00e9nyeges, hogy beadni a kor\u00e1bbi megold\u00e1st kell, nem ez az opcion\u00e1lis feladatban le\u00edrt viselked\u00e9st!

                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#4-opcionalis-feladat-3-imsc-pontert-teendo-szerkesztese","title":"4. Opcion\u00e1lis feladat 3 IMSc pont\u00e9rt - Teend\u0151 szerkeszt\u00e9se","text":"

                    Val\u00f3s\u00edtsd meg a teend\u0151k szerkeszt\u00e9s\u00e9nek lehet\u0151s\u00e9g\u00e9t az al\u00e1bbiak szerint:

                    • A fel\u00fcleten a teend\u0151k list\u00e1ban az elemre kattintva, az adott teend\u0151 adatai a szerkeszt\u0151 fel\u00fcleten (a kor\u00e1bbi feladatban bevezetett \u0171rlapon) ker\u00fcljenek megjelen\u00edt\u00e9sre, ahol azok \u00edgy szerkeszthet\u0151ek \u00e9s menthet\u0151ek lesznek.
                    • A ment\u00e9s sor\u00e1n a list\u00e1ban a szerkesztett teend\u0151 adatai friss\u00fcljenek, \u00e9s az \u0171rlap t\u0171nj\u00f6n el.
                    Megold\u00e1si tippek
                    • \u00c9rdemes karbantartani a teend\u0151k egyedi azonos\u00edt\u00f3j\u00e1t a besz\u00far\u00e1s sor\u00e1n, hogy meg tudjuk k\u00fcl\u00f6nb\u00f6ztetni ment\u00e9skor, szerkeszt\u00e9s vagy besz\u00far\u00e1s esete \u00e1ll fenn. Pl. besz\u00far\u00e1s eset\u00e9n haszn\u00e1lhatjuk a -1 \u00e9rt\u00e9ket, melyet ment\u00e9s sor\u00e1n lecser\u00e9l\u00fcnk az eddig haszn\u00e1ltakn\u00e1l eggyel nagyobb sz\u00e1mra. De tegy\u00fck fel, hogy a -1 is egy olyan \u00e9rt\u00e9k, mellyel rendelkezhet egy \u00e9rv\u00e9nyes to-do objektum. Mit lehet ekkor tenni? A TodoItem oszt\u00e1lyban az Id t\u00edpus\u00e1t alak\u00edtsuk \u00e1t int?-re. A ?-lel az \u00e9rt\u00e9k t\u00edpusok (int, bool, char, enum, struct stb.) is felvehetnek null \u00e9rt\u00e9ket. Ezeket nullable \u00e9rt\u00e9k t\u00edpusoknak (nullable value types) nevezz\u00fck. Ezek a Nullable<T> .NET strukt\u00far\u00e1ra k\u00e9pz\u0151dnek le ford\u00edt\u00e1s sor\u00e1n, melyek tartalmazz\u00e1k az eredeti v\u00e1ltoz\u00f3t, illetve egy flag-et, mely jelzi, ki van-e t\u00f6ltve az \u00e9rt\u00e9k, vagy sem. B\u0151vebben itt \u00e9s itt lehet ezekr\u0151l olvasni. Alkalmazzuk ezt a megold\u00e1s sor\u00e1n.
                    • A lista elemre kattint\u00e1shoz a ListView ItemClick esem\u00e9ny\u00e9t c\u00e9lszer\u0171 haszn\u00e1lni, miut\u00e1n bekapcsoltuk a IsItemClickEnabled tulajdons\u00e1got a ListView-n. Az \u00fajonnan kiv\u00e1lasztott listaelem kapcs\u00e1n inform\u00e1ci\u00f3t az esem\u00e9nykezel\u0151 ItemClickEventArgs param\u00e9ter\u00e9ben kapunk.
                    • A szerkesztend\u0151 adatok kezel\u00e9s\u00e9re t\u00f6bb megold\u00e1s is elk\u00e9pzelhet\u0151, ezekb\u0151l az egyik:
                      • Az EditedTodo property-t \u00e1ll\u00edtsuk be a szerkesztett teend\u0151re a kattint\u00e1skor.
                      • A ment\u00e9s gombra kattintva a Todos list\u00e1ban cser\u00e9lj\u00fck le a szerkesztett teend\u0151t az EditedTodo \u00e9rt\u00e9k\u00e9re. Val\u00f3j\u00e1ban ugyanazt az elemet cser\u00e9lj\u00fck le \u00f6nmag\u00e1ra, de a ListView \u00edgy friss\u00fclni tud.

                    4. iMSc feladat BEADAND\u00d3

                    Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol egy megl\u00e9v\u0151 elemre kattintva kit\u00f6lt\u0151dik az \u0171rlap! (f4.imsc.1.png)

                    Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol az el\u0151z\u0151 k\u00e9pen kiv\u00e1lasztott teend\u0151 ment\u00e9s hat\u00e1s\u00e1ra friss\u00fcl a list\u00e1ban! (f4.imsc.2.png)

                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/#beadas","title":"Bead\u00e1s","text":"

                    Ellen\u0151rz\u0151lista ism\u00e9tl\u00e9sk\u00e9ppen:

                    • A repository gy\u00f6k\u00e9rmapp\u00e1j\u00e1ban tal\u00e1lhat\u00f3 neptun.txt f\u00e1jlba \u00edrd bele a Neptun k\u00f3dod, csupa nagybet\u0171vel. A f\u00e1jlban csak ez a hat karakter legyen, semmi m\u00e1s.
                    • A GitHub-r\u00f3l le\u00f6lt\u00f6tt kiindul\u00f3 solutionben/projektekben kell dolgozni, nem \u00fajonnan l\u00e9trehozottban.
                    • Am\u00edg nem vagy rutinos a Visual Studio Git szolg\u00e1ltat\u00e1sainak haszn\u00e1lat\u00e1ban, a push-t k\u00f6vet\u0151en (legk\u00e9s\u0151bb akkor, amikor a h\u00e1zi feladatot beadottnak tekintj\u00fck) c\u00e9lszer\u0171 ellen\u0151rizni a GitHub webes fel\u00fclet\u00e9n a repository-ban a f\u00e1jlokra val\u00f3 r\u00e1pillant\u00e1ssal, hogy val\u00f3ban minden v\u00e1ltoztat\u00e1st felt\u00f6lt\u00f6tt\u00e9l-e.
                    • A GitHub fel\u00fclet\u00e9n ellen\u0151rizd a push-t k\u00f6vet\u0151en, hogy a GitHub Action alap\u00fa el\u0151ellen\u0151rz\u0151 hiba n\u00e9lk\u00fcl lefutott-e.
                    • L\u00e9nyeges, hogy a feladatok csak akkor ker\u00fclnek elfogad\u00e1sra, ha teljesen elk\u00e9sz\u00fclnek, \u00e9s minden tekintetben teljes\u00edtik a k\u00f6vetelm\u00e9nyeket. Nem fordul\u00f3 k\u00f3d, illetve r\u00e9szleges megold\u00e1s elfogad\u00e1s\u00e1ban nem \u00e9rdemes b\u00edzni.
                    • Term\u00e9szetesen saj\u00e1t munk\u00e1t kell beadni (hiszen \u00e9rt\u00e9kel\u00e9sre ker\u00fcl).
                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/","title":"3. HA - Entwurf der Benutzeroberfl\u00e4che","text":""},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

                    Die kleine Software, die in der Hausaufgabe verwirklicht werden soll, ist eine einfache Anwendung zur Aufgabenverwaltung, mit der Benutzer Aufgaben auflisten, erstellen und \u00e4ndern k\u00f6nnen.

                    Die eigenst\u00e4ndige Aufgabe baut auf dem auf, was in den XAML-Vorlesungen vermittelt wurde. Den praktischen Hintergrund f\u00fcr die Aufgaben liefert das Labor 3 - Entwurf der Benutzeroberfl\u00e4che.

                    Darauf aufbauend k\u00f6nnen die Aufgaben dieser Selbst\u00fcbung mit Hilfe der k\u00fcrzeren Leitf\u00e4den, die auf die Aufgabenbeschreibung folgen (manchmal standardm\u00e4\u00dfig eingeklappt), selbst\u00e4ndig bearbeitet werden.

                    Das Ziel der Hausaufgabe:

                    • \u00dcben der Verwendung der Oberfl\u00e4chenbeschreibungssprache XAML
                    • \u00dcben der Verwendung grundlegender Steuerelemente (Tabelle, Taste, Textfeld, Listen)
                    • Ereignisgesteuerte Verwaltung von Oberfl\u00e4cheninteraktionen
                    • Anzeige von Daten auf der Oberfl\u00e4che mit Datenbindung

                    Die erforderliche Entwicklungsumgebung wird hier beschrieben.

                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#das-verfahren-der-eingabe","title":"Das Verfahren der Eingabe","text":"

                    Auf das Moodle soll ein ZIP-Archiv hochgeladen werden, das die folgenden Anforderungen entspricht:

                    • Die Aufgaben sind aufeinander basiert, deshalb ist es gen\u00fcgend den resultierenden Quellcode am Ende der letzten Aufgabe hochzuladen (Visual Studio Solution Verzeichnis). Der Name des Verzeichnisses soll \"TodoXaml_NEPTUN\" sein (wo NEPTUN Ihre Neptun-Code ist).
                    • Wir erwarten keine schriftliche Begr\u00fcndung oder Beschreibung, aber die komplexe Codeteile sollen mit Kommentaren versehen werden
                    • Das ZIP-Archiv darf die Ausgangsdaten (.exe) und die tempor\u00e4ren Dateien nicht enthalten. Um diese Best\u00e4nde zu l\u00f6schen, Visual Studio soll ge\u00f6ffnet werden und in dem Solution Explorer Rechtsklick an dem \u201eClean Solution\u201d Men\u00fcelement. Das manuelle L\u00f6schen von den \"obj\" und \"bin\" Verzeichnissen kann auch n\u00f6tig sein.
                    • In den Aufgaben werden Sie aufgefordert, einen Screenshot von einem Teil Ihrer L\u00f6sung zu machen, da dies beweist, dass Sie Ihre L\u00f6sung selbst erstellt haben. Der erwartete Inhalt der Screenshots ist immer in der Aufgabe angegeben. Die Screenshots sollten als Teil der L\u00f6sung eingegeben, also innerhalb dem ZIP-Archiv auf das Moodle hochgeladen werden. Wenn Sie Inhalte im Screenshot haben, die Sie nicht hochladen m\u00f6chten, k\u00f6nnen Sie diese aus dem Screenshot ausblenden.
                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#vorbedingungen","title":"Vorbedingungen","text":"

                    MVVM-Modell - nicht benutzen! Verwenden Sie in dieser Hausaufgabe NICHT das MVVM-Muster (auch nicht in den sp\u00e4teren Teilaufgaben), f\u00fchren Sie NICHT die Klasse ViewModel ein. MVVM wird das Thema einer sp\u00e4teren Hausaufgabe sein.

                    Layout - Einfachheit Wie im Allgemeinen, auch in dieser Hausaufgabe sollte das grundlegende Layout der Seite mit Grid gestaltet werden. Bei der Gestaltung der einzelnen internen Abschnitte sollten Sie jedoch darauf achten, dass sie einfach gehalten sind: Wo StackPanelverwendet werden kann, sollten Sie nicht Gridverwenden.

                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#aufgabe-1-modellentwurf-und-testdaten","title":"Aufgabe 1. - Modellentwurf und Testdaten","text":"

                    Erstellen Sie ein neuen Projekt mit Visual Studio (WinUI 3 Projekt, Blank App, Packaged (WinUI 3 in Desktop) type), und addieren Sie einen Ordner namens Models zu dem erzeugten Projekt. Erstellen Sie die Klasse und den Enum-Typ, die in der folgenden Abbildung gezeigt werden, im Ordner Models. Die Klasse TodoItem enth\u00e4lt die Details zu den Aufgaben, f\u00fcr die Priorit\u00e4t wird ein aufgelisteter Typ erstellt.

                    Beide Typen sollten \u00f6ffentlich sein ( class und enum mit public vorangestellt), da Ihr sonst sp\u00e4ter bei der \u00dcbersetzung einen Fehler \"Inconsistent accessibility\" erhalten w\u00fcrden.

                    Auf der Seite MainPage wird eine Liste der zu erledigenden Aufgaben angezeigt. Jetzt verwenden Sie speicherinterne Testdaten, die in MainPage.xaml.cs erstellt wurden: Hier f\u00fchren Sie eine Eigenschaft List<TodoItem> mit dem Namen Todos ein (die sp\u00e4ter an das Steuerelement ListView auf der Benutzeroberfl\u00e4che gebunden wird). Diese Liste enth\u00e4lt TodoItem Objekte.

                    MainPage.xaml.cs
                    public List<TodoItem> Todos { get; set; } = new()\n{\n    new TodoItem()\n    {\n        Id = 3,\n        Title = \"Add Neptun code to neptun.txt\",\n        Description = \"NEPTUN\",\n        Priority = Priority.Normal,\n        IsDone = false,\n        Deadline = new DateTime(2024, 11, 08)\n    },\n    new TodoItem()\n    {\n        Id = 1,\n        Title = \"Buy milk\",\n        Description = \"Should be lactose and gluten free!\",\n        Priority = Priority.Low,\n        IsDone = true,\n        Deadline = DateTimeOffset.Now + TimeSpan.FromDays(1)\n    },\n    new TodoItem()\n    {\n        Id = 2,\n        Title = \"Do the Computer Graphics homework\",\n        Description = \"Ray tracing, make it shiny and gleamy! :)\",\n        Priority = Priority.High,\n        IsDone = false,\n        Deadline = new DateTime(2024, 11, 08)\n    },\n};\n
                    Erkl\u00e4rung des obigen Codes

                    In dem obigen Code sind mehrere moderne C#-Sprachelemente kombiniert:

                    • Dies ist eine automatisch implementierte Eigenschaft (siehe Labor 2 \"auto-implemented property\").
                    • Die Eigenschaft hat einen Anfangswert.
                    • Der Typ wird nicht nach new angegeben, da der Compiler ihn ableiten kann (siehe Labor 2 \"Target-typed new expressions\").
                    • Die Sammlungselemente werden in {} aufgelistet (siehe Labor 2 \"Collection initializer syntax\").

                    MainPage Klasse

                    W\u00e4hrend der Hausaufgabe werden Sie in der Klasse MainPage arbeiten, die aus der eingebauten Klasse Page abgeleitet ist. Die Klasse Page hilft Ihnen, zwischen den Seiten innerhalb des Fensters zu navigieren. Obwohl sie in dieser Hausaufgaa\u00f3be nicht verwendet wird, lohnt es sich, sich an ihre Verwendung zu gew\u00f6hnen. Da unsere Anwendung aus einer einzigen Seite besteht, instanziieren wir einfach ein Objekt MainPage im Hauptfenster (Sie k\u00f6nnen es sich in der Datei MainWindow.xaml ansehen).

                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#aufgabe-2-seitenlayout-liste-anzeigen","title":"Aufgabe 2 - Seitenlayout, Liste anzeigen","text":""},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#layout","title":"Layout","text":"

                    Unter MainPage.xamlerstellen Sie die Oberfl\u00e4che, auf der die Liste der Aufgaben angezeigt wird.

                    Die zu erstellende Anwendung mit einer Benutzeroberfl\u00e4che f\u00fcr Listen

                    Wie in der obigen Abbildung mit den drei Aufgaben zu sehen ist, werden die Aufgabendetails untereinander angezeigt, die Priorit\u00e4t der Aufgaben wird durch Farben angezeigt, und neben den erledigten Aufgaben werden mit einem H\u00e4kchen rechts bezeichnet.

                    Die Elemente sind in der folgenden Struktur auf der Oberfl\u00e4che angeordnet:

                    • Verwenden Sie in MainPageeine Gridmit zwei Zeilen und zwei Spalten von Elementen. Die erste Spalte sollte eine feste Breite haben (z. B: 300 px) und die zweite nimmt den restlichen Platz ein.
                    • Die erste Zeile der ersten Spalte sollte ein CommandBar Steuerelement mit einer Adresse und einer Taste enthalten. Das folgende Beispiel ist hilfreich:

                      <CommandBar VerticalContentAlignment=\"Center\"\n            Background=\"{ThemeResource AppBarBackgroundThemeBrush}\"\n            DefaultLabelPosition=\"Right\">\n    <CommandBar.Content>\n        <TextBlock Margin=\"12,0,0,0\"\n                   Style=\"{ThemeResource SubtitleTextBlockStyle}\"\n                   Text=\"To-Dos\" />\n    </CommandBar.Content>\n\n    <AppBarButton Icon=\"Add\"\n                  Label=\"Add\" />\n</CommandBar>\n

                      ThemeResource

                      Die ThemeResourceim Beispiel kann verwendet werden, um die Farben und Stile einzustellen, die je nach Thema der Oberfl\u00e4che variieren werden. Zum Beispiel hat AppBarBackgroundThemeBrush die richtige Hintergrundfarbe je nach dem Thema der Oberfl\u00e4che (hell/dunkel).

                      Einzelheiten finden Sie in der Dokumentation und die Beispiele in WinUI 3 Gallery App Colors.

                    Wenn Sie Ihre Arbeit richtig gemacht haben, sollte bei der Ausf\u00fchrung der Anwendung CommandBaran der richtigen Stelle erscheinen.

                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#liste-anzeigen","title":"Liste anzeigen","text":"

                    Stellen Sie in der Zelle unter CommandBar in einer Liste (ListView) die Aufgaben mit folgendem Inhalt untereinander. Die Daten sollen \u00fcber Datenverbindung in der Benutzeroberfl\u00e4che angezeigt werden (die Elemente sollen \u00fcber Datenverbindung aus der zuvor vorgestellten Liste Todos angezeigt werden).

                    • Titel der Aufgabe
                      • Fette Schriftart (SemiBold)
                      • Gef\u00e4rbt nach Priorit\u00e4t
                        • Hohe Priorit\u00e4t: ein roter Farbton
                        • Normale Priorit\u00e4t: eingebaute Vordergrundfarbe
                        • Niedrige Priorit\u00e4t: ein blauer Farbton
                    • Ein H\u00e4kchensymbol rechts neben dem Aufgabentitel, wenn die Aufgabe fertig ist
                    • Beschreibung der Aufgabe
                    • Abgabetermin im Format yyyy.MM.dd
                    • Der Hintergrund von ListView sollte derselbe sein wie der von CommandBar, so dass sie einen durchgehenden Balken auf der linken Seite bilden.
                    Elemente in der Liste

                    \u00dcberlegen Sie immer, ob Sie Daten an ein Objekt oder an eine Liste binden, und verwenden Sie die entsprechende Technik! Bei dieser Hausaufgabe ist es nicht sicher, dass sie in der Reihenfolge kommen, in der sie im Labor waren!\"

                    Bedingte Einf\u00e4rbung

                    Sie k\u00f6nnen einen Konverter oder eine Funktionsbindung auf Basis von x:Bind verwenden, um die Adresse einzuf\u00e4rben.

                    • Beispiel f\u00fcr Funktionsbindung auf der Grundlage von \"x:Bind\":

                      Foreground=\"{x:Bind local:MainPage.GetForeground(Priority)}\"\n

                      Hier ist \"GetForeground\" eine \u00f6ffentliche statische Funktion in der Klasse \"MainPage\", die das Objekt \"Brush\" mit der entsprechenden Farbe auf der Grundlage des aufgelisteten Typs \"Priorit\u00e4t\" zur\u00fcckgibt. Normalerweise w\u00e4re es nicht wichtig, dass die Funktion statisch ist, aber da wir die Datenverbindung in einem DataTemplate verwenden, ist der Kontext von x:Bind nicht die Seiteninstanz, sondern das Listenelement.

                    • Beispiel f\u00fcr die Verwendung des Konverters:

                      Erstellen Sie eine Konverterklasse in einem Ordner Converters, die die Schnittstelle IValueConverter implementiert.

                      public class PriorityBrushConverter : IValueConverter\n{\n    public object Convert(object value, Type targetType, object parameter, string language)\n    {\n        // TODO R\u00fcckgabe einer SolidColorBrush-Instanz\n    }\n\n    public object ConvertBack(object value, Type targetType, object parameter, string language)\n    {\n        throw new NotImplementedException();\n    }\n}\n

                      Instanziierung des Konverters unter den Ressourcen der MainPage.

                      xmlns:c=\"using:TodoXaml.Converters\"\n\n<Page.Resources>\n    <c:PriorityBrushConverter x:Key=\"PriorityBrushConverter\" />\n</Page.Resources>\n

                      Verwendung des Konverters als statische Ressource in der Datenverbindung

                      ``xml Foreground=\"{x:Bind Priority, Converter={StaticResource PriorityBrushConverter}}\" ```

                    Um die Pinsel (Brush) zu instanziieren, verwenden Sie die Klasse SolidColorBrush, oder k\u00f6nnen Sie auch eingebaute Pinsel aus C#-Code (wie mit ThemeResource oben) benutzen.

                    new SolidColorBrush(Colors.Red);\n\n(Brush)App.Current.Resources[\"ApplicationForegroundThemeBrush\"]\n
                    Fette Schriftart

                    Schriftattribute k\u00f6nnen unter die Eigenschaften namens \"Font...\" eingestellt werden: FontFamily , FontSize, FontStyle, FontStretch und FontWeight.

                    Sichtbarkeit des H\u00e4kchen-Symbol

                    F\u00fcr das H\u00e4kchen-Symbol verwenden Sie SymbolIcon, wobei die Eigenschaft Symbol auf Accept gesetzt ist.

                    Wenn das H\u00e4kchen-Symbol angezeigt wird, muss ein Wahr-Falsch-Wert in einen Sichtbarkeit-Typ umgewandelt werden. Man k\u00f6nnte daf\u00fcr einen Konverter verwenden, aber diese Konvertierung ist so \u00fcblich, dass in der Datenverbindung x:Bind die Konvertierung von bool in Sichtbarkeit bereits eingebaut ist.

                    Ausrichtung des H\u00e4kchen-Symbols

                    Der Titel der Aufgabe und das H\u00e4kchen-Symbol m\u00fcssen ausgerichtet sein (eines nach links und eines nach rechts). Hier ein Tipp: Sie k\u00f6nnen z. B. eine einzelne Zelle verwenden Grid. In Gridk\u00f6nnen Sie mehrere Steuerelemente in einer Zelle \"stapeln\" und ihre Ausrichtung separat einstellen. Im zweiten Labor haben wir das Problem der Anzeige von Name und Alter in ListView DataTemplatefolgenderma\u00dfen gel\u00f6st.

                    Datumsformatierung

                    Zur Formatierung des Datums der Abgabefrist k\u00f6nnen Sie auch einen Konverter oder eine Funktionsbindung auf der Grundlage von x:Bind verwenden, wobei Sie die Funktion DateTime.ToString mit Parametern binden.

                    Text=\"{x:Bind Deadline.ToString('yyyy.MM.dd', x:Null)}\"\n

                    Das x:Null wird ben\u00f6tigt, weil der zweite Parameter der Funktion ToString angegeben werden muss, aber in diesem Fall kann er null sein.

                    Abstand zwischen den Listenelementen

                    Auf dem Screenshot der Anleitung sehen Sie, dass zwischen den Listenelementen ein vertikaler Abstand besteht, so dass die Listenelemente gut voneinander getrennt sind. Dies ist nicht standardm\u00e4\u00dfig der Fall. Gl\u00fccklicherweise erfordert die L\u00f6sung, dass DataTemplate f\u00fcr die Anzeige der Elemente verwendet wird, so dass Sie durch eine kleine Anpassung (Tipp: geben Sie einen einzelnen Margin/Padding an) leicht etwas Platz zwischen den Listenelementen f\u00fcr eine bessere Lesbarkeit erreichen k\u00f6nnen.

                    Aufgabe 2 - EINGABE

                    F\u00fcgen Sie ein Bildschirmfoto der Anwendung ein, in der eine der Aufgaben in der Liste Ihren NEPTUN-Code als Namen oder Beschreibung hat (f2.png).

                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#aufgabe-3-eine-neue-aufgabe-hinzufugen","title":"Aufgabe 3 - Eine neue Aufgabe hinzuf\u00fcgen","text":"

                    Der Text \"To-Do item\" sollte auf der rechten Seite des Grids in Zeile 1 angezeigt werden, mit Schriftgrad 25, horizontal links ausgerichtet und vertikal zentriert, mit 20 Pixel Leerraum auf der linken Seite.

                    Klicken Sie auf der Oberfl\u00e4che auf die Taste Add, um in der zweiten Zeile ein Formular anzuzeigen, in dem Sie eine neue Aufgabe hinzuf\u00fcgen k\u00f6nnen.

                    Das Formular sollte wie das folgende aussehen:

                    Formular f\u00fcr die Bearbeitung einer Aufgabe

                    Das Formular sollte die folgenden Elemente enthalten, die untereinander angeordnet sind.

                    • Titel: Texteingabefeld
                    • Beschreibung: h\u00f6heres Texteingabefeld, akzeptiert auch Zeilenumbruch (Enter) (AcceptsReturn=\"True\")
                    • Abgabetermin: Datumsausw\u00e4hler (DatePicker) (Bemerkung: wir verwenden im Modell DateTimeOffset wegen dieses Controllers)
                    • Priorit\u00e4t: Dropdown-Liste (ComboBox) mit den Werten des Typs Priority
                    • Bereitschaft: Kontrollk\u00e4stchen (CheckBox)
                    • Speichern: Taste mit eingebautem Stil accent (Style=\"{StaticResource AccentButtonStyle}\")

                    Das Formular ben\u00f6tigt kein spezielles, benutzerdefiniertes Steuerelement (z. B. UserControl ): Verwenden Sie einfach einen der Layout-Paneltypen, die f\u00fcr die Aufgabe geeignet sind.

                    Zus\u00e4tzliche funktionale Anforderungen:

                    • Das Formular sollte nur sichtbar sein, wenn die Taste Add angeklickt wird, und verschwinden, wenn die Aufgabe gespeichert wird.
                    • Klicken Sie auf Save, um die Daten zur Liste hinzuzuf\u00fcgen, und das Formular wird ausgeblendet.
                    • Mit dem Klicken auf die Taste Add soll die Auswahl der aktuellen Element in der Liste entfernt werden (SelectedItem). (Nur die Auswahl, nicht das Element sich selbst.)
                    • Optionale Aufgabe: Das Formular sollte scrollbar sein, wenn sein Inhalt nicht auf den Bildschirm passt (verwenden SieScrollViewer ).

                    Layout des Formulars

                    • Die Steuerelemente TextBox, ComboBox und DatePicker haben eine Eigenschaft Header, in der der \u00dcberschrifttext \u00fcber dem Steuerelement angegeben werden kann. Verwenden Sie dies, um Kopftexte anzugeben, nicht eine separate TextBlock!
                    • Auf dem Formular sollten die Elemente nicht zu dicht nebeneinander liegen, mit etwa 15 Pixeln zus\u00e4tzlichem Abstand zwischen ihnen (die Eigenschaft StackPanel Spacing ist eine gute M\u00f6glichkeit, dies zu erreichen).
                    • Legen Sie einen sichtbaren Rahmen f\u00fcr das Formular fest. Wir tun dies nicht, um unsere Benutzeroberfl\u00e4che h\u00fcbscher zu machen, sondern um besser erkennen zu k\u00f6nnen, wo genau sich unser Formular befindet (eine Alternative w\u00e4re, die Hintergrundfarbe zu \u00e4ndern). Dieser \"Trick\" wird tempor\u00e4r auch w\u00e4hren der Gestaltung der Oberfl\u00e4che eingesetzt, wenn nicht klar ist, wo genau sich etwas auf der Oberfl\u00e4che befindet. Setzen Sie dazu die Eigenschaft BorderThickness des Formular-Containers auf 1 und die Rahmenfarbe (EigenschaftBorderBrush ) auf eine sichtbare Farbe (z.B. LightGray).
                    • Verwenden Sie links, rechts und unten im Formular einen Rand von 8 und oben einen Rand von 0 (dies ist der Abstand zwischen dem Rand des Formulars und seinem Inhalt, unabh\u00e4ngig davon, wie gro\u00df der Benutzer das Fenster zur Laufzeit skaliert).
                    • Zwischen dem Rahmen des Formulars und dem Rand der Steuerelemente sollten oben und unten jeweils 15 Pixel und links und rechts jeweils 10 Pixel Platz sein. Um dies zu tun, setzen Sie nicht die R\u00e4nder der Steuerelemente im Formular einzeln, sondern setzen Sie eine entsprechende Eigenschaft des Formular-Containers (die steuert, wie viel Platz zwischen den R\u00e4ndern des Containers und seinem inneren Inhalt vorhanden ist)!
                    • Die beiden vorangegangenen Punkte bedeuten auch, dass das Formular und die darin enthaltenen Textfelder automatisch mit dem Fenster skaliert werden sollten, wie in den Bildern unter dem Dropdown-Bereich dargestellt.

                      Illustration des Formularverhaltens und der erwarteten Gr\u00f6\u00dfe

                    Schritte zur Implementierung des Speicherns und der Kontrolle der Formularsichtbarkeit
                    1. Die Daten im Formular werden in einem neuen \"ToDoItem\"-Objekt gesammelt, dessen Eigenschaften (bidirektional!) zu der Oberfl\u00e4che gebunden werden. Erstellen Sie eine Eigenschaft mit dem Namen EditedTodo (der Anfangswert sollte null sein).
                    2. Klicken Sie auf die Taste Add, um EditedTodo zu kopieren.
                    3. F\u00fcgen Sie beim Speichern das zu bearbeitende Objekt in die Liste \"ToDos\" ein. Denken Sie daran, dass die Datenverbindungen in der Oberfl\u00e4che aktualisiert werden m\u00fcssen, wenn sich der Inhalt der Liste \u00e4ndert (dies erfordert \u00c4nderungen an der Art und Weise, wie wir unsere Daten speichern).
                    4. W\u00e4hrend des Speicherns wird die Eigenschaft \"EditedTodo\" gel\u00f6scht, auf \"null\" gesetzt.
                    5. Wenn Sie das oben beschriebene getan haben, sollte das Formular genau dann sichtbar sein, wenn EditedTodo nicht null ist (stellen Sie sicher, dass es so ist). Darauf aufbauend k\u00f6nnen Sie mehrere L\u00f6sungen entwickeln. Am einfachsten ist es, die klassische, auf Eigenschaften basierende Datenverbindung \"x:Bind\" zu verwenden:
                      1. F\u00fchren Sie eine neue Eigenschaft in unsere Klasse Page ein (z.B. IsFormVisible, mit dem Typ bool).
                      2. Dies sollte genau dann wahr sein, wenn EditedTodo nicht null ist. Sie sind daf\u00fcr verantwortlich, dies zu pflegen, z.B. im Setter EditedTodo.
                      3. Diese Eigenschaft kann mit der Sichtbarkeit des Containers, der unser Formular darstellt, verkn\u00fcpft werden (Eigenschaft \"Visibility\"). Sie sind zwar nicht vom selben Typ, aber unter WinUI gibt es eine automatische Konvertierung zwischen den Typen bool und Visibility.
                      4. Beachten Sie auch, dass bei einer \u00c4nderung der Quelleigenschaft (IsFormVisible) die damit verbundene Zieleigenschaft (Sichtbarkeit des Steuerelements) immer aktualisiert werden muss. Was wird ben\u00f6tigt? (Hinweis: in der Klasse, die direkt die Eigenschaft enth\u00e4lt - \u00fcberlegen Sie, um welche Klasse es in unserem Fall ist - muss eine geeignete Schnittstelle implementiert werden usw.)
                    Alternative M\u00f6glichkeiten f\u00fcr die L\u00f6sung

                    Andere Alternativen sind ebenfalls m\u00f6glich (nur interessehalber, aber verwenden Sie sie nicht diese in der L\u00f6sung):

                    1. Implementieren einer funktionsbasierte Datenverbindung, aber in unserem Fall w\u00e4re dies komplizierter.
                      • Bei einer auf der Grundlage von \"x:Bind\" gebundenen Funktion wird der Wert \"null\" oder ein anderer Wert als \"null\" der Eigenschaft \"EditedTodo\" zum Anzeigen und Ausblenden in \"Sichtbarkeit\" umgewandelt.
                      • Wenn wir Daten binden, m\u00fcssen wir auch FallbackValue='Collapsed' verwenden, denn leider ruft x:Bind die Funktion standardm\u00e4\u00dfig nicht auf, wenn der Wert null ist.
                      • Die gebundene Funktion muss einen Parameter haben, der die Eigenschaft angibt, deren \u00c4nderung die Aktualisierung der Datenverbindung bewirkt, und auch die \u00c4nderungsmeldung f\u00fcr die Eigenschaft muss hier implementiert werden.
                    2. Anwendung des Konverters.
                    Liste der Priorit\u00e4ten

                    Zeigen Sie in ComboBoxdie Werte des aufgelisteten Typs Priority an. Zu diesem Zweck k\u00f6nnen Sie die Funktion Enum.GetValues verwenden und eine Eigenschaft in MainPage.xaml.cserstellen.

                    public List<Priority> Priorities { get; } = Enum.GetValues(typeof(Priority)).Cast<Priority>().ToList();\n

                    Binden Sie die Liste \"Priorities\" an die Eigenschaft \"ItemsSource\" der \"ComboBox\".

                    <ComboBox ItemsSource=\"{x:Bind Priorities}\" />\n

                    Im obigen Beispiel gibt ItemsSource nur an, welche Elemente in der Liste der ComboBox erscheinen sollen. Aber das sagt nichts dar\u00fcber aus, woran das ausgew\u00e4hlte Element in der \"ComboBox\" gebunden sein soll. Dies erfordert eine weitere Datenverbindung. Dies wurde in der \u00dcbung nicht erw\u00e4hnt, aber es lohnt sich im Vorlesungsmaterial zum Beispiel SelectedItem suchen (alle Vorkommen lohnt es sich anzuschauen).

                    Einige wichtige Controller-Eigenschaften
                    • Die Eigenschaft IsChecked (und nicht Checked!) vonCheckBox
                    • Die Eigenschaft Date von DatePicker

                    Aufgabe 3 - EINGABE

                    F\u00fcgen Sie ein Bildschirmfoto der Anwendung ein, auf dem das Hinzuf\u00fcgen der neuen Aufgabe vor dem Speichern sehbar ist! (f3.1.png)

                    F\u00fcgen Sie ein Bildschirmfoto der Anwendung ein, auf dem die Aufgabe im vorherigen Bild der Liste hinzugef\u00fcgt wurde und das Formular verschwunden ist (f3.2.png)

                    Optionale \u00dcbungsaufgaben

                    Optionale \u00dcbungsaufgabe 1 - Ein Formular scrollbar machen

                    Alles, was Sie tun m\u00fcssen, ist, das Formular in ein ScrollViewer Steuerelement einzuschlie\u00dfen (und denken Sie daran, dass dies das \u00e4u\u00dferste Element in der Gridzelle sein wird, so dass Sie die Position innerhalb dem Grid daf\u00fcr angeben m\u00fcssen). Wenn Sie dies implementieren, kann es in Ihre eingereichte L\u00f6sung aufgenommen werden.

                    Optionale \u00dcbungsaufgabe 2 - Formular mit fester Breite

                    In unserer L\u00f6sung wird das Formular automatisch mit dem Fenster skaliert. Eine gute M\u00f6glichkeit ist zu \u00fcben, dies so zu \u00e4ndern, dass das Formular eine feste Breite (z. B. 500 Pixel) und eine H\u00f6he hat, die der Gesamth\u00f6he der darin enthaltenen Elemente entspricht. Wenn Sie f\u00fcr das Formular mit StackPanel gearbeitet haben, m\u00fcssen Sie nur drei Attribute hinzuf\u00fcgen oder \u00e4ndern. Dieses Verhalten wird in der nachstehenden animierten Abbildung veranschaulicht. Es ist wichtig, dass Sie die vorherige L\u00f6sung eingaben soll und nicht das in dieser optionalen \u00dcbung beschriebene Verhalten!

                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#4-optionale-aufgabe-fur-3-imsc-punkte-bearbeiten-einer-aufgabe-todo","title":"4. Optionale Aufgabe f\u00fcr 3 IMSc-Punkte - Bearbeiten einer Aufgabe (ToDo)","text":"

                    Machen Sie es m\u00f6glich, die Aufgaben wie folgt zu bearbeiten:

                    • Wenn Sie auf der Benutzeroberfl\u00e4che auf ein Element in der Aufgabenliste klicken, werden die Daten f\u00fcr diese Aufgabe in der Bearbeitungsoberfl\u00e4che angezeigt (das in der vorherigen Aufgabe vorgestellte Formular), wo sie bearbeitet und gespeichert werden kann.
                    • Beim Speichern sollte die bearbeitete Aufgabenliste aktualisiert werden und das Formular verschwinden.
                    Tipps zur L\u00f6sung
                    • Es lohnt sich, die eindeutige ID der Aufgaben w\u00e4hrend des Einf\u00fcgens beizubehalten, damit Sie w\u00e4hrend dem Speichern, zwischen Bearbeiten und Einf\u00fcgen unterscheiden k\u00f6nnen. Im Falle einer Einf\u00fcgung k\u00f6nnen Sie beispielsweise den Wert -1 verwenden, den wir durch eine Zahl ersetzen, die um eins gr\u00f6\u00dfer ist als die zuvor verwendete. Aber nehmen wir an, dass -1 auch ein Wert ist, den ein g\u00fcltiges Aufgabenobjekt haben kann. Was kann getan werden? \u00c4ndern Sie in der Klasse TodoItem den Typ von Id in int?. Bei ?k\u00f6nnen die Wertetypen (int, bool, char, enum, struct usw.) auch den Wert null annehmen. Diese werden als nullable Werttypen (nullable value types) bezeichnet. Sie werden w\u00e4hrend der Kompilierung auf die Struktur Nullable<T>.NET abgebildet, die die urspr\u00fcngliche Variable und ein Flag enth\u00e4lt, das angibt, ob der Wert gef\u00fcllt ist oder nicht. Lesen Sie mehr \u00fcber sie hier und hier. Wenden Sie dies in der L\u00f6sung an.
                    • Um auf das Listenelement zu klicken, empfiehlt es sich, das Ereignis ListView ItemClick zu verwenden, nachdem die Eigenschaft IsItemClickEnabled auf ListViewaktiviert wurde. Informationen \u00fcber das neu ausgew\u00e4hlte Listenelement werden im Parameter ItemClickEventArgs des Ereignishandlers angegeben.
                    • Es gibt mehrere M\u00f6glichkeiten, die zu bearbeitenden Daten zu behandeln, eine davon ist:
                      • Setzen Sie die Eigenschaft EditedTodo auf die bearbeitete Aufgabe, wenn Sie darauf klicken.
                      • Wenn Sie auf die Taste \"Save\" klicken, wird die bearbeitete Aufgabe in der Liste Todos durch den Wert EditedTodo ersetzt. Im Endeffekt ersetzen wir das gleiche Element durch sich selbst, aber ListView wird aktualisiert.

                    Aufgave 4. iMSc - EINGABE

                    F\u00fcgen Sie ein Bildschirmfoto der Anwendung ein, bei der ein Klick auf einen vorhandenen Eintrag das Formular ausf\u00fcllt (f4.imsc.1.png)

                    F\u00fcgen Sie ein Bildschirmfoto der Anwendung ein, auf dem die im vorherigen Screenshot ausgew\u00e4hlte Aufgabe in der Liste als Ergebnis der Speicheraktion aktualisiert wird! (f4.imsc.2.png)

                    "},{"location":"hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/#eingabe","title":"Eingabe","text":"

                    Checkliste f\u00fcr Wiederholungen:

                    • Es ist wichtig, dass nur die Aufgaben akzeptiert werden, die Sie vollst\u00e4ndig gemacht haben und die die Anforderungen in jeder Hinsicht erf\u00fcllen.
                    • Sie m\u00fcssen nat\u00fcrlich Ihre eigene Arbeit eingeben (da sie bewertet wird).
                    • Nicht nur das Quellcode, sondern auch die erwartete Bildschirmfotos sollen eingegeben werden.
                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/","title":"4. HF - T\u00f6bbsz\u00e1l\u00fa alkalmaz\u00e1sok fejleszt\u00e9se","text":""},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#bevezetes","title":"Bevezet\u00e9s","text":"

                    Az \u00f6n\u00e1ll\u00f3 feladat a konkurens/t\u00f6bbsz\u00e1l\u00fa alkalmaz\u00e1sok fejleszt\u00e9se el\u0151ad\u00e1sokon elhangzottakra \u00e9p\u00edt. A feladatok gyakorlati h\u00e1tter\u00e9\u00fcl a 4. labor \u2013 T\u00f6bbsz\u00e1l\u00fa alkalmaz\u00e1sok fejleszt\u00e9se laborgyakorlat szolg\u00e1l.

                    A fentiekre \u00e9p\u00edtve, jelen \u00f6n\u00e1ll\u00f3 gyakorlat feladatai a feladatle\u00edr\u00e1st k\u00f6vet\u0151 r\u00f6videbb ir\u00e1nymutat\u00e1s seg\u00edts\u00e9g\u00e9vel elv\u00e9gezhet\u0151k. Az \u00f6n\u00e1ll\u00f3 gyakorlat a k\u00f6vetkez\u0151 ismeretek elm\u00e9ly\u00edt\u00e9s\u00e9t c\u00e9lozza:

                    • Sz\u00e1lak ind\u00edt\u00e1sa \u00e9s le\u00e1ll\u00edt\u00e1sa, sz\u00e1lf\u00fcggv\u00e9ny
                    • Jelz\u00e9s \u00e9s jelz\u00e9sre v\u00e1rakoz\u00e1s (ManualResetEvent, AutoResetEvent)
                    • K\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1s megval\u00f3s\u00edt\u00e1sa (lock haszn\u00e1lata)
                    • WinUI fel\u00fcletelemekhez hozz\u00e1f\u00e9r\u00e9s munkasz\u00e1lakb\u00f3l
                    • Delegate-ek haszn\u00e1lat\u00e1nak gyakorl\u00e1sa (Action<T>)
                    • Felhaszn\u00e1l\u00f3i fel\u00fclet kialak\u00edt\u00e1s\u00e1nak gyakorl\u00e1sa: id\u0151z\u00edt\u0151 haszn\u00e1lata, fel\u00fcletelemek manipul\u00e1l\u00e1sa code behind f\u00e1jlb\u00f3l (ez nem kapcsol\u00f3dik a sz\u00e1lkezel\u00e9shez)

                    A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezet a szok\u00e1sos, itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s (a le\u00edr\u00e1sban szerepl\u0151 Windows App SDK-ra is sz\u00fcks\u00e9g van).

                    Ellen\u0151rz\u0151 futtat\u00e1sa

                    Ehhez a feladathoz \u00e9rdemi el\u0151ellen\u0151rz\u0151 nem tartozik: minden push ut\u00e1n lefut ugyan, de csak a neptun.txt kit\u00f6lt\u00f6tts\u00e9g\u00e9t ellen\u0151rzi \u00e9s azt, van-e ford\u00edt\u00e1si hiba. Az \u00e9rdemi ellen\u0151rz\u00e9st a hat\u00e1rid\u0151 lej\u00e1rta ut\u00e1n a laborvezet\u0151k teszik majd meg.

                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#a-beadas-menete","title":"A bead\u00e1s menete","text":"
                    • Az alapfolyamat megegyezik a kor\u00e1bbiakkal. GitHub Classroom seg\u00edts\u00e9g\u00e9vel hozz l\u00e9tre magadnak egy repository-t. A megh\u00edv\u00f3 URL-t Moodle-ben tal\u00e1lod (a t\u00e1rgy nyit\u00f3oldal\u00e1n a \"GitHub classroom hivatkoz\u00e1sok a h\u00e1zi feladatokhoz\" hivatkoz\u00e1sra kattintva megjelen\u0151 oldalon l\u00e1that\u00f3). Fontos, hogy a megfelel\u0151, ezen h\u00e1zi feladathoz tartoz\u00f3 megh\u00edv\u00f3 URL-t haszn\u00e1ld (minden h\u00e1zi feladathoz m\u00e1s URL tartozik). Kl\u00f3nozd le az \u00edgy elk\u00e9sz\u00fclt repository-t. Ez tartalmazni fogja a megold\u00e1s elv\u00e1rt szerkezet\u00e9t. A feladatok elk\u00e9sz\u00edt\u00e9se ut\u00e1n commit-old \u00e9s push-old a megold\u00e1sod.
                    • A neptun.txt f\u00e1jlba \u00edrd bele a Neptun k\u00f3dod!
                    • A kikl\u00f3nozott f\u00e1jlok k\u00f6z\u00f6tt a MultiThreadedApp.sln-t megnyitva kell dolgozni.
                    • A feladatok k\u00e9rik, hogy k\u00e9sz\u00edts k\u00e9perny\u0151k\u00e9pet a megold\u00e1s egy-egy r\u00e9sz\u00e9r\u0151l, mert ezzel bizony\u00edtod, hogy a megold\u00e1sod saj\u00e1t magad k\u00e9sz\u00edtetted. A k\u00e9perny\u0151k\u00e9pek elv\u00e1rt tartalm\u00e1t a feladat minden esetben pontosan megnevezi. A k\u00e9perny\u0151k\u00e9peket a megold\u00e1s r\u00e9szek\u00e9nt kell beadni, a repository-d gy\u00f6k\u00e9rmapp\u00e1j\u00e1ba tedd (a neptun.txt mell\u00e9). A k\u00e9perny\u0151k\u00e9pek \u00edgy felker\u00fclnek GitHub-ra git repository tartalm\u00e1val egy\u00fctt. Mivel a repository priv\u00e1t, azt az oktat\u00f3kon k\u00edv\u00fcl m\u00e1s nem l\u00e1tja. Amennyiben olyan tartalom ker\u00fcl a k\u00e9perny\u0151k\u00e9pre, amit nem szeretn\u00e9l felt\u00f6lteni, kitakarhatod a k\u00e9pr\u0151l.
                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-0-a-feladat-attekintese-ismerkedes-a-kiindulo-kerettel","title":"Feladat 0 \u2013 A feladat \u00e1ttekint\u00e9se, ismerked\u00e9s a kiindul\u00f3 kerettel","text":"

                    A feladat egy bicikliversenyt szimul\u00e1l\u00f3 alkalmaz\u00e1s elk\u00e9sz\u00edt\u00e9se. A megval\u00f3s\u00edt\u00e1s alappill\u00e9re az alkalmaz\u00e1slogika \u00e9s a megjelen\u00edt\u00e9s k\u00fcl\u00f6nv\u00e1laszt\u00e1sa: az alkalmaz\u00e1slogika semmilyen szinten nem f\u00fcgghet a megjelen\u00edt\u00e9st\u0151l, a megjelen\u00edt\u00e9s pedig f\u00fcgg az alkalmaz\u00e1slogik\u00e1t\u00f3l (\u00e9rtelemszer\u0171en, hiszen annak aktu\u00e1lis \u00e1llapot\u00e1t jelen\u00edti meg).

                    A kiindul\u00f3 keret m\u00e1r tartalmaz n\u00e9mi alkalmaz\u00e1s \u00e9s megjelen\u00edt\u00e9shez kapcsol\u00f3d\u00f3 logik\u00e1t. Futtassuk az alkalmaz\u00e1st, \u00e9s tekints\u00fck \u00e1t a fel\u00fclet\u00e9t:

                    • Az ablak fels\u0151 r\u00e9sz\u00e9n tal\u00e1lhat\u00f3 a versenyp\u00e1lya. Bal oldalon sorakoznak a biciklik, majd l\u00e1that\u00f3 a startvonal, a p\u00e1lya k\u00f6zepe fel\u00e9 egy k\u00f6ztes meg\u00e1ll\u00f3 (dep\u00f3), ill. a c\u00e9lvonal.
                    • Az ablak als\u00f3 r\u00e9sz\u00e9n a verseny vez\u00e9rl\u00e9s\u00e9re szolg\u00e1l\u00f3 gombok tal\u00e1lhat\u00f3k. M\u00e9g nem kapcsol\u00f3dik hozz\u00e1juk logika, a k\u00f6vetkez\u0151 viselked\u00e9st fogjuk a k\u00e9s\u0151bbiekben megval\u00f3s\u00edtani:
                      • Prepare Race: A verseny el\u0151k\u00e9sz\u00edt\u00e9se (biciklik l\u00e9trehoz\u00e1sa \u00e9s felsorakoztat\u00e1sa a startvonalhoz).
                      • Start Race: A verseny ind\u00edt\u00e1sa, mely hat\u00e1s\u00e1ra a biciklik egym\u00e1ssal versenyezve el\u00e9rnek a dep\u00f3ba, \u00e9s ott v\u00e1rakoznak.
                      • Start Next Bike From Depo: A dep\u00f3ban v\u00e1rakoz\u00f3 biciklik k\u00f6z\u00fcl elind\u00edt egyet (mely bicikli eg\u00e9szen a c\u00e9lvonalig halad). A gombon t\u00f6bbsz\u00f6r is lehet kattintani, minden alkalommal egy biciklit enged tov\u00e1bb.

                    Az al\u00e1bbi anim\u00e1lt k\u00e9pen azt illusztr\u00e1lja, hogy a megold\u00e1s sor\u00e1n hova szeretn\u00e9nk eljutni:

                    A j\u00e1t\u00e9k/szimul\u00e1ci\u00f3 alapelvelve a k\u00f6vetkez\u0151 (m\u00e9g nincs megval\u00f3s\u00edtva):

                    • Minden egyes biciklihez egy k\u00fcl\u00f6n sz\u00e1l tartozik.
                    • A j\u00e1t\u00e9k/szimul\u00e1ci\u00f3 iter\u00e1ci\u00f3kra bontott: minden iter\u00e1ci\u00f3ban a biciklihez tartoz\u00f3 sz\u00e1l (amennyiben az \u00e9ppen nem v\u00e1rakozik a verseny ind\u00edt\u00e1s\u00e1ra vagy a dep\u00f3ban) egy v\u00e9letlenszer\u0171 sz\u00e1m\u00e9rt\u00e9kkel l\u00e9p el\u0151re a p\u00e1ly\u00e1n, eg\u00e9szen addig, am\u00edg el nem \u00e9ri a c\u00e9lvonalat.

                    Egy extra megval\u00f3s\u00edtott funkci\u00f3 (ez m\u00e1r m\u0171k\u00f6dik): a vil\u00e1gos \u00e9s s\u00f6t\u00e9t t\u00e9ma k\u00f6z\u00f6tti v\u00e1lt\u00e1sra lehet\u0151s\u00e9g van a Ctrl+T billenty\u0171kombin\u00e1ci\u00f3val.

                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#alkalmazaslogika","title":"Alkalmaz\u00e1slogika","text":"

                    A kiindul\u00f3 keretben az alkalmaz\u00e1slogika oszt\u00e1lyai csak kezdetleges \u00e1llapotban vannak megval\u00f3s\u00edtva. Az oszt\u00e1lyok az AppLogic mapp\u00e1ban/n\u00e9vt\u00e9rben tal\u00e1lhat\u00f3k, n\u00e9zz\u00fck meg ezek k\u00f3dj\u00e1t:

                    • Bike: Egy biciklit reprezent\u00e1l, melyhez hozz\u00e1tartozik a bicikli rajtsz\u00e1ma, poz\u00edci\u00f3ja \u00e9s azon inform\u00e1ci\u00f3, hogy az adott bicikli nyerte-e meg a versenyt. A Step m\u0171velete a bicikli v\u00e9letlenszer\u0171 l\u00e9pt\u00e9kkel t\u00f6rt\u00e9n\u0151 l\u00e9ptet\u00e9s\u00e9re szolg\u00e1l a verseny k\u00f6zben.
                    • Game: A j\u00e1t\u00e9k vez\u00e9rl\u00e9s\u00e9nek logik\u00e1ja (ezt tov\u00e1bb lehetne darabolni, de az egyszer\u0171s\u00e9g kedv\u00e9\u00e9rt alapvet\u0151en ebbe az oszt\u00e1lyba fogunk dolgozni).
                      • Defini\u00e1lja az egyes versenyp\u00e1lya elemek, \u00fagymint startvonal, k\u00f6ztes meg\u00e1ll\u00f3 (dep\u00f3) \u00e9s c\u00e9lvonal poz\u00edci\u00f3it: StartLinePosition, DepoPosition \u00e9s FinishLinePosition konstansok.
                      • T\u00e1rolja a versenyz\u0151 bicikliket (Bikes tagv\u00e1ltoz\u00f3).
                      • PrepareRace m\u0171velet: El\u0151k\u00e9sz\u00edti a versenyt. Egyel\u0151re a CreateBike seg\u00e9df\u00fcggv\u00e9ny felhaszn\u00e1l\u00e1s\u00e1val l\u00e9trehoz h\u00e1rom biciklit. A feladata lesz m\u00e9g a biciklik felsorakoztat\u00e1sa a startvonalhoz.
                      • StartBikes m\u0171velet: Verseny ind\u00edt\u00e1sa (mely hat\u00e1s\u00e1ra a biciklik egym\u00e1ssal versenyezve el\u00e9rnek a dep\u00f3ba, \u00e9s ott v\u00e1rakoznak). Nincs megval\u00f3s\u00edtva.
                      • StartNextBikeFromDepo m\u0171velet: A dep\u00f3ban v\u00e1rakoz\u00f3 biciklik k\u00f6z\u00fcl elind\u00edt egyet (de csak egyet). Nincs megval\u00f3s\u00edtva.
                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#megjelenites","title":"Megjelen\u00edt\u00e9s","text":"

                    A kiindul\u00f3 keretben a megjelen\u00edt\u00e9s viszonylag j\u00f3l el\u0151 van k\u00e9sz\u00edtve, de ezen is fogunk m\u00e9g dolgozni.

                    A fel\u00fclet kialak\u00edt\u00e1sa a MainWindow.xaml-ben tal\u00e1lhat\u00f3, a k\u00f6vetkez\u0151 alapelvek szerint:

                    • Az ablak alapelrendez\u00e9s\u00e9nek kialak\u00edt\u00e1s\u00e1ra \"szok\u00e1sosan\" egy Grid-et haszn\u00e1ltunk, mely k\u00e9t sorb\u00f3l \u00e1ll. Az els\u0151 sor\u00e1ban tal\u00e1lhat\u00f3 a versenyp\u00e1lya a biciklikkel (* sormagass\u00e1g), az als\u00f3 r\u00e9szben pedig egy StackPanel a gombokkal (Auto sormagass\u00e1g).
                    • A p\u00e1lya kialak\u00edt\u00e1s\u00e1ra Rectangle objektumokat (h\u00e1tt\u00e9r, startvonal, depo, c\u00e9legyenes), a sz\u00f6vegelemek elrendez\u00e9s\u00e9re pedig (r\u00e9szben elforgatott) TextBlock objektumokat haszn\u00e1ltunk.
                    • Az egyes bicikliket egy vertik\u00e1lis StackPanel-en helyezt\u00fck el. A bicikliket egy-egy TextBlock objektummal jelen\u00edtj\u00fck meg (Webdings bet\u0171t\u00edpus, b bet\u0171). Haszn\u00e1lhattunk volna FontIcon-t is, a TextBlock-ra csak az\u00e9rt esett a v\u00e1laszt\u00e1sunk, mert ezzel m\u00e1r kor\u00e1bban megismerkedt\u00fcnk.
                    • A p\u00e1lya valamennyi elem\u00e9t \u00e9s a bicikliket tartalmaz\u00f3 StackPanel-t is a Grid els\u0151 (technikailag 0-dik) sor\u00e1ban helyezt\u00fck el. Ezek a defini\u00e1l\u00e1suk sorrendj\u00e9ben rajzol\u00f3dnak ki, az igaz\u00edt\u00e1sok \u00e9s marg\u00f3k \u00e1ltal meghat\u00e1rozott helyen. A biciklik TextBlock-j\u00e1nak poz\u00edcion\u00e1l\u00e1s\u00e1ra is a marg\u00f3t haszn\u00e1ljuk majd. Egy alternat\u00edva megold\u00e1s lett volna, ha minden fel\u00fcletelemet egy Canvas-re helyezt\u00fcnk volna el, \u00e9s azon \u00e1ll\u00edtottuk volna be az elemek abszol\u00fat poz\u00edci\u00f3j\u00e1t \u00e9s m\u00e9ret\u00e9t (Left, Top, Width, Height) a marg\u00f3k alkalmaz\u00e1sa helyett.

                    Az ablakhoz tartoz\u00f3 MainWindow.cs code behind f\u00e1jlt is n\u00e9zz\u00fck meg, f\u0151bb elemei a k\u00f6vetkez\u0151k:

                    • game tagv\u00e1ltoz\u00f3: Maga a Game j\u00e1t\u00e9kobjektum, melynek \u00e1llapot\u00e1t a f\u0151ablak megjelen\u00edti.
                    • bikeTextBlocks tagv\u00e1ltoz\u00f3: Ebben a list\u00e1ban t\u00e1roljuk majd a bicikliket megjelen\u00edt\u0151 TextBlock objektumokat. Egyel\u0151re \u00fcres, a karbantart\u00e1s\u00e1t nek\u00fcnk kell majd megval\u00f3s\u00edtani.
                    • Konstruktor: Be\u00e1ll\u00edtja a startvonal, dep\u00f3 \u00e9s c\u00e9lvonal fel\u00fcletelemek x koordin\u00e1t\u00e1j\u00e1t a Game \u00e1ltal meghat\u00e1rozott konstans \u00e9rt\u00e9kek alapj\u00e1n. Az x koordin\u00e1ta be\u00e1ll\u00edt\u00e1sa a baloldali marg\u00f3 (Margin) megfelel\u0151 be\u00e1ll\u00edt\u00e1s\u00e1val t\u00f6rt\u00e9nik (mivel ezek az elemek balra igaz\u00edtottak a kont\u00e9ner\u00fckben!). Ezen fel\u00fcl a AddKeyboardAcceleratorToChangeTheme seg\u00e9df\u00fcggv\u00e9ny seg\u00edts\u00e9g\u00e9vel beregisztr\u00e1lja a Ctrl+T gyors\u00edt\u00f3billenty\u0171t a vil\u00e1gos/s\u00f6t\u00e9t t\u00e9ma k\u00f6z\u00f6tti v\u00e1lt\u00e1sra.
                    • PrepareRaceButton_Click, StartRaceButton_Click, StartNextFromDepoButton_Click: a h\u00e1rom gomb esem\u00e9nykezel\u0151je.
                    • UpdateUI m\u0171velet: Kulcsfontoss\u00e1g\u00fa logik\u00e1t tartalmaz. A j\u00e1t\u00e9k \u00e1llapot\u00e1nak megfelel\u0151en friss\u00edti a fel\u00fcletet. V\u00e9gig iter\u00e1l a j\u00e1t\u00e9k \u00f6sszes biciklij\u00e9n, \u00e9s a biciklikhez tartoz\u00f3 TextBlock-ok x poz\u00edci\u00f3j\u00e1t be\u00e1ll\u00edtja a bicikli poz\u00edci\u00f3ja alapj\u00e1n (a baloldali marg\u00f3 megfelel\u0151 be\u00e1ll\u00edt\u00e1s\u00e1val). Az UpdateUI m\u0171velet egyel\u0151re soha nem h\u00edv\u00f3dik, \u00edgy a fel\u00fclet nem friss\u00fcl.
                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-1-a-felulet-frissitese","title":"Feladat 1 \u2013 A fel\u00fclet friss\u00edt\u00e9se","text":"

                    Jelen pillanatban hi\u00e1ba m\u00f3dos\u00edtan\u00e1nk fut\u00e1s k\u00f6zben a j\u00e1t\u00e9k \u00e1llapot\u00e1t: a fel\u00fcletbe be van \u00e9getve a h\u00e1rom bicikli fix poz\u00edci\u00f3ban, ezen fel\u00fcl a fel\u00fcletet friss\u00edt\u0151 UpdateUI m\u0171velet egyel\u0151re soha nem h\u00edv\u00f3dik. Miel\u0151tt belev\u00e1gn\u00e1nk a j\u00e1t\u00e9klogika megval\u00f3s\u00edt\u00e1s\u00e1ba, m\u00f3dos\u00edtsuk a fel\u00fclethez tartoz\u00f3 logik\u00e1t, hogy az k\u00e9pes legyen folyamatosan a j\u00e1t\u00e9k friss \u00e1llapot\u00e1t megjelen\u00edteni.

                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#a-biciklik-dinamikus-kezelese","title":"A biciklik dinamikus kezel\u00e9se","text":"

                    Az els\u0151 probl\u00e9ma: a MainWindow.xaml-be be van \u00e9getve a h\u00e1rom, biciklit megjelen\u00edt\u0151 TextBlock. \u00cdgy a fel\u00fclet\u00fcnk csak olyan j\u00e1t\u00e9k megjelen\u00edt\u00e9s\u00e9re lenne k\u00e9pes, melyben pontosan h\u00e1rom versenyz\u0151 szerepel. K\u00e9sz\u00edts\u00fck el\u0151 a megjelen\u00edt\u00e9st tetsz\u0151leges sz\u00e1m\u00fa bicikli kezel\u00e9s\u00e9re. Els\u0151 l\u00e9p\u00e9sben t\u00e1vol\u00edtsuk el a MainWindow.xaml-b\u0151l a h\u00e1rom biciklihez tartoz\u00f3 \"be\u00e9getett\" TextBlock defin\u00edci\u00f3t (kommentezz\u00fck ki a h\u00e1rom sort). Ezt k\u00f6vet\u0151en, a code behind f\u00e1jlban, a PrepareRaceButton_Click esem\u00e9nykezel\u0151ben a verseny el\u0151k\u00e9sz\u00edt\u00e9se (game.PrepareRace() h\u00edv\u00e1s) ut\u00e1n:

                    1. Dinamikusan hozzunk l\u00e9tre minden, a game objektumban szerepl\u0151 biciklihez (game.Bikes tulajdons\u00e1g!) egy megfelel\u0151 TextBlock objektumot . A l\u00e9trehozott TextBlock tulajdons\u00e1gai pontosan feleljenek meg annak, mint amit a xaml f\u00e1jlban kiiktattunk (FontFamily, FontSize, Margin, Text)
                    2. A l\u00e9trehozott TextBlock objektumokat fel kell venni a bikesPanel nev\u0171 StackPanel gyerekei k\u00f6z\u00e9 (a xaml f\u00e1jlban kikommentezett TextBlock-ok is ennek gyerekei voltak, ezt n\u00e9zz\u00fck meg!), m\u00e9gpedig a bikesPanel.Children.Add h\u00edv\u00e1s\u00e1val.
                    3. A l\u00e9trehozott TextBlock objektumokat vegy\u00fck fel a bikeTextBlocks list\u00e1ba is. Ez az\u00e9rt fontos - n\u00e9zz\u00fck is meg a k\u00f3dban - mert az UpdateUI fel\u00fcletfriss\u00edt\u0151 f\u00fcggv\u00e9ny a biciklikhez tartoz\u00f3 TextBlock-okat a bikeTextBlocks list\u00e1ban keresi (t\u00f6mbindex alapj\u00e1n p\u00e1ros\u00edtja a bicikliket \u00e9s a TextBlock-okat).

                    Annyiban megv\u00e1ltozik az alkalmaz\u00e1s m\u0171k\u00f6d\u00e9se (de ez sz\u00e1nd\u00e9kos), hogy indul\u00e1skor nem jelennek meg biciklik, hanem csak a Prepare Race gombon kattint\u00e1skor.

                    Pr\u00f3b\u00e1ljuk a megold\u00e1st magunkt\u00f3l megval\u00f3s\u00edtani a fenti pontokat k\u00f6vetve, majd ellen\u0151rizz\u00fck, hogy alapvet\u0151en megfelel-e az al\u00e1bbi megold\u00e1snak.

                    Megold\u00e1s
                    foreach (var bike in game.Bikes)\n{\n    var bikeTextBlock = new TextBlock()\n    {\n        Text = \"b\",\n        FontFamily = new FontFamily(\"Webdings\"),\n        FontSize = 64,\n        Margin = new Thickness(10, 0, 0, 0)\n    };\n\n    bikesPanel.Children.Add(bikeTextBlock);\n    bikeTextBlocks.Add(bikeTextBlock);\n}\n
                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#a-feluletfrissites-megvalositasa","title":"A fel\u00fcletfriss\u00edt\u00e9s megval\u00f3s\u00edt\u00e1sa","text":"

                    Most m\u00e1r pontosan annyi TextBlock-unk lesz, ah\u00e1ny bicikli van a game objektumban. S\u0151t, az UpdateUI m\u0171velettel tudjuk is a fel\u00fcletet b\u00e1rmikor friss\u00edteni (a game aktu\u00e1lis \u00e1llapot\u00e1nak megfelel\u0151en). A k\u00f6vetkez\u0151 kardin\u00e1lis k\u00e9rd\u00e9s: mikor h\u00edvjuk ez a f\u00fcggv\u00e9nyt, vagyis mikor friss\u00edts\u00fck a fel\u00fcletet. T\u00f6bb megold\u00e1s k\u00f6z\u00fcl v\u00e1laszthatunk:

                    • a) Mindig, amikor a Game \u00e1llapota megv\u00e1ltozik.
                    • b) Adott id\u0151k\u00f6z\u00f6nk\u00e9nt (pl. 100 ms-k\u00e9nt) \"folyamatosan\", egy id\u0151z\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel.

                    \u00c1ltal\u00e1noss\u00e1g\u00e1ban mindk\u00e9t megold\u00e1snak lehetnek el\u0151nyei \u00e9s h\u00e1tr\u00e1nyai. A b) bizonyos tekintetben egyszer\u0171bb (nem kell tudni, mikor v\u00e1ltozik a Game \u00e1llapota), ugyanakkor felesleges friss\u00edt\u00e9s is t\u00f6rt\u00e9nhet (ha nem v\u00e1ltozott az \u00e1llapot k\u00e9t friss\u00edt\u00e9s k\u00f6z\u00f6tt). De hat\u00e9konyabb is lehet, ha az \u00e1llapot nagyon gyakran v\u00e1ltozik, \u00e9s nem akarjuk minden v\u00e1ltoz\u00e1skor a fel\u00fcletet friss\u00edteni, el\u00e9g adott id\u0151k\u00f6z\u00f6nk\u00e9nt egyszer (pl. a szem\u00fcnk \u00fagysem tudja lek\u00f6vetni). Eset\u00fcnkben - els\u0151sorban egyszer\u0171s\u00e9ge miatt - a \"b)\", vagyis id\u0151z\u00edt\u0151 alap\u00fa megold\u00e1st v\u00e1lasztjuk.

                    WinUI 3 k\u00f6rnyezetben periodikus esem\u00e9nyek kezel\u00e9s\u00e9re a DispatchTimer oszt\u00e1ly alkalmaz\u00e1sa javasolt (k\u00fcl\u00f6n\u00f6sen, ha a fel\u00fcletelemekhez is hozz\u00e1 k\u00edv\u00e1nunk f\u00e9rni az id\u0151z\u00edtett m\u0171veletben).

                    A MainWindow oszt\u00e1lyban vezess\u00fcnk be egy tagv\u00e1ltoz\u00f3t:

                        private DispatcherTimer timer;\n

                    Ezt k\u00f6vet\u0151en a konstruktorban p\u00e9ld\u00e1nyos\u00edtsuk a timert, rendelj\u00fcnk a Tick esem\u00e9ny\u00e9hez egy esem\u00e9nykezel\u0151 f\u00fcggv\u00e9nyt (ez h\u00edv\u00f3dik adott id\u0151k\u00f6z\u00f6nk\u00e9nt), \u00e1ll\u00edtsuk be az id\u0151k\u00f6zt 100 ms-ra (Interval tulajdons\u00e1g), \u00e9s ind\u00edtsuk el a timert:

                    public MainWindow()\n{\n    ...\n\n    timer = new DispatcherTimer();\n    timer.Tick += Timer_Tick;\n    timer.Interval = TimeSpan.FromMilliseconds(100);\n    timer.Start();\n}\n\nprivate void Timer_Tick(object sender, object e)\n{\n    UpdateUI();\n}\n

                    Mint l\u00e1that\u00f3, az id\u0151z\u00edt\u0151 esem\u00e9nykezel\u0151ben az UpdateUI h\u00edv\u00e1s\u00e1val friss\u00edtj\u00fck a fel\u00fcletet.

                    K\u00e9rd\u00e9s, hogyan tudjuk a megold\u00e1sunkat tesztelni, vagyis azt ellen\u0151rizni, hogy a Timer_Tick esem\u00e9nykezel\u0151 val\u00f3ban megh\u00edv\u00f3dik-e 100 ms-k\u00e9nt. Ehhez Trace-elj\u00fck ki ideiglenesen a Visual Studio Output ablak\u00e1ba az aktu\u00e1lis id\u0151t megfelel\u0151en form\u00e1zva az esem\u00e9nykezel\u0151ben:

                    private void Timer_Tick(object sender, object e)\n{\n    System.Diagnostics.Trace.WriteLine($\"Time: {DateTime.Now.ToString(\"hh:mm:ss.fff\")}\");\n\n    UpdateUI();\n}\n

                    A Trace.WriteLine m\u0171velet a Visual Studio Output ablak\u00e1ba \u00edr egy sort, a DateTime.Now-val pedig az aktu\u00e1lis id\u0151t lehet lek\u00e9rdeni. Ezt alak\u00edtjuk a ToString h\u00edv\u00e1ssal megfelel\u0151 form\u00e1tum\u00fa sz\u00f6vegg\u00e9. Futtassuk az alkalmaz\u00e1st (l\u00e9nyeges, hogy debuggolva, vagyis az F5 billenty\u0171vel) \u00e9s ellen\u0151rizz\u00fck a Visual Studio Output ablak\u00e1t, hogy val\u00f3ban megjelenik egy \u00faj sor 100 ms-k\u00e9nt. Ha minden j\u00f3l m\u0171k\u00f6dik, a Trace-el\u0151 sort kommentezz\u00fck ki.

                    A DispatcherTimer pontoss\u00e1ga

                    Azt megfigyelhetj\u00fck, hogy a DispatcherTimer nem k\u00fcl\u00f6n\u00f6sebben pontos, de c\u00e9ljainknak t\u00f6k\u00e9letesen megfelel. Ugyanakkor sz\u00e1munkra fontos tulajdons\u00e1ga, hogy a UI sz\u00e1lon h\u00edv\u00f3dik (a Tick esem\u00e9nye ezen s\u00fcl el), \u00edgy a kezel\u0151f\u00fcggv\u00e9ny\u00fcnkb\u0151l (Timer_Tick) hozz\u00e1 tudunk f\u00e9rni a fel\u00fcletelemekhez.

                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#foablak-fejlece","title":"F\u0151ablak fejl\u00e9ce","text":"

                    A f\u0151ablak fejl\u00e9ce a \"Tour de France\" sz\u00f6veg legyen, hozz\u00e1f\u0171zve a saj\u00e1t Neptun k\u00f3dod: (pl. \"ABCDEF\" Neptun k\u00f3d eset\u00e9n \"Tour de France - ABCDEF\"), fontos, hogy ez legyen a sz\u00f6veg! Ehhez a f\u0151ablakunk Title tulajdons\u00e1g\u00e1t \u00e1ll\u00edtsuk be erre a sz\u00f6vegre a MainWindow.xaml f\u00e1jlban.

                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-2-a-verseny-elokeszitese","title":"Feladat 2 \u2013 A verseny el\u0151k\u00e9sz\u00edt\u00e9se","text":"

                    A fentiek sor\u00e1n el is k\u00e9sz\u00fclt\u00fcnk a megjelen\u00edt\u00e9si logik\u00e1val, a f\u00f3kuszunkat most m\u00e1r az alkalmaz\u00e1slogik\u00e1ra, \u00e9s az ahhoz kapcsol\u00f3d\u00f3 sz\u00e1lkezel\u00e9si t\u00e9mak\u00f6rre helyezz\u00fck \u00e1t. Ennek megfelel\u0151en mostant\u00f3l els\u0151dlegesen a Game oszt\u00e1lyban fogunk dolgozni.

                    Eml\u00e9keztet\u0151k\u00e9nt, a megold\u00e1sunk alapelve a k\u00f6vetkez\u0151 lesz:

                    • Minden egyes biciklihez egy k\u00fcl\u00f6n sz\u00e1lat ind\u00edtunk.
                    • A j\u00e1t\u00e9k/szimul\u00e1ci\u00f3 iter\u00e1ci\u00f3kra bontott: minden iter\u00e1ci\u00f3ban a biciklihez tartoz\u00f3 sz\u00e1l (amennyiben az \u00e9ppen nem v\u00e1rakozik a verseny ind\u00edt\u00e1s\u00e1ra vagy a dep\u00f3ban) egy v\u00e9letlenszer\u0171 sz\u00e1m\u00e9rt\u00e9kkel l\u00e9p el\u0151re a p\u00e1ly\u00e1n, eg\u00e9szen addig, am\u00edg el nem \u00e9ri a c\u00e9lvonalat.

                    A k\u00f6vetkez\u0151 l\u00e9p\u00e9seknek megfelel\u0151en alak\u00edtsuk ki a kereteket:

                    1. A Game oszt\u00e1ly CreateBike f\u00fcggv\u00e9ny\u00e9nek a v\u00e9g\u00e9n ind\u00edtsunk el egy a ker\u00e9kp\u00e1rhoz tartoz\u00f3 sz\u00e1lat.
                    2. A sz\u00e1lf\u00fcggv\u00e9ny a Game oszt\u00e1lyban legyen.
                    3. A sz\u00e1lf\u00fcggv\u00e9nynek a CreateBike adja \u00e1t param\u00e9terk\u00e9nt a bicikli objektumot, melyet az adott sz\u00e1l mozgatni fog.
                    4. A fut\u00f3 sz\u00e1lak ne blokkolj\u00e1k az alkalmaz\u00e1s bez\u00e1r\u00e1s\u00e1t (vagyis, amikor bez\u00e1rjuk a f\u0151ablakot, de van m\u00e9g fut\u00f3 sz\u00e1l, a process azonnal sz\u0171nj\u00f6n meg, ne v\u00e1rja be ezeket a sz\u00e1lakat)
                    5. A sz\u00e1lf\u00fcggv\u00e9ny megval\u00f3s\u00edt\u00e1sa els\u0151 k\u00f6rben a k\u00f6vetkez\u0151kre terjedjen ki.

                      Egy ciklusban minden iter\u00e1ci\u00f3ban:

                      • v\u00e9letlenszer\u0171 l\u00e9p\u00e9ssel (Bike oszt\u00e1ly Step f\u00fcggv\u00e9ny\u00e9nek h\u00edv\u00e1sa) l\u00e9ptesse a biciklit,
                      • majd altassa a sz\u00e1lat 100 ms-ig.

                      Mindez a mozgat\u00e1s addig tartson, m\u00edg a bicikli el nem \u00e9ri a startvonalat (a poz\u00edci\u00f3ja el nem \u00e9ri a StartLinePosition tagv\u00e1ltoz\u00f3 \u00e1ltal meghat\u00e1rozott \u00e9rt\u00e9ket).

                    Pr\u00f3b\u00e1ld a fentieket \u00f6n\u00e1ll\u00f3an megval\u00f3s\u00edtani az el\u0151ad\u00e1son \u00e9s a laboron tanultak alapj\u00e1n. A megold\u00e1sod debuggol\u00e1ssal tudod tesztelni, illetve mivel a fel\u00fclet logik\u00e1t kor\u00e1bban megval\u00f3s\u00edtottuk, az alkalmaz\u00e1st futtatva a Prepare Race gombra kattintva is: ekkor a biciklik el kell g\u00f6rd\u00fcljenek fokozatosan haladva eg\u00e9szen a startvonalig.

                    Ezekhez a l\u00e9p\u00e9sekhez m\u00e9g adunk megold\u00e1st (de sokkal t\u00f6bbet tanulsz bel\u0151le, ha magad pr\u00f3b\u00e1lkozol, csak ellen\u0151rz\u00e9sk\u00e9pen haszn\u00e1ld a megold\u00e1st):

                    Megold\u00e1s

                    A Game oszt\u00e1lyban a sz\u00e1lf\u00fcggv\u00e9ny:

                    void BikeThreadFunction(object bikeAsObject)\n{\n    Bike bike = (Bike)bikeAsObject;\n    while (bike.Position <= StartLinePosition)\n    {\n        bike.Step();\n\n        Thread.Sleep(100);\n    }\n}\n

                    Mint l\u00e1that\u00f3, sz\u00e1lf\u00fcggv\u00e9nyn\u00e9l nem a param\u00e9ter n\u00e9lk\u00fcli, hanem az object param\u00e9ter\u0171 lehet\u0151s\u00e9get v\u00e1lasztottuk, hiszen a sz\u00e1lf\u00fcggv\u00e9nynek \u00e1t kell adni az \u00e1ltala mozgatott biciklit.

                    A sz\u00e1l ind\u00edt\u00e1sa a CreateBike f\u00fcggv\u00e9ny v\u00e9g\u00e9n:

                    private void CreateBike()\n{\n    ...\n\n    var thread = new Thread(BikeThreadFunction);\n    thread.IsBackground = true; // Ne blokkolja a sz\u00e1l a processz megsz\u0171n\u00e9s\u00e9t\n    thread.Start(bike); // itt adjuk \u00e1t param\u00e9terben a sz\u00e1lf\u00fcggv\u00e9nynek a biciklit\n}\n

                    BEADAND\u00d3

                    Miel\u0151tt tov\u00e1bbmenn\u00e9l a k\u00f6vetkez\u0151 feladatra, egy k\u00e9perny\u0151ment\u00e9st kell k\u00e9sz\u00edtened.

                    K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st Feladat1.png n\u00e9ven az al\u00e1bbiak szerint:

                    • Ind\u00edtsd el az alkalmaz\u00e1st. Ha sz\u00fcks\u00e9ges, m\u00e9retezd \u00e1t kisebbre, hogy ne foglaljon sok helyet a k\u00e9perny\u0151n,
                    • a \u201eh\u00e1tt\u00e9rben\u201d a Visual Studio legyen, a Game.cs megnyitva,
                    • VS-ben zoomolj \u00fagy, hogy a Game oszt\u00e1ly CreateBike \u00e9s BikeThreadFunction f\u00fcggv\u00e9nye l\u00e1that\u00f3 legyen, az el\u0151t\u00e9rben pedig az alkalmaz\u00e1sod ablaka.
                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-2-a-verseny-inditasa","title":"Feladat 2 \u2013 A verseny ind\u00edt\u00e1sa","text":"

                    Val\u00f3s\u00edtsd meg a verseny ind\u00edt\u00e1s\u00e1t a rajtvonalr\u00f3l \u00e9s futtat\u00e1s\u00e1t mindaddig, am\u00edg a biciklik meg nem \u00e9rkeznek a dep\u00f3ba, a k\u00f6vetkez\u0151 ir\u00e1nyelveknek megfelel\u0151en:

                    • A versenyt a Start Race gombkattint\u00e1s sor\u00e1n m\u00e1r h\u00edvott Game oszt\u00e1lybeli StartBikes f\u00fcggv\u00e9ny ind\u00edtsa.
                    • Fontos, hogy a StartBikes m\u0171veletben ne \u00faj sz\u00e1lakat ind\u00edtsunk, hanem meg kell oldani, hogy megl\u00e9v\u0151 sz\u00e1lak v\u00e1rakozzanak, majd a StartBikes f\u00fcggv\u00e9ny h\u00edv\u00e1s\u00e1nak \"hat\u00e1s\u00e1ra\" folytass\u00e1k fut\u00e1sukat.
                    • Ha a felhaszn\u00e1l\u00f3 azel\u0151tt nyomja meg a Start Race gombot, hogy a biciklik el\u00e9rn\u00e9k a startvonalat, akkor a bicikliknek m\u00e1r nem kell meg\u00e1llni a startvonalon (de az is teljesen j\u00f3 megold\u00e1s, ha ilyen esetben a gomb lenyom\u00e1s\u00e1t m\u00e9g figyelmen k\u00edv\u00fcl hagyja az alkalmaz\u00e1s).
                    • A biciklik eg\u00e9szen a dep\u00f3ig haladjanak el (m\u00edg poz\u00edci\u00f3juk el nem \u00e9ri a DepoPosition tagv\u00e1ltoz\u00f3 \u00e1ltal meghat\u00e1rozott \u00e9rt\u00e9ket).
                    • A Game oszt\u00e1lyban dolgozz.

                    Tipp a megold\u00e1shoz

                    Mivel a v\u00e1rakoz\u00e1st k\u00f6vet\u0151en a versenyz\u0151knek egyszerre kell indulniuk, a v\u00e1rakoz\u00e1s \u00e9s ind\u00edt\u00e1s megval\u00f3s\u00edt\u00e1s\u00e1ra egy ManualResetEvent objektumot c\u00e9lszer\u0171 haszn\u00e1lni.

                    BEADAND\u00d3

                    Miel\u0151tt tov\u00e1bbmenn\u00e9l a k\u00f6vetkez\u0151 feladatra, egy k\u00e9perny\u0151ment\u00e9st kell k\u00e9sz\u00edtened.

                    K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st Feladat2.png n\u00e9ven az al\u00e1bbiak szerint:

                    • Ind\u00edtsd el az alkalmaz\u00e1st. Ha sz\u00fcks\u00e9ges, m\u00e9retezd \u00e1t kisebbre, hogy ne foglaljon sok helyet a k\u00e9perny\u0151n,
                    • a \u201eh\u00e1tt\u00e9rben\u201d a Visual Studio legyen, a Game.cs megnyitva,
                    • VS-ben zoomolj \u00fagy, hogy a Game oszt\u00e1ly BikeThreadFunction f\u00fcggv\u00e9nye l\u00e1that\u00f3 legyen, az el\u0151t\u00e9rben pedig az alkalmaz\u00e1sod ablaka.
                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-3-a-versenyzok-inditasa-a-depobol","title":"Feladat 3 \u2013 A versenyz\u0151k ind\u00edt\u00e1sa a dep\u00f3b\u00f3l","text":"

                    Val\u00f3s\u00edtsd meg a versenyz\u0151k ind\u00edt\u00e1s\u00e1t a dep\u00f3b\u00f3l \u00e9s futtat\u00e1s\u00e1t mindaddig, am\u00edg a biciklik meg nem \u00e9rkeznek a c\u00e9lba, a k\u00f6vetkez\u0151 ir\u00e1nyelveknek megfelel\u0151en:

                    • Az egyes versenyz\u0151ket a Start Next Bike From Depo gombkattint\u00e1s sor\u00e1n m\u00e1r h\u00edvott Game oszt\u00e1lybeli StartNextBikeFromDepo f\u00fcggv\u00e9ny ind\u00edtsa a dep\u00f3b\u00f3l.
                    • Minden gombkattint\u00e1sra csak egyetlen versenyz\u0151 indulhat el a dep\u00f3b\u00f3l.
                    • Fontos, hogy a StartNextBikeFromDepo m\u0171veletben ne \u00faj sz\u00e1lakat ind\u00edtsunk, hanem meg kell oldani, hogy megl\u00e9v\u0151 sz\u00e1lak v\u00e1rakozzanak, majd a StartNextBikeFromDepo f\u00fcggv\u00e9ny h\u00edv\u00e1s\u00e1nak \"hat\u00e1s\u00e1ra\" folytass\u00e1k fut\u00e1sukat.
                    • Ha a felhaszn\u00e1l\u00f3 azel\u0151tt nyomja meg a Start Next Bike From Depo gombot, hogy a biciklik el\u00e9rn\u00e9k a dep\u00f3t, akkor egy bicikli m\u00e1r tov\u00e1bbmehet a dep\u00f3b\u00f3l, amikor meg\u00e9rkezik oda (de az is teljesen j\u00f3 megold\u00e1s, ha ilyen esetben a gomb lenyom\u00e1s\u00e1t m\u00e9g figyelmen k\u00edv\u00fcl hagyja az alkalmaz\u00e1s).
                    • A biciklik eg\u00e9szen a c\u00e9legyenesig haladjanak el (m\u00edg poz\u00edci\u00f3juk el nem \u00e9ri a FinishLinePosition tagv\u00e1ltoz\u00f3 \u00e1ltal meghat\u00e1rozott \u00e9rt\u00e9ket). Amikor egy bicikli el\u00e9ri a c\u00e9lvonalat, a biciklihez tartoz\u00f3 sz\u00e1l fejezze be a fut\u00e1s\u00e1t.
                    • A Game oszt\u00e1lyban dolgozz.

                    Tipp a megold\u00e1shoz

                    A feladat megold\u00e1sa anal\u00f3g az el\u0151z\u0151\u00e9vel, \u00e1m ez\u00fattal a ManualResetEvent helyett egy m\u00e1s t\u00edpus\u00fa, de hasonl\u00f3 objektumot kell haszn\u00e1lni...

                    BEADAND\u00d3

                    Miel\u0151tt tov\u00e1bbmenn\u00e9l a k\u00f6vetkez\u0151 feladatra, egy k\u00e9perny\u0151ment\u00e9st kell k\u00e9sz\u00edtened.

                    K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st Feladat3.png n\u00e9ven az al\u00e1bbiak szerint:

                    • Ind\u00edtsd el az alkalmaz\u00e1st. Ha sz\u00fcks\u00e9ges, m\u00e9retezd \u00e1t kisebbre, hogy ne foglaljon sok helyet a k\u00e9perny\u0151n,
                    • a \u201eh\u00e1tt\u00e9rben\u201d a Visual Studio legyen, a Game.cs megnyitva,
                    • VS-ben zoomolj \u00fagy, hogy a Game oszt\u00e1ly BikeThreadFunction f\u00fcggv\u00e9nye l\u00e1that\u00f3 legyen, az el\u0151t\u00e9rben pedig az alkalmaz\u00e1sod ablaka.
                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-4-gyoztes-bicikli-megvalositasa","title":"Feladat 4 \u2013 Gy\u0151ztes bicikli megval\u00f3s\u00edt\u00e1sa","text":"

                    Val\u00f3s\u00edtsd meg a gy\u0151ztes bicikli meghat\u00e1roz\u00e1s\u00e1nak \u00e9s megjelen\u00edt\u00e9s\u00e9nek logik\u00e1j\u00e1t, a k\u00f6vetkez\u0151 ir\u00e1nyelveknek megfelel\u0151en:

                    • A biciklik k\u00f6z\u00fcl az a gy\u0151ztes, mely el\u0151sz\u00f6r \u00e9ri ez a c\u00e9lvonalat (a poz\u00edci\u00f3ja el\u0151sz\u00f6r \u00e9ri el a FinishLinePosition tagv\u00e1ltoz\u00f3 \u00e1ltal meghat\u00e1rozott \u00e9rt\u00e9ket).
                    • A megold\u00e1s sor\u00e1n haszn\u00e1ld fel, hogy a Bike oszt\u00e1lyban m\u00e1r van egy isWinner v\u00e1ltoz\u00f3, mely \u00e9rt\u00e9ke kezdetben hamis, \u00e9s a SetAsWinner m\u0171velettel igazz\u00e1 tehet\u0151, illetve az \u00e9rt\u00e9ke az IsWinner tulajdons\u00e1ggal lek\u00e9rdezhet\u0151.
                    • Annak eld\u00f6nt\u00e9se, hogy az adott bicikli lett-e a gy\u0151ztes, a Game oszt\u00e1lyban biciklihez tartoz\u00f3 sz\u00e1lf\u00fcggv\u00e9ny feladata, ide tedd a d\u00f6nt\u00e9si logik\u00e1t.
                    • Kulcsfontoss\u00e1g\u00fa, hogy pontosan egy gy\u0151ztes lehet. Ha egyn\u00e9l t\u00f6bb bicikli ker\u00fcl gy\u0151ztesnek megjel\u00f6l\u00e9sre (vagyis a Bike oszt\u00e1ly SetAsWinner m\u0171velete t\u00f6bb biciklire is megh\u00edv\u00e1sra ker\u00fcl), az nagyon s\u00falyos hiba!
                    • A Game oszt\u00e1lyban dolgozz.

                    A logika megval\u00f3s\u00edt\u00e1sa el\u0151tt egy kicsit finom\u00edtunk a megjelen\u00edt\u00e9sen, annak \u00e9rdek\u00e9ben, hogy a gy\u0151ztes bicikli megk\u00fcl\u00f6nb\u00f6ztethet\u0151 legyen a t\u00f6bbit\u0151l a fel\u00fcleten. Ehhez a MainWindow oszt\u00e1ly UpdateUI f\u00fcggv\u00e9ny\u00e9be tegy\u00fcnk be egy kis plusz logik\u00e1t: ha az adott bicikli gy\u0151ztes lett, akkor a megjelen\u00edt\u00e9s\u00e9t v\u00e1ltoztassuk \u00e1t egy serlegre. Ehhez a biciklihez tartoz\u00f3 TextBlock sz\u00f6veg\u00e9t kell \"%\"-ra v\u00e1ltoztatni:

                    private void UpdateUI()\n{\n    for (int i = 0; i < game.Bikes.Count;i++)\n    {\n        ...\n\n        if (bike.IsWinner)\n            tbBike.Text = \"%\";\n    }\n}\n

                    A logik\u00e1t ezt k\u00f6vet\u0151en \u00f6n\u00e1ll\u00f3an val\u00f3s\u00edtsd meg, az al\u00e1bbi ir\u00e1nyleveknek \u00e9s tippeknek megfelel\u0151en.

                    Ir\u00e1nyelvek \u00e9s tippek a megold\u00e1shoz

                    • Annak eld\u00f6nt\u00e9s\u00e9re, hogy volt-e m\u00e1r gy\u0151ztes, a Game oszt\u00e1lyban vezess be egy bool hasWinner seg\u00e9dv\u00e1ltoz\u00f3t (ez azt jelezze, volt-e m\u00e1r gy\u0151ztes hirdetve).
                    • El\u0151ad\u00e1son egy nagyon hasonl\u00f3 p\u00e9lda szerepelt a \"A lock haszn\u00e1lata\" t\u00e9mak\u00f6rben, r\u00e9szletes magyar\u00e1zattal.
                    • A megold\u00e1snak akkor is j\u00f3l kell m\u0171k\u00f6dnie (egy gy\u0151ztes lehet \u00e9s nem t\u00f6bb), ha a hasWinner felt\u00e9telvizsg\u00e1lat \u00e9s a hasWinner igazba \u00e1ll\u00edt\u00e1sa k\u00f6z\u00e9 egy hosszabb mesters\u00e9ges k\u00e9sleltet\u00e9s ker\u00fcl, azt szimul\u00e1lva, hogy a sz\u00e1l \"pechesen\" itt veszti el a fut\u00e1si jog\u00e1t, \u00e9s a dep\u00f3b\u00f3l a biciklik \"azonnal\" tov\u00e1bb vannak engedve (vagyis k\u00f6zel egyszerre \u00e9rnek a c\u00e9lba).
                    • A tesztel\u00e9s idej\u00e9re tegy\u00e9l ide (a felt\u00e9telvizsg\u00e1lat \u00e9s hasWinner \u00e1ll\u00edt\u00e1sa k\u00f6z\u00e9) egy Thread.Sleep(2000) sort, melyet tesztel\u00e9s ut\u00e1n kommentezz ki. Term\u00e9szetesen \u00fagy tesztelj, hogy a bicikliket a dep\u00f3b\u00f3l min\u00e9l ink\u00e1bb egyszerre engedd tov\u00e1bb a gombkattint\u00e1sokkal, hogy a biciklik kb. egyszerre \u00e9rjenek a c\u00e9lba. Ha t\u00f6bb gy\u0151ztes is lenne (mert nem j\u00f3 a megold\u00e1sod), akkor a c\u00e9lban t\u00f6bb bicikli is serlegg\u00e9 v\u00e1lik!

                    BEADAND\u00d3

                    Miel\u0151tt tov\u00e1bbmenn\u00e9l a k\u00f6vetkez\u0151 feladatra, egy k\u00e9perny\u0151ment\u00e9st kell k\u00e9sz\u00edtened.

                    K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st Feladat4.png n\u00e9ven az al\u00e1bbiak szerint:

                    • Ind\u00edtsd el az alkalmaz\u00e1st. Ha sz\u00fcks\u00e9ges, m\u00e9retezd \u00e1t kisebbre, hogy ne foglaljon sok helyet a k\u00e9perny\u0151n,
                    • a \u201eh\u00e1tt\u00e9rben\u201d a Visual Studio legyen, a Game.cs megnyitva,
                    • VS-ben zoomolj \u00fagy, hogy a Game oszt\u00e1ly BikeThreadFunction f\u00fcggv\u00e9nye l\u00e1that\u00f3 legyen, az el\u0151t\u00e9rben pedig az alkalmaz\u00e1sod ablaka.
                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-5-kolcsonos-kizaras-valamint-volatile","title":"Feladat 5 \u2013 K\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1s, valamint volatile","text":"

                    Az el\u0151z\u0151 feladatban l\u00e1ttuk, hogy a hasWinner lek\u00e9rdez\u00e9s\u00e9t \u00e9s be\u00e1ll\u00edt\u00e1s\u00e1t \"oszthatatlann\u00e1\", \"atomiv\u00e1\" kellett tegy\u00fck, vagyis ennek sor\u00e1n meg kellett val\u00f3s\u00edtsuk a k\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1st. K\u00e9rd\u00e9s, van-e esetleg m\u00e1r olyan m\u00e1s logika is az alkalmaz\u00e1sban, ahol ezt meg kellet volna tenni a konzisztencia garant\u00e1l\u00e1s\u00e1nak \u00e9rdek\u00e9ben. Ehhez azt kell megvizsg\u00e1ljuk, melyek azok a v\u00e1ltoz\u00f3k, melyeket t\u00f6bb sz\u00e1lb\u00f3l is \u00edrunk (vagy egyikb\u0151l \u00edrunk \u00e9s m\u00e1sikb\u00f3l olvasunk). A k\u00f6vetkez\u0151k \u00e9rintettek:

                    • Bike oszt\u00e1ly position tagja. Ezt a biciklik sz\u00e1lf\u00fcggv\u00e9nye m\u00f3dos\u00edtja a += oper\u00e1torral, a f\u0151sz\u00e1l pedig olvassa a Position property seg\u00edts\u00e9g\u00e9vel a megjelen\u00edt\u00e9s sor\u00e1n. K\u00e9rd\u00e9s, lehet-e ebb\u0151l b\u00e1rmif\u00e9le inkonzisztencia (mert ha igen, akkor meg kellene val\u00f3s\u00edtani a k\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1st, pl. a lock utas\u00edt\u00e1s seg\u00edts\u00e9g\u00e9vel). Ez m\u00e9lyebb \u00e1tgondol\u00e1st ig\u00e9nyel. Az int t\u00edpus\u00fa v\u00e1ltoz\u00f3k olvas\u00e1sa \u00e9s \u00edr\u00e1sa (sima = oper\u00e1tor) atomi, \u00edgy ez rendben is volna. Csakhogy itt m\u00f3dos\u00edt\u00e1sra nem az =, hanem += oper\u00e1tort haszn\u00e1ljuk. A += oper\u00e1tor nem atomi, t\u00f6bb l\u00e9p\u00e9sb\u0151l \u00e1ll: v\u00e1ltoz\u00f3 kiolvas\u00e1sa, n\u00f6vel\u00e9se, majd vissza\u00edr\u00e1sa (ha nem tiszta, pontosan mi\u00e9rt \u00e9s milyen probl\u00e9ma l\u00e9phet fel, mindenk\u00e9ppen n\u00e9zd \u00e1t a kapcsol\u00f3d\u00f3 el\u0151ad\u00e1s di\u00e1t). \u00cdgy, ha t\u00f6bb sz\u00e1l is haszn\u00e1lja \"egyszerre\" a += oper\u00e1tort ugyanazon a v\u00e1ltoz\u00f3n, akkor abb\u00f3l inkonzisztencia lehet. De ne kapkodjunk, gondoljunk bele jobban: a mi eset\u00fcnkben egyszerre egy sz\u00e1l h\u00edv +=-t, a m\u00e1sik sz\u00e1lunk csak olvassa a position \u00e9rt\u00e9k\u00e9t. Ebb\u0151l nem lehet inkonzisztencia, mert egyszer\u0171en csak arr\u00f3l van sz\u00f3, hogy az olvas\u00e1s el\u0151tt vagy a n\u00f6vel\u00e9s el\u0151tti \u00e9rt\u00e9ket, vagy az ut\u00e1ni \u00e9rt\u00e9ket kapja meg az olvas\u00f3 sz\u00e1l (ha szinte pont egyszerre olvas a += oper\u00e1tor-t v\u00e9grehajt\u00f3 m\u00e1sik sz\u00e1llal). \u00cdgy kijelenthetj\u00fck, ennek kapcs\u00e1n nincs sz\u00fcks\u00e9g k\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1s megval\u00f3s\u00edt\u00e1s\u00e1ra.
                    • Bike oszt\u00e1ly isWinner tagja. Ezt a biciklik sz\u00e1lf\u00fcggv\u00e9nye m\u00f3dos\u00edtja a SetAsWinner h\u00edv\u00e1s\u00e1val, a f\u0151sz\u00e1l pedig olvassa az IsWinner property seg\u00edts\u00e9g\u00e9vel a megjelen\u00edt\u00e9s sor\u00e1n. T\u00edpusa bool, melynek \u00edr\u00e1sa \u00e9s olvas\u00e1sa atomi, \u00edgy nincs sz\u00fcks\u00e9g k\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1s megval\u00f3s\u00edt\u00e1s\u00e1ra.
                    • Game oszt\u00e1ly hasWinner tagja. T\u00edpusa bool, melynek \u00edr\u00e1sa \u00e9s olvas\u00e1sa atomi, \u00edgy amiatt sz\u00fcks\u00e9g k\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1s megval\u00f3s\u00edt\u00e1s\u00e1ra. De volt egy plusz felt\u00e9tel\u00fcnk: csak egy gy\u0151ztes lehet versenyben, emiatt m\u00e9gis sz\u00fcks\u00e9g volt k\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1s megval\u00f3s\u00edt\u00e1s\u00e1ra (ezt az el\u0151z\u0151 feladatban meg is tett\u00fck).

                    Azt is mondhatn\u00e1nk, hogy a fenti h\u00e1rom v\u00e1ltoz\u00f3 tekintet\u00e9ben akkor minden rendben is van, de ez nincs \u00edgy. Amikor a v\u00e1ltoz\u00f3k \u00e9rt\u00e9k\u00e9t az egyik sz\u00e1l m\u00f3dos\u00edtja, el\u0151fordulhat, hogy a v\u00e1ltoz\u00f3k \u00e9rt\u00e9k\u00e9t a rendszer cache-eli (pl. regiszterben), \u00edgy a m\u00e1sik sz\u00e1l a v\u00e1ltoztat\u00e1s ut\u00e1n is a kor\u00e1bbi \u00e9rt\u00e9ket l\u00e1tja. Ennek megakad\u00e1lyoz\u00e1s\u00e1ra ezeket a v\u00e1ltoz\u00f3kat volatile-nak kell defini\u00e1lni a volatile kulcssz\u00f3val, mely a v\u00e1ltoz\u00f3 megv\u00e1ltoztat\u00e1sa ut\u00e1n garant\u00e1lja, hogy annak ki\u00edr\u00e1sa megt\u00f6rt\u00e9nik a mem\u00f3ri\u00e1ba, \u00e9s a m\u00e1sik sz\u00e1l friss \u00e9rt\u00e9ket olvas (a volatile m\u0171k\u00f6d\u00e9se enn\u00e9l valamivel \u00f6sszetettebb, el\u0151ad\u00e1son b\u0151vebben kifejt\u00e9sre ker\u00fcl). Fontos megjegyz\u00e9s: a volatile alkalmaz\u00e1s\u00e1ra nincs sz\u00fcks\u00e9g, ha az adott v\u00e1ltoz\u00f3t lock blokkb\u00f3l \u00edrjuk \u00e9s olvassuk, vagy az Interlocked oszt\u00e1ly seg\u00edts\u00e9g\u00e9vel m\u00f3dos\u00edtjuk. Amiatt csak a position \u00e9s az isWinner eset\u00e9ben vezess\u00fck be:

                    class Bike\n{\n    private volatile int position = 65;\n    private volatile bool isWinner;\n
                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-5-lepesek-naplozasa-nem-szalbiztos-net-osztalyok","title":"Feladat 5 \u2013 L\u00e9p\u00e9sek napl\u00f3z\u00e1sa (nem sz\u00e1lbiztos .NET oszt\u00e1lyok)","text":"

                    Val\u00f3s\u00edtsd meg a verseny sor\u00e1n a biciklik \u00e1ltal megtett minden egyes l\u00e9p\u00e9s napl\u00f3z\u00e1s\u00e1t a Game oszt\u00e1lyban egy (minden biciklire k\u00f6z\u00f6s) List<int> t\u00edpus\u00fa v\u00e1ltoz\u00f3ba. A napl\u00f3zott \u00e9rt\u00e9kekkel nem kell semmit csin\u00e1lni (pl. megjelen\u00edteni sem). A megold\u00e1s sor\u00e1n ki kell haszn\u00e1lni, hogy a Bike oszt\u00e1ly Step m\u0171velete visszaadja a megtett l\u00e9p\u00e9st egy int v\u00e1ltoz\u00f3 form\u00e1j\u00e1ban, ezt kell napl\u00f3zni (csak bele kell tenni a list\u00e1ba).

                    Tipp a megold\u00e1shoz

                    Mivel a List<T> oszt\u00e1ly nem sz\u00e1lbiztos (nem thread safe), \u00e9s t\u00f6bb sz\u00e1lb\u00f3l is \u00edrunk bele, meg kell val\u00f3s\u00edtani a hozz\u00e1f\u00e9r\u00e9s sor\u00e1n a k\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1st a lock utas\u00edt\u00e1s seg\u00edts\u00e9g\u00e9vel.

                    System.Collections.Concurrent n\u00e9vt\u00e9r gy\u0171jtem\u00e9nyoszt\u00e1lyai

                    Ha a List<T> helyett egy a c\u00e9lnak megfelel\u0151, System.Collections.Concurrent n\u00e9vt\u00e9rbeli oszt\u00e1ly objektum\u00e1ba napl\u00f3zn\u00e1nk (pl. ConcurrentQueue), akkor nem lenne sz\u00fcks\u00e9g a k\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1s megval\u00f3s\u00edt\u00e1s\u00e1ra, mert ebben a n\u00e9vt\u00e9rben sz\u00e1lbiztos (thread safe) gy\u0171jtem\u00e9nyoszt\u00e1lyok tal\u00e1lhat\u00f3k.

                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat-6-felulet-frissitese-minden-valtozas-eseten-felhasznaloi-feluletelemek-elerese-munkaszalakbol","title":"Feladat 6 \u2013 Fel\u00fclet friss\u00edt\u00e9se minden v\u00e1ltoz\u00e1s eset\u00e9n (felhaszn\u00e1l\u00f3i fel\u00fcletelemek el\u00e9r\u00e9se munkasz\u00e1lakb\u00f3l)","text":"

                    Aktu\u00e1lis megold\u00e1sunkban a fel\u00fclet friss\u00edt\u00e9s\u00e9t periodikusan, adott id\u0151k\u00f6z\u00f6nk\u00e9nt val\u00f3s\u00edtjuk meg egy id\u0151z\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel. Ezt a megold\u00e1st most lecser\u00e9lj\u00fck. Alak\u00edtsd \u00e1t a megold\u00e1st \u00fagy, hogy a fel\u00fclet friss\u00edt\u00e9se minden esetben azonnal megt\u00f6rt\u00e9njen, amikor a Game \u00e1llapota megv\u00e1ltozik (az id\u0151z\u00edtett friss\u00edt\u00e9st pedig m\u00e1r ne haszn\u00e1ld).

                    A k\u00f6vetkez\u0151 fejezetben a lehets\u00e9ges megold\u00e1sok r\u00f6viden \u00e1ttekint\u00e9sre ker\u00fclnek, \u00e9s v\u00e1lasztunk is egyet k\u00f6z\u00fcl\u00fck, de el\u0151bb pr\u00f3b\u00e1ld magadt\u00f3l \u00e1tgondolni, milyen megold\u00e1st c\u00e9lszer\u0171 ehhez v\u00e1lasztani. Kulcsfontoss\u00e1g\u00fa, hogy csak olyan megold\u00e1s fogadhat\u00f3 el, mely nem vezet be az alkalmaz\u00e1slogik\u00e1ban (Game oszt\u00e1ly) f\u00fcgg\u0151s\u00e9get a fel\u00fcltett\u0151l. Eml\u00e9kezz\u00fcnk vissza, az alapelv\u00fcnk az volt, hogy az alkalmaz\u00e1slogika nem f\u00fcgghet semmilyen szinten a fel\u00fclet logik\u00e1t\u00f3l!

                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#a-felulet-ertesitesenek-megvalositasa","title":"A fel\u00fclet \u00e9rtes\u00edt\u00e9s\u00e9nek megval\u00f3s\u00edt\u00e1sa","text":"

                    Alternat\u00edv\u00e1k:

                    1. Alkalmazhatjuk az Observer tervez\u00e9si mint\u00e1t. Err\u0151l a f\u00e9l\u00e9v sor\u00e1n k\u00e9s\u0151bb fogunk tanulni, b\u00e1r \u00e9rdemes megjegyezni, hogy a C# esem\u00e9nyek is az Observer minta alapkoncepci\u00f3ira \u00e9p\u00fclnek.
                    2. K\u00e9zenfekv\u0151 megold\u00e1s lehet egy C# esem\u00e9ny bevezet\u00e9se (pl. BikeStateChanged n\u00e9ven), melyet a Game oszt\u00e1ly akkor s\u00fct el, amikor egy bicikli \u00e1llapota megv\u00e1ltozott, param\u00e9terk\u00e9nt \u00e1tadva a bicikli objektumot. Ez egy kerek, \u00e1ltal\u00e1nos megold\u00e1s lenne: b\u00e1rmikor, b\u00e1rmely oszt\u00e1ly feliratkozhatna az esem\u00e9nyre. Ehhez - ha k\u00f6vetni szeretn\u00e9nk a Microsoft aj\u00e1nl\u00e1sokat - be kellene vezetni egy EventArgs lesz\u00e1rmazott oszt\u00e1lyt (esem\u00e9ny param\u00e9ter), \u00e9s be kellene vezetni egy \u00faj delegate t\u00edpust (vagy haszn\u00e1lhatn\u00e1nk a be\u00e9p\u00edtett EventHandler<TEventArgs> generikus delegate t\u00edpust).
                    3. Az el\u0151z\u0151 pontban eml\u00edtett C# esem\u00e9ny alap\u00fa megold\u00e1s teljesen \"korrekt\" lenne, ugyanakkor nek\u00fcnk nem felt\u00e9tlen c\u00e9lunk, hogy b\u00e1rmikor b\u00e1rmely oszt\u00e1ly feliratkozhasson az \u00e1llapotv\u00e1ltoz\u00e1s esem\u00e9nyre. Emiatt \u00e1tgondolhatunk egy \"c\u00e9lir\u00e1nyosabb\" megold\u00e1st (\u00e9s ezt is fogjuk alkalmazni). Ez, b\u00e1r delegate-et haszn\u00e1l, nem vezet be event esem\u00e9nyt, \u00e9s alapvet\u0151en csak egyetlen objektum sz\u00e1m\u00e1ra biztos\u00edt \u00e9rtes\u00edt\u00e9st/visszah\u00edv\u00e1st (a MainWindow-nak, hiszen \u0151 kell friss\u00edtse a fel\u00fclet\u00e9t, amikor v\u00e1ltozik egy bicikli \u00e1llapota). Ezen megk\u00f6zel\u00edt\u00e9s elemei a k\u00f6vetkez\u0151k:

                      • Game oszt\u00e1ly, mint \"\u00e9rtes\u00edt\u0151\":
                        • Azt a f\u00fcggv\u00e9nyt (delegate objektumot), melyet Game oszt\u00e1ly a biciklik \u00e1llapot\u00e1nak v\u00e1ltoz\u00e1sakor megh\u00edv (\u00e9rtes\u00edt\u00e9s/visszah\u00edv\u00e1s), a PrepareRace m\u0171velet param\u00e9terek\u00e9nt kapja meg a Game oszt\u00e1ly, melyet egy tagv\u00e1ltoz\u00f3ban el is t\u00e1rol.
                        • Ennek a param\u00e9ternek \u00e9s tagv\u00e1ltoz\u00f3nak a t\u00edpusa legyen Action<Bike> (az Action \u00e9s Action<T> t\u00edpusokr\u00f3l m\u00e1r kor\u00e1bban tanultunk).
                        • Amikor megv\u00e1ltozik egy bicikli \u00e1llapota (helye vagy \"nyertes\" \u00e1llapota a sz\u00e1lf\u00fcggv\u00e9nyben), akkor a Game oszt\u00e1ly h\u00edvja meg ezt a tagv\u00e1ltoz\u00f3ban t\u00e1rolt f\u00fcggv\u00e9nyt (de csak ha nem null, vagyis ez a f\u00fcggv\u00e9ny m\u00e1r be lett \u00e1ll\u00edtva, ill. a ?.Invoke is haszn\u00e1lhat\u00f3), param\u00e9terk\u00e9nt \u00e1tadva neki a megv\u00e1ltozott bicikli objektumot. Ez\u00e1ltal \u00e9rtes\u00edti az el\u0151fizet\u0151t.
                      • MainWindow, mint \"el\u0151fizet\u0151\":
                        • A MainWindow oszt\u00e1lyban be kell vezetni egy UpdateBikeUI(Bike bike) f\u00fcggv\u00e9nyt, \u00e9s a Game.PrepareRace h\u00edv\u00e1sakor ezt kell \u00e1tadni param\u00e9terk\u00e9nt (delegate objektumk\u00e9nt). Ebben az UpdateBikeUI f\u00fcggv\u00e9nyben kell gondoskodni arr\u00f3l, hogy a param\u00e9terk\u00e9nt kapott bicikli objektumhoz tartoz\u00f3 fel\u00fcletelem (TextBlock) friss\u00fclj\u00f6n.
                        • Az el\u0151z\u0151 pontban v\u00e1lik egy\u00e9rtelm\u0171v\u00e9, mi\u00e9rt Action<Bike> t\u00edpus\u00fa delegate-et haszn\u00e1ltunk, \u00e9s mi\u00e9rt nem pl. Action-t: a Game a \u00e9rtes\u00edt\u00e9s/visszah\u00edv\u00e1s sor\u00e1n \u00edgy meg tudja adna, mely bicikli v\u00e1ltozott, \u00e9s a visszah\u00edvott/beregisztr\u00e1lt f\u00fcggv\u00e9ny (eset\u00fcnkben MainWindow.UpdateBikeUI) \u00edgy megkapja ezt param\u00e9terben, \u00e9s \u00edgy tudja a megjelen\u00e9s\u00e9t friss\u00edteni (kapott bicikli \u00e1llapota alapj\u00e1n).
                      • Az id\u0151z\u00edt\u0151 ind\u00edt\u00e1s\u00e1t (MainWindow konstruktorban timer.Start() h\u00edv\u00e1s) kommentezd ki (hiszen a fel\u00fclet friss\u00edt\u00e9s\u00e9t m\u00e1r a fenti Action<Bike>) alap\u00fa \u00e9rtes\u00edt\u00e9s/visszah\u00edv\u00e1s seg\u00edts\u00e9g\u00e9vel oldjuk meg.

                    Val\u00f3s\u00edtsd meg a fenti 3. pontban v\u00e1zolt \u00e9rtes\u00edt\u00e9st! A MainWindow.UpdateBikeUI implement\u00e1ci\u00f3j\u00e1t megadjuk seg\u00edts\u00e9gk\u00e9ppen (a l\u00e9nyege az, hogy a param\u00e9terben kapott Bike alapj\u00e1n friss\u00edti a biciklit megjelen\u00edt\u0151 TextBlock-ot):

                    private void UpdateBikeUI(Bike bike)\n{\n    // El\u0151fordulhat, hogy az UpdateBikeUI olyan kor\u00e1n h\u00edv\u00f3dik, hogy a\n    // bikeTextBlocks m\u00e9g nincs felt\u00f6ltve, ilyenkor m\u00e9g nem tudjuk friss\u00edteni\n    // a fel\u00fcletet, t\u00e9rj\u00fcnk vissza.\n    if (bikeTextBlocks.Count != game.Bikes.Count)\n        return;\n\n    int marginAdjustmentForWheel = 8;\n\n    // Biciklihez tartoz\u00f3 TextBlock kikeres\u00e9se (azonos t\u00f6mbindex alapj\u00e1n).\n    var tbBike = bikeTextBlocks[game.Bikes.IndexOf(bike)];\n\n    // Akkor m\u00e9g ne \u00e1ll\u00edtsuk a bicikli poz\u00edci\u00f3j\u00e1t, amikor a m\u00e9rete a layout sor\u00e1n nem\n    // ker\u00fclt meghat\u00e1roz\u00e1sra (k\u00fcl\u00f6nben ugr\u00e1lna a bicikli, hiszen al\u00e1bb, a marg\u00f3 be\u00e1ll\u00edt\u00e1sakor\n    // \"\u00e9rv\u00e9nytelen\" 0 sz\u00e9less\u00e9g\u00e9rt\u00e9kkel sz\u00e1moln\u00e1nk.\n    if (tbBike.ActualWidth == 0)\n        return;\n\n    // Az ablak 0,0 pontja az orig\u00f3, ehhez k\u00e9pest n\u00e9zz\u00fck a start/dep\u00f3/finish vonalat.\n    // A gomb jobb sz\u00e9l\u00e9n van a ker\u00e9k, de ezt a gomb bal oldal\u00e1ra kell mozgatni: ActualWidth-et ki kell vonni.\n    tbBike.Margin = new Thickness(bike.Position - tbBike.ActualWidth + marginAdjustmentForWheel, 0, 0, 0);\n\n    if (bike.IsWinner)\n        tbBike.Text = \"%\"; // display a cup\n}\n

                    Fontos

                    A fenti l\u00e9p\u00e9sek/elvek megfelel\u0151 k\u00f6vet\u00e9se eset\u00e9n is fenn\u00e1ll, hogy megold\u00e1s m\u00e9g nem m\u0171k\u00f6d\u0151k\u00e9pes. Ha elind\u00edtjuk a versenyt, az al\u00e1bbi kiv\u00e9tel dob\u00f3dik az UpdateBikeUI f\u00fcggv\u00e9nyben a biciklihez tartoz\u00f3 TextBlock hozz\u00e1f\u00e9r\u00e9s sor\u00e1n: System.Runtime.InteropServices.COMException: 'The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD))

                    Mi ennek a hib\u00e1nak az oka? Miel\u0151tt az al\u00e1bbi eml\u00e9keztet\u0151t kinyitod, pr\u00f3b\u00e1lj magadt\u00f3l r\u00e1j\u00f6nni az el\u0151ad\u00e1son/laboron tanultak alapj\u00e1n.

                    Eml\u00e9keztet\u0151

                    Egy WinUI fel\u00fcletelemhez/vez\u00e9rl\u0151h\u00f6z csak abb\u00f3l a sz\u00e1lb\u00f3l lehet hozz\u00e1f\u00e9rni, mely az adott fel\u00fcletelemet l\u00e9trehozta, ugyanis ezek a fel\u00fcletelemek nem sz\u00e1lbiztosak, \u00e9s kiv\u00e9tel dob\u00e1s\u00e1val jelzik, ha m\u00e9gis \u201erosszul\u201d pr\u00f3b\u00e1ljuk \u0151ket haszn\u00e1lni.

                    A megold\u00e1st a k\u00f6vetkez\u0151 r\u00e9szfeladatban dolgozzuk ki.

                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#a-dispatecherqueue-alkalmazasa","title":"A DispatecherQueue alkalmaz\u00e1sa","text":"

                    Eset\u00fcnkben a konkr\u00e9t probl\u00e9m\u00e1t az okozza, hogy amikor a Game \u00e1llapota megv\u00e1ltozik, akkor Game oszt\u00e1lyban a v\u00e1ltoz\u00e1s\u00e9rtes\u00edt\u0151 delegate h\u00edv\u00e1sa a biciklikhez tartoz\u00f3 munkasz\u00e1lakon t\u00f6rt\u00e9nik, \u00edgy a beregisztr\u00e1lt MainWindow.UpdateBikeUI kezel\u0151f\u00fcggv\u00e9ny is ezekr\u0151l a sz\u00e1lakr\u00f3l h\u00edv\u00f3dik. Az UpdateBikeUI f\u00fcggv\u00e9nyben hozz\u00e1f\u00e9r\u00fcnk a fel\u00fcletelemekhez (biciklihez tartoz\u00f3 TextBlock- hoz). De ezeket a fel\u00fcletelemeket a f\u0151sz\u00e1lb\u00f3l hoztuk l\u00e9tre: \u00edgy csak a f\u0151 sz\u00e1lb\u00f3l szabad(na) hozz\u00e1juk f\u00e9rni.

                    A probl\u00e9m\u00e1ra a DispatcherQueue alkalmaz\u00e1sa jelent megold\u00e1st, mellyel a munkasz\u00e1lakb\u00f3l a h\u00edv\u00e1st \"\u00e1t tudjuk j\u00e1tszani\" a f\u0151sz\u00e1lba, melyb\u0151l m\u00e1r hozz\u00e1 tudunk f\u00e9rni a vez\u00e9rl\u0151kh\u00f6z. A DispacherQueue alkalmaz\u00e1sa el\u0151ad\u00e1son \u00e9s a kapcsol\u00f3d\u00f3 laboron is r\u00e9szletesen ismertet\u00e9sre ker\u00fclt.

                    Feladat: m\u00f3dos\u00edtsd \u00fagy a MainWindow.UpdateBikeUI f\u00fcggv\u00e9nyt, hogy a DispacherQueue alkalmaz\u00e1s\u00e1val a megfelel\u0151 sz\u00e1lb\u00f3l t\u00f6rt\u00e9njen a fel\u00fcletelemekhez t\u00f6rt\u00e9n\u0151 hozz\u00e1f\u00e9r\u00e9s (\u00e9s \u00edgy a mostani kiv\u00e9telt el tudd ker\u00fclni).

                    BEADAND\u00d3

                    Miel\u0151tt tov\u00e1bbmenn\u00e9l a k\u00f6vetkez\u0151 feladatra, egy k\u00e9perny\u0151ment\u00e9st kell k\u00e9sz\u00edtened.

                    K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st Feladat6.png n\u00e9ven az al\u00e1bbiak szerint:

                    • Ind\u00edtsd el az alkalmaz\u00e1st. Ha sz\u00fcks\u00e9ges, m\u00e9retezd \u00e1t kisebbre, hogy ne foglaljon sok helyet a k\u00e9perny\u0151n,
                    • a \u201eh\u00e1tt\u00e9rben\u201d a Visual Studio legyen, a MainWindow.xaml.cs megnyitva,
                    • VS-ben zoomolj \u00fagy, hogy a MainWindow oszt\u00e1ly UpdateBikeUI f\u00fcggv\u00e9nye l\u00e1that\u00f3 legyen, az el\u0151t\u00e9rben pedig az alkalmaz\u00e1sod ablaka.

                    Hasonl\u00f3 j\u00e1t\u00e9k megval\u00f3s\u00edt\u00e1sa a gyakorlatban

                    L\u00e9nyeges, hogy egy hasonl\u00f3 \"j\u00e1t\u00e9k\" megval\u00f3s\u00edt\u00e1s\u00e1ra nem szoktunk sz\u00e1lakat ind\u00edtani: a biciklik l\u00e9ptet\u00e9s\u00e9re egy timer sokkal praktikusabb lenne, mert az eg\u00e9sz j\u00e1t\u00e9k egysz\u00e1l\u00fa maradhatna, \u00e9s elker\u00fclhetn\u00e9nk sz\u00e1mos, a t\u00f6bbsz\u00e1l\u00fas\u00e1gb\u00f3l ad\u00f3d\u00f3 neh\u00e9zs\u00e9ge (jelen feladat keret\u00e9ben a c\u00e9lunk \u00e9rtelemszer\u0171en pont a t\u00f6bbsz\u00e1l\u00fas\u00e1g t\u00e9mak\u00f6r\u00e9nek gyakorl\u00e1sa volt).

                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#opcionalis-feladat-2-imsc-pontert","title":"Opcion\u00e1lis feladat \u2013 2 IMSc pont\u00e9rt","text":""},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#feladat","title":"Feladat","text":"

                    Tedd lehet\u0151v\u00e9 a biciklik gombkattint\u00e1sra t\u00f6rt\u00e9n\u0151 meg\u00e1ll\u00edt\u00e1s\u00e1t:

                    • Helyezz el egy gombot jobbra a t\u00f6bbit\u0151l, Stop Race felirattal.
                    • A Stop Race gombra kattint\u00e1s \u00e1ll\u00edtsa meg az \u00f6sszes biciklit, \u00e9s \u00e1ll\u00edtsa le a bicikliket futtat\u00f3 sz\u00e1lakat is. Ehhez vezess be egy StopRace publikus f\u00fcggv\u00e9nyt a Game oszt\u00e1lyba.
                    • A verseny ak\u00e1r az elind\u00edt\u00e1sa el\u0151tt is le\u00e1ll\u00edthat\u00f3 legyen.
                    • A StopRace m\u0171velet sz\u00e1lak le\u00e1ll\u00edt\u00e1sa ut\u00e1n v\u00e1rja meg, m\u00edg valamennyi sz\u00e1l val\u00f3ban be is fejezi a fut\u00e1s\u00e1t.
                    • A verseny le\u00e1ll\u00edt\u00e1sa ut\u00e1n (Stop Race kattint\u00e1s) semelyik gombra ne lehessen kattintani (minden gomb legyen letiltva, IsEnabled tulajdons\u00e1gukat \u00e1ll\u00edtsuk hamisba).
                    "},{"location":"hazi/4-tobbszalu-alkalmazasok-fejlesztese/#megoldas","title":"Megold\u00e1s","text":"

                    A k\u00f6vetkez\u0151kben megadjuk a feladat megold\u00e1s\u00e1nak n\u00e9h\u00e1ny fontos elem\u00e9t:

                    • Tegy\u00e9l fel egy Stop Race gombot a fel\u00fcletre, k\u00e9sz\u00edts hozz\u00e1 kezel\u0151f\u00fcggv\u00e9nyt, \u00e9s ebb\u0151l meg kell h\u00edvni az \u00fajonnan bevezetend\u0151 Game.StopRace f\u00fcggv\u00e9nyt.
                    • A meg\u00e1ll\u00edt\u00e1shoz sz\u00fcks\u00e9g lesz egy jelz\u00e9sre a bicikliket futtat\u00f3 sz\u00e1l fel\u00e9. Ez legyen egy bool t\u00edpus\u00fa v\u00e1ltoz\u00f3, amelyet a bicikliket futtat\u00f3 sz\u00e1l ciklusa figyel. Vedd fel ezt raceEnded n\u00e9ven, \u00e9s m\u00f3dos\u00edtsd a sz\u00e1lf\u00fcggv\u00e9nyt, hogy ha ennek \u00e9rt\u00e9ke igaz lesz, a sz\u00e1l fejezze be a fut\u00e1s\u00e1t (t\u00e9rjen vissza).
                    • Az el\u0151bb bevezetett bool v\u00e1ltoz\u00f3 \u00f6nmag\u00e1ban nem lesz el\u00e9g. Hiszen, amikor a bicikli a startvonaln\u00e1l vagy a dep\u00f3ban v\u00e1r, akkor a sz\u00e1la blokkolt \u00e1llapotban van (esem\u00e9ny jelz\u00e9sre v\u00e1r), ekkor nem tudja a raceEndedbool v\u00e1ltoz\u00f3t vizsg\u00e1lni. Emiatt be kell vezetni fel egy \u00faj ManualResetEvent t\u00edpus\u00fa v\u00e1ltoz\u00f3t, amely a le\u00e1ll\u00edt\u00e1s esem\u00e9nyt fogja jelezni (\u00e9s v\u00e1rakozni is lehet r\u00e1).
                    • Ezt az esem\u00e9nyt a bool v\u00e1ltoz\u00f3val egy\u00fctt a Stop Race gombra val\u00f3 kattint\u00e1s sor\u00e1n kell jelzettbe \u00e1ll\u00edtani (a Game.StopRace-ben).
                    • A bicikliket mozgat\u00f3 sz\u00e1lf\u00fcggv\u00e9nyben kommentezd ki (ne t\u00f6r\u00f6ld!) az eddigi v\u00e1rakoz\u00e1st megval\u00f3s\u00edt\u00f3 k\u00f3dr\u00e9szeket, \u00e9s k\u00e9sz\u00edts egy \u00faj megold\u00e1st az el\u0151bb felvett le\u00e1ll\u00edt\u00e1st jelz\u0151 ManualResetEvent seg\u00edts\u00e9g\u00e9vel. A v\u00e1rakoz\u00e1sokra tov\u00e1bbra is sz\u00fcks\u00e9g lesz, azonban a v\u00e1rakoz\u00f3 \u00e1llapotb\u00f3l akkor is ki kell l\u00e9pni, ha a le\u00e1ll\u00edt\u00e1st jelz\u0151 ManualResetEvent esem\u00e9ny lesz jelzett.
                    • Ha le\u00e1ll\u00edt\u00e1s t\u00f6rt\u00e9nt, a sz\u00e1l fut\u00e1s\u00e1t be kell fejezni (a sz\u00e1lf\u00fcggv\u00e9nyb\u0151l ki kell l\u00e9pni, pl. egy return utas\u00edt\u00e1ssal).
                    • A Game.StopRace m\u0171velet\u00e9ben a sz\u00e1laknak t\u00f6rt\u00e9n\u0151 jelz\u00e9s ut\u00e1n meg kell v\u00e1rni, m\u00edg a sz\u00e1lak val\u00f3ban ki is l\u00e9pnek. Ehhez az egyes biciklikhez tartoz\u00f3 sz\u00e1l objektumokra kell sorban Join()-t h\u00edvni. Ahhoz, hogy ez megtehet\u0151 legyen, a sz\u00e1lak ind\u00edt\u00e1sakor a sz\u00e1l objektumokat el kell t\u00e1rolni egy tagv\u00e1ltoz\u00f3ban (pl. egy List<Thread> -ben)

                    Megjegyz\u00e9s: sz\u00e1lak kil\u00e9ptet\u00e9s\u00e9re alternat\u00edv megold\u00e1s lett volna a bool \u00e9s ManualResetEvent bevezet\u00e9se helyett a sz\u00e1lakra Interrupt m\u0171velet h\u00edv\u00e1sa, \u00e9s a sz\u00e1lf\u00fcggv\u00e9nyekben az ennek hat\u00e1s\u00e1ra kiv\u00e1lt\u00f3d\u00f3 ThreadInterruptedException elkap\u00e1sa. Ez a t\u00e9mak\u00f6r el\u0151ad\u00e1son ker\u00fclt ismertet\u00e9sre.

                    BEADAND\u00d3

                    K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st Feladat_IMSc.png n\u00e9ven az al\u00e1bbiak szerint:

                    • Ind\u00edtsd el az alkalmaz\u00e1st. Ha sz\u00fcks\u00e9ges, m\u00e9retezd \u00e1t kisebbre, hogy ne foglaljon sok helyet a k\u00e9perny\u0151n,
                    • a \u201eh\u00e1tt\u00e9rben\u201d a Visual Studio legyen, a Game.cs megnyitva,
                    • VS-ben zoomolj \u00fagy, hogy a Game oszt\u00e1ly sz\u00e1lf\u00fcggv\u00e9nye f\u00fcggv\u00e9nye l\u00e1that\u00f3 legyen, az el\u0151t\u00e9rben pedig az alkalmaz\u00e1sod ablaka.
                    "},{"location":"hazi/5-mvvm/","title":"5. HF - Az MVVM minta \u00e9s az MVVM Toolkit alkalmaz\u00e1sa","text":""},{"location":"hazi/5-mvvm/#bevezetes","title":"Bevezet\u00e9s","text":"

                    A h\u00e1zi feladatban a 3. XAML laboron megval\u00f3s\u00edtott szem\u00e9ly regisztr\u00e1ci\u00f3s alkalmaz\u00e1st alak\u00edtjuk \u00e1t olyan m\u00f3don, hogy az MVVM mint\u00e1ra \u00e9p\u00fclj\u00f6n, valamint megismerked\u00fcnk az MVVM Toolkit alkalmaz\u00e1s\u00e1val.

                    Az \u00f6n\u00e1ll\u00f3 feladat a WinUI el\u0151ad\u00e1ssorozat v\u00e9g\u00e9n elhangzott MVVM t\u00e9mak\u00f6rre \u00e9p\u00edt. Megjegyz\u00e9s: az 5. labor \u2013 MVVM labor nagyon szerte\u00e1gaz\u00f3, \u00e9s egy komplexebb alkalmaz\u00e1s kontextus\u00e1ban mutat p\u00e9ld\u00e1t az MVVM minta alkalmaz\u00e1s\u00e1ra, sok m\u00e1s t\u00e9mak\u00f6r mellett. Jelen h\u00e1zi feladat sokkal f\u00f3kusz\u00e1ltabb, kisebb l\u00e9p\u00e9sekben \u00e9p\u00edtkezik: est\u00fcnkben esetben ink\u00e1bb a jelen h\u00e1zi feladat megold\u00e1sa seg\u00edti az 5. labor \u2013 MVVM kapcsol\u00f3d\u00f3 r\u00e9szeinek k\u00f6nnyebb meg\u00e9rt\u00e9s\u00e9t.

                    Az kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sanyag feldolgoz\u00e1s\u00e1val, jelen \u00f6n\u00e1ll\u00f3 gyakorlat feladatai a feladatle\u00edr\u00e1st k\u00f6vet\u0151 r\u00f6videbb ir\u00e1nymutat\u00e1s seg\u00edts\u00e9g\u00e9vel (n\u00e9ha alap\u00e9rtelmezetten \u00f6sszecsukva) \u00f6n\u00e1ll\u00f3an elv\u00e9gezhet\u0151k.

                    Az \u00f6n\u00e1ll\u00f3 gyakorlat c\u00e9lja:

                    • Az MVVM minta haszn\u00e1lat\u00e1nak gyakorl\u00e1sa
                    • NuGet referenci\u00e1k alkalmaz\u00e1sa
                    • Az MVVM Toolkit alapjaival val\u00f3 ismerked\u00e9s
                    • XAML technik\u00e1k gyakorl\u00e1sa

                    A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s, megegyezik a 3. h\u00e1zi feladat\u00e9val (XAML alapok).

                    "},{"location":"hazi/5-mvvm/#a-beadas-menete","title":"A bead\u00e1s menete","text":"
                    • Az alapfolyamat megegyezik a kor\u00e1bbiakkal. GitHub Classroom seg\u00edts\u00e9g\u00e9vel hozz l\u00e9tre magadnak egy repository-t. A megh\u00edv\u00f3 URL-t Moodle-ben tal\u00e1lod (a t\u00e1rgy nyit\u00f3oldal\u00e1n a \"GitHub classroom hivatkoz\u00e1sok a h\u00e1zi feladatokhoz\" hivatkoz\u00e1sra kattintva megjelen\u0151 oldalon l\u00e1that\u00f3). Fontos, hogy a megfelel\u0151, ezen h\u00e1zi feladathoz tartoz\u00f3 megh\u00edv\u00f3 URL-t haszn\u00e1ld (minden h\u00e1zi feladathoz m\u00e1s URL tartozik). Kl\u00f3nozd le az \u00edgy elk\u00e9sz\u00fclt repository-t. Ez tartalmazni fogja a megold\u00e1s elv\u00e1rt szerkezet\u00e9t. A feladatok elk\u00e9sz\u00edt\u00e9se ut\u00e1n commit-old \u00e9s push-old a megold\u00e1sod.
                    • A kikl\u00f3nozott f\u00e1jlok k\u00f6z\u00f6tt a HelloXaml.sln-t megnyitva kell dolgozni.
                    • A feladatok k\u00e9rik, hogy k\u00e9sz\u00edts k\u00e9perny\u0151k\u00e9pet a megold\u00e1s egy-egy r\u00e9sz\u00e9r\u0151l, mert ezzel bizony\u00edtod, hogy a megold\u00e1sod saj\u00e1t magad k\u00e9sz\u00edtetted. A k\u00e9perny\u0151k\u00e9pek elv\u00e1rt tartalm\u00e1t a feladat minden esetben pontosan megnevezi. A k\u00e9perny\u0151k\u00e9peket a megold\u00e1s r\u00e9szek\u00e9nt kell beadni, a repository-d gy\u00f6k\u00e9rmapp\u00e1j\u00e1ba tedd (a neptun.txt mell\u00e9). A k\u00e9perny\u0151k\u00e9pek \u00edgy felker\u00fclnek GitHub-ra a git repository tartalm\u00e1val egy\u00fctt. Mivel a repository priv\u00e1t, azt az oktat\u00f3kon k\u00edv\u00fcl m\u00e1s nem l\u00e1tja. Amennyiben olyan tartalom ker\u00fcl a k\u00e9perny\u0151k\u00e9pre, amit nem szeretn\u00e9l felt\u00f6lteni, kitakarhatod a k\u00e9pr\u0151l.
                    • Ehhez a feladathoz \u00e9rdemi el\u0151ellen\u0151rz\u0151 nem tartozik: minden push ut\u00e1n lefut ugyan, de csak a neptun.txt kit\u00f6lt\u00f6tts\u00e9g\u00e9t ellen\u0151rzi. Az \u00e9rdemi ellen\u0151rz\u00e9st a hat\u00e1rid\u0151 lej\u00e1rta ut\u00e1n a laborvezet\u0151k teszik majd meg.
                    "},{"location":"hazi/5-mvvm/#kikotesek","title":"Kik\u00f6t\u00e9sek","text":"

                    MVVM minta k\u00f6telez\u0151 alkalmaz\u00e1sa! Jelen h\u00e1zi feladatban az MVVM mint\u00e1t gyakoroljuk, \u00edgy a feladatok megold\u00e1s\u00e1ban k\u00f6telez\u0151 az MVVM minta alkalmaz\u00e1sa. Az ett\u0151l val\u00f3 elt\u00e9r\u00e9s a feladatok \u00e9rt\u00e9kel\u00e9s\u00e9nek elutas\u00edt\u00e1s\u00e1t vonja maga ut\u00e1n.

                    "},{"location":"hazi/5-mvvm/#feladat-0-kiindulo-allapot-attekintese","title":"Feladat 0 - Kiindul\u00f3 \u00e1llapot \u00e1ttekint\u00e9se","text":"

                    A kiindul\u00f3 \u00e1llapot alapvet\u0151en megegyezik a 3. A felhaszn\u00e1l\u00f3i fel\u00fclet kialak\u00edt\u00e1sa v\u00e9g\u00e1llapot\u00e1val. Vagyis egy olyan alkalmaz\u00e1s, melyben egy list\u00e1ban szem\u00e9lyek adatait lehet r\u00f6gz\u00edteni. A labor v\u00e9g\u00e1llapot\u00e1hoz k\u00e9pest egy kisebb v\u00e1ltoz\u00e1st tartalmaz. Laboron a fel\u00fclet teljes le\u00edr\u00e1s\u00e1t a MainWindow.xaml (\u00e9s a kapcsol\u00f3d\u00f3 code-behind f\u00e1jl) tartalmazta. Jelen kiindul\u00f3 megold\u00e1sban az a k\u00fcl\u00f6nbs\u00e9g, hogy ez \u00e1t lett mozgatva a Views mapp\u00e1ban lev\u0151 PersonListPage.xaml (\u00e9s code behind) f\u00e1jlba. A PersonListPage nem egy Window, hanem egy Page lesz\u00e1rmazott oszt\u00e1ly (ellen\u0151rizz\u00fck ezt a code behind f\u00e1jlban). De semmi m\u00e1s v\u00e1ltoz\u00e1s nincs! Mint a neve is utal r\u00e1, a Page egy \"oldalt\" reprezent\u00e1l az alkalmaz\u00e1sban: \u00f6nmag\u00e1ban nem tud megjelenni, hanem pl. egy ablakon kell elhelyezni. El\u0151nye, hogy az ablakon - megfelel\u0151 navig\u00e1ci\u00f3 kialak\u00edt\u00e1s\u00e1val - lehet\u0151s\u00e9g van oldalak (k\u00fcl\u00f6nb\u00f6z\u0151 Page lesz\u00e1rmazottak) k\u00f6z\u00f6tt navig\u00e1lni. Ezt mi nem fogjuk kihaszn\u00e1lni, egyetlen oldalunk lesz csak. Az oldal bevezet\u00e9s\u00e9vel a c\u00e9lunk mind\u00f6ssze az volt, hogy szeml\u00e9ltess\u00fck: az MVVM architekt\u00far\u00e1ban a n\u00e9zeteket nem csak Window (teljes ablak), hanem pl. Page objektumokkal is meg lehet val\u00f3s\u00edtani.

                    Mivel mindent \u00e1tmozgattunk a MainWindow-b\u00f3l a PersonListPage-be, a MainWindow.xaml-ban m\u00e1r semmi m\u00e1s nincs, mint egy ilyen PersonListPage objektum p\u00e9ld\u00e1nyos\u00edt\u00e1sa:

                    <views:PersonListPage/>\n

                    Ellen\u0151rizd a k\u00f3dban, hogy val\u00f3ban ez a helyzet!

                    "},{"location":"hazi/5-mvvm/#foablak-fejlece","title":"F\u0151ablak fejl\u00e9ce","text":"

                    A f\u0151ablak fejl\u00e9ce az \"MVVM\" sz\u00f6veg legyen, hozz\u00e1f\u0171zve a saj\u00e1t Neptun k\u00f3dod: (pl. \"ABCDEF\" Neptun k\u00f3d eset\u00e9n \"MVVM - ABCDEF\"), fontos, hogy ez legyen a sz\u00f6veg! Ehhez a f\u0151ablakunk Title tulajdons\u00e1g\u00e1t \u00e1ll\u00edtsuk be erre a sz\u00f6vegre a MainWindow.xaml f\u00e1jlban.

                    "},{"location":"hazi/5-mvvm/#feladat-1-mvvm-toolkit-alkalmazasa","title":"Feladat 1 - MVVM Toolkit alkalmaz\u00e1sa","text":"

                    A megl\u00e9v\u0151 alkalmaz\u00e1sban a Models mapp\u00e1ban lev\u0151 Person oszt\u00e1ly m\u00e1r implement\u00e1lja az INotifyPropertyChanged (becenev\u00e9n INPC) interf\u00e9szt (\u00edgy rendelkezik egy PropertyChanged esem\u00e9nnyel), valamint a Name \u00e9s az Age setter\u00e9ben jelzi is a tulajdons\u00e1g v\u00e1ltoz\u00e1s\u00e1t a PropertyChanged esem\u00e9ny els\u00fct\u00e9s\u00e9vel (n\u00e9zd meg ezt alaposan a Person.cs f\u00e1jlban).

                    Bemeleg\u00edt\u00e9sk\u00e9ppen/ism\u00e9tl\u00e9sk\u00e9ppen - a k\u00f3dot (PersonListPage.xaml \u00e9s PersonListPage.xaml.cs) alaposan \u00e1tn\u00e9zve \u00e9s az alkalmaz\u00e1st futtatva - fogalmazd meg magadban, mi\u00e9rt is volt erre az alkalmaz\u00e1sban sz\u00fcks\u00e9g!

                    A v\u00e1lasz (ism\u00e9tl\u00e9s)

                    Az alkalmaz\u00e1sban a PersonListPage.xaml-ben a TextBox-ok Text tulajdons\u00e1ga (ez a c\u00e9l tulajdons\u00e1g) hozz\u00e1 vannak k\u00f6tve a code behindban lev\u0151 Person t\u00edpus\u00fa NewPerson tag Age \u00e9s Name tulajdons\u00e1gaihoz (ezek a forr\u00e1sok a k\u00e9t adatk\u00f6t\u00e9sben). N\u00e9zz\u00fck meg a k\u00f3dban, hogy a NewPerson.Name \u00e9s NewPerson.Age forr\u00e1s tulajdons\u00e1gokat v\u00e1ltoztatjuk is a k\u00f3dban: a vez\u00e9rl\u0151 csak akkor tud ezekr\u0151l \u00e9rtes\u00fclni (\u00e9s \u00edgy szinkronban maradni a forr\u00e1ssal), ha ezekr\u0151l a Name \u00e9s Age v\u00e1ltoz\u00e1sokr\u00f3l \u00e9rtes\u00edt\u00e9st kap. Emiatt az Age \u00e9s Name tulajdons\u00e1gokat tartalmaz\u00f3 oszt\u00e1lynak, vagyis a Person-nek meg kell val\u00f3s\u00edtania az INotifyPropertyChanged interf\u00e9szt, \u00e9s a tulajdons\u00e1gok v\u00e1ltoz\u00e1sakor el kell s\u00fctnie a PropertyChanged esem\u00e9nyt megfelel\u0151en param\u00e9terezve.

                    Az alkalmaz\u00e1st futtatva ellen\u0151rizd, hogy a '+' \u00e9s '-' gombok hat\u00e1s\u00e1ra eszk\u00f6z\u00f6lt NewPerson.Age v\u00e1ltoz\u00e1sok val\u00f3ban \u00e9rv\u00e9nyre jutnak az \u00e9letkort megjelen\u00edt\u0151 TextBox-ban.

                    A Person oszt\u00e1lyban l\u00e1tszik, hogy az INotifyPropertyChanged megval\u00f3s\u00edt\u00e1sa \u00e9s a kapcsol\u00f3d\u00f3 k\u00f3d igencsak terjeng\u0151s. N\u00e9zd meg az el\u0151ad\u00e1sanyagban, milyen alternat\u00edv\u00e1k vannak az interf\u00e9sz megval\u00f3s\u00edt\u00e1s\u00e1ra (az \"INPC p\u00e9lda 1\" c\u00edm\u0171 di\u00e1t\u00f3l kezd\u0151d\u0151en kb. n\u00e9gy dia a n\u00e9gy lehet\u0151s\u00e9g illusztr\u00e1l\u00e1s\u00e1ra)! A legt\u00f6m\u00f6rebb legold\u00e1st az MVVM Toolkit alkalmaz\u00e1sa jelenti. A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben jelen terjeng\u0151sebb \"manu\u00e1lis\" INPC megval\u00f3s\u00edt\u00e1st \u00e1talak\u00edtjuk MVVM toolkit alap\u00fara.

                    "},{"location":"hazi/5-mvvm/#feladat-1a-mvvm-toolkit-nuget-referencia-felvetele","title":"Feladat 1/a - MVVM Toolkit NuGet referencia felv\u00e9tele","text":"

                    Els\u0151 l\u00e9p\u00e9sben NuGet referenci\u00e1t kell tenni az MVVM Toolkitre annak \u00e9rdek\u00e9ben, hogy haszn\u00e1lni lehessen a projektben.

                    Feladat: Vegy\u00e9l fel egy NuGet referenci\u00e1t a projektben a \"CommunityToolkit.Mvvm\" NuGet csomagra. Ez a Visual Studio oldal \u00edrja le, hogyan lehet egy NuGet referenci\u00e1t a projektbe felvenni NuGet Package Manager. Az el\u0151z\u0151 link az oldalon bel\u00fcl a \"NuGet Package Manager\" fejezetre ugrik, az itt megadott n\u00e9gy l\u00e9p\u00e9st kell k\u00f6vetni (term\u00e9szetesen azzal a k\u00fcl\u00f6nbs\u00e9ggel, hogy nem a \"Newtonsoft.Json\" hanem a \"CommunityToolkit.Mvvm\" csomagra kell a referenci\u00e1t felvenni).

                    Most, hogy a projekt\u00fcnkbe felvett\u00fck ezt a NuGet referenci\u00e1t, a k\u00f6vetkez\u0151 build sor\u00e1n (mivel annak r\u00e9szek\u00e9nt lefut egy NuGet restore l\u00e9p\u00e9s!) let\u00f6lt\u0151dik a NuGet csomag, kicsomagol\u00f3dnak a benne lev\u0151 DLL-ek a kimeneti mapp\u00e1ba, \u00edgy azok m\u00e1r szerves r\u00e9sz\u00e9t k\u00e9pezik az alkalmaz\u00e1snak (egy NuGet csomag tulajdonk\u00e9ppen egy zip \u00e1llom\u00e1ny). Fontos megeml\u00edteni, hogy Git-be sem a NuGet zip, sem a benne lev\u0151 dll-ek nem ker\u00fclnek fel, a solution gy\u00f6ker\u00e9ben lev\u0151 .gitignore f\u00e1jl ezeket kisz\u0171ri. Pont ez a NuGet koncepci\u00f3 l\u00e9nyege: a repository kicsi maradhat, mert a projektf\u00e1jl csak hivatkoz\u00e1sokat tartalmazza a NuGet csomagokra, \u00e9s amikor valaki egy frissen clone-ozott solution-t buildel, csak ekkor t\u00f6lt\u0151dnek le az online NuGet forr\u00e1sokb\u00f3l a hivatkozott NuGet csomagok.

                    A fenti NuGet-re vonatkoz\u00f3 koncepci\u00f3k ismerete fontos, a tananyag fontos r\u00e9sz\u00e9t k\u00e9pezik!

                    Egy NuGet referencia tulajdonk\u00e9ppen csak egy sor a .csproj projektle\u00edr\u00f3 f\u00e1jlban. A Solution Explorerben a \"HelloXaml\" projekt csom\u00f3pontra kattintva nyisd meg a .csproj projektf\u00e1jlt, \u00e9s ellen\u0151rizd, benne van ez a sor (a verzi\u00f3 lehet m\u00e1s lesz):

                        <PackageReference Include=\"CommunityToolkit.Mvvm\" Version=\"8.2.2\" />\n

                    A csproj f\u00e1jl megnyit\u00e1sa n\u00e9lk\u00fcl is ellen\u0151rizd a NuGet referenci\u00e1nkat: Solution Explorerben nyisd le a \"HelloXaml\"/\"Dependencies\"/\"Packages\" csom\u00f3pontot: ha minden rendben van, alatta l\u00e1that\u00f3 egy \"CommunityToolkit.Mvvm (verzi\u00f3)\" csom\u00f3pont.

                    "},{"location":"hazi/5-mvvm/#feladat-1b-inpc-megvalositas-mvvm-toolkit-alapokon","title":"Feladat 1/b - INPC megval\u00f3s\u00edt\u00e1s MVVM Toolkit alapokon","text":"

                    Most m\u00e1r tudjuk haszn\u00e1lni az MVVM Toolkit NuGet package-ben lev\u0151 oszt\u00e1lyokat, interf\u00e9szeket, attrib\u00fatumokat stb., \u00edgy \u00e1t tudunk t\u00e9rni az MVVM Toolkit alap\u00fa INPC megval\u00f3s\u00edt\u00e1sra.

                    • Kommentezd ki a Person oszt\u00e1lyt teljes eg\u00e9sz\u00e9ben.
                    • A kikommentezett r\u00e9sz felett vedd fel az oszt\u00e1lyt \u00fajonnan, de MVVM Toolkit alap\u00fa INPC megval\u00f3s\u00edt\u00e1ssal.
                      • A megval\u00f3s\u00edt\u00e1sban a \"INPC p\u00e9lda 4 - MVVM Toolkittel\" el\u0151ad\u00e1sdia seg\u00edt.
                      • Partial class kell legyen (vagyis az oszt\u00e1ly r\u00e9szei t\u00f6bb f\u00e1jlban is defini\u00e1lhat\u00f3k).
                      • A Toolkit-beli ObservableObject-b\u0151l sz\u00e1rmazzon: ez az \u0151s val\u00f3s\u00edtja meg az INotifyPropertyChanged interf\u00e9szt, \u00edgy nek\u00fcnk m\u00e1r nem kell.
                      • Name \u00e9s Age tulajdons\u00e1gok helyett name \u00e9s age tagv\u00e1ltoz\u00f3kat vezess\u00fcnk be, ObservableProperty attrib\u00fatummal ell\u00e1tva.

                    Meg is vagyunk.

                    A megold\u00e1s ellen\u0151rz\u00e9se
                    public partial class Person : ObservableObject\n{\n    [ObservableProperty]\n    private string name;\n\n    [ObservableProperty]\n    private int age;\n}\n

                    Ez a k\u00f3d, egy ford\u00edt\u00e1st k\u00f6vet\u0151en, alapjaiban ugyanazt a megold\u00e1st eredm\u00e9nyezi, mint a kor\u00e1bbi, sokkal terjeng\u0151sebb, imm\u00e1r kikommentezett forma. Vagyis (m\u00e9g ha nem is l\u00e1tjuk egyel\u0151re) sz\u00fcletik Name \u00e9s Age tulajdons\u00e1g, megfelel\u0151 PropertyChanged esem\u00e9ny els\u00fct\u00e9sekkel. Hogyan lehets\u00e9ges ez?

                    • Egyr\u00e9szt az ObservableObject \u0151s m\u00e1r megval\u00f3s\u00edtja az INotifyPropertyChanged interf\u00e9szt, \u00edgy a PropertyChanged esem\u00e9ny tagot is tartalmazza, ezt a sz\u00e1rmaztat\u00e1s r\u00e9v\u00e9n \"meg\u00f6r\u00f6kli\" az oszt\u00e1lyunk.
                    • A ford\u00edt\u00e1s sor\u00e1n lefut az MVVM Toolkit k\u00f3dgener\u00e1tora, mely minden ObservableProperty attrib\u00fatummal ell\u00e1tott tagv\u00e1ltoz\u00f3hoz gener\u00e1l egy ugyanolyan nev\u0171, de nagybet\u0171vel kezd\u0151d\u0151 tulajdons\u00e1got az oszt\u00e1lyba, mely tulajdons\u00e1g settere els\u00fcti megfelel\u0151 felt\u00e9telek mellett \u00e9s megfelel\u0151 param\u00e9terekkel a PropertyChanged esem\u00e9nyt. Hurr\u00e1, ezt a k\u00f3dot akkor nem nek\u00fcnk kell meg\u00edrni.
                    • K\u00e9rd\u00e9s, hol keletkezi ez a k\u00f3d. Az oszt\u00e1lyunk egy m\u00e1sik \"partial\" r\u00e9sz\u00e9ben. Egy ford\u00edt\u00e1st k\u00f6vet\u0151en Visual Studio-ban jobb gombbal kattintsunk a Person oszt\u00e1ly nev\u00e9n, majd a felugr\u00f3 men\u00fcben \"Go to Definition\". Ekkor egy als\u00f3 ablakban k\u00e9t tal\u00e1latot is kapunk: az egyik az \u00e1ltalunk \u00edrt fenti k\u00f3d, a m\u00e1sik (\"public class Person\") a gener\u00e1lt r\u00e9szre ugrik egy duplakatt hat\u00e1s\u00e1ra: l\u00e1tszik, hogy viszonylag terjeng\u0151s k\u00f3dot gener\u00e1lt a k\u00f3dgener\u00e1tor, de ami nek\u00fcnk fontos, hogy itt tal\u00e1lhat\u00f3 a Name \u00e9s Age tulajdons\u00e1g, benne - t\u00f6bbek k\u00f6z\u00f6tt - a OnPropertyChanged els\u00fct\u00e9s\u00e9vel.

                    A k\u00f3dgener\u00e1tor szok\u00e1sosan az oszt\u00e1lyunk m\u00e1sik \"partial\" fel\u00e9be dolgozik, annak \u00e9rdek\u00e9ben, hogy ne keveredjen az \u00e1ltalunk \u00edrt \u00e9s a gener\u00e1lt k\u00f3d! A partial classokat leggyakrabban a k\u00e9zzel \u00edrt \u00e9s a gener\u00e1lt k\u00f3d \"k\u00fcl\u00f6nv\u00e1laszt\u00e1s\u00e1ra\" haszn\u00e1ljuk.

                    Mivel sokkal kevesebb k\u00f3dot kell \u00edrni, a gyakorlatban az MVVM Toolkit alap\u00fa megold\u00e1st szoktuk haszn\u00e1lni (de a manu\u00e1lis megold\u00e1st is tudni kell, ez alapj\u00e1n \u00e9rthet\u0151, mi is t\u00f6rt\u00e9nik a sz\u00ednfalak m\u00f6g\u00f6tt).

                    BEADAND\u00d3

                    K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st f1b.png n\u00e9ven az al\u00e1bbiak szerint:

                    • Ind\u00edtsd el az alkalmaz\u00e1st. Ha sz\u00fcks\u00e9ges, m\u00e9retezd \u00e1t kisebbre, hogy ne foglaljon sok helyet a k\u00e9perny\u0151n,
                    • a \u201eh\u00e1tt\u00e9rben\u201d a Visual Studio legyen, a Person.cs megnyitva.
                    "},{"location":"hazi/5-mvvm/#feladat-2-atteres-mvvm-alapu-megoldasra","title":"Feladat 2 - \u00c1tt\u00e9r\u00e9s MVVM alap\u00fa megold\u00e1sra","text":"

                    Az el\u0151z\u0151 l\u00e9p\u00e9sben, b\u00e1r az MVVM Toolkitet haszn\u00e1ltuk, m\u00e9g nem t\u00e9rt\u00fcnk \u00e1t MVVM alap\u00fa megold\u00e1ra (a toolkitet csak az INPC egyszer\u0171bb megval\u00f3s\u00edt\u00e1s\u00e1ra haszn\u00e1ltuk).

                    A k\u00f6vetkez\u0151kben \u00e1talak\u00edtjuk az alkalmaz\u00e1sunk architekt\u00far\u00e1j\u00e1t, hogy az MVVM koncepci\u00f3j\u00e1t k\u00f6vesse. Az egyszer\u0171bb megval\u00f3s\u00edt\u00e1s \u00e9rdek\u00e9ben \u00e9p\u00edt\u00fcnk az MVVM Toolkitre.

                    Feladat: Dolgozd fel a kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sanyagot (WinUI anyagr\u00e9sz v\u00e9g\u00e9n tal\u00e1lhat\u00f3):

                    • \u00c9rtsd meg az MVVM minta alapkoncepci\u00f3it.
                    • Az el\u0151ad\u00e1sdi\u00e1kon tal\u00e1lhat\u00f3 p\u00e9ld\u00e1k teljes k\u00f3dja el\u00e9rhet\u0151 az El\u0151ad\u00e1s GitHub repository \"04-05 WinUI\\DancerProfiles\" mapp\u00e1ban (\"RelaxedMVVM\" \u00e9s \"StrictMVVM\"), ezek seg\u00edthetnek a meg\u00e9rt\u00e9sben \u00e9s a k\u00e9s\u0151bbi feladatok megold\u00e1s\u00e1ban.

                    Mit is jelent az MVVM minta a p\u00e9ld\u00e1nkra vet\u00edtve:

                    • A model oszt\u00e1ly a Models mapp\u00e1ban lev\u0151 Person oszt\u00e1ly, egy szem\u00e9ly adatait reprezent\u00e1lja (UI logik\u00e1t NEM tartalmaz, f\u00fcggetlen mindenf\u00e9le megjelen\u00edt\u00e9st\u0151l).
                    • Jelen pillanatban minden, megjelen\u00edt\u00e9shez kapcsol\u00f3d\u00f3 le\u00edr\u00e1s/logika a PersonListPage-ben van. A mostani PersonListPage-et kett\u00e9v\u00e1gjuk:
                      • A PersonListPage.xaml \u00e9s a code behindja lesz a View.
                      • Bevezet\u00fcnk egy a PersonListPage-hez tartoz\u00f3 ViewModel-t PersonListPageViewModel n\u00e9ven.
                        • Kulcsfontoss\u00e1g\u00fa: a PersonListPage code behindb\u00f3l minden megjelen\u00edt\u00e9si logik\u00e1t \u00e1tmozgatunk a PersonListPageViewModel-be. A minta l\u00e9nyege az, hogy a View csak tiszt\u00e1n a fel\u00fclet le\u00edr\u00e1s\u00e1t tartalmazza, a megjelen\u00edt\u00e9si logik\u00e1nak a ViewModelben van a helye.
                    • A minta m\u00e1sik alappill\u00e9re: a View-nk tartalmaz egy hivatkoz\u00e1st a ViewModelj\u00e9re (m\u00e9gpedig egy tulajdons\u00e1g form\u00e1j\u00e1ban).
                      • A p\u00e9ld\u00e1nkban azt jelenti, hogy a PersonListPage-nek kell legyen egy PersonListPageViewModel tulajdons\u00e1ga.
                      • Ez az\u00e9rt kulcsfontoss\u00e1g\u00fa, mert PersonListPage xaml f\u00e1jlunkban ezen tulajdons\u00e1gon kereszt\u00fcl tudunk adatk\u00f6t\u00e9st megval\u00f3s\u00edtani a ViewModel-be \u00e1tmozgatott tulajdons\u00e1gokra \u00e9s esem\u00e9nykezel\u0151kre!
                    • A PersonListPageViewModel \"dolgozik\" a modellel \u00e9s kezeli a felhaszn\u00e1l\u00f3i interakci\u00f3kat (esem\u00e9nykezel\u0151k).
                    • Mivel a Relaxed, \u00e9s nem a Strict MVVM mint\u00e1t haszn\u00e1ljuk, a Person modelloszt\u00e1lyunk k\u00f6r\u00e9 m\u00e1r nem vezet\u00fcnk be egy PersonViewModel csomagol\u00f3t.

                    Feladat: alak\u00edtsd \u00e1t a megl\u00e9v\u0151 logik\u00e1t \u00edgy, hogy a fenti elveket k\u00f6vet\u0151 MVVM mint\u00e1t k\u00f6vesse. A PersonListPageViewModel oszt\u00e1lyt egy \u00fajonnan l\u00e9trehozott ViewModels mapp\u00e1ba tedd. Pr\u00f3b\u00e1ld magad kidolgozni a megold\u00e1st a fenti seg\u00edts\u00e9g alapj\u00e1n! Ehhez egy el\u0151zetes tippet adunk, mert erre nehezebb r\u00e1j\u00f6nni: Az esem\u00e9nyekhez az esem\u00e9nykezel\u0151 m\u0171veleteket adatk\u00f6t\u00e9ssel is meg lehet adni: l\u00e1sd el\u0151ad\u00e1s dia \"Esem\u00e9nyek \u00e9s funkci\u00f3k k\u00f6t\u00e9se\" c\u00edmmel (az \u00e1talak\u00edt\u00e1s ut\u00e1n az esem\u00e9nykezel\u0151ket csak \u00edgy tudjuk megadni). Az is fontos, hogy adatk\u00f6tni csak publikus tulajdons\u00e1ghoz/m\u0171velethez lehet, ennek kapcs\u00e1n is lesz \u00e1talak\u00edtand\u00f3!

                    Tippek / megold\u00e1s visszaellen\u0151rz\u00e9se
                    1. PersonListPage.xaml.cs code-behind f\u00e1jlb\u00f3l szinte mindent (kiv\u00e9ve this.InitializeComponent() h\u00edv\u00e1s a konstruktorban) \u00e1t kell mozgatni az \u00fajonnan bevezetett PersonListPageViewModel-be, mert ez mind UI logika.
                    2. A PersonListPageViewModel publikus oszt\u00e1ly legyen.
                    3. A PersonListPage code behindba fel kell venni egy ViewModel nev\u0171, PersonListPageViewModel t\u00edpus\u00fa, csak getterrel rendelkez\u0151 auto implement\u00e1lt tulajdons\u00e1got, \u00e9s ezt egy \u00faj objektumra inicializ\u00e1lni is kell. Vagyis a view hozza l\u00e9tre \u00e9s tartalmazza a ViewModel-t!
                    4. A PersonListPage.xaml-ben a k\u00e9t TextBox adatk\u00f6t\u00e9s\u00e9t megfelel\u0151en igaz\u00edtani kell (a NewPerson.Name \u00e9s NewPerson.Age m\u00e1r egy szinttel m\u00e9lyebben, a code behind ViewModel tulajdons\u00e1g\u00e1n kereszt\u00fcl \u00e9rhet\u0151 el).
                    5. A PersonListPage.xaml-ben az esem\u00e9nykezel\u0151k (Click) igaz\u00edt\u00e1sa h\u00e1rom helyen. Ezt tr\u00fckk\u00f6sebb. Esem\u00e9nykezel\u0151 f\u00fcggv\u00e9ny az eddig alkalmazott szintaktik\u00e1val nem adhat\u00f3 m\u00e1r meg, mert az esem\u00e9nykezel\u0151k nem a code behindban tal\u00e1lhat\u00f3k (\u00e1tker\u00fcltek a ViewModel-be).
                      • Az esem\u00e9nyekhez az esem\u00e9nykezel\u0151 m\u0171veleteket adatk\u00f6t\u00e9ssel is meg lehet adni! L\u00e1sd el\u0151ad\u00e1s dia \"Esem\u00e9nyek \u00e9s funkci\u00f3k k\u00f6t\u00e9se\" c\u00edmmel. Ez nek\u00fcnk az\u00e9rt j\u00f3, mert a code behind ViewModel tulajdons\u00e1g\u00e1ban ott a PersonListPageViewModel objektum, melyben ott vannak az esem\u00e9nykezel\u0151k (AddButton_Click, IncreaseButton_Click, DecreaseButton_Click), ezeket kell k\u00f6t\u00f6tt tulajdons\u00e1gk\u00e9nt megadni az adatk\u00f6t\u00e9sben (pl. ViewModel.AddButton_Click stb.).
                      • Fontos, hogy az esem\u00e9nykezel\u0151 f\u00fcggv\u00e9nyek legyenek publikusak, m\u00e1sk\u00fcl\u00f6nben nem m\u0171k\u00f6dik az adatk\u00f6t\u00e9s (\u00e1t kell alak\u00edtani priv\u00e1tr\u00f3l).

                    Tov\u00e1bbi l\u00e9nyeges \u00e1talak\u00edtand\u00f3k:

                    • A ViewModel-ben jelenleg a Click esem\u00e9nykezel\u0151k nevei: AddButton_Click, IncreaseButton_Click \u00e9s DecreaseButton_Click. Ez nem szerencs\u00e9s. A ViewModel-ben \"szemantikailag\" nem esem\u00e9nykezel\u0151kben gondolkodunk. Helyette m\u00f3dos\u00edt\u00f3 m\u0171veletekben, melyek m\u00f3dos\u00edtj\u00e1k a ViewModel \u00e1llapot\u00e1t. A fentiek helyett ennek megfelel\u0151en sokkal jobban passzol\u00f3 \u00e9s kifejez\u0151 nevek az AddPersonToList, IncreaseAge \u00e9s DecreaseAge. Nevezd \u00e1t a f\u00fcggv\u00e9nyeket ennek megfelel\u0151en! Persze a tov\u00e1bbiakban is adatk\u00f6t\u00e9ssel ezeket kell k\u00f6tni a XAML f\u00e1jlban a Click esem\u00e9nyekhez.
                    • A fenti f\u00fcggv\u00e9nyek param\u00e9terlist\u00e1ja egyel\u0151re az \"object sender, RoutedEventArgs e\". Ugyanakkor ezeket a param\u00e9tereket nem haszn\u00e1ljuk semmire. Szerencs\u00e9re a x:Bind esem\u00e9ny adatk\u00f6t\u00e9s rugalmas annyira, hogy param\u00e9ter n\u00e9lk\u00fcli m\u0171velet is megadhat\u00f3, azzal is j\u00f3l m\u0171k\u00f6dik. Ennek tudat\u00e1ban t\u00e1vol\u00edtsd el a fenti felesleges param\u00e9tereket a ViewModel\u00fcnk h\u00e1rom f\u00fcggv\u00e9ny\u00e9b\u0151l. \u00cdgy egy letisztultabb megold\u00e1st kapunk.

                    Ellen\u0151rizd, hogy az \u00e1talak\u00edt\u00e1sok ut\u00e1n is pontosan ugyan\u00fagy m\u0171k\u00f6dik az alkalmaz\u00e1s, mint el\u0151tte!

                    Mit nyert\u00fcnk azzal, hogy kor\u00e1bbi megold\u00e1sunkat MVVM alap\u00fara alak\u00edtottuk \u00e1t? A v\u00e1laszt az el\u0151ad\u00e1sanyag adja meg! P\u00e1r dolog kiemelve:

                    • Sz\u00e9pen k\u00fcl\u00f6nv\u00e1lnak (nem keverednek) a k\u00fcl\u00f6nb\u00f6z\u0151 felel\u0151ss\u00e9g\u0171 r\u00e9szek, \u00edgy jobban meg\u00e9rthet\u0151:
                      • UI f\u00fcggetlen logika (model \u00e9s kapcsol\u00f3d\u00f3 oszt\u00e1lyok).
                      • UI logika (ViewModel)
                      • UI puszta megjelen\u00e9s (View)
                    • Mivel a UI logika k\u00fcl\u00f6n van, lehet(ne) hozz\u00e1 unit teszteket \u00edrni

                    Min\u00e9l komplexebb egy alkalmaz\u00e1s, ann\u00e1l ink\u00e1bb igazak ezek.

                    BEADAND\u00d3

                    K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st f2.png n\u00e9ven az al\u00e1bbiak szerint:

                    • Ind\u00edtsd el az alkalmaz\u00e1st. Ha sz\u00fcks\u00e9ges, m\u00e9retezd \u00e1t kisebbre, hogy ne foglaljon sok helyet a k\u00e9perny\u0151n,
                    • a \u201eh\u00e1tt\u00e9rben\u201d a Visual Studio legyen, a PersonListPageViewModel.cs megnyitva.
                    "},{"location":"hazi/5-mvvm/#feladat-3-vezerlok-tiltasaengedelyezese","title":"Feladat 3 - Vez\u00e9rl\u0151k tilt\u00e1sa/enged\u00e9lyez\u00e9se","text":"

                    Jelen \u00e1llapotban kiss\u00e9 furcs\u00e1n viselkedik az alkalmaz\u00e1s: a \"-\" gombbal negat\u00edv tartom\u00e1nyba is vihet\u0151 egy \u00e9letkor, vagy a \"+\"-szal 150 f\u00f6l\u00e9, illetve a \"+Add\" gombbal olyan szem\u00e9ly is felvehet\u0151, mely \u00e9rtelmetlen tulajdons\u00e1gokkal rendelkezik. Ezeket a gombokat le kellene tiltani, amikor az \u00e1ltaluk kiv\u00e1ltott m\u0171veletnek nincs \u00e9rtelme, illetve enged\u00e9lyezni, amikor van.

                    A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben val\u00f3s\u00edtsuk meg a \"-\" gomb tilt\u00e1s\u00e1t/enged\u00e9lyez\u00e9s\u00e9t ennek megfelel\u0151en. A gomb akkor legyen csak enged\u00e9lyezett, ha a szem\u00e9ly \u00e9letkora 0-n\u00e1l nagyobb.

                    Pr\u00f3b\u00e1ld ezt els\u0151 k\u00f6rben magadt\u00f3l megval\u00f3s\u00edtani, legal\u00e1bbis az alapjait lefektetni! Mindenk\u00e9ppen adatk\u00f6t\u00e9s alap\u00fa megold\u00e1sban gondolkozz, csak ez fogadhat\u00f3 el! Ha elakadsz, a megold\u00e1sod nem \"akar\" m\u0171k\u00f6dni, akkor gondold \u00e1t, mi lehet az oka, a megold\u00e1st pedig az al\u00e1bbiaknak megfelel\u0151en alak\u00edtsd ki.

                    A probl\u00e9m\u00e1ra t\u00f6bbf\u00e9le megold\u00e1s is kidolgozhat\u00f3. Mindben k\u00f6z\u00f6s, hogy a \"-\" gomb IsEnabled tulajdons\u00e1g\u00e1t k\u00f6tj\u00fck valamilyen m\u00f3don. Az \u00e1ltalunk v\u00e1lasztott megold\u00e1sban egy a PersonListPageViewModel-ben \u00fajonnan bevezetett bool tulajdons\u00e1ghoz k\u00f6ss\u00fck.

                    PersonListPageViewModel.cs
                        public bool IsDecrementEnabled\n    {\n        get { return NewPerson.Age > 0; }\n    }\n
                    PersonListPage.xaml-be a '-' gombhoz
                        IsEnabled=\"{x:Bind ViewModel.IsDecrementEnabled, Mode=OneWay}\"\n

                    Pr\u00f3b\u00e1ljuk ki! Sajnos nem m\u0171k\u00f6dik, a \"-\" gomb nem tilt\u00f3dik le, amikor 0 vagy kisebb \u00e9rt\u00e9k\u0171 lesz az \u00e9letkor (pl. a gomb sokszori kattint\u00e1s\u00e1val). Ha t\u00f6r\u00e9spontot tesz\u00fcnk az IsDecrementEnabled belsej\u00e9be, \u00e9s \u00edgy ind\u00edtjuk az alkalmaz\u00e1st, azt tapasztaljuk, hogy a tulajdons\u00e1g \u00e9rt\u00e9k\u00e9t csak egyszer k\u00e9rdezi le a k\u00f6t\u00f6tt vez\u00e9rl\u0151, az alkalmaz\u00e1s indul\u00e1sakor: ut\u00e1na hi\u00e1ba kattintunk pl. a \"-\" gombon, t\u00f6bbsz\u00f6r nem. Pr\u00f3b\u00e1ld is ki!

                    Gondold \u00e1t, mi okozza ezt, \u00e9s csak ut\u00e1na haladj tov\u00e1bb az \u00fatmutat\u00f3val!

                    Indokl\u00e1s

                    A kor\u00e1bban tanultaknak megfelel\u0151en az adatk\u00f6t\u00e9s csak akkor k\u00e9rdezi le a forr\u00e1stulajdons\u00e1g (eset\u00fcnkben IsDecrementEnabled) \u00e9rt\u00e9k\u00e9t, ha annak v\u00e1ltoz\u00e1s\u00e1r\u00f3l az INotifyPropertyChanged seg\u00edts\u00e9g\u00e9vel \u00e9rtes\u00edt\u00e9st kap! M\u00e1rpedig, jelen megold\u00e1sunkban hi\u00e1ba v\u00e1ltozik a NewPerson objektum Age tulajdons\u00e1ga, ennek megt\u00f6rt\u00e9ntekor a semmif\u00e9le \u00e9rtes\u00edt\u00e9s nincs az erre \u00e9p\u00fcl\u0151 IsDecrementEnabled tulajdons\u00e1g megv\u00e1ltoz\u00e1s\u00e1r\u00f3l!

                    A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben val\u00f3s\u00edtsd meg a kapcsol\u00f3d\u00f3 v\u00e1ltoz\u00e1s\u00e9rtes\u00edt\u00e9st a PersonListPageViewModel oszt\u00e1lyban:

                    • MVVM Toolkit \"alapokon\" val\u00f3s\u00edtsd meg az INotifyPropertyChanged interf\u00e9szt
                      • ObservableObject sz\u00e1rmaztat\u00e1st haszn\u00e1lj.
                      • Az IsDecrementEnabled tulajdons\u00e1g maradhat a mostani form\u00e1j\u00e1ban (egy getter only property), nem sz\u00fcks\u00e9ges [ObservableProperty] alap\u00fara \u00e1t\u00edrni (de az is j\u00f3 megold\u00e1s, \u00e9s a h\u00e1zi feladat tekintet\u00e9ben is teljesen elfogadhat\u00f3, csak kicsit m\u00e1sk\u00e9nt kell dolgozni a k\u00f6vetkez\u0151 l\u00e9p\u00e9sekben).
                    • Pr\u00f3b\u00e1ld magadt\u00f3l megval\u00f3s\u00edtani a k\u00f6vetkez\u0151t a ViewModel oszt\u00e1lyban (a Person oszt\u00e1ly marad v\u00e1ltozatlan): amikor a NewPerson.Age v\u00e1ltozik, akkor az ObservableObject \u0151sb\u0151l \u00f6r\u00f6k\u00f6lt OnPropertyChanged h\u00edv\u00e1s\u00e1val jelezz\u00fck a IsDecrementEnabled tulajdons\u00e1g v\u00e1ltoz\u00e1s\u00e1t. Tipp: a Person oszt\u00e1ly m\u00e1r rendelkezik PropertyChanged esem\u00e9nnyel, hiszen maga is megval\u00f3s\u00edtja az INotifyPropertyChanged interf\u00e9szt, erre az esem\u00e9nyre fel lehet iratkozni! Az egyszer\u0171s\u00e9g \u00e9rdek\u00e9ben az nem zavar minket, ha az IsDecrementEnabled v\u00e1ltoz\u00e1s\u00e1t esetleg akkor is jelezz\u00fck, ha tulajdonk\u00e9pen \"logikailag\" esetleg nem is v\u00e1ltozik.
                    • A fentieket k\u00fcl\u00f6n esem\u00e9nykezel\u0151 f\u00fcggv\u00e9ny bevezet\u00e9se n\u00e9lk\u00fcl is meg lehet oldani (tipp: esem\u00e9nykezel\u0151 megad\u00e1sa lambda kifejez\u00e9ssel).

                    Teszteld is a megold\u00e1sod! Ha j\u00f3l dolgozt\u00e1l, a gombnak akkor is le kell tilt\u00f3dnia, ha a TextBoxba k\u00e9zzel \u00edrsz be negat\u00edv \u00e9letkor \u00e9rt\u00e9ket (\u00e9s ut\u00e1na kikattintasz a TextBoxb\u00f3l). Gondold \u00e1t, mi\u00e9rt van ez \u00edgy!

                    A \"+\" gombra \u00e9s a \"+Add\" gomra is dolgozz ki hasonl\u00f3 megold\u00e1st!

                    • Az \u00e9letkor maxim\u00e1lis \"elfogadhat\u00f3\" \u00e9rt\u00e9ke 150 legyen.
                    • A n\u00e9v csak akkor elfogadhat\u00f3, ha van benne legal\u00e1bb egy nem whitespace karakter (ez ut\u00f3bbi ellen\u0151rz\u00e9s\u00e9re a string oszt\u00e1ly IsNullOrWhiteSpace statikus m\u0171velet\u00e9t haszn\u00e1ld).
                    • Azzal az esettel nem kell foglalkozni, hogy ha a felhaszn\u00e1l\u00f3 az \u00e9letkor TextBox-ba nem \u00e9rv\u00e9nyes sz\u00e1mot \u00edr be (ezt jelen megold\u00e1ssal nem is lehet kezelni).

                    A tesztel\u00e9s sor\u00e1n azt tapasztaljuk, hogy ha pl. kit\u00f6r\u00f6lj\u00fck a nevet a n\u00e9v TextBox-ban, a \"+Add\" gomb \u00e1llapota nem azonnal v\u00e1ltozik, hanem csak ha elhagyjuk a TextBox-ot? Mi\u00e9rt van ez? M\u00f3dos\u00edtsd a megold\u00e1sod, hogy ez minden sz\u00f6veg v\u00e1ltoz\u00e1skor, a TextBox elhagy\u00e1sa n\u00e9lk\u00fcl is megt\u00f6rt\u00e9njen. Tipp: l\u00e1sd el\u0151ad\u00e1sanyag \"x:Bind mikor friss\u00fcl az adat?\" c\u00edm\u0171 dia.

                    BEADAND\u00d3

                    K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st f3.png n\u00e9ven az al\u00e1bbiak szerint:

                    • Ind\u00edtsd el az alkalmaz\u00e1st. Ha sz\u00fcks\u00e9ges, m\u00e9retezd \u00e1t kisebbre, hogy ne foglaljon sok helyet a k\u00e9perny\u0151n,
                    • az \u00e9letkor legyen 0-ra lecs\u00f6kkentve az alkalmaz\u00e1sban,
                    • a \u201eh\u00e1tt\u00e9rben\u201d a Visual Studio legyen, a PersonListPageViewModel.cs megnyitva.
                    "},{"location":"hazi/5-mvvm/#feladat-4-command-hasznalata","title":"Feladat 4 - Command haszn\u00e1lata","text":"

                    Jelen pillanatban a \"-\" gomb vonatkoz\u00e1s\u00e1ban eset\u00e9ben k\u00e9t feladatunk van:

                    • A Click eset\u00e9n az esem\u00e9nykezel\u0151 m\u0171velet futtat\u00e1sa
                    • A gomb tilt\u00e1sa/enged\u00e9lyez\u00e9se az IsEnabled tulajdons\u00e1g seg\u00edts\u00e9g\u00e9vel

                    Bizonyos vez\u00e9rl\u0151k - ilyen a gomb is - t\u00e1mogatj\u00e1k, hogy ezt a kett\u0151t, a Command mint\u00e1ra \u00e9p\u00edtve, egy parancs objektum seg\u00edts\u00e9g\u00e9vel adhassuk meg. A Command tervez\u00e9si minta koncepci\u00f3j\u00e1val a \"Tervez\u00e9si mint\u00e1k 3\" el\u0151ad\u00e1s alapj\u00e1n lehet r\u00e9sztelesebben megismerkedni (b\u00e1r ott csak az alap Command mint\u00e1val ismerkedt\u00fcnk meg, mely a parancs futtat\u00e1s\u00e1t t\u00e1mogatja, tilt\u00e1s\u00e1t/enged\u00e9lyez\u00e9s\u00e9t nem). A Command minta MVVM specifikus megval\u00f3s\u00edt\u00e1s\u00e1val a WinUI el\u0151ad\u00e1ssorozat v\u00e9ge fel\u00e9, a \"Command minta\" c\u00edm\u0171 di\u00e1t\u00f3l kezdve lehet megismerkedni.

                    Az alapelv a k\u00f6vetkez\u0151: a gombn\u00e1l a Click \u00e9s IsEnabled \"megad\u00e1sa\" helyett a gomb Command tulajdons\u00e1g\u00e1t \u00e1ll\u00edtjuk egy ICommand interf\u00e9szt megval\u00f3s\u00edt\u00f3 command objektumra. A futtat\u00e1s, illetve tilt\u00e1s/enged\u00e9lyez\u00e9s m\u00e1r ezen command objektum feladata.

                    Alapesetben egy alkalmaz\u00e1sban minden parancshoz egy k\u00fcl\u00f6n ICommand implement\u00e1ci\u00f3t kellene k\u00e9sz\u00edteni. Ez azonban sok parancs eset\u00e9n sok oszt\u00e1ly bevezet\u00e9s\u00e9t ig\u00e9nyli. Az MVVM Toolkit ebben is a seg\u00edts\u00e9g\u00fcnkre siet. Biztos\u00edt egy RelayCommand oszt\u00e1lyt, mely megval\u00f3s\u00edtja az ICommand interf\u00e9szt. Ez az oszt\u00e1ly b\u00e1rmilyen parancs/k\u00f3d futtat\u00e1s\u00e1ra haszn\u00e1lhat\u00f3, \u00edgy nem kell tov\u00e1bbi command oszt\u00e1lyokat bevezetni. Hogyan lehets\u00e9ges ez? \u00dagy, hogy a RelayCommand-nak konstruktor param\u00e9terekben, k\u00e9t delegate form\u00e1j\u00e1ban tudjuk a v\u00e9grehajt\u00e1shoz \u00e9s a tilt\u00e1shoz/enged\u00e9lyez\u00e9shez tartoz\u00f3k k\u00f3dot:

                    • Els\u0151 param\u00e9terben a parancs futtat\u00e1sakor v\u00e9grehajtand\u00f3 k\u00f3dot adjuk meg.
                    • M\u00e1sodik param\u00e9terben (ez opcion\u00e1lis) azt a k\u00f3dot, melyet a command h\u00edv annak ellen\u0151rz\u00e9s\u00e9re, hogy enged\u00e9lyezni/tiltani kell mag\u00e1t (az itt megadott f\u00fcggv\u00e9nynek bool-lal kell visszat\u00e9rnie, true esetben enged\u00e9lyezett lesz a parancs).

                    A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben a \"-\" gomb kezel\u00e9s\u00e9t alak\u00edtjuk \u00e1t command alap\u00fara. El\u0151sz\u00f6r pr\u00f3b\u00e1ld a nagyj\u00e1t \u00f6n\u00e1ll\u00f3an megval\u00f3s\u00edtani a kapcsol\u00f3d\u00f3 WinUI el\u0151ad\u00e1sanyag alapj\u00e1n. A parancs futtat\u00e1sa egyszer\u0171bb, de a parancs tilt\u00e1s-enged\u00e9lyez\u00e9shez lesz m\u00e9g teend\u0151nk. F\u0151bb l\u00e9p\u00e9sek:

                    • Egy csak getterrel rendelkez\u0151 publikus RelayCommand tulajdons\u00e1g felv\u00e9tele a ViewModel-be, pl. DecreaseAgeCommand n\u00e9ven. Az el\u0151ad\u00e1sanyaggal ellent\u00e9tben eset\u00fcnkben nem kell a RelayCommand-nak generikus param\u00e9tert megadni, mert a parancskezel\u0151 f\u00fcggv\u00e9ny\u00fcnknek (DecreaseAge) nincs param\u00e9tere.
                    • Az \u00fajonnan bevezetett tulajdons\u00e1gnak a ViewModel konstruktorban \u00e9rt\u00e9ket adni. A RelayCommand konstruktor param\u00e9tereit add meg megfelel\u0151en.
                    • A PersonListPage.xaml-ben a \"-\" gombn\u00e1l a Click \u00e9s IsEnabled adatk\u00f6t\u00e9s\u00e9re nincs m\u00e1r sz\u00fcks\u00e9g, ezek t\u00f6rlend\u0151k. Helyette a gomb Command tulajdons\u00e1g\u00e1t k\u00f6sd a ViewModel-ben az el\u0151z\u0151 l\u00e9p\u00e9sben bevezetett DecreaseAgeCommand tulajdons\u00e1ghoz.

                    Ha kipr\u00f3b\u00e1ljuk, a parancs futtat\u00e1s m\u0171k\u00f6dik, a tilt\u00e1s/enged\u00e9lyez\u00e9s viszont m\u00e9g nem: ha j\u00f3l megfigyelj\u00fck, a gomb mindig enged\u00e9lyezett marad megjelen\u00e9s\u00e9ben. Ennek, kicsit jobban belegondolva, logikus oka van: a RelayCommand meg tudja ugyan h\u00edvni a m\u00e1sodik konstruktor param\u00e9ter\u00e9ben megadott m\u0171veletet az \u00e1llapot ellen\u0151rz\u00e9s\u00e9hez, de nem tudja, hogy minden NewPerson.Age v\u00e1ltoz\u00e1skor meg kellene ezt tennie! Ezen tudunk seg\u00edteni. A ViewModel-\u00fcnk konstruktor\u00e1ban m\u00e1r feliratkoztunk kor\u00e1bban a NewPerson.PropertyChanged esem\u00e9nyre: erre \u00e9p\u00edtve, amikor v\u00e1ltozik az \u00e9letkor (vagy amikor v\u00e1ltozhat, az nem probl\u00e9ma, ha n\u00e9ha feleslegesen megtessz\u00fck) h\u00edvd meg a DecreaseAgeCommand NotifyCanExecuteChanged m\u0171velet\u00e9t. Ennek a m\u0171veletnek nagyon besz\u00e9des neve van: \u00e9rtes\u00edti a parancsot, hogy megv\u00e1ltoz(hat)ott azon \u00e1llapot, mely alapj\u00e1n a parancs tiltott/enged\u00e9lyezett \u00e1llapota \u00e9p\u00edt. \u00cdgy a parancs friss\u00edteni fogja mag\u00e1t, pontosabban a parancshoz tartoz\u00f3 gomb \u00e1llapot\u00e1t.

                    \u00cdrd \u00e1t \"+\" gomb kezel\u00e9s\u00e9t is hasonl\u00f3an, parancs alap\u00fara! A \"+Add\" gomb kezel\u00e9s\u00e9t ne v\u00e1ltoztasd meg!

                    BEADAND\u00d3

                    K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st f4.png n\u00e9ven az al\u00e1bbiak szerint:

                    • Ind\u00edtsd el az alkalmaz\u00e1st. Ha sz\u00fcks\u00e9ges, m\u00e9retezd \u00e1t kisebbre, hogy ne foglaljon sok helyet a k\u00e9perny\u0151n,
                    • a n\u00e9v TextBox legyen \u00fcres az alkalmaz\u00e1sban,
                    • a \u201eh\u00e1tt\u00e9rben\u201d a Visual Studio legyen, a PersonListPageViewModel.cs megnyitva.
                    "},{"location":"hazi/5-mvvm/#feladat-5-command-hasznalata-mvvm-toolkit-alapu-kodgeneralassal","title":"Feladat 5 - Command haszn\u00e1lata MVVM Toolkit alap\u00fa k\u00f3dgener\u00e1l\u00e1ssal","text":"

                    Az el\u0151z\u0151 feladatban a command tulajdons\u00e1gok bevezet\u00e9s\u00e9t \u00e9s azok p\u00e9ld\u00e1nyos\u00edt\u00e1s\u00e1t \"manu\u00e1lisan\" oldottuk meg. Az MVVM Toolkit ezt le tudja egyszer\u0171s\u00edteni: megfelel\u0151 attrib\u00fatum alkalmaz\u00e1sa eset\u00e9n a tulajdons\u00e1got \u00e9s a p\u00e9ld\u00e1nyos\u00edt\u00e1st automatikusan le tudja gener\u00e1lni.

                    Alak\u00edtsuk \u00e1t a DecreaseAgeCommand kezel\u00e9s\u00e9t (csak ezt, az IncreaseAgeCommand maradjon!) gener\u00e1lt k\u00f3d alap\u00fara:

                    1. L\u00e1sd el a PersonListPageViewModel oszt\u00e1lyt a partial kulcssz\u00f3val.
                    2. T\u00f6r\u00f6ld ki a DecreaseAgeCommand tulajdons\u00e1got \u00e9s ennek p\u00e9ld\u00e1nyos\u00edt\u00e1s\u00e1t a konstruktorb\u00f3l.
                    3. A DecreaseAge m\u0171veletet l\u00e1sd el ezzel az attrib\u00fatummal: [RelayCommand(CanExecute = nameof(IsDecrementEnabled))].
                      • Ennek hat\u00e1s\u00e1ra a k\u00f3dgener\u00e1tor bevezet egy RelayCommand tulajdons\u00e1got az oszt\u00e1lyban, melynek neve a m\u0171velet\u00fcnk neve (DecreaseAge), hozz\u00e1f\u0171zve a \"Command\" stringet. Ezzel meg is kapjuk a kor\u00e1bban k\u00e9zzel bevezetett DecreaseAgeCommand nev\u0171 tulajdons\u00e1got.
                      • A CanExecute attrib\u00fatum tulajdons\u00e1gban egy string form\u00e1ban annak a boollal visszat\u00e9r\u0151 m\u0171veletnek vagy tulajdons\u00e1gnak a nev\u00e9t lehet megadni, melyet a gener\u00e1lt k\u00f3d a parancs tilt\u00e1s\u00e1nak/enged\u00e9lyez\u00e9s\u00e9nek sor\u00e1n haszn\u00e1l (a RelayCommand konstruktor m\u00e1sodik param\u00e9tere lesz). Nek\u00fcnk m\u00e1r van ilyen tulajdons\u00e1gunk, \"IsDecrementEnabled\" n\u00e9vben. Az\u00e9rt nem egyszer\u0171 string form\u00e1j\u00e1ban adjuk meg, mert ha ut\u00f3lag valaki \u00e1tnevezi az IsDecrementEnabled m\u0171veletet, akkor a mostani \"IsDecrementEnabled\" m\u00e1r nem j\u00f3 m\u0171veletre mutatna. A nameof kifejez\u00e9s haszn\u00e1lat\u00e1val ez a probl\u00e9ma elker\u00fclhet\u0151. A CanExecute megad\u00e1sa \u00e1ltal\u00e1noss\u00e1g\u00e1ban nem k\u00f6telez\u0151 (nem adjuk meg, ha nem akarjuk a parancsot soha tiltani).

                    Teszteld a megold\u00e1st (\u00e9letkor cs\u00f6kkent\u00e9se), ugyan\u00fagy kell m\u0171k\u00f6dnie, mint kor\u00e1bban.

                    BEADAND\u00d3

                    K\u00e9sz\u00edts egy k\u00e9perny\u0151ment\u00e9st f5.png n\u00e9ven az al\u00e1bbiak szerint:

                    • Ind\u00edtsd el az alkalmaz\u00e1st. Ha sz\u00fcks\u00e9ges, m\u00e9retezd \u00e1t kisebbre, hogy ne foglaljon sok helyet a k\u00e9perny\u0151n,
                    • a \u201eh\u00e1tt\u00e9rben\u201d a Visual Studio legyen, a PersonListPageViewModel.cs megnyitva.
                    "},{"location":"hazi/5-mvvm/#feladat-6-strict-mvvm","title":"Feladat 6 - Strict MVVM","text":"

                    Jelen megold\u00e1sunk a Relaxed MVVM megk\u00f6zel\u00edt\u00e9st k\u00f6veti. A k\u00f6vetkez\u0151 l\u00e9p\u00e9sekben \u00e1tgondoljuk, mit is jelent ez pontosan, \u00e9s mit jelentene a Strict MVVM megk\u00f6zel\u00edt\u00e9sre val\u00f3 \u00e1t\u00e1ll\u00e1s (megval\u00f3s\u00edtani nem fogjuk).

                    Jelen megold\u00e1sunk a Relaxed MVVM megk\u00f6zel\u00edt\u00e9st k\u00f6veti, vagyis a View-ban k\u00f6zvetlen\u00fcl a Person modell oszt\u00e1lyhoz adatk\u00f6t\u00fcnk (\u00e9s a PersonPageViewModel-ben is a Person modell oszt\u00e1lyt haszn\u00e1ljuk). Ennek el\u0151nye az egyszer\u0171s\u00e9g. De van egy h\u00e1tr\u00e1nya is: a Person modell oszt\u00e1lyunkban k\u00e9nytelenek voltunk megval\u00f3s\u00edtani az INotifyPropertyChanged interf\u00e9szt (m\u00e9gha az MVVM toolkit seg\u00edts\u00e9g\u00e9vel is), k\u00fcl\u00f6nben nem m\u0171k\u00f6dne j\u00f3l az adatk\u00f6t\u00e9s. Vannak olyan helyzetek, amikor a modell oszt\u00e1lyunkat nem szeretn\u00e9nk ilyen, n\u00e9mik\u00e9ppen m\u00e1r a fel\u00fcletet kiszolg\u00e1l\u00f3 logik\u00e1val \"szennyezni\", hanem a lehet\u0151 legtiszt\u00e1bb form\u00e1ban szeretn\u00e9nk a modell oszt\u00e1lyunkat tartani. Ekkor a Strict MVVM megk\u00f6zel\u00edt\u00e9s jelenti a megold\u00e1st (l\u00e1sd \"Strict MVVM - be\u00e1gyaz\u00e1s\" el\u0151ad\u00e1sdia). Mit jelentene ez a sz\u00e1munkra, mit kellene a k\u00f3don v\u00e1ltoztatni? Gondold \u00e1t az el\u0151ad\u00e1sdia alapj\u00e1n a sz\u00fcks\u00e9ges v\u00e1ltoztat\u00e1sokat! Megval\u00f3s\u00edtani/dokument\u00e1lni nem kell, ez csak egy \u00e1tgondol\u00f3s feladat \ud83d\ude0a!

                    Strict MVVM alap\u00fa megold\u00e1s
                    • A Person modell oszt\u00e1lyban m\u00e1r nem val\u00f3s\u00edtan\u00e1nk meg az INotifyPropertyChanged interf\u00e9szt, az oszt\u00e1ly leegyszer\u0171s\u00f6dik, csak egyszer\u0171 tulajdons\u00e1gokat tartalmazna (ez volt a c\u00e9l).
                    • Be kellene vezetni egy PersonViewModel oszt\u00e1lyt (mely egy Person modell objektumot csomagolna be). Ebben:
                      • Be kellene vezetni a Name \u00e9s Age tulajdons\u00e1gokat.
                      • Meg kellene val\u00f3s\u00edtani az INotifyPropertyChanged interf\u00e9szt:
                        • ObservableObject sz\u00e1rmaztat\u00e1s
                        • A tulajdons\u00e1gok setter\u00e9ben a SetProperty \u0151sb\u0151l \u00f6r\u00f6k\u00f6lt seg\u00e9df\u00fcggv\u00e9ny haszn\u00e1lata (hogy kiv\u00e1lt\u00f3djon a PropertyChanged esem\u00e9ny)
                    • A Viewhoz tartoz\u00f3 PersonPageViewModel-\u00fcnket \u00e1t kellene alak\u00edtani, hogy ne a Person modell, hanem az \u00faj PersonViewModel-t haszn\u00e1lja.
                    "},{"location":"hazi/5-mvvm/index_ger/","title":"5. HA - Verwendung der MVVM-Muster und des MVVM-Toolkits","text":""},{"location":"hazi/5-mvvm/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

                    In dieser Hausaufgabe werden wir die w\u00e4hrend der 3. Labor\u00fcbung (XAML) implementierte Anwendung f\u00fcr Personenregistrierung so ver\u00e4ndern, dass sie auf dem MVVM-Muster basiert, und wir werden das MVVM-Toolkit kennenlernen.

                    Die Hausaufgabe baut auf dem MVVM-Thema auf, das am Ende der WinUI-Vorlesungsreihe behandelt wurde. Hinweis: Labor 5 - MVVM-Labor ist sehr abwechslungsreich und zeigt neben vielen anderen Themen ein Beispiel f\u00fcr das MVVM-Muster im Kontext einer komplexeren Anwendung. Die vorliegende Hausaufgabe ist fokussierter, in kleineren Schritten aufgebaut: In unserem Fall wird die L\u00f6sung dieser Hausaufgabe helfen, um die zugeh\u00f6rige Teile von Lab 5 - MVVM zu verstehen.

                    Durch das Durcharbeiten des zugeh\u00f6rigen Vorlesungsmaterials k\u00f6nnen die Aufgaben dieser eigenst\u00e4ndigen \u00dcbung mit Hilfe der k\u00fcrzeren Leitf\u00e4den, die auf die Aufgabenbeschreibung folgen (manchmal standardm\u00e4\u00dfig eingefaltet), selbst\u00e4ndig bearbeitet werden.

                    Das Ziel der Hausaufgabe:

                    • \u00dcben Sie die Verwendung der MVVM-Muster
                    • NuGet-Referenzen verwenden
                    • Kennenlernen der Grundlagen des MVVM-Toolkits
                    • \u00dcben von XAML-Techniken

                    Die erforderliche Entwicklungsumgebung wird hier beschrieben, identisch mit Hausaufgabe 3 (XAML-Grundlagen).

                    "},{"location":"hazi/5-mvvm/index_ger/#das-verfahren-fur-die-einreichung","title":"Das Verfahren f\u00fcr die Einreichung","text":"

                    Auf das Moodle soll ein ZIP-Archiv hochgeladen werden, das die folgenden Anforderungen entspricht:

                    • Die Aufgaben sind aufeinander basiert, deshalb ist es gen\u00fcgend den resultierenden Quellcode am Ende der letzten Aufgabe hochzuladen (Visual Studio Solution Verzeichnis). Der Name des Verzeichnisses soll \"MVVM_NEPTUN\" sein (wo NEPTUN Ihre Neptun-Code ist).
                    • Wir erwarten keine schriftliche Begr\u00fcndung oder Beschreibung, aber die komplexe Codeteile sollen mit Kommentaren versehen werden
                    • Das ZIP-Archiv darf die Ausgangsdaten (.exe) und die tempor\u00e4ren Dateien nicht enthalten. Um diese Best\u00e4nde zu l\u00f6schen, Visual Studio soll ge\u00f6ffnet werden und in dem Solution Explorer Rechtsklick an dem \u201eClean Solution\u201d Men\u00fcelement. Das manuelle L\u00f6schen von den \"obj\" und \"bin\" Verzeichnissen kann auch n\u00f6tig sein.
                    • In den Aufgaben werden Sie aufgefordert, einen Screenshot von einem Teil Ihrer L\u00f6sung zu machen, da dies beweist, dass Sie Ihre L\u00f6sung selbst erstellt haben. Der erwartete Inhalt der Screenshots ist immer in der Aufgabe angegeben. Die Screenshots sollten als Teil der L\u00f6sung eingegeben, also innerhalb dem ZIP-Archiv auf das Moodle hochgeladen werden. Wenn Sie Inhalte im Screenshot haben, die Sie nicht hochladen m\u00f6chten, k\u00f6nnen Sie diese aus dem Screenshot ausblenden.
                    "},{"location":"hazi/5-mvvm/index_ger/#bedingungen","title":"Bedingungen","text":"

                    Obligatorische Verwendung der MVVM-Muster! In dieser Hausaufgabe \u00fcben wir das MVVM-Muster, daher ist das MVVM-Muster f\u00fcr die L\u00f6sung der Aufgaben obligatorisch erforderlich. Andernfalls wird die Bewertung der Aufgaben verweigert.

                    "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-0-uberblick-uber-den-ausgangszustand","title":"Aufgabe 0 - \u00dcberblick \u00fcber den Ausgangszustand","text":"

                    Der Ausgangszustand ist im Grunde derselbe wie die Endzustand von der Labor\u00fcbung 3. Entwurf der Benutzeroberfl\u00e4che. Also eine solche Anwendung, die die Speicherung der Daten von Personen in einer Liste erm\u00f6glicht. Sie enth\u00e4lt eine kleinere \u00c4nderung im Vergleich zum Endzustand des Labors. Im Labor war die vollst\u00e4ndige Beschreibung der Oberfl\u00e4che in MainWindow.xaml (und die zugeh\u00f6rige Code-Behind-Datei) verf\u00fcgbar. Der Unterschied zu dieser urspr\u00fcnglichen L\u00f6sung besteht darin, dass sie nach PersonListPage.xaml (und in den Code dahinter) im Ordner Views verschoben wurde. PersonListPage ist keine Window, sondern eine von Page abgeleitete Klasse (siehe den Code hinter der Datei). Aber sonst hat sich nichts ge\u00e4ndert! Wie der Name schon sagt, stellt Page eine \"Seite\" in der Anwendung dar: Sie kann nicht selbst angezeigt werden, sondern muss z. B. in einem Fenster platziert werden. Der Vorteil dieses Fensters ist, dass es m\u00f6glich ist, zwischen den Seiten (verschiedene Page Nachkommen) zu navigieren, indem man die entsprechende Navigation verwendet. Wir werden das nicht ausnutzen, wir werden nur eine Seite haben. Der Zweck der Einf\u00fchrung dieser Seite war nur zu veranschaulichen, dass in der MVVM-Architektur, Ansichten k\u00f6nnen nicht nur mit Window (full window), sondern auch mit Objekten wie Page implementiert werden.

                    Da alles von MainWindow nach PersonListPage verschoben wurde, gibt es auf MainWindow.xaml nichts anderes als eine Kopie eines solchen PersonListPage Objekts:

                    <views:PersonListPage/>\n

                    Pr\u00fcfen Sie im Code, ob dies tats\u00e4chlich der Fall ist!

                    "},{"location":"hazi/5-mvvm/index_ger/#kopfzeile-des-hauptfensters","title":"Kopfzeile des Hauptfensters","text":"

                    Die \u00dcberschrift des Hauptfensters sollte \"MVVM\" sein, angeh\u00e4ngt mit Ihrem Neptun-Code: (z.B.\"MVVM - ABCDEF\" im Falle des Neptun-Codes \"ABCDEF\"), ist es wichtig, dass dies der Text ist! Setzen Sie dazu die Eigenschaft Title Ihres Hauptfensters auf diesen Text in der Datei MainWindow.xaml.

                    "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-1-verwendung-des-mvvm-toolkits","title":"Aufgabe 1 - Verwendung des MVVM-Toolkits","text":"

                    In der bestehenden Anwendung implementiert die Klasse Person im Ordner Models bereits die Schnittstelle INotifyPropertyChanged (Spitzname INPC) (sie hat also ein Ereignis PropertyChanged ) und zeigt au\u00dferdem eine Eigenschafts\u00e4nderung in den Settern Name und Age an, indem sie das Ereignis PropertyChanged ausl\u00f6st (siehe Person.cs f\u00fcr eine detaillierte Betrachtung).

                    Zum Aufw\u00e4rmen/Wiederholen - nachdem Sie sich den Code (PersonListPage.xaml und PersonListPage.xaml.cs) genau angesehen und die Anwendung ausgef\u00fchrt haben - sagen Sie sich, warum dies in der Anwendung erforderlich war!

                    Die Antwort (Wiederholung)

                    In der Anwendung ist die Eigenschaft Text von TextBox (dies ist die Zieleigenschaft) in PersonListPage.xaml an die Eigenschaften Age und Name des Members NewPerson mit dem Typ Person im Code-Behind-Datei gebunden (dies sind die Quellen in den beiden Datenverbindungen). Beachten Sie im Code, dass die Quelleneigenschaften NewPerson.Name und NewPerson.Age ebenfalls im Code ge\u00e4ndert werden: Der Controller kann nur \u00fcber diese \u00c4nderungen informiert werden (und somit mit der Quelle synchron bleiben), wenn er \u00fcber diese \u00c4nderungen an Name und Age informiert wird. Aus diesem Grund muss die Klasse, die die Eigenschaften Age und Name enth\u00e4lt, d.h. Person, die Schnittstelle INotifyPropertyChanged implementieren und das Ereignis PropertyChanged ausl\u00f6sen, wenn sich die Eigenschaften \u00e4ndern, wobei das Ereignis entsprechend parametrisiert sein muss.

                    Wenn Sie die Anwendung ausf\u00fchren, \u00fcberpr\u00fcfen Sie, ob die \u00c4nderungen, die Sie auf NewPerson.Age durch Dr\u00fccken der Tasten \"+\" und \"-\" vornehmen, tats\u00e4chlich in der TextBox, die das Alter anzeigt, wiedergegeben werden.

                    In der Klasse Person k\u00f6nnen Sie sehen, dass die Implementierung von INotifyPropertyChanged und der dazugeh\u00f6rige Code recht umfangreich ist. Schauen Sie sich die Vorlesungsunterlagen an, um zu sehen, welche Alternativen es f\u00fcr die Implementierung der Schnittstelle gibt (ausgehend von der Folie \"INPC Beispiel 1\", etwa vier Folien zur Veranschaulichung der vier M\u00f6glichkeiten)! Die kompakteste L\u00f6sung ist das MVVM-Toolkit. Im n\u00e4chsten Schritt werden wir die derzeitige umfangreichere \"manuelle\" INPC-Implementierung in ein MVVM-Toolkit umwandeln.

                    "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-1a-aufnahme-des-mvvm-toolkit-nuget-referenzes","title":"Aufgabe 1/a - Aufnahme des MVVM Toolkit NuGet Referenzes","text":"

                    Zun\u00e4chst muss eine NuGet-Referenz auf das MVVM-Toolkit erstellt werden, damit es im Projekt verwendet werden kann.

                    Aufgabe: F\u00fcgen Sie eine NuGet-Referenz f\u00fcr das NuGet-Paket \"CommunityToolkit.Mvvm\" in das Projekt ein. Auf dieser Visual Studio-Seite wird beschrieben, wie eine NuGet-Referenz mit dem NuGet Package Manager zu einem Projekt hinzugef\u00fcgt wird. Der vorhergehende Link auf der Seite f\u00fchrt Sie zum Abschnitt \"NuGet Package Manager\". Folgen Sie den vier hier angegebenen Schritten (mit dem Unterschied, dass Sie auf das Paket \"CommunityToolkit.Mvvm\" statt auf \"Newtonsoft.Json\" verweisen m\u00fcssen).

                    Nachdem wir nun diese NuGet-Referenz zu unserem Projekt hinzugef\u00fcgt haben, wird der n\u00e4chste Build (da er einen NuGet restore Schritt enth\u00e4lt!) das NuGet-Paket herunterladen, die darin enthaltenen DLLs in den Ausgabeordner entpacken und sie zu einem integralen Bestandteil der Anwendung machen (ein NuGet-Paket ist eigentlich eine Zip-Datei). Es ist wichtig zu beachten, dass weder die NuGet-Zipdatei noch die darin enthaltenen DLLs in Git enthalten sind. Sie werden von der Datei .gitignore im Stammverzeichnis der L\u00f6sung herausgefiltert. Dies ist der eigentliche Kern des NuGet-Konzepts: Das Repository kann klein bleiben, da die Projektdatei nur Verweise auf NuGet-Pakete enth\u00e4lt, und wenn jemand eine frisch geklonte L\u00f6sung erstellt, werden die referenzierten NuGet-Pakete erst dann aus den Online-NuGet-Ressourcen heruntergeladen.

                    Die Kenntnis der oben genannten NuGet-Konzepte ist wichtig, sie sind ein wichtiger Teil des Lehrmaterials!

                    Eine NuGet-Referenz ist eigentlich nur eine Zeile in der Projektbeschreibungsdatei .csproj. Klicken Sie im Solution Explorer auf den Projektknoten \"HelloXaml\", \u00f6ffnen Sie die Projektdatei .csproj und \u00fcberpr\u00fcfen Sie, ob diese Zeile enthalten ist (die Version kann unterschiedlich sein):

                        <PackageReference Include=\"CommunityToolkit.Mvvm\" Version=\"8.2.2\" />\n

                    Sie k\u00f6nnen unsere NuGet-Referenz \u00fcberpr\u00fcfen, ohne die Datei csproj zu \u00f6ffnen: \u00d6ffnen Sie im Solution Explorer den Knoten \"HelloXaml\"/\"Dependencies\"/\"Packages\": Wenn alles in Ordnung ist, sehen Sie darunter einen Knoten \"CommunityToolkit.Mvvm (Version)\".

                    "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-1b-inpc-implementierung-auf-basis-des-mvvm-toolkits","title":"Aufgabe 1/b - INPC-Implementierung auf Basis des MVVM-Toolkits","text":"

                    Jetzt k\u00f6nnen wir die Klassen, Schnittstellen, Attribute usw. im MVVM Toolkit NuGet-Paket verwenden, so dass wir zur MVVM Toolkit-basierten INPC-Implementierung wechseln k\u00f6nnen.

                    • Kommentieren Sie die ganze Klasse Person aus.
                    • F\u00fcgen Sie oberhalb des auskommentierten Teils die Klasse als neu hinzu, aber mit einer MVVM-Toolkit-basierten INPC-Implementierung.
                      • Die Pr\u00e4sentation \"INPC Beispiel 4 - MVVM Toolkit\" wird Ihnen bei der Umsetzung helfen.
                      • Es muss sich um eine partielle Klasse handeln (d.h. Teile der Klasse k\u00f6nnen in mehreren Dateien definiert sein).
                      • Sie stammt von ObservableObject aus dem Toolkit: Dieser Vorg\u00e4nger implementiert die Schnittstelle INotifyPropertyChanged, so dass wir sie nicht mehr ben\u00f6tigen.
                      • Ersetzen Sie die Eigenschaften Name und Age mit Mitgliedsvariablen name und age, die auch die Attribute ObservableProperty besitzen.

                    Wir sind fertig.

                    \u00dcberpr\u00fcfung der L\u00f6sung

                    ```` csharp public partial class Person : ObservableObject { [ObservableProperty] private string name;

                    [ObservableProperty]\nprivate int age;\n

                    } ```

                    Dieser Code ergibt nach einer \u00dcbersetzung im Wesentlichen die gleiche L\u00f6sung wie die fr\u00fchere, viel ausf\u00fchrlichere und jetzt auskommentierte L\u00f6sung. Das hei\u00dft (auch wenn wir es noch nicht sehen), es werden die Eigenschaften Name und Age erstellt, mit entsprechenden PropertyChanged Ereignisausl\u00f6sern. Wie ist das m\u00f6glich?

                    • Einerseits implementiert der Vorfahre ObservableObject bereits die Schnittstelle INotifyPropertyChanged, enth\u00e4lt also auch das Ereignis PropertyChanged, das durch Ableitung an unsere Klasse \"vererbt\" wird.
                    • W\u00e4hrend der Kompilierung wird der MVVM-Toolkit-Codegenerator ausgef\u00fchrt, der f\u00fcr jede Membervariable mit dem Attribut ObservableProperty in der Klasse eine Eigenschaft mit dem gleichen Namen, aber mit einem Gro\u00dfbuchstaben beginnend, erzeugt, die unter den richtigen Bedingungen und mit den richtigen Parametern das Ereignis PropertyChanged ausl\u00f6st. Hurra, wir m\u00fcssen diesen Code nicht schreiben.
                    • Die Frage ist, wo dieser Code generiert wird. In einem anderen \"partiellen\" Teil unserer Klasse. Nach einer \u00dcbersetzung in Visual Studio klicken Sie mit der rechten Maustaste auf den Klassennamen Person und w\u00e4hlen im Popup-Men\u00fc \"Go to Definition\". In einem unteren Fenster erhalten wir zwei Ergebnisse: das eine ist der Code, den wir oben geschrieben haben, das andere (\"public class Person\") springt nach einem Doppelklick zum generierten Teil des Codes: Sie sehen, dass der Code-Generator einen relativ ausf\u00fchrlichen Code generiert hat, aber was f\u00fcr uns wichtig ist, ist, dass die Eigenschaften Name und Age hier stehen, darunter - unter anderem - die Eigenschaft OnPropertyChanged.

                    Der Code-Generator arbeitet in der Regel in der anderen \"partiellen\" H\u00e4lfte unserer Klasse, um den von uns geschriebenen und den von uns generierten Code nicht zu verwechseln! Teilklassen werden am h\u00e4ufigsten verwendet, um handgeschriebenen Code von generiertem Code zu \"trennen\".

                    Da viel weniger Code geschrieben werden muss, verwenden wir in der Praxis die auf dem MVVM-Toolkit basierende L\u00f6sung (aber Sie m\u00fcssen auch die manuelle L\u00f6sung kennen, damit Sie verstehen k\u00f6nnen, was hinter den Kulissen geschieht).

                    EINGABE

                    Machen Sie einen Screenshot mit dem Namen f1b.png wie folgt:

                    • Starten Sie die App. Verkleinern Sie sie gegebenenfalls, damit sie nicht zu viel Platz auf dem Bildschirm einnimmt,
                    • Im \"Hintergrund\" sollte Visual Studio mit \"Person.cs\" ge\u00f6ffnet sein.
                    "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-2-umstellung-auf-eine-mvvm-basierte-losung","title":"Aufgabe 2 - Umstellung auf eine MVVM-basierte L\u00f6sung","text":"

                    Im vorherigen Schritt haben wir zwar das MVVM-Toolkit verwendet, sind aber noch nicht zu einer MVVM-basierten L\u00f6sung gewechselt (das Toolkit wurde nur f\u00fcr eine einfachere Implementierung von INPC verwendet).

                    Im Folgenden werden wir die Architektur unserer Anwendung so anpassen, dass sie dem MVVM-Konzept folgt. Wir bauen auf dem MVVM-Toolkit auf, um die Implementierung zu erleichtern.

                    Aufgabe: Arbeiten Sie das entsprechende Vorlesungsmaterial durch (am Ende des WinUI-Abschnitts):

                    • Verstehen Sie der grundlegenden Konzepte des MVVM-Musters.
                    • Der vollst\u00e4ndige Code f\u00fcr die Beispiele in den Folien ist im Ordner \"04-05 WinUI\\DancerProfiles\" (\"RelaxedMVVM\" und \"StrictMVVM\") von GitHub Repository verf\u00fcgbar und kann Ihnen helfen, die zu verstehen und die Aufgaben sp\u00e4ter zu l\u00f6sen.

                    Was bedeutet das MVVM-Muster f\u00fcr unser Beispiel:

                    • Die Modellklasse ist die Klasse Person im Ordner Models, die die Daten einer Person repr\u00e4sentiert (sie enth\u00e4lt KEINE UI-Logik und ist unabh\u00e4ngig von der Anzeige).
                    • Im Moment sind alle Beschreibungen/Logiken im Zusammenhang mit der Visualisierung in PersonListPage. Die aktuelle PersonListPage wird in zwei Teile aufgeteilt:
                      • PersonListPage.xaml und seiner Code-Behind-Datei wird die Ansicht.
                      • Wir f\u00fchren ein ViewModel f\u00fcr PersonListPage mit dem Namen PersonListPageViewModel ein.
                        • Sehr wichtig: Die gesamte Anzeigelogik wird von PersonListPage Code-Behind-Datei ins PersonListPageViewModel bewegt. Der Sinn des Musters ist, dass die View nur eine reine Beschreibung der Oberfl\u00e4che enth\u00e4lt, die Anzeigelogik befindet sich im ViewModel.
                    • Eine weitere S\u00e4ule des Musters: Unsere View enth\u00e4lt einen Verweis auf ihr ViewModel (in Form einer Eigenschaft).
                      • In unserem Beispiel bedeutet dies, dass PersonListPage eine PersonListPageViewModel Eigenschaft haben muss.
                      • Dies ist sehr wichtig, da wir in unserer PersonListPage Xaml-Datei diese Eigenschaft verwenden k\u00f6nnen, um die Datenverbindung an Eigenschaften und Ereignishandler zu implementieren, die in das ViewModel verschoben wurden!
                    • PersonListPageViewModel \"arbeitet\" mit dem Modell und behandelt die Benutzerinteraktionen (Ereignishandler).
                    • Da wir eher das Relaxed- als das Strict-MVVM-Muster verwenden, f\u00fchren wir keinen PersonViewModel-Wrapper noch um unsere Person-Modellklasse herum ein.

                    Aufgabe: \u00c4ndern Sie die bestehende Logik so, dass sie dem MVVM-Muster folgt und den oben genannten Grunds\u00e4tzen entspricht. Legen Sie die Klasse PersonListPageViewModel in einem neu erstellten Ordner ViewModels ab. Versuchen Sie, die L\u00f6sung anhand der obigen Hilfe selbst zu bearbeiten! Dazu geben wir einen vorherigen Hinweis, da das schwieriger herauszufinden ist: Sie k\u00f6nnen auch Ereignishandler f\u00fcr Ereignisse durch Datenverbindung angeben: siehe die Folie \"Bindung von Ereignissen und Funktionen\" (nach der Modifikation ist dies die einzige M\u00f6glichkeit, Ereignishandler anzugeben). Es ist auch wichtig zu beachten, dass Daten nur an \u00f6ffentliche Eigenschaften/Operationen gebunden werden k\u00f6nnen, so dass auch dies ge\u00e4ndert werden muss!

                    Tipps/Pr\u00fcfung der L\u00f6sung
                    1. Aus der PersonListPage.xaml.cs Code-Behind-Datei sollte fast alles (au\u00dfer this.InitializeComponent() Aufruf im Konstruktor) in die neu eingef\u00fchrte PersonListPageViewModel verschoben werden, da es sich um UI-Logik handelt.
                    2. PersonListPageViewModel sollte eine \u00f6ffentliche Klasse sein.
                    3. In der PersonListPage Code-Behind-Datei m\u00fcssen Sie eine automatisch implementierte Eigenschaft namens ViewModel vom Typ PersonListPageViewModel mit nur Getter einf\u00fcgen und diese auf ein neues Objekt initialisieren. Mit anderen Worten, die Ansicht erstellt und enth\u00e4lt das ViewModel!
                    4. In PersonListPage.xaml m\u00fcssen die beiden Datenverbindungen der zwei TextBox entsprechend korrigiert werden ( NewPerson.Name und NewPerson.Age sind jetzt eine Ebene tiefer verf\u00fcgbar, \u00fcber die ViewModel-Eigenschaft der Code-Behind-Datei).
                    5. In PersonListPage.xaml m\u00fcssen die Ereignishandler (Click) an drei Stellen korrigiert werden. Dies ist komplizierter. Die Ereignishandler-Funktion kann nicht mehr mit der bisher verwendeten Syntax angegeben werden, da die Ereignishandler nicht mehr in der Code-Behind-Datei liegen (sie wurden in das ViewModel verschoben).
                      • Ereignishandler k\u00f6nnen f\u00fcr Ereignisse durch Datenverbindung angegeben werden! Siehe Pr\u00e4sentationsfolie \"Binden von Ereignissen und Funktionen\". Das ist gut f\u00fcr uns, denn in der ViewModel-Eigenschaft der Code-Behind-Datei ist das PersonListPageViewModel-Objekt, das die Ereignishandler enth\u00e4lt (AddButton_Click, IncreaseButton_Click, DecreaseButton_Click), und diese m\u00fcssen als gebundene Eigenschaften in der Datenverbindung angegeben werden (z.B. ViewModel.AddButton_Click usw.).
                      • Es ist wichtig, dass die Ereignishandler-Funktionen \u00f6ffentlich sind, sonst funktioniert die Datenverbindung nicht (muss von privat konvertiert werden).

                    Andere wichtige Modifikationen:

                    • Die aktuellen Namen der Ereignishandler von Click in ViewModel lauten AddButton_Click, IncreaseButton_Click und DecreaseButton_Click. Das ist nicht gl\u00fccklich. Im ViewModel denken wir \"semantisch\" nicht im Sinne von Ereignishandlern. Stattdessen werden im Sinne von Modifizierungsoperationen denken, die den Zustand des ViewModel \u00e4ndern. Also statt dem oberen Namen werden wir die folgenden, sehr viel geignetere und aussagekr\u00e4ftigere Namen verwenden: AddPersonToList, IncreaseAge und DecreaseAge. Benennen Sie die Funktionen entsprechend um! Nat\u00fcrlich m\u00fcssen Sie diese noch an die Click Ereignisse in der XAML-Datei binden.
                    • Die Parameterliste f\u00fcr die oben genannten Funktionen lautet zun\u00e4chst \"object sender, RoutedEventArgs e\". Diese Parameter werden jedoch nicht f\u00fcr irgendetwas verwendet. Gl\u00fccklicherweise ist die x:Bind-Ereignisbindung so flexibel, dass Sie auch eine Operation ohne Parameter angeben k\u00f6nnen, und das funktioniert auch problemlos. Entfernen Sie daher die oben genannten unn\u00f6tigen Parameter aus den drei Funktionen unseres ViewModel. Dies f\u00fchrt zu einer schlankeren L\u00f6sung.

                    Pr\u00fcfen Sie, ob die Anwendung nach den \u00c4nderungen genauso funktioniert wie vorher!

                    Was haben wir durch die Umstellung unserer bisherigen L\u00f6sung auf eine MVVM-Basis gewonnen? Die Antwort finden Sie in den Vorlesungsmaterial! Ein paar Dinge sind hervorzuheben:

                    • Die verschiedenen Zust\u00e4ndigkeiten sind gut voneinander getrennt (nicht vermischt), so dass es leichter zu verstehen ist:
                      • UI-unabh\u00e4ngige Logik (Modell und zugeh\u00f6rige Klassen).
                      • UI-Logik (ViewModel)
                      • Nur UI-Erscheinung (View)
                    • Da die UI-Logik separat ist, k\u00f6nn(t)en Sie Unit-Tests f\u00fcr sie schreiben.

                    Je komplexer eine Anwendung ist, desto mehr sind diese wahr.

                    EINGABE

                    Machen Sie einen Screenshot mit dem Namen f2.png wie folgt:

                    • Starten Sie die App. Verkleinern Sie sie gegebenenfalls, damit sie nicht zu viel Platz auf dem Bildschirm einnimmt,
                    • Im \"Hintergrund\" sollte Visual Studio mit PersonListPageViewModel.cs ge\u00f6ffnet sein.
                    "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-3-deaktivierenaktivieren-von-controllern","title":"Aufgabe 3 - Deaktivieren/Aktivieren von Controllern","text":"

                    In diesem Stadium verh\u00e4lt sich die Anwendung etwas komisch: Sie k\u00f6nnen die Taste \"-\" verwenden, um ein Alter in den negativen Bereich zu verschieben, oder die Taste \"+\", um es \u00fcber 150 zu verschieben, oder die Taste \"+Add\", um eine Person mit sinnlosen Attributen hinzuzuf\u00fcgen. Diese Tasten sollten deaktiviert werden, wenn die von ihnen ausgel\u00f6ste Aktion keinen Sinn ergibt, und aktiviert werden, wenn sie Sinn hat.

                    Im n\u00e4chsten Schritt deaktivieren/aktivieren Sie die Taset \"-\" entsprechend. Die Taste sollte nur aktiviert werden, wenn das Alter der Person gr\u00f6\u00dfer als 0 ist.

                    Versuchen Sie, es zuerst selbst zu l\u00f6sen, zumindest um die Grundlagen zu schaffen! Denken Sie unbedingt \u00fcber eine L\u00f6sung mit Datenverbindung, nur diese ist akzeptabel! Wenn Sie nicht weiterkommen kann, Ihre L\u00f6sung nicht funktionieren \"will\", \u00fcberdenken Sie, was der Grund daf\u00fcr sein k\u00f6nnte, und konstruiren Sie Ihre L\u00f6sung wie folgt.

                    Es gibt mehrere m\u00f6gliche L\u00f6sungen f\u00fcr dieses Problem. In allen gemeinsam ist, dass die Eigenschaft IsEnabled der Taste \"-\" in irgendeiner Weise gebunden ist. In unserer L\u00f6sung binden wir sie an eine bool-Eigenschaft, die in PersonListPageViewModel neu eingef\u00fchrt wurde.

                    PersonListPageViewModel.cs
                        public bool IsDecrementEnabled\n    {\n        get { return NewPerson.Age > 0; }\n    }\n
                    In PersonListPage.xaml zu der Taste '-'
                        IsEnabled=\"{x:Bind ViewModel.IsDecrementEnabled, Mode=OneWay}\"\n

                    Probieren wir es aus! Leider funktioniert es nicht, die \"-\"-Taste wird nicht deaktiviert, wenn das Alter auf 0 oder weniger gesetzt wird (z.B. durch wiederholtes Anklicken der Taste). Wenn Sie einen Haltepunkt in IsDecrementEnabled setzen und die Anwendung auf diese Weise starten, werden Sie feststellen, dass der Wert der Eigenschaft nur einmal vom gebundenen Steuerelement abgefragt wird, wenn die Anwendung startet: Danach k\u00f6nnen Sie auf die Taste \"-\" mehrmals klicken, aber es wird nicht mehr als einmal abgefragt. Probieren Sie es aus!

                    \u00dcberdenken Sie, was die Ursache daf\u00fcr ist, und lesen Sie erst dann der Leitfaden weiter!

                    Begr\u00fcndung

                    Wie wir bereits gelernt haben, ruft die Datenverbindung den Wert der Quelleigenschaft (in diesem Fall IsDecrementEnabled) nur ab, wenn sie \u00fcber INotifyPropertyChanged \u00fcber eine \u00c4nderung informiert wird! Aber in unserer L\u00f6sung gibt es jedoch, selbst wenn sich die Eigenschaft Age des Objekts NewPerson \u00e4ndert, keine Benachrichtigung \u00fcber die \u00c4nderung der darauf basierenden Eigenschaft IsDecrementEnabled!

                    Im n\u00e4chsten Schritt implementieren Sie die entsprechende \u00c4nderungsmeldung in der Klasse PersonListPageViewModel:

                    • Implementieren Sie die INotifyPropertyChanged Schnittstelle auf MVVM Toolkit \"Grundlagen\"!
                    • Die Eigenschaft IsDecrementEnabled kann so bleiben, wie sie ist (get only property), sie muss nicht auf [ObservableProperty] umgeschrieben werden (aber das ist auch eine gute L\u00f6sung und f\u00fcr Hausaufgaben durchaus akzeptabel, sie muss nur in den n\u00e4chsten Schritten etwas anders bearbeitet werden).
                    • Versuchen Sie, Folgendes in der ViewModel-Klasse selbst zu implementieren (die Klasse Person bleibt unver\u00e4ndert): Wenn sich NewPerson.Age \u00e4ndert, wird die vom Vorg\u00e4nger geerbte Eigenschaft OnPropertyChanged aufgerufen, um die \u00c4nderung der Eigenschaft IsDecrementEnabled anzuzeigen. Hinweis: Die Klasse Person hat bereits ein Ereignis PropertyChanged, da sie selbst die Schnittstelle INotifyPropertyChanged implementiert, k\u00f6nnen Sie dieses Ereignis abonnieren! Wegen der Einfachheit haben wir nichts dagegen, wenn wir eine \u00c4nderung an IsDecrementEnabled melden, auch wenn sie sich nicht wirklich \"logisch\" \u00e4ndert.
                    • Die obigen Schritte k\u00f6nnen auch ohne die Implementierung einer separaten Ereignishandler-Funktion durchgef\u00fchrt werden: Dies wird empfohlen, ist aber nicht zwingend erforderlich (Tipp: Geben Sie eine Ereignishandler-Funktion mit einem Lambda-Ausdruck an).

                    Testen Sie Ihre L\u00f6sung! Wenn Sie richtig gearbeitet haben, sollte die Taste auch dann deaktiviert sein, wenn Sie manuell einen negativen Alterswert in die Textbox eingeben (und dann aus der Textbox herausklicken). Denken Sie dar\u00fcber nach, warum das so ist!

                    Erarbeiten Sie eine \u00e4hnliche L\u00f6sung f\u00fcr die Taste \"+\" und die Taste \"+Add\"!

                    • Das \"akzeptable\" H\u00f6chstalter sollte 150 Jahre sein.
                    • Der Name ist nur akzeptabel, wenn er mindestens ein Zeichen enth\u00e4lt, das kein Leerzeichen ist (um letzteres zu pr\u00fcfen, verwenden Sie die statische Operation der String-Klasse IsNullOrWhiteSpace).
                    • Der Fall, dass der Benutzer eine ung\u00fcltige Zahl in die Alters-Textbox eingibt (was bei dieser L\u00f6sung nicht m\u00f6glich ist), muss nicht behandelt werden.

                    Beim Testen haben wir festgestellt, dass sich der Zustand der Taste \"+Add\" nicht sofort \u00e4ndert, wenn wir beispielsweise den Namen in der Textbox \"Name\" l\u00f6schen, sondern erst, wenn wir die Textbox verlassen? Warum ist das so? \u00c4ndern Sie Ihre L\u00f6sung so, dass dies bei jeder Text\u00e4nderung geschieht, ohne die TextBox zu verlassen. Hinweis: siehe die Folie \"x:Bind wann werden die Daten aktualisiert?\" in der Vorlesungsmaterial.

                    EINGABE

                    Machen Sie einen Screenshot mit dem Namen f3.png wie folgt:

                    • Starten Sie die App. Verkleinern Sie sie gegebenenfalls, damit sie nicht zu viel Platz auf dem Bildschirm einnimmt,
                    • sollte das Alter in der Anwendung auf 0 reduziert werden,
                    • im \"Hintergrund\" sollte Visual Studio mit PersonListPageViewModel.cs ge\u00f6ffnet sein.
                    "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-4-command-verwenden","title":"Aufgabe 4 - Command verwenden","text":"

                    Derzeit haben wir zwei Aufgaben f\u00fcr die Taste \"-\":

                    • F\u00fcr Click, die Ausf\u00fchrung der Ereignishandler-Funktion
                    • Deaktivieren/Aktivieren der Taste mit der Eigenschaft IsEnabled

                    Einige Controller, wie z. B. die Taste, unterst\u00fctzen die M\u00f6glichkeit, beide Aufgaben, aufbauend auf dem Command-Muster, mit einem Command-Objekt zu machen. Das Konzept des Command-Entwurfsmusters kann in der Vorlesung \"Design Patterns 3\" ausf\u00fchrlicher behandelt werden (obwohl wir dort nur das grundlegende Command-Muster kennengelernt haben, das die Ausf\u00fchrung von Befehlen unterst\u00fctzt, nicht aber das Verbieten/Erlauben). Die MVVM-spezifische Umsetzung des Command-Patterns finden Sie gegen Ende der WinUI-Vorlesungsreihe, beginnend mit der Folie \"Command-Muster\".

                    Das Grundprinzip ist: Anstatt die \"Angaben\" von Click und IsEnabled f\u00fcr die Taste, setzen wir die Eigenschaft Command der Taste auf ein Befehlsobjekt, das die Schnittstelle ICommand implementiert. Es liegt an diesem Befehlsobjekt, den Befehl auszuf\u00fchren oder zu deaktivieren/aktivieren.

                    Standardm\u00e4\u00dfig sollte eine Anwendung f\u00fcr jeden Befehl eine eigene ICommand Implementierung haben. Dies erfordert jedoch die Einf\u00fchrung vieler Klassen f\u00fcr viele Befehle. Das MVVM-Toolkit ist hier, um zu helfen. Stellt eine Klasse RelayCommand zur Verf\u00fcgung, die die Schnittstelle ICommand implementiert. Diese Klasse kann zur Ausf\u00fchrung beliebiger Befehle/Codes verwendet werden, so dass keine zus\u00e4tzlichen Befehlsklassen eingef\u00fchrt werden m\u00fcssen. Wie ist das m\u00f6glich? So, dass RelayCommand hat den Code f\u00fcr die Ausf\u00fchrung und deaktivieren/aktivieren in Konstruktor-Parameter, in Form von zwei Delegaten:

                    • Der erste Parameter gibt den Code an, der ausgef\u00fchrt werden soll, wenn der Befehl ausgef\u00fchrt wird.
                    • Der zweite Parameter (optional) ist der Code, den der Befehl aufruft, um zu pr\u00fcfen, ob er sich selbst zulassen oder verbieten soll (die hier angegebene Funktion muss einen booleschen Wert zur\u00fcckgeben, im wahren Fall wird der Befehl zugelassen).

                    Der n\u00e4chste Schritt besteht darin, die Behandlung der Taste \"-\" auf command basierende umzustellen. Versuchen Sie zuerst, das meiste davon selbst zu implementieren, basierend auf dem zugeh\u00f6rigen WinUI-Vorlesungen. Das Ausf\u00fchren des Befehls ist einfacher, aber Sie m\u00fcssen etwas Arbeit investieren, um den Befehl zu deaktivieren und zu aktivieren. Die wichtigsten Schritte:

                    • F\u00fcgen Sie eine \u00f6ffentliche RelayCommand Eigenschaft mit nur Getter zum ViewModel hinzu, z.B. DecreaseAgeCommand. Anders als in den Vorlesungsmaterial brauchen wir in unserem Fall RelayCommand keinen allgemeinen Parameter zu geben, da unsere Befehlsbehandlungsfunktion (DecreaseAge) keinen Parameter hat.
                    • Geben Sie der neu eingef\u00fchrten Eigenschaft im ViewModel-Konstruktor einen Wert. Geben Sie die Parameter des RelayCommand Konstruktors entsprechend an.
                    • In PersonListPage.xaml muss die Taste \"-\" nicht mehr Click und IsEnabled binden, sie werden gel\u00f6scht. Binden Sie stattdessen die Eigenschaft Command der Taste an die Eigenschaft DecreaseAgeCommand, die im vorherigen Schritt im ViewModel eingef\u00fchrt wurde.

                    Wenn Sie es ausprobieren, funktioniert die Ausf\u00fchrund des Befehls, aber das Deaktivieren/Aktivieren nicht: Wenn Sie es gut beobachten, bleibt die Taste in ihrem Aussehen immer aktiviert. Es gibt einen logischen Grund daf\u00fcr, wenn man dar\u00fcber nachdenkt: RelayCommand kann die Aktion im zweiten Konstruktorparameter aufrufen, um den Zustand zu \u00fcberpr\u00fcfen, aber es wei\u00df nicht, dass es dies jedes Mal tun sollte, wenn NewPerson.Age sich \u00e4ndert! Wir k\u00f6nnen dabei helfen. In unserem ViewModel-Konstruktor haben wir bereits das NewPerson.PropertyChanged -Ereignis abonniert: Darauf aufbauend rufen wir, wenn sich das Alter \u00e4ndert (oder wenn es sich \u00e4ndern k\u00f6nnte, es ist kein Problem, dies manchmal unn\u00f6tigerweise zu tun), die Method NotifyCanExecuteChanged von DecreaseAgeCommand auf. Diese Operation hat einen sehr aussagekr\u00e4ftigen Namen: Sie teilt dem Befehl mit, dass sich der Zustand, auf dem der verbotene/erlaubte Zustand des Befehls aufgebaut ist, ge\u00e4ndert hat. Auf diese Weise wird der Befehl selbst aktualisiert, genauer gesagt der Zustand der mit dem Befehl verbundenen Taste.

                    \u00c4ndern Sie die Behandlung der \"+\"-Taste auf \u00e4hnliche Weise auf Befehlsbasis! \u00c4ndern Sie nicht die Behandlung der Taste \"+Add\"!

                    EINGABE

                    Machen Sie einen Screenshot mit dem Namen f4.png wie folgt:

                    • Starten Sie die App. Verkleinern Sie sie gegebenenfalls, damit sie nicht zu viel Platz auf dem Bildschirm einnimmt,
                    • der Name TextBox sollte in der Anwendung leer sein,
                    • im \"Hintergrund\" sollte Visual Studio mit PersonListPageViewModel.cs ge\u00f6ffnet sein.
                    "},{"location":"hazi/5-mvvm/index_ger/#aufgabe-5-verwendung-von-command-mit-mvvm-toolkit-basierter-codegenerierung","title":"Aufgabe 5 - Verwendung von Command mit MVVM Toolkit-basierter Codegenerierung","text":"

                    In der vorigen Aufgabe wurde die Einf\u00fchrung von Command-Eigenschaften und deren Instanziierung \"manuell\" gemacht. Das MVVM Toolkit kann dies vereinfachen: Wenn das richtige Attribut verwendet wird, k\u00f6nnen die Eigenschaft und die Instanziierung automatisch generiert werden.

                    \u00c4ndern wir die Behandlung von DecreaseAgeCommand (nur dieses, IncreaseAgeCommand soll unver\u00e4ndert bleiben! ) auf eine generierte Codebasis:

                    1. Erg\u00e4nzen Sie die Klasse PersonListPageViewModel mit dem Schl\u00fcsselwort partial.
                    2. Entfernen Sie die Eigenschaft DecreaseAgeCommand und ihre Instanziierung aus dem Konstruktor.
                    3. Erg\u00e4nzen Sie DecreaseAge mit diesem Attribut: [RelayCommand(CanExecute = nameof(IsDecrementEnabled))].
                      • Als Ergebnis f\u00fchrt der Codegenerator eine Eigenschaft RelayCommand in die Klasse ein, die mit dem Namen unserer Operation (DecreaseAge) benannt ist und an die die Zeichenfolge \"Command\" angeh\u00e4ngt ist. So erhalten wir die Eigenschaft DecreaseAgeCommand, die wir zuvor manuell eingef\u00fchrt haben.
                      • Die Attributeigenschaft CanExecute kann verwendet werden, um in Form einer Zeichenkette den Namen der Operation oder Eigenschaft mit booleschen R\u00fcckgabewert anzugeben, die der generierte Code verwenden wird, wenn er den Befehl verbietet/erlaubt (er ist der zweite Parameter des Konstruktors RelayCommand). Wir haben bereits eine solche Eigenschaft, die \"IsDecrementEnabled\" hei\u00dft. Sie wird nicht als einfache Zeichenkette angegeben, denn wenn jemand die Operation IsDecrementEnabled nachtr\u00e4glich umbenennt, w\u00fcrde die aktuelle \"IsDecrementEnabled\" nicht auf die richtige Operation verweisen. Die Verwendung des Ausdrucks nameof vermeidet dieses Problem. Die Angabe von CanExecute ist im Allgemeinen optional (geben Sie es nicht an, wenn Sie den Befehl niemals deaktivieren wollen).

                    Testen Sie die L\u00f6sung (Verkleinerung des Alters), sie sollte genauso funktionieren wie zuvor.

                    EINGABE

                    Machen Sie einen Screenshot mit dem Namen f5.png wie folgt:

                    • Starten Sie die App. Verkleinern Sie sie gegebenenfalls, damit sie nicht zu viel Platz auf dem Bildschirm einnimmt,
                    • im \"Hintergrund\" sollte Visual Studio mit PersonListPageViewModel.cs ge\u00f6ffnet sein.
                    "},{"location":"hazi/5b-mvvm-advanced/","title":"5b. HF - MVVM mint\u00e1ra \u00e9p\u00fcl\u0151 alkalmaz\u00e1sok (opcion\u00e1lis)","text":""},{"location":"hazi/5b-mvvm-advanced/#bevezetes","title":"Bevezet\u00e9s","text":"

                    A h\u00e1zi feladatban a laboron elkezdett recept alkalmaz\u00e1st fogjuk tov\u00e1bb b\u0151v\u00edteni az MVVM mint\u00e1t haszn\u00e1lva.

                    Az \u00f6n\u00e1ll\u00f3 feladat az MVVM el\u0151ad\u00e1sokon elhangzottakra \u00e9p\u00edt. A feladatok gyakorlati h\u00e1tter\u00e9\u00fcl a 5. labor \u2013 MVVM laborgyakorlat szolg\u00e1l.

                    A fentiekre \u00e9p\u00edtve, jelen \u00f6n\u00e1ll\u00f3 gyakorlat feladatai a feladatle\u00edr\u00e1st k\u00f6vet\u0151 r\u00f6videbb ir\u00e1nymutat\u00e1s seg\u00edts\u00e9g\u00e9vel (n\u00e9ha alap\u00e9rtelmezetten \u00f6sszecsukva) \u00f6n\u00e1ll\u00f3an elv\u00e9gezhet\u0151k.

                    Az \u00f6n\u00e1ll\u00f3 gyakorlat c\u00e9lja:

                    • MVVM minta haszn\u00e1lat\u00e1nak gyakorl\u00e1sa
                    • Adatok megjelen\u00edt\u00e9se \u00e9s interakci\u00f3k kezel\u00e9se a fel\u00fcleten adatk\u00f6t\u00e9ssel
                    • Dependency Injection minta alkalmaz\u00e1sa
                    • Adatok kezel\u00e9se a szolg\u00e1ltat\u00e1s r\u00e9tegben HTTP k\u00e9r\u00e9seken illetve egy lok\u00e1lis adatt\u00e1r seg\u00edts\u00e9g\u00e9vel

                    A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s.

                    Fejleszt\u0151k\u00f6rnyezet WinUI3 fejleszt\u00e9shez

                    A kor\u00e1bbi laborokhoz hasonl\u00f3an plusz komponensek telep\u00edt\u00e9se sz\u00fcks\u00e9ges. A fenti oldal eml\u00edti, hogy sz\u00fcks\u00e9g van a \".NET desktop development\" Visual Studio Workload telep\u00edt\u00e9s\u00e9re, valamint ugyanitt az oldal alj\u00e1n van egy \"WinUI t\u00e1mogat\u00e1s\" fejezet, az itt megadott l\u00e9p\u00e9seket is mindenk\u00e9ppen meg kell tenni!

                    "},{"location":"hazi/5b-mvvm-advanced/#a-beadas-menete","title":"A bead\u00e1s menete","text":"
                    • Az alapfolyamat megegyezik a kor\u00e1bbiakkal. GitHub Classroom seg\u00edts\u00e9g\u00e9vel hozz l\u00e9tre magadnak egy repository-t. A megh\u00edv\u00f3 URL-t Moodle-ben tal\u00e1lod (a t\u00e1rgy nyit\u00f3oldal\u00e1n a \"GitHub classroom hivatkoz\u00e1sok a h\u00e1zi feladatokhoz\" hivatkoz\u00e1sra kattintva megjelen\u0151 oldalon l\u00e1that\u00f3). Fontos, hogy a megfelel\u0151, ezen h\u00e1zi feladathoz tartoz\u00f3 megh\u00edv\u00f3 URL-t haszn\u00e1ld (minden h\u00e1zi feladathoz m\u00e1s URL tartozik). Kl\u00f3nozd le az \u00edgy elk\u00e9sz\u00fclt repository-t. Ez tartalmazni fogja a megold\u00e1s elv\u00e1rt szerkezet\u00e9t. A feladatok elk\u00e9sz\u00edt\u00e9se ut\u00e1n commit-old \u00e9s push-old a megold\u00e1sod.
                    • A kikl\u00f3nozott f\u00e1jlok k\u00f6z\u00f6tt a MvvmLab.sln-t megnyitva kell dolgozni.
                    • A feladatok k\u00e9rik, hogy k\u00e9sz\u00edts k\u00e9perny\u0151k\u00e9pet a megold\u00e1s egy-egy r\u00e9sz\u00e9r\u0151l, mert ezzel bizony\u00edtod, hogy a megold\u00e1sod saj\u00e1t magad k\u00e9sz\u00edtetted. A k\u00e9perny\u0151k\u00e9pek elv\u00e1rt tartalm\u00e1t a feladat minden esetben pontosan megnevezi. A k\u00e9perny\u0151k\u00e9peket a megold\u00e1s r\u00e9szek\u00e9nt kell beadni, a repository-d gy\u00f6k\u00e9rmapp\u00e1j\u00e1ba tedd (a neptun.txt mell\u00e9). A k\u00e9perny\u0151k\u00e9pek \u00edgy felker\u00fclnek GitHub-ra a git repository tartalm\u00e1val egy\u00fctt. Mivel a repository priv\u00e1t, azt az oktat\u00f3kon k\u00edv\u00fcl m\u00e1s nem l\u00e1tja. Amennyiben olyan tartalom ker\u00fcl a k\u00e9perny\u0151k\u00e9pre, amit nem szeretn\u00e9l felt\u00f6lteni, kitakarhatod a k\u00e9pr\u0151l.
                    • Ehhez a feladathoz \u00e9rdemi el\u0151ellen\u0151rz\u0151 nem tartozik: minden push ut\u00e1n lefut ugyan, de csak a neptun.txt kit\u00f6lt\u00f6tts\u00e9g\u00e9t ellen\u0151rzi. Az \u00e9rdemi ellen\u0151rz\u00e9st a hat\u00e1rid\u0151 lej\u00e1rta ut\u00e1n a laborvezet\u0151k teszik majd meg.
                    "},{"location":"hazi/5b-mvvm-advanced/#kikotesek","title":"Kik\u00f6t\u00e9sek","text":"

                    MVVM minta k\u00f6telez\u0151 alkalmaz\u00e1sa! Jelen h\u00e1zi feladatban az MVVM mint\u00e1t gyakoroljuk, \u00edgy a feladatok megold\u00e1s\u00e1ban k\u00f6telez\u0151 az MVVM minta alkalmaz\u00e1sa. Az ett\u0151l val\u00f3 elt\u00e9r\u00e9s a feladatok \u00e9rt\u00e9kel\u00e9s\u00e9nek elutas\u00edt\u00e1s\u00e1t vonja maga ut\u00e1n.

                    "},{"location":"hazi/5b-mvvm-advanced/#kiindulo-allapot","title":"Kiindul\u00f3 \u00e1llapot","text":"

                    A kiindul\u00f3 \u00e1llapot \u00e9p\u00edt az 5. labor v\u00e9g\u00e1llapot\u00e1ra, de ahhoz k\u00e9pest egy l\u00e9nyeges v\u00e1ltoztat\u00e1st tartalmaz.

                    Az alkalmaz\u00e1s az indul\u00e1sa ut\u00e1n l\u00e9trehoz egy ShellPage t\u00edpus\u00fa oldalt, ami a projektben a Views mapp\u00e1ban tal\u00e1lhat\u00f3 meg. Ez egy NavigationView-t tartalmaz (aka. Hamburger men\u00fc), mely a navig\u00e1ci\u00f3t fogja eset\u00fcnkben kezelni. Tartalmazhat NavigationViewItem-eket, melyek a men\u00fcpontokat reprezent\u00e1lj\u00e1k, \u00e9s mindig el\u00e9rhet\u0151ek az alkalmaz\u00e1sban. A men\u00fcpontokra kattintva a Frame-en bel\u00fcl a megfelel\u0151 oldal jelenik meg a projektben tal\u00e1lhat\u00f3 seg\u00e9doszt\u00e1lyok seg\u00edts\u00e9g\u00e9vel, ami t\u00e1mogatja a kor\u00e1bbi oldalra t\u00f6rt\u00e9n\u0151 vissza navig\u00e1ci\u00f3t is.

                    "},{"location":"hazi/5b-mvvm-advanced/#1-feladat-receptek-kedvenckent-kezelese","title":"1. Feladat - Receptek kedvenck\u00e9nt kezel\u00e9se","text":"

                    Feladatunk funkcion\u00e1lis k\u00f6vetelm\u00e9nyei a k\u00f6vetkez\u0151ek:

                    • A recepteket kedvencek k\u00f6z\u00e9 lehessen menteni

                      • Jelenjen meg egy kit\u00f6ltetlen csillag ikonnal rendelkez\u0151 gomb a recept r\u00e9szletes oldalon (pl. bal oldali oszlop tetej\u00e9n), amelyre kattintva a receptet a kedvencek k\u00f6z\u00e9 menthetj\u00fck.
                      • A kedvenc kezel\u0151 gomb ikonja v\u00e1ltson tele csillagra, a sz\u00f6vege pedig \"Remove from Favorites\"-re, ha a recept kedvencnek lett jel\u00f6lve.
                      • A kor\u00e1bban kedvencnek jel\u00f6lt recept kivehet\u0151 a kedvencek k\u00f6z\u00fcl ugyanezen a gombon t\u00f6rt\u00e9n\u0151 kattint\u00e1ssal: ekkor a gomb ikonja \u00e1llapota vissza\u00e1ll \u00fcres csillagra, a sz\u00f6vege pedig \"Add to Favorites\"-re.
                      • A kedvenc receptek list\u00e1j\u00e1t lok\u00e1lisan t\u00e1roljuk, az alkalmaz\u00e1s bez\u00e1r\u00e1s\u00e1val ne vesszenek el.

                        Add To FavoritesRemove From Favorites

                        A k\u00e9t gomb \u00e1llapot megjelen\u00edt\u00e9se

                        A fenti \u00e1bra felett az \"Add To Favorites\" \u00e9s \"Remove From Favorites\"-en kattintva lehet v\u00e1ltani a k\u00e9t \u00e1llapotot megjelen\u00edt\u0151 k\u00e9pek k\u00f6z\u00f6tt.

                    • A kedvencek list\u00e1j\u00e1t jelen\u00edts\u00fck meg egy k\u00fcl\u00f6n oldalon.

                      • A kedvencek list\u00e1t a hamburger men\u00fcb\u0151l lehessen el\u00e9rni
                      • A list\u00e1ban l\u00e9v\u0151 elemek kin\u00e9zete hasonl\u00f3 legyen a receptek list\u00e1j\u00e1ban l\u00e9v\u0151 elemekhez
                      • A lista ne legyen csoportos\u00edtva
                      • A kedvencek list\u00e1j\u00e1nak elemei k\u00f6z\u00f6tt a recepteket kattintva megnyithatjuk a recept r\u00e9szletes oldal\u00e1t (pont \u00fagy, mint a Recipes oldalon)

                    "},{"location":"hazi/5b-mvvm-advanced/#11-kedvencek-kezelese-a-szolgaltatas-retegben","title":"1.1 Kedvencek kezel\u00e9se a szolg\u00e1ltat\u00e1s r\u00e9tegben","text":"

                    Bottom-up megval\u00f3s\u00edt\u00e1si sorrendben haladva k\u00e9sz\u00edts\u00fck el el\u0151sz\u00f6r a szolg\u00e1ltat\u00e1s r\u00e9tegben a kedvencek kezel\u00e9s\u00e9hez sz\u00fcks\u00e9ges funkci\u00f3kat.

                    A kedvencnek megjel\u00f6l\u00e9st az online szolg\u00e1ltat\u00e1s nem t\u00e1mogatja. A megold\u00e1s alapelve \u00edgy a k\u00f6vetkez\u0151 lesz:

                    • Lok\u00e1lisan perzisztensen elt\u00e1roljuk a kedvencnek megjel\u00f6lt receptek azonos\u00edt\u00f3it (annak \u00e9rdek\u00e9ben, hogy a program \u00fajraindul\u00e1s\u00e1t k\u00f6vet\u0151en megmaradjon ez az inform\u00e1ci\u00f3).
                    • A kedvencnek megjel\u00f6lt receptek r\u00e9szletes adatait (c\u00edm, k\u00e9p) az online szolg\u00e1ltat\u00e1st\u00f3l k\u00e9rdezz\u00fck le (az azonos\u00edt\u00f3ik alapj\u00e1n).

                    Lok\u00e1lis perzisztens adatt\u00e1rol\u00e1shoz a kiindul\u00f3 projektben el\u0151 van k\u00e9sz\u00edtve az ILocalSettingsService interf\u00e9sz (\u00e9s egy ezt megval\u00f3s\u00edt\u00f3 implement\u00e1ci\u00f3). Erre \u00e9p\u00edtve kulcs \u00e9rt\u00e9k p\u00e1rokat tudunk JSON soros\u00edtva t\u00e1rolni lok\u00e1lisan az alkalmaz\u00e1sban.

                    public interface ILocalSettingsService\n{\n    Task<T> ReadSettingAsync<T>(string key);\n    Task SaveSettingAsync<T>(string key, T value);\n}\n

                    Haszn\u00e1lata sor\u00e1n \u00e9rdemes odafigyelni arra, hogy a f\u00fcggv\u00e9nyek generikusak, \u00edgy a t\u00edpusokat explicit meg kell(het) adni a h\u00edv\u00e1s sor\u00e1n.

                    A fenti ILocalSettingsService seg\u00edts\u00e9g\u00e9vel egy adott kulcs alatt fogjuk a kedvenc receptek azonos\u00edt\u00f3inak list\u00e1j\u00e1t elt\u00e1rolni.

                    Szint\u00e9n fontos, hogy a f\u00fcggv\u00e9nyek Task-kal t\u00e9rnek vissza, teh\u00e1t aszinkronok, \u00edgy await kulcssz\u00f3val kell h\u00edvni \u0151ket, \u00e9s a h\u00edv\u00f3 f\u00fcggv\u00e9nynek is aszinkronnak kell lennie (a r\u00e9szletesebb szab\u00e1lyhalmaz a kapcsol\u00f3d\u00f3 \"5. MVVM\" labor le\u00edr\u00e1s\u00e1ban tal\u00e1lhat\u00f3).

                    A kedvencek kezel\u00e9se a labor sor\u00e1n bevezetett IRecipeService interf\u00e9sz \u00e9s az ezt megval\u00f3s\u00edt\u00f3 RecipeService oszt\u00e1ly feladata legyen.

                    Els\u0151 l\u00e9p\u00e9sben azt kell megoldani, hogy a RecipeService sz\u00e1m\u00e1ra rendelkez\u00e9sre \u00e1lljon egy ILocalSettingsService interf\u00e9szt megval\u00f3s\u00edt\u00f3 objektum, melyet fel tud haszn\u00e1lni megval\u00f3s\u00edt\u00e1s\u00e1ban a kedvenc receptazonos\u00edt\u00f3k elt\u00e1rol\u00e1s\u00e1ra \u00e9s lek\u00e9rdez\u00e9s\u00e9re. A c\u00e9lunk az, hogy RecipeService-ben ILocalSettingsService interf\u00e9szk\u00e9nt kapjuk meg \u00e9s t\u00e1roljuk ezt az implement\u00e1ci\u00f3s objektumot, semmif\u00e9le f\u00fcgg\u00e9st nem szeretn\u00e9nk itt bevezetni a konkr\u00e9t implement\u00e1ci\u00f3t\u00f3l. Ezt a laboron m\u00e1r alkalmazott DI kont\u00e9ner seg\u00edts\u00e9g\u00e9vel val\u00f3s\u00edtsuk meg.

                    Tip

                    A megval\u00f3s\u00edt\u00e1s sor\u00e1n a RecipeService-ben ahhoz hasonl\u00f3an kell kezelj\u00fck a ILocalSettingsService-t, mint a ahogy a labor sor\u00e1n a MainPageViewModel-ben kezelt\u00fck a IRecipeService-t.

                    Miut\u00e1n a fenti el\u0151k\u00e9sz\u00edt\u00e9ssel elk\u00e9sz\u00fclt\u00e9l, val\u00f3s\u00edtsd meg a sz\u00fcks\u00e9ges funkci\u00f3kat a RecipeService oszt\u00e1lyban! Az al\u00e1bbiakban ehhez n\u00e9mi ir\u00e1nymutat\u00e1st adunk.

                    RecipeService v\u00e1za

                    A RecipeService-nek (\u00e9s interf\u00e9sznek) a k\u00f6vetkez\u0151 \u00faj funkci\u00f3kkal kell rendelkeznie:

                    1. Recept kedvenc \u00e1llapot\u00e1nak m\u00f3dos\u00edt\u00e1sa id (int) alapj\u00e1n az \u00faj \u00e1llapottal (bool). (Recept r\u00e9szletes oldalon gombra kattint\u00e1s sor\u00e1n haszn\u00e1ljuk.)

                      1. K\u00e9rdezz\u00fck le az ILocalSettingsService-b\u0151l kedvencek azonos\u00edt\u00f3inak list\u00e1j\u00e1t.
                      2. Lista m\u00f3dos\u00edt\u00e1sa a kapott id \u00e9s \u00faj kedvenc \u00e1llapot alapj\u00e1n.
                        1. Kedvencnek jel\u00f6l\u00e9s eset\u00e9n, berakjuk, egy\u00e9bk\u00e9nt t\u00f6r\u00f6lj\u00fck.
                        2. Gondoljunk arra is, ha a lista m\u00e1r tartalmazza az adott id-t, akkor ne adjuk hozz\u00e1 \u00fajra. (Lista helyett egy\u00e9bk\u00e9nt lehet haszn\u00e1lni egy speci\u00e1lis halmaz tulajdons\u00e1g\u00fa kollekci\u00f3t is, a HashSet<T>-et, mely egy elemet csak egyszer tartalmaz.)
                    2. Kedvenc receptek lek\u00e9rdez\u00e9se. (Kedvencek oldalon list\u00e1z\u00e1s sor\u00e1n haszn\u00e1ljuk.)

                      1. K\u00e9rdezz\u00fck le az ILocalSettingsService-b\u0151l a kedvenc receptek azonos\u00edt\u00f3inak list\u00e1j\u00e1t.
                      2. A kapott id-k alapj\u00e1n egyes\u00e9vel k\u00e9rj\u00fck le a recepteket a REST API-t\u00f3l, a GET /api/Recipes/{id}/Header v\u00e9gponton kereszt\u00fcl. Ez a laborhoz k\u00e9pest egy \u00faj v\u00e9gpont, \u00e9s az adott azonos\u00edt\u00f3j\u00fa recept RecipeHeader-be soros\u00edtott adataival t\u00e9r vissza. Ehhez a v\u00e9gponthoz \u00e9rdemes \u00faj seg\u00e9df\u00fcggv\u00e9nyt is k\u00e9sz\u00edteni. Dolgozhatunk a laboron m\u00e1r bevezetett RecipeService-ben lev\u0151 HttpClient-et haszn\u00e1l\u00f3 m\u0171veletek \"mint\u00e1j\u00e1ra\".
                      3. A lek\u00e9rdezett RecipeHeader objektumokb\u00f3l \u00f6ssze\u00e1ll\u00edtott list\u00e1val t\u00e9rj\u00fcnk vissza.
                    3. Recept kedvenc \u00e1llapot\u00e1nak lek\u00e9rdez\u00e9se id alapj\u00e1n. (Recept r\u00e9szletes oldal bet\u00f6lt\u00e9sekor a gomb \u00e1llapot\u00e1nak be\u00e1ll\u00edt\u00e1s\u00e1hoz haszn\u00e1ljuk.)

                      1. Igaz hamis \u00e9rt\u00e9kkel t\u00e9rj\u00fcnk vissza, att\u00f3l f\u00fcgg\u0151en, hogy az adott azonos\u00edt\u00f3 szerepel-e a kedvenc receptek list\u00e1j\u00e1ban.

                    Els\u0151 h\u00edv\u00e1s

                    Gondolni kell arra is, ha m\u00e9g most h\u00edvjuk meg el\u0151sz\u00f6r a lek\u00e9rdez\u0151 f\u00fcggv\u00e9nyt, \u00e9s nincs m\u00e9g mentett kedvenc recept azonos\u00edt\u00f3 list\u00e1nk (null-lal t\u00e9r vissza az adott kulcs\u00fa elem lek\u00e9rdez\u00e9sekor az ILocalSettingsService.ReadSettingAsync).

                    "},{"location":"hazi/5b-mvvm-advanced/#12-kedvencnek-jeloles-a-reszletes-oldalon","title":"1.2 Kedvencnek jel\u00f6l\u00e9s a r\u00e9szletes oldalon","text":"

                    A recept r\u00e9szletes oldalon (a RecipeDetailPage-en) meg kell jelen\u00edteni egy gombot, melynek k\u00e9t \u00e1llapota van:

                    1. Ha a recept nincs kedvencnek jel\u00f6lve, akkor egy \u00fcres csillag ikon jelenik meg a gombon, a gomb felirata pedig legyen \"Add to Favorites\".
                    2. Ha a recept kedvencnek van jel\u00f6lve, akkor egy kit\u00f6lt\u00f6tt csillag ikon jelenik meg a gombon, a gomb felirata pedig legyen \"Remove from Favorites\".
                    Add To FavoritesRemove From Favorites

                    Ezt az igaz-hamis \u00e1llapotot \u00e9s m\u00f3dos\u00edt\u00f3 m\u0171veletet c\u00e9lszer\u0171 a RecipeDetailPageViewModel-ban t\u00e1rolni/bevezetni (mivel a ViewModelnek defin\u00edci\u00f3 szerint ez a feladata), majd adatk\u00f6t\u00e9ssel k\u00f6tni az \u00e1llapotot gomb kin\u00e9zet\u00e9hez, illetve a m\u0171veletet commandj\u00e1hoz. Mindenk\u00e9ppen az MVVM mint\u00e1t k\u00f6vetve dolgozzunk!

                    RecipeDetailPageViewModel m\u00f3dos\u00edt\u00e1sa

                    A RecipeDetailViewModel-t m\u00f3dos\u00edtani sz\u00fcks\u00e9ges a k\u00f6vetkez\u0151kkel:

                    1. Kedvenc \u00e1llapot t\u00e1rol\u00e1sa
                      1. Az \u00e1llapotot egy bool t\u00edpus\u00fa property-ben t\u00e1roljuk (mindenk\u00e9ppen \u00e9rdemes az [ObservableProperty] attrib\u00fatumot haszn\u00e1lni, m\u0171k\u00f6d\u00e9s\u00e9nek \u00e9s jelent\u0151s\u00e9g\u00e9nek \u00e1tism\u00e9tl\u00e9s\u00e9vel).
                      2. Az \u00e1llapotot az IRecipeService-b\u0151l lek\u00e9rdezve inicializ\u00e1ljuk az oldalra val\u00f3 navig\u00e1l\u00e1skor.
                      1. Elmenti az \u00faj kedvenc \u00e1llapotot az IRecipeService seg\u00edts\u00e9g\u00e9vel.
                      2. Gondoskodik a ViewModel oszt\u00e1lyunkban t\u00e1rolt bool kedvenc \u00e1llapot tulajdons\u00e1g karbantart\u00e1s\u00e1r\u00f3l.

                      \u00daj command f\u00fcggv\u00e9ny k\u00e9sz\u00edt\u00e9se, amely

                      Tipp a megold\u00e1shoz

                      A megold\u00e1s elve hasonl\u00edt a SendComment parancsf\u00fcggv\u00e9nyhez, de itt a CanExecute-tal nem kell foglalkozzunk, hiszen az \u00faj commandunk mindig futtathat\u00f3.

                    \u00c1llapot t\u00e1rol\u00e1sa a modellben

                    A kedvenc \u00e1llapotot a RecipeHeader modellben is t\u00e1rolhatn\u00e1nk, viszont az k\u00e9t m\u00e1sik probl\u00e9m\u00e1t is gener\u00e1lna: a modellnek kell megval\u00f3s\u00edtania az INotifyPropertyChanged interf\u00e9szt, hogy az \u00e1llapot v\u00e1ltoz\u00e1s\u00e1t jelezni tudja. Ezen fel\u00fcl az \u00faj property \u00e9rt\u00e9k\u00e9t valamelyik m\u00e1sik r\u00e9tegben (ViewModel vagy Service) kellene kit\u00f6lteni, mivel ez az inf\u00f3 csak lok\u00e1lisan \u00e9rhet\u0151 el, a RecipeHeaderpedig alapvet\u0151en most csak egy DTO (Data Transfer Object) a modell r\u00e9tegben.

                    RecipeDetailPage (vagyis a View) m\u00f3dos\u00edt\u00e1sa

                    A RecipeDetailPage-en a k\u00f6vetkez\u0151ket kell m\u00f3dos\u00edtani:

                    1. \u00daj gomb hozz\u00e1ad\u00e1sa az oldal tetej\u00e9re, tartalma legyen egy SymbolIcon \u00e9s egy TextBlock egym\u00e1s mellett.
                      1. A SymbolIcon-nak a Symbol tulajdons\u00e1g\u00e1hoz haszn\u00e1ljuk a Symbol.SolidStar \u00e9s Symbol.OutlineStar enum \u00e9rt\u00e9keket a csillag ikonokhoz.
                    2. A gomb commandj\u00e1t adatk\u00f6tni kell a ViewModel-ben tal\u00e1lhat\u00f3 command-hoz.

                    A ViewModel-ben t\u00e1rolt bool \u00e9rt\u00e9ket valamilyen m\u00f3don Symbol enumra (gomb ikonja) \u00e9s string-re (gomb aktu\u00e1lis sz\u00f6vege) kell konvert\u00e1lni, hogy a fel\u00fcleten a gomb megjelen\u00e9se mindk\u00e9t \u00e1llapotban a megfelel\u0151 legyen. Erre t\u00f6bb megold\u00e1s is lehets\u00e9ges:

                    • x:Bind haszn\u00e1lata, ahol nem property-t k\u00f6t\u00fcnk, hanem egy a xaml.cs-ben l\u00e9v\u0151 seg\u00e9df\u00fcggv\u00e9nyt, mely a konverzi\u00f3t elv\u00e9gzi. Vagyis property k\u00f6t\u00e9s helyett f\u00fcggv\u00e9ny/funkci\u00f3 k\u00f6t\u00e9st haszn\u00e1lunk. El\u0151ad\u00e1sanyagban a \"Property k\u00f6t\u00e9se funkci\u00f3khoz\"-ra \u00e9rdemes r\u00e1keresni, illetve a 3. h\u00e1zi feladatban a \"f\u00fcggv\u00e9ny k\u00f6t\u00e9s p\u00e9lda\"-ra.
                    • Az IValueConverter interf\u00e9sz implement\u00e1l\u00e1sa \u00e9s haszn\u00e1lata az adatk\u00f6t\u00e9s sor\u00e1n.
                    • A RecipeDetailPageViewModel-ben t\u00e1roljuk a n\u00e9zethez sz\u00fcks\u00e9ges adatokat \u00faj tuljadons\u00e1gokat bevezetve (a tulajdons\u00e1gok t\u00edpusa a n\u00e9zet sz\u00e1m\u00e1ra sz\u00fcks\u00e9ges Symbol \u00e9s string), \u00e9s ezekhez t\u00f6rt\u00e9nik az adatk\u00f6t\u00e9s.
                      • Tal\u00e1n ez a legegyszer\u0171bb megold\u00e1s, ha nem szeretn\u00e9nk k\u00fcl\u00f6n konvertert \u00edrni vagy az adatk\u00f6t\u00e9seket \"bonyol\u00edtani\", viszont a legkev\u00e9sb\u00e9 is lesz karbantarthat\u00f3, mivel a ViewModel view specifikus adatokat is tartalmaz, melyeket k\u00fcl\u00f6n karban is kell tartani ha a bool property megv\u00e1ltozik.

                    1.2. feladat BEADAND\u00d3

                    Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol a teend\u0151 r\u00e9szletes oldalon megjelenik a kedvencnek jel\u00f6l\u00e9s gomb! (f1.2.1.png)

                    Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol a teend\u0151 r\u00e9szletes oldalon egy m\u00e1r kedvencnek jel\u00f6lt recepthez a kedvencekb\u0151l elt\u00e1vol\u00edt\u00e1s gomb jelenik meg! (f1.2.2.png)

                    "},{"location":"hazi/5b-mvvm-advanced/#13-kedvencek-oldal-navigacio","title":"1.3 Kedvencek oldal navig\u00e1ci\u00f3","text":"

                    A kedvencek oldalra navig\u00e1l\u00e1shoz t\u00f6bb l\u00e9p\u00e9sre is sz\u00fcks\u00e9g\u00fcnk lesz, melyek a kiindul\u00f3 projekt saj\u00e1toss\u00e1gaib\u00f3l ad\u00f3d\u00f3dnak, de ezeket itt r\u00e9szletesen \u00e1tvessz\u00fck (a navig\u00e1ci\u00f3 megval\u00f3s\u00edt\u00e1sa nem r\u00e9sze a tanagyagnak).

                    1. Hozzuk l\u00e9tre a FavoritesPage-et a Views mapp\u00e1ban (Add/New Item/Blank Page (WinUI3))

                      Ford\u00edt\u00e1si hib\u00e1k

                      Ha valami\u00e9rt egzotikus hib\u00e1kat kapn\u00e1nk az \u00faj oldal felv\u00e9tele ut\u00e1n t\u00f6r\u00f6lj\u00fck ki a projekt f\u00e1jlb\u00f3l az al\u00e1bbi sorokat:

                      <ItemGroup>\n    <None Remove=\"Views\\FavoritesPage.xaml\" />\n</ItemGroup>\n
                      <Page Update=\"Views\\FavoritesPage.xaml\">\n    <Generator>MSBuild:Compile</Generator>\n</Page>\n
                    2. Hozzuk l\u00e9tre a FavoritesPageViewModel oszt\u00e1lyt a ViewModels mapp\u00e1ban

                      1. Gondoskodjunk arr\u00f3l, hogy a megfelel\u0151 oszt\u00e1lyb\u00f3l sz\u00e1rmazzon!
                      2. Val\u00f3s\u00edtsa meg az INavigationAware interf\u00e9szt a navig\u00e1ci\u00f3 t\u00e1mogat\u00e1s\u00e1hoz (egyel\u0151re \u00fcres f\u00fcggv\u00e9nyt\u00f6rzzsel).
                    3. Regisztr\u00e1ljuk be az App.xaml.cs-ben a Dependency Injection kont\u00e9nerbe az \u00faj n\u00e9zetet \u00e9s az \u00faj ViewModelt:

                      services.AddTransient<FavoritesPage>();\nservices.AddTransient<FavoritesPageViewModel>();\n
                    4. A Pages oszt\u00e1lyban (PageService.cs) vegy\u00fcnk fel egy \u00faj kulcsot a kedvencek oldalhoz, \u00e9s konfigur\u00e1ljuk a navig\u00e1ci\u00f3t ehhez a kulcshoz:

                      Pages
                      public static string Favorites { get; } = \"Favorites\";\n
                      PageService konstruktor
                      Configure<FavoritesPageViewModel, FavoritesPage>(Pages.Favorites);\n
                    5. A ShellPage-en a NavigationView-hoz adjunk hozz\u00e1 egy \u00faj NavigationViewItem-et a kedvencek oldalhoz:

                      <NavigationViewItem helpers:NavigationHelper.NavigateTo=\"Favorites\" Content=\"Favorites\">\n    <NavigationViewItem.Icon>\n        <SymbolIcon Symbol=\"SolidStar\" />\n    </NavigationViewItem.Icon>\n</NavigationViewItem>\n

                      Navig\u00e1ci\u00f3

                      A navig\u00e1ci\u00f3 a helpers:NavigationHelper.NavigateTo=\"Favorites\" attached property seg\u00edts\u00e9g\u00e9vel t\u00f6rt\u00e9nik, ahol azt a kulcsot adhatjuk meg, amilyen kulcs\u00fa oldalra navig\u00e1lni szeretn\u00e9nk.

                    "},{"location":"hazi/5b-mvvm-advanced/#14-kedvencek-oldal-logika","title":"1.4 Kedvencek oldal logika","text":"

                    A kedvencek oldal (FavoritesPage) a MainPage mint\u00e1j\u00e1ra k\u00e9sz\u00fclj\u00f6n el, \u00e9s a receptek list\u00e1j\u00e1t jelen\u00edtse meg, csoportos\u00edt\u00e1s n\u00e9lk\u00fcl (!) egy AdaptiveGridView vez\u00e9rl\u0151ben.

                    A ViewModel (FavoritesPageViewModel) a MainPageViewModel mint\u00e1j\u00e1ra k\u00e9sz\u00fclj\u00f6n el, \u00e9s a navig\u00e1ci\u00f3 sor\u00e1n k\u00e9rdezze le az IRecipeService-t\u0151l a kedvenc receptek list\u00e1j\u00e1t (GetFavoriteRecipesAsync) \u00e9s t\u00e1rolja el egy megfelel\u0151, pl. gener\u00e1lt tulajdons\u00e1gba. Mivel itt nem csoportos\u00edtjuk a recepteket, RecipeGroup-ok helyett RecipeHeader-ekkel kell dolgozni.

                    1.4. feladat BEADAND\u00d3

                    Illessz be egy k\u00e9perny\u0151k\u00e9pet az alkalmaz\u00e1sr\u00f3l, ahol kedvencek lista l\u00e1that\u00f3! (f1.4.png)

                    "},{"location":"hazi/5b-mvvm-advanced/#beadas","title":"Bead\u00e1s","text":"

                    Ellen\u0151rz\u0151lista ism\u00e9tl\u00e9sk\u00e9ppen:

                    • A repository gy\u00f6k\u00e9rmapp\u00e1j\u00e1ban tal\u00e1lhat\u00f3 neptun.txt f\u00e1jlba \u00edrd bele a Neptun k\u00f3dod, csupa nagybet\u0171vel. A f\u00e1jlban csak ez a hat karakter legyen, semmi m\u00e1s.
                    • A GitHub-r\u00f3l le\u00f6lt\u00f6tt kiindul\u00f3 solutionben/projektekben kell dolgozni, nem \u00fajonnan l\u00e9trehozottban.
                    • Am\u00edg nem vagy rutinos a Visual Studio Git szolg\u00e1ltat\u00e1sainak haszn\u00e1lat\u00e1ban, a push-t k\u00f6vet\u0151en (legk\u00e9s\u0151bb akkor, amikor a h\u00e1zi feladatot beadottnak tekintj\u00fck) c\u00e9lszer\u0171 ellen\u0151rizni a GitHub webes fel\u00fclet\u00e9n a repository-ban a f\u00e1jlokra val\u00f3 r\u00e1pillant\u00e1ssal, hogy val\u00f3ban minden v\u00e1ltoztat\u00e1st felt\u00f6lt\u00f6tt\u00e9l-e.
                    • A GitHub fel\u00fclet\u00e9n ellen\u0151rizd a push-t k\u00f6vet\u0151en, hogy a GitHub Action alap\u00fa el\u0151ellen\u0151rz\u0151 hiba n\u00e9lk\u00fcl lefutott-e.
                    • L\u00e9nyeges, hogy a feladatok csak akkor ker\u00fclnek elfogad\u00e1sra, ha teljesen elk\u00e9sz\u00fclnek, \u00e9s minden tekintetben teljes\u00edtik a k\u00f6vetelm\u00e9nyeket. Nem fordul\u00f3 k\u00f3d, illetve r\u00e9szleges megold\u00e1s elfogad\u00e1s\u00e1ban nem \u00e9rdemes b\u00edzni.
                    • Term\u00e9szetesen saj\u00e1t munk\u00e1t kell beadni (hiszen \u00e9rt\u00e9kel\u00e9sre ker\u00fcl).
                    "},{"location":"hazi/5b-mvvm-advanced/index_ger/","title":"5. HF - Anwendungen auf der Grundlage des MVVM-Modells","text":""},{"location":"hazi/5b-mvvm-advanced/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

                    Als Hausaufgabe werden wir die in der \u00dcbung begonnene Rezeptanwendung mit Hilfe der MVVM-Vorlage erweitern.

                    Die eigenst\u00e4ndige \u00dcbung baut auf dem auf, was in den MVVM-Vorlesungen gesagt wurde. Den praktischen Hintergrund f\u00fcr die \u00dcbungen liefert die Labor\u00fcbung 5 - MVVM-Labor\u00fcbung.

                    Darauf aufbauend k\u00f6nnen die Aufgaben dieser Selbst\u00fcbung mit Hilfe der k\u00fcrzeren Leitf\u00e4den, die auf die Aufgabenbeschreibung folgen (manchmal standardm\u00e4\u00dfig eingeklappt), selbst\u00e4ndig bearbeitet werden.

                    Das Ziel der unabh\u00e4ngigen \u00dcbung:

                    • \u00dcben mit dem MVVM-Beispiel
                    • Anzeige von Daten und Verwaltung von Interaktionen auf der Schnittstelle mit Datenbindung
                    • Anwendung des Dependency Injection-Musters
                    • Datenverwaltung auf der Dienstebene \u00fcber HTTP-Anfragen oder ein lokales Repository

                    Die erforderliche Entwicklungsumgebung wird hier beschrieben.

                    Entwicklungsumgebung f\u00fcr WinUI3-Entwicklung

                    Wie in den vorherigen \u00dcbungen m\u00fcssen zus\u00e4tzliche Komponenten installiert werden. Auf der obigen Seite wird erw\u00e4hnt, dass Sie Visual Studio Workload f\u00fcr die \".NET-Desktop-Entwicklung\" installieren m\u00fcssen, und es gibt einen Abschnitt \"WinUI-Unterst\u00fctzung\" am unteren Ende der Seite, Sie sollten den Schritten dort folgen!

                    "},{"location":"hazi/5b-mvvm-advanced/index_ger/#das-verfahren-fur-die-einreichung","title":"Das Verfahren f\u00fcr die Einreichung","text":"
                    • Der grundlegende Prozess ist derselbe wie zuvor. Erstellen Sie mit GitHub Classroom ein Repository f\u00fcr sich selbst. Sie finden die Einladungs-URL in Moodle (Sie k\u00f6nnen sie sehen, indem Sie auf den Link*\"GitHub classroom links for homework*\" auf der Startseite des Fachs klicken). Es ist wichtig, dass Sie die richtige Einladungs-URL f\u00fcr diese Hausaufgabe verwenden (jede Hausaufgabe hat eine andere URL). Klonen Sie das resultierende Repository. Dazu geh\u00f6rt auch die erwartete Struktur der L\u00f6sung. Nachdem Sie die Aufgaben erledigt haben, \u00fcbergeben Sie Ihre L\u00f6sung alt und dr\u00fccken Sie sie alt.
                    • Um mit den geklonten Dateien zu arbeiten, \u00f6ffnen Sie MvvmLab.sln.
                    • In den \u00dcbungen werden Sie aufgefordert, einen Screenshot von einem Teil Ihrer L\u00f6sung zu machen, da dies beweist, dass Sie Ihre L\u00f6sung selbst erstellt haben. Der erwartete Inhalt der Screenshots wird immer in der Aufgabe angegeben. Screenshots sollten als Teil der L\u00f6sung eingereicht werden, legen Sie sie in den Stammordner Ihres Repositorys (neben neptun.txt). Die Screenshots werden dann zusammen mit dem Inhalt des Git-Repositorys auf GitHub hochgeladen. Da das Repository privat ist, ist es f\u00fcr niemanden au\u00dfer den Lehrkr\u00e4ften sichtbar. Wenn Sie Inhalte auf dem Screenshot haben, die Sie nicht hochladen m\u00f6chten, k\u00f6nnen Sie diese aus dem Screenshot herausschneiden.
                    • Diese Aufgabe enth\u00e4lt keinen sinnvollen Pre-Checker: Sie wird nach jedem Push ausgef\u00fchrt, pr\u00fcft aber nur, ob neptun.txt gef\u00fcllt ist. Die inhaltliche \u00dcberpr\u00fcfung wird von den Laborleitern nach Ablauf der Frist durchgef\u00fchrt.
                    "},{"location":"hazi/5b-mvvm-advanced/index_ger/#verbindungen","title":"Verbindungen","text":"

                    Obligatorische Verwendung des MVVM-Beispiels! In dieser Hausaufgabe \u00fcben wir das MVVM-Pattern, daher ist das MVVM-Pattern f\u00fcr die L\u00f6sung der Aufgaben zwingend erforderlich. Andernfalls wird die Bewertung der Aufgaben verweigert.

                    "},{"location":"hazi/5b-mvvm-advanced/index_ger/#ausgangszustand","title":"Ausgangszustand","text":"

                    Der Ausgangszustand baut auf dem Endzustand von Labor 5 auf, allerdings mit einer wichtigen \u00c4nderung.

                    Wenn die Anwendung gestartet wird, wird eine Seite des Typs ShellPage erstellt, die sich im Ordner Views des Projekts befindet. Es enth\u00e4lt eine NavigationView(aka. Hamburger Men\u00fc), das in unserem Fall die Navigation \u00fcbernimmt. Sie kann NavigationViewItementhalten, die Men\u00fcpunkte darstellen und in der Anwendung immer verf\u00fcgbar sind. Wenn Sie auf die Men\u00fcpunkte innerhalb von Frameklicken, wird die entsprechende Seite mit Hilfe der Hilfsklassen im Projekt aufgerufen, die auch die Navigation zur\u00fcck zur vorherigen Seite unterst\u00fctzt.

                    "},{"location":"hazi/5b-mvvm-advanced/index_ger/#1-aufgabe-rezepte-als-favoriten-verwalten","title":"1. Aufgabe - Rezepte als Favoriten verwalten","text":"

                    Die funktionalen Anforderungen an unsere Aufgabe sind:

                    • Rezepte als Favoriten speichern

                      • Auf der Detailseite des Rezepts (z. B. oben in der linken Spalte) sollte eine Schaltfl\u00e4che mit einem nicht ausgef\u00fcllten Sternsymbol zu finden sein, die angeklickt werden kann, um das Rezept als Favorit zu speichern.
                      • Das Symbol der Favoriten-Schaltfl\u00e4che sollte sich in einen vollen Stern und der Text in \"Aus den Favoriten entfernen\" \u00e4ndern, wenn das Rezept als Favorit markiert wurde.
                      • Ein zuvor als Favorit gekennzeichnetes Rezept kann durch Klicken auf dieselbe Schaltfl\u00e4che aus den Favoriten entfernt werden: Das Symbol der Schaltfl\u00e4che wird dann wieder zu einem leeren Stern und der Text \u00e4ndert sich in \"Zu Favoriten hinzuf\u00fcgen\".
                      • Ihre Liste der Lieblingsrezepte wird lokal gespeichert, damit sie nicht verloren geht, wenn Sie die App schlie\u00dfen.

                        === \"Zu Favoriten hinzuf\u00fcgen\"

                        === \"Aus Favoriten entfernen\"

                        Zwei Schaltfl\u00e4chenstatus anzeigen

                        Klicken Sie oberhalb der Abbildung auf \"Zu Favoriten hinzuf\u00fcgen\" und \"Aus Favoriten entfernen\", um zwischen den beiden Status der Bilder zu wechseln.

                    • Zeigen Sie die Liste der Favoriten auf einer separaten Seite an.

                      • Die Favoritenliste kann \u00fcber das Hamburger-Men\u00fc aufgerufen werden
                      • Die Eintr\u00e4ge in der Liste sollten \u00e4hnlich aussehen wie die Eintr\u00e4ge in der Rezeptliste
                      • Die Liste sollte nicht gruppiert werden
                      • Klicken Sie auf ein Rezept in der Favoritenliste, um die detaillierte Rezeptseite zu \u00f6ffnen (genau wie auf der Seite Rezepte)

                    "},{"location":"hazi/5b-mvvm-advanced/index_ger/#11-verwaltung-von-favoriten-in-der-dienstebene","title":"1.1 Verwaltung von Favoriten in der Dienstebene","text":"

                    In einer Bottom-up-Implementierungsreihenfolge erstellen wir zun\u00e4chst die Funktionen, die f\u00fcr die Verwaltung der Favoriten in der Dienstschicht erforderlich sind.

                    Favoriten werden vom Online-Dienst nicht unterst\u00fctzt. Das Grundprinzip der L\u00f6sung lautet also:

                    • Die Kennungen der als Favoriten markierten Rezepte werden lokal gespeichert (damit diese Informationen bei einem Neustart des Programms erhalten bleiben).
                    • Die Angaben zu den als Favoriten gekennzeichneten Rezepten (Adresse, Bild) werden vom Online-Dienst angefordert (auf der Grundlage ihrer Kennungen).

                    F\u00fcr die lokale persistente Datenspeicherung wird die Schnittstelle ILocalSettingsService (und eine Implementierung) im urspr\u00fcnglichen Projekt vorbereitet. Darauf aufbauend k\u00f6nnen wir nach JSON sortierte Schl\u00fcssel-Wert-Paare lokal in der Anwendung speichern.

                    public interface ILocalSettingsService\n{\n    Task<T> ReadSettingAsync<T>(string key);\n    Task SaveSettingAsync<T>(string key, T value);\n}\n

                    Bei der Verwendung ist zu beachten, dass die Funktionen generisch sind, so dass die Typen beim Aufruf explizit angegeben werden m\u00fcssen.

                    Mit Hilfe der obigen ILocalSettingsService speichern wir eine Liste der bevorzugten Rezept-IDs unter einem bestimmten Schl\u00fcssel.

                    Wichtig ist auch, dass die Funktionen Taskzur\u00fcckgeben, also asynchron sind. Sie m\u00fcssen also mit dem Schl\u00fcsselwort await aufgerufen werden, und die aufrufende Funktion muss ebenfalls asynchron sein (f\u00fcr einen detaillierteren Satz von Regeln siehe den zugeh\u00f6rigen Abschnitt \"5. MVVM\" Laborbeschreibung).

                    Die Verwaltung der Favoriten sollte in der Verantwortung der Schnittstelle IRecipeService und der Klasse RecipeService liegen, die sie implementiert.

                    Der erste Schritt besteht darin, RecipeService ein Objekt zur Verf\u00fcgung zu stellen, das die Schnittstelle ILocalSettingsService implementiert, die es in seiner Implementierung verwenden kann, um seine bevorzugten Rezeptbezeichnungen zu speichern und abzurufen. Unser Ziel ist es, dieses Implementierungsobjekt in RecipeServiceals Schnittstelle zu ILocalSettingsService zu erhalten und zu speichern, wir wollen hier keine Abh\u00e4ngigkeiten von der spezifischen Implementierung einf\u00fchren. Dazu wird der bereits im Labor verwendete DI-Beh\u00e4lter verwendet.

                    Tip

                    Bei der Umsetzung sollten wir ILocalSettingsServicein RecipeServicegenauso behandeln, wie wir IRecipeServicein MainPageViewModelim Labor behandelt haben.

                    Nachdem Sie die obigen Vorbereitungen getroffen haben, implementieren Sie die notwendige Funktionalit\u00e4t in der Klasse RecipeService! Hier finden Sie einige Hinweise dazu.

                    RezeptService vase

                    Der RecipeService (und die Schnittstelle) sollten die folgenden neuen Eigenschaften haben:

                    1. \u00c4ndern Sie den Status des Rezeptfavoriten basierend auf id (int) mit dem neuen Status (bool). (Rezeptdetailseite, die beim Anklicken der Schaltfl\u00e4che angezeigt wird)

                      1. Abfrage von ILocalSettingsService nach einer Liste von Favoriten-IDs.
                      2. \u00c4ndern Sie die Liste anhand der erhaltenen ID und des neuen Favoritenstatus.
                        1. Wenn Sie es als Favorit markieren, wird es hinzugef\u00fcgt, andernfalls wird es gel\u00f6scht.
                        2. Wenn die Liste die ID bereits enth\u00e4lt, f\u00fcgen Sie sie nicht erneut hinzu. (Anstelle einer Liste k\u00f6nnen Sie auch eine spezielle Sammlung mit Mengeneigenschaften verwenden, HashSet<T>, die ein Element nur einmal enth\u00e4lt)
                    2. Fragen Sie Ihre Lieblingsrezepte ab. (Wird f\u00fcr die Auflistung auf der Seite Favoriten verwendet.)

                      1. Abfrage von ILocalSettingsService nach der Liste der IDs Ihrer Lieblingsrezepte.
                      2. Auf der Grundlage der empfangenen IDs rufen wir die Rezepte einzeln von der REST-API ab, und zwar \u00fcber den Endpunkt \"GET /api/Recipes/{id}/Header\". Dies ist ein neuer Endpunkt in Bezug auf das Labor und gibt die Rezeptdaten mit der angegebenen ID zur\u00fcck, sortiert nach dem RecipeHeader. F\u00fcr diesen Endpunkt lohnt es sich auch, eine neue Hilfsfunktion zu erstellen. Wir k\u00f6nnen ein \"Muster\" von Operationen mit HttpClient in RecipeService erarbeiten, das bereits im Labor implementiert wurde.
                      3. R\u00fcckgabe mit einer Liste der abgerufenen RecipeHeader-Objekte.
                    3. Abfrage des Favoritenstatus eines Rezepts anhand der ID. (Dient zum Einstellen des Schaltfl\u00e4chenstatus beim Laden einer Rezeptdetailseite)

                      1. R\u00fcckgabe mit einem true/false-Wert, je nachdem, ob die Kennung in der Liste der Lieblingsrezepte enthalten ist.

                    Erster Anruf

                    Sie sollten auch bedenken, wenn Sie die Abfragefunktion zum ersten Mal aufrufen und keine ID-Liste der Lieblingsrezepte gespeichert haben (null wird zur\u00fcckgegeben, wenn ILocalSettingsService.ReadSettingAsync f\u00fcr das angegebene Schl\u00fcsselelement aufgerufen wird).

                    "},{"location":"hazi/5b-mvvm-advanced/index_ger/#12-auf-der-detailseite-als-favorit-markieren","title":"1.2 Auf der Detailseite als Favorit markieren","text":"

                    Auf der Rezeptseite (unter RecipeDetailPage) sollten Sie eine Schaltfl\u00e4che mit zwei Zust\u00e4nden sehen:

                    1. Wenn das Rezept nicht als Favorit markiert ist, erscheint ein leeres Sternsymbol auf der Schaltfl\u00e4che und die Schaltfl\u00e4che ist mit \"Zu Favoriten hinzuf\u00fcgen\" beschriftet.
                    2. Wenn das Rezept als Favorit markiert ist, erscheint ein ausgef\u00fclltes Sternsymbol auf der Schaltfl\u00e4che und die Schaltfl\u00e4che ist mit \"Aus Favoriten entfernen\" beschriftet.

                    === \"Zu Favoriten hinzuf\u00fcgen\"

                    === \"Aus Favoriten entfernen\"

                    Dieser true/false-Zustand und die \u00e4ndernde Aktion sollten in RecipeDetailPageViewModelgespeichert/implementiert werden (da dies per Definition die Aufgabe des ViewModels ist) und dann mit dem Zustand der Schaltfl\u00e4che und dem Befehl der Aktion datengebunden werden. Achten Sie darauf, das MVVM-Modell zu befolgen!

                    RecipeDetailPageViewModel modification

                    Das RecipeDetailViewModel sollte wie folgt ge\u00e4ndert werden:

                    1. Favoritenstatus speichern
                      1. Der Zustand wird in einer Eigenschaft vom Typ bool gespeichert (verwenden Sie unbedingt das Attribut [ObservableProperty] und wiederholen Sie dessen Funktion und Bedeutung).
                      2. Der Status wird initialisiert, indem er beim Aufrufen der Seite von \"IRecipeService\" abgerufen wird.
                      1. Speichert den neuen Favoritenstatus unter Verwendung von IRecipeService.
                      2. K\u00fcmmert sich um die Pflege der in unserer ViewModel-Klasse gespeicherten Eigenschaft \"bool\".

                      Erstellen Sie eine neue Befehlsfunktion, die

                      Tipp f\u00fcr die L\u00f6sung

                      Das Prinzip ist \u00e4hnlich wie bei der Befehlsfunktion SendComment, aber hier m\u00fcssen wir uns nicht mit CanExecute befassen, da unser neuer Befehl immer ausf\u00fchrbar ist.

                    Speichern eines Zustands im Modell

                    Der Status der Favoriten k\u00f6nnte im Modell \"RecipeHeader\" gespeichert werden, aber das w\u00fcrde zu zwei weiteren Problemen f\u00fchren: Das Modell m\u00fcsste die Schnittstelle \"INotifyPropertyChanged\" implementieren, um eine Status\u00e4nderung anzuzeigen. Dar\u00fcber hinaus sollte der Wert der neuen Eigenschaft in einer anderen Schicht (ViewModel oder Service) gef\u00fcllt werden, da diese Information nur lokal verf\u00fcgbar ist und der \"RecipeHeader\" im Grunde nur noch ein DTO (Data Transfer Object) in der Modellschicht ist.

                    RecipeDetailPage (d.h. die Ansicht) \u00e4ndern

                    Auf der \"RecipeDetailPage\" sollte folgendes ge\u00e4ndert werden:

                    1. F\u00fcgen Sie oben auf der Seite eine neue Schaltfl\u00e4che hinzu, mit einem \"SymbolSymbol\" und einem \"Textblock\" nebeneinander.
                      1. F\u00fcr die Eigenschaft SymbolIcon von Symbol sind die Enum-Werte Symbol.SolidStar und Symbol.OutlineStar f\u00fcr die Sternsymbole zu verwenden.
                    2. Der Schaltfl\u00e4chenbefehl muss mit dem Befehl im ViewModel datengebunden sein.

                    Der im ViewModel gespeicherte \"bool\"-Wert muss auf irgendeine Weise in ein \"Symbol\"-Enum (Schaltfl\u00e4chensymbol) und einen \"String\" (tats\u00e4chlicher Schaltfl\u00e4chentext) umgewandelt werden, so dass die Schaltfl\u00e4che in beiden Zust\u00e4nden auf der Oberfl\u00e4che erscheint. Es gibt mehrere m\u00f6gliche L\u00f6sungen:

                    • verwenden Sie \"x:Bind\", wobei Sie keine Eigenschaft binden, sondern eine Hilfsfunktion in xaml.cs, die die Umwandlung vornimmt. Das hei\u00dft, dass wir anstelle der Eigenschaftsbindung eine Funktions-/Funktionsbindung verwenden. In den Vorlesungsunterlagen sollten Sie nach \"Binding Property to Functions\" und in Hausaufgabe 3 nach \"Function Binding Example\" suchen.
                    • Implementierung und Verwendung der Schnittstelle \"IValueConverter\" bei der Datenbindung.
                    • Im \"RecipeDetailPageViewModel\" werden die f\u00fcr die Ansicht erforderlichen Daten gespeichert, indem neue Ergebniseigenschaften eingef\u00fchrt werden (die f\u00fcr die Ansicht erforderlichen Eigenschaftstypen sind \"Symbol\" und \"String\") und die Daten an sie gebunden werden.
                      • Dies ist wahrscheinlich die einfachste L\u00f6sung, wenn Sie keinen separaten Konverter schreiben oder die Datenbindungen \"verkomplizieren\" wollen, aber es ist auch die am wenigsten wartbare, da die ViewModel-Ansicht ansichtsspezifische Daten enth\u00e4lt, die separat gepflegt werden m\u00fcssen, wenn sich die bool-Eigenschaft \u00e4ndert.

                    Aufgabe 1.2. einzureichen

                    F\u00fcgen Sie einen Screenshot des Antrags ein, auf dem Sie eine Schaltfl\u00e4che zum Markieren als Favorit auf der Detailseite sehen (f1.2.1.png)

                    F\u00fcgen Sie einen Screenshot der App ein, auf der die Schaltfl\u00e4che \"Aus Favoriten entfernen\" auf der Detailseite eines bereits als Favorit markierten Rezepts erscheint (f1.2.2.png)

                    "},{"location":"hazi/5b-mvvm-advanced/index_ger/#13-navigation-der-favoritenseite","title":"1.3 Navigation der Favoritenseite","text":"

                    Um zur Favoritenseite zu navigieren, sind mehrere Schritte erforderlich, die f\u00fcr das urspr\u00fcngliche Projekt spezifisch sind, aber wir werden sie hier im Detail erl\u00e4utern (die Implementierung der Navigation ist nicht Teil des Tutorials).

                    1. Erstellen Sie FavoritesPageim Ordner Views (Add/New Item/Blank Page (WinUI3))

                      \u00dcbersetzungsfehler

                      Wenn Sie aus irgendeinem Grund exotische Fehler erhalten, nachdem Sie eine neue Seite hinzugef\u00fcgt haben, l\u00f6schen Sie die folgenden Zeilen in der Projektdatei:

                      <ItemGroup>\n    <Keine Remove=\"ViewsFavoritesPage.xaml\" />\n</EinzelteilGruppe>\n
                      <Seite Update=\"ViewsFavoritesPage.xaml\">\n    <Generator>MSBuild:Compile</Generator>\n</Seite>\n
                    2. Erstellen Sie die Klasse FavoritesPageViewModel im Ordner ViewModels

                      1. Achten Sie darauf, dass es aus der richtigen Klasse kommt!
                      2. Konfigurieren Sie die Schnittstelle INavigationAware so, dass sie die Navigation unterst\u00fctzt (vorerst mit einer leeren Funktionstaste).
                    3. Registrieren Sie den neuen View und das neue ViewModel im Dependency Injection Container in App.xaml.cs:

                      services.AddTransient<FavoritesPage>();\nservices.AddTransient<FavoritesPageViewModel>();\n
                    4. F\u00fcgen Sie in der Klasse Pages (PageService.cs) einen neuen Schl\u00fcssel f\u00fcr die Favoritenseite hinzu und konfigurieren Sie die Navigation zu diesem Schl\u00fcssel:

                      Pages
                      public static string Favorites { get; } = \"Favorites\";\n
                      PageService konstruktor
                      Configure<FavoritesPageViewModel, FavoritesPage>(Pages.Favorites);\n
                    5. F\u00fcgen Sie unter ShellPageeine neue NavigationViewItembis NavigationViewf\u00fcr die Favoritenseite hinzu:

                      <NavigationViewItem helpers:NavigationHelper.NavigateTo=\"Favorites\" Content=\"Favorites\">\n    <NavigationViewItem.Icon>\n        <SymbolIcon Symbol=\"SolidStar\" />\n    </NavigationViewItem.Icon>\n</NavigationViewItem>\n

                      Navigation

                      Die Navigation erfolgt \u00fcber die angeh\u00e4ngte Eigenschaft helpers:NavigationHelper.NavigateTo=\"Favorites\", in der Sie den Schl\u00fcssel angeben k\u00f6nnen, um zu der Seite mit dem Schl\u00fcssel zu navigieren, zu dem Sie navigieren m\u00f6chten.

                    "},{"location":"hazi/5b-mvvm-advanced/index_ger/#14-logik-der-favoritenseite","title":"1.4 Logik der Favoritenseite","text":"

                    Die Favoritenseite (FavoritesPage) sollte nach dem Vorbild von MainPage gestaltet werden und die Liste der Rezepte ohne Gruppierung (!) in einem AdaptiveGridView Steuerelement anzeigen.

                    Erstellen Sie ein ViewModel (FavoritesPageViewModel) basierend auf MainPageViewModel und rufen Sie die Liste der Lieblingsrezepte ( IRecipeService) w\u00e4hrend der Navigation (GetFavoriteRecipesAsync) von ab und speichern Sie sie in einer geeigneten Eigenschaft, z.B. generated. Da wir die Rezepte hier nicht gruppieren, m\u00fcssen Sie mit RecipeHeaderstatt mit RecipeGrouparbeiten.

                    1.4. exercise REQUIRED

                    Einf\u00fcgen eines Screenshots der Anwendung mit einer Liste von Favoriten (f1.4.png)

                    "},{"location":"hazi/5b-mvvm-advanced/index_ger/#vorlegen-bei","title":"Vorlegen bei","text":"

                    Checkliste f\u00fcr Wiederholungen:

                    • Es ist wichtig, dass nur die Aufgaben akzeptiert werden, die Sie vollst\u00e4ndig gemacht haben und die die Anforderungen in jeder Hinsicht erf\u00fcllen.
                    • Sie m\u00fcssen nat\u00fcrlich Ihre eigene Arbeit eingeben (da sie bewertet wird).
                    • Nicht nur das Quellcode, sondern auch die erwartete Bildschirmfotos sollen eingegeben werden.
                    "},{"location":"hazi/6-tervezesi-mintak/","title":"6. HF - Tervez\u00e9si mint\u00e1k (kiterjeszthet\u0151s\u00e9g)","text":"

                    A h\u00e1zi feladatban a kapcsol\u00f3d\u00f3 laboron (6. labor \u2013 Tervez\u00e9si mint\u00e1k (kiterjeszthet\u0151s\u00e9g)) elkezdett adatfeldolgoz\u00f3/anonimiz\u00e1l\u00f3 alkalmaz\u00e1st fogjuk tov\u00e1bbfejleszteni.

                    Az \u00f6n\u00e1ll\u00f3 feladat az tervez\u00e9si mint\u00e1k el\u0151ad\u00e1sokon elhangzottakra \u00e9p\u00edt: - \"El\u0151ad\u00e1s 08 - Tervez\u00e9si mint\u00e1k 1\" el\u0151ad\u00e1s: \"B\u0151v\u00edthet\u0151s\u00e9ghez, kiterjeszthet\u0151s\u00e9ghez kapcsol\u00f3d\u00f3 alap tervez\u00e9si mint\u00e1k\" nagyfejezet: bevezet\u0151 p\u00e9lda, Template Method, Strategy, Open/Closed elv, SRP elv, egy\u00e9b technik\u00e1k (met\u00f3dusreferencia/lambda) - \"El\u0151ad\u00e1s 09 - Tervez\u00e9si mint\u00e1k 1\" el\u0151ad\u00e1s: Dependency Injection minta

                    A feladatok gyakorlati h\u00e1tter\u00e9\u00fcl a 6. labor \u2013 Tervez\u00e9si mint\u00e1k (kiterjeszthet\u0151s\u00e9g) laborgyakorlat szolg\u00e1l.

                    Az \u00f6n\u00e1ll\u00f3 gyakorlat c\u00e9lja:

                    • Kapcsol\u00f3d\u00f3 tervez\u00e9si mint\u00e1k \u00e9s egy\u00e9b kiterjeszthet\u0151s\u00e9gi technik\u00e1k alkalmaz\u00e1sa
                    • Integr\u00e1ci\u00f3s \u00e9s egys\u00e9gtesztek koncepci\u00f3inak gyakorl\u00e1sa

                    A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s. Enn\u00e9l a h\u00e1zi feladatn\u00e1l nincs sz\u00fcks\u00e9g WinUI-ra (egy konzol alap\u00fa alkalmaz\u00e1s kontextus\u00e1ban kell dolgozni), \u00edgy pl. Linux/MacOS k\u00f6rnyezetben is elv\u00e9gezhet\u0151.

                    "},{"location":"hazi/6-tervezesi-mintak/#a-beadas-menete","title":"A bead\u00e1s menete","text":"
                    • Az alapfolyamat megegyezik a kor\u00e1bbiakkal. GitHub Classroom seg\u00edts\u00e9g\u00e9vel hozz l\u00e9tre magadnak egy repository-t. A megh\u00edv\u00f3 URL-t Moodle-ben tal\u00e1lod (a t\u00e1rgy nyit\u00f3oldal\u00e1n a \"GitHub classroom hivatkoz\u00e1sok a h\u00e1zi feladatokhoz\" hivatkoz\u00e1sra kattintva megjelen\u0151 oldalon l\u00e1that\u00f3). Fontos, hogy a megfelel\u0151, ezen h\u00e1zi feladathoz tartoz\u00f3 megh\u00edv\u00f3 URL-t haszn\u00e1ld (minden h\u00e1zi feladathoz m\u00e1s URL tartozik). Kl\u00f3nozd le az \u00edgy elk\u00e9sz\u00fclt repository-t. Ez tartalmazni fogja a megold\u00e1s elv\u00e1rt szerkezet\u00e9t. A feladatok elk\u00e9sz\u00edt\u00e9se ut\u00e1n commit-old \u00e9s push-old a megold\u00e1sod.
                    • A kikl\u00f3nozott f\u00e1jlok k\u00f6z\u00f6tt a Patterns-Extensibility.sln-t megnyitva kell dolgozni.
                    • A feladatok k\u00e9rik, hogy k\u00e9sz\u00edts k\u00e9perny\u0151k\u00e9pet a megold\u00e1s egy-egy r\u00e9sz\u00e9r\u0151l, mert ezzel bizony\u00edtod, hogy a megold\u00e1sod saj\u00e1t magad k\u00e9sz\u00edtetted. A k\u00e9perny\u0151k\u00e9pek elv\u00e1rt tartalm\u00e1t a feladat minden esetben pontosan megnevezi. A k\u00e9perny\u0151k\u00e9peket a megold\u00e1s r\u00e9szek\u00e9nt kell beadni, a repository-d gy\u00f6k\u00e9rmapp\u00e1j\u00e1ba tedd (a neptun.txt mell\u00e9). A k\u00e9perny\u0151k\u00e9pek \u00edgy felker\u00fclnek GitHub-ra a git repository tartalm\u00e1val egy\u00fctt. Mivel a repository priv\u00e1t, azt az oktat\u00f3kon k\u00edv\u00fcl m\u00e1s nem l\u00e1tja. Amennyiben olyan tartalom ker\u00fcl a k\u00e9perny\u0151k\u00e9pre, amit nem szeretn\u00e9l felt\u00f6lteni, kitakarhatod a k\u00e9pr\u0151l.
                    • Ehhez a feladathoz \u00e9rdemi el\u0151ellen\u0151rz\u0151 nem tartozik: minden push ut\u00e1n lefut ugyan, de csak a neptun.txt kit\u00f6lt\u00f6tts\u00e9g\u00e9t ellen\u0151rzi. Az \u00e9rdemi ellen\u0151rz\u00e9st a hat\u00e1rid\u0151 lej\u00e1rta ut\u00e1n a laborvezet\u0151k teszik majd meg.
                    "},{"location":"hazi/6-tervezesi-mintak/#1-feladat","title":"1. Feladat","text":"

                    A h\u00e1zi feladat megold\u00e1s\u00e1nak alapja a k\u00f6vetkez\u0151:

                    • A Strategy \u00e9s a kapcsol\u00f3d\u00f3 Dependency Injection (DI) tervez\u00e9si minta ismerete
                    • Ezen mint\u00e1k alkalmaz\u00e1s\u00e1nak pontos meg\u00e9rt\u00e9se a labor feladat\u00e1nak a kontextus\u00e1ban (anonimiz\u00e1l\u00f3)

                    A h\u00e1zi feladat kiindul\u00f3 \u00e1llapota megfelel a 6. labor v\u00e9g\u00e1llapot\u00e1nak: ez a h\u00e1zi feladat solutionj\u00e9ben a \"Strategy-DI\" projekt. Futtat\u00e1shoz/debuggol\u00e1shoz be kell \u00e1ll\u00edtani, hogy ez legyen a startup projekt (jobb katt, \"Set as Startup Project\"). Ennek forr\u00e1sk\u00f3dj\u00e1t alaposan n\u00e9zd \u00e1t \u00e9s \u00e9rtsd meg.

                    • A Program.cs f\u00e1jlban tal\u00e1lhat\u00f3 h\u00e1rom Anonymizer, elt\u00e9r\u0151 strategy implement\u00e1ci\u00f3kkal param\u00e9terezve. R\u00e1hangol\u00f3d\u00e1sk\u00e9ppen \u00e9rdemes ezeket egyes\u00e9vel kipr\u00f3b\u00e1lni/futtatni, \u00e9s megn\u00e9zni, hogy val\u00f3ban a v\u00e1lasztott strategy implement\u00e1ci\u00f3knak megfelel\u0151en t\u00f6rt\u00e9nik az anonimiz\u00e1l\u00e1s \u00e9s a progress kezel\u00e9s (eml\u00e9keztet\u0151 laborr\u00f3l: az anonimiz\u00e1l\u00f3 bemenete \"bin\\Debug\\net8.0\" mapp\u00e1ban lev\u0151 us-500.csv, kimenete az ugyanitt tal\u00e1lhat\u00f3 \"us-500.processed.txt\").
                    • Szint\u00e9n \u00e9rdemes a Program.cs f\u00e1jlban kiindulva, t\u00f6r\u00e9spontokat elhelyezve v\u00e9gig l\u00e9pkedni a k\u00f3don (ez is seg\u00edtheti az ism\u00e9tl\u00e9st/teljes meg\u00e9rt\u00e9st).

                    Dependency Injection (manu\u00e1lis) vs. Dependency Injection Container

                    A labor sor\u00e1n, \u00e9s jelen h\u00e1zi feladatban a Dependency Injection egyszer\u0171, manu\u00e1lis v\u00e1ltozat\u00e1t haszn\u00e1ljuk (el\u0151ad\u00e1son is ez szerepel). Ez esetben az oszt\u00e1ly f\u00fcgg\u0151s\u00e9geit manu\u00e1lisan p\u00e9ld\u00e1nyos\u00edtjuk \u00e9s adjuk \u00e1t az oszt\u00e1ly konstruktor\u00e1ban. Alternat\u00edv \u00e9s komplexebb alkalmaz\u00e1sok eset\u00e9ben gyakran haszn\u00e1lt alternat\u00edva egy Dependency Injection Container alkalmaz\u00e1sa, melybe beregisztr\u00e1lhatjuk, hogy az egyes interf\u00e9sz t\u00edpusokhoz milyen implement\u00e1ci\u00f3t k\u00edv\u00e1nunk haszn\u00e1lni. Az MVVM labor sor\u00e1n \"mell\u00e9kesen\" haszn\u00e1ltuk ezt a technik\u00e1t, de a DI kont\u00e9nerek alkalmaz\u00e1sa nem tananyag. A manu\u00e1lis v\u00e1ltozata viszont az, \u00e9s kiemelt fontoss\u00e1g\u00fa, hiszen en\u00e9lk\u00fcl nincs \u00e9rtelme a Strategy minta alkalmaz\u00e1s\u00e1nak.

                    Saj\u00e1t szavaiddal megfogalmazva adj r\u00f6vid v\u00e1laszt a Feladatok mapp\u00e1ban tal\u00e1lthat\u00f3 readme.md f\u00e1jlban az al\u00e1bbi k\u00e9rd\u00e9sekre:

                    • Mit biztos\u00edt a Strategy a DI mint\u00e1val kombin\u00e1lva a labor p\u00e9lda keret\u00e9ben, mik az egy\u00fcttes alkalmaz\u00e1suk el\u0151nyei?
                    • Mit jelent az, hogy a Strategy minta alkalmaz\u00e1s\u00e1val az Open/Closed elv megval\u00f3sul a megold\u00e1sban? (az Open/Closed elvr\u0151l az el\u0151ad\u00e1s \u00e9s laboranyagban is olvashatsz).
                    "},{"location":"hazi/6-tervezesi-mintak/#2-feladat-null-strategy","title":"2. Feladat - Null Strategy","text":"

                    Az Anonymizer konstruktor param\u00e9tereit megvizsg\u00e1lva azt l\u00e1tjuk, hogy progress strat\u00e9gi\u00e1nak null is megadhat\u00f3. Ez logikus, hiszen lehet, hogy az Anonymizer felhaszn\u00e1l\u00f3ja nem k\u00edv\u00e1ncsi semmif\u00e9le progress inform\u00e1ci\u00f3ra. Ennek a megk\u00f6zel\u00edt\u00e9snek van egy h\u00e1tr\u00e1nya is. Ez esetben az oszt\u00e1lyban a _progress tagv\u00e1ltoz\u00f3 null lesz, \u00e9s \u00edgy az alkalmaz\u00e1sa sor\u00e1n sz\u00fcks\u00e9g van a null vizsg\u00e1latra. Ellen\u0151rizz\u00fck, hogy a _progess haszn\u00e1latakor val\u00f3ban van null vizsg\u00e1lat a ?. oper\u00e1tor alkalmaz\u00e1s\u00e1val. De ez egy vesz\u00e9lyes j\u00e1t\u00e9k, mert komplexebb esetben hacsak egyetlen helyen is lefelejt\u0151dik a null vizsg\u00e1lat, akkor fut\u00e1s k\u00f6zben NullReferenceException-t kapunk. Az ehhez hasonl\u00f3 null hivatkoz\u00e1s hib\u00e1k a leggyakoribbak k\u00f6z\u00e9 tartoznak.

                    Feladat: Dolgozz ki egy olyan megold\u00e1st, mely a fent v\u00e1zolt hibalehet\u0151s\u00e9get kiz\u00e1rja. Tipp: olyan megold\u00e1sra van sz\u00fcks\u00e9g, melyn\u00e9l a _progress tag soha nem lehet null. A megold\u00e1sra el\u0151sz\u00f6r magadt\u00f3l pr\u00f3b\u00e1lj r\u00e1j\u00f6nni.

                    Megold\u00e1s alapelve

                    A megold\u00e1s \"tr\u00fckkje\" a k\u00f6vetkez\u0151. Egy olyan IProgress strategy implement\u00e1ci\u00f3t kell k\u00e9sz\u00edteni (pl. NullProgress n\u00e9ven), melyet akkor haszn\u00e1lunk, amikor nincs sz\u00fcks\u00e9g progress inform\u00e1ci\u00f3ra. Ez az implement\u00e1ci\u00f3 a progress \"sor\u00e1n\" nem csin\u00e1l semmit, a f\u00fcggv\u00e9ny t\u00f6rzse \u00fcres. Amikor az Anonymizer konstruktor\u00e1ban null-t ad meg az oszt\u00e1ly p\u00e9ld\u00e1nyos\u00edt\u00f3ja progressk\u00e9nt, akkor egy NullProgress objektumot hozzunk l\u00e9tre a konstruktorban, \u00e9s a _progress tagot \u00e1ll\u00edtsuk erre. Most m\u00e1r a _progress soha nem lehet null, a null vizsg\u00e1latot vegy\u00fck is ki a k\u00f3db\u00f3l.

                    Ennek a technik\u00e1nak is van neve, Null Object n\u00e9ven szok\u00e1s r\u00e1 hivatkozni.

                    "},{"location":"hazi/6-tervezesi-mintak/#3-feladat-tesztelhetoseg","title":"3. Feladat - Tesztelhet\u0151s\u00e9g","text":"

                    Vegy\u00fck \u00e9szre, hogy az Anonymizer oszt\u00e1ly m\u0171k\u00f6d\u00e9s\u00e9nek van m\u00e9g sz\u00e1mos aspektusa, melyeket valamelyik megold\u00e1sunkkal kiterjeszthet\u0151v\u00e9 lehetne tenni. T\u00f6bbek k\u00f6z\u00f6tt ilyen a:

                    • Bemenet kezel\u00e9se: Most csak f\u00e1jl alap\u00fa, adott CSV form\u00e1tumot t\u00e1mogatunk.
                    • Kimenet kezel\u00e9se: Most csak f\u00e1jl alap\u00fa, adott CSV form\u00e1tumot t\u00e1mogatunk.

                    Ezeket az SRP elve miatt illene az oszt\u00e1lyr\u00f3l lev\u00e1lasztani, m\u00e1s oszt\u00e1lyba tenni (ism\u00e9teld \u00e1t, mit jelent az SRP elv). A lev\u00e1laszt\u00e1st nem felt\u00e9telen kiterjeszthet\u0151 m\u00f3don kellene megtenni, hiszen nem mer\u00fclt fel ig\u00e9ny arra, hogy k\u00fcl\u00f6nb\u00f6z\u0151 bemenetekkel \u00e9s kimenetekkel kellene tudni dolgozni. \u00cdgy a lev\u00e1laszt\u00e1s sor\u00e1n nem alkalmazn\u00e1nk a Strategy mint\u00e1t.

                    Ugyanakkor van m\u00e9g egy kritikus szempont, melyr\u0151l nem besz\u00e9lt\u00fcnk (\u00e9s a r\u00e9gebbi, klasszikus design pattern irodalmak sem felt\u00e9tlen emlegetik). Ez az egys\u00e9gtesztelhet\u0151s\u00e9g.

                    Jelen pillanatban az Anonymizer oszt\u00e1lyunkhoz automata integr\u00e1ci\u00f3s teszteket tudunk \u00edrni, automata egys\u00e9gteszteket nem:

                    • Az integr\u00e1ci\u00f3s tesztek a teljes m\u0171k\u00f6d\u00e9st egyben vizsg\u00e1lj\u00e1k: ebben benne van a bemenet feldolgoz\u00e1sa, adatfeldolgoz\u00e1s, kimenet el\u0151\u00e1ll\u00edt\u00e1sa. Ez p\u00e9ld\u00e1nkban egyszer\u0171: el\u00e1ll\u00edtunk bizonyos bemeneti CVS \u00e1llom\u00e1nyokat, \u00e9s megn\u00e9zz\u00fck, a v\u00e1rt kimeneti \u00e1llom\u00e1ny \u00e1ll\u00edt\u00f3dik-e el\u0151.
                    • Az integr\u00e1ci\u00f3s tesztek nagyon lass\u00faak tudnak lenni: sokszor f\u00e1jlokb\u00f3l, adatb\u00e1zisokb\u00f3l, felh\u0151 alap\u00fa szolg\u00e1ltat\u00e1sokb\u00f3l veszik a bemenetet, illetve ezek szolg\u00e1lnak kimenetk\u00e9nt. Egy nagyobb term\u00e9k eset\u00e9ben - mikor sok ezer teszt van - ez a lass\u00fas\u00e1g korl\u00e1toz\u00f3 t\u00e9nyez\u0151, ritk\u00e1bban tudjuk futtatni \u00e9s/vagy nem tudunk j\u00f3 tesztlefedetts\u00e9get el\u00e9rni.

                    A fentiek miatt sokszor nagyobb k\u00f3dlefedetts\u00e9get nem a lassabb integr\u00e1ci\u00f3s, hanem nagyon gyorsan fut\u00f3 egys\u00e9gtesztekkel szoktunk/tudunk el\u00e9rni. Ezek mindenf\u00e9le lass\u00fa f\u00e1jl/adatb\u00e1zis/h\u00e1l\u00f3zat/felh\u0151 el\u00e9r\u00e9s n\u00e9lk\u00fcl \u00f6nmag\u00e1ban egy-egy logikai egys\u00e9get tesztelnek a k\u00f3dban, ezt viszont \u00edgy m\u00e1r vill\u00e1mgyorsan. \u00cdgy sokat tudunk futtatni adott id\u0151 alatt, j\u00f3 tesztlefedetts\u00e9ggel.

                    Tesztpiramis

                    Ezt egy tesztpiramissal szok\u00e1s szeml\u00e9ltetni, melynek t\u00f6bb form\u00e1ja terjedt el az irodalomban. Egy egyszer\u0171 vari\u00e1ns a k\u00f6vetkez\u0151:

                    Min\u00e9l fentebb vagyunk a piramis r\u00e9tegeiben, ann\u00e1l \u00e1tfog\u00f3bbak ugyan a tesztek, de ann\u00e1l lassabbak \u00e9s k\u00f6lts\u00e9gesebben is futtathat\u00f3k. \u00cdgy ezekb\u0151l \u00e1ltal\u00e1ban kevesebbet is k\u00e9sz\u00edt\u00fcnk (ez\u00e1ltal kisebb k\u00f3dlefedetts\u00e9get is \u00e9r\u00fcnk el vel\u00fck). A piramis cs\u00facs\u00e1n az automata E2E (End-to-end) vagy GUI tesztek vannak. Alatta vannak t\u00f6bb egys\u00e9get/modult egyben tesztel\u0151 integr\u00e1ci\u00f3s tesztek. A piramis talapzat\u00e1ban az egys\u00e9gtesztek vannak, ezekb\u0151l k\u00e9sz\u00edt\u00fcnk a legt\u00f6bbet (a piramis talapzata a legsz\u00e9lesebb).

                    Fun fact: Amikor egy term\u00e9k fejleszt\u00e9se sor\u00e1n hossz\u00fa ideig elhanyagolj\u00e1k az egys\u00e9gtesztek k\u00e9sz\u00edt\u00e9s\u00e9t, akkor - mivel a k\u00f3d szerkezete nem t\u00e1mogatja - m\u00e1r nagyon neh\u00e9z egys\u00e9gteszteket ut\u00f3lag k\u00e9sz\u00edteni. \u00cdgy ezekb\u0151l csak nagyon kev\u00e9s lesz, n\u00e9mi integr\u00e1ci\u00f3s tesztekkel kieg\u00e9sz\u00edtve, \u00e9s jobb h\u00edj\u00e1n tesztel\u0151csapatok \u00e1ltal elk\u00e9sz\u00edtett sok-sok end-to-end/GUI teszttel (de ezzel sokszor nem lehet j\u00f3 tesztlefedetts\u00e9get el\u00e9rni egy komplex term\u00e9kben). Egy piramissal szemben ennek fagyit\u00f6lcs\u00e9r form\u00e1ja van, csak p\u00e1r gomb\u00f3cot kell a tetej\u00e9re k\u00e9pzelni. Szok\u00e1s ezt fagyi \"mint\u00e1nak\" is nevezni (\u00e9s ez nem az a fagyi, amit szeret\u00fcnk). Azt az\u00e9rt \u00e9rdemes megjegyezni, hogy mindent a hely\u00e9n kell kezelni: vannak kiv\u00e9telek (olyan alkalmaz\u00e1sok, ahol az egyes r\u00e9szekben alig van logika, az eg\u00e9sz alkalmaz\u00e1sban az egyes nagyon egyszer\u0171 r\u00e9szek integr\u00e1ci\u00f3ja a hangs\u00falyos: ilyen esetben term\u00e9szetszer\u0171en az integr\u00e1ci\u00f3s tesztek t\u00fals\u00falyosak).

                    Az oszt\u00e1lyok k\u00f3dja alapesetben sokszor nem egys\u00e9gtesztelhet\u0151. Jelen form\u00e1j\u00e1ban ilyen az Anonymizer is. Ebbe be van \u00e9getve, hogy csak a lass\u00fa, f\u00e1jl alap\u00fa bemenettel tud dolgozni. De amikor mi pl. a Run m\u0171velet logik\u00e1j\u00e1t szeretn\u00e9nk egys\u00e9gtesztelni, teljesen mindegy, hogy f\u00e1jlb\u00f3l j\u00f6nnek-e az adatok (lassan), vagy egyszer\u0171en k\u00f3db\u00f3l a new oper\u00e1torral el\u0151\u00e1ll\u00edtunk n\u00e9h\u00e1ny Person objektumot a tesztel\u00e9shez (t\u00f6bb nagys\u00e1grenddel gyorsabban).

                    A megold\u00e1s - a k\u00f3dunk egys\u00e9gtesztelhet\u0151v\u00e9 t\u00e9tel\u00e9hez - egyszer\u0171:

                    • A Strategy (+DI) minta (vagy delegate-ek) alkalmaz\u00e1ssal v\u00e1lasszuk le az egys\u00e9gtesztelni k\u00edv\u00e1nt oszt\u00e1lyr\u00f3l a tesztel\u00e9st akad\u00e1lyoz\u00f3 vagy lass\u00edt\u00f3 (pl. bemenet/kimenet kezel\u00e9s) logik\u00e1kat. Ezeknek k\u00e9sz\u00edt\u00fcnk a val\u00f3di logik\u00e1t megval\u00f3s\u00edt\u00f3 implement\u00e1ci\u00f3it, illetve tesztel\u00e9st seg\u00edt\u0151, \u00fan. mock implement\u00e1ci\u00f3it.
                    • Ennek megfelel\u0151en a Strategy mint\u00e1t sokszor nem az\u00e9rt haszn\u00e1ljuk, mert az \u00fcgyf\u00e9lig\u00e9nyek miatt t\u00f6bbf\u00e9le viselked\u00e9st kell benevezni, hanem az\u00e9rt, hogy a k\u00f3dunk egys\u00e9gtesztelhet\u0151 legyen.

                    Ennek megfelel\u0151en elk\u00e9sz\u00edtj\u00fck a megold\u00e1sunk egys\u00e9gtesztel\u00e9sre is el\u0151k\u00e9sz\u00edtett v\u00e1ltozat\u00e1t, melyben a bemenet \u00e9s kimenet kezel\u00e9se is le van v\u00e1lasztva a Strategy minta alkalmaz\u00e1s\u00e1val.

                    Feladat: Alak\u00edtsd \u00e1t a Strategy-DI projektben tal\u00e1lhat\u00f3 megold\u00e1st olyan m\u00f3don, hogy az oszt\u00e1ly egys\u00e9g tesztelhet\u0151 legyen, m\u00e9gpedig a Strategy minta seg\u00edts\u00e9g\u00e9vel. R\u00e9szletesebben:

                    • Vezess be egy InputReaders mapp\u00e1t, melyben vezess be egy bemenet feldolgoz\u00f3 strategy interf\u00e9szt IInputReader n\u00e9ven (egyetlen, List<Person> Read() m\u0171velettel), \u00e9s az Anonymizer oszt\u00e1lyb\u00f3l a Strategy mint\u00e1t k\u00f6vetve szervezd ki a bemenet feldolgoz\u00e1st egy CsvInputReader nev\u0171 strategy implement\u00e1ci\u00f3ba. Ez az oszt\u00e1ly konstruktor param\u00e9terben kapja meg a f\u00e1jl \u00fatvonal\u00e1t, melyb\u0151l a bemenet\u00e9t olvassa.
                    • Vezess be egy ResultWriters mapp\u00e1t, melyben vezess be egy eredm\u00e9ny ki\u00edr\u00f3 strategy interf\u00e9szt IResultWriter n\u00e9ven (egyetlen, void Write(List<Person> persons) m\u0171velettel), \u00e9s az Anonymizer oszt\u00e1lyb\u00f3l a Strategy mint\u00e1t k\u00f6vetve szervezd ki a kimenet \u00edr\u00e1s\u00e1t egy CsvResultWriter nev\u0171 strategy implement\u00e1ci\u00f3ba. Ez az oszt\u00e1ly konstruktor param\u00e9terben kapja meg a f\u00e1jl \u00fatvonal\u00e1t, melybe a kimenetet bele kell \u00edrja.
                    • B\u0151v\u00edtsd ki a Anonymizer oszt\u00e1lyt, bele\u00e9rtve annak konstruktor\u00e1t (Strategy + DI minta), hogy b\u00e1rmilyen IInputReader \u00e9s IResultWriter implement\u00e1ci\u00f3val haszn\u00e1lhat\u00f3 legyen.
                    • A Program.cs f\u00e1jlban alak\u00edtsd \u00e1t az Anonymizer oszt\u00e1ly haszn\u00e1lat\u00e1t, hogy az \u00fajonnan bevezetett CsvInputReader \u00e9s CsvResultWriter oszt\u00e1lyok is \u00e1t legyenek param\u00e9terk\u00e9nt \u00e1tadva.

                    A k\u00f6vetkez\u0151 l\u00e9p\u00e9s egys\u00e9gtesztek k\u00e9sz\u00edt\u00e9se (lenne) az Anonymizer oszt\u00e1lyhoz. Ehhez olyan, \u00fan. mock strategy implement\u00e1ci\u00f3kat kell bevezetni, melyek nemcsak tesztadatokat szolg\u00e1ltatnak (term\u00e9szetesen gyorsan, f\u00e1jlkezel\u00e9s n\u00e9lk\u00fcl), hanem ellen\u0151rz\u00e9seket is v\u00e9geznek (adott logikai egys\u00e9g val\u00f3ban j\u00f3l m\u0171k\u00f6dik-e). Ez most bonyolultnak hangzik, de szerencs\u00e9re a legt\u00f6bb modern keretrendszerben van r\u00e1 k\u00f6nyvt\u00e1r t\u00e1mogat\u00e1s (.NET-ben a moq). Ennek alkalmaz\u00e1sa t\u00falmutat a t\u00e1rgy keretein, \u00edgy a feladatunk egys\u00e9gtesztelhet\u0151s\u00e9ghez kapcsol\u00f3d\u00f3 vonulat\u00e1t ebben a pontban lez\u00e1rjuk.

                    3. feladat BEADAND\u00d3

                    • Illessz be egy k\u00e9perny\u0151k\u00e9pet, melyen az Anonymizer oszt\u00e1ly konstruktora \u00e9s a Run f\u00fcggv\u00e9ny implement\u00e1ci\u00f3ja l\u00e1tszik (f3.1.png).
                    "},{"location":"hazi/6-tervezesi-mintak/#4-feladat-delegate-ek-alkalmazasa","title":"4. Feladat - Delegate-ek alkalmaz\u00e1sa","text":"

                    Napjainkban rohamosan terjed a kor\u00e1bban szigor\u00faan objektumorient\u00e1lt nyelvekben is a funkcion\u00e1lis programoz\u00e1st t\u00e1mogat\u00f3 eszk\u00f6z\u00f6k megjelen\u00e9se, \u00e9s az alkalmaz\u00e1sfejleszt\u0151k is egyre nagyobb szeretettel alkalmazz\u00e1k ezeket (merthogy sokszor jelent\u0151sen r\u00f6videbb k\u00f3ddal, kisebb \"cerem\u00f3ni\u00e1val\" lehet ugyanazt seg\u00edts\u00e9g\u00fckkel megval\u00f3s\u00edtani). Egy ilyen eszk\u00f6z C# nyelven a delegate, \u00e9s ehhez kapcsol\u00f3d\u00f3an a lambda kifejez\u00e9s.

                    Mint a f\u00e9l\u00e9v sor\u00e1n kor\u00e1bban l\u00e1ttuk, delegate-ek seg\u00edts\u00e9g\u00e9vel olyan k\u00f3dot tudunk \u00edrni, melybe bizonyos logik\u00e1k/viselked\u00e9sek nincsenek be\u00e9getve, ezeket \"k\u00edv\u00fclr\u0151l\" kap meg a k\u00f3d. Pl. egy sorrendez\u0151 f\u00fcggv\u00e9nynek delegate form\u00e1j\u00e1ban adjuk \u00e1t param\u00e9terk\u00e9nt, hogyan kell k\u00e9t elemet \u00f6sszehasonl\u00edtani, vagy mely mez\u0151je/tulajdons\u00e1ga szerint kell az \u00f6sszehasonl\u00edt\u00e1st elv\u00e9gezni (\u00edgy v\u00e9gs\u0151 soron meghat\u00e1rozni a k\u00edv\u00e1nt sorrendet).

                    Ennek megfelel\u0151en a delegate-ek alkalmaz\u00e1sa egy \u00fajabb alternat\u00edva (a Template Method \u00e9s a Strategy mellett) a k\u00f3d \u00fajrafelhaszn\u00e1lhat\u00f3v\u00e1/kiterjeszthet\u0151v\u00e9 t\u00e9tel\u00e9re, kiterjeszt\u00e9si pontok bevezet\u00e9s\u00e9re.

                    A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben a kor\u00e1bban Strategy mint\u00e1val megval\u00f3s\u00edtott progress kezel\u00e9st alak\u00edtjuk \u00e1t delegate alap\u00fara (\u00faj funkci\u00f3t nem vezet\u00fcnk be, ez egy puszt\u00e1n \"technikai\" \u00e1talak\u00edt\u00e1s lesz).

                    Feladat: Alak\u00edtsd \u00e1t a Strategy-DI projektben tal\u00e1lhat\u00f3 megold\u00e1st olyan m\u00f3don, hogy a progress kezel\u00e9s Strategy helyett delegate alapon legyen megval\u00f3s\u00edtva. R\u00e9szletesebben:

                    • Ne vezess be saj\u00e1t delegate t\u00edpust (haszn\u00e1ld a .NET \u00e1ltal biztos\u00edtott Action t\u00edpust).
                    • A megl\u00e9v\u0151 SimpleProgress \u00e9s PercentProgress oszt\u00e1lyokat ne haszn\u00e1ld a megold\u00e1sodban(de ne is t\u00f6r\u00f6ld ezeket!).
                    • Legyen lehet\u0151s\u00e9ge az Anonymizer haszn\u00e1l\u00f3j\u00e1nak tov\u00e1bbiakban is null-t megadni a konstruktorban, ha nem k\u00edv\u00e1n semmif\u00e9le progress kezel\u00e9st haszn\u00e1lni.
                    • A Program.cs f\u00e1jlban kommentezd ki az eddigi Anonymizer haszn\u00e1latokat. Ugyanitt vezess be egy \u00faj p\u00e9ld\u00e1t az Anonymizer olyan haszn\u00e1lat\u00e1ra, melyben a progress kezel\u00e9s lambda kifejez\u00e9s form\u00e1j\u00e1ban van megadva, \u00e9s a lambda kifejez\u00e9s pontosan a kor\u00e1bbi \"simple progress\" logik\u00e1j\u00e1t val\u00f3s\u00edtja meg. A \"percent progress\"-re nem kell hasonl\u00f3t megval\u00f3s\u00edtani, azt ebben a megold\u00e1sban nem kell t\u00e1mogatni (a k\u00f6vetkez\u0151 feladatban t\u00e9r\u00fcnk vissza r\u00e1).

                    Tippek

                    • A delegate alap\u00fa megold\u00e1s alapelve nagyon hasonl\u00edt a Strategy-hez: csak nem strategy-ket kap \u00e9s t\u00e1rol az oszt\u00e1ly tagv\u00e1ltoz\u00f3kban (interf\u00e9sz hivatkoz\u00e1sokon kereszt\u00fcl), hanem delegate-eket, \u00e9s az ezek \u00e1ltal hivatkozott f\u00fcggv\u00e9nyeket h\u00edvja a kiterjeszt\u00e9si pontokban.
                    • Ehhez hasonl\u00f3t m\u00e1r csin\u00e1lt\u00e1l is a 2. h\u00e1zi feladatban a ReportPrinter r\u00e9szben ;).

                    4. feladat BEADAND\u00d3

                    • Illessz be egy k\u00e9perny\u0151k\u00e9pet, melyen az Anonymizer oszt\u00e1ly konstruktora \u00e9s a Run f\u00fcggv\u00e9ny implement\u00e1ci\u00f3ja l\u00e1tszik (f4.1.png).
                    • Illessz be egy k\u00e9perny\u0151k\u00e9pet, melyen a Program.cs f\u00e1jl tartalma (k\u00fcl\u00f6n\u00f6sen az \u00faj r\u00e9szek) l\u00e1tszik (f4.2.png).
                    "},{"location":"hazi/6-tervezesi-mintak/#5-feladat-delegate-ek-alkalmazasa-ujrafelhasznalhato-logikaval","title":"5. Feladat - Delegate-ek alkalmaz\u00e1sa \u00fajrafelhaszn\u00e1lhat\u00f3 logik\u00e1val","text":"

                    Az el\u0151z\u0151 feladatban feltett\u00fck, hogy a \"simple progress\" \u00e9s a \"percent progress\" logik\u00e1j\u00e1t csak egyszer haszn\u00e1ltuk, \u00edgy nem kellett \u00fajrafelhaszn\u00e9lhat\u00f3v\u00e1 tenni. Ennek megfelel\u0151en pl. a \"simple progress\" logik\u00e1j\u00e1t a lehet\u0151 legegyszer\u0171bb form\u00e1ban, egy lambda kifejez\u00e9ssel adtuk meg (nem kellett k\u00fcl\u00f6n f\u00fcggv\u00e9nyt bevezetni r\u00e1). Amennyiben az Anonymizer l\u00e9trehoz\u00e1sakor a delegate-nek mindig m\u00e1s \u00e9s m\u00e1s implement\u00e1ci\u00f3t adunk meg, akkor ez a lambda alap\u00fa megold\u00e1s t\u00f6k\u00e9letes.

                    Viszont mi a helyzet akkor, ha a fenti p\u00e9ld\u00e1ban szerepl\u0151 \"simple progress\" logik\u00e1t t\u00f6bb helyen, t\u00f6bb Anonymizer objektumn\u00e1l is fel szeretn\u00e9nk haszn\u00e1lni? S\u00falyos hiba lenne a lambda kifejez\u00e9st copy-paste-tel \"szapor\u00edtani\", k\u00f3dduplik\u00e1ci\u00f3hoz vezetne (ellentmondana a \"Do Not Repeat Yourself\", r\u00f6viden DRY elvnek).

                    K\u00e9rd\u00e9s: van-e megold\u00e1s arra, hogy delegate-ek eset\u00e9ben is \u00fajrafelhaszn\u00e1lhat\u00f3 k\u00f3dot adjunk meg? Term\u00e9szetesen igen, hiszen delegate-ek eset\u00e9ben nem k\u00f6telez\u0151 a lambda kifejez\u00e9sek haszn\u00e1lata, lehet vel\u00fck k\u00f6z\u00f6ns\u00e9ges m\u0171veletekre (ak\u00e1r statikus, ak\u00e1r nem statikusakra is), mint azt kor\u00e1bban a f\u00e9l\u00e9v sor\u00e1n l\u00e1ttuk, \u00e9s sz\u00e1mos esetben alkalmaztuk is.

                    Amennyiben a \"simple progress\" \u00e9s/vagy \"percent progress\" logik\u00e1t/logik\u00e1kat \u00fajrafelhaszn\u00e1lhat\u00f3v\u00e1 szeretn\u00e9nk tenni delegate-ek alkalmaz\u00e1sakor, tegy\u00fck ezeket egy k\u00fcl\u00f6n f\u00fcggv\u00e9nyekbe valamilyen, az adott esetben legink\u00e1bb passzol\u00f3 oszt\u00e1lyba/oszt\u00e1lyokba, \u00e9s egy ilyen m\u0171veletet adjuk meg az Anonymizer konstruktornak param\u00e9terk\u00e9nt.

                    Feladat: B\u0151v\u00edtsd ki a kor\u00e1bbi megold\u00e1st \u00fagy, hogy a \"simple progress\" \u00e9s \"percent progress\" logik\u00e1ja \u00fajrafelhaszn\u00e1lhat\u00f3 legyen. R\u00e9szletesebben:

                    • A \"simple progress\" \u00e9s \"percent progress\" logik\u00e1kat egy \u00fajonnan bevezetett AllProgresses nev\u0171 statikus oszt\u00e1ly k\u00e9t statikus m\u0171velet\u00e9ben val\u00f3s\u00edtsd meg (az oszt\u00e1ly a projekt gy\u00f6ker\u00e9be ker\u00fclj\u00f6n).
                    • Vezess be k\u00e9t olyan \u00faj Anonymizer haszn\u00e1latot a Program.cs f\u00e1jlban a megl\u00e9v\u0151k mell\u00e9, melyek az AllProgresses k\u00e9t m\u0171velet\u00e9t haszn\u00e1lj\u00e1k (itt ne haszn\u00e1lj lambda kifejez\u00e9st).
                    • A megl\u00e9v\u0151 IProgress interf\u00e9szt \u00e9s ennek implement\u00e1ci\u00f3i t\u00f6r\u00f6lhet\u0151k lenn\u00e9nek (hiszen ezek m\u00e1r nincsenek haszn\u00e1latban). De NE t\u00f6r\u00f6ld \u0151ket annak \u00e9rdek\u00e9ben, hogy a kor\u00e1bbi megold\u00e1sodhoz tartoz\u00f3 progress logika is ellen\u0151rizhet\u0151 legyen.

                    Elk\u00e9sz\u00fclt\u00fcnk, \u00e9rt\u00e9kelj\u00fck a megold\u00e1st:

                    • Kijelenthet\u0151, hogy a delegate alap\u00fa megold\u00e1s a Strategy-n\u00e9l kisebb cerem\u00f3ni\u00e1val j\u00e1rt: nem kellett interf\u00e9szt \u00e9s implement\u00e1ci\u00f3s oszt\u00e1lyokat bevezetni (a be\u00e9p\u00edtett Action \u00e9s Func generikus delegate t\u00edpusokat tudtuk haszn\u00e1lni).
                    • A teljesen \"eseti\" logik\u00e1t lambda kifejez\u00e9s form\u00e1j\u00e1ban legegyszer\u0171bb megadni. Ha \u00fajrafelhaszn\u00e1lhat\u00f3 logik\u00e1ra van sz\u00fcks\u00e9g, akkor viszont vezess\u00fcnk be \"hagyom\u00e1nyos\", \u00fajrafelhaszn\u00e1lhat\u00f3 f\u00fcggv\u00e9nyeket.

                    5. feladat BEADAND\u00d3

                    • Illessz be egy k\u00e9perny\u0151k\u00e9pet, melyen az AllProgresses.cs f\u00e1jl tartalma l\u00e1tszik (f5.1.png).
                    • Illessz be egy k\u00e9perny\u0151k\u00e9pet, melyen a Program.cs f\u00e1jl tartalma (k\u00fcl\u00f6n\u00f6sen az \u00faj r\u00e9szek) l\u00e1tszik (f5.2.png).
                    "},{"location":"hazi/6-tervezesi-mintak/#refaktoralas-refactoring-fogalma","title":"Refaktor\u00e1l\u00e1s (Refactoring) fogalma","text":"

                    A labor \u00e9s a h\u00e1zi feladat megval\u00f3s\u00edt\u00e1sa sor\u00e1n sz\u00e1mos olyan l\u00e9p\u00e9s volt, mely sor\u00e1n a k\u00f3dot \u00fagy alak\u00edtottuk \u00e1t, hogy az alkalmaz\u00e1s k\u00fcls\u0151 viselked\u00e9se nem v\u00e1ltozott, csak a bels\u0151 fel\u00e9p\u00edt\u00e9se. M\u00e9gpedig annak \u00e9rdek\u00e9ben, hogy valamilyen szempontb\u00f3l jobb k\u00f3dmin\u0151s\u00e9gi jellemz\u0151kkel rendelkezzen. Ezt a k\u00f3d refaktor\u00e1l\u00e1s\u00e1nak (angolul refactoring) nevezz\u00fck. Ez egy nagyon fontos fogalom, a mindennapi munka sor\u00e1n nagyon gyakran haszn\u00e1ljuk. K\u00fcl\u00f6n irodalma van, a fontosabb technik\u00e1kkal a k\u00e9s\u0151bbiekben \u00e9rdemes megismerkedni. A komolyabb fejleszt\u0151eszk\u00f6z\u00f6k be\u00e9p\u00edtetten t\u00e1mogatnak bizonyos refaktor\u00e1l\u00e1si m\u0171veleteket: a Visual Studio ebben nem a leger\u0151sebb, de az\u00e9rt p\u00e1r alapm\u0171veletet t\u00e1mogat (pl. Extract Method, Extract base class stb.). Manu\u00e1lisan gyakoroltuk, ennek kapcs\u00e1n k\u00fcl\u00f6n feladatunk nem lesz, de a Refaktor\u00e1l\u00e1s fogalm\u00e1t ismerni kell.

                    "},{"location":"hazi/6-tervezesi-mintak/#6-opcionalis-feladat-integracios-teszt-keszitese","title":"6. Opcion\u00e1lis feladat - Integr\u00e1ci\u00f3s teszt k\u00e9sz\u00edt\u00e9se","text":"

                    A feladat megold\u00e1s\u00e1val +1 IMSc pont szerezhet\u0151.

                    A kor\u00e1bbi, 3. feladat sor\u00e1n ismertet\u00e9sre ker\u00fclt az integr\u00e1ci\u00f3s teszt fogalma. Jelen opcion\u00e1lis feladat c\u00e9lja ennek gyakorl\u00e1sa, jobb meg\u00e9rt\u00e9se egy egyszer\u0171 feladaton kereszt\u00fcl.

                    K\u00e9sz\u00edts egy integr\u00e1ci\u00f3s tesztet az Anonymizer oszt\u00e1lyhoz, a k\u00f6vetkez\u0151k szerint:

                    1. A Solutionben a Test mapp\u00e1ban el\u0151k\u00e9sz\u00edtett IntegrationTest projektben dolgozz. Ez egy NUnit teszt projekt.
                    2. Ebben a projektben m\u00e1r el\u0151re felvett\u00fcnk egy projekt referenci\u00e1t a Strategy-DI projektre, \u00edgy l\u00e1tjuk a Strategy-DI projektben lev\u0151 (publikus) oszt\u00e1lyokat. \u00c9rtelemszer\u0171en ez el\u0151felt\u00e9tele annak, hogy tudjuk tesztelni \u0151ket. Ellen\u0151rizd a projekt referencia megl\u00e9t\u00e9t (Solution Explorerben a projekt alatt a Dependencies/Projects csom\u00f3pont).
                    3. Az AnonymizerIntegrationTest oszt\u00e1lyban m\u00e1r van egy Anonymize_CleanInput_MaskNames_Test nev\u0171 tesztel\u00e9st v\u00e9gz\u0151 m\u0171velet (a teszt m\u0171veleteket [Test] attrib\u00fatummal kell ell\u00e1tni, ez erre a m\u0171veletre m\u00e1r el\u0151 van k\u00e9sz\u00edtve). A m\u0171velet t\u00f6rzse egyel\u0151re \u00fcres, ebben kell dolgozni a k\u00f6vetkez\u0151 l\u00e9p\u00e9sekben.
                      1. Hozz l\u00e9tre egy Anonymizer objektumot, mely
                        • a @\"TestFiles\\us-500-01-clean.input.csv\" bemenettel dolgozik (ez megtal\u00e1lhat\u00f3 a projekt TestFiles mapp\u00e1j\u00e1ban, n\u00e9zd meg a tartalm\u00e1t),
                        • a kimente legyen a @\"us-500-01-maskedname.processed.txt\" f\u00e1jl,
                        • \"***\" param\u00e9ter\u0171 NameMaskingAnonymizerAlgorithm-t haszn\u00e1l.
                      2. Futtasd az anonimiz\u00e1l\u00f3t a Run m\u0171velet\u00e9nek h\u00edv\u00e1s\u00e1val, hogy \u00e1lljon el\u0151 a kimenti \u00e1llom\u00e1ny.
                      3. Az Assert.AreEqual h\u00edv\u00e1ssal ellen\u0151rizd, hogy az anonimiz\u00e1l\u00e1s sor\u00e1n el\u0151\u00e1llt kimeneti \u00e1llom\u00e1ny tartalma megegyezik-e a v\u00e1rt tartalommal. A v\u00e1rt tartalom a @\"TestFiles\\us-500-01-maskedname.processed-expected.txt\" f\u00e1jlban \u00e9rhet\u0151 el (ez megtal\u00e1lhat\u00f3 a projekt TestFiles mapp\u00e1j\u00e1ban, n\u00e9zd meg a tartalm\u00e1t). Tipp: egy f\u00e1jl tartalm\u00e1t pl. a File.ReadAllBytes statikus m\u0171velettel egy l\u00e9p\u00e9sben be lehet olvasni.
                    4. Ellen\u0151rizd, hogy az integr\u00e1ci\u00f3s teszt hiba n\u00e9lk\u00fcl lefut.
                      1. Buildeld meg a projektet
                      2. Nyisd meg a Test Explorert (Test/Test Explorer men\u00fc)
                      3. A teszt futtat\u00e1s\u00e1ra a Test Explorer n\u00e9zet tetej\u00e9n tal\u00e1lhat\u00f3 eszk\u00f6zs\u00e1von lev\u0151 gombokkal van lehet\u0151s\u00e9g. De a teszt debuggol\u00e1s\u00e1ra is van lehet\u0151s\u00e9g, jobb gombbal a tesztre kattintva \u00e9s a Debug men\u00fc kiv\u00e1laszt\u00e1s\u00e1val: ez nagyon hasznos tud lenni, ha a teszt\u00fcnk hib\u00e1san fut, \u00e9s szeretn\u00e9nk t\u00f6r\u00e9spontok seg\u00edts\u00e9g\u00e9vel a k\u00f3don l\u00e9pkedni, illetve a v\u00e1ltoz\u00f3k \u00e9rt\u00e9k\u00e9t megn\u00e9zni.
                      4. Ha a teszt hiba n\u00e9lk\u00fcl fut le, a teszthez tartoz\u00f3 ikon z\u00f6ld lesz. Ha hib\u00e1val, akkor piros, \u00e9s a hiba\u00fczenetr\u0151l a tesztet kiv\u00e1lasztva Test Explorer n\u00e9zet alj\u00e1n kapunk b\u0151vebb inform\u00e1ci\u00f3t.
                    "},{"location":"hazi/6-tervezesi-mintak/#7-opcionalis-feladat-unit-teszt-keszitese","title":"7. Opcion\u00e1lis feladat - Unit teszt k\u00e9sz\u00edt\u00e9se","text":"

                    A feladat megold\u00e1s\u00e1val +2 IMSc pont szerezhet\u0151.

                    A kor\u00e1bbi, 3. feladat sor\u00e1n ismertet\u00e9sre ker\u00fclt az egys\u00e9gteszt fogalma. Jelen opcion\u00e1lis feladat c\u00e9lja ennek gyakorl\u00e1sa, jobb meg\u00e9rt\u00e9se egy feladaton kereszt\u00fcl.

                    El\u0151k\u00e9sz\u00edt\u00e9s:

                    1. Vegy\u00e9l fel a solution-be egy \u00faj \"NUnit Test Project\" t\u00edpus\u00fa projektet \"UnitTest\" n\u00e9ven (jobb katt a Solution-\u00f6n a Solution Explorerben/Add/New Project).
                    2. Ebben az \u00faj projektben vegy\u00e9l fel projekt referenci\u00e1t a Strategy-DI projektre, hogy a projektben el\u00e9rhet\u0151k legyenek a Strategy-DI-ben defini\u00e1lt t\u00edpusok (jobb katt a Unit Test projekt Dependencies csom\u00f3pontj\u00e1n/Add Project Reference, a megjelen\u0151 ablakban pipa a Strategy-DI projekten, \"OK\").
                    3. A projektben sz\u00fcletik egy UnitTest1.cs \u00e1llom\u00e1ny, benne egy Test oszt\u00e1ly. Ezeket c\u00e9lszer\u0171 AnonymizerTest-re nevezni.

                    K\u00e9sz\u00edts egy egys\u00e9gtesztet az Anonymizer oszt\u00e1lyhoz, mely ellen\u0151rzi, hogy a Run m\u0171velete pontosan azokkal a szem\u00e9ly adatokkal h\u00edvja meg sorrendhelyesen az anonimiz\u00e1l\u00f3 algoritmust, melyeket az Anonymizer a bemenet\u00e9n beolvas (amennyiben nincsenek trimmelend\u0151 v\u00e1rosnevek).

                    • A tesztf\u00fcggv\u00e9ny neve legyen RunShouldCallAlgorithmForEachInput.
                    • Alapvet\u0151 fontoss\u00e1g\u00fa, hogy nagyon gyors egys\u00e9gtesztet kell \u00edrni, nem integr\u00e1ci\u00f3s tesztet: teh\u00e1t csak a Run logik\u00e1j\u00e1t akarjuk \u00f6nmag\u00e1ban tesztelni, mindenf\u00e9le f\u00e1jlfeldolgoz\u00e1s n\u00e9lk\u00fcl. A megold\u00e1sban semmif\u00e9le f\u00e1jlkezel\u00e9s nem lehet!
                    • Tipp: Mem\u00f3ri\u00e1ban hozz l\u00e9tre 2-3 Person objektumot, ezekkel dolgozz bemenetk\u00e9nt.
                    • Tipp: Olyan bemen\u0151 szem\u00e9lyadatokkal dolgozz, melyekre a TrimCityNames f\u00fcggv\u00e9nynek nincs hat\u00e1sa (vagyis nincsenek benne \u00e1lt\u00e1vol\u00edtand\u00f3 adatok), ez egyszer\u0171bb\u00e9 teszi a tesztel\u00e9st.
                    • Tipp: Olyan IInputReader, IAnonymizerAlgorithm implement\u00e1ci\u00f3kat hozz l\u00e9tre (\u00e9s az Anonymizert ezekkel haszn\u00e1ld), melyek megfelel\u0151 tesztadatokat biztos\u00edtanak, \u00e9s/vagy fut\u00e1s k\u00f6zben adatokat gy\u0171jtenek annak \u00e9rdek\u00e9ben, hogy a fut\u00e1s ut\u00e1n ellen\u0151rizni tudd ezen adatok alapj\u00e1n, hogy a tesztelend\u0151 felt\u00e9telek teljes\u00fclnek. Ezeket a strategy implement\u00e1ci\u00f3kat mindenk\u00e9ppen a teszt projektben vedd fel, mert csak a tesztel\u00e9st szolg\u00e1lj\u00e1k.

                    Tov\u00e1bbi gyakorl\u00e1sk\u00e9ppen k\u00e9sz\u00edthetsz egy olyan m\u00e1sik egys\u00e9gtesztet, mely azt ellen\u0151rzi, hogy minden bemeneti szem\u00e9lyadat eljut-e a kimenetre is.

                    "},{"location":"hazi/6-tervezesi-mintak/#osszegzes","title":"\u00d6sszegz\u00e9s","text":"

                    T\u00f6bb feladat nem lesz \ud83d\ude0a. De ha k\u00edv\u00e1ncsi vagy pl. arra, hogy jelen megold\u00e1s mennyire tekinthet\u0151 \"t\u00f6k\u00e9letesnek\"/hi\u00e1nyosnak, illetve mikor \u00e9rdemes Template Methoddal, Strategyvel, vagy ink\u00e1bb delegate-ekkel dolgozni, akkor \u00e9rdemes elolvasnod az al\u00e1bbiakat, melyben \u00e9rt\u00e9kelj\u00fck a laboron elkezdett \u00e9s a h\u00e1zi feladat keret\u00e9ben befejezett megold\u00e1st.

                    "},{"location":"hazi/6-tervezesi-mintak/#a-munkafolyamatunk-attekintese","title":"A munkafolyamatunk \u00e1ttekint\u00e9se","text":"
                    • A v\u00e1ltoz\u00f3 ig\u00e9nyek sor\u00e1n organikusan jelentek meg tervez\u00e9si mint\u00e1k, \u00e9s vezett\u00fcnk be egy\u00e9b technik\u00e1kat a refaktor\u00e1l\u00e1sok sor\u00e1n. Ez teljesen term\u00e9szetes, a gyakorlatban is sokszor \u00edgy dolgozunk.
                    • Egy komplexebb feladat eset\u00e9ben egy\u00e9bk\u00e9nt is sokszor - k\u00fcl\u00f6n\u00f6sen ha nem rendelkez\u00fcnk sok\u00e9ves tapasztalattal - egy egyszer\u0171bb implement\u00e1ci\u00f3val indulunk (ezt l\u00e1tjuk \u00e1t els\u0151re), \u00e9s alak\u00edtjuk \u00e1t olyanra, hogy az adott kontextusban k\u00edv\u00e1nt kiterjeszthet\u0151s\u00e9gi/\u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1gi param\u00e9terekkel rendelkezzen.
                    "},{"location":"hazi/6-tervezesi-mintak/#ujrafelhasznalhatosagi-es-kiterjeszthetoseg-szintjei-az-egyes-megoldasokban","title":"\u00dajrafelhaszn\u00e1lhat\u00f3s\u00e1gi \u00e9s kiterjeszthet\u0151s\u00e9g szintjei az egyes megold\u00e1sokban","text":"

                    Megpr\u00f3b\u00e1lhatjuk \u00e1br\u00e1ba \u00f6nteni, hogy v\u00e1lt a megold\u00e1sunk az egyes iter\u00e1ci\u00f3kkal egyre ink\u00e1bb \u00fajrafelhaszn\u00e1lhat\u00f3v\u00e1 \u00e9s kiterjeszthet\u0151v\u00e9:

                    Term\u00e9szetesen a % szinteket nem szabad t\u00fal komolyan venni. Mindenesetre a fejl\u0151d\u00e9s j\u00f3l megfigyelhet\u0151.

                    Mi\u00e9rt \"csak\" 70%-os a v\u00e9gs\u0151 megold\u00e1sn\u00e1l mutat\u00f3nk?

                    Felmer\u00fclhet a k\u00e9rd\u00e9s, mi\u00e9rt adunk jelem megold\u00e1sra kb. 70%-ot? T\u00f6bbek k\u00f6z\u00f6tt:

                    • Az Anonymizer oszt\u00e1lyba az adattiszt\u00edt\u00e1s m\u00f3dja mereven be van \u00e9getve (trimmel\u00e9s adott oszlopra adott m\u00f3don).
                    • Nem k\u00f6vett\u00fcnk egy nagyon fontos \u00e1ltal\u00e1nos alapelvet: a UI \u00e9s a logika k\u00fcl\u00f6nv\u00e1laszt\u00e1s\u00e1t. A k\u00f3dunk t\u00f6bb pontban konzolra \u00edr, \u00edgy p\u00e9ld\u00e1ul egy grafikus fel\u00fclettel nem haszn\u00e1lhat\u00f3!
                    • Bizonyos az anonimiz\u00e1l\u00f3 algoritmusaink nagyon specifikusak. Lehetne olyan \u00e1ltal\u00e1nosabb algoritmusokat k\u00e9sz\u00edteni, melyek tetsz\u0151leges mez\u0151ket kicsillagoznak (nem csak a nevet be\u00e9getetten), illetve tetsz\u0151leges mez\u0151ket s\u00e1vos\u00edtanak (nem csak az \u00e9letkort).
                    • Jelen megold\u00e1s csak Person objektumokkal tud m\u0171k\u00f6dni.
                    • Nem lehet egyszerre alkalmazni kombin\u00e1lni k\u00fcl\u00f6nb\u00f6z\u0151 anonimiz\u00e1l\u00f3 algoritmusokat.
                    "},{"location":"hazi/6-tervezesi-mintak/#kiterjesztesi-technikak-attekintese","title":"Kiterjeszt\u00e9si technik\u00e1k \u00e1ttekint\u00e9se","text":"
                    • Template Method: Egyszer\u0171 esetben, ha a viselked\u00e9sek k\u00fcl\u00f6nb\u00f6z\u0151 aspektusainak nem kell sok keresztkombin\u00e1ci\u00f3j\u00e1t t\u00e1mogatni, nagyon k\u00e9nyelmes \u00e9s egyszer\u0171 megold\u00e1st ad, k\u00fcl\u00f6n\u00f6sen, ha egy\u00e9bk\u00e9nt is kell haszn\u00e1ljuk a sz\u00e1rmaztat\u00e1st. De nem, vagy csak nehezen egys\u00e9gtesztelhet\u0151 alaposzt\u00e1lyt eredm\u00e9nyez.
                    • Strategy: Nagyon rugalmas megold\u00e1st biztos\u00edt, \u00e9s nem vezet kombinatorikus robban\u00e1shoz, ha t\u00f6bb aspektus ment\u00e9n kell az oszt\u00e1lyt kiterjeszteni, \u00e9s t\u00f6bb keresztkombin\u00e1ci\u00f3ban is szeretn\u00e9nk ezeket haszn\u00e1lni. Sok esetben csak az\u00e9rt alkalmazzuk, hogy az oszt\u00e1lyunkr\u00f3l interf\u00e9szek seg\u00edts\u00e9g\u00e9vel lev\u00e1lasszuk a f\u00fcgg\u0151s\u00e9geit, \u00e9s \u00edgy egys\u00e9gtesztelhet\u0151v\u00e9 tegy\u00fck az oszt\u00e1lyunkat.
                    • Delegate/lambda: Ez a megk\u00f6zel\u00edt\u00e9s kisebb cerem\u00f3ni\u00e1val j\u00e1r, mint a Strategy alkalmaz\u00e1sa, ugyanis nincs sz\u00fcks\u00e9g interf\u00e9szek \u00e9s implement\u00e1ci\u00f3s oszt\u00e1lyok bevezet\u00e9s\u00e9re, emiatt egyre ink\u00e1bb (rohamosan) terjed a haszn\u00e1lata a modern objektumorient\u00e1lt nyelvekben is. K\u00fcl\u00f6n\u00f6sen akkor j\u00f6nnek ki az el\u0151nyei, ha a viselked\u00e9seket nem akarjuk \u00fajrafelhaszn\u00e1lhat\u00f3v\u00e1 tenni (mert ekkor csak egy-egy lambda kifejez\u00e9ssel megadjuk ezeket, mindenf\u00e9le \u00faj oszt\u00e1lyok/k\u00fcl\u00f6n f\u00fcggv\u00e9nyek bevezet\u00e9se n\u00e9lk\u00fcl).

                    \u00c9rdemes \u00f6sszeszedni, hogy a Strategy-nek mikor lehet/van van el\u0151nye a delegate-ekkel szemben:

                    • Ha kiterjesztend\u0151 oszt\u00e1ly adott aspektus\u00e1hoz t\u00f6bb (min\u00e9l t\u00f6bb, ann\u00e1l ink\u00e1bb) m\u0171velet tartozik. Ilyenkor a strategy interf\u00e9sz ezeket \"mag\u00e1t\u00f3l\" sz\u00e9pen \u00f6sszefogja, csoportos\u00edtja (mint a p\u00e9ld\u00e1nkban az IAnonymizerAlgorithm interf\u00e9sz az Anonymize \u00e9s GetAnonymizerDescription m\u0171veleteket). Ezek \u00e9rtelemszer\u0171en az interf\u00e9sz implement\u00e1ci\u00f3kban is egy\u00fctt jelennek meg (delegate-ek eset\u00e9ben nincs ilyen csoportos\u00edt\u00e1s). Ez \u00e1tl\u00e1that\u00f3bb\u00e1 teheti, sok m\u0171velet eset\u00e9n egy\u00e9rtelm\u0171en azz\u00e1 is teszi a megold\u00e1st.
                    • Az adott nyelv puszt\u00e1n objektumorient\u00e1lt, nem t\u00e1mogatja a delegate/lambda alkalmaz\u00e1s\u00e1t. De ma m\u00e1r a legt\u00f6bb modern OO nyelv szerencs\u00e9re t\u00e1mogatja valamilyen form\u00e1ban (Java \u00e9s C++ is).
                    • A strategy implement\u00e1ci\u00f3k a tagv\u00e1ltoz\u00f3ikban \u00e1llapotot is tudnak t\u00e1rolni, melyet l\u00e9trehoz\u00e1sukkor meg tudunk adni. Ezt haszn\u00e1ltuk is (a NameMaskingAnonymizerAlgorithm eset\u00e9ben ilyen volt a _mask, a AgeAnonymizerAlgorithm eset\u00e9ben a _rangeSize). Ez nem azt jelenti, hogy ilyen esetben egy\u00e1ltal\u00e1n nem tudunk delegate-eket haszn\u00e1lni, hiszen:

                      • ezeket az adatokat ak\u00e1r \u00fajonnan bevezetett f\u00fcggv\u00e9ny param\u00e9terben is \u00e1tadhatjuk az egyes delegate h\u00edv\u00e1sok sor\u00e1n,
                      • illetve, lambda haszn\u00e1lata eset\u00e9n a \"variable capture\" mechanizmus seg\u00edts\u00e9g\u00e9vel a lambda f\u00fcggv\u00e9nyek tudnak \u00e1llapotot \u00e1tvenni k\u00f6rnyezet\u00fckb\u0151l.

                      De ezek a megold\u00e1sok nem mindig alkalmazhat\u00f3k, vagy legal\u00e1bbis k\u00f6r\u00fclm\u00e9nyes lehet az alkalmaz\u00e1suk.

                    Mindenk\u00e9ppen meg kell eml\u00edteni, hogy nem csak jelen gyakorlatban eml\u00edtett n\u00e9h\u00e1ny minta szolg\u00e1lja a kiterjeszthet\u0151s\u00e9get \u00e9s \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1got, hanem gyakorlatilag az \u00f6sszes. Most kiemelt\u00fcnk p\u00e1rat, melyek (m\u00e9g p. az Observert/Iteratort/Adaptert ide sorolva) tal\u00e1n a leggyakrabban, legsz\u00e9lesebb k\u00f6rben alkalmazhat\u00f3k \u00e9s bukkannak is fel keretrendszerekben.

                    Ha id\u00e1ig olvastad, mindenk\u00e9ppen j\u00e1r egy extra thumbs up \ud83d\udc4d!

                    "},{"location":"hazi/beadas-ellenorzes/","title":"H\u00e1zi feladat bead\u00e1sa sor\u00e1n ellen\u0151rizend\u0151k","text":"
                    • A repository gy\u00f6k\u00e9rmapp\u00e1j\u00e1ban tal\u00e1lhat\u00f3 neptun.txt f\u00e1jlba \u00edrd bele a Neptun k\u00f3dod, csupa nagybet\u0171vel. A f\u00e1jlban csak ez a hat karakter legyen, semmi m\u00e1s.
                    • A GitHub-r\u00f3l le\u00f6lt\u00f6tt kiindul\u00f3 solutionben/projektekben kell dolgozni, nem \u00fajonnan l\u00e9trehozottban.
                    • Am\u00edg nem vagy rutinos a Visual Studio Git szolg\u00e1ltat\u00e1sainak haszn\u00e1lat\u00e1ban, a push-t k\u00f6vet\u0151en (legk\u00e9s\u0151bb akkor, amikor a h\u00e1zi feladatot beadottnak tekintj\u00fck) c\u00e9lszer\u0171 ellen\u0151rizni a GitHub webes fel\u00fclet\u00e9n a repository-ban a f\u00e1jlokra val\u00f3 r\u00e1pillant\u00e1ssal, hogy val\u00f3ban minden v\u00e1ltoztat\u00e1st felt\u00f6lt\u00f6tt\u00e9l-e.
                    • A GitHub fel\u00fclet\u00e9n ellen\u0151rizd a push-t k\u00f6vet\u0151en, hogy a GitHub Action alap\u00fa el\u0151ellen\u0151rz\u0151 hiba n\u00e9lk\u00fcl lefutott-e.
                    • L\u00e9nyeges, hogy a feladatok csak akkor ker\u00fclnek elfogad\u00e1sra, ha teljesen elk\u00e9sz\u00fclnek, \u00e9s minden tekintetben teljes\u00edtik a k\u00f6vetelm\u00e9nyeket. Nem fordul\u00f3 k\u00f3d, illetve r\u00e9szleges megold\u00e1s elfogad\u00e1s\u00e1ban nem \u00e9rdemes b\u00edzni.
                    • Term\u00e9szetesen saj\u00e1t munk\u00e1t kell beadni (hiszen \u00e9rt\u00e9kel\u00e9sre ker\u00fcl).
                    "},{"location":"hazi/beadas-ellenorzes/index_ger/","title":"Bei der Abgabe von Hausaufgaben sollten Sie Folgendes \u00fcberpr\u00fcfen","text":"
                    • Geben Sie in der Datei neptun.txt im Stammverzeichnis des Repositorys Ihren Neptun-Code in Gro\u00dfbuchstaben ein. Die Datei sollte nur diese sechs Zeichen enthalten und nichts anderes.
                    • Sie sollten in den urspr\u00fcnglichen L\u00f6sungen/Projekten arbeiten, die Sie von GitHub heruntergeladen haben, und nicht in neu erstellten Projekten.
                    • Solange Sie nicht mit Visual Studio Git vertraut sind, sollten Sie nach dem Push (sp\u00e4testens wenn die Hausarbeit als eingereicht gilt) \u00fcberpr\u00fcfen, ob Sie alle \u00c4nderungen hochgeladen haben, indem Sie sich die Dateien im Repository auf der GitHub-Weboberfl\u00e4che ansehen.
                    • \u00dcberpr\u00fcfen Sie in der GitHub-Schnittstelle nach dem Push, ob der GitHub Action-basierte Pre-Validator fehlerfrei gelaufen ist.
                    • Es ist wichtig, dass Aufgaben nur angenommen werden, wenn sie vollst\u00e4ndig abgeschlossen sind und den Anforderungen in jeder Hinsicht entsprechen. Nicht rotierenden Codes oder Teill\u00f6sungen sollte man nicht trauen.
                    • Nat\u00fcrlich m\u00fcssen Sie Ihre eigene Arbeit einreichen (da sie bewertet wird).
                    "},{"location":"hazi/eloellenorzes-ertekeles/","title":"A h\u00e1zi feladat el\u0151ellen\u0151rz\u00e9se \u00e9s hivatalos \u00e9rt\u00e9kel\u00e9se","text":"

                    Minden egyes alkalommal, miut\u00e1n a GitHub-ra push-olt\u00e1l k\u00f3dot, a GitHub-on automatikusan lefut a felt\u00f6lt\u00f6tt k\u00f3d (el\u0151)ellen\u0151rz\u00e9se, \u00e9s meg lehet n\u00e9zni a kimenet\u00e9t! Az ellen\u0151rz\u0151t maga a GitHub futtatja. A push-t k\u00f6vet\u0151en a feladat egy v\u00e1rakoz\u00e1si sorba ker\u00fcl, majd adott id\u0151 ut\u00e1n lefutnak az ellen\u0151rz\u0151 tesztek. Azt nem lehet tudni, mennyi ez az id\u0151, a GitHub-on m\u00falik. Amikor csak egy-k\u00e9t feladat van a sorban a szervezetre (ez n\u00e1lunk a t\u00e1rgy), akkor a tapasztalatok alapj\u00e1n az ellen\u0151rz\u00e9s 1-2 percen bel\u00fcl elindul. De ha a t\u00e1rgy alatt egyszerre sokan kezdik majd felt\u00f6lteni a megold\u00e1st, akkor ez j\u00f3 es\u00e9llyel belassul. Nem \u00e9rdemes ez\u00e9rt sem az utols\u00f3 pillanatra hagyni a bead\u00e1st: lehet, hogy ekkor a k\u00e9sleltet\u00e9sek miatt m\u00e1r nem kapsz esetleg id\u0151ben visszajelz\u00e9st.

                    Hivatalosan a feladat azon \u00e1llapota ker\u00fcl \u00e9rt\u00e9kel\u00e9sre, amely a hat\u00e1rid\u0151 lej\u00e1rtakor GitHub-on fent van. A hivatalos ellen\u0151rz\u00e9st szok\u00e1sos m\u00f3don, saj\u00e1t, oktat\u00f3i k\u00f6rnyezetben v\u00e9gezz\u00fck \u00e9s az eredm\u00e9nyt Moodleben publik\u00e1ljuk a sz\u00e1monk\u00e9r\u00e9sn\u00e9l. Vagyis a hivatalos eredm\u00e9ny tekintet\u00e9ben teljesen mindegy, hogy a GitHub-on a hat\u00e1rid\u0151 lej\u00e1rta lefutott-e m\u00e1r b\u00e1rmif\u00e9le (el\u0151)ellen\u0151rz\u00e9s, vagy hogy az ellen\u0151rz\u00e9s esetleg csak k\u00e9s\u0151bb tudott elindulni. A GitHub \u00e1ltali ellen\u0151rz\u00e9s csak azt a c\u00e9lt szolg\u00e1lja, hogy m\u00e9g a hat\u00e1rid\u0151 lej\u00e1rta el\u0151tt visszajelz\u00e9st kaphasson mindenki. A hat\u00e1rid\u0151 lej\u00e1rta ut\u00e1ni hivatalos ellen\u0151rz\u00e9s tartalmaz m\u00e9g plusz l\u00e9p\u00e9seket a GitHub alap\u00fa el\u0151ellen\u0151rz\u00e9shez k\u00e9pest, az el\u0151ellen\u0151rz\u00e9s ilyen \u00e9rtelemben r\u00e9szleges, de az\u00e9rt sok probl\u00e9m\u00e1t seg\u00edthet megfogni!

                    Arra k\u00e9r\u00fcnk, hogy ne apr\u00e1nk\u00e9nt push-olj, csak a k\u00e9sz, \u00e1tn\u00e9zett, fordul\u00f3 megold\u00e1st tedd fel! Ez nem a legszerencs\u00e9sebb, de a GitHub korl\u00e1tozott id\u0151t biztos\u00edt az ellen\u0151rz\u0151k futtat\u00e1s\u00e1ra: ha elfogy a havi keret, akkor m\u00e1r nem fogsz visszajelz\u00e9st kapni, csak a hat\u00e1rid\u0151 ut\u00e1ni hivatalos ellen\u0151rz\u00e9s kimenet\u00e9t kapja meg mindenki.

                    A (f\u00e9l)automata ellen\u0151rz\u0151, most m\u00e9g egy r\u00e9szben k\u00eds\u00e9rleti projekt. Ha valaki az \u00fatmutat\u00f3ban inkonzisztenci\u00e1t tal\u00e1l, vagy az ellen\u0151rz\u0151 adott helyzetet nem kezel \u00e9s indokolatlanul panaszkodik, Benedek Zolt\u00e1n felel\u0151s oktat\u00f3 fel\u00e9 legyen sz\u00edves jelezni! Ugyanakkor ezeket nagy t\u00f6megben nem fogjuk tudni kezelni. Ha j\u00f3 a megold\u00e1sod, \u00e9s az ellen\u0151rz\u0151 indokolatlanul panaszkodik, a hivatalos ellen\u0151rz\u00e9s sor\u00e1n term\u00e9szetesen el fogjuk fogadni.

                    Az el\u0151ellen\u0151rz\u0151 \u2013 k\u00fcl\u00f6n\u00f6sen az els\u0151 h\u00e1zi feladat eset\u00e9ben \u2013 sokszor el\u00e9gg\u00e9 \"g\u00e9pk\u00f6zeli megfogalmaz\u00e1sban\" jelzi az esetleges probl\u00e9m\u00e1kat. Ha semmik\u00e9ppen nem tudod \u00e9rtelmezni, \u00edrj Benedek Zolt\u00e1nnak Teams-ben, a hiba\u00fczenet megad\u00e1s\u00e1val, illetve egy linkkel a GitHub repository-dra (m\u00e1sk\u00fcl\u00f6nben nem tudjuk, hol tal\u00e1lhat\u00f3 a k\u00f3dod).

                    Az, hogy az el\u0151ellen\u0151rz\u0151 milyen m\u00e9lys\u00e9gben ellen\u0151rzi a megold\u00e1st, a h\u00e1zi feladatt\u00f3l f\u00fcgg. Az 1-3 feladat eset\u00e9ben el\u00e9g alapos, m\u00edg a 4-5 feladat eset\u00e9n csak a Neptun.txt kit\u00f6lt\u00f6tts\u00e9g\u00e9t ellen\u0151rzi, \u00e9s azt, van-e ford\u00edt\u00e1si hiba (az \u00e9rdemi \u00e9rt\u00e9kel\u00e9s ut\u00f3lag t\u00f6rt\u00e9nik).

                    "},{"location":"hazi/eloellenorzes-ertekeles/#a-github-altal-futtatott-ellenorzesek-megtekintese","title":"A GitHub \u00e1ltal futtatott ellen\u0151rz\u00e9sek megtekint\u00e9se","text":"
                    1. GitHub-on a navig\u00e1l\u00e1s a repository-hoz
                    2. Actions tabf\u00fclre v\u00e1lt\u00e1s
                    3. Itt megjelenik egy t\u00e1bl\u00e1zat, minden push \u00e1ltal futtatott ellen\u0151rz\u00e9shez egy k\u00fcl\u00f6n sor, a tetej\u00e9n van legfrissebb. A sor elej\u00e9n lev\u0151 ikon jelzi a st\u00e1tuszt: v\u00e1r, fut, sikeres, sikertelen lehet. A sor sz\u00f6vege a Git commit neve.
                    4. Egy sorban a commit nev\u00e9n kattintva jelenik meg egy \u00e1tfog\u00f3 oldal az ellen\u0151rz\u0151 fut\u00e1s\u00e1r\u00f3l, ez sok inform\u00e1ci\u00f3t nem tartalmaz. Ezen az oldalon baloldalt kell a \"build\" vagy \"build-and-check\" (vagy hasonl\u00f3 nev\u0171) linken kattintani, ez \u00e1tnavig\u00e1l az ellen\u0151rz\u00e9s r\u00e9szletes n\u00e9zet\u00e9re. Ez egy \u201e\u00e9l\u0151\u201d n\u00e9zet, ha fut a teszt, folyamatosan friss\u00fcl. Ha v\u00e9gzett, a csom\u00f3pontokat lenyitva lehet megn\u00e9zni az adott l\u00e9p\u00e9s kimenet\u00e9t. Ha minden siker\u00fclt, egy ehhez hasonl\u00f3 n\u00e9zet l\u00e1that\u00f3:

                    5. Itt a legfontosabb tal\u00e1n a \"Run tests\" l\u00e9p\u00e9s. Ha valamelyik l\u00e9p\u00e9s sikertelen, pipa helyett piros x van a csom\u00f3pont elej\u00e9n, \u00e9s a csom\u00f3pontot kibontva a teszt kimenete utal a hiba ok\u00e1ra. Az els\u0151 h\u00e1zi feladat eset\u00e9ben az \"Error Message\"-re, ill. az \"Assert\"-re \u00e9rdemes sz\u00f6vegesen (control+F) keresni a kimenetben, ennek a k\u00f6rny\u00e9k\u00e9n szokott lenni hivatkoz\u00e1s a hiba ok\u00e1ra.

                    "},{"location":"hazi/fejlesztokornyezet/","title":"Fejleszt\u0151k\u00f6rnyezet h\u00e1zi feladatokhoz","text":""},{"location":"hazi/fejlesztokornyezet/#bevezetes","title":"Bevezet\u00e9s","text":"

                    A f\u00e9l\u00e9v sor\u00e1n a h\u00e1zi feladatok megold\u00e1s\u00e1hoz a Visual Studio 2022 fejleszt\u0151k\u00f6rnyezetet kell haszn\u00e1lni (a Visual Studio for Mac nem alkalmas). Ennek futtat\u00e1s\u00e1hoz Windows oper\u00e1ci\u00f3s rendszerre van sz\u00fcks\u00e9g. Ha telep\u00edtve van m\u00e1r a g\u00e9p\u00fcnkre a Visual Studio 2022, akkor a Start men\u00fcb\u0151l ind\u00edtsuk el a \u201eVisual Studio Installer\u201d-t. Ez indul\u00e1skor ellen\u0151rzi, \u00e9rhet\u0151-e el Visual Studio-b\u00f3l \u00fajabb v\u00e1ltozat online, \u00e9s ha igen, az Update gombra kattintva ind\u00edtsuk is el a legfrissebb verzi\u00f3 telep\u00edt\u00e9s\u00e9t.

                    Mi\u00e9rt is van sz\u00fcks\u00e9g Visual Studiora \u00e9s Windowsra?

                    VS Code, illetve a Visual Studio for Mac a k\u00f6vetkez\u0151k miatt nem haszn\u00e1lhat\u00f3k:

                    • Nem t\u00e1mogatj\u00e1k az UML (szer\u0171) modellez\u00e9st, melyre az els\u0151 h\u00e1zi feladatn\u00e1l sz\u00fcks\u00e9g van.
                    • \u00c9rdemben nem t\u00e1mogatj\u00e1k a WinUI3 felhaszn\u00e1l\u00f3i fel\u00fclettel rendelkez\u0151 .NET alkalmaz\u00e1sok fejleszt\u00e9s\u00e9t (erre a 3. h\u00e1zi feladatt\u00f3l kezd\u0151d\u0151en \u00e9p\u00edtenek bizonyos h\u00e1zi feladatok).
                    "},{"location":"hazi/fejlesztokornyezet/#visual-studio-edition-ok","title":"Visual Studio edition-\u00f6k","text":"

                    A Visual Studionak t\u00f6bb kiad\u00e1sa l\u00e9tezik:

                    • A t\u00e1rgy teljes\u00edt\u00e9s\u00e9hez megfelel a Microsoft honlapj\u00e1r\u00f3l let\u00f6lthet\u0151 \u00e9s ingyenesen haszn\u00e1lhat\u00f3 Community edition.
                    • Term\u00e9szetesen a Professional \u00e9s Enterprise v\u00e1ltozatok is haszn\u00e1lhat\u00f3k, a t\u00e1rgy vonatkoz\u00e1s\u00e1ban ugyanakkor ezek \u00e9rdemi pluszt nem adnak. Ezek az egy\u00e9bk\u00e9nt fizet\u0151s v\u00e1ltozatok az egyetem hallgat\u00f3i sz\u00e1m\u00e1ra ingyenesen el\u00e9rhet\u0151k (a https://azureforeducation.microsoft.com/devtools honlapon, az Azure Dev Tools for Teaching program keret\u00e9ben).
                    "},{"location":"hazi/fejlesztokornyezet/#telepitendo-komponensek","title":"Telep\u00edtend\u0151 komponensek","text":"

                    A t\u00e1rgy els\u0151 el\u0151ad\u00e1sa r\u00f6viden kit\u00e9r a .NET k\u00fcl\u00f6nb\u00f6z\u0151 v\u00e1ltozataira (.NET Framework, .NET Core, .NET 5-8 \u00e9s stb.). A feladatok megold\u00e1s\u00e1hoz a .NET 8-et haszn\u00e1ljuk a f\u00e9l\u00e9v sor\u00e1n. A Visual Studio ezt telep\u00edti, de sz\u00fcks\u00e9g van a \".NET desktop development\" Visual Studio Workload telep\u00edt\u00e9s\u00e9re:

                    1. Visual Studio telep\u00edt\u0151 ind\u00edt\u00e1sa (pl. a Windows Start men\u00fcben a \u201eVisual Studio Installer\u201d beg\u00e9pel\u00e9s\u00e9vel).
                    2. Modify gombra kattint\u00e1s
                    3. A megjelen\u0151 ablakban ellen\u0151rizz\u00fck, hogy a \".NET desktop development\" k\u00e1rtya ki van-e pip\u00e1lva.
                    4. Ha nincs, pip\u00e1ljuk ki, majd a jobb als\u00f3 sarokban a Modify gombra kattintva telep\u00edts\u00fck.
                    "},{"location":"hazi/fejlesztokornyezet/#class-diagram-tamogatas","title":"Class diagram t\u00e1mogat\u00e1s","text":"

                    Bizonyos h\u00e1zi feladatok eset\u00e9n (m\u00e1r az els\u0151n\u00e9l is) sz\u00fcks\u00e9g van Visual Studio Class Diagram t\u00e1mogat\u00e1sra. Ezt a k\u00f6vetkez\u0151k\u00e9ppen tudjuk ut\u00f3lag telep\u00edteni a Visual Studio al\u00e1:

                    1. Visual Studio telep\u00edt\u0151 ind\u00edt\u00e1sa (pl. a Windows Start men\u00fcben a \u201eVisual Studio Installer\u201d beg\u00e9pel\u00e9s\u00e9vel).
                    2. Modify gombra kattint\u00e1s
                    3. A megjelen\u0151 ablakban \"Individual components\" f\u00fcl kiv\u00e1laszt\u00e1sa
                    4. A keres\u0151mez\u0151be \"class designer\" beg\u00e9pel\u00e9se, majd gy\u0151z\u0151dj\u00fcnk meg, hogy a sz\u0171rt list\u00e1ban a \"Class Designer\" elem ki van pip\u00e1lva.
                    5. Ha nincs, pip\u00e1ljuk ki, majd a jobb als\u00f3 sarokban a Modify gombra kattintva telep\u00edts\u00fck.

                    "},{"location":"hazi/fejlesztokornyezet/#winui-tamogatas","title":"WinUI t\u00e1mogat\u00e1s","text":"

                    XAML/WinUI technol\u00f3gi\u00e1khoz kapcsol\u00f3d\u00f3 feladatok eset\u00e9n (3. h\u00e1zi feladatt\u00f3l kezd\u0151d\u0151en) sz\u00fcks\u00e9g van Windows App SDK el\u0151zetes telep\u00edt\u00e9s\u00e9re \u00e9s bizonyos speci\u00e1lis g\u00e9pi szint\u0171 be\u00e1ll\u00edt\u00e1sok m\u00f3dos\u00edt\u00e1s\u00e1ra.

                    1. A sz\u00e1m\u00edt\u00f3g\u00e9pen enged\u00e9lyezni kell a \"Developer mode\" (\"Fejleszt\u0151i m\u00f3d\")-ot. A Windows Start men\u00fcben a \"Developer settings\"/\"Fejleszt\u0151i funkci\u00f3k\"-ra \u00e9rdemes keresni (annak f\u00fcggv\u00e9ny\u00e9ben hogy angol vagy magyar Windowst haszn\u00e1lunk).

                    2. A Visual Studio telep\u00edt\u0151ben gy\u0151z\u0151dj\u00fcnk meg, hogy a \".NET Desktop Development\" workload telep\u00edtve van (ha nincs, pip\u00e1ljuk \u00e9s telep\u00edts\u00fck)

                    3. \"Windows App SDK C# templates\" Visual Studio komponens telep\u00edt\u00e9se.

                      A Visual Studio telep\u00edt\u0151ben v\u00e1lasszuk ki a \".NET Desktop Development\" workload-ot, jobb oldalt az \"Installation details\" panelen alul pip\u00e1ljuk a \"Windows App SDK C# Templates\" komponenst, majd jobb als\u00f3 sarokban \"Modify\" gomb.

                    4. Windows App SDK telep\u00edt\u00e9se

                      A legfrissebb innen telep\u00edthet\u0151: https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/downloads. Ugyanakkor a f\u00e9l\u00e9v sor\u00e1n laborokon, h\u00e1zikban az \"1.4.4 (1.4.231219000)\" verzi\u00f3t haszn\u00e1ljuk, \u00e9rdemes ezt telep\u00edteni akkor is, ha \u00fajabb verzi\u00f3 j\u00f6nne ki, mely innen \u00e9rhet\u0151 el: https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/older-downloads. Egy modern g\u00e9pre az x64-es verzi\u00f3t kell telep\u00edteni.

                    5. Ha a fentiek telep\u00edt\u00e9se ut\u00e1n Windows 11-en nem akarna m\u0171k\u00f6dni, akkor fel kell tenni a Visual Studio telep\u00edt\u0151ben a Windows 10 SDK-b\u00f3l a 10.0.19041-et, vagy \u00fajabbat (az Idividual Comopnents alatt tal\u00e1lhat\u00f3)

                    "},{"location":"hazi/fejlesztokornyezet/#macbook-es-linux-hasznalok-szamara-informaciok","title":"MacBook \u00e9s Linux haszn\u00e1l\u00f3k sz\u00e1m\u00e1ra inform\u00e1ci\u00f3k","text":"

                    A t\u00e1rgy felel\u0151s oktat\u00f3j\u00e1t\u00f3l (Benedek Zolt\u00e1n) BME Cloud hozz\u00e1f\u00e9r\u00e9s ig\u00e9nylelhet\u0151 e-mailben.

                    "},{"location":"hazi/fejlesztokornyezet/index_ger/","title":"Entwicklungsumgebung f\u00fcr Hausaufgaben","text":""},{"location":"hazi/fejlesztokornyezet/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

                    F\u00fcr die Hausaufgaben w\u00e4hrend des Semesters muss die Entwicklungsumgebung Visual Studio 2022 verwendet werden (Visual Studio f\u00fcr Mac ist nicht geeignet). Zum Ausf\u00fchren ben\u00f6tigen Sie ein Windows-Betriebssystem. Wenn Sie Visual Studio 2022 bereits auf Ihrem Computer installiert haben, starten Sie den \"Visual Studio Installer\" \u00fcber das Startmen\u00fc. Dadurch wird beim Start gepr\u00fcft, ob eine neuere Version von Visual Studio online verf\u00fcgbar ist. Ist dies der Fall, klicken Sie auf Aktualisieren, um die Installation der neuesten Version zu starten.

                    Warum brauche ich Visual Studio und Windows?

                    VS Code oder Visual Studio f\u00fcr Mac kann aus folgenden Gr\u00fcnden nicht verwendet werden:

                    • Sie unterst\u00fctzen keine UML-\u00e4hnliche Modellierung, die f\u00fcr die erste Hausaufgabe ben\u00f6tigt wird.
                    • Sie unterst\u00fctzen nicht die Entwicklung von .NET-Anwendungen mit der Benutzeroberfl\u00e4che WinUI3 (einige Hausaufgaben ab Hausaufgabe 3 bauen darauf auf).
                    "},{"location":"hazi/fejlesztokornyezet/index_ger/#visual-studio-ausgabe-ok","title":"Visual Studio Ausgabe-\u00f6k","text":"

                    Es gibt verschiedene Editionen von Visual Studio:

                    • Um den Kurs zu absolvieren, k\u00f6nnen Sie die kostenlose Community-Edition von der Microsoft-Website herunterladen.
                    • Die Professional- und Enterprise-Versionen k\u00f6nnen nat\u00fcrlich auch verwendet werden, bieten aber keinen inhaltlichen Mehrwert. Diese kostenpflichtigen Versionen sind f\u00fcr Universit\u00e4tsstudenten kostenlos erh\u00e4ltlich (unter https://azureforeducation.microsoft.com/devtools, als Teil des Azure Dev Tools for Teaching Programms).
                    "},{"location":"hazi/fejlesztokornyezet/index_ger/#zu-installierende-komponenten","title":"Zu installierende Komponenten","text":"

                    In der ersten Vorlesung des Kurses werden kurz die verschiedenen Versionen von .NET (.NET Framework, .NET Core, .NET 5-8 usw.) behandelt. Wir werden .NET 8 verwenden, um die Probleme w\u00e4hrend des Semesters zu l\u00f6sen. Visual Studio installiert dies, aber Sie m\u00fcssen den \".NET Desktop Development\" Visual Studio Workload installieren:

                    1. Starten Sie das Visual Studio-Installationsprogramm (z. B. durch Eingabe von \"Visual Studio Installer\" im Windows-Startmen\u00fc).
                    2. Klicken Sie auf die Schaltfl\u00e4che \u00c4ndern
                    3. Vergewissern Sie sich in dem nun erscheinenden Fenster, dass die Karte \".NET-Desktop-Entwicklung\" aktiviert ist.
                    4. Wenn nicht, entfernen Sie das H\u00e4kchen und klicken Sie unten rechts auf \u00c4ndern, um es zu installieren.
                    "},{"location":"hazi/fejlesztokornyezet/index_ger/#unterstutzung-von-klassendiagrammen","title":"Unterst\u00fctzung von Klassendiagrammen","text":"

                    F\u00fcr bestimmte Hausaufgaben (sogar f\u00fcr die erste) ben\u00f6tigen Sie die Unterst\u00fctzung von Visual Studio Class Diagram. Diese kann unter Visual Studio wie folgt installiert werden:

                    1. Starten Sie das Visual Studio-Installationsprogramm (z. B. durch Eingabe von \"Visual Studio Installer\" im Windows-Startmen\u00fc).
                    2. Klicken Sie auf die Schaltfl\u00e4che \u00c4ndern
                    3. W\u00e4hlen Sie in dem nun erscheinenden Fenster die Registerkarte \"Einzelne Komponenten\"
                    4. Geben Sie in das Suchfeld \"Klassendesigner\" ein und vergewissern Sie sich, dass \"Klassendesigner\" in der gefilterten Liste nicht angekreuzt ist.
                    5. Wenn nicht, entfernen Sie das H\u00e4kchen und klicken Sie unten rechts auf \u00c4ndern, um es zu installieren.

                    "},{"location":"hazi/fejlesztokornyezet/index_ger/#winui-unterstutzung","title":"WinUI-Unterst\u00fctzung","text":"

                    F\u00fcr Aufgaben, die sich auf XAML/WinUI-Technologien beziehen (ab Hausaufgabe 3), ist es notwendig, das Windows App SDK vorzuinstallieren und einige spezifische Einstellungen auf Maschinenebene zu \u00e4ndern.

                    1. Der \"Entwicklermodus\" muss auf dem Computer aktiviert sein. Suchen Sie im Windows-Startmen\u00fc nach \"Entwicklereinstellungen\" (je nachdem, ob Sie ein englisches oder ungarisches Windows verwenden).

                    2. Vergewissern Sie sich im Visual Studio-Installationsprogramm, dass der \".NET Desktop Development\"-Workload installiert ist (falls nicht, entfernen Sie die Markierung und installieren Sie ihn)

                    3. installation der Visual Studio Komponente \"Windows App SDK C# Templates\".

                      W\u00e4hlen Sie im Visual Studio-Installationsprogramm den Workload \".NET Desktop Development\", markieren Sie die Komponente \"Windows App SDK C# Templates\" im Bereich \"Installationsdetails\" auf der rechten Seite und klicken Sie dann auf die Schaltfl\u00e4che \"\u00c4ndern\" in der rechten unteren Ecke.

                    4. Windows-SDK installieren

                      Es kann installiert werden von: https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/downloads. W\u00e4hrend des Semesters werden wir \"1.4.4 (1.4.231219000)\" in \u00dcbungen und Tutorien verwenden. Es wird empfohlen, diese Version zu installieren, auch wenn eine neuere Version ver\u00f6ffentlicht wird.

                    "},{"location":"hazi/fejlesztokornyezet/index_ger/#informationen-fur-macbook-und-linux-benutzer","title":"Informationen f\u00fcr MacBook- und Linux-Benutzer","text":"

                    Sie k\u00f6nnen den Zugang zur BME-Cloud beim zust\u00e4ndigen Dozenten (Zolt\u00e1n Benedek) per E-Mail anfordern.

                    "},{"location":"hazi/git-github-github-classroom/","title":"Git, GitHub, GitHub Classroom","text":"

                    A t\u00e1rgy keret\u00e9ben nem c\u00e9lunk a Git \u00e9s GitHub r\u00e9szletes megismer\u00e9se, csak a legsz\u00fcks\u00e9gesebb l\u00e9p\u00e9sekre szor\u00edtkozunk, valamint a legfontosabb parancsokat haszn\u00e1ljuk ahhoz, hogy a h\u00e1zi feladat(ok) kiindul\u00e1si programv\u00e1z\u00e1t hallgat\u00f3k\u00e9nt egy dedik\u00e1lt GitHub repository-b\u00f3l le tudjuk t\u00f6lteni, illetve a k\u00e9sz munk\u00e1t GitHubra fel tudjuk t\u00f6lteni.

                    "},{"location":"hazi/git-github-github-classroom/#git","title":"Git","text":"

                    A Git egy sok szolg\u00e1ltat\u00e1ssal rendelkez\u0151, rendk\u00edv\u00fcl n\u00e9pszer\u0171 \u00e9s elterjedt, ingyenesen let\u00f6lthet\u0151 \u00e9s telep\u00edthet\u0151, elosztottan is haszn\u00e1lhat\u00f3 verzi\u00f3kezel\u0151 rendszer. A k\u00f6zpontos\u00edtott rendszerekhez k\u00e9pest (TFS, CVS, SVN) a GIT eset\u00e9ben nem egyetlen k\u00f6zponti repository-ba dolgoznak a fejleszt\u0151k, hanem mindenki egy saj\u00e1t lok\u00e1lis repository p\u00e9ld\u00e1nnyal rendelkezik.

                    Egy Git repository (becenev\u00e9n rep\u00f3) nem m\u00e1s, mint egy k\u00f6z\u00f6ns\u00e9ges k\u00f6nyvt\u00e1r a f\u00e1jlrendszerben, benne \u00e1llom\u00e1nyokkal (pl. forr\u00e1sk\u00f3d) \u00e9s alk\u00f6nyvt\u00e1rakkal, illetve egy \".git\" alk\u00f6nyvt\u00e1rral, melyben minden, a verzi\u00f3kezel\u00e9shez kapcsol\u00f3d\u00f3 extra inform\u00e1ci\u00f3 megtal\u00e1lhat\u00f3.

                    A Git alap\u00fa munkafolyamat legfontosabb l\u00e9p\u00e9sei - n\u00e9mi egyszer\u0171s\u00edt\u00e9ssel - a k\u00f6vetkez\u0151k (felt\u00e9ve, hogy l\u00e9tezik egy k\u00f6zponti repository, ahol a verzi\u00f3kezelt k\u00f3d adott v\u00e1ltozata m\u00e1r el\u00e9rhet\u0151):

                    1. A fejleszt\u0151 kl\u00f3nozza (clone) az adott k\u00f6zponti repository-t, melynek sor\u00e1n egy azzal megegyez\u0151 helyi repository j\u00f6n l\u00e9tre a saj\u00e1t sz\u00e1m\u00edt\u00f3g\u00e9p\u00e9n. Ezt a m\u0171veletet el\u00e9g egyszer elv\u00e9gezni.
                    2. A fejleszt\u0151 a helyi repository-hoz tartoz\u00f3 munkak\u00f6nyvt\u00e1rban (working directory) v\u00e1ltoztat\u00e1sokat v\u00e9gez a k\u00f3don: \u00faj f\u00e1jlokat vesz fel, megl\u00e9v\u0151ket m\u00f3dos\u00edt \u00e9s t\u00f6r\u00f6l.
                    3. Ha elk\u00e9sz\u00fcl egy \u00e9rdemi r\u00e9szfeladat, akkor a fejleszt\u0151 a v\u00e1ltoztat\u00e1sokat commit-olja a sz\u00e1m\u00edt\u00f3g\u00e9p\u00e9n lev\u0151 helyi repository-ba. Ennek sor\u00e1n a commit-ot c\u00e9lszer\u0171 egy a v\u00e1ltoztat\u00e1sok jelleg\u00e9t j\u00f3l \u00f6sszefoglal\u00f3 megjegyz\u00e9ssel ell\u00e1tni.
                    4. A helyi repository-b\u00f3l egy push m\u0171velettel a fejleszt\u0151 fel\u00f6lti a v\u00e1ltoz\u00e1sokat a k\u00f6zponti repository-ba, ahol \u00edgy v\u00e1ltoztat\u00e1sai m\u00e1sok sz\u00e1m\u00e1ra is l\u00e1that\u00f3v\u00e1 v\u00e1lnak.

                    Minden egyes commit tulajdonk\u00e9ppen egy id\u0151b\u00e9lyeggel, a fejleszt\u0151 felhaszn\u00e1l\u00f3nev\u00e9vel \u00e9s e-mail c\u00edm\u00e9vel ell\u00e1tott k\u00f3dot \u00e9rint\u0151 v\u00e1ltoz\u00e1shalmaz. A repositoryban ezek \"egym\u00e1sut\u00e1nis\u00e1g\u00e1b\u00f3l\" \u00e1ll \u00f6ssze a teljes verzi\u00f3t\u00f6rt\u00e9net. Mivel a legt\u00f6bb esetben a fejleszt\u0151k csapatban dolgoznak, id\u0151nk\u00e9nt sz\u00fcks\u00e9g van arra, hogy m\u00e1sok \u00e1ltal a k\u00f6zponti repository-ba push-olt v\u00e1ltoztat\u00e1sokat a fejleszt\u0151k a saj\u00e1t lok\u00e1lis repository-jukba let\u00f6lts\u00e9k \u00e9s belemerge-elj\u00e9k: erre szolg\u00e1l a pull m\u0171velet. Fontos szab\u00e1ly, hogy push-olni csak akkor lehet a k\u00f6zponti repository-ba (a Git csak akkor engedi), ha el\u0151tte m\u00e1sok v\u00e1ltoztat\u00e1sait a saj\u00e1t lok\u00e1lis repository-nkba egy pull m\u0171velettel el\u0151tte belemerge-elt\u00fck. A Szoftvertechnik\u00e1k t\u00e1rgy keret\u00e9ben a pull m\u0171veletet nem kell haszn\u00e1lni, mert mindenki \u00f6n\u00e1ll\u00f3an, saj\u00e1t repository-ba dolgozik. Megjegyz\u00e9s: ha esetleg a GitHub fel\u00fclet\u00e9n k\u00f6zvetlen v\u00e1ltoztatunk f\u00e1jlokon (vagy t\u00f6bb clone-ban is dolgozunk), akkor sz\u00fcks\u00e9g van a pull haszn\u00e1lat\u00e1ra ez esetben is. A fentieken t\u00falmen\u0151en a Git sz\u00e1mos tov\u00e1bbi szolg\u00e1ltat\u00e1st biztos\u00edt (pl. teljes verzi\u00f3t\u00f6rt\u00e9net megtekint\u00e9se minden f\u00e1jlra, commit t\u00f6rt\u00e9net megtekint\u00e9se, tetsz\u0151leges m\u00faltbeli verzi\u00f3ra vissza\u00e1ll\u00e1s, \u00e1gak kezel\u00e9se stb.).

                    "},{"location":"hazi/git-github-github-classroom/#github","title":"GitHub","text":"

                    A GitHub egy online el\u00e9rhet\u0151 website \u00e9s szolg\u00e1ltat\u00e1s (https://github.com), mely teljes k\u00f6r\u0171 Git szolg\u00e1ltat\u00e1st biztos\u00edt. Mindezt r\u00e1ad\u00e1sul \u2013 legal\u00e1bbis publikus, vagyis mindenki sz\u00e1m\u00e1ra hozz\u00e1f\u00e9rhet\u0151 repositoryk vonatkoz\u00e1s\u00e1ban \u2013 teljesen ingyenesen biztos\u00edtja. Napjainkra a GitHub v\u00e1lt a k\u00f6z\u00f6ss\u00e9gi k\u00f3d (verzi\u00f3kezelt) t\u00e1rol\u00e1s\u00e1nak els\u0151 sz\u00e1m\u00fa platformj\u00e1v\u00e1, a legt\u00f6bb ny\u00edlt forr\u00e1sk\u00f3d\u00fa projekt \u201eotthon\u00e1v\u00e1\u201d.

                    "},{"location":"hazi/git-github-github-classroom/#github-classroom","title":"GitHub Classroom","text":"

                    A GitHub Classroom egy ingyenesen el\u00e9rhet\u0151 GitHub-bal integr\u00e1lt szolg\u00e1ltat\u00e1s, mely t\u00f6bbek k\u00f6z\u00f6tt oktat\u00e1si int\u00e9zm\u00e9nyek sz\u00e1m\u00e1ra lehet\u0151v\u00e9 teszi \u00f6n\u00e1ll\u00f3 tanul\u00f3i feladatokhoz tartoz\u00f3, tanul\u00f3nk\u00e9nt egyedi GitHub repository-k l\u00e9trehoz\u00e1s\u00e1t, ez\u00e1ltal a kiindul\u00e1si k\u00f3d tanul\u00f3k sz\u00e1m\u00e1ra t\u00f6rt\u00e9n\u0151 \u201ekioszt\u00e1s\u00e1t\u201d, valamint az elk\u00e9sz\u00fclt feladatok \u201ebeszed\u00e9s\u00e9t\u201d.

                    "},{"location":"hazi/git-github-github-classroom/#git-github-es-github-classroom-a-targy-kontextusaban","title":"Git, GitHub \u00e9s GitHub Classroom a t\u00e1rgy kontextus\u00e1ban","text":"

                    A t\u00e1rgy keret\u00e9ben a GitHub Classroom seg\u00edts\u00e9g\u00e9vel kap minden hallgat\u00f3 minden h\u00e1zi feladat\u00e1hoz egy dedik\u00e1lt, a GitHub-on hostolt repository-t, mely a megfelel\u0151 kiindul\u00e1si k\u00f6rnyezettel (kiindul\u00f3 Visual Studio solution-\u00f6k) inicializ\u00e1l\u00e1sra ker\u00fcl. Mindenkinek a sz\u00e1m\u00e1ra dedik\u00e1lt repository-t kell a saj\u00e1t g\u00e9p\u00e9re clone-oznia, ebbe a v\u00e1ltoztat\u00e1sait commit-olni, \u00e9s a hat\u00e1rid\u0151ig az elk\u00e9sz\u00fclt megold\u00e1s\u00e1t push-olni (hogy GitHub-on is el\u00e9rhet\u0151 legyen a megold\u00e1s). A pontos l\u00e9p\u00e9sekre r\u00f6videsen visszat\u00e9r\u00fcnk.

                    "},{"location":"hazi/git-github-github-classroom/#visual-studio-es-a-git","title":"Visual Studio \u00e9s a Git","text":"

                    A Git egy elosztott verzi\u00f3kezel\u0151 rendszer. Ahhoz, hogy a saj\u00e1t g\u00e9p\u00fcnk\u00f6n dolgozni tudjunk vele, a Git-nek telep\u00edtve kell lennie. K\u00e9tf\u00e9le m\u00f3don tudjuk haszn\u00e1lni:

                    • A Git \u00f6nmag\u00e1ban is telep\u00edthet\u0151, \u00e9s parancssorb\u00f3l is ki tudjuk adni a sz\u00fcks\u00e9ges clone, commit, push stb. parancsokat.
                    • Haszn\u00e1lhatunk a parancsok kiad\u00e1s\u00e1ra egy grafikus fel\u00fclettel rendelkez\u0151 eszk\u00f6zt. Ilyenek pl. a GitHub Desktop, a GitExtensions, vagy maga a Visual Studio is (mely integr\u00e1lt grafikus Git szolg\u00e1ltat\u00e1sokat is biztos\u00edt).

                    A k\u00e9t megk\u00f6zel\u00edt\u00e9st a mindennapokban kombin\u00e1ltan szoktuk haszn\u00e1lni. Egy repo lekl\u00f3noz\u00e1sa sokszor parancssorb\u00f3l a legegyszer\u0171bb/leggyorsabb. A v\u00e1ltoz\u00e1sok commit-ol\u00e1s\u00e1ra, a k\u00f6zponti repositoryval val\u00f3 szinkroniz\u00e1ci\u00f3ra (push, pull), a verzi\u00f3t\u00f6rt\u00e9nek megjelen\u00edt\u00e9s\u00e9re m\u00e1r c\u00e9lszer\u0171bb egy grafikus eszk\u00f6zt haszn\u00e1lni, k\u00fcl\u00f6n\u00f6sen akkor, ha m\u00e9g kev\u00e9sb\u00e9 vagyunk rutinosak. A t\u00e1rgy keret\u00e9ben a kl\u00f3noz\u00e1sra a parancssor vagy a Visual Studio, az egy\u00e9b parancsok kiad\u00e1s\u00e1ra a Visual Studio javasolt. A git haszn\u00e1latr\u00f3l (a h\u00e1zi feladatok kontextus\u00e1ban) itt tal\u00e1lhat\u00f3 b\u0151vebb le\u00edr\u00e1s.

                    "},{"location":"hazi/git-github-github-classroom/#git-telepitese","title":"Git telep\u00edt\u00e9se","text":"

                    Amennyiben a sz\u00e1m\u00edt\u00f3g\u00e9p\u00fcnkre nincs m\u00e9g a Git telep\u00edtve, \u00e9s szeretn\u00e9nk azt parancssorb\u00f3l is haszn\u00e1lni, akkor innen telep\u00edthet\u0151 Windows oper\u00e1ci\u00f3s rendszerre: https://git-scm.com/download/win. Egy\u00e9b oper\u00e1ci\u00f3s rendszerek eset\u00e9n pedig innen \u00e9rdemes indulni: https://git-scm.com/downloads.

                    Git Credential Manager telep\u00edt\u00e9se

                    A GitHub m\u00e1r egy ideje nem t\u00e1mogatja az egyszer\u0171 felhaszn\u00e1l\u00f3n\u00e9v/jelsz\u00f3 alap\u00fa hiteles\u00edt\u00e9st. Ha git parancssorban a login sor\u00e1n \"Support for password authentication was removed.\" hiba\u00fczenetet kapunk, ez az oka. K\u00e9t megold\u00e1s is l\u00e9tezik a probl\u00e9ma megold\u00e1s\u00e1ra:

                    • A legegyszer\u0171bb megold\u00e1s a \"Git Credential Manager\" telep\u00edt\u00e9se. Ezt fel lehet telep\u00edten a git telep\u00edt\u00e9se sor\u00e1n (csak be kell pip\u00e1lni a telep\u00edt\u0151ben), de k\u00fcl\u00f6n is telep\u00edthet\u0151 innen. A telep\u00edt\u00e9st k\u00f6vet\u0151en nincs teend\u0151nk vele, a git automatikusan haszn\u00e1lni fogja, \u00e9s egy b\u00f6ng\u00e9sz\u0151 alap\u00fa (\u00fan. OAuth) hiteles\u00edt\u00e9si folyamaton vezeti v\u00e9gig a felhaszn\u00e1l\u00f3t, ill. plusz k\u00e9nyelmi funkci\u00f3k\u00e9nt meg is jegyzi a hiteles\u00edt\u00e9si adatokat.
                    • A m\u00e1sik megold\u00e1s a PAT (Personal Access Token) haszn\u00e1lata, err\u0151l pl. itt lehet olvasni.
                    "},{"location":"hazi/hf-folyamat/","title":"H\u00e1zi feladat munkafolyamat \u00e9s a Git/GitHub haszn\u00e1lata","text":"

                    Ha m\u00e9g nem olvastad, c\u00e9lszer\u0171 itt kezdeni: Git, GitHub, GitHub Classroom

                    "},{"location":"hazi/hf-folyamat/#lepesek","title":"L\u00e9p\u00e9sek","text":"

                    Az egyes h\u00e1zi feladatok kiindul\u00f3 keret\u00e9t GitHub/GitHub Classroom seg\u00edts\u00e9g\u00e9vel publik\u00e1ljuk. Az \u00edgy publik\u00e1lt h\u00e1zi feladatok kiindul\u00f3 k\u00f6rnyezet let\u00f6lt\u00e9s\u00e9nek \u00e9s a megold\u00e1s bead\u00e1s\u00e1nak l\u00e9p\u00e9sei a k\u00f6vetkez\u0151k:

                    1. Az elindul\u00e1ssal ne v\u00e1rd meg a hat\u00e1rid\u0151 k\u00f6zeledt\u00e9t, legal\u00e1bb a saj\u00e1t repository l\u00e9trehoz\u00e1s\u00e1ig juss el miel\u0151bb. \u00cdgy, ha b\u00e1rmi elakad\u00e1s lenne, m\u00e9g id\u0151ben tudunk seg\u00edteni.
                    2. Regisztr\u00e1lj egy GitHub accountot (https://github.com/), ha m\u00e9g nem regisztr\u00e1lt\u00e1l, \u00e9s l\u00e9pj be vele GitHub-ra.
                    3. A feladathoz tartoz\u00f3 linket nyisd meg. Ez minden feladathoz m\u00e1s lesz, Moodle-ben ker\u00fclnek meghirdet\u00e9sre fokozatosan a f\u00e9l\u00e9v folyam\u00e1n. A form\u00e1tuma a k\u00f6vetkez\u0151h\u00f6z hasonl\u00f3: https://classroom.github.com/abcdefgh. Ha a hivatkoz\u00e1sra kattintva hib\u00e1t kapsz (\"There was a problem authenticating with GitHub, please try again.\"), copy-paste-tel m\u00e1sold be k\u00f6zvetlen\u00fcl a b\u00f6ng\u00e9sz\u0151 c\u00edmsor\u00e1ba a c\u00edmet.
                    4. Ha k\u00e9ri, adj enged\u00e9lyt a GitHub Classroom alkalmaz\u00e1snak, hogy haszn\u00e1lja az account adataidat.
                    5. L\u00e1tni fogsz egy oldalt, ahol elfogadhatod a feladatot (\"Accept this assignment\"). Kattints a gombra.
                    6. V\u00e1rd meg, am\u00edg elk\u00e9sz\u00fcl a repository. A GitHub nem mindig friss\u00edti az oldalt mag\u00e1t\u00f3l, c\u00e9lszer\u0171 az oldal id\u0151nk\u00e9nti k\u00e9zi friss\u00edt\u00e9s\u00e9vel pr\u00f3b\u00e1lkozni (pl. F5 billenty\u0171). Ha elk\u00e9sz\u00fclt a repository, az oldal ki\u00edrja az \u00faj repository url-j\u00e9t, amin kattintva a repository-ra lehet navig\u00e1lni (ehhez hasonl\u00f3: https://github.com/bmeviauab00/hazi1-2024-username). De nem is felt\u00e9tlen sz\u00fcks\u00e9ges az url elment\u00e9se, a GitHub nyit\u00f3oldal\u00e1n (https://github.com/) baloldalt a saj\u00e1t repository-k k\u00f6zt b\u00e1rmikor meg lehet k\u00e9s\u0151bb is tal\u00e1lni.
                    7. Kl\u00f3nozd le a repository-t (ennek mik\u00e9ntj\u00e9re r\u00f6videsen visszat\u00e9r\u00fcnk). Ebben tal\u00e1lni fogsz egy keretet, vagy kiindul\u00f3 k\u00f3dot. Ezen dolgozz, ezt v\u00e1ltoztasd. Az alap\u00e9rtelmezett git \u00e1gon/branchen dolgozz (ha ez nem mond semmit, nem baj: ez csak azoknak sz\u00f3l, akik a git haszn\u00e1lat\u00e1ban j\u00e1rtasak \u00e9s t\u00f6bb \u00e1gon szoktak dolgozni).
                    8. A kiindul\u00f3 projektben van egy .github/workflows mappa, ennek tartalm\u00e1t tilos megv\u00e1ltoztatni, t\u00f6r\u00f6lni stb.
                    9. A munka sor\u00e1n a kiindul\u00e1si rep\u00f3ban lev\u0151 solutionben/projektben kell dolgozni, \u00faj projektet/solutiont ne hozz l\u00e9tre.
                    10. A repository gy\u00f6k\u00e9rmapp\u00e1j\u00e1ban tal\u00e1lhat\u00f3 neptun.txt f\u00e1jlba \u00edrd bele a Neptun k\u00f3dod, csupa nagybet\u0171vel. A f\u00e1jlban csak ez a hat karakter legyen, semmi m\u00e1s.
                    11. Oldd meg a feladatot. Pushold a hat\u00e1rid\u0151ig. Az alap\u00e9rtelmezett \"Main\" \u00e1gon kell dolgozni k\u00f6zvetlen\u00fcl, nincsenek pull requestek. Ak\u00e1rh\u00e1ny commitod lehet, a legutols\u00f3 \u00e1llapotot fogjuk n\u00e9zni.
                    12. Az eredm\u00e9nyek Moodle-ben ker\u00fclnek meghirdet\u00e9sre (a nyit\u00f3oldalon kapcsol\u00f3d\u00f3 h\u00e1zi feladat oldal\u00e1t Moodle-ben megnyitva az oldal alj\u00e1n a \"Visszajelz\u00e9s\" szekci\u00f3ban l\u00e1that\u00f3). Eredm\u00e9nyek az adott feladatra vonatkoz\u00f3 hat\u00e1rid\u0151t k\u00f6vet\u0151 p\u00e1r napon bel\u00fcl v\u00e1rhat\u00f3k.
                    13. A h\u00e1zi feladatot k\u00fcl\u00f6n explicit beadni nem kell, csak legyen fent GitHub-on hat\u00e1rid\u0151re a megold\u00e1s.
                    14. Amikor a h\u00e1zi feladatod beadottnak tekinted, c\u00e9lszer\u0171 ellen\u0151rizni a b\u00f6ng\u00e9sz\u0151ben a GitHub webes fel\u00fclet\u00e9n a repository-ban a f\u00e1jlokra val\u00f3 r\u00e1pillant\u00e1ssal, hogy val\u00f3ban minden v\u00e1ltoztat\u00e1st push-olt\u00e1l-e, \u00e9s hogy a neptun.txt val\u00f3ban ki van-e t\u00f6ltve.

                    A fenti l\u00e9p\u00e9sek kapcs\u00e1n k\u00e9t k\u00e9rd\u00e9s v\u00e1r m\u00e9g megv\u00e1laszol\u00e1sra:

                    • Hogyan kl\u00f3nozzuk (clone) a rep\u00f3nkat (mely a h\u00e1zi feladat kiindul\u00f3 keret\u00e9t tartalmazza)?
                    • Hogyan commit-\u00e1ljunk \u00e9s push-oljunk GitHub-ra?

                    Ezek nagy r\u00e9sz\u00e9t Szoftvertechnol\u00f3gia t\u00e1rgyb\u00f3l m\u00e1r tanultad a k\u00e9pz\u00e9s sor\u00e1n. De ha esetleg nem eml\u00e9kszel ennek minden r\u00e9szleteire, vagy ha szeretn\u00e9l megismerkedni azzal, hogyan tudod ezeket nem csak parancssorb\u00f3l, hanem Visual Studio-b\u00f3l haszn\u00e1lni, akkor mindenk\u00e9ppen olvasd el az al\u00e1bbiakat. R\u00f6viden mindenre kit\u00e9r\u00fcnk a git haszn\u00e1lata kapcs\u00e1n, amire a h\u00e1zi feladatok megold\u00e1sa sor\u00e1n sz\u00fcks\u00e9g lehet (m\u00e9g azok is meg tudj\u00e1k oldani a h\u00e1zi feladatot, akik git-et nem tanultak, \u00e9s \u00edgy kapcsol\u00f3dnak be a t\u00e1rgyba).

                    Amennyiben a git login sor\u00e1n \"Support for password authentication was removed\" hiba\u00fczenetet kapsz, a git telep\u00edt\u00e9s\u00e9t ismertet\u0151 oldal alj\u00e1n a Git Credential Manager-r\u0151l sz\u00f3l\u00f3 szakaszt \u00e9rdemes elolvasni.

                    "},{"location":"hazi/hf-folyamat/#github-repository-klonozasa","title":"GitHub repository kl\u00f3noz\u00e1sa","text":"

                    K\u00e9t lehet\u0151s\u00e9get n\u00e9z\u00fcnk meg al\u00e1bb:

                    • Kl\u00f3noz\u00e1s a GitHub webes fel\u00fclet\u00e9r\u0151l indulva Visual Studioban (vagy ak\u00e1r egyb\u0151l Visual Studio-b\u00f3l indulva)
                    • Kl\u00f3noz\u00e1s parancssorb\u00f3l
                    "},{"location":"hazi/hf-folyamat/#clone-a-github-webes-feluleterol-indulva-visual-studio-ban","title":"Clone a GitHub webes fel\u00fclet\u00e9r\u0151l indulva Visual Studio-ban","text":"

                    Egy (h\u00e1zi feladathoz tartoz\u00f3) repository kl\u00f3noz\u00e1sra sz\u00e1mos m\u00f3d van, egy lehet\u0151s\u00e9g a k\u00f6vetkez\u0151. Nyissuk meg az elk\u00e9sz\u00fclt repository online oldal\u00e1t, melyre t\u00f6bb m\u00f3don eljuthatunk. Lehet\u0151s\u00e9gek pl.:

                    • A repo l\u00e9trehoz\u00e1sakor megjelenik a GitHub fel\u00fcleten az url, csak kattintani kell rajta.
                    • A GitHub nyit\u00f3oldalon (https://github.com) - ha be vagyunk l\u00e9pve - list\u00e1z\u00f3dnak baloldalt azon repository-k, melyekhez van hozz\u00e1f\u00e9r\u00e9s\u00fcnk, csak kattintsunk a megfelel\u0151n.
                    • Amikor elk\u00e9sz\u00fcl a rep\u00f3nk (a GitHub classroom feladat elfogad\u00e1sa sor\u00e1n), e-mail \u00e9rtes\u00edt\u00e9st is kapunk r\u00f3la, ebben is megtal\u00e1lhat\u00f3 a link.

                    Az oldal k\u00e9pe nagyj\u00e1b\u00f3l megfelel a k\u00f6vetkez\u0151nek (az mindenk\u00e9ppen k\u00fcl\u00f6nbs\u00e9g, hogy a rep\u00f3 url v\u00e9g\u00e9n mindenkin\u00e9l a saj\u00e1t felhaszn\u00e1l\u00f3neve szerepel):

                    Kattintsunk a z\u00f6ld sz\u00edn\u0171 Code gombon, majd a leny\u00edl\u00f3 men\u00fcben az \"Open in Visual Studio\" linkre:

                    A b\u00f6ng\u00e9sz\u0151nk ekkor j\u00f3 es\u00e9llyel feldob egy ablakot (pl. a Chrome/Edge eset\u00e9ben a c\u00edmsor alatt) melyben egy k\u00fcl\u00f6n gombkattint\u00e1ssal (Open\u2026) tudjuk ind\u00edtani a Visual Studio-t. A felk\u00edn\u00e1lt lehet\u0151s\u00e9gnek lehet, kiss\u00e9 fura a neve, ha \"Microsoft Visual Studio Web Protocol Handler Selector\" n\u00e9ven hivatkozik r\u00e1, v\u00e1lasszuk/enged\u00e9lyezz\u00fck ki b\u00e1tran. Illetve, itt c\u00e9lszer\u0171 az \"Always allow github.com to open links ...\" vagy hasonl\u00f3 sz\u00f6veg\u0171 jel\u00f6l\u0151n\u00e9gyzetet is pip\u00e1lni. Ha minden j\u00f3l megy, a Visual Studio elindul, \u00e9s indul\u00e1s ut\u00e1n feldob egy ablakot, melyben a \"Repository location\" ki is van t\u00f6ltve a repository-nk URL-j\u00e9vel. A Path alatt adjuk meg, hogy hova szeretn\u00e9nk a h\u00e1tt\u00e9rt\u00e1runkon clone-ozni, majd kattintsunk a Clone gombra:

                    Alternat\u00edv kl\u00f3noz\u00e1si lehet\u0151s\u00e9g Visual Studioban

                    Ha nem m\u0171k\u00f6dik a b\u00f6ng\u00e9sz\u0151ablakban az \"Open in Visual Studio\" vagy \"Microsoft Visual Studio Web Protocol Handler Selector\" hivatkoz\u00e1s, akkor indulhatunk egyb\u0151l a Visual Studio-b\u00f3l is. Csak ind\u00edtsuk el a Visual Studio-t, \u00e9s a startup ablakban v\u00e1lasszuk jobboldalt a \"Clone Repository\" gombot (vagy a startup ablakot \u00e1tugorva v\u00e1lasszuk ki a \"Git/Clone Repository men\u00fct\" a VS f\u0151ablak\u00e1ban), melynek hat\u00e1s\u00e1ra a fenti ablak jelenik meg, a Repository URL-be pedig \u00edrjuk be a rep\u00f3nk URL-j\u00e9t. A Clone-ra kattintva n\u00e9h\u00e1ny m\u00e1sodperc alatt a repository a megadott c\u00e9lmapp\u00e1ba kl\u00f3noz\u00f3dik.

                    A kl\u00f3noz\u00e1st k\u00f6vet\u0151en pl. Windows Explorer-ben meg tudjuk tekinteni a l\u00e9trehozott mapp\u00e1kat \u00e9s f\u00e1jlokat:

                    Ebb\u0151l j\u00f3l l\u00e1that\u00f3, hogy egy Git repository nem m\u00e1s, mint mapp\u00e1k \u00e9s f\u00e1jlok gy\u0171jtem\u00e9nye, valamint egy a gy\u00f6k\u00e9rben tal\u00e1lhat\u00f3 .git mappa, mely (n\u00e9mi egyszer\u0171s\u00edt\u00e9ssel \u00e9lve) az egyes f\u00e1jlok verzi\u00f3t\u00f6rt\u00e9net\u00e9t tartalmazza. A munka megkezd\u00e9s\u00e9hez csak meg kell nyissuk az adott h\u00e1zi feladathoz tartoz\u00f3 .sln kiterjeszt\u00e9s\u0171 solution f\u00e1jlt (pl. duplakatt Windows Explorerben).

                    Az els\u0151 h\u00e1zi feladat speci\u00e1lis (k\u00e9t solution is van)!

                    Az els\u0151 h\u00e1zi feladat kiv\u00e9telesen k\u00e9t f\u0151 r\u00e9szb\u0151l \u00e1ll, melyekhez elt\u00e9r\u0151 solution tartozik. Az els\u0151h\u00f6z a Feladat1 mapp\u00e1ban tal\u00e1lhat\u00f3 MusicApp.sln f\u00e1jlt, a m\u00e1sodikhoz a Feladat2-ben tal\u00e1lhat\u00f3 Shapes.sln-t kell megnyitni. A megnyit\u00e1st megtehetj\u00fck Explorerb\u0151l, az adott .sln f\u00e1jlon dupl\u00e1n kattintva. Ugyanakkor van erre m\u00e1s m\u00f3d is: amennyiben Visual Studio-ban a Git gy\u00f6k\u00e9rmapp\u00e1t nyitottuk meg (a Clone-t k\u00f6vet\u0151en is ez a helyzet \u00e1llt el\u0151) a Solution Explorer n\u00e9zet fejl\u00e9c\u00e9ben \"Switch View\" gombot lenyomva a Solution Explorer list\u00e1zza a Git gy\u00f6k\u00e9rmappa alatti solution-\u00f6ket, \u00e9s ezek b\u00e1rmelyik\u00e9n dupl\u00e1n kattintva az adott solution megny\u00edlik:

                    "},{"location":"hazi/hf-folyamat/#clone-parancssorbol","title":"Clone parancssorb\u00f3l","text":"

                    Alternat\u00edv lehet\u0151s\u00e9g a parancssor haszn\u00e1lata. Parancssorban navig\u00e1ljunk abba a mapp\u00e1ba, ahov\u00e1 a forr\u00e1sk\u00f3dot ki szeretn\u00e9nk clone-ozni, \u00e9s itt adjuk ki a k\u00f6vetkez\u0151 parancsot: git clone <repo url> , ahol a <repo url> a repositorynk c\u00edme (pl. b\u00f6ng\u00e9sz\u0151 c\u00edms\u00e1vj\u00e1b\u00f3l bem\u00e1solva, ehhez hasonl\u00f3: https://github.com/bmeviauab00/hazi1-2024-myusername). A parancs lefut\u00e1sa ut\u00e1n egy a repository nev\u00e9nek megfelel\u0151 alk\u00f6nyvt\u00e1rban tal\u00e1ljuk az \u00faj helyi rep\u00f3nkat.

                    Parancssori git

                    Ne f\u00e9lj\u00fcnk a parancssori git-et haszn\u00e1lni, egy repository clone-oz\u00e1s\u00e1nak tulajdonk\u00e9ppen ez a legegyszer\u0171bb m\u00f3dja.

                    Amennyiben a parancs futtat\u00e1sa sor\u00e1n azt tapasztaljuk, hogy a git parancsot nem ismeri fel a k\u00f6rnyezet, annak oka val\u00f3sz\u00edn\u0171leg az, hogy nem telep\u00edtett\u00fcnk m\u00e9g a parancssori gitet a g\u00e9p\u00fcnkre. Err\u0151l b\u0151vebben itt.

                    "},{"location":"hazi/hf-folyamat/#napi-git-munka-visual-studio-segitsegevel-commit-push","title":"Napi Git munka Visual Studio seg\u00edts\u00e9g\u00e9vel (commit, push)","text":"

                    Miut\u00e1n lekl\u00f3noztuk az adott h\u00e1zi feladathoz tartoz\u00f3 GitHub repository-t a sz\u00e1m\u00edt\u00f3g\u00e9p\u00fcnkre, \u00e9s ennek sor\u00e1n l\u00e9trej\u00f6tt a lok\u00e1lis Git repository-nk, a benne lev\u0151 .sln f\u00e1jlokat Visual Studioban megnyitva pont \u00fagy dolgozunk \u2013 vesz\u00fcnk fel \u00faj f\u00e1jlokat, m\u00f3dos\u00edtunk/t\u00f6rl\u00fcnk megl\u00e9v\u0151ket \u2013 mintha a f\u00e1jlok nem is tartozn\u00e1nak semmif\u00e9le Git rep\u00f3hoz. Ugyanakkor, legk\u00e9s\u0151bb a feladat bead\u00e1sakor a v\u00e1ltoztat\u00e1sainkat commit-olni kell, majd push-olni GitHub-ra. A munka sor\u00e1n ak\u00e1rh\u00e1nyszor commit-\u00e1lhatjuk/push-olhatjuk az el\u0151z\u0151 commit \u00f3ta eszk\u00f6z\u00f6lt m\u00f3dos\u00edt\u00e1sainkat: a h\u00e1zi feladat ellen\u0151rz\u00e9sekor a hat\u00e1rid\u0151 pillanat\u00e1ban a GitHub-on tal\u00e1lhat\u00f3 \u00e1llapot ker\u00fcl elb\u00edr\u00e1l\u00e1sra, teljesen mindegy, h\u00e1ny commit tartozik hozz\u00e1. A commit \u00e9s push m\u0171veletek v\u00e9grehajt\u00e1s\u00e1hoz a Visual Studio \"Git\" men\u00fcj\u00e9ben lev\u0151 parancsokat haszn\u00e1ljuk.

                    "},{"location":"hazi/hf-folyamat/#commit","title":"Commit","text":"

                    Az el\u0151z\u0151 commit \u00f3ta eszk\u00f6z\u00f6lt v\u00e1ltoztat\u00e1sok megtekint\u00e9s\u00e9hez v\u00e1lasszuk ki a \"View\\Git Changes\" men\u00fct. Ennek hat\u00e1s\u00e1ra megjelenik a \"Git Changes\" n\u00e9zet a v\u00e1ltoz\u00e1sok list\u00e1j\u00e1val:

                    A v\u00e1ltoztat\u00e1sok commit-\u00e1l\u00e1s\u00e1hoz \u00edrjunk a fenti sz\u00f6vegmez\u0151be egy a v\u00e1ltoztat\u00e1sokra jellemz\u0151 egy-k\u00e9t soros le\u00edr\u00e1st (pl. \"V\u00e9gs\u0151 megold\u00e1s\", \"Az xyz hiba jav\u00edt\u00e1sa\" stb.). A lehet\u0151s\u00e9geink ezt k\u00f6vet\u0151en a k\u00f6vetkez\u0151k:

                    • \"Commit All\" gomb: Csak helyben commit-olja a v\u00e1ltoztat\u00e1sokat (a k\u00f6zponti Git rep\u00f3ban mindaddig nem jelenik meg a commit, am\u00edg egy k\u00fcl\u00f6n Push paranccsal fel nem \"toljuk\").
                    • \"Commit All and Push\", mely a \"Commit All\" gomb melletti ny\u00edl lenyit\u00e1s\u00e1val \u00e9rhet\u0151 el. Hat\u00e1sa: commit, majd ut\u00e1na push. Ha a v\u00e1ltoztat\u00e1sainkat egyb\u0151l publik\u00e1lni is szeretn\u00e9nk a GitHub-on lev\u0151 k\u00f6zponti rep\u00f3ba, akkor haszn\u00e1ljuk b\u00e1tran parancsot. A h\u00e1zi feladatok tekintet\u00e9ben c\u00e9lszer\u0171 is ezt haszn\u00e1lni, mert ekkor nincs sz\u00fcks\u00e9g a commit-ot k\u00f6vet\u0151en k\u00fcl\u00f6n push m\u0171veletre. Megjegyz\u00e9s: ha a parancs az \"Unable to push to the remote repository because your local branch is behind the remote branch\" hib\u00e1val z\u00e1rul, el\u0151bb pull-oljuk, majd ism\u00e9telj\u00fck meg a push-t. Erre m\u00e9g al\u00e1bb visszat\u00e9r\u00fcnk.
                    • \"Commit All and Sync\", mely a \"Commit All\" gomb melletti ny\u00edl lenyit\u00e1s\u00e1val \u00e9rhet\u0151 el. Hat\u00e1sa: commit ut\u00e1n pull (leszedi a saj\u00e1t helyi rep\u00f3nkba m\u00e1sok esetleges v\u00e1ltoztat\u00e1sait a k\u00f6zponti rep\u00f3b\u00f3l), majd push. \u00cdgy a k\u00f6zponti rep\u00f3ban lev\u0151 esetleges v\u00e1ltoz\u00e1sokat lehozza a helyi rep\u00f3nkba, az ezt k\u00f6vet\u0151en a v\u00e1ltoztat\u00e1sainkat egyb\u0151l publik\u00e1lja is ide.

                    Note

                    A git commit-ot mindig meg kell el\u0151zze egy \u00fan. stage l\u00e9p\u00e9s, mely sor\u00e1n kiv\u00e1lasztjuk azokat a helyi v\u00e1ltoztat\u00e1sokat, melyeket a k\u00f6vetkez\u0151 commit-ba be k\u00edv\u00e1nunk tenni. Ez az \u00fan. staging area ter\u00fcletre teszi az \u00e1ltalunk kiv\u00e1lasztott v\u00e1ltoz\u00e1sokat (a f\u00e1jlrendszerben nem mozgat semmif\u00e9le f\u00e1jlt, ez csak a git a bels\u0151 nyilv\u00e1ntart\u00e1s\u00e1ban jelenik meg). Ez az\u00e9rt j\u00f3, mert plusz rugalmass\u00e1got biztos\u00edt, hiszen nem biztos, mindig minden v\u00e1ltoztat\u00e1st bele k\u00edv\u00e1nunk tenni a k\u00f6vetkez\u0151 commit-ba. A fenti \"Commit all\" stb. parancsok nev\u00e9ben nem v\u00e9letlen van benne az \"all\": ezek a sz\u00ednfalak m\u00f6g\u00f6tt a commit el\u0151tt egy megfelel\u0151 git paranccsal valamennyi v\u00e1ltoz\u00e1st a git staging area-ra tesznek, \u00edgy ezt nek\u00fcnk nem kell k\u00fcl\u00f6n megtenn\u00fcnk.

                    "},{"location":"hazi/hf-folyamat/#push-pull","title":"Push, Pull","text":"

                    A commit m\u0171velet csak a helyi repository-ban \"\u00e9rv\u00e9nyes\u00edti\" a v\u00e1ltoztat\u00e1sokat. Ezt k\u00f6vet\u0151en a v\u00e1ltoztat\u00e1sokat a GitHub k\u00f6zponti repository-nkba fel kell t\u00f6lteni a push m\u0171velettel. Erre a l\u00e9p\u00e9sre csak akkor van sz\u00fcks\u00e9g, ha a commit sor\u00e1n nem haszn\u00e1ltuk a \"Commit All and Push\" vagy \"Commit All and Sync\" parancsokat. A push m\u0171velet VS-ben a \"Git/Push\" men\u00fc seg\u00edt\u00e9s\u00e9vel ind\u00edthat\u00f3. Ha t\u00f6bben dolgozunk, a k\u00f6zponti repository-ban lehetnek m\u00e1sok \u00e1ltal pusholt, hozz\u00e1nk m\u00e9g le nem t\u00f6lt\u00f6tt commitok (vagy ak\u00e1r olyanok, melyeket mi magunk push-oltunk egy m\u00e1sik lok\u00e1lis clone-b\u00f3l, vagy ha a GitHub online fel\u00fclet\u00e9n eszk\u00f6z\u00f6lt\u00fcnk a k\u00f3don v\u00e1ltoz\u00e1sokat). Ezeket a pull m\u0171velettel tudjuk a helyi rep\u00f3nkba merge-elni (Git/Pull men\u00fc). A h\u00e1zi feladat vonatkoz\u00e1s\u00e1ban ezt nem haszn\u00e1ljuk, hiszen mindenki saj\u00e1t dedik\u00e1lt k\u00f6zponti repositoryval rendelkezik, melyben egyed\u00fcl dolgozik (kiv\u00e9ve, ha esetleg valaki a GitHub fel\u00fclet\u00e9nek seg\u00edts\u00e9g\u00e9vel v\u00e1ltoztatott a k\u00f3don, akkor ezt egy pull-lal tudja a helyi rep\u00f3j\u00e1ba lehozni).

                    Note

                    A push csak akkor hajthat\u00f3 v\u00e9gre, ha a k\u00f6zponti rep\u00f3ban nincs olyan v\u00e1ltoz\u00e1s, melyet m\u00e9g a pull paranccsal nem hoztunk le \u00e9s merge-elt\u00fcnk a saj\u00e1t lok\u00e1lis rep\u00f3nkba. Ha ez nincs \u00edgy, egy ehhez hasonl\u00f3 hiba\u00fczenet kapunk: \"Unable to push to the remote repository because your local branch is behind the remote branch\". Ekkor pull-oljunk, ut\u00e1na ism\u00e9telj\u00fck meg a pusht.

                    Note

                    A pull m\u0171velet csak akkor hajthat\u00f3 v\u00e9gre, ha nincs olyan v\u00e1ltoztat\u00e1sunk helyben, melyeket m\u00e9g nem commit\u00e1ltunk. Ha van ilyen, akkor azokat vagy commit\u00e1ljuk (vagy ha ezt nem akarjuk megtenni m\u00e9g, akkor stash-elj\u00fck a pull idej\u00e9re).

                    Tip

                    A Pull \u00e9s Push parancsok a \u201eGit Changes\u201d (View/Git Changes men\u00fc jelen\u00edti meg) panel tetej\u00e9n el\u00e9rhet\u0151 le \u00e9s fel nyilakkal is v\u00e9grehajthat\u00f3k:

                    "},{"location":"hazi/hf-folyamat/#git-history","title":"Git history","text":"

                    A Git egy v\u00e1ltoz\u00e1sk\u00f6vet\u0151 rendszer. A v\u00e1ltoz\u00e1s egys\u00e9ge a commit (melyben tetsz\u0151leges sz\u00e1m\u00fa f\u00e1jlt \u00e9rint\u0151 v\u00e1ltoz\u00e1s lehet), a Git historyban a commitok egym\u00e1sut\u00e1nis\u00e1g\u00e1t l\u00e1thatjuk. A f\u00e1jlokat \u00e9rint\u0151 v\u00e1ltoz\u00e1sokon t\u00falmen\u0151en minden commithoz tartozik egy egyedi azonos\u00edt\u00f3 (commit hash), id\u0151b\u00e9lyeg, illetve egy szerz\u0151. A szerz\u0151 felhaszn\u00e1l\u00f3, aki a v\u00e1ltoz\u00e1sokat eszk\u00f6z\u00f6lte (val\u00f3j\u00e1ban van k\u00fcl\u00f6n Author \u00e9s Commiter, de a kett\u0151 \u00e1ltal\u00e1ban ugyanaz). Visual Studioban a historyt a View/Git Repository men\u00fcvel tudjuk megjelen\u00edteni, de a history term\u00e9szetesen a GitHub online fel\u00fclet\u00e9n is megjelen\u00edthet\u0151. A Visual Studioban a \"Git Repository\" n\u00e9zetet a View/Git Repository men\u00fcvel tudjuk megjelen\u00edteni.

                    • Outgoing commits: Megmutatja, hogy milyen, a lok\u00e1lis repository-nkba m\u00e1r l\u00e9tez\u0151, de a k\u00f6zponti rep\u00f3ba m\u00e9g nem push-olt commitok vannak. Ezeket a Push m\u0171velettel tudjuk felt\u00f6lteni.
                    • Incoming commits: Megmutatja, hogy a k\u00f6zponti repository-ban milyen m\u00e1sok \u00e1ltal pusholt, hozz\u00e1nk m\u00e9g le nem t\u00f6lt\u00f6tt commitok vannak. Ezek akkor jelennek meg, ha a Fetch paranccsal lehozzuk a helyi rep\u00f3ba (ez m\u00e9g nem merge-el). Ezeket a Pull m\u0171velettel tudjuk a helyi rep\u00f3nkba merge-elni. A fetch parancsot ritk\u00e1n haszn\u00e1ljuk: \u00e1ltal\u00e1ban a pullt haszn\u00e1ljuk mag\u00e1ban, ami egy fetch + merge (v\u00e1ltoz\u00e1sok merge-el\u00e9se a helyi rep\u00f3ba) kombin\u00e1ci\u00f3ja.

                    P\u00e9lda:

                    Tip

                    Am\u00edg nem vagyunk rutinosak a Visual Studio Git szolg\u00e1ltat\u00e1sainak haszn\u00e1lat\u00e1ban, a push-t k\u00f6vet\u0151en (legk\u00e9s\u0151bb akkor, amikor a h\u00e1zi feladatot beadottnak tekintj\u00fck) c\u00e9lszer\u0171 ellen\u0151rizni a GitHub webes fel\u00fclet\u00e9n a repository-ban a f\u00e1jlokra val\u00f3 r\u00e1pillant\u00e1ssal, hogy val\u00f3ban minden v\u00e1ltoztat\u00e1st felt\u00f6lt\u00f6tt\u00fcnk-e.

                    "},{"location":"hazi/hf-folyamat/#egyeb-iranyelvek","title":"Egy\u00e9b ir\u00e1nyelvek","text":"

                    A Git commit \u00e9s push sor\u00e1n megfigyelhetj\u00fck, hogy a solution-jeink k\u00f6ztes \u00e9s kimeneti \u00e1llom\u00e1nyai (.dll, .exe stb. f\u00e1jlok) nem ker\u00fclnek bele a commitba, \u00e9s \u00edgy nem ker\u00fclnek fel GitHubra sem. Ez \u00edgy is van j\u00f3l, ezen \u00e1llom\u00e1nyok b\u00e1rmikor reproduk\u00e1lhat\u00f3k, a verzi\u00f3kezel\u0151 rendszernek nem feladata ezek t\u00e1rol\u00e1sa, csak felesleges \u00e9s zavar\u00f3 helyfoglal\u00f3k lenn\u00e9nek. Felmer\u00fcl a k\u00e9rd\u00e9s, honnan tudja a Git, hogy mely \u00e1llom\u00e1nyokat sz\u00fcks\u00e9ges figyelmen k\u00edv\u00fcl hagyni a commit sor\u00e1n. Erre szolg\u00e1l a repository-ban (tipikusan annak gy\u00f6k\u00e9rmapp\u00e1j\u00e1ban) tal\u00e1lhat\u00f3 .gitignore f\u00e1jl, mely felsorolja azon mapp\u00e1kat, f\u00e1jlkiterjeszt\u00e9seket, illetve egyedi f\u00e1jlokat, melyeket a commit sor\u00e1n figyelmen k\u00edv\u00fcl szeretn\u00e9nk hagyni. A .gitignore f\u00e1jl tartalma teljes eg\u00e9sz\u00e9ben a kez\u00fcnk al\u00e1 tartozik, szabadon szerkeszthet\u0151/commit\u00e1lhat\u00f3/pusholhat\u00f3. A t\u00e1rgy keret\u00e9ben minden kiindul\u00f3 rep\u00f3nak r\u00e9sze egy .gitignore f\u00e1jl, ne v\u00e1ltoztassuk a tartalm\u00e1t! \u00cdgy a commit/push sor\u00e1n a kimeneti \u00e1llom\u00e1nyok a h\u00e1zi feladatok eset\u00e9ben sem ker\u00fclnek fel GitHub-ra, \u00e9s egy \u00edgy is van rendj\u00e9n.

                    A f\u00e9l\u00e9vben a feladatok megold\u00e1sa sor\u00e1n az egyes oszt\u00e1lyok, interf\u00e9szek stb. forr\u00e1sk\u00f3dj\u00e1t k\u00fcl\u00f6n f\u00e1jlba kell tenni, vagyis egy C# forr\u00e1sf\u00e1jlban egy oszt\u00e1ly/interf\u00e9sz/stb. defin\u00edci\u00f3ja legyen.

                    "},{"location":"hazi/hf-folyamat/#git-hasznalata-parancssorbol","title":"Git haszn\u00e1lata parancssorb\u00f3l","text":"

                    B\u00e1r sokan \u00f3dzkodnak a git parancssori alkalmaz\u00e1s\u00e1t\u00f3l, az egyszer\u0171bb m\u0171veleteket gyakran gyorsabban v\u00e9gre tudjuk hajtani parancssorb\u00f3l, mint a GUI fel\u00fcleteken t\u00f6rt\u00e9n\u0151 kattintgat\u00e1sokkal. Az al\u00e1bbiakban egy egyszer\u0171 l\u00e9p\u00e9ssorozattal illusztr\u00e1ljuk ezt. Ezeket a t\u00e1rgy keret\u00e9ben nem kell tudni, de hosszabb t\u00e1von mindenk\u00e9ppen hasznos (\u00e9s az ipar\u00e1gban elv\u00e1r\u00e1s is) az ismeret\u00fck.

                    1. Repository clone (ezt csak egyszer)

                      git clone https://github.com/bmeviauab00/hazi1-2022-myusername

                    2. V\u00e1ltoztat\u00e1sok v\u00e9grehajt\u00e1sa a helyi rep\u00f3ban (f\u00e1jlrendszerben, fejleszt\u0151eszk\u00f6zben).

                    3. V\u00e1ltoztat\u00e1sok megtekint\u00e9se, mutatja melyek az \u00faj/t\u00f6r\u00f6lt/m\u00f3dosult f\u00e1jlok (nem k\u00f6telez\u0151, csak ha k\u00edv\u00e1ncsiak vagyunk r\u00e1)*

                      git status

                    4. Minden v\u00e1ltoztat\u00e1s felt\u00e9tele a staging area-ra

                      git add -A

                      Ha ezt k\u00f6vet\u0151en ism\u00e9t kiadjuk git status parancsot (nem k\u00f6telez\u0151), l\u00e1tjuk, hogy minden v\u00e1ltoz\u00e1s stage-elve van.

                    5. Commit

                      git commit -m \"megjegyz\u00e9s a commithoz\"

                    6. Push

                      git push

                    Megjegyz\u00e9sek:

                    • Ha t\u00f6bben is dolgozunk az adott git \u00e1gon, akkor a 6. push el\u0151tt sz\u00fcks\u00e9g lehet/van egy git pull-ra, hogy m\u00e1sok v\u00e1ltoztat\u00e1sai megjelenjenek a mi helyi rep\u00f3nkban (en\u00e9lk\u00fcl nem fogunk tudni push-olni). A pull-nak c\u00e9lszer\u0171 lehet megadni a --rebase opci\u00f3t is, hogy ne sz\u00fclessen a merge-hez egy plusz merge commit, ennek magyar\u00e1zat\u00e1ra itt nem t\u00e9r\u00fcnk ki.
                    • Mint kor\u00e1bban eml\u00edtett\u00fck, a commit sor\u00e1n a commithoz hozz\u00e1rendel\u0151dik egy felhaszn\u00e1l\u00f3n\u00e9v \u00e9s e-mail c\u00edm. Ha ezek nincsenek a git sz\u00e1m\u00e1ra bekonfigur\u00e1lva, akkor a git a commit sor\u00e1n ezt hiba\u00fczenetben jelzi. Ekkor az al\u00e1bbi parancsokkal - \u00e9rtelemszer\u0171en a saj\u00e1t felhaszn\u00e1l\u00f3nev\u00fcnket \u00e9s e-mail c\u00edm\u00fcnket megadva - tudjuk ezeket a git glob\u00e1lis konfigur\u00e1ci\u00f3j\u00e1ban be\u00e1ll\u00edtani (ezt csak egyszer kell megtenni):

                      git config --global user.email \"you@example.com\"\ngit config --global user.name \"myusername\"\n
                    • Windows parancssorban \u00f6sszevonhatunk t\u00f6bb parancsot is egy sorba, pl. egy l\u00e9p\u00e9sben minden v\u00e1ltoz\u00e1sra stage/commit/push:

                      git add -A & git commit -m \"All tests run\" & git push

                      Powershell haszn\u00e1latakor a & helyett ;-t kell szepar\u00e1tork\u00e9nt haszn\u00e1lni.

                    "},{"location":"hazi/imsc-liftsystem/","title":"IMSc. HF - Liftrendszer","text":""},{"location":"hazi/imsc-liftsystem/#bevezetes","title":"Bevezet\u00e9s","text":"

                    A h\u00e1zi feladatban egy konzol alap\u00fa alkalmaz\u00e1st kell elk\u00e9sz\u00edteni, mely egy liftrendszert szimul\u00e1l, az Observer tervez\u00e9si mint\u00e1ra \u00e9p\u00edtve.

                    A megval\u00f3s\u00edt\u00e1s\u00e9rt 7 IMSc pont szerezhet\u0151.

                    A h\u00e1zi feladat az Observer \u00e9s Adapter tervez\u00e9si minta ismeret\u00e9re \u00e9p\u00edt (l\u00e1sd kapcsol\u00f3d\u00f3 tervez\u00e9si mint\u00e1k el\u0151ad\u00e1sanyag).

                    A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s. Ez egy konzolos alkalmaz\u00e1s, ak\u00e1r Linux/Mac k\u00f6rnyezetben is megval\u00f3s\u00edthat\u00f3.

                    "},{"location":"hazi/imsc-liftsystem/#a-beadas-menete","title":"A bead\u00e1s menete","text":"
                    • Az alapfolyamat megegyezik a kor\u00e1bbiakkal. GitHub Classroom seg\u00edts\u00e9g\u00e9vel hozz l\u00e9tre magadnak egy repository-t. A megh\u00edv\u00f3 URL-t Moodle-ben tal\u00e1lod (a t\u00e1rgy nyit\u00f3oldal\u00e1n a \"GitHub classroom hivatkoz\u00e1sok a h\u00e1zi feladatokhoz\" hivatkoz\u00e1sra kattintva megjelen\u0151 oldalon l\u00e1that\u00f3). Fontos, hogy a megfelel\u0151, ezen h\u00e1zi feladathoz tartoz\u00f3 megh\u00edv\u00f3 URL-t haszn\u00e1ld (minden h\u00e1zi feladathoz m\u00e1s URL tartozik). Kl\u00f3nozd le az \u00edgy elk\u00e9sz\u00fclt repository-t. Ez tartalmazni fogja a megold\u00e1s elv\u00e1rt szerkezet\u00e9t. A feladatok elk\u00e9sz\u00edt\u00e9se ut\u00e1n commit-old \u00e9s push-old a megold\u00e1sod.
                    • Ehhez a feladathoz \u00e9rdemi el\u0151ellen\u0151rz\u0151 nem tartozik: minden push ut\u00e1n lefut ugyan, de csak a neptun.txt kit\u00f6lt\u00f6tts\u00e9g\u00e9t ellen\u0151rzi. Az \u00e9rdemi ellen\u0151rz\u00e9st a hat\u00e1rid\u0151 lej\u00e1rta ut\u00e1n a laborvezet\u0151k teszik majd meg.
                    "},{"location":"hazi/imsc-liftsystem/#1-feladat-liftrendszer-alapok","title":"1. feladat - Liftrendszer alapok","text":"

                    K\u00e9sz\u00edts egy Lift oszt\u00e1lyt, mely egy emeletes h\u00e1z felvon\u00f3j\u00e1t reprezent\u00e1lja! A k\u00f6vetkez\u0151 tagokkal rendelkezzen:

                    • Floor tulajdons\u00e1g: Aktu\u00e1lis emelet. Eg\u00e9sz \u00e9rt\u00e9k.
                    • TargetFloor tulajdons\u00e1g: C\u00e9l emelet. Eg\u00e9sz \u00e9rt\u00e9k.
                    • Stairway tulajdons\u00e1g: L\u00e9pcs\u0151h\u00e1z sz\u00e1ma, melyben a lift tal\u00e1lhat\u00f3. Eg\u00e9sz \u00e9rt\u00e9k. Egy l\u00e9pcs\u0151h\u00e1zban egy lift lehet (ezt nem kell valid\u00e1lni az alkalmaz\u00e1sban, de mindig \u00edgy haszn\u00e1ljuk).
                    • Call m\u0171velet: A lift h\u00edv\u00e1s\u00e1ra szolg\u00e1l, be\u00e1ll\u00edtja a c\u00e9lemeletet a param\u00e9terben magadott \u00e9rt\u00e9kre.
                    • Step m\u0171velet: A lift egy emelettel t\u00f6rt\u00e9n\u0151 l\u00e9ptet\u00e9s\u00e9re szolg\u00e1l (amennyiben az aktu\u00e1lis \u00e9s c\u00e9lemelet nem egyezik: ha egyezik, nem csin\u00e1l semmit). V\u00e9letlenszer\u0171 esetben - \u00e1tlagosan kb. minden 5. l\u00e9p\u00e9s sor\u00e1n - a lift ideiglenesen beragad: ez azt jelenti, hogy az adott l\u00e9p\u00e9s sor\u00e1n nem v\u00e1lt emeletet a c\u00e9lemelet ir\u00e1ny\u00e1ba.

                    K\u00e9sz\u00edts egy LiftDoor oszt\u00e1lyt, mely egy liftajt\u00f3t reprezent\u00e1l:

                    • Konstruktor param\u00e9terben lehessen megadni a lift objektumot, melyhez a lift tartozik, valamint azt, hogy az ajt\u00f3 melyik emeleten helyezkedik el.
                    • A liftajt\u00f3 kijelz\u0151je mindig a liftj\u00e9nek aktu\u00e1lis emelet\u00e9t mutatja, kiv\u00e9ve, amikor a lift az adott liftajt\u00f3 szintj\u00e9re \u00e9rkezik. Ekkor, ha ez volt a c\u00e9l\u00e1llom\u00e1s, egy 'o' jelenik meg a kijelz\u0151n (jelezve, hogy ny\u00edlik az ajt\u00f3), egy\u00e9bk\u00e9nt egy '*'.
                    • A kijelz\u0151h\u00f6z nem kell k\u00fcl\u00f6n oszt\u00e1lyt k\u00e9sz\u00edteni, a megjelen\u00edt\u00e9s\u00e9rt a LiftDoor oszt\u00e1ly felel.
                    • Egy adott lifthez tartoz\u00f3 ajt\u00f3k adatai egy oszlopban, egym\u00e1s alatt (emelet sorrendj\u00e9ben) jelenjenek meg. Az 1. oszlopban az 1. l\u00e9pcs\u0151h\u00e1z, 2. oszlopban a 2. l\u00e9pcs\u0151h\u00e1z stb. lift/liftajt\u00f3 adatok jelenjenek meg. Az oszlopok 20-as karaktersz\u00e9less\u00e9g\u0171ek, \u00edgy az 1. oszlop a 20-as, a 2. oszlop a 40-es stb. karakterpoz\u00edci\u00f3ban kezd\u0151dik. Az al\u00e1bbi \u00e1bra illusztr\u00e1lja az elrendez\u00e9st k\u00e9t lift eset\u00e9re (1. lift az els\u0151 l\u00e9pcs\u0151h\u00e1zban, 2. lift a 2. l\u00e9pcs\u0151h\u00e1zban tal\u00e1lhat\u00f3):

                      Az \u00e1bra azt is illusztr\u00e1lja, hogy a liftajt\u00f3knak milyen form\u00e1ban kell a kimenetet megjelen\u00edteni (emelet ut\u00e1n kett\u0151spont, majd [ ] k\u00f6z\u00f6tt a kijelz\u0151 \u00e9rt\u00e9ke).

                    • A konzolra \u00edr\u00e1s sor\u00e1n a Console.SetCursorPosition m\u0171veletet \u00e9rdemes haszn\u00e1lni az \u00edr\u00e1si poz\u00edci\u00f3 be\u00e1ll\u00edt\u00e1s\u00e1ra.

                    • Egyszer\u0171s\u00edt\u00e9s: a Lift oszt\u00e1lynak nem kell tudnia, hogy h\u00e1ny szint tartozik hozz\u00e1, \u00edgy nem sz\u00fcks\u00e9ges erre vonatkoz\u00f3 valid\u00e1ci\u00f3kat sem megval\u00f3s\u00edtani.
                    • Kulcsfontoss\u00e1g\u00fa, hogy a Lift oszt\u00e1ly nem tudhatja, milyen m\u00e1s oszt\u00e1lyok \u00e9p\u00edtenek az \u00e1llapot\u00e1ra. Pl. eset\u00fcnkben egyel\u0151re a LiftDoor ilyen (k\u00e9s\u0151bb lesz m\u00e1s is). Vagyis a rendszernek k\u00f6nnyen b\u0151v\u00edthet\u0151nek kell lenni m\u00e1s oszt\u00e1lyokkal, melyek a Lift m\u0171k\u00f6d\u00e9s\u00e9t\u0151l/\u00e1llapot\u00e1t\u00f3l f\u00fcggenek, \u00faj ilyen oszt\u00e1ly bevezet\u00e9sekor a Lift oszt\u00e1lyt nem szabad a k\u00e9s\u0151bbiekben m\u00f3dos\u00edtani. Ennek megfelel\u0151en a Lift - LiftDoor viszony\u00e1t az Observer mint\u00e1ra kell \u00e9p\u00edteni.
                    • A j\u00f6v\u0151ben a tov\u00e1bbfejleszt\u00e9s sor\u00e1n lehetnek m\u00e1s Subject oszt\u00e1lyok is, ez\u00e9rt be kell vezetni egy Subject \u0151soszt\u00e1lyt a k\u00f3dduplik\u00e1ci\u00f3 elker\u00fcl\u00e9s\u00e9re (de a h\u00e1zi feladatban csak egy subject lesz).
                    • A megold\u00e1s NEM \u00e9p\u00edthet .NET event-ekre (ugyanezen oszt\u00e1lyokkal/interf\u00e9szekkel pl. Java nyelven is megval\u00f3s\u00edthat\u00f3nak kell lennie).

                    A liftrendszer konfigur\u00e1ci\u00f3 \u00f6ssze\u00e1ll\u00edt\u00e1s\u00e9rt \u00e9s a szimul\u00e1ci\u00f3 futtat\u00e1s\u00e1\u00e9rt egy LiftSystemModel oszt\u00e1ly legyen a felel\u0151s. Ennek forr\u00e1sk\u00f3dj\u00e1t al\u00e1bb megadjuk, ebb\u0151l kell egy p\u00e9ld\u00e1nyt a Main f\u00fcggv\u00e9nyben l\u00e9trehozni, \u00e9s a Run f\u00fcggv\u00e9ny\u00e9t megh\u00edvni:

                    class LiftSystemModel\n{\n    int iterationCount = 0;\n\n    Lift lift1 = new() { Stairway = 1 };\n    Lift lift2 = new() { Stairway = 2 };\n\n    public LiftSystemModel()\n    {\n        var a1 = new LiftDoor(1, lift1);\n        var a2 = new LiftDoor(2, lift1);\n        var a3 = new LiftDoor(3, lift1);\n        var a4 = new LiftDoor(4, lift1);\n        var a5 = new LiftDoor(5, lift1);\n\n        var b1 = new LiftDoor(1, lift2);\n        var b2 = new LiftDoor(2, lift2);\n        var b3 = new LiftDoor(3, lift2);\n        var b4 = new LiftDoor(4, lift2);\n        var b5 = new LiftDoor(5, lift2);\n    }\n\n    public void Run()\n    {\n        while (true)\n        {\n            Step();\n            Thread.Sleep(1000);\n            iterationCount++;\n        }\n    }\n\n    private void Step()\n    {\n        lift1.Step();\n        lift2.Step();\n\n        if (iterationCount == 0)\n            lift1.Call(5);\n        if (iterationCount == 2)\n            lift2.Call(5);\n\n        if (iterationCount == 6)\n            lift1.Call(1);\n        if (iterationCount == 9)\n            lift2.Call(1);\n    }\n\n}\n

                    A fenti k\u00f3d r\u00f6vid magyar\u00e1zata:

                    • A modell k\u00e9t liftet tartalmaz, az egyik az 1., a m\u00e1sik a 2. l\u00e9pcs\u0151h\u00e1zban tal\u00e1lhat\u00f3.
                    • A konstruktorban l\u00e9trehozzuk a k\u00e9t lifthez az egyes emeleteken tal\u00e1lhat\u00f3 ajt\u00f3kat (mindk\u00e9t l\u00e9pcs\u0151h\u00e1z 5 emeletes).
                    • A Run egy v\u00e9gtelen ciklusban futtatja a szimul\u00e1ci\u00f3t. A Step m\u0171veletben l\u00e9ptet, v\u00e1r egy m\u00e1sodpercet, majd megn\u00f6veli az aktu\u00e1lis iter\u00e1ci\u00f3sz\u00e1mot.
                    • A Step h\u00edv\u00f3dik minden iter\u00e1ci\u00f3ban. Ebben l\u00e9ptetj\u00fck mindk\u00e9t liftet, \u00e9s bizonyos iter\u00e1ci\u00f3kban h\u00edvjuk a k\u00e9t liftet az 5. illetve 1. emeletre.

                    A k\u00f6vetkez\u0151 mozg\u00f3k\u00e9p illusztr\u00e1lja a m\u0171k\u00f6d\u00e9st:

                    "},{"location":"hazi/imsc-liftsystem/#2-feladat-liftcontroller-osztaly-bevezetese","title":"2. Feladat - LiftController oszt\u00e1ly bevezet\u00e9se","text":"

                    K\u00e9sz\u00edts el egy LiftController oszt\u00e1lyt, mely egy adott liftre vonatkoz\u00f3an folyamatosan meg tudja jelen\u00edteni, mely szinten van \u00e9s mely szintre h\u00edvt\u00e1k utolj\u00e1ra (ez a k\u00f6zponti vez\u00e9rl\u0151terem sz\u00e1m\u00e1ra hasznos).

                    • Konstruktor param\u00e9terben lehessen a lift objektumot megadni, melyhez a LiftController tartozik.
                    • LiftSystemModel konstruktor\u00e1ban mindk\u00e9t lifthez vegy\u00fcnk fel egy-egy LiftController p\u00e9ld\u00e1nyt.
                    • Kulcsfontoss\u00e1g\u00fa, hogy a bevezet\u00e9se sor\u00e1n NE kelljen a Lift oszt\u00e1lyt m\u00f3dos\u00edtani (az Observer mint\u00e1nak k\u00f6sz\u00f6nhet\u0151en).
                    • LiftController-ek a hozz\u00e1juk tartoz\u00f3 lift oszlop\u00e1ban a liftajt\u00f3k alatt jelen\u00edts\u00e9k meg egy \"->\" el\u0151tt az aktu\u00e1lis, ut\u00e1na pedig a c\u00e9l emeletet.

                    A megold\u00e1s illusztr\u00e1l\u00e1sa:

                    "},{"location":"hazi/imsc-liftsystem/#3-feladat-meglevo-liftmonitor-osztaly-beillesztese","title":"3. feladat - Megl\u00e9v\u0151 LiftMonitor oszt\u00e1ly beilleszt\u00e9se","text":"

                    A feladat a liftek m\u0171k\u00f6d\u00e9si st\u00e1tusz\u00e1r\u00f3l inform\u00e1ci\u00f3 megjelen\u00edt\u00e9se. Eml\u00e9kezz\u00fcnk: a liftek v\u00e9letlenszer\u0171 id\u0151k\u00f6z\u00f6nk\u00e9nt elakadnak, mint ahogy a kor\u00e1bbi le\u00edr\u00e1sban szerepelt! Minden id\u0151pillanatban tudni szeretn\u00e9nk, hogy egy lift m\u0171k\u00f6dik (st\u00e1tusza \"OK\"), vagy el van akadva (st\u00e1tusza \"stuck\"). Ehhez rendelkez\u00e9sre is \u00e1ll az al\u00e1bbi oszt\u00e1ly:

                    class LiftMonitor\n{\n    int prevFloor;\n    bool isPrevFloorInitialized;\n\n    public void CheckLift(Lift lift)\n    {\n        Console.SetCursorPosition(lift.Stairway * 20, 13);\n        if (lift.Floor == prevFloor && isPrevFloorInitialized)\n        {\n            Console.Write($\"LiftMonitor: stuck!\");\n        }\n        else\n            Console.Write($\"LiftMonitor: OK    \");\n\n        prevFloor = lift.Floor;\n        isPrevFloorInitialized = true;\n    }\n}\n

                    Vegy\u00fck fel a fenti oszt\u00e1lyt!

                    Ett\u0151l a pillanatt\u00f3l feltessz\u00fck, hogy a fenti oszt\u00e1lyt egy k\u00f6nyvt\u00e1r form\u00e1j\u00e1ban kaptuk meg, \u00edgy forr\u00e1sk\u00f3dja nem m\u00f3dos\u00edthat\u00f3!

                    Illessz\u00fck be az Adapter minta seg\u00edts\u00e9g\u00e9vel a fenti oszt\u00e1lyt a megold\u00e1sunkba:

                    • A LiftMonitor oszt\u00e1ly nem m\u00f3dos\u00edthat\u00f3!
                    • Kulcsfontoss\u00e1g\u00fa, hogy a beilleszt\u00e9se sor\u00e1n NE kelljen a Lift oszt\u00e1lyt m\u00f3dos\u00edtani (az Observer mint\u00e1nak k\u00f6sz\u00f6nhet\u0151en). Tipp: a Lift akkor is kell \u00e9rtes\u00edtse a megfigyel\u0151it, ha beragad\u00e1s miatt nem v\u00e1ltott szintet, m\u00e1sk\u00fcl\u00f6nben a LiftMonitor nem tudja detekt\u00e1lni a beragad\u00e1st.
                    • LiftSystemModel konstruktor\u00e1ban mindk\u00e9t lifthez vegy\u00fcnk fel egy-egy monitoroz\u00e1st megval\u00f3s\u00edt\u00f3 objektumot.
                    • Csak Object Adapter alap\u00fa megold\u00e1s fogadhat\u00f3 el (Class Adapter nem).

                    A megold\u00e1s m\u0171k\u00f6d\u00e9s\u00e9nek illusztr\u00e1l\u00e1sa:

                    "},{"location":"labor/1-model-es-kod-kapcsolata/","title":"1. A modell \u00e9s a k\u00f3d kapcsolata","text":""},{"location":"labor/1-model-es-kod-kapcsolata/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                    A gyakorlat c\u00e9lja:

                    • Ismerked\u00e9s a hallgat\u00f3kkal/gyakorlatvezet\u0151vel
                    • A gyakorlatokra vonatkoz\u00f3 k\u00f6vetelm\u00e9nyek pontos\u00edt\u00e1sa
                    • Elindul\u00e1s Visual Studio-val \u00e9s .NET alkalmaz\u00e1sok fejleszt\u00e9s\u00e9vel.
                    • Egy egyszer\u0171 Hello World .NET alkalmaz\u00e1s elk\u00e9sz\u00edt\u00e9se, C# alapok
                    • Az UML \u00e9s a k\u00f3d kapcsolat\u00e1nak szeml\u00e9ltet\u00e9se
                    • Az interf\u00e9sz \u00e9s az absztrakt \u0151soszt\u00e1ly alkalmaz\u00e1stechnik\u00e1ja
                    Gyakorlatvezet\u0151knek

                    B\u00e1r a hallgat\u00f3k k\u00f6z\u00f6tt biztosan vannak olyanok, akik kor\u00e1bban, a Prog2 (C++) t\u00e1rgy keret\u00e9ben vagy m\u00e1s okb\u00f3l kifoly\u00f3lag m\u00e1r haszn\u00e1lt\u00e1k a Visual Studio k\u00f6rnyezetet, szinte biztosan lesznek olyanok is, akik m\u00e9g nem haszn\u00e1lt\u00e1k, vagy m\u00e1r kev\u00e9sb\u00e9 eml\u00e9keznek r\u00e1. A c\u00e9l jelen esetben a fel\u00fclettel val\u00f3 ismerked\u00e9s, ez\u00e9rt a feladatok megold\u00e1sa sor\u00e1n folyamatosan ismertess\u00fck a haszn\u00e1lt dolgokat (pl. Solution Explorer, F5-futtat\u00e1s, breakpoint haszn\u00e1lat stb.), hogy elk\u00e9sz\u00edts\u00fck \u00e9let\u00fcnk els\u0151 C# alkalmaz\u00e1s\u00e1t.

                    "},{"location":"labor/1-model-es-kod-kapcsolata/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                    A gyakorlat elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                    • Visual Studio 2022

                    Visual Studio-b\u00f3l a legfrissebb verzi\u00f3t c\u00e9lszer\u0171 feltenni. A Community Edition, Professional \u00e9s az Enterprise verzi\u00f3 is megfelel. A Community Edition ingyenes, let\u00f6lthet\u0151 a Microsoft honlapj\u00e1r\u00f3l. A Professional fizet\u0151s, de az egyetem hallgat\u00f3i sz\u00e1m\u00e1ra ez is ingyenesen el\u00e9rhet\u0151 (https://azureforeducation.microsoft.com/devtools honlapon, az Azure Dev Tools for Teaching program keret\u00e9ben).

                    Visual Studio Class Diagram t\u00e1mogat\u00e1s

                    Jelen gyakorlat bizonyos feladatain\u00e1l (\u00e9s az els\u0151 h\u00e1zi feladat eset\u00e9ben is) a Visual Studio Class Designer t\u00e1mogat\u00e1s\u00e1t haszn\u00e1ljuk. A Visual Studio nem teszi fel minden esetben a Class Designer komponenst a telep\u00edt\u00e9s sor\u00e1n. Ha nem lehet Class Diagram-ot felvenni a Visual Studio projektbe (mert a Class Diagram nem szerepel a list\u00e1ban az Add New Item parancs sor\u00e1n megjelen\u0151 ablak list\u00e1j\u00e1ban \u2013 err\u0151l a jelen \u00fatmutat\u00f3 k\u00e9s\u0151bbi fejezet\u00e9ben b\u0151vebben), akkor a Class Diagram komponenst ut\u00f3lag kell telep\u00edteni:

                    1. Visual Studio telep\u00edt\u0151 ind\u00edt\u00e1sa (pl. a Windows Start men\u00fcben a \u201eVisual Studio Installer\u201d beg\u00e9pel\u00e9s\u00e9vel).
                    2. A megjelen\u0151 ablakban \u201eIndividual components\u201d f\u00fcl kiv\u00e1laszt\u00e1sa
                    3. A keres\u0151mez\u0151be \u201eclass designer\u201d beg\u00e9pel\u00e9se, majd gy\u0151z\u0151dj\u00fcnk meg, hogy a sz\u0171rt list\u00e1ban a \u201eClass Designer\u201d elem ki van pip\u00e1lva.

                    Amit \u00e9rdemes \u00e1tn\u00e9zned:

                    • A gyakorlathoz nem kapcsol\u00f3dik a t\u00e1rgyb\u00f3l el\u0151ad\u00e1s. Ugyanakkor a gyakorlat \u00e9p\u00edt az UML alapismeretekre, illetve az UML oszt\u00e1lydiagram \u00e9s a k\u00f3d egym\u00e1sra t\u00f6rt\u00e9n\u0151 lek\u00e9pez\u00e9s\u00e9nek alapjaira.
                    "},{"location":"labor/1-model-es-kod-kapcsolata/#gyakorlat-menete","title":"Gyakorlat menete","text":"

                    A gyakorlatvezet\u0151 a gyakorlat elej\u00e9n \u00f6sszefoglalja a gyakorlatokra vonatkoz\u00f3 k\u00f6vetelm\u00e9nyeket:

                    • A t\u00e1rgyi adatlapon ezek t\u00f6bbs\u00e9ge megtal\u00e1lhat\u00f3
                    • Az otthoni feladatokr\u00f3l inform\u00e1ci\u00f3 a t\u00e1rgy honlapj\u00e1n tal\u00e1lhat\u00f3.

                    Visual Studio fejleszt\u0151eszk\u00f6zzel, .NET alkalmaz\u00e1sokat fogunk k\u00e9sz\u00edteni C# nyelven. A C# hasonl\u00edt a Java-hoz, fokozatosan ismerj\u00fck meg a k\u00fcl\u00f6nbs\u00e9geket. A gyakorlat vezetett, gyakorlatvezet\u0151 instrukci\u00f3i alapj\u00e1n egy\u00fctt ker\u00fclnek elv\u00e9gz\u00e9sre a feladatok.

                    "},{"location":"labor/1-model-es-kod-kapcsolata/#megoldas","title":"Megold\u00e1s","text":"A k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se

                    L\u00e9nyeges, hogy a labor sor\u00e1n a laborvezet\u0151t k\u00f6vetve kell dolgozni, tilos (\u00e9s \u00e9rtelmetlen) a k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se. Ugyanakkor az ut\u00f3lagos \u00f6n\u00e1ll\u00f3 gyakorl\u00e1s sor\u00e1n hasznos lehet a k\u00e9sz megold\u00e1s \u00e1ttekint\u00e9se, \u00edgy ezt el\u00e9rhet\u0151v\u00e9 tessz\u00fck.

                    A megold\u00e1s GitHubon \u00e9rhet\u0151 el. A legegyszer\u0171bb m\u00f3d a let\u00f6lt\u00e9s\u00e9re, ha parancssorb\u00f3l a git clone utas\u00edt\u00e1ssal lekl\u00f3nozzuk a g\u00e9p\u00fcnkre:

                    git clone https://github.com/bmeviauab00/lab-modellkod-kiindulo -b megoldas

                    Ehhez telep\u00edtve kell legyen a g\u00e9pre a parancssori git, b\u0151vebb inform\u00e1ci\u00f3 itt.

                    "},{"location":"labor/1-model-es-kod-kapcsolata/#1-feladat-hello-world-net-konzol-alkalmazas-elkeszitese","title":"1. Feladat - \u201eHello world\u201d .NET konzol alkalmaz\u00e1s elk\u00e9sz\u00edt\u00e9se","text":"

                    A feladat egy olyan C# nyelv\u0171 konzol alkalmaz\u00e1s elk\u00e9sz\u00edt\u00e9se, amely a konzolra ki\u00edrja a \u201eHello world!\u201d sz\u00f6veget.

                    Az alkalmaz\u00e1st C# nyelven k\u00e9sz\u00edtj\u00fck el. A leford\u00edtott alkalmaz\u00e1s futtat\u00e1s\u00e1t a .NET runtime v\u00e9gzi. A ford\u00edt\u00e1s/futtat\u00e1s elm\u00e9leti h\u00e1tter\u00e9t, valamint a .NET alapjait az els\u0151 el\u0151ad\u00e1s ismerteti.

                    A solution \u00e9s azon bel\u00fcli projekt l\u00e9trehoz\u00e1s\u00e1nak l\u00e9p\u00e9sei Visual Studio 2022 eset\u00e9n:

                    1. \u00daj projekt var\u00e1zsl\u00f3 elind\u00edt\u00e1sa, melyre k\u00e9t m\u00f3d is van
                      • Ind\u00edt\u00f3ablak seg\u00edts\u00e9g\u00e9vel
                        1. Ind\u00edtsuk el a Visual Studio-t
                        2. A megjelen\u0151 ind\u00edt\u00f3ablak jobb oldali s\u00e1vj\u00e1ban Create new project
                      • M\u00e1r fut\u00f3 Visual Studio-ban
                        1. File / New-Project
                    2. A Create new project var\u00e1zsl\u00f3ban a Console app (\u00e9s NEM a Console app (.NET Framework) sablont v\u00e1lasszuk ki, ebb\u0151l is a C#-osat. Azt, hogy C#-os, a sablon ikonj\u00e1nak bal fels\u0151 sarka jelzi. Ha nem l\u00e1tjuk a list\u00e1ban, r\u00e1 kell keresni/sz\u0171rni. R\u00e1kereshet\u00fcnk a fels\u0151 keres\u0151s\u00e1vban a \u201econsole\u201d be\u00edr\u00e1s\u00e1val. Vagy az alatta lev\u0151 leny\u00edl\u00f3 mez\u0151k seg\u00edts\u00e9g\u00e9vel: az els\u0151ben (nyelvkiv\u00e1laszt\u00f3) \u201eC#\u201d, a harmadikban (projektt\u00edpus kiv\u00e1laszt\u00f3) \u201eConsole\u201d.

                    3. Next gomb az var\u00e1zsl\u00f3ablak alj\u00e1n, a k\u00f6vetkez\u0151 var\u00e1zsl\u00f3oldalon:

                      1. Project name: Hello World
                      2. Location: a laborokban a c:\\work\\ mapp\u00e1ba dolgozzunk, ehhez van \u00edr\u00e1si jogunk.
                      3. Solution name: Hello World (elvileg ez be is lesz \u00edrva, mire ide\u00e9r\u00fcnk)
                      4. Place solution and project in the same directory: nincs pipa (de nincs k\u00fcl\u00f6n\u00f6sebb jelent\u0151s\u00e9ge).
                      5. Next gomb az var\u00e1zsl\u00f3ablak alj\u00e1n, a k\u00f6vetkez\u0151 var\u00e1zsl\u00f3oldalon:

                        1. Framework: .NET 8 (Long-term support).
                        2. A \"Do not use top level statements\" jel\u00f6l\u0151n\u00e9gyzetet pip\u00e1ljuk be (ennek magyar\u00e1zat\u00e1ra mindj\u00e1rt visszat\u00e9r\u00fcnk).
                      6. A projekttel egy \u00faj solution is l\u00e9trej\u00f6n, mely strukt\u00far\u00e1ja a Visual Studio Solution Explorer ablak\u00e1ban tekinthet\u0151 \u00e1t. Egy solution t\u00f6bb projectb\u0151l \u00e1llhat, egy project pedig t\u00f6bb f\u00e1jlb\u00f3l. A solution a teljes munkak\u00f6rnyezetet fogja \u00f6ssze (egy .sln kiterjeszt\u00e9s\u0171 f\u00e1jl tartozik hozz\u00e1), m\u00edg egy projekt kimenete egy .exe vagy .dll f\u00e1jl jellemz\u0151en, vagyis egy \u00f6sszetett alkalmaz\u00e1s/rendszer egy komponens\u00e9t \u00e1ll\u00edtja el\u0151. A projektf\u00e1jlok kiterjeszt\u00e9se C# alkalmaz\u00e1sok eset\u00e9n .csproj.

                        A Program.cs f\u00e1jlunk tartalma a k\u00f6vetkez\u0151:

                        Program.cs
                        namespace HelloWorld\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n        }\n    }\n}\n

                        Vegy\u00fcnk fel egy Console.ReadKey() sort:

                        namespace HelloWorld\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n            Console.ReadKey();\n        }\n    }\n}\n
                        1. Futtassuk az alkalmaz\u00e1st (pl. az F5 billenty\u0171 haszn\u00e1lat\u00e1val).

                          A k\u00f3d fel\u00e9p\u00edt\u00e9se nagyon hasonl\u00edt a Java-hoz, illetve a C++-hoz. Az oszt\u00e1lyaink n\u00e9vterekbe szervezettek. N\u00e9vteret defini\u00e1lni a namespace kulcssz\u00f3val tudunk. N\u00e9vtereket hat\u00f3k\u00f6rbe \u201ehozni\u201d a using kulcssz\u00f3val tudjuk. pl.:

                          using System.Collections.Generic;\n
                        2. Egy konzolos C# alkalmaz\u00e1sban az alkalmaz\u00e1sunk bel\u00e9p\u00e9si pontj\u00e1t egy statikus Main nev\u0171 f\u00fcggv\u00e9ny meg\u00edr\u00e1s\u00e1val adjuk meg. Az oszt\u00e1lyunk neve b\u00e1rmi lehet, a VS egy Program nev\u0171 oszt\u00e1lyt gener\u00e1lt eset\u00fcnkben. A Main f\u00fcggv\u00e9ny param\u00e9terlist\u00e1ja k\u00f6t\u00f6tt: vagy ne adjunk meg param\u00e9tereket, vagy egy string[]-\u00f6t adjunk meg, amiben fut\u00e1s k\u00f6zben megkapjuk az parancssori argumentumokat.

                        3. .NET-ben a standard ki \u00e9s bemenet kezel\u00e9s\u00e9re a System n\u00e9vt\u00e9r Console oszt\u00e1lya haszn\u00e1land\u00f3. A WriteLine statikus m\u0171velet\u00e9vel egy sort tudunk ki\u00edrni, a ReadKey m\u0171velettel egy billenty\u0171 lenyom\u00e1s\u00e1ra v\u00e1rakozhatunk.

                        Top level statements, Implicit \u00e9s static usings \u00e9s n\u00e9vterek

                        A projekt l\u00e9trehoz\u00e1sakor kor\u00e1bban bepip\u00e1ltuk a \"Do not use top level statements\" jel\u00f6l\u0151n\u00e9gyzetet. Ha ezt nem tett\u00fck volna meg, akkor a Program.cs f\u00e1jlunkban mind\u00f6ssze egyetlen \u00e9rdemi sort tal\u00e1ltunk volna:

                        // See https://aka.ms/new-console-template for more information\nConsole.WriteLine(\"Hello World!\");\n

                        Ez m\u0171k\u00f6d\u00e9s\u00e9ben ekvivalens a fenti Program oszt\u00e1lyt \u00e9s ebben Main f\u00fcggv\u00e9nyt tartalmaz\u00f3 k\u00f3ddal. N\u00e9zz\u00fck, mik teszik ezt lehet\u0151v\u00e9 (ezekr\u0151l pl. itt https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/top-level-statements olvashatunk b\u0151vebben, mindkett\u0151 C# 10 \u00fajdons\u00e1g):

                        • Top level statements. Ennek az a l\u00e9nyege, hogy mindenf\u00e9le oszt\u00e1ly/Main \u00e9s egy\u00e9b f\u00fcggv\u00e9nydefin\u00edci\u00f3 n\u00e9lk\u00fcl a projektben egyetlen forr\u00e1sf\u00e1jlban k\u00f6zvetlen\u00fcl is \u00edrhatunk k\u00f3dot. Ez esetben ezt a sz\u00ednfalak m\u00f6g\u00f6tt a ford\u00edt\u00f3 berakja egy \u00e1ltalunk nem l\u00e1that\u00f3 oszt\u00e1ly statikus Main f\u00fcggv\u00e9ny\u00e9be. A bevezet\u00e9s\u00e9nek a motiv\u00e1ci\u00f3ja az volt, hogy a nagyon egyszer\u0171, \u201escript\u201d szer\u0171 alkalmaz\u00e1sok eset\u00e9n kevesebb legyen a boilerplate k\u00f3d.
                        • Implicit global usings. Annak f\u00fcggv\u00e9ny\u00e9ben, hogy pontosan milyen projektt\u00edpust hoztunk l\u00e9tre, bizonyos alapn\u00e9vterek a sz\u00ednfalak m\u00f6g\u00f6tt automatikusan using-olva lesznek minden forr\u00e1sf\u00e1jlban (ehhez a compiler a global using utas\u00edt\u00e1st haszn\u00e1lja). A l\u00e9nyeg: a fejleszt\u0151knek \u00edgy bizonyos, gyakran haszn\u00e1lt n\u00e9vtereket (pl. System.IO, System.Collections.Generic stb.) nem kell a forr\u00e1sf\u00e1jlonk\u00e9nt using-olni.
                        • Static using. Lehet\u0151s\u00e9g\u00fcnk van C#-ban n\u00e9vterek helyett statikus oszt\u00e1lyokat is usingolni, \u00edgy azokat a haszn\u00e1latuk sor\u00e1n nem fontos ki\u00edrni. Gyakori eset erre a Console vagy a Math oszt\u00e1ly usingol\u00e1sa.

                          using static System.Console;\n\nnamespace ConsoleApp12\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            WriteLine(\"Hello, World!\");\n        }\n    }\n}\n
                        • F\u00e1jl szint\u0171 n\u00e9vterek. C# 10-ben szint\u00e9n egy egyszer\u0171s\u00edt\u00e9st kapunk a n\u00e9vterek deklar\u00e1l\u00e1sa sor\u00e1n, mert m\u00e1r nem k\u00f6telez\u0151 a kapcsos z\u00e1r\u00f3jeleket kitenni, \u00edgy az adott namespace a teljes f\u00e1jlra \u00e9rv\u00e9nyes lesz pl.:

                          namespace HelloWorld;\n\ninternal class Program\n{\n    // ...\n}\n

                        Inconsistent visibility vagy inconsistent accessibility hiba

                        A f\u00e9l\u00e9v sor\u00e1n a programoz\u00e1si feladatok megval\u00f3s\u00edt\u00e1sa sor\u00e1n tal\u00e1lkozhatunk inconsistent visibility-re vagy inconsistent accessibility-re panaszkod\u00f3 ford\u00edt\u00e1si hiba\u00fczenetekkel. A jelens\u00e9g h\u00e1tter\u00e9ben az \u00e1ll, hogy .NET k\u00f6rnyezetben lehet\u0151s\u00e9g van az egyes t\u00edpusok (oszt\u00e1ly, interf\u00e9sz stb.) l\u00e1that\u00f3s\u00e1g\u00e1nak szab\u00e1lyoz\u00e1s\u00e1ra:

                        • internal vagy nem adjuk meg a l\u00e1that\u00f3s\u00e1got: a t\u00edpus csak az adott szerelv\u00e9nyen (.exe, .dll)/projekten, bel\u00fcl l\u00e1that\u00f3
                        • public: a t\u00edpus m\u00e1s szerelv\u00e9nyek/projektek sz\u00e1m\u00e1ra is l\u00e1that\u00f3

                        A hiba legegyszer\u0171bben \u00fagy h\u00e1r\u00edthat\u00f3 el, ha minden t\u00edpusunkat publikusnak defini\u00e1ljuk, pl.:

                        public class HardDisk\n{\n    // ...\n}\n
                        "},{"location":"labor/1-model-es-kod-kapcsolata/#elmeleti-attekintes","title":"Elm\u00e9leti \u00e1ttekint\u00e9s","text":"

                        Az alfejezetek nem tartalmaznak feladatot, a hallgat\u00f3k sz\u00e1m\u00e1ra ismertetik a kapcsol\u00f3d\u00f3 elm\u00e9leti t\u00e9mak\u00f6r\u00f6ket, p\u00e9ld\u00e1kkal illusztr\u00e1lva.

                        "},{"location":"labor/1-model-es-kod-kapcsolata/#a-az-uml-osztalydiagram-es-a-kod-kapcsolatanak-elmelete-hallgato","title":"A) Az UML oszt\u00e1lydiagram \u00e9s a k\u00f3d kapcsolat\u00e1nak elm\u00e9lete [hallgat\u00f3]*","text":"

                        Az anyag itt el\u00e9rhet\u0151: Az UML oszt\u00e1lydiagram \u00e9s a k\u00f3d kapcsolata. Ez a t\u00e9mak\u00f6r kor\u00e1bbi f\u00e9l\u00e9vben a Szoftvertechnol\u00f3gia t\u00e1rgy keret\u00e9ben ker\u00fclt ismertet\u00e9sre.

                        "},{"location":"labor/1-model-es-kod-kapcsolata/#b-interfesz-es-absztrakt-ososztaly-hallgato","title":"B) Interf\u00e9sz \u00e9s absztrakt (\u0151s)oszt\u00e1ly [hallgat\u00f3]*","text":"

                        Az anyag itt el\u00e9rhet\u0151: Interf\u00e9sz \u00e9s absztrakt (\u0151s)oszt\u00e1ly.

                        T\u00e9mak\u00f6r\u00f6k:

                        • Absztrakt oszt\u00e1ly fogalma \u00e9s defini\u00e1l\u00e1sa C# nyelven
                        • Interf\u00e9sz fogalma \u00e9s defini\u00e1l\u00e1sa C# nyelven
                        • Absztrakt \u0151s \u00e9s interf\u00e9sz \u00f6sszehasonl\u00edt\u00e1sa
                        "},{"location":"labor/1-model-es-kod-kapcsolata/#2-feladat-az-uml-es-a-kod-kapcsolatanak-szemleltetese","title":"2. Feladat - Az UML \u00e9s a k\u00f3d kapcsolat\u00e1nak szeml\u00e9ltet\u00e9se","text":""},{"location":"labor/1-model-es-kod-kapcsolata/#feladat-leirasa-equipment-inventory","title":"Feladat le\u00edr\u00e1sa - Equipment inventory","text":"

                        Feladat: Egy sz\u00e1m\u00edt\u00f3g\u00e9palkatr\u00e9sz nyilv\u00e1ntart\u00f3 alkalmaz\u00e1s kifejleszt\u00e9s\u00e9vel b\u00edztak meg benn\u00fcnket. B\u0151vebben:

                        • K\u00fcl\u00f6nb\u00f6z\u0151 t\u00edpus\u00fa alkatr\u00e9szeket kell tudni kezelni. Kezdetben a HardDisk, SoundCard \u00e9s LedDisplay t\u00edpusokat kell t\u00e1mogatni, de a rendszer legyen k\u00f6nnyen b\u0151v\u00edthet\u0151 \u00faj t\u00edpusokkal.
                        • Az alkatr\u00e9szekhez tartoz\u00f3 adatok: beszerz\u00e9s \u00e9ve, \u00e9letkora (sz\u00e1m\u00edtott), beszerz\u00e9si \u00e1ra \u00e9s aktu\u00e1lis \u00e1ra (sz\u00e1m\u00edtott), de ezeken fel\u00fcl t\u00edpusf\u00fcgg\u0151 adatokat is tartalmazhatnak (pl. a HardDisk eset\u00e9ben a kapacit\u00e1s).
                        • Az aktu\u00e1lis \u00e1r f\u00fcgg az alkatr\u00e9sz t\u00edpus\u00e1t\u00f3l, a beszerz\u00e9si \u00e1rt\u00f3l \u00e9s az alkatr\u00e9sz gy\u00e1rt\u00e1si \u00e9v\u00e9t\u0151l. Pl. min\u00e9l \u00f6regebb egy alkatr\u00e9sz, ann\u00e1l nagyobb kedvezm\u00e9nyt adunk r\u00e1, de a kedvezm\u00e9ny m\u00e9rt\u00e9ke f\u00fcgg az alkatr\u00e9sz t\u00edpust\u00f3l is.
                        • List\u00e1zni kell tudni a k\u00e9szleten lev\u0151 alkatr\u00e9szeket.
                        • A LedDisplay oszt\u00e1lynak k\u00f6telez\u0151en egy DisplayBase oszt\u00e1lyb\u00f3l kell sz\u00e1rmaznia, \u00e9s a DisplayBase oszt\u00e1ly forr\u00e1sk\u00f3dja nem megv\u00e1ltoztathat\u00f3. Jelen p\u00e9ld\u00e1ban ennek nincs sok \u00e9rtelme, a gyakorlatban azonban gyakran tal\u00e1lkozunk hasonl\u00f3 helyzettel, amikor is az \u00e1ltalunk haszn\u00e1lt keretrendszer/platform el\u0151\u00edrja, hogy adott esetben egy-egy be\u00e9p\u00edtett oszt\u00e1lyb\u00f3l kell sz\u00e1rmaztassunk. Tipikusan ez a helyzet, amikor ablakokkal, \u0171rlapokkal, saj\u00e1t vez\u00e9rl\u0151t\u00edpusokkal dolgozunk: ezeket a keretrendszer be\u00e9p\u00edtett oszt\u00e1lyaib\u00f3l kell sz\u00e1rmaztatnunk, \u00e9s a keretrendszer - pl. Java, .NET - forr\u00e1sk\u00f3dja nem \u00e1ll rendelkez\u00e9s\u00fcnkre (de legal\u00e1bbis biztosan nem akarjuk megv\u00e1ltoztatni). A p\u00e9ld\u00e1nkban a DisplayBase-b\u0151l val\u00f3 sz\u00e1rmaztat\u00e1s el\u0151\u00edr\u00e1s\u00e1val ezt a helyzetet szimul\u00e1ljuk.

                        A megval\u00f3s\u00edt\u00e1s sor\u00e1n jelent\u0151s egyszer\u0171s\u00edt\u00e9ssel \u00e9l\u00fcnk: az alkatr\u00e9szeket csak mem\u00f3ri\u00e1ban tarjuk nyilv\u00e1n, a list\u00e1z\u00e1s is a lehet\u0151 legegyszer\u0171bb, egyszer\u0171en csak ki\u00edrjuk a nyilv\u00e1ntartott alkatr\u00e9szek adatait a konzolra.

                        A kezdeti egyeztet\u00e9sek sor\u00e1n a megrendel\u0151nkt\u0151l a k\u00f6vetkez\u0151 inform\u00e1ci\u00f3t kapjuk: egy bels\u0151 munkat\u00e1rsuk m\u00e1r elindult a fejleszt\u00e9ssel, de id\u0151 hi\u00e1ny\u00e1ban csak f\u00e9lk\u00e9sz megold\u00e1sig jutott. A feladatunk r\u00e9sz\u00e9t k\u00e9pezi a f\u00e9lk\u00e9sz megold\u00e1s megismer\u00e9se, illetve ebb\u0151l kiindulva kell a feladatot megval\u00f3s\u00edtani.

                        "},{"location":"labor/1-model-es-kod-kapcsolata/#class-diagram","title":"Class Diagram","text":"

                        Nyissuk meg a megrendel\u0151nkt\u0151l kapott forr\u00e1sk\u00f3d solution-j\u00e9t, melyet a k\u00f6vetkez\u0151 l\u00e9p\u00e9seket k\u00f6vetve tudunk megtenni.

                        Ehhez kl\u00f3nozzuk le a kiindul\u00f3 projekt online GitHub rendszerben el\u00e9rhet\u0151 Git repositoryj\u00e1t a C:\\Work mapp\u00e1n bel\u00fcl egy \u00faj saj\u00e1t mapp\u00e1ba: pl.: C:\\Work\\NEPTUN\\lab1. Ebben az \u00faj mapp\u00e1ban nyissunk meg egy command line-t vagy powershellt \u00e9s futtassuk az al\u00e1bbi git parancsot:

                        git clone https://github.com/bmeviauab00/lab-modellkod-kiindulo.git\n

                        Git \u00e9s GitHub

                        A Git-r\u0151l, mint forr\u00e1sk\u00f3dkezel\u0151 rendszerr\u0151l, az els\u0151 h\u00e1zi feladat kontextus\u00e1ban olvashatunk majd b\u0151vebben.

                        Nyissuk meg a lekl\u00f3nozott mapp\u00e1ban tal\u00e1lhat\u00f3 src/EquipmentInventory.sln Visual Studio solutiont.

                        A Solution Explorerben szemmel fussuk \u00e1t a f\u00e1jlokat. Az meg\u00e9rt\u00e9st seg\u00edten\u00e9, ha egy oszt\u00e1lydiagramon megjelen\u00edten\u00e9nk az oszt\u00e1lyok k\u00f6z\u00f6tti kapcsolatokat. Vegy\u00fcnk is fel egy oszt\u00e1lydiagramot a projekt\u00fcnkbe. A Solution Explorerben a projekten (\u00e9s nem a solution-\u00f6n!) jobb gombbal kattintva a felugr\u00f3 men\u00fcben az Add/New Item elemet v\u00e1lasztva, majd a megjelen\u0151 ablakban a Class Diagram elemet v\u00e1lasszuk ki, az ablak alj\u00e1n a diagram nev\u00e9nek a Main.cd-t adjuk meg, \u00e9s OK-zuk le az ablakot.

                        Class Diagram hi\u00e1nyz\u00f3 sablon

                        Ha a Class Diagram elem nem jelenik meg a list\u00e1ban, akkor nincs telep\u00edtve a VS megfelel\u0151 komponense. Err\u0151l jelen dokumentum El\u0151felt\u00e9telek fejezet\u00e9ben olvashatsz b\u0151vebben.

                        Ekkor a Solution Explorerben megjelenik a Main.cd diagramf\u00e1jl, duplakattint\u00e1ssal nyissuk meg. A diagramunk jelenleg \u00fcres. A Solution Explorerb\u0151l drag&drop-pal dobjuk r\u00e1 a .cs forr\u00e1sf\u00e1jlokat a diagramra. Ekkor a VS megn\u00e9zi, milyen oszt\u00e1lyok vannak ezekben a forr\u00e1sf\u00e1jlokban, \u00e9s visszafejti \u0151ket UML oszt\u00e1lyokk\u00e1. Alak\u00edtsuk ki a k\u00f6vetkez\u0151 \u00e1br\u00e1nak megfelel\u0151 elrendez\u00e9st (az oszt\u00e1lyok tagjainak megjelen\u00edt\u00e9s\u00e9t a t\u00e9glalapuk jobb fels\u0151 sark\u00e1ban lev\u0151 duplany\u00edlra kattint\u00e1ssal \u00e9rhetj\u00fck el):

                        Az oszt\u00e1lyokhoz tartoz\u00f3 forr\u00e1sk\u00f3dot is megn\u00e9zhetj\u00fck, ak\u00e1r a diagramon a megfelel\u0151 oszt\u00e1lyra dupl\u00e1n kattintva, ak\u00e1r a Solution Explorerb\u0151l a .cs f\u00e1jlokat megnyitva. A k\u00f6vetkez\u0151ket tapasztaljuk:

                        • A SoundCard, HardDisk \u00e9s LedDisplay oszt\u00e1lyok viszonylag j\u00f3l kidolgozottak, rendelkeznek a sz\u00fcks\u00e9ges attrib\u00fatumokkal \u00e9s lek\u00e9rdez\u0151 f\u00fcggv\u00e9nyekkel.
                        • Az LedDisplay a k\u00f6vetelm\u00e9nyeknek megfelel\u0151en a DisplayBase oszt\u00e1lyb\u00f3l sz\u00e1rmazik.
                        • Az EquipmentInventory felel\u0151s ugyan a k\u00e9szleten lev\u0151 alkatr\u00e9szek nyilv\u00e1ntart\u00e1s\u00e1\u00e9rt, de gyakorlatilag semmi nincs ebb\u0151l megval\u00f3s\u00edtva.
                        • Tal\u00e1lunk egy IEquipment interf\u00e9szt, GetAge \u00e9s GetPrice m\u0171veletekkel
                        "},{"location":"labor/1-model-es-kod-kapcsolata/#equipmentinventory","title":"EquipmentInventory","text":"

                        \u00c1lljunk neki a megold\u00e1s kidolgoz\u00e1s\u00e1nak. El\u0151sz\u00f6r is az alapkoncepci\u00f3kat fektess\u00fck le. Az EquipmentInventory oszt\u00e1lyban egy heterog\u00e9n kollekci\u00f3ban t\u00e1roljuk a k\u00fcl\u00f6nb\u00f6z\u0151 alkatr\u00e9sz t\u00edpusokat. Ez a kulcsa az alkatr\u00e9szek egys\u00e9ges kezel\u00e9s\u00e9nek, vagyis annak, hogy a megold\u00e1sunk \u00faj alkatr\u00e9szt\u00edpusokkal k\u00f6nnyen b\u0151v\u00edthet\u0151 legyen.

                        Mint kor\u00e1bban taglaltuk, az egys\u00e9ges kezel\u00e9st vagy k\u00f6z\u00f6s \u0151soszt\u00e1ly, vagy k\u00f6z\u00f6s interf\u00e9sz bevezet\u00e9s\u00e9vel lehet megoldani. Eset\u00fcnkben a k\u00f6z\u00f6s \u0151soszt\u00e1ly (pl. EquipmentBase) \u00fagy t\u0171nik, kiesik, mert ennek bevezet\u00e9s\u00e9vel az LedDisplay oszt\u00e1lynak k\u00e9t \u0151soszt\u00e1lya is lenne: a k\u00f6telez\u0151nek kik\u00f6t\u00f6tt DisplayBase, \u00e9s az \u00e1ltalunk az egys\u00e9ges kezel\u00e9sre bevezetett EquipmentBase. Ez nem lehets\u00e9ges, .NET k\u00f6rnyezetben egy oszt\u00e1lynak csak egy \u0151se lehet. Az a megold\u00e1s pedig, hogy a DisplayBase-t \u00fagy m\u00f3dos\u00edtjuk, hogy \u0151 is az EquipmentBase-b\u0151l sz\u00e1rmazik, a k\u00f6vetelm\u00e9ny\u00fcnknek megfelel\u0151en nem lehets\u00e9ges (kik\u00f6t\u00e9s volt, hogy a forr\u00e1sk\u00f3dja nem m\u00f3dos\u00edthat\u00f3). Marad teh\u00e1t az interf\u00e9sz alap\u00fa megk\u00f6zel\u00edt\u00e9s. Minden bizonnyal az alkalmaz\u00e1s kor\u00e1bbi fejleszt\u0151je is erre a k\u00f6vetkeztet\u00e9sre jutott, ez\u00e9rt is vezette be az IEquipment interf\u00e9szt.

                        Vegy\u00fcnk fel egy IEquipment t\u00edpus\u00fa elemekb\u0151l \u00e1ll\u00f3 generikus list\u00e1t (ne property-t hanem field-et!) az EquipmentInventory oszt\u00e1lyba. A l\u00e1that\u00f3s\u00e1ga \u2013 az egys\u00e9gbez\u00e1r\u00e1sra t\u00f6rekedve \u2013 legyen private. A neve legyen equipment (ne legyen \u201es\u201d a v\u00e9g\u00e9n, angolban az equipment t\u00f6bbes sz\u00e1ma is equipment). A tagv\u00e1ltoz\u00f3 felv\u00e9tel\u00e9hez a Visual Studio Class Details ablak\u00e1t haszn\u00e1ljuk. Ha az ablak nem l\u00e1that\u00f3, a View / Other Windows / Class Details men\u00fc kiv\u00e1laszt\u00e1s\u00e1val jelen\u00edthet\u0151 meg.

                        A tagv\u00e1ltoz\u00f3 t\u00edpusa teh\u00e1t List<IEquipment>. A .NET List t\u00edpusa egy dinamikusan ny\u00fajt\u00f3zkod\u00f3 generikus t\u00f6mb (mint Java-ban az ArrayList). A diagramon az EquipmentInventory oszt\u00e1lyra pillantva azt l\u00e1tjuk, hogy csak a tagv\u00e1ltoz\u00f3 neve jelenik meg, a t\u00edpusa nem. A diagram h\u00e1tter\u00e9n jobb gombbal kattintva a Change Members Format men\u00fcb\u0151l a Display Full Signature-t v\u00e1lasszuk ki. Ezt k\u00f6vet\u0151en a diagramon l\u00e1that\u00f3v\u00e1 v\u00e1lik a tagv\u00e1ltoz\u00f3k t\u00edpusa, valamint a m\u0171veletek teljes szignat\u00far\u00e1ja.

                        Az EquipmentInventory oszt\u00e1lyon dupl\u00e1n kattintva elnavig\u00e1lhatunk a forr\u00e1sk\u00f3dba, \u00e9s mint l\u00e1that\u00f3, val\u00f3ban egy lista t\u00edpus\u00fa tagv\u00e1ltoz\u00f3k\u00e9nt jelenik meg a k\u00f3dban:

                        class EquipmentInventory\n{\n    private List<IEquipment> equipment;\n

                        Ennek egyr\u00e9szt \u00f6r\u00fcl\u00fcnk, mert a Visual Studio t\u00e1mogatja a round-trip engineering technik\u00e1t: a modellt \u00e9rint\u0151 v\u00e1ltoz\u00e1sokat azonnal \u00e1tvezeti a k\u00f3dba, \u00e9s viszont. M\u00e1sr\u00e9szt a kor\u00e1bbiakban azt taglaltuk, hogy ha egy oszt\u00e1lyban egy gy\u0171jtem\u00e9ny tag van egy m\u00e1sik oszt\u00e1ly elemeib\u0151l, akkor annak az UML modellben egy 1-t\u00f6bb t\u00edpus\u00fa asszoci\u00e1ci\u00f3s kapcsolatk\u00e9nt \u201eillik\u201d megjelennie a k\u00e9t oszt\u00e1ly k\u00f6z\u00f6tt. A modell\u00fcnkben egyel\u0151re nem ezt tapasztaljuk. Szerencs\u00e9re a VS modellez\u0151 fel\u00fclete r\u00e1vehet\u0151, hogy ilyen form\u00e1ban jelen\u00edtse meg ezt a kapcsolatt\u00edpust. Ehhez kattintsunk a diagramon jobb gombbal az equipment tagv\u00e1ltoz\u00f3n, \u00e9s a men\u00fcb\u0151l v\u00e1lasszuk ki a Show as Collection Association elemet. Az IEquipment interf\u00e9szt ezt k\u00f6vet\u0151en mozgassuk ki jobbra, hogy kell\u0151 hely legyen a diagramon az asszoci\u00e1ci\u00f3s kapcsolat \u00e9s a kapcsolaton lev\u0151 szerep (role) adatainak megjelen\u00edt\u00e9s\u00e9re:

                        A dupla ny\u00edl v\u00e9gz\u0151d\u00e9s a \u201et\u00f6bbes\u201d oldalon nem szabv\u00e1nyos UML, de ne szomorodjunk el t\u0151le k\u00fcl\u00f6n\u00f6sebben, nincs semmi jelent\u0151s\u00e9ge. Annak mindenk\u00e9ppen \u00f6r\u00fcl\u00fcnk, hogy a kapcsolatot reprezent\u00e1l\u00f3 ny\u00edl az IEquipment v\u00e9g\u00e9n a szerepben a tagv\u00e1ltoz\u00f3 neve (s\u0151t, m\u00e9g a pontos t\u00edpusa is) fel van t\u00fcntetve.

                        Navig\u00e1ljunk el az EquipmentInventory forr\u00e1sk\u00f3dj\u00e1hoz, \u00e9s \u00edrjuk meg a konstruktor\u00e1t, ami inicializ\u00e1lja az equipment gy\u0171jtem\u00e9nyt!

                        public EquipmentInventory()\n{\n    equipment = new List<IEquipment>();\n}\n

                        Ezut\u00e1n \u00edrjuk meg a ListAll met\u00f3dust, ami ki\u00edrja az elemek \u00e9letkor\u00e1t, \u00e9s az aktu\u00e1lis \u00e9rt\u00e9k\u00fcket:

                        public void ListAll()\n{\n    foreach (IEquipment eq in equipment)\n    {\n        Console.WriteLine($\"\u00c9letkor: {eq.GetAge()}\\t\u00c9rt\u00e9ke: {eq.GetPrice()}\");\n    }\n}\n

                        Az elemeken a foreach utas\u00edt\u00e1ssal iter\u00e1lunk v\u00e9gig. A foreach utas\u00edt\u00e1s haszn\u00e1lata sor\u00e1n az in kulcssz\u00f3 ut\u00e1n egy gy\u0171jtem\u00e9nynek kell \u00e1llnia, az in el\u0151tt pedig egy v\u00e1ltoz\u00f3 deklar\u00e1ci\u00f3nak (eset\u00fcnkben IEquipment eq), ahol a t\u00edpus a gy\u0171jtem\u00e9ny elemt\u00edpusa. Minden iter\u00e1ci\u00f3ban ez a v\u00e1ltoz\u00f3 a gy\u0171jtem\u00e9ny iter\u00e1ci\u00f3beli \u00e9rt\u00e9k\u00e9t veszi fel.

                        A Console.WriteLine m\u0171veletnek vagy egy egyszer\u0171 stringet adunk meg, vagy, mint eset\u00fcnkben, egy form\u00e1z\u00e1si stringet. A behelyettes\u00edt\u00e9seket string interpol\u00e1ci\u00f3val oldottuk meg: a behelyettes\u00edtend\u0151 \u00e9rt\u00e9keket {} k\u00f6z\u00f6tt kell megadni. Ha string interpol\u00e1ci\u00f3t haszn\u00e1lunk, a stringnek $ jellel kell kezd\u0151dnie.

                        \u00cdrjunk meg egy AddEquipment nev\u0171 f\u00fcggv\u00e9nyt, ami felvesz egy \u00faj eszk\u00f6zt a k\u00e9szletbe:

                        public void AddEquipment(IEquipment eq)\n{\n     equipment.Add(eq);\n}\n
                        "},{"location":"labor/1-model-es-kod-kapcsolata/#iequipment-megvalositok","title":"IEquipment megval\u00f3s\u00edt\u00f3k","text":"

                        Kor\u00e1bbi d\u00f6nt\u00e9s\u00fcnk \u00e9rtelm\u00e9ben az IEquipment interf\u00e9szt haszn\u00e1ljuk az k\u00fcl\u00f6nb\u00f6z\u0151 alkatr\u00e9sz t\u00edpusok egys\u00e9ges kezel\u00e9s\u00e9re. Est\u00fcnkben mind a SoundCard, mind a HardDisk oszt\u00e1ly rendelkezik GetAge() \u00e9s GetPrice() met\u00f3dussal, m\u00e9gsem tudjuk \u0151ket egys\u00e9gesen kezelni (pl. k\u00f6z\u00f6s list\u00e1ban t\u00e1rolni). Ahhoz, hogy ezt meg tudjuk tenni, el kell \u00e9rn\u00fcnk, hogy mindk\u00e9t oszt\u00e1ly megval\u00f3s\u00edtsa az IEquipment interf\u00e9szt. M\u00f3dos\u00edtsuk a forr\u00e1sukat:

                        public class SoundCard : IEquipment\n
                        public class HardDisk : IEquipment\n

                        Ezt k\u00f6vet\u0151en a SoundCard \u00e9s HardDisk oszt\u00e1lyban implement\u00e1lnunk kell az IEquipment interf\u00e9szben lev\u0151 met\u00f3dusokat. Azt tapasztaljuk, hogy ezzel nincs most teend\u0151k, a GetPrice \u00e9s GetAge f\u00fcggv\u00e9nyek m\u00e1r meg vannak \u00edrva mindk\u00e9t helyen.

                        Pr\u00f3bak\u00e9ppen a Program.cs f\u00e1jlban tal\u00e1lhat\u00f3 Main f\u00fcggv\u00e9ny\u00fcnkben hozzunk l\u00e9tre egy EquipmentInventory objektumot, t\u00f6lts\u00fck fel HardDisk \u00e9s SoundCard objektumokkal, majd list\u00e1zzuk a k\u00e9sztelet a konzolra. Ammennyiben nem 2021 az aktu\u00e1lis \u00e9v, az al\u00e1bbi sorokn\u00e1l a 2021-es \u00e9vet \u00edrjuk \u00e1t az aktu\u00e1lis \u00e9vre, a 2020-at pedig enn\u00e9l eggyel kisebb sz\u00e1mra!

                        static void Main( string[] args )\n{\n    EquipmentInventory ei = new EquipmentInventory();\n\n    ei.AddEquipment(new HardDisk(2021, 30000, 80));\n    ei.AddEquipment(new HardDisk(2020, 25000, 120));\n    ei.AddEquipment(new HardDisk(2020, 25000, 250));\n\n    ei.AddEquipment(new SoundCard(2021, 8000));\n    ei.AddEquipment(new SoundCard(2020, 7000));\n    ei.AddEquipment(new SoundCard(2020, 6000));\n\n    ei.ListAll();\n}\n

                        Az alkalmaz\u00e1st futtatva azt tapasztaljuk, hogy b\u00e1r megold\u00e1sunk kezdetleges, de m\u0171k\u00f6dik:

                        Folytassuk a munk\u00e1t a LedDisplay oszt\u00e1llyal. A DisplayBase \u0151s forr\u00e1sk\u00f3dj\u00e1t a k\u00f6vetelm\u00e9nyek miatt nem m\u00f3dos\u00edthatjuk. De ez semmif\u00e9le probl\u00e9m\u00e1t nem okoz, a LedDisplay oszt\u00e1lyunk fogja az IEquipment interf\u00e9szt implement\u00e1lni, m\u00f3dos\u00edtsuk a k\u00f3dot ennek megfelel\u0151en:

                        public class LedDisplay : DisplayBase, IEquipment\n

                        A LedDisplay oszt\u00e1lyban m\u00e1r meg kell \u00edrni az interf\u00e9szben szerepl\u0151 f\u00fcggv\u00e9nyeket:

                        public double GetPrice()\n{\n    return this.price;\n}\n\npublic int GetAge()\n{\n    return DateTime.Today.Year - this.manufacturingYear;\n}\n

                        B\u0151v\u00edts\u00fck a Main f\u00fcggv\u00e9ny\u00fcnket is, vegy\u00fcnk fel k\u00e9t LedDisplay objektumot a k\u00e9szlet\u00fcnkbe (itt is \u00e9l, hogy ammennyiben nem 2021 az aktu\u00e1lis \u00e9v, az al\u00e1bbi sorokn\u00e1l a 2021-es \u00e9vet \u00edrjuk \u00e1t az aktu\u00e1lis \u00e9vre, a 2020-at pedig enn\u00e9l eggyel kisebb sz\u00e1mra!

                        ei.AddEquipment(new LedDisplay(2020, 80000, 17, 16));\nei.AddEquipment(new LedDisplay (2021, 70000, 17, 12));\n\nei.ListAll();\nConsole.ReadKey();\n

                        Tesztel\u00e9sk\u00e9ppen futtassuk az alkalmaz\u00e1st.

                        "},{"location":"labor/1-model-es-kod-kapcsolata/#3-feladat-az-interfesz-es-az-absztrakt-ososztaly-alkalmazastechnikaja","title":"3. Feladat - Az interf\u00e9sz \u00e9s az absztrakt \u0151soszt\u00e1ly alkalmaz\u00e1stechnik\u00e1ja","text":""},{"location":"labor/1-model-es-kod-kapcsolata/#interfesz-problematikaja","title":"Interf\u00e9sz problematik\u00e1ja","text":"

                        \u00c9rt\u00e9kelj\u00fck a jelenlegi, interf\u00e9sz alap\u00fa megold\u00e1sunkat.

                        Az egyik f\u0151 probl\u00e9ma, hogy k\u00f3dunk tele van a karbantarthat\u00f3s\u00e1got \u00e9s b\u0151v\u00edthet\u0151s\u00e9get rombol\u00f3 k\u00f3dduplik\u00e1ci\u00f3val:

                        • A yearOfCreation \u00e9s newPrice tagok minden alkatr\u00e9sz t\u00edpusban (kiv\u00e9ve a speci\u00e1lis LedDisplay-t) k\u00f6z\u00f6sek, ezeket \u00faj t\u00edpus bevezet\u00e9sekor is copy-paste technik\u00e1val \u00e1t kell venni.
                        • A GetAge f\u00fcggv\u00e9ny implement\u00e1ci\u00f3ja szinten minden alkatr\u00e9sz t\u00edpusban (kiv\u00e9ve a speci\u00e1lis LedDisplay-t) azonos, szint\u00e9n copy-paste-tel \u201eszapor\u00edtand\u00f3\u201d.
                        • A konstruktorok yearOfCreation \u00e9s newPrice tagokat inicializ\u00e1l\u00f3 sorai szint\u00e9n duplik\u00e1ltak az egyes oszt\u00e1lyokban.

                        B\u00e1r ez a k\u00f3dduplik\u00e1ci\u00f3 egyel\u0151re nem t\u0171nik jelent\u0151snek, \u00faj alkatr\u00e9sz t\u00edpusok bevezet\u00e9s\u00e9vel egyre ink\u00e1bb elm\u00e9rgesedik a helyzet, jobb id\u0151ben elej\u00e9t venni a j\u00f6v\u0151beli f\u00e1jdalmaknak.

                        A m\u00e1sik probl\u00e9ma abb\u00f3l ad\u00f3dik, hogy az alkatr\u00e9sz adatok list\u00e1z\u00e1sa jelenleg f\u00e1jdalmasan hi\u00e1nyos, nem jelenik meg az alkatr\u00e9sz t\u00edpusa (csak a kora \u00e9s az \u00e1ra). A t\u00edpus megjelen\u00edt\u00e9s\u00e9hez az IEquipment interf\u00e9szt b\u0151v\u00edteni kell, pl. egy GetDescription nev\u0171 m\u0171velet bevezet\u00e9s\u00e9vel. Vegy\u00fcnk is fel egy GetDescription f\u00fcggv\u00e9nyt az interf\u00e9szbe!

                        public interface IEquipment\n{\n    double GetPrice();\n    int GetAge();\n    string GetDescription();\n}\n

                        Ekkor minden IEquipment interf\u00e9szt implement\u00e1l\u00f3 oszt\u00e1lyban meg kellene val\u00f3s\u00edtani ezt a met\u00f3dust is, ami sok oszt\u00e1ly eset\u00e9n sok munka (valamint egy t\u00f6bbkomponens\u0171, vagyis t\u00f6bb DLL-b\u0151l \u00e1ll\u00f3 alkalmaz\u00e1s eset\u00e9ben, amikor ezek nem egy fejleszt\u0151 c\u00e9g kez\u00e9ben vannak, sokszor nem is megoldhat\u00f3). A Build parancs futtat\u00e1s\u00e1val ellen\u0151rizz\u00fck, hogy a GetDescription felv\u00e9tele ut\u00e1n h\u00e1rom helyen is ford\u00edt\u00e1si hib\u00e1t kapunk.

                        Interf\u00e9szben alap\u00e9rtelmezett implement\u00e1ci\u00f3 megad\u00e1sa

                        \u00c9rdemes tudni, hogy C# 8-t\u00f3l (illetve .NET vagy .NET Core runtime is kell hozz\u00e1, .NET Framework alatt nem t\u00e1mogatott) kezdve interf\u00e9sz m\u0171veleteknek is lehet alap\u00e9rtelmezett implement\u00e1ci\u00f3t adni (default interface methods), \u00edgy a fenti probl\u00e9ma megold\u00e1s\u00e1hoz nincs sz\u00fcks\u00e9g absztrakt oszt\u00e1lyra, de interf\u00e9sznek tov\u00e1bbiakban sem lehet tagv\u00e1ltoz\u00f3ja. B\u0151vebben inform\u00e1ci\u00f3 itt: default interface methods.

                        public interface IEquipment\n{\n    double GetPrice();\n    int GetAge();\n    string GetDescription() { return \"EquipmentBase\"; }\n}\n
                        "},{"location":"labor/1-model-es-kod-kapcsolata/#absztrakt-osztaly","title":"Absztrakt oszt\u00e1ly","text":"

                        Mindk\u00e9t probl\u00e9m\u00e1ra megold\u00e1st jelent egy k\u00f6z\u00f6s absztrakt \u0151s bevezet\u00e9se (kiv\u00e9ve az LedDisplay oszt\u00e1lyt, amire m\u00e9g visszat\u00e9r\u00fcnk). Ebbe fel tudjuk k\u00f6lt\u00f6ztetni a lesz\u00e1rmazottakra k\u00f6z\u00f6s k\u00f3dot, valamint az \u00fajonnan bevezetett GetDescription m\u0171velethez egy alap\u00e9rtelmezett implement\u00e1ci\u00f3t tudunk megadni. Legyen az \u00faj absztrakt \u0151soszt\u00e1lyunk neve EquipmentBase. K\u00e9rd\u00e9s, sz\u00fcks\u00e9g van-e a tov\u00e1bbiakban az IEquipment interf\u00e9szre, vagy az teljesen kiv\u00e1lthat\u00f3 az \u00faj EquipmentBase oszt\u00e1llyal. Az IEquipment interf\u00e9szt meg kell tartsuk, mert a LedDisplay oszt\u00e1lyunkat nem tudjuk az EquipmentBase-b\u0151l sz\u00e1rmaztatni: m\u00e1r van egy k\u00f6telez\u0151en el\u0151\u00edrt \u0151soszt\u00e1lya, a DisplayBase: emiatt az EquipmentInventory a tov\u00e1bbfejlesztett megold\u00e1sunkban is IEquipment interf\u00e9szk\u00e9nt hivatkozik az k\u00fcl\u00f6nb\u00f6z\u0151 alkatr\u00e9szekre.

                        \u00c1lljunk is neki az \u00e1talak\u00edt\u00e1snak. Legyen az oszt\u00e1lydiagramunk az akt\u00edv tabf\u00fcl. A Toolbox-b\u00f3l drag&drop-pal dobjunk fel egy Abstract Class elemet a diagramra, a neve legyen EquipmentBase.

                        A k\u00f6vetkez\u0151kben azt kell el\u00e9rj\u00fck, hogy a SoundCard \u00e9s a HardDisk oszt\u00e1lyok sz\u00e1rmazzanak az EquipmentBase-b\u0151l (a LedDisplay-nek m\u00e1r van m\u00e1sik \u0151se, \u00edgy ott ezt nem tudjuk megtenni). Ehhez v\u00e1lasszuk ki az Inheritance kapcsolatot a Toolbox-ban, majd h\u00fazzunk egy-egy vonalat a gyermekoszt\u00e1lyb\u00f3l kiindulva az \u0151soszt\u00e1lyba a SoundCard \u00e9s HardDisk eset\u00e9ben egyar\u00e1nt.

                        A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben alak\u00edtsuk \u00e1t \u00fagy a k\u00f3dot, hogy ne a HardDisk \u00e9s SoundCard val\u00f3s\u00edts\u00e1k meg k\u00fcl\u00f6n-k\u00fcl\u00f6n az IEquipment interf\u00e9szt, hanem a k\u00f6z\u00f6s \u0151s\u00fck, az EquipmentBase egyszer. Ehhez m\u00f3dos\u00edtsuk az EquipmentBase oszt\u00e1lyt \u00fagy, hogy val\u00f3s\u00edtsa meg az interf\u00e9szt (ak\u00e1r a diagramon h\u00fazzunk be egy inheritance kapcsolatot az EquipmentBase-b\u0151l az IEquipment-be, vagy az EquipmentBase forr\u00e1sk\u00f3dj\u00e1t m\u00f3dos\u00edtsuk). A HardDisk \u00e9s SoundCard oszt\u00e1lyokb\u00f3l t\u00f6r\u00f6lj\u00fck az IEquipment megval\u00f3s\u00edt\u00e1s\u00e1t (az \u0151s m\u00e1r implement\u00e1lja).

                        A diagramunk \u00e9s a forr\u00e1sk\u00f3dunk vonatkoz\u00f3 r\u00e9szei ezt k\u00f6vet\u0151en \u00edgy n\u00e9znek ki:

                        public abstract class EquipmentBase : IEquipment\n
                        public class HardDisk : EquipmentBase\n
                        public class SoundCard : EquipmentBase\n

                        A k\u00f3dunk m\u00e9g nem fordul, ennek t\u00f6bb oka is van. Az EquipmentBase implement\u00e1lja az IEquipment interf\u00e9szt, de m\u00e9g nincsenek benne implement\u00e1lva az interf\u00e9sz m\u0171veletei. Vagy gener\u00e1ltassuk le a met\u00f3dusokat a smart tag haszn\u00e1lat\u00e1val, vagy g\u00e9pelj\u00fck be a k\u00f6vetkez\u0151 elveknek megfelel\u0151en:

                        • A newPrice \u00e9s yearOfCreation duplik\u00e1lva vannak a HardDisk \u00e9s SoundCard oszt\u00e1lyokban: mozgassuk (\u00e9s ne m\u00e1soljuk!) \u00e1t ezeket a k\u00f6z\u00f6s EquipmentBase \u0151sbe, \u00e9s protected l\u00e1that\u00f3s\u00e1got adjunk meg.
                        • A GetAge m\u0171velet duplik\u00e1lva van a HardDisk \u00e9s SoundCard oszt\u00e1lyokban, ezekb\u0151l t\u00f6r\u00f6lj\u00fck ki az implement\u00e1ci\u00f3t \u00e9s vigy\u00fck \u00e1t az EquipmentBase oszt\u00e1lyba.
                        • A GetPrice m\u0171veletet absztrakt m\u0171veletk\u00e9nt vegy\u00fck fel az \u0151sbe. Ez sz\u00e1nd\u00e9kos tervez\u0151i d\u00f6nt\u00e9s, \u00edgy r\u00e1k\u00e9nyszer\u00edtj\u00fck a lesz\u00e1rmazott oszt\u00e1lyokat, hogy mindenk\u00e9ppen defini\u00e1lj\u00e1k fel\u00fcl ezt a m\u0171veletet.
                        • A GetDescription eset\u00e9ben viszont pont ford\u00edtottja a helyzet: ezt virtu\u00e1lisnak defini\u00e1ljuk (\u00e9s nem absztraktnak), vagyis m\u00e1r az \u0151sben is adunk meg implement\u00e1ci\u00f3t. \u00cdgy a lesz\u00e1rmazottak nincsenek r\u00e1k\u00e9nyszer\u00edtve a m\u0171velet fel\u00fcldefini\u00e1l\u00e1s\u00e1ra.

                        A fentieknek megfelel\u0151 k\u00f3d a k\u00f6vetkez\u0151:

                        public abstract class EquipmentBase : IEquipment\n{\n    protected int yearOfCreation;\n    protected int newPrice;\n\n    public int GetAge()\n    {\n        return DateTime.Today.Year - yearOfCreation;\n    }\n\n    public abstract double GetPrice();\n\n    public virtual string GetDescription()\n    {\n        return \"EquipmentBase\";\n    }\n}\n

                        N\u00e9h\u00e1ny kieg\u00e9sz\u00edt\u0151 gondolat a k\u00f3dr\u00e9szletre vonatkoz\u00f3an:

                        • Az absztrakt oszt\u00e1lyok eset\u00e9ben az abstract kulcssz\u00f3t ki kell \u00edrni a class sz\u00f3 el\u00e9.
                        • Az absztrakt m\u0171veletek eset\u00e9ben az abstract kulcssz\u00f3t kell megadni
                        • .NET k\u00f6rnyezetben lehet\u0151s\u00e9g\u00fcnk van szab\u00e1lyozni, hogy egy m\u0171velet virtu\u00e1lis-e vagy sem. Ebb\u0151l a szempontb\u00f3l a C++ nyelvhez hasonl\u00edt. Amennyiben egy m\u0171veletet virtu\u00e1liss\u00e1 szeretn\u00e9nk tenni, a virtual kulcssz\u00f3t kell a m\u0171veletre megadni. Eml\u00e9keztet\u0151: akkor defini\u00e1ljunk egy m\u0171veletet virtu\u00e1lisnak, ha a lesz\u00e1rmazottak azt fel\u00fcldefini\u00e1l(hat)j\u00e1k. Csak ekkor garant\u00e1lt, hogy egy \u0151sreferenci\u00e1n megh\u00edvva az adott m\u0171veletet a lesz\u00e1rmazottbeli verzi\u00f3 h\u00edv\u00f3dik meg.
                        "},{"location":"labor/1-model-es-kod-kapcsolata/#leszarmazottak","title":"Lesz\u00e1rmazottak","text":"

                        A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben t\u00e9rj\u00fcnk \u00e1t az EquipmentBase lesz\u00e1rmazottakra. C# nyelven az absztrakt \u00e9s virtu\u00e1lis m\u0171veletek fel\u00fcldefini\u00e1l\u00e1sakor a lesz\u00e1rmazottban meg kell adni az override kulcssz\u00f3t. Els\u0151 l\u00e9p\u00e9sben a GetPrice m\u0171veletet defini\u00e1ljuk fel\u00fcl:

                        HardDisk.cs
                        public override double GetPrice()\n{\n    return yearOfCreation < (DateTime.Today.Year - 4)\n        ? 0\n        : newPrice - (DateTime.Today.Year - yearOfCreation) * 5000;\n}\n
                        SoundCard.cs
                        public override double GetPrice()\n{\n    return yearOfCreation < (DateTime.Today.Year - 4)\n        ? 0 \n        : newPrice - (DateTime.Today.Year - yearOfCreation) * 2000;\n}\n

                        A k\u00f6vetkez\u0151kben l\u00e9p\u00e9sben a GetDescription m\u0171veletet \u00edrjuk meg a HardDisk \u00e9s SoundCard oszt\u00e1lyokban. Mivel itt az \u0151sbeli virtu\u00e1lis f\u00fcggv\u00e9nyt defini\u00e1ljuk fel\u00fcl, szint\u00e9n meg kell adni az override kulcssz\u00f3t:

                        HardDisk.cs
                        public override string GetDescription()\n{\n    return \"Hard Disk\";\n}\n
                        SoundCard.cs
                        public override string GetDescription()\n{\n    return \"Sound Card\";\n}\n

                        Felmer\u00fclhet benn\u00fcnk a k\u00e9rd\u00e9s, mi\u00e9rt d\u00f6nt\u00f6ttek \u00fagy a C# nyelv tervez\u0151i, hogy a m\u0171veletek fel\u00fcldefini\u00e1l\u00e1sakor egy extra kulcssz\u00f3t kelljen megadni, hasonl\u00f3ra pl. a C++ nyelv eset\u00e9ben nem volt sz\u00fcks\u00e9g. Az ok egyszer\u0171: a k\u00f3d \u00edgy kifejez\u0151bb. A lesz\u00e1rmazottak k\u00f3dj\u00e1t n\u00e9zve az override sz\u00f3 azonnal egy\u00e9rtelm\u0171v\u00e9 teszi, hogy valamelyik \u0151sben ez a m\u0171velet absztrakt vagy virtu\u00e1lis, nem kell valamennyi \u0151s k\u00f3dj\u00e1t ehhez \u00e1ttekinteni.

                        "},{"location":"labor/1-model-es-kod-kapcsolata/#leddisplay-ose","title":"LedDisplay \u0151se","text":"

                        A LedDisplay oszt\u00e1lyunk \u0151se meg van k\u00f6tve, annak k\u00f3dja nem m\u00f3dos\u00edthat\u00f3, \u00edgy nem tudjuk az EquipmentBase-b\u0151l sz\u00e1rmaztatni. A GetAge m\u0171veletet \u00edgy nem tudjuk t\u00f6r\u00f6lni, ez a k\u00f3dduplik\u00e1ci\u00f3 itt megmarad (de csak a LedDisplay eset\u00e9ben, ami csak egy oszt\u00e1ly a sok k\u00f6z\u00fcl!).

                        Note

                        Val\u00f3j\u00e1ban egy kis plusz munk\u00e1val ett\u0151l a duplik\u00e1ci\u00f3t\u00f3l is meg tudn\u00e1nk szabadulni. Ehhez valamelyik oszt\u00e1lyban (pl. EquipmentBase) fel kellene venni egy statikus seg\u00e9df\u00fcggv\u00e9nyt, mely param\u00e9terben megkapn\u00e1 a gy\u00e1rt\u00e1si \u00e9vet, \u00e9s visszaadn\u00e1 az \u00e9letkort. Az EquipmentBase.GetAge \u00e9s a LedDisplay.GetAge ezt a seg\u00e9df\u00fcggv\u00e9nyt haszn\u00e1ln\u00e1 kimenete el\u0151\u00e1ll\u00edt\u00e1s\u00e1ra.

                        A LedDisplay oszt\u00e1lyunkban ad\u00f3sak vagyunk m\u00e9g a GetDescription meg\u00edr\u00e1s\u00e1val:

                        LedDisplay.cs
                        public string GetDescription()\n{\n    return \"Led Display\";\n}\n

                        Figyelj\u00fck meg, hogy itt NEM adtuk meg az override kulcssz\u00f3t. Mikor egy interf\u00e9sz f\u00fcggv\u00e9nyt implement\u00e1lunk, az override-ot nem kell/szabad ki\u00edrni.

                        "},{"location":"labor/1-model-es-kod-kapcsolata/#getdescription-hasznalata","title":"GetDescription haszn\u00e1lata","text":"

                        M\u00f3dos\u00edtsuk az EquipmentInventory.ListAll m\u0171velet\u00e9t, hogy az elemek le\u00edr\u00e1s\u00e1t is \u00edrja ki a kimenetre:

                        EquipmentInventory.cs
                        public void ListAll()\n{\n    foreach (IEquipment eq in equipment)\n    {\n        Console.WriteLine($\"Le\u00edr\u00e1s: {eq.GetDescription()}\\t\" +\n            $\"\u00c9letkor: {eq.GetAge()}\\t\u00c9rt\u00e9ke: {eq.GetPrice()}\");\n    }\n}\n

                        \u00cdgy m\u00e1r sokkal informat\u00edvabb kimetet kapunk az alkalmaz\u00e1s futtat\u00e1sakor:

                        "},{"location":"labor/1-model-es-kod-kapcsolata/#konstruktor-kodduplikacio","title":"Konstruktor k\u00f3dduplik\u00e1ci\u00f3","text":"

                        A k\u00f3dunkat \u00e1ttekintve m\u00e9g egy helyen tal\u00e1lunk k\u00f3dduplik\u00e1ci\u00f3t. Valamennyi EquipmentBase lesz\u00e1rmazott (HardDisk, SoundCard) konstruktor\u00e1ban ott van ez a k\u00e9t sor:

                         this.yearOfCreation = yearOfCreation;\n this.newPrice = newPrice;\n

                        Ha belegondolunk, ezek a yearOfCreation \u00e9s newPrice tagok az \u0151sben vannak defini\u00e1lva, \u00edgy egy\u00e9bk\u00e9nt is az \u0151 felel\u0151ss\u00e9ge kellene legyen ezek inicializ\u00e1l\u00e1sa. Vegy\u00fcnk is fel egy megfelel\u0151 konstruktort az EquipmentBase-ben:

                        EquipmentBase.cs
                        public EquipmentBase(int yearOfCreation, int newPrice)\n{\n    this.yearOfCreation = yearOfCreation;\n    this.newPrice = newPrice;\n}\n

                        A HardDisk \u00e9s SoundCard lesz\u00e1rmazottak konstruktor\u00e1nak t\u00f6rzs\u00e9b\u0151l vegy\u00fck ki a k\u00e9t tag inicializ\u00e1l\u00e1s\u00e1t, helyette a base kulcssz\u00f3val hivatkozva h\u00edvjuk meg az \u0151s konstruktor\u00e1t:

                        HardDisk.cs
                        public HardDisk(int yearOfCreation, int newPrice, int capacityGB)\n    : base(yearOfCreation, newPrice)\n{\n    this.capacityGB = capacityGB;\n}\n
                        SoundCard.cs
                        public SoundCard(int yearOfCreation, int newPrice)\n    : base(yearOfCreation, newPrice)\n{\n}\n
                        "},{"location":"labor/1-model-es-kod-kapcsolata/#ertekeles","title":"\u00c9rt\u00e9kel\u00e9s","text":"

                        Az interf\u00e9sz \u00e9s absztrakt \u0151s egy\u00fcttes haszn\u00e1lat\u00e1val siker\u00fclt a legkevesebb kompromisszummal j\u00e1r\u00f3 megold\u00e1st kidolgoznunk:

                        • IEquipment interf\u00e9szk\u00e9nt hivatkozva egys\u00e9gesen tudjuk kezelni az alkatr\u00e9szek valamennyi t\u00edpus\u00e1t, m\u00e9g azokat is, melyekn\u00e9l az \u0151soszt\u00e1ly meg volt k\u00f6tve (puszt\u00e1n absztrakt \u0151s haszn\u00e1lat\u00e1val ezt nem tudtuk volna el\u00e9rni).
                        • Az EquipmentBase absztrakt \u0151s bevezet\u00e9s\u00e9vel egy kiv\u00e9telt\u0151l eltekintve a k\u00fcl\u00f6nb\u00f6z\u0151 alkatr\u00e9szt\u00edpusokra k\u00f6z\u00f6s k\u00f3dot fel tudtuk vinni egy k\u00f6z\u00f6s \u0151sbe, \u00edgy el tudtuk ker\u00fclni a k\u00f3dduplik\u00e1ci\u00f3t.
                        • Az EquipmentBase absztrakt \u0151s bevezet\u00e9s\u00e9vel alap\u00e9rtelmezett implement\u00e1ci\u00f3t tudunk megadni az \u00fajonnan bevezetett IEquipment m\u0171veletek eset\u00e9ben (pl. GetDescripton), \u00edgy nem vagyunk r\u00e1k\u00e9nyszer\u00edtve, hogy minden IEquipment implement\u00e1ci\u00f3s oszt\u00e1lyban meg kelljen azt adni.

                        Z\u00e1r\u00e1sk\u00e9ppen vess\u00fcnk egy pillant\u00e1st megold\u00e1sunk UML (szer\u0171) oszt\u00e1lydiagramj\u00e1ra:

                        C# 11 - Statikus interf\u00e9szek

                        A C# 11 leg\u00fajabb \u00fajdons\u00e1ga a statikus interf\u00e9sz tagok defini\u00e1l\u00e1sa, amivel olyan tagokat k\u00f6vetelhet\u00fcnk meg az implement\u00e1l\u00f3 oszt\u00e1lyt\u00f3l, amelyek nem az objektum p\u00e9ld\u00e1nyra vonatkoznak, hanem az oszt\u00e1lynak kell egy adott statikus taggal rendelkeznie. B\u0151vebben

                        "},{"location":"labor/1-model-es-kod-kapcsolata/#megjegyzes-opcionalis-hazi-gyakorlo-feladat","title":"Megjegyz\u00e9s - opcion\u00e1lis h\u00e1zi gyakorl\u00f3 feladat","text":"

                        Jelen megold\u00e1sunk nem t\u00e1mogatja az alkatr\u00e9szspecifikus adatok (pl. HardDisk eset\u00e9ben a kapacit\u00e1s) megjelen\u00edt\u00e9s\u00e9t a list\u00e1z\u00e1s sor\u00e1n. Ahhoz, hogy ezt meg tudjuk tenni, az alkatr\u00e9sz adatok form\u00e1zott stringbe \u00edr\u00e1s\u00e1t az EqipmentInventory oszt\u00e1lyb\u00f3l az alkatr\u00e9sz oszt\u00e1lyokba kellene vinni, a k\u00f6vetkez\u0151 elveknek megfelel\u0151en:

                        • Bevezethet\u00fcnk ehhez az IEquipment interf\u00e9szbe egy GetFormattedString m\u0171veletet, mely egy string t\u00edpus\u00fa objektummal t\u00e9r vissza. Alternat\u00edv megold\u00e1s lehet, ha a System.Object ToString() m\u0171velet\u00e9t defini\u00e1ljuk fel\u00fcl. .NET-ben ugyanis minden t\u00edpus implicit m\u00f3don a System.Object-b\u0151l sz\u00e1rmazik, aminek van egy virtu\u00e1lis ToString() m\u0171velete.
                        • Az EquipmentBase-ben meg\u00edrjuk a k\u00f6z\u00f6s tagok (le\u00edr\u00e1s, \u00e1r, kor) stringbe form\u00e1z\u00e1s\u00e1t.
                        • Amennyiben egy alkatr\u00e9sz t\u00edpusspecifikus adattal is rendelkezik, akkor oszt\u00e1ly\u00e1ban override-oljuk a stringbe form\u00e1z\u00f3 f\u00fcggv\u00e9nyt: ennek a f\u00fcggv\u00e9nynek egyr\u00e9szt meg kell h\u00edvnia az \u0151s v\u00e1ltozat\u00e1t (a base kulcssz\u00f3 haszn\u00e1lat\u00e1val), majd ehhez hozz\u00e1 kell f\u0171zni a saj\u00e1t form\u00e1zott adatait, \u00e9s ezzel a stringgel kell visszat\u00e9rnie.
                        "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/","title":"1. Relationship between the model and the code","text":""},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#the-goal-of-the-exercise","title":"The goal of the exercise","text":"

                        The goal of the exercise:

                        • Getting to know the students/trainer
                        • Clarification of the requirements for exercises
                        • Getting started with Visual Studio and .NET application development.
                        • Building a simple Hello World .NET application, C# basics
                        • Illustrating the relationship between UML and code
                        • The interface and the abstract primitive class application technique
                        For teachers

                        While there will certainly be some students who have used the Visual Studio environment before, in Prog2 (C++) or for other reasons, there will almost certainly be others who have not used it or who remember it less. The goal here is to get familiar with the interface, so as you work through the exercises, you will be introduced to the things you use (e.g. Solution Explorer, F5 running, using breakpoints, etc.) to build your first C# application.

                        "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#prerequisites","title":"Prerequisites","text":"

                        The tools needed to carry out the exercise:

                        • Visual Studio 2022

                        The latest version of Visual Studio should be installed. The Community Edition, Professional and Enterprise versions are also suitable. The Community Edition is free and can be downloaded from the Microsoft website. The Professional is paid, but it is also available free of charge to students of the university (on the website, as part of the Azure Dev Tools for Teaching programme).

                        Visual Studio Class Diagram support

                        For some of the exercises in this exercise (and also for the first homework) we will use the Visual Studio Class Designer support. Visual Studio does not always add the Class Designer component during installation. If it is not possible to add a Class Diagram to your Visual Studio project (because the Class Diagram is not listed in the list of the window that appears during the Add New Item command - more on this later in this guide), you will need to install the Class Diagram component later:

                        1. Start the Visual Studio installer (e.g. by typing \"Visual Studio Installer\" in the Windows Start menu).
                        2. In the window that appears, select the \"Individual components\" tab
                        3. In the search box, type \"class designer\" and then make sure that \"Class Designer\" is unchecked in the filtered list.

                        What you should check out:

                        • The exercise does not include a lecture on the subject. At the same time, the exercise builds on basic UML knowledge and the basics of mapping UML class diagrams to code.
                        "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#course-of-exercise","title":"Course of exercise","text":"

                        The trainer will summarise the requirements for the exercises at the beginning of the exercise:

                        • Most of these can be found in the fact sheet
                        • Information on homework is available on the subject's website.

                        Using Visual Studio development tool, we will build .NET applications in C#. C# is similar to Java, we will gradually learn the differences. The tutorial is guided, with instructions from the tutor, and the tasks are done together.

                        "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#solution","title":"Solution","text":"Download the finished solution

                        It is essential that you follow the lab guide during the lab, it is forbidden (and pointless) to download the ready-made solution. However, during subsequent self-practice, it can be useful to review the ready-made solution, so we make it available.

                        The solution is available on GitHub. The easiest way to download it is to clone it from the command line to your machine using the git clone command:

                        git clone https://github.com/bmeviauab00/lab-modellkod-kiindulo -b solved

                        You need to have git installed on your machine, more information here.

                        "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#1-task-build-a-hello-world-net-console-application","title":"1. Task - Build a \"Hello world\" .NET console application","text":"

                        The task is to create a C# console application that prints the text \"Hello world!\" to the console.

                        The application is written in C#. The compiled application is run by the .NET runtime. The theoretical background of compiling/running and the basics of .NET are covered in the first lecture.

                        The steps to create a solution and a project within it in Visual Studio 2022:

                        1. Start a new project wizard, which can be done in two ways
                          • Using the startup window
                            1. Launch Visual Studio
                            2. In the right-hand sidebar of the launch window that appears Create new project
                          • Already running in Visual Studio
                            1. File / New-Project
                        2. In the Create new project wizard, select the Console app (and NOT the Console app (.NET Framework) template, including the C# one. That it is C# is indicated by the top left corner of the template icon. If you don't see it in the list, you have to search/filter for it. You can search for it by typing \"console\" in the top search bar. Or by using the drop-down boxes below: in the first (language selector) \"C#\", in the third (project type selector) \"Console\".

                          Creating a project

                        3. Next button at the bottom of the wizard window, on the next wizard page:

                          1. Project name: Hello World
                          2. Location: in the labs, work in the c:\\work\\ folder, you have write access to it.
                          3. Solution name: Hello World (this should be written in by the time we get here)
                          4. Place solution and project in the same directory: no tick (but not particularly significant).
                          5. Next button at the bottom of the wizard window, on the next wizard page:

                            1. Framework: .NET 8 (Long-term support).
                            2. Check the \"Do not use top level statements\" checkbox (we'll explain this in a moment).
                          6. The project also creates a new solution, whose structure can be viewed in the Visual Studio Solution Explorer window. A solution can consist of several projects, and a project can consist of several files. A solution is a summary of the entire working environment (it includes a file with the extension .sln), while the output of a project is typically a file .exe or .dll, i.e. a component of a complex application/system. The project file extension for C# applications is .csproj.

                            The content of our Program.cs file is as follows:

                            Program.cs
                            namespace HelloWorld\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n        }\n    }\n}\n

                            Take a Console.ReadKey() line:

                            namespace HelloWorld\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n            Console.ReadKey();\n        }\n    }\n}\n
                            1. Run the application (e.g. using the F5 key).

                              The structure of the code is very similar to Java and C++. Our classes are organised into namespaces. You can define a namespace with the keyword namespace. You can \"scope\" namespaces with the using keyword. e.g:

                              using System.Collections.Generic;\n
                            2. In a console C# application, you specify the entry point of your application by writing a static function called Main. Our class name can be anything, VS generated a class called Program in our case. The parameter list of the Main function is bound: either no parameters are given, or a string[] is given, in which the command line arguments are given at runtime.

                            3. in .NET, the Console class of the System namespace is used to handle standard input and output. With the static operation WriteLine you can write a line, with ReadKey you can wait for a key to be pressed.

                            Top level statements, Implicit and static usings and namespaces

                            When the project was created, we previously checked the \"Do not use top level statements\" checkbox. If we had not done this, we would have found only one meaningful line in our Program.cs file:

                            // See https://aka.ms/new-console-template for more information\nConsole.WriteLine(\"Hello World!\");\n

                            This is functionally equivalent to the code above containing the Program class and its Main function. Let's look at what makes this possible (you can read more about them here https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/top-level-statements, both new in C# 10):

                            • Top level statements. The idea is that you can write code directly in a single source file without any class/Main and other function definitions in the project. In this case, behind the scenes, the compiler puts this into a static Main function of a class we don't see. The motivation for its introduction was to reduce boilerplate code for very simple, \"script-like\" applications.
                            • Implicit global usings. Depending on exactly what project type you have created, certain base namespaces will be automatically using behind the scenes in all source files (the compiler uses the global using directive for this). The point is: this way, developers don't have to use certain frequently used namespaces (e.g. System.IO, System.Collections.Generic, etc.) as source files.
                            • Static using. It is possible to use static classes instead of namespaces in C#, so it is not important to write them when using them. A common case is the use of the Console or Math class.

                              using static System.Console;\n\nnamespace ConsoleApp12\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            WriteLine(\"Hello, World!\");\n        }\n    }\n}\n
                            • File-level namespaces. In C# 10, we also get a simplification when declaring namespaces, because it is no longer mandatory to use brackets, so the given namespace will be valid for the whole file, e.g.:

                              namespace HelloWorld;\n\ninternal class Program\n{\n    // ...\n}\n

                            Inconsistent visibility or inconsistent accessibility error

                            During the semester, you may encounter translation error messages complaining about inconsistent visibility or inconsistent accessibility when implementing programming tasks. This phenomenon is due to the possibility to control the visibility of each type (class, interface, etc.) in a .NET environment:

                            • internal or no visibility is specified: the type is visible only inside the assembly (.exe, .dll)/project
                            • public: the type is visible to other assemblies/projects

                            The easiest way to avoid this error is to define all our types as public, e.g.:

                            public class HardDisk\n{\n    // ...\n}\n
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#theoretical-overview","title":"Theoretical overview","text":"

                            The sub-chapters do not contain exercises, but provide students with an introduction to the related theoretical topics, illustrated with examples.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#a-theory-of-the-relationship-between-the-uml-class-diagram-and-code-student","title":"A) Theory of the relationship between the UML class diagram and code [student]*","text":"

                            The material is available here: The relationship between the UML class diagram and code. The relationship between the UML class diagram and code. This topic was covered in the previous semester in the Software Engineering course.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#b-interface-and-abstract-parent-class-student","title":"B) Interface and abstract (parent) class [student]*","text":"

                            The material is available here: Interface and abstract (base) class. Interface and abstract (base) class.

                            Topics:

                            • Abstract class concept and definition in C#
                            • Interface concepts and definitions in C#
                            • Comparison of abstract base class and interface
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#2-task-illustrate-the-relationship-between-uml-and-code","title":"2. Task - Illustrate the relationship between UML and code","text":""},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#task-description-equipment-inventory","title":"Task description - Equipment inventory","text":"

                            Task: We were asked to develop a computer parts inventory application. Read more:

                            • You need to be able to handle different types of parts. Initially, HardDisk, SoundCard and LedDisplay types should be supported, but the system should be easily extensible to new types.
                            • The data related to the parts are: year of purchase, age (calculated), purchase price and current price (calculated), but may also include type-specific data (e.g. capacity for HardDisk).
                            • The actual price depends on the type of part, the purchase price and the year of production of the part. For example, the older the part, the bigger the discount, but the discount depends on the part type.
                            • You must be able to list the parts in stock.
                            • The LedDisplay class must be derived from an DisplayBase class, and the source code of the DisplayBase class cannot be changed. In this example this does not make much sense, but in practice we often encounter similar situations where the framework/platform we are using requires us to derive from a built-in class. Typically, this is the case when working with windows, forms, custom control types: we have to derive them from the framework's built-in classes, and we don't have (or at least certainly don't want to change) the source code of the framework - e.g. Java, .NET. In our example, we simulate this situation by specifying a derivation from DisplayBase.

                            The implementation is simplified considerably: the parts are only stored in memory, and the listing is as simple as possible, simply by writing the data of the registered parts to the console.

                            During the initial discussions, we receive the following information from the client: an internal staff member has already started the development, but due to lack of time, they have only reached a half-finished solution. Part of our task is to understand the semi-finished solution and to implement the task from there.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#class-diagram","title":"Class Diagram","text":"

                            Let's open the source code solution from our customer, which we can do by following the steps below.

                            To do this, clone the Git repository of the initial project, available online on GitHub, to a new folder of its own within C:\\Work: e.g: C:\\Work\\NEPTUN\\lab1. In this new folder, open a command line or powershell and run the following git command:

                            git clone https://github.com/bmeviauab00/lab-modellkod-kiindulo.git\n

                            Note

                            You will read more about Git as a source code management system in the context of the first homework assignment.

                            Open the Visual Studio solution src/EquipmentInventory.sln in the cloned folder.

                            In Solution Explorer, run through the files by eye. It would help to understand the relationships between classes by displaying them on a class diagram. Let's include a class diagram in our project. In the Solution Explorer, right-click on the project (not the solution!), select Add/New Item from the pop-up menu, then in the window that appears, select Class Diagram, enter Main.cd as the name of the diagram at the bottom of the window, and OK-close the window.

                            Missing Class Diagram template

                            If the Class Diagram item does not appear in the list, then the appropriate component of VS is not installed. You can read more about this in the Prerequisites section of this document.

                            The chart file Main.cd will then appear in Solution Explorer, double-click on it to open it. Our chart is currently empty. From Solution Explorer, drag&drop the .cs source files onto the diagram. VS then looks at what classes are in these source files and decomposes them into UML classes. Build the layout as shown in the following figure (you can display the members of the classes by clicking on the double arrow in the top right corner of their rectangle):

                            Starting class diagram

                            You can also view the source code for the classes, either by double-clicking on the corresponding class on the diagram or by opening the .cs files from Solution Explorer. Here's what we see:

                            • The SoundCard, HardDisk and LedDisplay classes are relatively well developed, with the necessary attributes and query functions.
                            • The LedDisplay is derived from the DisplayBase class as required.
                            • EquipmentInventory is responsible for the inventory of parts in stock, but practically none of this is implemented.
                            • We find an interface IEquipment with operations GetAge and GetPrice
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#equipmentinventory","title":"EquipmentInventory","text":"

                            Let's start working on a solution. First, let's lay down the basic concepts. In the EquipmentInventory class, we store a heterogeneous collection of different types of equipment. This is the key to consistent parts management, so that our solution can be easily extended with new parts types.

                            As discussed earlier, unified management can be achieved either by implementing a common base class or a common interface. In our case, the common base class (e.g. EquipmentBase) seems to be dropped, because by introducing it, the LedDisplay class would have two base classes: the mandatory DisplayBase, and the EquipmentBase that we introduce for uniform management. This is not possible, in a .NET environment a class can have only one base class. The solution to modify DisplayBase to be derived from EquipmentBase is not possible according to our requirement (it was a requirement that its source code cannot be modified). This leaves the interface-based approach. This was probably the conclusion of the previous developer of the application, which is why he introduced the IEquipment interface.

                            Add a generic list of items of type IEquipment (not property but field!) to the EquipmentInventory class. Its visibility - in an effort to be unified - should be private. The name should be equipment (no \"s\" at the end, in English the plural of equipment is also equipment). To add a member variable, we use the Visual Studio Class Details window. If the window is not visible, it can be displayed by selecting View / Other Windows / Class Details.

                            Class Details

                            The member variable type is therefore List. The type of .NET List is a dynamically stretching generic array (like ArrayList in Java). Looking at the EquipmentInventory class in the diagram, we see that only the name of the member variable is displayed, not the type. Right-click on the background of the diagram and select Display Full Signature from the Change Members Format menu. The chart will then display the type of member variables and the full signature of the operations.

                            EquipmentInventory

                            By double-clicking on the EquipmentInventory class, you can navigate to the source code, and as you can see, it does indeed appear in the code as a member variable of type list:

                            class EquipmentInventory\n{\n    private List<IEquipment> equipment;\n

                            On the one hand, we're happy about this because Visual Studio supports round-trip engineering: changes to the model are immediately reflected in the code, and vice versa. On the other hand, we have previously discussed that if a class has a collection of members from another class, then it \"fits\" in the UML model as a type 1-more association relation between the two classes. This is not yet the case in our model. Fortunately, the VS modelling interface can be made to display this type of connection in this form. To do this, right-click on the equipment tag variable on the diagram and select Show as Collection Association from the menu. The IEquipment interface should then be moved to the right to allow enough space on the diagram to display the association relationship and the role on the relationship:

                            Collection association

                            The double arrow ending on the \"plural\" side is not standard UML, but don't be too sad about it, it's not important. We are certainly pleased that the arrow representing the relationship at the end of the IEquipment role shows the name (and even the exact type) of the member variable.

                            Navigate to the source code of EquipmentInventory and write the constructor that initializes the equipment collection

                            public EquipmentInventory()\n{\n    equipment = new List<IEquipment>();\n}\n

                            Then write the ListAll method, which prints the age of the elements and their current values:

                            public void ListAll()\n{\n    foreach (IEquipment eq in equipment)\n    {\n        Console.WriteLine($\"Age: {eq.GetAge()}\\t\u00c9rt\u00e9ke: {eq.GetPrice()}\");\n    }\n}\n

                            Iterate through the elements using the foreach statement. When using the foreach statement, the in keyword should be followed by a collection and preceded by a variable declaration (in this case IEquipment eq), where type is the element type of the collection. In each iteration, this variable takes the iteration value of the collection.

                            Console.WriteLine is either a simple string or, as in this case, a formatting string. The substitutions are solved by string interpolation: the values to be substituted must be given between `. If string interpolation is used, the string must start with$`.

                            Write a function called AddEquipment that adds a new device to the inventory:

                            public void AddEquipment(IEquipment eq)\n{\n     equipment.Add(eq);\n}\n
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#iequipment-implementers","title":"IEquipment implementers","text":"

                            We have previously decided to use the IEquipment interface to manage the different component types in a uniform way. In our example, both SoundCard and HardDisk have GetAge() and GetPrice() methods, yet we cannot manage them in a unified way (e.g., store them in a common list). To do this, we need to get both classes to implement the IEquipment interface. Change their source:

                            public class SoundCard : IEquipment\n
                            public class HardDisk : IEquipment\n

                            Then we need to implement the methods in the IEquipment interface in the SoundCard and HardDisk classes. We find that there is nothing to do with this now, the GetPrice and GetAge functions are already written in both places.

                            As a test, in our Main function in Program.cs, create an EquipmentInventory object, populate it with HardDisk and SoundCard objects, and then list the object on the console. If 2021 is not the current year, in the following rows, copy the year 2021 to the current year and the year 2020 to a smaller number!

                            static void Main( string[] args )\n{\n    EquipmentInventory ei = new EquipmentInventory();\n\n    ei.AddEquipment(new HardDisk(2021, 30000, 80));\n    ei.AddEquipment(new HardDisk(2020, 25000, 120));\n    ei.AddEquipment(new HardDisk(2020, 25000, 250));\n\n    ei.AddEquipment(new SoundCard(2021, 8000));\n    ei.AddEquipment(new SoundCard(2020, 7000));\n    ei.AddEquipment(new SoundCard(2020, 6000));\n\n    ei.ListAll();\n}\n

                            Running the application, we find that although our solution is rudimentary, it works:

                            Console output

                            Continue with the LedDisplay class. The DisplayBase base class source code cannot be modified due to requirements. But this doesn't cause any problems, our LedDisplay class will implement the IEquipment interface, so modify the code accordingly:

                            public class LedDisplay : DisplayBase, IEquipment\n

                            In the LedDisplay class, the functions in the interface must already be written:

                            public double GetPrice()\n{\n    return this.price;\n}\n\npublic int GetAge()\n{\n    return DateTime.Today.Year - this.manufacturingYear;\n}\n

                            Let's extend our Main function by adding two LedDisplay objects to our set (again, if 2021 is not the current year, we should rewrite 2021 to the current year in the following lines, and 2020 to a smaller number!

                            ei.AddEquipment(new LedDisplay(2020, 80000, 17, 16));\nei.AddEquipment(new LedDisplay (2021, 70000, 17, 12));\n\nei.ListAll();\nConsole.ReadKey();\n

                            As a test, run the application.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#3-task-application-of-the-interface-and-the-abstract-primitive-class","title":"3. Task - Application of the interface and the abstract primitive class","text":""},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#interface-problems","title":"Interface problems","text":"

                            Evaluate our current interface-based solution.

                            One of the main problems is that our code is full of code duplication that destroys maintainability and extensibility:

                            • The yearOfCreation and newPrice tags are common to all part types (except the special LedDisplay), and must be copy-pasted when a new type is introduced.
                            • The implementation of the GetAge function is the same for all component types (except for the special LedDisplay), also copy-paste \"propagated\".
                            • The lines of the constructors yearOfCreation and newPrice initializing tags are also duplicated in each class.

                            Although this code duplication does not seem significant at the moment, the situation is getting worse as new component types are introduced, and it is better to prevent future pains in time.

                            The other problem is that the listing of parts data is currently painfully incomplete, with no part type (only age and price). To display the type, the IEquipment interface must be extended, e.g. by introducing an operation called GetDescription. Let's add a GetDescription function to the interface!

                            public interface IEquipment\n{\n    double GetPrice();\n    int GetAge();\n    string GetDescription();\n}\n

                            Then every class implementing the IEquipment interface would have to implement this method, which is a lot of work for many classes (and often not even feasible for a multi-component application, i.e. one with several DLLs, when they are not in the hands of a single developer). Run the Build command to check that after adding GetDescription, you get compilation errors in three places.

                            Specifying default implementation in interface

                            It is worth knowing that starting from C# 8 (or .NET or .NET Core runtime, not supported under .NET Framework), interface operations can be given default implementation (default interface methods), so to solve the above problem you don't need an abstract class, but interface can no longer have member variables. More information here: default interface methods.

                            public interface IEquipment\n{\n    double GetPrice();\n    int GetAge();\n    string GetDescription() { return \"EquipmentBase\"; }\n}\n
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#abstract-class","title":"Abstract class","text":"

                            A solution to both problems is the introduction of a common abstract base class (except for the LedDisplay class, which we will come back to). We can move the code common to descendants into it, and provide a default implementation for the newly introduced GetDescription operation. Let our new abstract base class be called EquipmentBase. The question is whether the IEquipment interface is still needed, or whether it can be completely replaced by the new EquipmentBase class. We need to keep the IEquipment interface, because we cannot derive our LedDisplay class from EquipmentBase: it already has a mandatory base class, DisplayBase: for this reason, EquipmentInventory in our enhanced solution also refers to the various components as IEquipment interface.

                            Let's start the transformation. Let our class diagram be the active tab. From the Toolbox, drag&drop an Abstract Class element onto the diagram, name it EquipmentBase.

                            Toolbox - abstract class

                            In the following, we need to make the SoundCard and HardDisk classes derive from EquipmentBase (LedDisplay already has another base class, so we cannot do this there). To do this, select the Inheritance link in the Toolbox, then draw a line from the child class to the base class for both SoundCard and HardDisk.

                            In the next step, let's modify the code so that HardDisk and SoundCard do not implement the IEquipment interface separately, but rather their common base class EquipmentBase implement it once. To do this, modify the EquipmentBase class to implement the interface (either by drawing an inheritance link from EquipmentBase to IEquipment on the diagram, or by modifying the source code of EquipmentBase). Delete the implementation of IEquipment from the HardDisk and SoundCard classes (the base class already implements it).

                            The relevant parts of our diagram and source code will then look like this:

                            EquipmentBase and HardDisk/SoundCard

                            public abstract class EquipmentBase : IEquipment\n
                            public class HardDisk : EquipmentBase\n
                            public class SoundCard : EquipmentBase\n

                            Our code is not yet turning, for several reasons. The EquipmentBase implements the IEquipment interface, but it does not yet implement the interface operations. Either generate the methods using the smart tag, or type them according to the following principles:

                            • The newPrice and yearOfCreation are duplicated in the HardDisk and SoundCard classes: move (not copy!) them to the common EquipmentBase base class and give protected visibility.
                            • The GetAge operation is duplicated in the HardDisk and SoundCard classes, delete the implementation from these and move it to the EquipmentBase class.
                            • The GetPrice operation is included in the base class as an abstract operation. This is a deliberate design decision, so we force descendant classes to override this operation anyway.
                            • In the case of GetDescription, the opposite is true: it is defined as virtual (and not abstract), i.e. we provide an implementation in the base class. This way, descendants are not forced to override the operation.

                            The code corresponding to the above is:

                            public abstract class EquipmentBase : IEquipment\n{\n    protected int yearOfCreation;\n    protected int newPrice;\n\n    public int GetAge()\n    {\n        return DateTime.Today.Year - yearOfCreation;\n    }\n\n    public abstract double GetPrice();\n\n    public virtual string GetDescription()\n    {\n        return \"EquipmentBase\";\n    }\n}\n

                            Some additional thoughts on the code fragment:

                            • For abstract classes, the keyword abstract must be written before the word class.
                            • For abstract operations, the keyword abstract must be specified
                            • in a .NET environment, you can control whether an operation is virtual or not. In this respect, it is similar to C++. To make an operation virtual, the keyword virtual must be specified for the operation. Reminder: define an operation as virtual if its descendants overdefine it. Only then is it guaranteed that the descendant version will be called when invoking the given operation on an ancestor reference.
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#descendants","title":"Descendants","text":"

                            In the next step, let's move on to the EquipmentBase descendants. When overriding abstract and virtual operations in C#, you must specify the override keyword in the descendant. First, the GetPrice operation is redefined:

                            HardDisk.cs
                            public override double GetPrice()\n{\n    return yearOfCreation < (DateTime.Today.Year - 4)\n        ? 0\n        : newPrice - (DateTime.Today.Year - yearOfCreation) * 5000;\n}\n
                            SoundCard.cs
                            public override double GetPrice()\n{\n    return yearOfCreation < (DateTime.Today.Year - 4)\n        ? 0 \n        : newPrice - (DateTime.Today.Year - yearOfCreation) * 2000;\n}\n

                            In the next step, the GetDescription operation is written in the HardDisk and SoundCard classes. Since the virtual function of the base class is being overridden here, the override keyword must also be specified:

                            HardDisk.cs
                            public override string GetDescription()\n{\n    return \"Hard Disk\";\n}\n
                            SoundCard.cs
                            public override string GetDescription()\n{\n    return \"Sound Card\";\n}\n

                            One might ask why the designers of the C# language decided to add an extra keyword to the definition of operations, which was not necessary in the case of C++. The reason is simple: the code is more expressive. Looking at the descendant code, the word override immediately makes it clear whether this operation is abstract or virtual in one of the base classes, without having to look at the code of all the ancestors.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#base-class-of-leddisplay","title":"Base class of LedDisplay","text":"

                            The base class of our LedDisplay class is bound, its code cannot be modified, so we cannot derive it from EquipmentBase. We cannot delete the GetAge operation, this code duplication is preserved here (but only for LedDisplay, which is only one class among many!).

                            Note

                            In fact, with a little extra work we could get rid of this duplication. This would require a static helper function in one of the classes (e.g. EquipmentBase), which would get the year of manufacture and return the age. EquipmentBase.GetAge and LedDisplay.GetAge would use this helper function to produce their output.

                            In our LedDisplay class, we are yet to write GetDescription:

                            LedDisplay.cs
                            public string GetDescription()\n{\n    return \"Led Display\";\n}\n

                            Note that we have NOT specified the override keyword here. When an interface function is implemented, override is not required/allowed to be written.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#use-getdescription","title":"Use GetDescription","text":"

                            Modify the EquipmentInventory.ListAll operation to also write the description of the items to the output:

                            EquipmentInventory.cs
                            public void ListAll()\n{\n    foreach (IEquipment eq in equipment)\n    {\n        Console.WriteLine($\"Description: {eq.GetDescription()}\\t\" +\n            $\"Age: {eq.GetAge()}\\t\u00c9rt\u00e9ke: {eq.GetPrice()}\");\n    }\n}\n

                            This gives a more informative output when the application is run:

                            Console output

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#constructor-code-duplication","title":"Constructor code duplication","text":"

                            Looking at our code, there is one more duplication. All EquipmentBase descendants (HardDisk, SoundCard) have these two lines in their constructor:

                             this.yearOfCreation = yearOfCreation;\n this.newPrice = newPrice;\n

                            If you think about it, these yearOfCreation and newPrice members are defined in the base class, so it should be his responsibility to initialize them anyway. Let's add a corresponding constructor in EquipmentBase:

                            EquipmentBase.cs
                            public EquipmentBase(int yearOfCreation, int newPrice)\n{\n    this.yearOfCreation = yearOfCreation;\n    this.newPrice = newPrice;\n}\n

                            Remove the initialization of the two members from the constructor of the descendants HardDisk and SoundCard, and instead invoke the base class\u2019s constructor by referencing the base keyword:

                            HardDisk.cs
                            public HardDisk(int yearOfCreation, int newPrice, int capacityGB)\n    : base(yearOfCreation, newPrice)\n{\n    this.capacityGB = capacityGB;\n}\n
                            SoundCard.cs
                            public SoundCard(int yearOfCreation, int newPrice)\n    : base(yearOfCreation, newPrice)\n{\n}\n
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#evaluation","title":"Evaluation","text":"

                            By using a combination of interface and abstract base class, we have managed to develop the solution with the least compromise:

                            • By referring to IEquipment as an interface, we can uniformly handle all types of parts, even those where the base class was bound (using abstract base classes alone would not have achieved this).
                            • By introducing the EquipmentBase abstract base class, we were able to put the code common to different part types into a common base, with one exception, thus avoiding code duplication.
                            • By introducing the EquipmentBase abstract ancestor, we can specify a default implementation for newly introduced IEquipment operations (e.g. GetDescripton), so we are not forced to specify it in every IEquipment implementation class.

                            Finally, let's take a look at the UML (like) class diagram of our solution:

                            Ultimate class diagram

                            Static interfaces

                            The latest addition to C# 11 is the definition of static interface members, which allows you to require an implementing class to have members that do not refer to the object instance, but rather the class must have a specific static member. Read more

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_eng/#note-optional-homework-exercise","title":"Note - optional homework exercise","text":"

                            Our solution does not support the display of component specific data (e.g. capacity for HardDisk) during listing. To do this, the writing of component data to a formatted string should be moved from the EqipmentInventory class to the component classes, following the principles below:

                            • To do this, we can introduce an GetFormattedString operation in the IEquipment interface, which returns an object of type string. Alternatively, you can override the ToString()operation ofSystem.Object. indeed, in .NET, all types are implicitly derived from System.Object, which has a virtual ToString() operation.
                            • In EquipmentBase we write the formatting of the common tags (description, price, age) into a string.
                            • If a component also has type-specific data, then its class overrides the function that formats it into a string: this function must first call its ancestor (using the base keyword), then append its own formatted data to it, and return with this string.
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/","title":"1. Beziehung zwischen dem Modell und dem Code","text":""},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#das-ziel-der-ubung","title":"Das Ziel der \u00dcbung","text":"

                            Das Ziel der \u00dcbung:

                            • Kennenlernen der Studenten/Studentinnen und des \u00dcbungsleiters/ins
                            • Kl\u00e4rung der Anforderungen f\u00fcr \u00dcbungen
                            • Erste Schritte mit Visual Studio und der Entwicklung von .NET-Anwendungen.
                            • Erstellen einer einfachen Hello World .NET-Anwendung, C#-Grundlagen
                            • Veranschaulichung der Beziehung zwischen UML und Code
                            • Anwendungstechnik der Schnittstelle und der abstrakte Basisklasse
                            F\u00fcr \u00dcbungsleiter/in

                            Sicherlich gibt es einige Teilnehmer, die Visual Studio bereits in Prog2 (C++) oder aus anderen Gr\u00fcnden verwendet haben, aber es wird auch einige geben, die es noch nicht verwendet haben oder sich weniger daran erinnern. Das Ziel ist in diesem Fall, die Benutzeroberfl\u00e4che kennenzulernen. So w\u00e4hrend der L\u00f6sung der \u00dcbungen, sollten die benutzte Dinge (z. B. Solution Explorer, Ausf\u00fchren mit F5, Verwenden von Haltepunkten usw.) auch besprochen werden.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#voraussetzungen","title":"Voraussetzungen","text":"

                            Die f\u00fcr die Ausf\u00fchrung der \u00dcbung ben\u00f6tigten Werkzeuge:

                            • Visual Studio 2022

                            Es sollte die neueste Version von Visual Studio installiert sein. Die Versionen Community Edition, Professional und Enterprise sind ebenfalls geeignet. Die Community Edition ist kostenlos und kann von der Microsoft-Website heruntergeladen werden. Der Professional ist kostenpflichtig, steht aber auch f\u00fcr Studenten der Universit\u00e4t kostenlos zur Verf\u00fcgung (auf der Website, im Rahmen des Programms Azure Dev Tools for Teaching).

                            Visual Studio Class Diagram support

                            F\u00fcr einige Aufgaben in dieser \u00dcbung (und auch f\u00fcr die erste Hausaufgabe) werden wir die Unterst\u00fctzung des Visual Studio Class Designer nutzen. Visual Studio f\u00fcgt die Komponente Class Designer w\u00e4hrend der Installation nicht immer hinzu. Wenn es nicht m\u00f6glich ist, ein Klassendiagramm zu Ihrem Visual Studio-Projekt hinzuzuf\u00fcgen (weil das Klassendiagramm nicht in der Liste des Fensters aufgef\u00fchrt ist, das w\u00e4hrend des Befehls Neues Element hinzuf\u00fcgen angezeigt wird - mehr dazu sp\u00e4ter in diesem Handbuch), m\u00fcssen Sie die Komponente Klassendiagramm sp\u00e4ter installieren:

                            1. Starten Sie das Visual Studio-Installationsprogramm (z. B. durch Eingabe von \"Visual Studio Installer\" im Windows-Startmen\u00fc).
                            2. W\u00e4hlen Sie in dem nun erscheinenden Fenster die Registerkarte \"Individual components\"
                            3. Geben Sie in das Suchfeld \"class designer\" ein und vergewissern Sie sich, dass \"Class Designer\" in der gefilterten Liste angekreuzt ist.

                            Was Sie sich ansehen sollten:

                            • Die \u00dcbung beinhaltet keine Vorlesung zu diesem Thema. Gleichzeitig baut die \u00dcbung auf grundlegendem UML-Kenntnisse und den Grundlagen der Abbildung von UML-Klassendiagrammen auf Code.
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#verlauf-der-ubung","title":"Verlauf der \u00dcbung","text":"

                            Der/die \u00dcbungsleiter/in fasst die Anforderungen f\u00fcr die \u00dcbungen am Anfang der \u00dcbung zusammen:

                            • Die meisten davon finden Sie in dem Merkblatt
                            • Informationen zu den Hausaufgaben finden Sie auf der Website des Fachs.

                            Mit dem Entwicklungsumgebung Visual Studio werden wir .NET-Anwendungen in C# erstellen. C# ist \u00e4hnlich wie Java, wir lernen stufenweise die Unterschiede. Die Aufgaben werden gemeinsam unter der Leitung des \u00dcbungsleiters/ins durchgef\u00fchrt.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#losung","title":"L\u00f6sung","text":"Laden Sie die fertige L\u00f6sung herunter

                            Es ist wichtig, dass Sie sich w\u00e4hrend des Praktikums an die Anleitung halten. Es ist verboten (und sinnlos), die fertige L\u00f6sung herunterzuladen. Allerdings kann es bei der anschlie\u00dfenden Selbstein\u00fcbung n\u00fctzlich sein, die fertige L\u00f6sung zu \u00fcberpr\u00fcfen, daher stellen wir sie zur Verf\u00fcgung.

                            Die L\u00f6sung ist auf GitHub verf\u00fcgbar. Der einfachste Weg, es herunterzuladen, ist, es von der Kommandozeile aus mit dem Befehl git clone auf Ihren Computer zu klonen:

                            git clone https://github.com/bmeviauab00/lab-modellkod-kiindulo -b solved

                            Sie m\u00fcssen Git auf Ihrem Rechner installiert haben, weitere Informationen hier.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#1-aufgabe-erstellen-einer-hello-world-net-konsolenanwendung","title":"1. Aufgabe - Erstellen einer \"Hello World\" .NET-Konsolenanwendung","text":"

                            Die Aufgabe ist die Erstellung einer C#-Konsolenanwendung, die den Text \"Hello world!\" auf der Konsole ausgibt.

                            Die Anwendung wird in C# geschrieben. Die kompilierte Anwendung wird von der .NET-Laufzeitumgebung ausgef\u00fchrt. In der ersten Vorlesung werden die theoretischen Hintergr\u00fcnde des Kompilierens/Ablaufens und die Grundlagen von .NET behandelt.

                            Die Schritte zum Erstellen einer Projektmappe und eines Projekts in Visual Studio 2022:

                            1. Starten Sie einen neuen \"Neues Projekt erstellen\" Dialogfeld, was auf zwei Arten geschehen kann
                              • Verwendung des Startfensters
                                1. Visual Studio starten
                                2. In der rechten Seitenleiste des erscheinenden Startfensters Create new project
                              • Bereits in Visual Studio ausgef\u00fchrt
                                1. File / New-Project
                            2. W\u00e4hlen Sie im Dialogfeld \"Neues Projekt erstellen\" die Vorlage \" Console app \" (und NICHT die Vorlage \" Console app (.NET Framework)\", einschlie\u00dflich der C#-Vorlage. Dass es sich um C# handelt, ist an der oberen linken Ecke des Vorlagensymbols zu erkennen. Wenn Sie es nicht in der Liste sehen, m\u00fcssen Sie es suchen/filtern. Sie k\u00f6nnen danach suchen, falls Sie in der oberen Suchleiste \"console\" eingeben. Oder verwenden Sie die Dropdown-Felder unten: im ersten (Sprachauswahl) \"C#\", im dritten (Projekttypauswahl) \"Console\".

                            3. Next-Taste am unteren Rand des Dialogfeldes \"Neues Projekt erstellen\", auf der n\u00e4chsten Seite:

                              1. Project name: Hello World
                              2. Location: In den Labors arbeiten wir im Ordner c:\\work\\<IhreName>, auf den Sie Schreibrechte haben.
                              3. Solution name: Hello World (dies sollte bis zu unserer Ankunft hier eingeschrieben sein)
                              4. Place solution and project in the same directory: kein H\u00e4kchen (aber nicht besonders wichtig).
                            4. Next-Taste am unteren Rand des Dialogfeldes \"Neues Projekt erstellen\", auf der n\u00e4chsten Seite:

                              1. Framework: .NET 8 (Langfristige Unterst\u00fctzung).
                              2. Aktivieren Sie das Kontrollk\u00e4stchen \"Do not use top level statements\" (wir werden dies gleich erkl\u00e4ren).

                            Das Projekt erstellt auch eine neue Projektmappe, deren Struktur im Visual Studio Solution Explorer-Fenster angezeigt werden kann. Eine L\u00f6sung kann aus mehreren Projekten bestehen, und ein Projekt kann aus mehreren Dateien bestehen. Ein Solution ist eine Zusammenfassung der gesamten Arbeitsumgebung (sie hat die Dateierweiterung .sln ), w\u00e4hrend die Ausgabe eines Projekts typischerweise eine Datei .exe oder .dll ist, d. h. eine Komponente einer komplexen Anwendung/eines komplexen Systems. Projektdateierweiterung f\u00fcr C#-Anwendungen .csproj.

                            Der Inhalt unserer Datei Program.cs ist die folgende:

                            Program.cs
                            namespace HelloWorld\n{\n    internal class  Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n        }\n    }\n}\n

                            Nehmen wir eine Console.ReadKey() Zeile aus:

                            namespace HelloWorld\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n            Console.ReadKey();\n        }\n    }\n}\n
                            1. F\u00fchren wir die Anwendung aus (z. B. mit der Taste F5 ).

                              Die Struktur des Codes ist sehr \u00e4hnlich zu Java und C++. Unsere Klassen sind in Namespaces organisiert. Sie k\u00f6nnen einen Namespace mit dem Schl\u00fcsselwort namespace definieren. Wir k\u00f6nnen Namespaces mit dem Schl\u00fcsselwort using \"ins Geltungsbereich bringen\". z.B.:

                              using System.Collections.Generic;\n
                            2. In einer C#-Konsolenanwendung wird der Eintrittspunkt der Anwendung mit einer statischen Funktion namens Main gegeben. Unser Klassenname kann beliebig sein, in unserem Fall hat VS eine Klasse namens Program erzeugt. Die Parameterliste der Funktion Main ist gebunden: entweder werden keine Parameter angegeben, oder es wird ein string[]angegeben, in dem die Befehlszeilenargumente zur Laufzeit angegeben werden.

                            3. in .NET wird die Klasse Console aus dem Namensraum System verwendet, um die Standardeingabe und -ausgabe zu verarbeiten. Mit der statischen Aktion WriteLine k\u00f6nnen Sie eine Zeile drucken, mit ReadKey k\u00f6nnen Sie auf das Dr\u00fccken einer Taste warten.

                            Top-Level-Anweisungen, implizite und statische Verwendungen und Namespaces

                            Bei der Projekterstellung haben wir zuvor das Kontrollk\u00e4stchen \"Do not use top level statements\" aktiviert. Falls wir dies nicht getan h\u00e4tten, h\u00e4tten wir in unserer Datei Program.cs nur eine einzige Zeile mit Inhalt gefunden:

                            // siehe https://aka.ms/new-console-template f\u00fcr weitere Informationen\nConsole.WriteLine(\"Hello World!\");\n

                            Es ist funktionell \u00e4quivalent zu dem obigen Code, der die Klasse Program und ihre Funktion Main enth\u00e4lt. Schauen wir uns an, was dies m\u00f6glich macht (Sie k\u00f6nnen hier mehr dar\u00fcber lesen https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/top-level-statements, beide neu in C# 10):

                            • Top level statements. Die Idee ist, dass man Code direkt in einer einzigen Quelldatei schreiben kann, ohne dass Klassen/Main und andere Funktionsdefinitionen im Projekt vorhanden sind. In diesem Fall setzt der Compiler dies hinter den Kulissen in eine statische Main-Funktion einer Klasse, die wir nicht sehen. Die Motivation f\u00fcr seine Einf\u00fchrung war die Reduzierung von \"Boilerplate\"-Code f\u00fcr sehr einfache, \"skriptartige\" Anwendungen.
                            • Implicit global usings. Je nachdem, welchen Projekttyp Sie erstellt haben, werden bestimmte Basis-Namensr\u00e4ume automatisch im Hintergrund in allen Quelldateien verwendet (der Compiler verwendet dazu die global using-Direktive). Der Punkt ist: Auf diese Weise m\u00fcssen Entwickler bestimmte h\u00e4ufig verwendete Namespaces (z.B. System.IO, System.Collections.Generic, etc.) nicht als Quelldateien verwenden.
                            • Static using. Es ist m\u00f6glich, statische Klassen statt Namespaces in C# mit using zu verwenden, so es nicht wichtig ist, diese auszuschreiben, wenn sie verwendet werden. Ein h\u00e4ufiger Fall ist die Verwendung der Klasse \"Console\" oder \"Math\".

                              using static System.Console;\n\nnamensraum ConsoleApp12\n{\n    internal class Program\n    {\n        static void Main(string[] args)\n        {\n            WriteLine(\"Hello World!\");\n        }\n    }\n}\n
                            • Namensr\u00e4ume auf Dateiebene. In C# 10 gibt es auch eine Vereinfachung bei der Deklaration von Namespaces, da es nicht mehr zwingend erforderlich ist, Klammern zu verwenden, so dass der angegebene Namespace f\u00fcr die ganze Datei g\u00fcltig ist, z.B:

                              namespace HelloWorld;\n\ninternal class Program\n{\n    // ...\n}\n

                            Inconsistent visibility oder inconsistent accessibility Fehler

                            W\u00e4hrend des Semesters k\u00f6nnen Sie bei der Durchf\u00fchrung von Programmieraufgaben auf \u00dcbersetzungsfehlermeldungen sto\u00dfen, die sich \u00fcber inconsistent visibility oder inconsistent accessibility beschweren. Dieses Ph\u00e4nomen ist auf die M\u00f6glichkeit zur\u00fcckzuf\u00fchren, die Sichtbarkeit der einzelnen Typen (Klassen, Schnittstellen usw.) in einer .NET-Umgebung zu steuern:

                            • internal oder keine Sichtbarkeit angeben: der Typ ist nur in der angegebenen Assembly (.exe, .dll)/dem angegebenen Projekt sichtbar
                            • public: der Typ ist auch f\u00fcr andere Assemblys/Projekte sichtbar

                            Der einfachste Weg, diesen Fehler zu vermeiden, ist, alle unsere Typen als \u00f6ffentlich zu definieren, z.B.:

                            public class HardDisk\n{\n    // ...\n}\n
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#theoretischer-uberblick","title":"Theoretischer \u00dcberblick","text":"

                            Die Unterkapitel enthalten keine \u00dcbungen, sondern bieten den Studierenden eine mit Beispielen illustrierte Einf\u00fchrung in die entsprechenden theoretischen Themen.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#a-theorie-der-beziehung-zwischen-dem-uml-klassendiagramm-und-dem-code-student","title":"A) Theorie der Beziehung zwischen dem UML-Klassendiagramm und dem Code [Student]*","text":"

                            Das Material ist hier verf\u00fcgbar: Die Beziehung zwischen dem UML-Klassendiagramm und dem Code Dieses Thema wurde im vorangegangenen Semester in der Vorlesung Softwaretechnologien behandelt.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#b-schnittstelle-und-abstrakte-basisklasse-student","title":"B) Schnittstelle und abstrakte (Basis)Klasse [Student]*","text":"

                            Das Material ist hier verf\u00fcgbar: Schnittstelle und abstrakte (angestammte) Klasse.

                            Themen:

                            • Konzept und Definition abstrakter Klassen in C#
                            • Schnittstellenkonzepte und -definitionen in C#
                            • Vergleich von abstraktem Basisklasse und Schnittstelle
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#2-aufgabe-veranschaulichen-der-beziehung-zwischen-uml-und-code","title":"2. Aufgabe - Veranschaulichen der Beziehung zwischen UML und Code","text":""},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#aufgabenbeschreibung-equipment-inventory","title":"Aufgabenbeschreibung - Equipment inventory","text":"

                            Aufgabe: Wir haben die Aufgabe bekommen, eine Computerteilregister-Anwendung zu entwickeln. Lesen Sie mehr:

                            • Es soll f\u00e4hig sein, verschiedene Arten von Teilen zu behandeln. Anf\u00e4nglich sollten die Typen HardDisk, SoundCard und LedDisplay unterst\u00fctzt werden, aber das System sollte leicht auf neue Typen erweiterbar sein.
                            • Daten der Teilen: Kaufsjahr, Alter (berechnet), Kaufspreis und aktueller Preis (berechnet), kann aber auch typspezifische Daten enthalten (z. B. Kapazit\u00e4t f\u00fcr HardDisk ).
                            • Der aktueller Preis h\u00e4ngt von der Art des Teils, dem Einkaufspreis und dem Produktionsjahr des Teils ab. Je \u00e4lter das Teil ist, desto h\u00f6her ist der Preisnachlass, aber der Preisnachlass h\u00e4ngt von der Art des Teils ab.
                            • Es soll f\u00e4hig sein, die speicherte Teilen aufzulisten.
                            • Die Klasse LedDisplay muss von einer Klasse DisplayBase abgeleitet sein, und der Quellcode der Klasse DisplayBase darf nicht ver\u00e4ndert werden. In diesem Beispiel hat dies nicht viel Sinn, aber in der Praxis treffen wir oft auf \u00e4hnliche Situationen, in denen das von uns verwendete Framework/die Plattform verlangt, dass wir von einer eingebauten Klasse ableiten. Typischerweise ist dies der Fall, wenn wir mit Fenstern, Formularen oder benutzerdefinierten Steuerelementen arbeiten: Wir m\u00fcssen sie von den eingebauten Klassen des Frameworks ableiten, und wir haben den Quellcode des Frameworks nicht (oder wollen ihn zumindest nicht \u00e4ndern) - z.B. Java, .NET. In unserem Beispiel simulieren wir diese Situation, indem wir eine Ableitung von DisplayBaseverlangen.

                            Die Implementierung ist erheblich vereinfacht: Die Teile werden nur im Speicher abgelegt, und die Auflistung ist so einfach wie m\u00f6glich, einfach die Daten der registrierten Teile werden auf die Konsole geschrieben.

                            Bei den ersten Gespr\u00e4chen erhalten wir vom Kunden folgende Information: Ein interner Mitarbeiter hat bereits mit der Entwicklung begonnen, ist aber aus Zeitmangel nur zu einer halbfertigen L\u00f6sung gekommen. Ein Teil unserer Aufgabe besteht darin, die halbfertige L\u00f6sung zu verstehen und die Aufgabe von dort aus umzusetzen.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#klassendiagramm","title":"Klassendiagramm","text":"

                            \u00d6ffnen wir die Quellcode-L\u00f6sung unseres Kunden source code mit dem Ausf\u00fchren der nachstehenden Schritte.

                            Klonen wir das Git-Repository des urspr\u00fcnglichen Projekts, das online auf GitHub verf\u00fcgbar ist, in einen eigenen Ordner innerhalb des Ordners C:\\Work: z. B.: C:\\Work\\NEPTUN\\lab1. \u00d6ffnen wir in diesem neuen Ordner eine Befehlszeile oder Powershell und f\u00fchren wir den folgenden git-Befehl aus:

                            git clone https://github.com/bmeviauab00/lab-modellkod-kiindulo.git\n

                            Git und GitHub

                            Sie werden mehr \u00fcber Git als Quellcode-Verwaltungssystem im Rahmen der ersten Hausaufgabe erfahren.

                            \u00d6ffnen wir die Visual Studio Solution src/EquipmentInventory.sln im geklonten Ordner.

                            Blicken wir die Dateien im Solution Explorer lurz \u00fcber. Es w\u00e4re hilfreich, die Beziehungen zwischen den Klassen in einem Klassendiagramm darzustellen, um sie zu verstehen. Wir wollen ein Klassendiagramm in unser Projekt einf\u00fcgen. Klicken wir im Solution Explorer mit der rechten Maustaste auf das Projekt (nicht auf das Solution!), und w\u00e4hlen wir im Popup-Men\u00fc die Option Add/New Item. Dann w\u00e4hlen wir in dem erscheinenden Fenster die Option Class Diagram, geben wir am unten im Fenster Main.cd als der Namen des Diagramms ein, und schlie\u00dfen wir das Fenster mit OK.

                            Fehlende Class Diagram-Vorlage

                            Wenn das Element Class Diagram nicht in der Liste erscheint, ist die entsprechende Komponente von VS nicht installiert. Weitere Informationen hierzu finden Sie im Abschnitt Voraussetzungen in diesem Dokument.

                            Die Diagrammdatei Main.cd wird dann im Solution Explorer angezeigt. Doppelklicken wir darauf, um sie zu \u00f6ffnen. Unseres Diagramm ist derzeit leer. Ziehen wir die .cs-Quelldateien aus Solution Explorer mit drag&drop auf das Diagramm. VS pr\u00fcft dann, welche Klassen in diesen Quelldateien enthalten sind, und zerlegt sie in UML-Klassen. Erstellen wir das Layout wie in der folgenden Abbildung gezeigt (man kann die Mitglieder der Klassen anzeigen, falls man auf den Doppelpfeil in der oberen rechten Ecke ihres Rechtecks klickt):

                            Wir k\u00f6nnen auch den Quellcode der Klassen anschauen, falls wir entweder auf die entsprechende Klasse im Diagramm doppelklicken oder die .cs-Dateien im Solution Explorer \u00f6ffnen. Wir werden die Folgenden erfahren:

                            • Die Klassen SoundCard, HardDisk und LedDisplay sind relativ gut entwickelt und verf\u00fcgen \u00fcber die notwendigen Attribute und Abfragefunktionen.
                            • LedDisplay wird bei Bedarf von DisplayBase abgeleitet.
                            • Obwohl EquipmentInventory f\u00fcr die Register der auf Lager befindlichen Teile verantwortlich ist, wird praktisch nichts davon umgesetzt.
                            • Wir finden eine Schnittstelle IEquipment, mit GetAge und GetPrice Funktionen.
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#equipmentinventory","title":"EquipmentInventory","text":"

                            Lassen wir uns an der L\u00f6sung arbeiten. Lassen wir uns zuerst die grundlegenden Konzepte festlegen. In der Klasse EquipmentInventory speichern wir eine heterogene Sammlung verschiedener Teiltypen. Dies ist der Schl\u00fcssel zu einer konsistenten Teilverwaltung, so dass unsere L\u00f6sung problemlos mit neuen Teiltypen erweitert werden kann.

                            Wie fr\u00fcher erw\u00e4hnt, kann eine einheitliche Verwaltung entweder durch die Implementierung einer gemeinsamen Basisklasse oder einer gemeinsamen Schnittstelle erreicht werden. In unserem Fall scheint die gemeinsame Basisklasse (z. B. EquipmentBase) eliminiert zu werden, denn durch ihre Einf\u00fchrung h\u00e4tte die Klasse LedDisplay zwei Basisklassen: DisplayBase, die obligatorisch ist, und EquipmentBase, die wir zur einheitlichen Verwaltung einf\u00fchren. Dies ist nicht m\u00f6glich, in einer .NET-Umgebung kann eine Klasse nur einen Vorg\u00e4nger haben. Die L\u00f6sung, DisplayBaseso zu \u00e4ndern, dass es von EquipmentBasestammt, ist nach unseren Anforderungen nicht m\u00f6glich (es war eine Anforderung, dass der Quellcode nicht ge\u00e4ndert werden durfte). Es bleibt also der schnittstellenbasierte Ansatz. Dies ist sicherlich die Schlussfolgerung des vorherigen Entwicklers der Anwendung, weshalb er die Schnittstelle IEquipment eingef\u00fchrt hat.

                            F\u00fcgen wir eine generische Liste von Elementen des Typs IEquipment (keine Eigenschaft, sondern ein Feld!) zur Klasse EquipmentInventory hinzu. Ihre Sichtbarkeit sollte - in dem Bem\u00fchen um Integration - privatesein. Der Name sollte equipment sein (ohne \"s\" am Ende, im Englisch ist der Plural von equipment auch equipment). Um eine Membervariable hinzuzuf\u00fcgen, verwenden wir das Class Details Fenster von Visual Studio. Wenn das Fenster nicht sichtbar ist, kann es durch Auswahl von View / Other Windows / Class Details angezeigt werden.

                            Der Typ der Mitgliedsvariablen ist List<IEquipment>. Der .NET-Typ List ist ein dynamisch dehnbares generisches Array (wie ArrayListin Java). Falls wir auf die Klasse EquipmentInventory im Diagramm blicken, so siehen wir, dass nur der Name der Mitgliedsvariablen angezeigt wird, nicht aber der Typ. Klicken wit mit der rechten Maustaste auf den Hintergrund des Diagramms und w\u00e4hlen wir im Change Members Format Men\u00fc die Option Display Full Signature. Das Diagramm zeigt dann den Typ der Mitgliedsvariablen und die vollst\u00e4ndige Signatur der Operationen.

                            Wenn wir auf die Klasse EquipmentInventory doppelklicken, k\u00f6nnen wir zum Quellcode navigieren, und wie wir sehen k\u00f6nnen, erscheint sie im Code tats\u00e4chlich als Mitgliedsvariable vom Typ Liste:

                            class EquipmentInventory\n{\n    private List<IEquipment> equipment;\n

                            Einerseits freuen wir uns dar\u00fcber, weil Visual Studio Round-Trip-Engineering unterst\u00fctzt: \u00c4nderungen am Modell spiegeln sich sofort im Code wider und umgekehrt. Andererseits haben wir bereits dar\u00fcber gesprochen, dass eine Klasse, die eine Sammlung von Mitgliedern einer anderen Klasse hat, sollte in das UML-Modell als eine Assoziationsbeziehung vom Typ 1-mehr zwischen den beiden Klassen erscheinen. Dies ist noch nicht der Fall in unserem Modell. Gl\u00fccklicherweise kann die VS-Modellierungsschnittstelle dazu gebracht werden, diese Art von Verbindung in dieser Form anzuzeigen. Klicken wir dazu im Diagramm mit der rechten Maustaste auf die Membervariable equipment und w\u00e4hlen wir im Men\u00fc die Option Show as Collection Association aus. Die Schnittstelle IEquipment sollte dann nach rechts verschoben werden, damit im Diagramm gen\u00fcgend Platz f\u00fcr die Darstellung der Assoziationsverbindung und der Rolle der Verbindung bleibt:

                            Der Doppelpfeil, der auf der \"Mehr\"-Seite endet, entspricht nicht dem UML-Standard, aber sei man nicht zu traurig dar\u00fcber, es ist nicht wichtig. Wir freuen uns dar\u00fcber, dass der Name (und sogar der genaue Typ) der Mitgliedsvariablen am IEquipment Ende der die Beziehung darstellende Pfeil in der Rolle anzeigt ist.

                            Navigieren wir zum Quellcode von EquipmentInventory und schreiben wir den Konstruktor, der die Sammlung equipment initialisiert!

                            public EquipmentInventory()\n{\n    equipment = new List<IEquipment>();\n}\n

                            Schreiben wir dann die Methode ListAll, die das Alter der Elemente und ihren aktuellen Preis ausgibt:

                            public void ListAll()\n{\n    foreach (IEquipment eq in equipment)\n    {\n        Console.WriteLine($\"Alter: {eq.GetAge()}\\t\u00c9rt\u00e9ke: {eq.GetPrice()}\");\n    }\n}\n

                            Mit dem Befehl foreach durchlaufen wir die Elemente. Bei der Verwendung des Befehls foreach sollte in von einer Sammlung gefolgt werden, und in sollte eine Variablendeklaration (in diesem Fall IEquipment eq) vorangestellt werden, wo type der Elementtyp der Sammlung ist. Bei jeder Iteration nimmt diese Variable den Iterationswert der Sammlung an.

                            Der Operation Console.WriteLine wird entweder eine einfache Zeichenfolge oder, wie in unserem Fall, eine Formatierungszeichenfolge \u00fcbergeben. Die Ersetzungen werden durch String-Interpolation gel\u00f6st: Die zu ersetzenden Werte m\u00fcssen zwischen {} angegeben werden. Bei der String-Interpolation muss der String mit $ beginnen.

                            Schreiben wir eine Funktion mit der Bezeichnung AddEquipment, die ein neues Ger\u00e4t zu der Menge hinzuf\u00fcgt:

                            public void AddEquipment(IEquipment eq)\n{\n     equipment.Add(eq);\n}\n
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#verwirklichern-von-iequipment","title":"Verwirklichern von IEquipment","text":"

                            Wir haben entschieden, die Schnittstelle IEquipment zu verwenden, um die verschiedenen Komponententypen einheitlich zu verwalten. In unserem Fall haben sowohl die Klassen SoundCard als auch HardDisk die Methoden GetAge() und GetPrice(), aber wir k\u00f6nnen sie nicht einheitlich verwalten (z. B. in einer gemeinsamen Liste speichern). Zu diesem Zweck m\u00fcssen wir beide Klassen dazu bringen, die Schnittstelle IEquipment zu implementieren. \u00c4ndern Sie ihr Quellcode:

                            public class SoundCard : IEquipment\n
                            public class HardDisk : IEquipment\n

                            Dann m\u00fcssen wir die Methoden der Schnittstelle IEquipment in den Klassen SoundCard und HardDisk implementieren. Wir stellen fest, dass es damit nichts mehr zu tun gibt, die Funktionen GetPrice und GetAge sind bereits an beiden Stellen geschrieben.

                            Erstellen wir testweise ein Objekt EquipmentInventory in unserer Main Funktion in Program.cs, f\u00fcllen wir es mit den Objekten HardDisk und SoundCard auf, und listen wir das Objekt dann in der Konsole aus. Wenn 2021 nicht das aktuelle Jahr ist, schreiben wir in den folgenden Zeilen das Jahr 2021 auf das aktuelle Jahr und das Jahr 2020 auf eine mit eins kleinere Zahl um!

                            static void Main( string[] args )\n{\n    EquipmentInventory ei = new EquipmentInventory();\n\n    ei.AddEquipment(new HardDisk(2021, 30000, 80));\n    ei.AddEquipment(new HardDisk(2020, 25000, 120));\n    ei.AddEquipment(new HardDisk(2020, 25000, 250));\n\n    ei.AddEquipment(new SoundCard(2021, 8000));\n    ei.AddEquipment(new SoundCard(2020, 7000));\n    ei.AddEquipment(new SoundCard(2020, 6000));\n\n    ei.ListAll();\n}\n

                            Wenn wir die Anwendung ausf\u00fchren, stellen wir fest, dass unsere L\u00f6sung zwar anf\u00e4nglich ist, aber funktioniert:

                            Arbeiten wir weiter mit der Klasse LedDisplay. Der Quellcode von DisplayBase kann aufgrund der Anforderungen nicht ge\u00e4ndert werden. Aber das ist kein Problem, unsere Klasse LedDisplay wird die Schnittstelle IEquipment implementieren, lassen wir uns den Code entsprechend \u00e4ndern:

                            public class LedDisplay : DisplayBase, IEquipment\n

                            In der Klasse LedDisplay m\u00fcssen die Funktionen der Schnittstelle bereits geschrieben sein:

                            public double GetPrice()\n{\n    return this.price;\n}\n\npublic int GetAge()\n{\n    return DateTime.Today.Year - this.manufacturingYear;\n}\n

                            Erweitern wir unsere Main Funktion, f\u00fcgen wir zwei LedDisplay Objekte zu unserer Liste hinzu (auch hier gilt: Wenn 2021 nicht das aktuelle Jahr ist, schreiben wir in den folgenden Zeilen das Jahr 2021 auf das aktuelle Jahr und das Jahr 2020 auf eine mit eins kleinere Zahl um!)

                            ei.AddEquipment(new LedDisplay(2020, 80000, 17, 16));\nei.AddEquipment(new LedDisplay (2021, 70000, 17, 12));\n\nei.ListAll();\nConsole.ReadKey();\n

                            F\u00fchren wir die Anwendung testweise aus.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#3-aufgabe-anwendung-der-schnittstelle-und-der-abstrakten-basisklasse","title":"3. Aufgabe - Anwendung der Schnittstelle und der abstrakten Basisklasse","text":""},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#schnittstellenprobleme","title":"Schnittstellenprobleme","text":"

                            Bewerten wir unsere aktuelle schnittstellenbasierte L\u00f6sung.

                            Eines der Hauptprobleme ist, dass unser Code mit Code-Duplikationen voll ist, die die Wartbarkeit und Erweiterbarkeit zerst\u00f6ren:

                            • Die Mitglieder yearOfCreation und newPrice gelten f\u00fcr alle Komponententypen (mit Ausnahme des speziellen LedDisplay) und m\u00fcssen immer mit copy-paste hinzugef\u00fcgt werden, wenn ein neuer Typ eingef\u00fchrt wird.
                            • Die Implementierungsebene der Funktion GetAge ist f\u00fcr alle Komponententypen (mit Ausnahme der speziellen LedDisplay) gleich, auch mit copy-paste wird \"vermehrt\".
                            • Die Zeilen in den Konstruktoren, die die Mitglieder yearOfCreation und newPrice initialisieren, werden ebenfalls in jeder Klasse dupliziert.

                            Auch wenn diese Codeduplizierung im Moment noch unbedeutend zu sein scheint, wird die Situation mit der Einf\u00fchrung neuer Komponententypen immer schlechter, und es ist besser, k\u00fcnftigen Problemen rechtzeitig vorzubeugen.

                            Ein weiteres Problem besteht darin, dass die Auflistung der Teiledaten derzeit schmerzlich unvollst\u00e4ndig ist, da es keine Teileart gibt (nur Alter und Preis). Um den Typ anzuzeigen, muss die Schnittstelle IEquipment erweitert werden, z. B. durch Einf\u00fchrung einer Operation namens GetDescription. F\u00fcgen wir der Schnittstelle eine Funktion GetDescription hinzu!

                            public interface IEquipment\n{\n    double GetPrice();\n    int GetAge();\n    string GetDescription();\n}\n

                            Dann m\u00fcsste jede Klasse, die die Schnittstelle IEquipment implementiert, diese Methode implementieren, was f\u00fcr viele Klassen eine Menge Arbeit bedeutet (und f\u00fcr eine Mehrkomponenten-Anwendung, d.h. eine Anwendung, die aus mehreren DLLs besteht, oft gar nicht machbar ist, wenn sie nicht in den H\u00e4nden eines einzigen Entwicklers liegen). F\u00fchren wir den Befehl Build aus, um zu \u00fcberpr\u00fcfen, ob Sie nach dem Hinzuf\u00fcgen von GetDescription an drei Stellen \u00dcbersetzungsfehler erhalten.

                            Standardimplementierung in der Schnittstelle festlegen

                            Es ist wichtig zu wissen, dass ab C# 8 (genauer .NET oder .NET Core Runtime ist auch n\u00f6tig, es ist unter .NET Framework nicht unterst\u00fctzt ) Schnittstellenoperationen eine Standardimplementierung erhalten k\u00f6nnen (default interface methods), so dass wir zur L\u00f6sung des obigen Problems keine abstrakte Klasse ben\u00f6tigen, aber die Schnittstelle kann keine Mitgliedsvariablen mehr haben. Weitere Informationen finden Sie hier: default interface methods.

                            public interface IEquipment\n{\n    double GetPrice();\n    int GetAge();\n    string GetDescription() { return \"EquipmentBase\"; }\n}\n
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#abstrakte-klasse","title":"Abstrakte Klasse","text":"

                            Eine L\u00f6sung f\u00fcr beide Probleme ist die Einf\u00fchrung eines gemeinsamen abstrakten Vorfahres (mit Ausnahme der Klasse LedDisplay, auf die wir noch zur\u00fcckkommen werden). Wir k\u00f6nnen den Code, der allen Nachkommen gemeinsam ist, dorthin verschieben und eine Standardimplementierung f\u00fcr die neu eingef\u00fchrte Operation GetDescription bereitstellen. Nennen wir unsere neue abstrakte Basisklasse EquipmentBase. Die Frage ist, ob die Schnittstelle IEquipment noch ben\u00f6tigt wird oder ob sie vollst\u00e4ndig durch die neue Klasse EquipmentBase ersetzt werden kann. Wir m\u00fcssen die Schnittstelle IEquipment beibehalten, weil wir unsere Klasse LedDisplay nicht von EquipmentBaseableiten k\u00f6nnen: Sie hat bereits eine obligatorische Basisklasse, DisplayBase, deshalb bezieht sich EquipmentInventory in unserer erweiterten L\u00f6sung auf die verschiedenen Komponenten als Schnittstelle IEquipment.

                            Beginnen wir mit der Umwandlung. Unser Klassendiagramm soll die aktive Registerkarte sein. Ziehen wir aus der Toolbox mit drag&drop ein Abstract Class Element auf das Diagramm und benennen wir es EquipmentBase.

                            Im Folgenden m\u00fcssen wir die Klassen SoundCard und HardDisk von EquipmentBaseableiten ( LedDisplayhat bereits einen anderen Vorfahren, so dass wir dies dort nicht tun k\u00f6nnen). W\u00e4hlen wir dazu die Verkn\u00fcpfung Inheritance in der Toolbox und ziehen wir dann eine Linie von der Kindklasse zur Basisklasse sowohl f\u00fcr SoundCard als auch f\u00fcr HardDisk.

                            Im n\u00e4chsten Schritt \u00e4ndern wir den Code so, dass HardDisk und SoundCard die Schnittstelle IEquipment nicht separat implementieren, sondern ihr gemeinsamer Vorfahre EquipmentBase dies tut. \u00c4ndern wir dazu die Klasse EquipmentBase so, dass sie die Schnittstelle implementiert (entweder durch Einf\u00fcgen eines inheritance Beziehung von EquipmentBasezu IEquipmentim Diagramm oder durch \u00c4ndern des Quellcodes von EquipmentBase ). Entfernen wir die Implementierung von IEquipment aus den Klassen HardDisk und SoundCard (der Vorg\u00e4nger implementiert sie bereits).

                            Die relevanten Teile unseres Diagramms und des Quellcodes sehen dann wie folgt aus:

                            public abstract class EquipmentBase : IEquipment\n
                            public class HardDisk : EquipmentBase\n
                            public class SoundCard : EquipmentBase\n

                            Unser Code kann aus mehreren Gr\u00fcnden noch nicht kompiliert werden. EquipmentBase implementiert die Schnittstelle IEquipment, aber sie implementiert noch nicht die Operationen der Schnittstelle. Erzeugen wir die Methoden entweder mit Hilfe des Smarttags oder geben wir sie nach den folgenden Grunds\u00e4tzen ein:

                            • Die Mitglieder newPrice und yearOfCreation sind in den Klassen HardDisk und SoundCard dupliziert: verschieben (nicht kopieren!) wir sie in den gemeinsamen Vorfahren EquipmentBase und geben wir protected Sichtbarkeit.
                            • Die Operation GetAge wird in den Klassen HardDisk und SoundCard dupliziert, l\u00f6schen wir die Implementierung aus diesen Klassen und verschieben wir sie in die Klasse EquipmentBase.
                            • Die Operation GetPrice wird als abstrakte Operation in den Vorg\u00e4nger aufgenommen. Dies ist eine bewusste Design-Entscheidung, so dass wir nachkommende Klassen zwingen, diesen Vorgang trotzdem zu \u00fcberschreiben.
                            • F\u00fcr GetDescription gilt das Gegenteil: Wir definieren es als virtuell (und nicht abstrakt), d. h. wir geben eine Implementierung im Vorg\u00e4nger an. Auf diese Weise sind die Nachkommen nicht gezwungen, den Vorgang au\u00dfer Kraft zu setzen.

                            Der entsprechende Code lautet:

                            public abstract class EquipmentBase : IEquipment\n{\n    protected int yearOfCreation;\n    protected int newPrice;\n\n    public int GetAge()\n    {\n        return DateTime.Today.Year - yearOfCreation;\n    }\n\n    public abstract double GetPrice();\n\n    public virtual string GetDescription()\n    {\n        r\u00fcckgabe \"EquipmentBase\";\n    }\n}\n

                            Einige zus\u00e4tzliche Gedanken zum Codefragment:

                            • Bei abstrakten Klassen muss das Schl\u00fcsselwort \"abstrakt\" vor das Wort \"Klasse\" geschrieben werden.
                            • F\u00fcr abstrakte Operationen muss das Schl\u00fcsselwort abstract angegeben werden.
                            • In .NET-Umgebung kann man steuern, ob ein Vorgang virtuell ist oder nicht. In dieser Hinsicht ist es \u00e4hnlich wie C++. Wenn man eine Operation virtuell machen will, muss man das Schl\u00fcsselwort virtual f\u00fcr die Operation angeben. Zur Erinnerung: Man definiert eine Operation als virtuell, wenn ihre Nachkommen sie \u00fcberdefinieren. Nur dann ist gew\u00e4hrleistet, dass die Nachfolgeversion aufgerufen wird, wenn die angegebene Operation auf einen Vorg\u00e4ngerverweis angewendet wird.
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#nachkommenschaft","title":"Nachkommenschaft","text":"

                            Im n\u00e4chsten Schritt gehen wir zu den Nachkommen von EquipmentBase \u00fcber. Wenn abstrakte und virtuelle Operationen in C# \u00fcberschrieben werden, muss das Schl\u00fcsselwort override im Nachfahren angegeben werden. Zuerst wird die Methode GetPrice neu definiert:

                            HardDisk.cs
                            public override double GetPrice()\n{\n    return yearOfCreation < (DateTime.Today.Year - 4)\n        ? 0\n        : newPrice - (DateTime.Today.Year - yearOfCreation) * 5000;\n}\n
                            SoundCard.cs
                            public override double GetPrice()\n{\n    return yearOfCreation < (DateTime.Today.Year - 4)\n        ? 0 \n        : newPrice - (DateTime.Today.Year - yearOfCreation) * 2000;\n}\n

                            Im n\u00e4chsten Schritt werden wir die Operation GetDescription in die Klassen HardDisk und SoundCard schreiben. Da wir hier die virtuelle Vorg\u00e4ngerfunktion umdefinieren, m\u00fcssen wir auch das Schl\u00fcsselwort override angeben:

                            HardDisk.cs
                            public override string GetDescription()\n{\n    return \"Hard Disk\";\n}\n
                            SoundCard.cs
                            public override string GetDescription()\n{\n    return \"Sound Card\";\n}\n

                            Man k\u00f6nnte sich fragen, warum die Entwickler der Sprache C# beschlossen haben, der Definition von Operationen ein zus\u00e4tzliches Schl\u00fcsselwort hinzuzuf\u00fcgen, was im Fall von C++ nicht notwendig war. Der Grund daf\u00fcr ist einfach: Der Code ist aussagekr\u00e4ftiger. Wenn man sich den Code der Nachkommen ansieht, macht das Wort override sofort klar, dass diese Operation in einem der Vorfahren abstrakt oder virtuell ist, ohne dass man sich den Code aller Vorfahren ansehen muss.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#vorfahre-von-leddisplay","title":"Vorfahre von LedDisplay","text":"

                            Die Basisklasse unserer LedDisplay Klasse ist gebunden, ihr Code kann nicht ge\u00e4ndert werden, daher k\u00f6nnen wir sie nicht von EquipmentBaseableiten. Wir k\u00f6nnen die Funktion GetAge nicht l\u00f6schen, diese Code-Duplizierung bleibt hier erhalten (aber nur f\u00fcr LedDisplay, die nur eine Klasse unter vielen ist!).

                            Note

                            Mit ein wenig zus\u00e4tzlicher Arbeit k\u00f6nnten wir diese Doppelung beseitigen. Dazu m\u00fcsste eine statische Hilfsfunktion in eine der Klassen aufgenommen werden (z. B. EquipmentBase) , die das Produktionsjahr ermittelt und das Alter zur\u00fcckgibt. EquipmentBase.GetAge und LedDisplay.GetAge w\u00fcrden diese Hilfsfunktion f\u00fcr ihre Ausgabe verwenden.

                            In unserer Klasse LedDisplay m\u00fcssen wir noch GetDescription schreiben:

                            LedDisplay.cs
                            public string GetDescription()\n{\n    return \"Led Display\";\n}\n

                            Beachten wir, dass das Schl\u00fcsselwort override hier NICHT angegeben ist. Wenn eine Schnittstellenfunktion implementiert ist, muss/darf overridenicht ausgeschrieben werden.

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#getdescription-verwenden","title":"GetDescription verwenden","text":"

                            \u00c4ndern wir die Operation EquipmentInventory.ListAll, um auch die Beschreibung der Elemente in die Ausgabe zu schreiben:

                            EquipmentInventory.cs
                            public void ListAll()\n{\n    foreach (IEquipment eq in equipment)\n    {\n        Console.WriteLine(\"$Description: {eq.GetDescription()}\\t\" +\n            $\"Alter: {eq.GetAge()}\\tValue: {eq.GetPrice()}\");\n    }\n}\n

                            Dies f\u00fchrt zu einer informativeren Ausgabe, wenn die Anwendung ausgef\u00fchrt wird:

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#duplizierung-von-konstruktorcode","title":"Duplizierung von Konstruktorcode","text":"

                            Ein Blick auf unseren Code zeigt, dass es eine weitere Duplikation gibt. Alle Nachfahren von EquipmentBase (HardDisk, SoundCard) haben diese beiden Zeilen in ihrem Konstruktor:

                             this.yearOfCreation = yearOfCreation;\n this.newPrice = newPrice;\n

                            Wenn wir nachdenken, werden diese yearOfCreation und newPrice Mitglieder im Vorfahren definiert, also sollte es seine Verantwortung sein, sie zu initialisieren. F\u00fcgen wir einen entsprechenden Konstruktor in EquipmentBasehinzu:

                            EquipmentBase.cs
                            public EquipmentBase(int Erstellungsjahr, int neuerPreis)\n{\n    this.yearOfCreation = yearOfCreation;\n    this.newPrice = newPrice;\n}\n

                            Entfernen wir die Initialisierung der beiden Mitglieder aus dem Konstruktor der Nachfahren HardDisk und SoundCard und rufen wir stattdessen den Konstruktor des Vorfahren auf, indem wir auf das Schl\u00fcsselwort base verweisen:

                            HardDisk.cs
                            public HardDisk(int yearOfCreation, int newPrice, int capacityGB)\n    : base(yearOfCreation, newPrice)\n{\n    this.capacityGB = capacityGB;\n}\n
                            SoundCard.cs
                            public SoundCard(int yearOfCreation, int newPrice)\n    : base(yearOfCreation, newPrice)\n{\n}\n
                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#bewertung","title":"Bewertung","text":"

                            Durch die Verwendung einer Kombination aus Schnittstelle und abstrakter Basisklasse ist es uns gelungen, die L\u00f6sung mit dem geringsten Kompromiss zu entwickeln:

                            • IEquipment als Schnittstelle k\u00f6nnen wir alle Arten von Teilen einheitlich behandeln, auch solche, bei denen die Basisklasse gebunden war (mit abstrakter Basisklasse allein h\u00e4tten wir dies nicht erreichen k\u00f6nnen).
                            • Durch die Einf\u00fchrung der abstrakten Basisklasse EquipmentBase konnten wir den Code, der in den verschiedenen Komponententypen gemeinsam ist, mit einer Ausnahme in einen gemeinsamen Basisklasse bringen und so Code-Duplikationen vermeiden.
                            • Durch die Einf\u00fchrung des abstrakten Vorg\u00e4ngers EquipmentBase k\u00f6nnen wir eine Standardimplementierung f\u00fcr neu eingef\u00fchrte IEquipment Operationen (z.B. GetDescripton) angeben, so dass wir nicht gezwungen sind, diese in jeder IEquipment Implementierungsklasse anzugeben.

                            Werfen wir abschlie\u00dfend noch einen Blick auf das UML-Klassendiagramm unserer L\u00f6sung:

                            C# 11 - Statische Schnittstellen

                            Die neueste Funktion von C# 11 ist die Definition von statischen Schnittstellenmitgliedern, die es Ihnen erm\u00f6glicht, von einer implementierenden Klasse zu verlangen, dass sie Mitglieder hat, die sich nicht auf die Objektinstanz beziehen, sondern die Klasse muss \u00fcber ein bestimmtes statisches Mitglied verf\u00fcgen. Mehr lesen

                            "},{"location":"labor/1-model-es-kod-kapcsolata/index_ger/#hinweis-fakultative-hausaufgabe","title":"Hinweis - fakultative Hausaufgabe","text":"

                            Unsere L\u00f6sung unterst\u00fctzt nicht die Anzeige von komponentenspezifischen Daten (z.B. Kapazit\u00e4t f\u00fcr HardDisk ) w\u00e4hrend der Auflistung. Zu diesem Zweck sollte das Schreiben von Komponentendaten in eine formatierte Zeichenkette von der Klasse EqipmentInventory in die Komponentenklassen verlagert werden, und zwar nach den folgenden Grunds\u00e4tzen:

                            • Sie k\u00f6nnen eine GetFormattedString Operation in die IEquipment Schnittstelle einf\u00fchren, die ein Objekt vom Typ string zur\u00fcckgibt. Alternativ kann die Operation System.Object ToString() au\u00dfer Kraft gesetzt werden. In .NET sind alle Typen implizit von System.Objectabgeleitet, das \u00fcber eine virtuelle Operation ToString() verf\u00fcgt.
                            • In EquipmentBaseschreiben Sie die Formatierung der gemensamen Mitglieder (Beschreibung, Preis, Alter) in Strings.
                            • Wenn eine Komponente auch typspezifische Daten hat, dann \u00fcberschreibt ihre Klasse die Funktion, die sie in eine Zeichenkette formatiert: Diese Funktion muss zuerst ihren Vorg\u00e4nger aufrufen (mit dem Schl\u00fcsselwort base ), dann ihre eigenen formatierten Daten an sie anh\u00e4ngen und mit dieser Zeichenkette zur\u00fcckkehren.
                            "},{"location":"labor/2-nyelvi-eszkozok/","title":"2. Nyelvi eszk\u00f6z\u00f6k","text":""},{"location":"labor/2-nyelvi-eszkozok/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                            A gyakorlat sor\u00e1n a hallgat\u00f3k megismerkednek a legfontosabb modern, a .NET k\u00f6rnyezetben is rendelkez\u00e9sre \u00e1ll\u00f3 nyelvi eszk\u00f6z\u00f6kkel. Felt\u00e9telezz\u00fck, hogy a hallgat\u00f3 a kor\u00e1bbi tanulm\u00e1nyai sor\u00e1n elsaj\u00e1t\u00edtotta az objektum-orient\u00e1lt szeml\u00e9letm\u00f3dot, \u00e9s tiszt\u00e1ban van az objektum-orient\u00e1lt alapfogalmakkal. Jelen gyakorlat sor\u00e1n azokra a .NET-es nyelvi elemekre koncentr\u00e1lunk, amelyek t\u00falmutatnak az \u00e1ltal\u00e1nos objektum-orient\u00e1lt szeml\u00e9leten, ugyanakkor nagyban hozz\u00e1j\u00e1rulnak a j\u00f3l \u00e1tl\u00e1that\u00f3 \u00e9s k\u00f6nnyen karbantarthat\u00f3 k\u00f3d elk\u00e9sz\u00edt\u00e9s\u00e9hez. Ezek a k\u00f6vetkez\u0151k:

                            • Tulajdons\u00e1g (property)
                            • Deleg\u00e1t (delegate, met\u00f3dusreferencia)
                            • Esem\u00e9ny (event)
                            • Attrib\u00fatum (attribute)
                            • Lambda kifejez\u00e9s (lambda expression)
                            • Generikus t\u00edpus (generic type)
                            • N\u00e9h\u00e1ny tov\u00e1bbi nyelvi konstrukci\u00f3

                            Kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sok: a 2. el\u0151ad\u00e1s \u00e9s a 3. el\u0151ad\u00e1s eleje \u2013 Nyelvi eszk\u00f6z\u00f6k.

                            "},{"location":"labor/2-nyelvi-eszkozok/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                            A gyakorlat elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                            • Visual Studio 2022

                            Gyakorlat Linuxon vagy macOS alatt

                            A gyakorlat anyag alapvet\u0151en Windowsra \u00e9s Visual Studiora k\u00e9sz\u00fclt, de az elv\u00e9gezhet\u0151 m\u00e1s oper\u00e1ci\u00f3s rendszereken is m\u00e1s fejleszt\u0151eszk\u00f6z\u00f6kkel (pl. VS Code, Rider, Visual Studio for Mac), vagy ak\u00e1r egy sz\u00f6vegszerkeszt\u0151vel \u00e9s CLI (parancssori) eszk\u00f6z\u00f6kkel. Ezt az teszi lehet\u0151v\u00e9, hogy a p\u00e9ld\u00e1k egy egyszer\u0171 Console alkalmaz\u00e1s kontextus\u00e1ban ker\u00fclnek ismertet\u00e9sre (nincsenek Windows specifikus elemek), a .NET SDK pedig t\u00e1mogatott Linuxon \u00e9s macOS alatt. Hello World Linuxon

                            "},{"location":"labor/2-nyelvi-eszkozok/#bevezeto","title":"Bevezet\u0151","text":"

                            Kitekint\u0151 r\u00e9szek

                            Jelen \u00fatmutat\u00f3 t\u00f6bb helyen is b\u0151v\u00edtett ismeretanyagot, illetve extra magyar\u00e1zatot ad meg jelen megjegyz\u00e9ssel egyez\u0151 sz\u00ednnel keretezett \u00e9s ugyanilyen ikonnal ell\u00e1tott form\u00e1ban. Ezek hasznos kitekint\u00e9sek, de nem k\u00e9pezik az alap tananyag r\u00e9sz\u00e9t.

                            "},{"location":"labor/2-nyelvi-eszkozok/#megoldas","title":"Megold\u00e1s","text":"A k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se

                            L\u00e9nyeges, hogy a labor sor\u00e1n a laborvezet\u0151t k\u00f6vetve kell dolgozni, tilos (\u00e9s \u00e9rtelmetlen) a k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se. Ugyanakkor az ut\u00f3lagos \u00f6n\u00e1ll\u00f3 gyakorl\u00e1s sor\u00e1n hasznos lehet a k\u00e9sz megold\u00e1s \u00e1ttekint\u00e9se, \u00edgy ezt el\u00e9rhet\u0151v\u00e9 tessz\u00fck.

                            A megold\u00e1s GitHubon \u00e9rhet\u0151 el itt. A legegyszer\u0171bb m\u00f3d a let\u00f6lt\u00e9s\u00e9re, ha parancssorb\u00f3l a git clone utas\u00edt\u00e1ssal lekl\u00f3nozzuk a g\u00e9p\u00fcnkre:

                            git clone https://github.com/bmeviauab00/lab-nyelvieszkozok-megoldas

                            Ehhez telep\u00edtve kell legyen a g\u00e9pre a parancssori git, b\u0151vebb inform\u00e1ci\u00f3 itt.

                            "},{"location":"labor/2-nyelvi-eszkozok/#0-feladat-var-kulcsszo-implicit-tipusu-lokalis-valtozok-implicitly-typed-local-variables","title":"0. Feladat - var kulcssz\u00f3 - Implicit t\u00edpus\u00fa lok\u00e1lis v\u00e1ltoz\u00f3k (implicitly typed local variables)","text":"

                            Egy egyszer\u0171, bemeleg\u00edt\u0151 feladattal kezd\u00fcnk. A k\u00f6vetkez\u0151 p\u00e9ld\u00e1ban egy Person nev\u0171 oszt\u00e1lyt fogunk elk\u00e9sz\u00edteni, mely egy szem\u00e9lyt reprezent\u00e1l.

                            1. Hozzunk l\u00e9tre egy \u00faj C# konzolos alkalmaz\u00e1st. .NET alap\u00fat (vagyis ne .NET Framework-\u00f6set):
                              • Erre az els\u0151 gyakorlat alkalm\u00e1val l\u00e1ttunk p\u00e9ld\u00e1t, le\u00edr\u00e1sa annak \u00fatmutat\u00f3j\u00e1ban szerepel.
                              • A \"Do not use top level statements\" jel\u00f6l\u0151n\u00e9gyzetet pip\u00e1ljuk be a projekt l\u00e9trehoz\u00e1s sor\u00e1n.
                            2. Adjunk hozz\u00e1 egy \u00faj oszt\u00e1lyt az alkalmaz\u00e1sunkhoz Person n\u00e9ven. (\u00daj oszt\u00e1ly hozz\u00e1ad\u00e1s\u00e1hoz a Solution Explorerben kattintsunk jobb eg\u00e9rgombbal a projekt f\u00e1jlra \u00e9s v\u00e1lasszuk az Add / Class men\u00fcpontot. Az el\u0151ugr\u00f3 ablakban a l\u00e9trehozand\u00f3 f\u00e1jl nev\u00e9t m\u00f3dos\u00edtsuk Person.cs-re, majd nyomjuk meg az Add gombot.)
                            3. Tegy\u00fck az oszt\u00e1lyt publikuss\u00e1. Ehhez az oszt\u00e1ly neve el\u00e9 be kell \u00edrni a public kulcssz\u00f3t. Erre a m\u00f3dos\u00edt\u00e1sra itt val\u00f3j\u00e1ban m\u00e9g nem volna sz\u00fcks\u00e9g, ugyanakkor egy k\u00e9s\u0151bbi feladat m\u00e1r egy publikus oszt\u00e1lyt fog ig\u00e9nyelni.

                              public class Person\n{\n}\n
                            4. Eg\u00e9sz\u00edts\u00fck ki a Program.cs f\u00e1jl Main f\u00fcggv\u00e9ny\u00e9t, hogy kipr\u00f3b\u00e1lhassuk az \u00faj oszt\u00e1lyunkat.

                              static void Main(string[] args)\n{\n    Person p = new Person();\n}\n
                            5. A lok\u00e1lis v\u00e1ltoz\u00f3k t\u00edpus\u00e1nak explicit megad\u00e1sa helyett haszn\u00e1lhatjuk a var kulcssz\u00f3t is:

                              static void Main(string[] args)\n{\n    var p = new Person();\n}\n

                              Ezt implicitly typed local variables-nek, magyarul implicit t\u00edpus\u00fa lok\u00e1lis v\u00e1ltoz\u00f3-nak nevezz\u00fck. Ilyenkor a ford\u00edt\u00f3 a kontextusb\u00f3l, az egyenl\u0151s\u00e9gjel jobb oldal\u00e1b\u00f3l megpr\u00f3b\u00e1lja kital\u00e1lni a v\u00e1ltoz\u00f3 t\u00edpus\u00e1t, fenti esetben ez egy Person lesz. Fontos, hogy ett\u0151l a nyelv m\u00e9g statikusan tipusos marad (teh\u00e1t nem \u00fagy m\u0171k\u00f6dik mint a JavaScript-es var kulcssz\u00f3), mert a p v\u00e1ltoz\u00f3 t\u00edpusa a k\u00e9s\u0151bbiekben nem v\u00e1ltozhat meg, ez csak egy egyszer\u0171 szintaktikai \u00e9des\u00edt\u0151szer annek \u00e9rdek\u00e9ben, hogy t\u00f6m\u00f6rebben tudjunk lok\u00e1lis v\u00e1ltoz\u00f3kat defini\u00e1lni (ne kelljen a t\u00edpust \"dupl\u00e1n\", az = bal \u00e9s jobb oldal\u00e1n is megadni).

                              Target-typed new expressions

                              Egy m\u00e1sik megk\u00f6zel\u00edt\u00e9s lehet a a C# 9-ben megjelent Target-typed new expressions, ahol a new oper\u00e1tor eset\u00e9n hagyhat\u00f3 el a t\u00edpus, ha az a ford\u00edt\u00f3 \u00e1ltal kital\u00e1lhat\u00f3 a kontextusb\u00f3l (pl.: \u00e9rt\u00e9kad\u00e1s bal oldala, param\u00e9ter t\u00edpusa stb.). A fenti Person konstruktorunk a k\u00f6vetkez\u0151k\u00e9ppen n\u00e9zne ki:

                              Person p = new();\n

                              Ennek a megk\u00f6zel\u00edt\u00e9snek az el\u0151nye a var-ral szemben, hogy tagv\u00e1ltoz\u00f3k eset\u00e9ben is alkalmazhat\u00f3.

                            "},{"location":"labor/2-nyelvi-eszkozok/#1-feladat-tulajdonsag-property","title":"1. Feladat \u2013 Tulajdons\u00e1g (property)","text":"

                            A tulajdons\u00e1gok seg\u00edts\u00e9g\u00e9vel tipikusan (de mint l\u00e1tni fogjuk, nem kiz\u00e1r\u00f3lagosan) oszt\u00e1lyok tagv\u00e1ltoz\u00f3ihoz f\u00e9rhet\u00fcnk hozz\u00e1 szintaktika tekintet\u00e9ben hasonl\u00f3 m\u00f3don, mintha egy hagyom\u00e1nyos tagv\u00e1ltoz\u00f3t \u00e9rn\u00e9nk el. A hozz\u00e1f\u00e9r\u00e9s sor\u00e1n azonban lehet\u0151s\u00e9g\u00fcnk van arra, hogy az egyszer\u0171 \u00e9rt\u00e9k lek\u00e9rdez\u00e9s vagy be\u00e1ll\u00edt\u00e1s helyett met\u00f3dusszer\u0171en implement\u00e1ljuk a v\u00e1ltoz\u00f3 el\u00e9r\u00e9s\u00e9nek a m\u00f3dj\u00e1t, s\u0151t k\u00fcl\u00f6n k\u00fcl\u00f6n is meghat\u00e1rozhatjuk a lek\u00e9rdez\u00e9s \u00e9s a be\u00e1ll\u00edt\u00e1s l\u00e1that\u00f3s\u00e1g\u00e1t.

                            "},{"location":"labor/2-nyelvi-eszkozok/#tulajdonsag-szintaktikaja","title":"Tulajdons\u00e1g szintaktik\u00e1ja","text":"

                            A k\u00f6vetkez\u0151 p\u00e9ld\u00e1ban egy Person nev\u0171 oszt\u00e1lyt fogunk elk\u00e9sz\u00edteni, mely egy szem\u00e9lyt reprezent\u00e1l. K\u00e9t tagv\u00e1ltoz\u00f3ja van, name \u00e9s age. A tagv\u00e1ltoz\u00f3khoz k\u00f6zvetlen\u00fcl nem f\u00e9rhet\u00fcnk hozz\u00e1 (mivel priv\u00e1tok), csak a Name, illetve Age publikus tulajdons\u00e1gokon kereszt\u00fcl kezelhetj\u00fck \u0151ket. A p\u00e9lda j\u00f3l szeml\u00e9lteti, hogy a .NET-es tulajdons\u00e1gok egy\u00e9rtelm\u0171en megfelelnek a C++-b\u00f3l \u00e9s Java-b\u00f3l m\u00e1r j\u00f3l ismert SetX(\u2026) illetve GetX() t\u00edpus\u00fa met\u00f3dusoknak, csak itt ez a megold\u00e1s egys\u00e9gbez\u00e1rtabb m\u00f3don nyelvi szinten t\u00e1mogatott.

                            1. Az el\u0151z\u0151 feladatban bevezetett Person oszt\u00e1lyon bel\u00fcl hozzunk l\u00e9tre egy int t\u00edpus\u00fa age nev\u0171 tagv\u00e1ltoz\u00f3t \u00e9s egy ezt el\u00e9rhet\u0151v\u00e9 tev\u0151 Age tulajdons\u00e1got.

                              public class Person\n{\n    private int age;\n    public int Age\n    {\n        get { return age; }\n        set { age = value; }\n    }\n}\n

                              Visual Studio snippetek

                              A laboron ugyan a gyakorl\u00e1s kedv\u00e9\u00e9rt k\u00e9zzel g\u00e9pelt\u00fck be a teljes tulajdons\u00e1got, de a Visual Studio-ban a gyakran el\u0151fordul\u00f3 k\u00f3dr\u00e9szletek l\u00e9trehoz\u00e1s\u00e1ra \u00fagynevezett code snippetek \u00e1llnak rendelkez\u00e9s\u00fcnkre, melyekkel a gyakori nyelvi konstrukci\u00f3kat tudjuk sablonszer\u0171en felhaszn\u00e1lni. A fenti property k\u00f3dr\u00e9szletet a propfull snippettel tudjuk el\u0151csalni. G\u00e9pelj\u00fck be a snippet nev\u00e9t (propfull), majd addig nyomjuk a Tab billenty\u0171t am\u00edg a snippet nem aktiv\u00e1l\u00f3dik (tipikusan 2x).

                              Eml\u00edt\u00e9sre m\u00e9lt\u00f3 egy\u00e9b snippetek a teljess\u00e9g ig\u00e9nye n\u00e9lk\u00fcl:

                              • ctor: konstruktor
                              • for: for ciklus
                              • foreach: foreach ciklus
                              • prop: auto property (l\u00e1sd k\u00e9s\u0151bb)
                              • switch: switch utas\u00edt\u00e1s
                              • cw: Console.WriteLine

                              Ilyen snippeteket egy\u00e9bk\u00e9nt mi is k\u00e9sz\u00edthet\u00fcnk.

                            2. Eg\u00e9sz\u00edts\u00fck ki a Program.cs f\u00e1jl Main f\u00fcggv\u00e9ny\u00e9t, hogy kipr\u00f3b\u00e1lhassuk az \u00faj tulajdons\u00e1gunkat.

                              static void Main(string[] args)\n{\n    var p = new Person();\n    p.Age = 17;\n    p.Age++;\n    Console.WriteLine(p.Age);\n}\n
                            3. Futtassuk a programunkat (F5)

                              L\u00e1thatjuk, hogy a tulajdons\u00e1g a tagv\u00e1ltoz\u00f3khoz hasonl\u00f3an haszn\u00e1lhat\u00f3. A tulajdons\u00e1g lek\u00e9rdez\u00e9se eset\u00e9n a tulajdons\u00e1gban defini\u00e1lt get r\u00e9sz fog lefutni, \u00e9s a tulajdons\u00e1g \u00e9rt\u00e9ke a return \u00e1ltal visszaadott \u00e9rt\u00e9k lesz. A tulajdons\u00e1g be\u00e1ll\u00edt\u00e1sa eset\u00e9n a tulajdons\u00e1gban defini\u00e1lt set r\u00e9sz fog lefutni, \u00e9s a speci\u00e1lis value v\u00e1ltoz\u00f3 \u00e9rt\u00e9ke ebben a szakaszban megfelel a tulajdons\u00e1gnak \u00e9rt\u00e9k\u00fcl adott kifejez\u00e9ssel.

                              Figyelj\u00fck meg a fenti megold\u00e1sban azt, hogy milyen eleg\u00e1nsan tudjuk egy \u00e9vvel megemelni az ember \u00e9letkor\u00e1t. Java, vagy C++ k\u00f3dban egy hasonl\u00f3 m\u0171veletet a p.setAge(p.getAge() + 1) form\u00e1ban \u00edrhattunk volna le, amely jelent\u0151sen k\u00f6r\u00fclm\u00e9nyesebb \u00e9s nehezen olvashat\u00f3bb szintaktika a fentin\u00e9l. A tulajdons\u00e1gok haszn\u00e1lat\u00e1nak legf\u0151bb hozad\u00e9ka, hogy k\u00f3dunk szintaktikailag tiszt\u00e1bb lesz, az \u00e9rt\u00e9kad\u00e1sok/lek\u00e9rdez\u00e9sek pedig az esetek t\u00f6bbs\u00e9g\u00e9ben j\u00f3l elv\u00e1lnak a t\u00e9nyleges f\u00fcggv\u00e9nyh\u00edv\u00e1sokt\u00f3l.

                            4. Gy\u0151z\u0151dj\u00fcnk meg r\u00f3la, hogy a programunk val\u00f3ban elv\u00e9gzi a get \u00e9s set r\u00e9szek h\u00edv\u00e1s\u00e1t. Ehhez helyezz\u00fcnk t\u00f6r\u00e9spontokat (breakpoint) a getter \u00e9s setter blokkok belsej\u00e9be a k\u00f3dszerkeszt\u0151 bal sz\u00e9l\u00e9n l\u00e1that\u00f3 sz\u00fcrke s\u00e1vra kattintva.

                            5. Futtassuk a programot l\u00e9p\u00e9sr\u0151l l\u00e9p\u00e9sre. Ehhez a programot F5 helyett az F11 billenty\u0171vel ind\u00edtsuk, majd az F11 tov\u00e1bbi megnyom\u00e1saival engedj\u00fck sorr\u00f3l sorra a v\u00e9grehajt\u00e1st.

                              L\u00e1thatjuk, hogy a programunk val\u00f3ban minden egyes alkalommal megh\u00edvja a gettert, amikor \u00e9rt\u00e9klek\u00e9rdez\u00e9s, illetve a settert, amikor \u00e9rt\u00e9kbe\u00e1ll\u00edt\u00e1s t\u00f6rt\u00e9nik.

                            6. A setter f\u00fcggv\u00e9nyek egyik fontos funkci\u00f3ja, hogy lehet\u0151s\u00e9get k\u00edn\u00e1lnak az \u00e9rt\u00e9kvalid\u00e1ci\u00f3ra. Eg\u00e9sz\u00edts\u00fck ki ennek szellem\u00e9ben az Age tulajdons\u00e1g setter-\u00e9t.

                              public int Age\n{\n    get { return age; }\n    set \n    {\n        if (value < 0)\n            throw new ArgumentException(\"\u00c9rv\u00e9nytelen \u00e9letkor!\");\n        age = value; \n    }\n}\n

                              Figyelj\u00fck meg, hogy m\u00edg az egyszer\u0171 getter \u00e9s setter eset\u00e9ben az \u00e9rt\u00e9klek\u00e9rdez\u00e9st/be\u00e1ll\u00edt\u00e1st egy sorban tartjuk, addig komplexebb t\u00f6rzs eset\u00e9n m\u00e1r t\u00f6bb sorra t\u00f6rdelj\u00fck.

                            7. Az alkalmaz\u00e1s tesztel\u00e9s\u00e9hez rendelj\u00fcnk hozz\u00e1 negat\u00edv \u00e9rt\u00e9ket az \u00e9letkorhoz a Program oszt\u00e1ly Main f\u00fcggv\u00e9ny\u00e9ben.

                              p.Age = -2;\n
                            8. Futtassuk a programot, gy\u0151z\u0151dj\u00fcnk meg arr\u00f3l, hogy az ellen\u0151rz\u00e9s helyesen m\u0171k\u00f6dik, majd h\u00e1r\u00edtsuk el a hib\u00e1t azzal, hogy pozit\u00edvra cser\u00e9lj\u00fck a be\u00e1ll\u00edtott \u00e9letkort.

                              p.Age = 2;\n
                            "},{"location":"labor/2-nyelvi-eszkozok/#autoimplementalt-tulajdonsag-auto-implemented-property","title":"Autoimplement\u00e1lt tulajdons\u00e1g (auto-implemented property)","text":"

                            A mindennapi munk\u00e1nk sor\u00e1n tal\u00e1lkozhatunk a tulajdons\u00e1goknak egy sokkal t\u00f6m\u00f6rebb szintaktik\u00e1j\u00e1val is. Ez a szintaktika akkor alkalmazhat\u00f3, ha egy olyan tulajdons\u00e1got szeretn\u00e9nk l\u00e9trehozni, melyben:

                            • nem szeretn\u00e9nk semmilyen kieg\u00e9sz\u00edt\u0151 logik\u00e1val ell\u00e1tni a getter \u00e9s setter met\u00f3dusokat,
                            • nincs sz\u00fcks\u00e9g\u00fcnk a priv\u00e1t tagv\u00e1ltoz\u00f3 k\u00f6zvetlen el\u00e9r\u00e9s\u00e9re.

                            Erre n\u00e9zz\u00fcnk a k\u00f6vetkez\u0151kben p\u00e9ld\u00e1t.

                            1. Eg\u00e9sz\u00edts\u00fck ki a Person oszt\u00e1lyunkat egy ilyen, \u00fan. \u201eautoimplement\u00e1lt\u201d tulajdons\u00e1ggal (auto-implemented property). K\u00e9sz\u00edts\u00fcnk egy string t\u00edpus\u00fa Name nev\u0171 tulajdons\u00e1got.

                              public string Name { get; set; }\n

                              A szintaktikai k\u00fcl\u00f6nbs\u00e9g a kor\u00e1bbiakhoz k\u00e9pest: a get \u00e9s a set \u00e1gnak sem adtunk implement\u00e1ci\u00f3t (nincsenek kapcsos z\u00e1r\u00f3jelek). Autoimplemet\u00e1lt tulajdons\u00e1g eset\u00e9n a ford\u00edt\u00f3 egy rejtett, k\u00f3db\u00f3l nem el\u00e9rhet\u0151 v\u00e1ltoz\u00f3t gener\u00e1l az oszt\u00e1lyba, mely a tulajdons\u00e1g aktu\u00e1lis \u00e9rt\u00e9k\u00e9nek t\u00e1rol\u00e1s\u00e1ra szolg\u00e1l. Hangs\u00falyozand\u00f3, hogy ez nem a kor\u00e1bban bevezetett name tagv\u00e1ltoz\u00f3t \u00e1ll\u00edtja \u00e9s k\u00e9rdezi le (az ki is t\u00f6r\u00f6lhetn\u00e9nk), hanem egy rejtett, \u00faj v\u00e1ltoz\u00f3n dolgozik!

                            2. Most ellen\u0151rizz\u00fck a m\u0171k\u00f6d\u00e9s\u00e9t a Main f\u00fcggv\u00e9ny kieg\u00e9sz\u00edt\u00e9s\u00e9vel.

                              static void Main(string[] args)\n{\n    // ...\n    p.Name = \"Luke\";\n    // ...\n    Console.WriteLine(p.Name);\n}\n
                            "},{"location":"labor/2-nyelvi-eszkozok/#alapertelmezett-ertek-default-value","title":"Alap\u00e9rtelmezett \u00e9rt\u00e9k (default value)","text":"

                            Az autoimplement\u00e1lt tulajdons\u00e1gok eset\u00e9ben megadhat\u00f3 a kezdeti \u00e9rt\u00e9k\u00fck is a deklar\u00e1ci\u00f3 sor\u00e1n.

                            1. Adjunk kiindul\u00f3 \u00e9rt\u00e9ket a Name tulajdons\u00e1gnak.

                              public string Name { get; set; } = \"anonymous\";\n
                            "},{"location":"labor/2-nyelvi-eszkozok/#tulajdonsagok-lathatosaga","title":"Tulajdons\u00e1gok l\u00e1that\u00f3s\u00e1ga","text":"

                            A tulajdons\u00e1gok nagy el\u0151nye a teljesen szabad implement\u00e1ci\u00f3 mellett, hogy a getter \u00e9s a setter l\u00e1that\u00f3s\u00e1g\u00e1t k\u00fcl\u00f6n k\u00fcl\u00f6n is lehet \u00e1ll\u00edtani.

                            1. \u00c1ll\u00edtsuk a Name tulajdons\u00e1g setter\u00e9nek a l\u00e1that\u00f3s\u00e1g\u00e1t priv\u00e1tra.

                              public string Name { get; private set; }\n

                              Ilyenkor a Program oszt\u00e1lyban ford\u00edt\u00e1si hib\u00e1t kapunk a p.Name = \"Luke\"; utas\u00edt\u00e1sra. Az alapvet\u0151 szab\u00e1ly az, hogy a getter \u00e9s a setter \u00f6r\u00f6kli a property l\u00e1that\u00f3s\u00e1g\u00e1t, mely tov\u00e1bb sz\u0171k\u00edthet\u0151, de nem laz\u00edthat\u00f3. A l\u00e1that\u00f3s\u00e1g szab\u00e1lyoz\u00e1sa autoimplement\u00e1lt \u00e9s nem autoimplement\u00e1lt tulajdons\u00e1gok eset\u00e9n is haszn\u00e1lhat\u00f3.

                            2. \u00c1ll\u00edtsuk vissza a l\u00e1that\u00f3s\u00e1got (t\u00e1vol\u00edtsuk el a private kulcssz\u00f3t a Name tulajdons\u00e1g settere el\u0151l), hogy megsz\u0171nj\u00f6n a ford\u00edt\u00e1si hiba.

                            "},{"location":"labor/2-nyelvi-eszkozok/#csak-olvashato-tulajdonsag-readonly-property","title":"Csak olvashat\u00f3 tulajdons\u00e1g (readonly property)","text":"

                            A setter elhagyhat\u00f3, \u00edgy egy olyan tulajdons\u00e1got kapunk, mely csak olvashat\u00f3. Autoimplement\u00e1lt tulajdons\u00e1g eset\u00e9n ennek is adhat\u00f3 kezd\u0151\u00e9rt\u00e9k: erre csak konstruktorban, vagy alap\u00e9rtelmezett \u00e9rt\u00e9kkel val\u00f3 ell\u00e1t\u00e1ssal (l\u00e1sd fent) van lehet\u0151s\u00e9g, ellent\u00e9tben a priv\u00e1t setterrel rendelkez\u0151 tulajdons\u00e1gokkal, melyek settere b\u00e1rmely, az oszt\u00e1lyban tal\u00e1lhat\u00f3 tagf\u00fcggv\u00e9nyb\u0151l h\u00edvhat\u00f3.

                            Csak olvashat\u00f3 tulajdons\u00e1g defini\u00e1l\u00e1s\u00e1t a k\u00f6vetkez\u0151 k\u00f3dr\u00e9szletek illusztr\u00e1lj\u00e1k (a k\u00f3dunkba NE vezess\u00fck be):

                            a) Autoimplement\u00e1lt eset

                            public string Name { get; }\n

                            b) Nem autoimplement\u00e1lt eset

                            private string name;\n...\npublic string Name { get {return name; } }\n
                            "},{"location":"labor/2-nyelvi-eszkozok/#szamitott-ertek-calculated-value","title":"Sz\u00e1m\u00edtott \u00e9rt\u00e9k (calculated value)","text":"

                            A csak getterrel rendelkez\u0151 tulajdons\u00e1goknak van m\u00e9g egy haszn\u00e1lati m\u00f3dja. Valamilyen sz\u00e1m\u00edtott \u00e9rt\u00e9k meghat\u00e1roz\u00e1s\u00e1ra is haszn\u00e1lhat\u00f3, mely mindig kisz\u00e1mol egy megadott logika alapj\u00e1n egy \u00e9rt\u00e9ket, de a \"csak olvashat\u00f3 tulajdons\u00e1g\"-gal szemben nincs m\u00f6g\u00f6tte k\u00f6zvetlen\u00fcl a tulajdons\u00e1ghoz tartoz\u00f3 adattag. Ezt a k\u00f6vetkez\u0151 k\u00f3dr\u00e9szlet illusztr\u00e1lja (a k\u00f3dunkba NE vezess\u00fck be):

                            public int AgeInDogYear { get { return Age * 7; } }\n
                            "},{"location":"labor/2-nyelvi-eszkozok/#2-feladat-delegat-delegate-metodusreferencia","title":"2. Feladat \u2013 Deleg\u00e1t (delegate, met\u00f3dusreferencia)","text":"

                            Forduljon a k\u00f3d!

                            A tov\u00e1bbi feladatok \u00e9p\u00edteni fognak az el\u0151z\u0151 feladatok v\u00e9geredm\u00e9nyeire. Ha programod nem fordul le, vagy nem megfelel\u0151en m\u0171k\u00f6dik, jelezd ezt a gyakorlatvezet\u0151dnek a feladatok v\u00e9g\u00e9n, \u00e9s seg\u00edt elh\u00e1r\u00edtani a hib\u00e1t.

                            A deleg\u00e1tok t\u00edpusos met\u00f3dusreferenci\u00e1kat jelentenek .NET-ben, a C/C++ f\u00fcggv\u00e9nypointerek modern megfelel\u0151i. Egy deleg\u00e1t seg\u00edts\u00e9g\u00e9vel egy olyan t\u00edpus\u00fa v\u00e1ltoz\u00f3t defini\u00e1lhatunk, amellyel met\u00f3dusokra tudunk mutatni/hivatkozni. Nem ak\u00e1rmilyenre, hanem - a C++ f\u00fcggv\u00e9nypointerekkel anal\u00f3g m\u00f3don - olyanokra, amely t\u00edpusa (param\u00e9terlist\u00e1ja \u00e9s visszat\u00e9r\u00e9si \u00e9rt\u00e9ke) megfelel a deleg\u00e1t t\u00edpus\u00e1nak. A deleg\u00e1t v\u00e1ltoz\u00f3 \"megh\u00edv\u00e1s\u00e1val\" az \u00e9rt\u00e9k\u00fcl adott (beregisztr\u00e1lt) met\u00f3dus automatikusan megh\u00edv\u00f3dik. A deleg\u00e1tok haszn\u00e1lat\u00e1nak egyik el\u0151nye az, hogy fut\u00e1si id\u0151ben d\u00f6nthetj\u00fck el, hogy t\u00f6bb met\u00f3dus k\u00f6z\u00fcl \u00e9ppen melyiket szeretn\u00e9nk megh\u00edvni.

                            N\u00e9h\u00e1ny p\u00e9lda deleg\u00e1tok haszn\u00e1lat\u00e1ra:

                            • egy univerz\u00e1lis sorrendez\u0151 f\u00fcggv\u00e9nynek param\u00e9terk\u00e9nt az elemek \u00f6sszehasonl\u00edt\u00e1s\u00e1t v\u00e9gz\u0151 f\u00fcggv\u00e9ny \u00e1tad\u00e1sa,
                            • egy \u00e1ltal\u00e1nos gy\u0171jtem\u00e9nyen univerz\u00e1lis sz\u0171r\u00e9si logika megval\u00f3s\u00edt\u00e1sa, melynek param\u00e9terben egy deleg\u00e1t form\u00e1j\u00e1ban adjuk \u00e1t azt a f\u00fcggv\u00e9nyt, amely eld\u00f6nti, hogy egy elemet bele kell-e venni a sz\u0171rt list\u00e1ba,
                            • a publish-subscribe minta megval\u00f3s\u00edt\u00e1sa, amikor bizonyos objektumok m\u00e1s objektumokat \u00e9rtes\u00edtenek bizonyos magukkal kapcsolatos esem\u00e9nyek bek\u00f6vetkez\u00e9s\u00e9r\u0151l.

                            A k\u00f6vetkez\u0151 p\u00e9ld\u00e1nkban lehet\u0151v\u00e9 tessz\u00fck, hogy a kor\u00e1bban l\u00e9trehozott Person oszt\u00e1ly objektumai szabadon \u00e9rtes\u00edthess\u00e9k m\u00e1s oszt\u00e1lyok objektumait arr\u00f3l, ha egy szem\u00e9ly \u00e9letkora megv\u00e1ltozott. Ennek \u00e9rdek\u00e9ben bevezet\u00fcnk egy deleg\u00e1t t\u00edpust (AgeChangingDelegate), mely param\u00e9terlist\u00e1j\u00e1ban \u00e1t tudja adni az ember\u00fcnk \u00e9letkor\u00e1nak aktu\u00e1lis, illetve \u00faj \u00e9rt\u00e9k\u00e9t. Ezt k\u00f6vet\u0151en l\u00e9trehozunk egy publikus AgeChangingDelegate t\u00edpus\u00fa tagv\u00e1ltoz\u00f3t a Person oszt\u00e1lyban, mely lehet\u0151v\u00e9 teszi, hogy egy k\u00fcls\u0151 f\u00e9l megadhassa azt a f\u00fcggv\u00e9nyt, amelyen kereszt\u00fcl az adott Person p\u00e9ld\u00e1ny v\u00e1ltoz\u00e1sair\u00f3l \u00e9rtes\u00edt\u00e9st k\u00e9r.

                            1. Hozzunk l\u00e9tre egy \u00faj deleg\u00e1t t\u00edpust, mely void visszat\u00e9r\u00e9si \u00e9rt\u00e9k\u0171, \u00e9s k\u00e9t darab int param\u00e9tert elv\u00e1r\u00f3 f\u00fcggv\u00e9nyre tud hivatkozni. Figyelj\u00fcnk r\u00e1, hogy az \u00faj t\u00edpust a Person oszt\u00e1ly el\u0151tt, k\u00f6zvetlen\u00fcl a n\u00e9vt\u00e9r scope-j\u00e1ban defini\u00e1ljuk!

                              namespace PropertyDemo\n{\n    public delegate void AgeChangingDelegate(int oldAge, int newAge);\n\n    public class Person\n    {\n        // ...\n

                              Az AgeChangingDelegate egy t\u00edpus (figyelj\u00fck a VS sz\u00ednez\u00e9s\u00e9t is), mely b\u00e1rhol szerepelhet, ahol t\u00edpus \u00e1llhat (pl. lehet l\u00e9trehozni ez alapj\u00e1n tagv\u00e1ltoz\u00f3t, lok\u00e1lis v\u00e1ltoz\u00f3t, f\u00fcggv\u00e9ny param\u00e9tert stb.).

                            2. Tegy\u00fck lehet\u0151v\u00e9, hogy a Person objektumai r\u00e1mutathassanak tetsz\u0151leges, a fenti szignat\u00far\u00e1nak megfelel\u0151 f\u00fcggv\u00e9nyre. Ehhez hozzunk l\u00e9tre egy AgeChangingDelegate t\u00edpus\u00fa tagv\u00e1ltoz\u00f3t a Person oszt\u00e1lyban!

                              public class Person\n{\n    public AgeChangingDelegate AgeChanging;\n

                              Ez \u00edgy most mennyire objektumorient\u00e1lt?

                              A publikus tagv\u00e1ltoz\u00f3k\u00e9nt l\u00e9trehozott met\u00f3dusreferencia val\u00f3j\u00e1ban (egyel\u0151re) s\u00e9rti az objektumorint\u00e1lt egys\u00e9gbez\u00e1r\u00e1si/inform\u00e1ci\u00f3rejt\u00e9si elveket. Erre k\u00e9s\u0151bb visszat\u00e9r\u00fcnk m\u00e9g.

                            3. H\u00edvjuk meg a f\u00fcggv\u00e9nyt minden alkalommal, amikor az ember\u00fcnk kora megv\u00e1ltozik. Ehhez eg\u00e9sz\u00edts\u00fck ki az Age tulajdons\u00e1g setter\u00e9t a k\u00f6vetkez\u0151kkel.

                              public int Age\n{\n    get { return age; }\n    set \n    {\n        if (value < 0)\n            throw new ArgumentException(\"\u00c9rv\u00e9nytelen \u00e9letkor!\");\n        if (AgeChanging != null)\n            AgeChanging(age, value);\n        age = value; \n    }\n}\n

                              A fenti k\u00f3dr\u00e9szlet sz\u00e1mos fontos szab\u00e1lyt demonstr\u00e1l:

                              • A valid\u00e1ci\u00f3s logika \u00e1ltal\u00e1ban megel\u0151zi az \u00e9rtes\u00edt\u00e9si logik\u00e1t.
                              • Az \u00e9rtes\u00edt\u00e9si logika jelleg\u00e9t\u0151l f\u00fcgg, hogy az \u00e9rt\u00e9kad\u00e1s el\u0151tt, vagy ut\u00e1n futtatjuk le (ebben az esetben, mivel a \"changing\" sz\u00f3 egy folyamatban l\u00e9v\u0151 dologra utal, az \u00e9rtes\u00edt\u00e9s megel\u0151zi az \u00e9rt\u00e9kad\u00e1st, a bek\u00f6vetkez\u00e9st m\u00falt id\u0151 jelezni: \"changed\")
                              • Fel kell k\u00e9sz\u00fcln\u00fcnk r\u00e1, hogy a delegate t\u00edpus\u00fa tagv\u00e1ltoz\u00f3hoz m\u00e9g senki nem rendelt \u00e9rt\u00e9ket (nincs egy subscriber/el\u0151fizet\u0151 sem). Ilyen esetekben a megh\u00edv\u00e1suk kiv\u00e9telt okozna, ez\u00e9rt megh\u00edv\u00e1s el\u0151tt mindig ellen\u0151rizni kell, hogy a tagv\u00e1ltoz\u00f3 \u00e9rt\u00e9ke null-e.
                              • Az esem\u00e9ny els\u00fct\u00e9sekor a null vizsg\u00e1latot \u00e9s az esem\u00e9ny els\u00fct\u00e9st eleg\u00e1nsabb, t\u00f6m\u00f6rebb, \u00e9s sz\u00e1lbiztosabb form\u00e1ban is meg tudjuk tenni a \"?.\" null-conditional oper\u00e1torral (C# 6-t\u00f3l):
                              if (AgeChanging != null)\n    AgeChanging(age, value);\n

                              helyett

                              AgeChanging?.Invoke(age, value);\n

                              Ez csak akkor s\u00fcti el az esem\u00e9nyt, ha nem null, egy\u00e9bk\u00e9nt semmit nem csin\u00e1l.

                            4. Ha szigor\u00faan n\u00e9zz\u00fck, akkor csak akkor kellene els\u00fctni az esem\u00e9nyt, ha a kor val\u00f3ban v\u00e1ltozik is, vagyis a property set \u00e1g\u00e1ban meg kellene vizsg\u00e1lni, az \u00faj \u00e9rt\u00e9k egyezik-e a r\u00e9givel. Megold\u00e1s lehet, ha a setter els\u0151 sor\u00e1ban azonnal visszat\u00e9r\u00fcnk, ha az \u00faj \u00e9rt\u00e9k egyezik a r\u00e9givel:

                              if (age == value) \n    return;\n\u2026\n
                            5. K\u00e9sz vagyunk a Person oszt\u00e1ly k\u00f3dj\u00e1val. T\u00e9rj\u00fcnk \u00e1t az el\u0151fizet\u0151re! Ehhez mindenek el\u0151tt a Program oszt\u00e1lyt kell kieg\u00e9sz\u00edten\u00fcnk egy \u00fajabb f\u00fcggv\u00e9nnyel.

                              class Program\n{\n    // ...\n\n    private static void PersonAgeChanging(int oldAge, int newAge)\n    {\n        Console.WriteLine(oldAge + \" => \" + newAge);\n    }\n}\n

                              Tipp

                              Fokozottan \u00fcgyelj\u00fcnk r\u00e1, hogy az \u00faj f\u00fcggv\u00e9ny a megfelel\u0151 scope-ba ker\u00fclj\u00f6n! M\u00edg a delegate t\u00edpust az oszt\u00e1lyon k\u00edv\u00fclre (de namespace-en bel\u00fclre) helyezt\u00fck el, a f\u00fcggv\u00e9nyt az oszt\u00e1lyon bel\u00fclre helyezz\u00fck!

                            6. V\u00e9gezet\u00fcl iratkozzunk fel a v\u00e1ltoz\u00e1sk\u00f6vet\u00e9sre a Main f\u00fcggv\u00e9nyben!

                              static void Main(string[] args)\n{\n  Person p = new Person();\n  p.AgeChanging = new AgeChangingDelegate(PersonAgeChanging);\n  // ...\n
                            7. Futtassuk a programot!

                              Pl. az AgeChanging?.Invoke(age, value); sorra t\u00f6r\u00e9spontot helyezve, az alkalmaz\u00e1st debuggolva futtatva, \u00e9s a k\u00f3dot l\u00e9ptetve figyelj\u00fck meg, hogy az esem\u00e9ny minden egyes setter fut\u00e1skor, \u00edgy az els\u0151 \u00e9rt\u00e9kad\u00e1skor \u00e9s az inkrement\u00e1l\u00e1s sor\u00e1n egyar\u00e1nt lefut.

                            8. Eg\u00e9sz\u00edts\u00fck ki a Main f\u00fcggv\u00e9nyt t\u00f6bbsz\u00f6ri feliratkoz\u00e1ssal (a += oper\u00e1torral lehet \u00faj feliratkoz\u00f3t felvenni a megl\u00e9v\u0151k mell\u00e9), majd futtassuk a programot.

                              p.AgeChanging = new AgeChangingDelegate(PersonAgeChanging);\np.AgeChanging += new AgeChangingDelegate(PersonAgeChanging);\np.AgeChanging += PersonAgeChanging; // T\u00f6m\u00f6rebb szintaktika\n

                              L\u00e1that\u00f3an minden egyes \u00e9rt\u00e9kv\u00e1ltoz\u00e1skor mind a h\u00e1rom beregisztr\u00e1lt/\u201efeliratkozott\u201d f\u00fcggv\u00e9ny lefut. Ez az\u00e9rt lehets\u00e9ges, mert a delegate t\u00edpus\u00fa tagv\u00e1ltoz\u00f3k val\u00f3j\u00e1ban nem csup\u00e1n egy f\u00fcggv\u00e9nyreferenci\u00e1t, hanem egy f\u00fcggv\u00e9nyreferencia-list\u00e1t tartalmaznak (\u00e9s tartanak karban).

                              Figyelj\u00fck meg a fenti harmadik sorban, hogy a f\u00fcggv\u00e9nyreferenci\u00e1kat az el\u0151sz\u00f6r l\u00e1tottn\u00e1l t\u00f6m\u00f6rebb szintaxissal is le\u00edrhatjuk: csak a f\u00fcggv\u00e9ny nev\u00e9t adjuk meg a += oper\u00e1tor ut\u00e1n, a new AgeChangingDelegate(...) n\u00e9lk\u00fcl. Ett\u0151l f\u00fcggetlen\u00fcl ekkor is egy AgeChangingDelegate objektum fogja becsomagolni a PersonAgeChanging f\u00fcggv\u00e9nyeket a sz\u00ednfalak m\u00f6g\u00f6tt. A gyakorlatban ezt a t\u00f6m\u00f6rebb szintaktik\u00e1t szoktuk haszn\u00e1lni.

                            9. Pr\u00f3b\u00e1ljuk ki a leiratkoz\u00e1st is (szabadon v\u00e1lasztott ponton), majd futtassuk a programot.

                              p.AgeChanging -= PersonAgeChanging;\n
                            "},{"location":"labor/2-nyelvi-eszkozok/#3-feladat-esemeny-event","title":"3. Feladat \u2013 Esem\u00e9ny (event)","text":"

                            Ahogyan a tulajdons\u00e1gok a getter \u00e9s setter met\u00f3dusoknak, addig a fent l\u00e1tott delegate mechanizmus a Java-b\u00f3l ismert Event Listener-eknek k\u00edn\u00e1lj\u00e1k egy a szintaktika tekintet\u00e9ben letisztultabb alternat\u00edv\u00e1j\u00e1t. A fenti megold\u00e1sunk azonban egyel\u0151re m\u00e9g s\u00falyosan s\u00e9rt p\u00e1r OO elvet (egys\u00e9gbez\u00e1r\u00e1s, inform\u00e1ci\u00f3rejt\u00e9s). Ezt az al\u00e1bbi k\u00e9t p\u00e9ld\u00e1val tudjuk demonstr\u00e1lni.

                            1. Az esem\u00e9nyt val\u00f3j\u00e1ban k\u00edv\u00fclr\u0151l (m\u00e1s oszt\u00e1lyok m\u0171veleteib\u0151l) is ki tudjuk v\u00e1ltani. Ez szerencs\u00e9tlen, hiszen \u00edgy az esem\u00e9ny hamis m\u00f3don akkor is kiv\u00e1lthat\u00f3, r\u00e1ad\u00e1sul val\u00f3tlan adatokkal, amikor az a gyakorlatban be sem k\u00f6vetkezett, becsapva az \u00f6sszes el\u0151fizet\u0151t. Ennek demonstr\u00e1l\u00e1s\u00e1ra sz\u00farjuk be a k\u00f6vetkez\u0151 sort a Main f\u00fcggv\u00e9ny v\u00e9g\u00e9re.

                              p.AgeChanging(67, 12);\n

                              Itt a p Person objektum vonatkoz\u00e1s\u00e1ban egy val\u00f3tlan \u00e9letkorv\u00e1ltoz\u00e1s esem\u00e9nyt v\u00e1ltottunk ki, becsapva minden el\u0151fizet\u0151t. A j\u00f3 megold\u00e1s az lenne, ha az esem\u00e9nyt csak a Person oszt\u00e1ly m\u0171veletei tudn\u00e1k kiv\u00e1ltani.

                            2. Egy m\u00e1sik probl\u00e9ma a k\u00f6vetkez\u0151. B\u00e1r a += \u00e9s a -= tekintettel vannak a list\u00e1ba feliratkozott t\u00f6bbi f\u00fcggv\u00e9nyre, val\u00f3j\u00e1ban az = oper\u00e1torral b\u00e1rmikor fel\u00fcl\u00edrhatjuk (kit\u00f6r\u00f6lhetj\u00fck) m\u00e1sok feliratkoz\u00e1sait. Pr\u00f3b\u00e1ljuk ki ezt is, a k\u00f6vetkez\u0151 sor besz\u00far\u00e1s\u00e1val (k\u00f6zvetlen\u00fcl a fel \u00e9s leiratkoz\u00e1sok ut\u00e1n sz\u00farjuk be).

                              p.AgeChanging = null;\n
                            3. L\u00e1ssuk el az event kulcssz\u00f3val az AgeChanging tagv\u00e1ltoz\u00f3t Person.cs-ben!

                              Person.cs
                              public event AgeChangingDelegate AgeChanging;\n

                              Az event kulcssz\u00f3 feladata val\u00f3j\u00e1ban az, hogy a fenti k\u00e9t probl\u00e9m\u00e1t kiz\u00e1rva visszak\u00e9nyszer\u00edtse programunkat az objektumorient\u00e1lt mederbe.

                            4. Pr\u00f3b\u00e1ljuk meg leford\u00edtani a programot. L\u00e1tni fogjuk, hogy a ford\u00edt\u00f3 a kor\u00e1bbi kih\u00e1g\u00e1sainkat most m\u00e1r ford\u00edt\u00e1si hibak\u00e9nt kezeli.

                            5. T\u00e1vol\u00edtsuk el a h\u00e1rom hib\u00e1s k\u00f3dsort (figyelj\u00fck meg, hogy m\u00e1r az els\u0151 k\u00f6zvetlen \u00e9rt\u00e9kad\u00e1s is hib\u00e1nak min\u0151s\u00fcl), majd ford\u00edtsuk le \u00e9s futtassuk az alkalmaz\u00e1sunkat!

                            "},{"location":"labor/2-nyelvi-eszkozok/#4-feladat-attributumok","title":"4. Feladat \u2013 Attrib\u00fatumok","text":""},{"location":"labor/2-nyelvi-eszkozok/#sorositas-testreszabasa-attributummal","title":"Soros\u00edt\u00e1s testreszab\u00e1sa attrib\u00fatummal","text":"

                            Az attrib\u00fatumok seg\u00edts\u00e9g\u00e9vel deklarat\u00edv m\u00f3don metaadatokkal l\u00e1thatjuk el forr\u00e1sk\u00f3dunkat. Az attrib\u00fatum is tulajdonk\u00e9ppen egy oszt\u00e1ly, melyet hozz\u00e1k\u00f6t\u00fcnk a program egy megadott elem\u00e9hez (t\u00edpushoz, oszt\u00e1lyhoz, interf\u00e9szhez, met\u00f3dushoz stb.). Ezeket a metainform\u00e1ci\u00f3kat a program fut\u00e1sa k\u00f6zben b\u00e1rki (ak\u00e1r mi magunk is) kiolvashatja az \u00fagynevezett reflection mechanizmus seg\u00edts\u00e9g\u00e9vel. Az attrib\u00fatumok a Java annot\u00e1ci\u00f3k .NET-beli megfelel\u0151inek is tekinthet\u0151k.

                            property vs. attrib\u00fatum vs. static

                            Felmer\u00fcl a k\u00e9rd\u00e9s, hogy milyen oszt\u00e1lyjellemz\u0151k ker\u00fcljenek tulajdons\u00e1gokba \u00e9s melyek attrib\u00fatumokba egy oszt\u00e1ly eset\u00e9ben. A tulajdons\u00e1gok mag\u00e1ra az objektum p\u00e9ld\u00e1nyra vonatkoznak, m\u00edg az attrib\u00fatum az azt le\u00edr\u00f3 oszt\u00e1lyra (vagy annak valamilyen tagj\u00e1ra).

                            Ilyen szempontb\u00f3l az attrib\u00fatumok k\u00f6zelebb \u00e1llnak a statikus tulajdons\u00e1gokhoz, m\u00e9gis megfontoland\u00f3, hogy egy adott adatot statikus tagk\u00e9nt vagy attrib\u00fatumk\u00e9nt defini\u00e1ln\u00e1nk. Attrib\u00fatummal sokkal deklarat\u00edvabb a le\u00edr\u00e1s, \u00e9s nem szennyezz\u00fck olyan r\u00e9szletekkel a k\u00f3dot, melyeknek nem kellene az oszt\u00e1ly publikus interf\u00e9sz\u00e9n megjelennie.

                            A NET sz\u00e1mos be\u00e9p\u00edtett attrib\u00fatumot defini\u00e1l, melyek funkci\u00f3ja a legk\u00fcl\u00f6nb\u00f6z\u0151bb f\u00e9le lehet. A k\u00f6vetkez\u0151 p\u00e9ld\u00e1ban haszn\u00e1lt attrib\u00fatumok p\u00e9ld\u00e1ul az XML soros\u00edt\u00f3val k\u00f6z\u00f6lnek k\u00fcl\u00f6nb\u00f6z\u0151 metainform\u00e1ci\u00f3kat.

                            1. Sz\u00farjuk be a Main f\u00fcggv\u00e9ny v\u00e9g\u00e9re a k\u00f6vetkez\u0151 k\u00f3dr\u00e9szletet, majd futtassuk a programunkat!

                              var serializer = new XmlSerializer(typeof(Person));\nvar stream = new FileStream(\"person.txt\", FileMode.Create);\nserializer.Serialize(stream, p);\nstream.Close();\nProcess.Start(new ProcessStartInfo\n{\n    FileName = \"person.txt\",\n    UseShellExecute = true,\n});\n

                              A fenti p\u00e9ld\u00e1b\u00f3l az utols\u00f3 Process.Start f\u00fcggv\u00e9nyh\u00edv\u00e1s nem a soros\u00edt\u00f3 logika r\u00e9sze, csup\u00e1n egy frapp\u00e1ns megold\u00e1s arra, hogy a Windows alap\u00e9rtelmezett sz\u00f6vegf\u00e1jl n\u00e9zeget\u0151j\u00e9vel megnyissuk a keletkezett adat\u00e1llom\u00e1nyt. Ezt kipr\u00f3b\u00e1lhatjuk, de a haszn\u00e1lt .NET runtime-t\u00f3l \u00e9s az oper\u00e1ci\u00f3s rendszer\u00fcnkt\u0151l f\u00fcgg, t\u00e1mogatott-e. Ha nem, fut\u00e1s k\u00f6zben hib\u00e1t kapunk. Ez esetben hagyjuk kikommentezve, \u00e9s a person.txt f\u00e1jlt a f\u00e1jlrendszerben megkeresve k\u00e9zzel nyissuk meg (a Visual Studio mapp\u00e1nkban a *\\bin\\Debug\\* alatt tal\u00e1lhat\u00f3 az .exe alkalmaz\u00e1sunk mellett).

                            2. N\u00e9zz\u00fck meg a keletkezett f\u00e1jl szerkezet\u00e9t. Figyelj\u00fck meg, hogy minden tulajdons\u00e1g a nev\u00e9nek megfelel\u0151 XML elemre lett lek\u00e9pezve.

                            3. .NET attrib\u00fatumok seg\u00edts\u00e9g\u00e9vel olyan metaadatokkal l\u00e1thatjuk el a Person oszt\u00e1lyunkat, melyek k\u00f6zvetlen\u00fcl m\u00f3dos\u00edtj\u00e1k a soros\u00edt\u00f3 viselked\u00e9s\u00e9t. Az XmlRoot attrib\u00fatum lehet\u0151s\u00e9get k\u00edn\u00e1l a gy\u00f6k\u00e9relem \u00e1tnevez\u00e9s\u00e9re. Helyezz\u00fck el a Person oszt\u00e1ly f\u00f6l\u00e9!

                              [XmlRoot(\"Szem\u00e9ly\")]\npublic class Person \n{\n    // ...\n}\n
                            4. Az XmlAttribute attrib\u00fatum jelzi a soros\u00edt\u00f3 sz\u00e1m\u00e1ra, hogy a jel\u00f6lt tulajdons\u00e1got ne xml elemre, hanem xml attrib\u00fatumra k\u00e9pezze le. L\u00e1ssuk el ezzel az Age tulajdons\u00e1got (\u00e9s ne a tagv\u00e1ltoz\u00f3t!)!

                              [XmlAttribute(\"Kor\")]\npublic int Age\n
                            5. Az XmlIgnore attrib\u00fatum jelzi a soros\u00edt\u00f3nak, hogy a jel\u00f6lt tulajdons\u00e1g teljesen elhagyand\u00f3 az eredm\u00e9nyb\u0151l. Pr\u00f3b\u00e1ljuk ki a Name tulajdons\u00e1g f\u00f6l\u00f6tt.

                              [XmlIgnore]\npublic string Name { get; set; }\n
                            6. Futtassuk az alkalmaz\u00e1sunkat! Hasonl\u00edtsuk \u00f6ssze az eredm\u00e9nyt a kor\u00e1bbiakkal.

                            7. "},{"location":"labor/2-nyelvi-eszkozok/#5-feladat-delegat-2","title":"5. Feladat \u2013 Deleg\u00e1t 2.","text":"

                              A 2. \u00e9s 3. feladatokban a deleg\u00e1tokkal esem\u00e9ny alap\u00fa \u00fczenetk\u00fcld\u00e9st val\u00f3s\u00edtottunk meg. A deleg\u00e1tok haszn\u00e1lat\u00e1nak m\u00e1sik tipikus eset\u00e9ben a f\u00fcggv\u00e9nyreferenci\u00e1kat arra haszn\u00e1ljuk, hogy egy algoritmus vagy \u00f6sszetettebb m\u0171velet sz\u00e1m\u00e1ra egy el\u0151re nem defini\u00e1lt l\u00e9p\u00e9s implement\u00e1ci\u00f3j\u00e1t \u00e1tadjuk.

                              A be\u00e9p\u00edtett generikus lista oszt\u00e1ly (List<T>) FindAll f\u00fcggv\u00e9nye p\u00e9ld\u00e1ul k\u00e9pes arra, hogy visszaadjon egy \u00faj list\u00e1ban minden olyan elemet, mely egy adott felt\u00e9telnek eleget tesz. A konkr\u00e9t sz\u0171r\u00e9si felt\u00e9telt egy f\u00fcggv\u00e9ny, pontosabban delegate form\u00e1j\u00e1ban adhatjuk meg param\u00e9terben (ez a FindAll minden elemre megh\u00edvja), mely igazat ad minden olyan elemre, amit az eredm\u00e9nylist\u00e1ban szeretn\u00e9nk l\u00e1tni. A f\u00fcggv\u00e9ny param\u00e9ter\u00e9nek a t\u00edpusa a k\u00f6vetkez\u0151 el\u0151re defini\u00e1lt delegate t\u00edpus (nem kell beg\u00e9pelni/l\u00e9trehozni, hiszen m\u00e1r l\u00e9tezik):

                              public delegate bool Predicate<T>(T obj)\n

                              Note

                              A fenti teljes defin\u00edci\u00f3 megjelen\u00edt\u00e9s\u00e9hez csak g\u00e9pelj\u00fck be valahova, pl. a Main f\u00fcggv\u00e9ny v\u00e9g\u00e9re a Predicate t\u00edpusnevet, kattintsunk rajta eg\u00e9rrel, \u00e9s az F12 billenty\u0171vel navig\u00e1ljunk el a defin\u00edci\u00f3j\u00e1hoz.

                              Vagyis bemenetk\u00e9nt egy olyan t\u00edpus\u00fa v\u00e1ltoz\u00f3t v\u00e1r, mint a listaelemek t\u00edpusa, kimenetk\u00e9nt pedig egy logikai (bool) \u00e9rt\u00e9ket. A fentiek demonstr\u00e1l\u00e1s\u00e1ra kieg\u00e9sz\u00edtj\u00fck a kor\u00e1bbi programunkat egy sz\u0171r\u00e9ssel, mely a list\u00e1b\u00f3l csak a p\u00e1ratlan elemeket fogja megtartani.

                              1. Val\u00f3s\u00edtsunk meg egy olyan sz\u0171r\u0151f\u00fcggv\u00e9nyt az alkalmaz\u00e1sunkban, amely a p\u00e1ratlan sz\u00e1mokat adja vissza:

                                private static bool MyFilter(int n)\n{\n    return n % 2 == 1;\n}\n
                              2. Eg\u00e9sz\u00edts\u00fck ki a kor\u00e1bban \u00edrt k\u00f3dunkat a sz\u0171r\u0151 f\u00fcggv\u00e9ny\u00fcnk haszn\u00e1lat\u00e1val:

                                var list = new List<int>();\nlist.Add(1);\nlist.Add(2);\nlist.Add(3);\nlist = list.FindAll(MyFilter);\n\nforeach (int n in list)\n{\n    Console.WriteLine($\"Value: {n}\");\n}\n
                              3. Futtassuk az alkalmaz\u00e1st. Figyelj\u00fck meg, hogy a konzolon val\u00f3ban csak a p\u00e1ratlan sz\u00e1mok jelennek meg.

                              4. \u00c9rdekess\u00e9gk\u00e9nt elhelyezhet\u00fcnk egy t\u00f6r\u00e9spontot (breakpoint) a MyFilter f\u00fcggv\u00e9ny\u00fcnk belsej\u00e9ben, \u00e9s megfigyelhetj\u00fck, hogy a f\u00fcggv\u00e9ny val\u00f3ban minden egyes listaelemre k\u00fcl\u00f6n-k\u00fcl\u00f6n megh\u00edv\u00f3dik.

                              Collection initializer szintaxis

                              Minden Add met\u00f3dussal rendelkez\u0151, az IEnumerable interf\u00e9szt implement\u00e1l\u00f3 oszt\u00e1lyra (tipikusan kollekci\u00f3k) a collection initializer szintaxis az al\u00e1bbi m\u00f3don:

                              var list = new List<int>() { 1, 2, 3 };\n

                              C# 12-t\u0151l kezdve m\u00e9g egyszer\u0171bb szintaxis (\u00fan. collection expression) is haszn\u00e1lhat\u00f3 egy gy\u0171jtem\u00e9ny inicializ\u00e1l\u00e1s\u00e1ra, ha v\u00e1ltoz\u00f3 t\u00edpus\u00e1ra a ford\u00edt\u00f3 ki tudja k\u00f6vetkeztetni, hogy gy\u0171jetm\u00e9nyr\u0151l van sz\u00f3. Pl.:

                              List<int> list = [1, 2, 3];\n
                              "},{"location":"labor/2-nyelvi-eszkozok/#6-feladat-lambda-kifejezesek","title":"6. Feladat \u2013 Lambda kifejez\u00e9sek","text":"

                              Az \u00e9rintett t\u00e9mak\u00f6r\u00f6k az el\u0151ad\u00e1sanyagban r\u00e9szletesen szerepelnek, itt nem ism\u00e9telj\u00fck meg \u0151ket L\u00e1sd \u201eEl\u0151ad\u00e1s 02 - Nyelvi eszk\u00f6z\u00f6k.pdf\u201d dokumentum \"Lambda expression (lambda kifejez\u00e9s)\" fejezete. A kulcselem a => (lambda oper\u00e1tor), mely seg\u00edts\u00e9g\u00e9vel lambda kifejez\u00e9sek, vagyis n\u00e9vtelen f\u00fcggv\u00e9nyek defini\u00e1l\u00e1s\u00e1ra van lehet\u0151s\u00e9g.

                              Action \u00e9s Func

                              A .NET be\u00e9p\u00edtett Func \u00e9s Action generikus delegate t\u00edpusokra itt id\u0151 hi\u00e1ny\u00e1ban nem t\u00e9r\u00fcnk ki. Ett\u0151l m\u00e9g beletartoznak az alapanyagba!

                              Az el\u0151z\u0151, 5. feladatot oldjuk meg a k\u00f6vetkez\u0151k\u00e9ppen: ne adjunk meg k\u00fcl\u00f6n sz\u0171r\u0151f\u00fcggv\u00e9nyt, hanem a sz\u0171r\u00e9si logik\u00e1t egy lambda kifejez\u00e9s form\u00e1j\u00e1ban adjuk meg a FindAll m\u0171veletnek.

                              Ehhez mind\u00f6ssze egy sort kell megv\u00e1ltoztatni:

                              list = list.FindAll((int n) => { return n % 2 == 1; });\n

                              Egy n\u00e9v n\u00e9lk\u00fcli f\u00fcggv\u00e9nyt defini\u00e1ltunk \u00e9s adtunk \u00e1t a FindAll m\u0171veletnek:

                              • ez egy lambda kifejez\u00e9s,
                              • a => bal oldal\u00e1n megadtuk a m\u0171velet param\u00e9tereket (itt csak egy volt),
                              • a => jobb oldal\u00e1n adtuk meg a m\u0171velet t\u00f6rzs\u00e9t (ugyanaz, mint a kor\u00e1bbi MyFilter t\u00f6rzse).

                              A fenti sort j\u00f3val egyszer\u0171bb \u00e9s \u00e1ttekinthet\u0151bb form\u00e1ba is \u00edrhatjuk:

                              list = list.FindAll(n => n % 2 == 1);\n

                              A k\u00f6vetkez\u0151 egyszer\u0171s\u00edt\u00e9seket eszk\u00f6z\u00f6lt\u00fck:

                              • a param\u00e9ter t\u00edpus\u00e1t nem \u00edrtuk ki: a ford\u00edt\u00f3 ki tudja k\u00f6vetkeztetni a FindAll delegate param\u00e9teram\u00e9ter\u00e9nek t\u00edpus\u00e1b\u00f3l, mely a kor\u00e1bban vizsg\u00e1lt Predicate.
                              • a param\u00e9ter k\u00f6r\u00fcli z\u00e1r\u00f3jelet elhagyhattuk (mert csak egy param\u00e9ter van)
                              • a => jobb oldal\u00e1n elhagyhattuk a {} z\u00e1r\u00f3jeleket \u00e9s a return-t (mert egyetlen kifejez\u00e9sb\u0151l \u00e1llt a f\u00fcggv\u00e9ny t\u00f6rzse, mellyel a f\u00fcggv\u00e9ny visszat\u00e9r).
                              "},{"location":"labor/2-nyelvi-eszkozok/#7-tovabbi-nyelvi-konstrukciok","title":"7. Tov\u00e1bbi nyelvi konstrukci\u00f3k","text":"

                              Az al\u00e1bbiakban kitekint\u00fcnk n\u00e9h\u00e1ny olyan C# nyelvi elemre, melyek a napi programoz\u00e1si feladatok sor\u00e1n egyre gyakrabban haszn\u00e1latosak. A gyakorlat sor\u00e1n j\u00f3 es\u00e9llyel m\u00e1r nem marad id\u0151 ezek \u00e1ttekint\u00e9s\u00e9re.

                              "},{"location":"labor/2-nyelvi-eszkozok/#kifejezestorzsu-tagok-expression-bodied-members","title":"Kifejez\u00e9st\u00f6rzs\u0171 tagok (Expression-bodied members)","text":"

                              Id\u0151nk\u00e9nt olyan r\u00f6vid f\u00fcggv\u00e9nyeket, illetve tulajdons\u00e1gok eset\u00e9n kifejezetten gyakran olyan r\u00f6vid get/set/init defin\u00edci\u00f3kat \u00edrunk, melyek egyetlen kifejez\u00e9sb\u0151l \u00e1llnak. Ez esetben a f\u00fcggv\u00e9ny, illetve tulajdons\u00e1g eset\u00e9n a get/set/init t\u00f6rzse megadhat\u00f3 \u00fan. kifejez\u00e9st\u00f6rzs\u0171 tagok (expression-bodied members) szintaktik\u00e1val is, a => alkalmaz\u00e1s\u00e1val. Ez akkor is megtehet\u0151, ha az adott kontextusban van visszat\u00e9r\u00e9si \u00e9rt\u00e9k (return utas\u00edt\u00e1s), ak\u00e1r nincs.

                              A p\u00e9ld\u00e1kban l\u00e1tni fogjuk, hogy a kifejez\u00e9stest\u0171 tagok alkalmaz\u00e1sa nem t\u00f6bb, mint egy kisebb szintaktikai \"csavar\" annak \u00e9rdek\u00e9ben, hogy ilyen egyszer\u0171 esetekben min\u00e9l kevesebb k\u00f6r\u00edt\u0151 k\u00f3dot kelljen \u00edrni.

                              N\u00e9zz\u00fcnk el\u0151sz\u00f6r egy f\u00fcggv\u00e9ny p\u00e9ld\u00e1t (feltessz\u00fck, hogy az oszt\u00e1lyban van egy Age tagv\u00e1ltoz\u00f3 vagy tulajdons\u00e1g):

                              public int GetAgeInDogYear() => Age * 7; \npublic void DisplayName() => Console.WriteLine(ToString());\n
                              Mint l\u00e1that\u00f3, elhagytuk a {} z\u00e1r\u00f3jeleket \u00e9s a return utas\u00edt\u00e1st, \u00edgy t\u00f6m\u00f6rebb a szintaktika.

                              Fontos

                              B\u00e1r itt is a => tokent haszn\u00e1ljuk, ennek semmi k\u00f6ze nincs a kor\u00e1bban t\u00e1rgyalt lambda kifejez\u00e9sekhez: egyszer\u0171en csak arr\u00f3l van sz\u00f3, hogy ugyanazt a => tokent (szimb\u00f3lump\u00e1rt) k\u00e9t teljesen elt\u00e9r\u0151 dologra haszn\u00e1lja a C# nyelv.

                              P\u00e9lda tulajdons\u00e1g getter megad\u00e1s\u00e1ra:

                              public int AgeInDogYear { get => Age * 7; }\n

                              S\u0151t, ha csak getterje van a tulajdons\u00e1gnak, a get kulcssz\u00f3t \u00e9s a kapcsos z\u00e1r\u00f3jeleket is lehagyhatjuk.

                              public int AgeInDogYear => Age * 7;\n

                              Ezt az k\u00fcl\u00f6nb\u00f6zteti meg a kor\u00e1bban l\u00e1tott f\u00fcggv\u00e9nyek hasonl\u00f3 szintaktik\u00e1j\u00e1t\u00f3l, hogy itt nem \u00edrtuk ki a kerek z\u00e1r\u00f3jeleket.

                              Note

                              A Microsoft hivatalos dokument\u00e1ci\u00f3j\u00e1nak magyar ford\u00edt\u00e1s\u00e1ban az \"expression-bodied members\" nem \"kifejez\u00e9st\u00f6rzs\u0171\", hanem \"kifejez\u00e9stest\u0171\" tagk\u00e9nt szerepel. K\u00f6sz\u00f6nj\u00fck sz\u00e9pen, de a f\u00fcggv\u00e9nyeknek sokkal ink\u00e1bb t\u00f6rzse, mint teste van a magyar terminol\u00f3gi\u00e1ban, \u00edgy ezt nem vessz\u00fck \u00e1t...

                              "},{"location":"labor/2-nyelvi-eszkozok/#objektuminicializalo-object-initializer","title":"Objektuminicializ\u00e1l\u00f3 (Object initializer)","text":"

                              A publikus tulajdons\u00e1gok/tagv\u00e1ltoz\u00f3k inicializ\u00e1l\u00e1sa \u00e9s a konstruktorh\u00edv\u00e1s kombin\u00e1lhat\u00f3 egy \u00fagynevezett objektuminicializ\u00e1l\u00f3 (object initializer) szintaxis seg\u00edts\u00e9g\u00e9vel. Ennek alkalmaz\u00e1sa sor\u00e1n a konstruktorh\u00edv\u00e1s ut\u00e1n kapcsos z\u00e1r\u00f3jelekkel blokkot nyitunk, ahol a publikus tulajdons\u00e1gok/tagv\u00e1ltoz\u00f3k \u00e9rt\u00e9ke adhat\u00f3 meg, az al\u00e1bbi szintaktik\u00e1val.

                              var p = new Person()\n{\n    Age = 17,\n    Name = \"Luke\",\n};\n

                              Az tulajdons\u00e1gok/tagok inicializ\u00e1l\u00e1sa a konstruktor lefut\u00e1sa ut\u00e1n t\u00f6rt\u00e9nik (amennyiben tartozik az oszt\u00e1lyhoz konstruktor). Ez a szintaktika az\u00e9rt is el\u0151ny\u00f6s, mert egy kifejez\u00e9snek sz\u00e1m\u00edt (azon h\u00e1rommal szemben, mintha l\u00e9trehozn\u00e1nk egy inicializ\u00e1latlan, Person objektumot, \u00e9s k\u00e9t tov\u00e1bbi l\u00e9p\u00e9sben adn\u00e1nk \u00e9rt\u00e9ket az Age \u00e9s Name tagoknak). \u00cdgy ak\u00e1r k\u00f6zvetlen\u00fcl f\u00fcggv\u00e9nyh\u00edv\u00e1s param\u00e9terek\u00e9nt \u00e1tadhat\u00f3 egy inicializ\u00e1lt objektum, an\u00e9lk\u00fcl, hogy k\u00fcl\u00f6n v\u00e1ltoz\u00f3t kellene deklar\u00e1lni.

                              void Foo(Person p)\n{\n    // do something with p\n}\n
                              Foo(new Person() { Age = 17, Name = \"Luke\" });\n

                              A szintaxis r\u00e1ad\u00e1sul copy-paste bar\u00e1t, mert ahogy a fenti p\u00e9ld\u00e1kban is l\u00e1tszik, hogy nem sz\u00e1m\u00edt, hogy az utols\u00f3 tulajdons\u00e1g megad\u00e1sa ut\u00e1n van-e vessz\u0151, vagy nincs.

                              "},{"location":"labor/2-nyelvi-eszkozok/#tulajdonsagok-init-only-setter","title":"Tulajdons\u00e1gok - Init only setter","text":"

                              Az el\u0151z\u0151 pontban l\u00e9v\u0151 objektuminicializ\u00e1l\u00f3 szintaxis nagyon k\u00e9nyelmes, viszont azt k\u00f6veteli meg a tulajdons\u00e1gt\u00f3l, hogy publikus legyen. Ha azt akarjuk, hogy egy tulajdons\u00e1g \u00e9rt\u00e9ke csak az objektum l\u00e9trehoz\u00e1sakor legyen megadhat\u00f3, ahhoz konstruktor param\u00e9tert kell bevezess\u00fcnk, \u00e9s egy csak olvashat\u00f3 (csak getterrel rendelkez\u0151) tulajdons\u00e1gnak kell azt \u00e9rt\u00e9k\u00fcl adjuk. Erre a probl\u00e9m\u00e1ra ad egyszer\u0171bb megold\u00e1st az \u00fan. Init only setter szintaxis, ahol olyan \"settert\" tudunk k\u00e9sz\u00edteni az init kulcssz\u00f3val, mely \u00e1ll\u00edt\u00e1sa csak a konstruktorban \u00e9s az el\u0151z\u0151 fejezetben ismertetett objektuminicializ\u00e1l\u00f3 szintaxis alkalmaz\u00e1sa sor\u00e1n enged\u00e9lyezett, ezt k\u00f6vet\u0151en m\u00e1r nem.

                              public string Name { get; init; }\n
                              var p = new Person()\n{\n    Age = 17,\n    Name = \"Luke\",\n};\n\np.Name = \"Test\"; // build hiba, ut\u00f3lag nem megv\u00e1ltoztathat\u00f3\n

                              Tov\u00e1bb\u00e1 lehet\u0151s\u00e9g\u00fcnk van az init only setter k\u00f6telez\u0151s\u00e9g\u00e9t is be\u00e1ll\u00edtani a tulajdons\u00e1gon alkalmazott required kulcssz\u00f3val. Ekkor a tulajdons\u00e1g \u00e9rt\u00e9k\u00e9t mindenk\u00e9ppen meg kell adni az objektuminicializ\u00e1l\u00f3 szintaxisban, k\u00fcl\u00f6nben ford\u00edt\u00e1si hib\u00e1t kapunk.

                              public required string Name { get; init; }\n

                              Ez az\u00e9rt is hasznos, mert ha egy\u00e9bk\u00e9nt is szeretn\u00e9nk tulajdons\u00e1gokat publik\u00e1lni az oszt\u00e1lyb\u00f3l, \u00e9s egy\u00e9bk\u00e9nt is szeretn\u00e9nk t\u00e1mogatni az objektum inicializ\u00e1l\u00f3 szintaxist, akkor \u00edgy meg tudjuk sp\u00f3rolni a k\u00f6telez\u0151 konstruktor param\u00e9tereket.

                              "},{"location":"labor/2-nyelvi-eszkozok/#8-feladat-generikus-osztalyok","title":"8. Feladat \u2013 Generikus oszt\u00e1lyok","text":"

                              Megjegyz\u00e9s: erre a feladatra j\u00f3 es\u00e9llyel nem marad id\u0151. Ez esetben c\u00e9lszer\u0171 a feladatot gyakorl\u00e1sk\u00e9ppen otthon elv\u00e9gezni.

                              A .NET generikus oszt\u00e1lyai hasonl\u00edtanak C++ nyelv template oszt\u00e1lyaihoz, de k\u00f6zelebb \u00e1llnak a Java-ban m\u00e1r megismert generikus oszt\u00e1lyokhoz. A seg\u00edts\u00e9g\u00fckkel \u00e1ltal\u00e1nos (t\u00f6bb t\u00edpusra is m\u0171k\u00f6d\u0151), de ugyanakkor t\u00edpusbiztos oszt\u00e1lyokat hozhatunk l\u00e9tre. Generikus oszt\u00e1lyok n\u00e9lk\u00fcl, ha \u00e1ltal\u00e1nosan szeretn\u00e9nk kezelni egy probl\u00e9m\u00e1t, akkor object t\u00edpus\u00fa adatokat haszn\u00e1lunk (mert .NET-ben minden oszt\u00e1ly az object oszt\u00e1lyb\u00f3l sz\u00e1rmazik). Ez a helyzet p\u00e9ld\u00e1ul az ArrayList-tel is, ami egy \u00e1ltal\u00e1nos c\u00e9l\u00fa gy\u0171jtem\u00e9ny, tetsz\u0151leges, object t\u00edpus\u00fa elemek t\u00e1rol\u00e1s\u00e1ra alkalmas. L\u00e1ssunk egy p\u00e9ld\u00e1t az ArrayList haszn\u00e1lat\u00e1ra:

                              var list = new ArrayList();\nlist.Add(1);\nlist.Add(2);\nlist.Add(3);\nfor (int n = 0; n < list.Count; n++)\n{\n    // Castolni kell, k\u00fcl\u00f6nben nem fordul\n    int i = (int)list[n];\n    Console.WriteLine($\"Value: {i}\");\n}\n

                              A fenti megold\u00e1ssal a k\u00f6vetkez\u0151 probl\u00e9m\u00e1k ad\u00f3dnak:

                              • Az ArrayList minden egyes elemet object-k\u00e9nt t\u00e1rol.
                              • Amikor hozz\u00e1 szeretn\u00e9nk f\u00e9rni a lista egy elem\u00e9hez, mindig a megfelel\u0151 t\u00edpus\u00fav\u00e1 kell cast-olni.
                              • Nem t\u00edpusbiztos. A fenti p\u00e9ld\u00e1ban semmi nem akad\u00e1lyoz meg abban (\u00e9s semmilyen hiba\u00fczenet sem jelzi), hogy az int t\u00edpus\u00fa adatok mell\u00e9 besz\u00farjunk a list\u00e1ba egy m\u00e1sik t\u00edpus\u00fa objektumot. Ilyenkor csak a lista bej\u00e1r\u00e1sa sor\u00e1n kapn\u00e1nk hib\u00e1t, amikor a nem int t\u00edpust int t\u00edpus\u00fara pr\u00f3b\u00e1lunk castolni. Generikus gy\u0171jtem\u00e9nyek haszn\u00e1latakor az ilyen hib\u00e1k m\u00e1r a ford\u00edt\u00e1s sor\u00e1n kider\u00fclnek.
                              • \u00c9rt\u00e9k t\u00edpus\u00fa adatok t\u00e1rol\u00e1sakor a lista lassabban m\u0171k\u00f6dik, mert az \u00e9rt\u00e9k t\u00edpust el\u0151sz\u00f6r be kell dobozolni (boxing), hogy az object-k\u00e9nt (azaz referencia t\u00edpusk\u00e9nt) t\u00e1rolhat\u00f3 legyen.

                              A fenti probl\u00e9ma megold\u00e1sa egy generikus lista haszn\u00e1lat\u00e1val a k\u00f6vetkez\u0151k\u00e9ppen n\u00e9z ki (a gyakorlat sor\u00e1n csak a kiemelt sort m\u00f3dos\u00edtsuk a kor\u00e1bban beg\u00e9pelt p\u00e9ld\u00e1ban):

                              var list = new List<int>();\nlist.Add(1);\nlist.Add(2);\nlist.Add(3);\nfor (int n = 0; n < list.Count; n++)\n{\n    int i = list[n]; // Nem kell cast-olni\n    Console.WriteLine($\"Value: {i}\");\n}\n
                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/","title":"2. Sprachliche Mittel","text":""},{"location":"labor/2-nyelvi-eszkozok/index_ger/#das-ziel-der-ubung","title":"Das Ziel der \u00dcbung","text":"

                              W\u00e4hrend der \u00dcbung lernen die Studenten die wichtigsten modernen Sprachelementen kennen, die auch in der .NET-Umgebung verf\u00fcgbar sind. Es wird vorausgesetzt, dass der/die Student/in den objektorientierten Ansatz in seinem/ihrem bisherigen Studium beherrscht und mit den grundlegenden Konzepten der Objektorientierung vertraut ist. In dieser \u00dcbung werden wir uns auf die Sprachelemente in .NET konzentrieren, die \u00fcber den allgemeinen objektorientierten Ansatz hinausgehen, aber wesentlich zur Erstellung von transparentem und wartbarem Code beitragen. Diese sind:

                              • Eigenschaft (property)
                              • Delegat (delegate, Methodenreferenz)
                              • Ereignis (event)
                              • Attribut (attribute)
                              • Lambda-Ausdruck (lambda expression)
                              • Generischer Typ (generic type)
                              • Einige zus\u00e4tzliche Sprachkonstruktionen

                              Zugeh\u00f6rige Vorlesungen: Vorlesung 2 und Anfang der Vorlesung 3 - Sprachliche Mittel.

                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#voraussetzungen","title":"Voraussetzungen","text":"

                              Die f\u00fcr die Durchf\u00fchrung der \u00dcbung ben\u00f6tigten Werkzeuge:

                              • Visual Studio 2022

                              \u00dcbung unter Linux oder macOS

                              Das \u00dcbungsmaterial ist grunds\u00e4tzlich f\u00fcr Windows und Visual Studio gedacht, kann aber auch auf anderen Betriebssystemen mit anderen Entwicklungswerkzeugen (z.B. VS Code, Rider, Visual Studio f\u00fcr Mac) oder sogar mit einem Texteditor und CLI (Kommandozeilen)-Tools durchgef\u00fchrt werden. Dies wird dadurch erm\u00f6glicht, dass die Beispiele im Kontext einer einfachen Konsolenanwendung pr\u00e4sentiert werden (keine Windows-spezifischen Elemente) und das .NET SDK auf Linux und macOS unterst\u00fctzt wird. Hello World unter Linuxon

                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

                              Ausblick

                              Dieser Leitfaden enth\u00e4lt an mehreren Stellen zus\u00e4tzliche Informationen und Erkl\u00e4rungen, die in derselben Farbe wie dieser Hinweis und mit demselben Symbol umrahmt sind. Dies sind n\u00fctzliche Erkenntnisse, die jedoch nicht Teil des Kernlehrmaterial sind.

                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#losung","title":"L\u00f6sung","text":"Laden Sie die fertige L\u00f6sung herunter

                              Es ist wichtig, dass Sie sich w\u00e4hrend des Praktikums an die Anleitung halten. Es ist verboten (und sinnlos), die fertige L\u00f6sung herunterzuladen. Allerdings kann es bei der anschlie\u00dfenden Selbstein\u00fcbung n\u00fctzlich sein, die fertige L\u00f6sung zu \u00fcberpr\u00fcfen, daher stellen wir sie zur Verf\u00fcgung.

                              Die L\u00f6sung ist auf GitHub [hier] verf\u00fcgbar (https://github.com/bmeviauab00/lab-nyelvieszkozok-megoldas). Der einfachste Weg, es herunterzuladen, ist, es von der Kommandozeile aus mit dem Befehl git clone auf Ihren Computer zu klonen:

                              git clone https://github.com/bmeviauab00/lab-nyelvieszkozok-megoldas

                              Sie m\u00fcssen Git auf Ihrem Computer installiert haben, weitere Informationen hier.

                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#0-aufgabe-schlusselwort-var-implizit-typisierte-lokale-variablen-implicitly-typed-local-variables","title":"0. Aufgabe - Schl\u00fcsselwort var - Implizit typisierte lokale Variablen (implicitly typed local variables)","text":"

                              Wir beginnen mit einer einfachen Aufw\u00e4rm\u00fcbung. Im folgenden Beispiel erstellen wir eine Klasse namens Person, die eine Person darstellt.

                              1. Erstellen wir eine neue C#-Konsolenanwendung. .NET-Basis (d.h. nicht.NET Framework):
                                • Ein Beispiel daf\u00fcr haben wir in der ersten \u00dcbung gesehen, die im Leitfaden beschrieben wird.
                                • Das Kontrollk\u00e4stchen \"Do not use top level statements\" ist bei der Projekterstellung aktiviert.
                              2. F\u00fcgen wir eine neue Klasse mit dem Namen Person zu unserer Anwendung hinzu. (Um eine neue Klasse im Solution Explorer hinzuzuf\u00fcgen, klicken wir mit der rechten Maustaste auf die Projektdatei und w\u00e4hlen wir Add / Class. \u00c4ndern wir den Namen der zu erstellenden Datei im erscheinenden Fenster auf Person.csund klicken wir auf Add.)
                              3. Lassen wir uns die Klasse \u00f6ffentlich machen. Dazu m\u00fcssen wir das Schl\u00fcsselwort public vor dem Klassennamen eingeben. Diese \u00c4nderung w\u00e4re hier eigentlich nicht n\u00f6tig, aber eine sp\u00e4tere Aufgabe wird eine \u00f6ffentliche Klasse erfordern.

                                public class Person\n{\n}\n
                              4. Erg\u00e4nzen wir die Funktion Main in der Datei Program.cs, um unsere neue Klasse zu testen.

                                static void Main(string[] args)\n{\n    Person p = new Person();\n}\n
                              5. Anstatt den Typ der lokalen Variablen explizit anzugeben, k\u00f6nnen wir das Schl\u00fcsselwort var verwenden:

                                static void Main(string[] args)\n{\n    var p = new Person();\n}\n

                                Dies wird als implicitly typed local variables bezeichnet, auf Deutsch implizit typisierte lokale Variablen genannt. In diesem Fall versucht der Compiler, den Typ der Variablen aus dem Kontext, aus der rechten Seite des Gleichheitszeichens zu erkennen. In diesem Fall ist es Person. Es ist wichtig anzumerken, dass die Sprache dadurch statisch typisiert bleibt (es funktioniert also nicht wie das JavaScript-Schl\u00fcsselwort var ), da der Typ der p -Variable sp\u00e4ter nicht mehr ge\u00e4ndert werden kann. Es ist nur ein einfaches syntaktisches Bonbon, um die Definition lokaler Variablen kompakter zu machen (keine Notwendigkeit, den Typ \"zweimal\" anzugeben, auf der linken und auf der rechten Seite von = ).

                                Target-typed new expressions

                                Ein weiterer Ansatz k\u00f6nnte die Target-typed new expressions in C# 9 sein, wo der Typ f\u00fcr den neuen Operator weggelassen werden kann, wenn er vom Compiler aus dem Kontext erkannt werden kann (z.B.: linke Seite eines Wertes, Typ eines Parameters, etc.). Unser obiger Person -Konstruktor w\u00fcrde wie folgt aussehen:

                                Person p = new();\n

                                Der Vorteil dieses Ansatzes gegen\u00fcber var ist, dass er auch f\u00fcr Membervariablen verwendet werden kann.

                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#1-aufgabe-eigenschaft-property","title":"1. Aufgabe - Eigenschaft (property)","text":"

                              Eigenschaften erlauben uns typischerweise (aber nicht ausschlie\u00dflich, wie wir noch sehen werden) den Zugriff auf Membervariablen von Klassen auf eine syntaktisch \u00e4hnliche Weise wie den Zugriff auf eine traditionelle Membervariable. Beim Zugriff haben wir jedoch die M\u00f6glichkeit, anstelle einer einfachen Wertabfrage oder Einstellung eine methoden\u00e4hnliche Art des Zugriffs auf die Variable zu implementieren, und wir k\u00f6nnen sogar die Sichtbarkeit der Abfrage und der Einstellung separat definieren.

                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#syntax-von-eigenschaften","title":"Syntax von Eigenschaften","text":"

                              Im folgenden Beispiel erstellen wir eine Klasse namens Person, die eine Person darstellt. Sie hat zwei Mitgliedsvariablen, name und age. Auf Mitgliedsvariablen kann nicht direkt zugegriffen werden (da sie privat sind), sie k\u00f6nnen nur \u00fcber die \u00f6ffentlichen Eigenschaften Name und Age verwaltet werden. Das Beispiel veranschaulicht, dass die .NET-Eigenschaften eindeutig den aus C++ und Java bekannten Methoden SetX(\u2026) und GetX() entsprechen, aber die sind auf einheitlichere Weise, auf Sprachebene unterst\u00fctzt.

                              1. Erstellen wir in der Klasse Person, die in der vorherigen Aufgabe erstellt war, eine Membervariable des Typs int mit dem Namen age und eine Eigenschaft Age, die sie verf\u00fcgbar macht.

                                public class Person\n{\n    private int age;\n    public int Age\n    {\n        get { return age; }\n        set { age = value; }\n    }\n}\n

                                Visual Studio Snippets

                                Obwohl wir die gesamte Eigenschaft im Labor zu \u00dcbungszwecken manuell eingegeben haben, stellt Visual Studio Code Snippets zur Verf\u00fcgung, um h\u00e4ufig vorkommende Codeteile zu erstellen, mit denen wir allgemeine Sprachkonstrukte als Vorlagen verwenden k\u00f6nnen. Der obige Eigenschaftscodeschnipsel kann mit dem Schnipsel propfull abgerufen werden. Geben Sie den Namen des Schnipsels ein (propfull) und dr\u00fccken Sie dann die Tab -Taste, bis der Schnipsel aktiviert ist (normalerweise 2x).

                                Weitere erw\u00e4hnenswerte Schnipseln sind unter anderem:

                                • ctor: Konstruktor
                                • for: f\u00fcr Zyklus
                                • foreach: foreach-Schleife
                                • prop: automatische Eigenschaft (siehe sp\u00e4ter)
                                • switch: Schaltbefehl
                                • cw: Console.WriteLine

                                Wir k\u00f6nnen solche Schnipseln herstellen.

                              2. Ergn\u00e4nzen wir die Funktion Main in der Datei Program.cs, um unsere neue Eigenschaft zu testen.

                                static void Main(string[] args)\n{\n    var p = new Person();\n    p.Age = 17;\n    p.Age++;\n    Console.WriteLine(p.Age);\n}\n
                              3. F\u00fchren wir unseren Programm aus (F5)

                                Wir sehen, dass die Eigenschaft auf \u00e4hnliche Weise wie die Mitgliedsvariablen verwendet werden kann. Wenn die Eigenschaft abgefragt wird, wird der in der Eigenschaft definierte Teil get ausgef\u00fchrt und der Wert der Eigenschaft ist der durch return zur\u00fcckgegebene Wert. Wenn die Eigenschaft gesetzt ist, wird der in der Eigenschaft definierte Abschnitt set ausgef\u00fchrt, und der Wert der speziellen Variablen value in diesem Abschnitt entspricht dem als Eigenschaftswert angegebenen Ausdruck.

                                Beachten wir in der obigen L\u00f6sung, wie elegant wir ein Jahr zum Alter einer Person hinzuf\u00fcgen k\u00f6nnen. In Java- oder C++-Code h\u00e4tte ein \u00e4hnlicher Vorgang in der Form p.setAge(p.getAge() + 1) geschrieben werden k\u00f6nnen, was eine wesentlich umst\u00e4ndlichere und schwieriger zu lesende Syntax ist als die Obige. Der Hauptvorteil der Verwendung von Eigenschaften besteht darin, dass unser Code syntaktisch sauberer ist und Wertzuweisungen/-abfragen in den meisten F\u00e4llen gut von tats\u00e4chlichen Funktionsaufrufen getrennt sind.

                              4. \u00dcberpr\u00fcfen wir, dass unser Programm wirklich get und set aufruft. Dazu setzen wir Haltepunkte (breakpoints) innerhalb der Getter- und Setter-Bl\u00f6cke, dazu klicken wir auf den grauen Balken am linken Rand des Code-Editors.

                              5. F\u00fchren wir das Programm Schritt f\u00fcr Schritt aus. Starten wir dazu das Programm mit F11 statt F5, und dr\u00fccken wir dann erneut F11, um es Zeile f\u00fcr Zeile ablaufen zu lassen.

                                Wir sehen, dass unser Programm tats\u00e4chlich jedes Mal den Getter aufruft, wenn ein Wert abgefragt wird, und den Setter, wenn ein Wert gesetzt wird.

                              6. Ein wichtiges Merkmal von Setter-Funktionen ist, dass sie die M\u00f6glichkeit der Wert\u00fcberpr\u00fcfung bieten. F\u00fcgen wir in diesem Sinne dem Setter der Eigenschaft Age etwas hinzu.

                                public int Age\n{\n    get { return age; }\n    set \n    {\n        if (value < 0)\n            throw new ArgumentException(\"Ung\u00fcltiges Alter!\");\n        age = value; \n    }\n}\n

                                Beachten wir, dass bei einfachen Gettern und Settern die Abfrage bzw. das Setzen von Werten in einer Zeile erfolgt, w\u00e4hrend sie bei komplexeren Stammdaten auf mehrere Zeilen aufgeteilt wird.

                              7. Um die Anwendung zu testen, ordnen wir dem Alter einen negativen Wert in der Funktion Main der Klasse Program zu.

                                p.Age = -2;\n
                              8. F\u00fchren wir das Programm aus, um es zu testen, ob die Pr\u00fcfung korrekt funktioniert, und korrigieren wir dann den Fehler, \u00e4ndern wir das eingestellte Alter auf positiv.

                                p.Age = 2;\n
                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#auto-implementierte-eigenschaft-auto-implemented-property","title":"Auto-implementierte Eigenschaft (auto-implemented property)","text":"

                              In unserer t\u00e4glichen Arbeit begegnen wir auch einer viel kompakteren Syntax von Eigenschaften. Diese Syntax kann verwendet werden, wenn wir eine Eigenschaft erstellen m\u00f6chten, in der:

                              • wir wollen keine zus\u00e4tzliche Logik zu den Getter- und Setter-Methoden hinzuf\u00fcgen,
                              • m\u00fcssen wir nicht direkt auf die private Mitgliedsvariable zugreifen.

                              Nachfolgend ein Beispiel daf\u00fcr.

                              1. F\u00fcgen wir eine solche automatisch implementierte Eigenschaft (auto-implemented property) zu unserer Klasse Person hinzu. Erstellen wir eine Eigenschaft vom Typ string mit dem Namen Name.

                                public string Name { get; set; }\n

                                Der syntaktische Unterschied zu den vorherigen ist, dass weder der get- noch der set-Zweig implementiert wurden (keine Klammern). Im Falle einer automatisch implementierten Eigenschaft erzeugt der Compiler eine versteckte Variable in der Klasse, auf die vom Code aus nicht zugegriffen werden kann und die zum Speichern des aktuellen Werts der Eigenschaft verwendet wird. Es sollte betont werden, dass dies nicht die zuvor eingef\u00fchrte name Mitgliedsvariable (die gel\u00f6scht werden k\u00f6nnte) anh\u00e4lt und abfragt, sondern auf eine versteckte, neue Variable wirkt!

                              2. \u00dcberpr\u00fcfen wir nun ihre Funktionalit\u00e4t, und erg\u00e4nzen wir die Funktion Main.

                                static void Main(string[] args)\n{\n    // ...\n    p.Name = \"Lukas\";\n    // ...\n    Console.WriteLine(p.Name);\n}\n
                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#standardwert-default-value","title":"Standardwert (default value)","text":"

                              F\u00fcr automatisch implementierte Eigenschaften k\u00f6nnen wir bei der Deklaration auch deren Anfangswert angeben.

                              1. Geben wir der Eigenschaft Name einen Anfangswert.

                                public string Name { get; set; } = \"anonymous\";\n
                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#sichtbarkeit-von-eigenschaften","title":"Sichtbarkeit von Eigenschaften","text":"

                              Ein gro\u00dfer Vorteil der Eigenschaften, neben der v\u00f6llig freien Implementierung, ist, dass die Sichtbarkeit des Getters und des Setters getrennt eingestellt werden kann.

                              1. Setzen wir die Sichtbarkeit des Setters der Eigenschaft Name auf privat.

                                public string Name { get; private set; }\n

                                In diesem Fall wird ein \u00dcbersetzungsfehler in der Klasse Program f\u00fcr die Richtlinie p.Name = \"Luke\"; zur\u00fcckgegeben. Die Grundregel ist, dass Getter und Setter die Sichtbarkeit der Eigenschaft erben, die weiter eingeschr\u00e4nkt, aber nicht gelockert werden kann. Die Sichtbarkeitskontrolle kann sowohl f\u00fcr autoimplementierte als auch f\u00fcr nicht autoimplementierte Eigenschaften verwendet werden.

                              2. Stellen wir die Sichtbarkeit wieder her (entfernen wir das Schl\u00fcsselwort private aus dem Property Setter Name ), um den \u00dcbersetzungsfehler zu vermeiden.

                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#nur-lese-eigenschaft-readonly-property","title":"Nur-Lese-Eigenschaft (readonly property)","text":"

                              Der Setter kann weggelassen werden, um eine schreibgesch\u00fctzte Eigenschaft zu erhalten. F\u00fcr eine automatisch implementierte Eigenschaft kann auch ein Anfangswert angegeben werden: Dies ist nur in einem Konstruktor oder durch Angabe eines Standardwerts (siehe oben) m\u00f6glich, im Gegensatz zu Eigenschaften mit einem privaten Setter, deren Setter von jeder Mitgliedsfunktion der Klasse aufgerufen werden kann.

                              Die Definition einer schreibgesch\u00fctzten Eigenschaft wird in den folgenden Codeschnipseln veranschaulicht (implementieren wir sie NICHT in unserem Code):

                              a) Autoimplementierter Fall

                              public string Name { get; }\n

                              b) Nicht automatisch implementierter Fall

                              private string name;\n...\npublic string Name { get {return name; } }\n
                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#berechneter-wert-calculated-value","title":"Berechneter Wert (calculated value)","text":"

                              Eigenschaften mit nur Getter haben eine andere Verwendung. Sie kann auch verwendet werden, um einen berechneten Wert zu ermitteln, der immer einen Wert auf der Grundlage einer bestimmten Logik berechnet, aber im Gegensatz zur \"Nur-Lese-Eigenschaft\" verf\u00fcgt sie nicht \u00fcber ein Datenelement direkt hinter ihr. Dies wird im folgenden Codeschnipsel veranschaulicht (\u00fcbernehmen wir ihn NICHT in unserem Code):

                              public int AgeInDogYear { get { return Age * 7; } }\n
                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#2-aufgabe-delegat-delegate-methodenreferenz","title":"2. Aufgabe - Delegat (delegate, Methodenreferenz)","text":"

                              Stellen wir sicher, dass der Code kompilierbar ist!

                              Die folgenden \u00dcbungen bauen auf den Ergebnissen der vorherigen \u00dcbungen auf. Wenn Ihr Programm nicht abst\u00fcrzt oder nicht richtig funktioniert, melden Sie dies Ihrem/er \u00dcbungsleiter/in am Ende der Aufgaben, damit er/sie Ihnen bei der Behebung des Problems helfen kann.

                              Delegate sind Methodenreferenzen in .NET, das moderne \u00c4quivalent zu C/C++-Funktionszeigern. Ein Delegat ist eine M\u00f6glichkeit, einen Variablentyp zu definieren, der verwendet werden kann, um auf Methoden zu verweisen. Nicht irgendein Zeiger, sondern - \u00e4hnlich wie bei C++-Funktionszeigern - solche, deren Typ (Parameterliste und R\u00fcckgabewert) dem Typ des Delegaten entspricht. Durch das \"Aufrufen\" der Delegatvariable wird die als Wert angegebene (registrierte) Methode automatisch aufgerufen. Ein Vorteil der Verwendung von Delegaten ist, dass wir zur Laufzeit entscheiden k\u00f6nnen, welche von mehreren Methoden wir aufrufen m\u00f6chten.

                              Einige Beispiele f\u00fcr den Einsatz von Delegaten:

                              • die Funktion, die die Elemente vergleicht, als Parameter an eine universelle Ordnungsfunktion \u00fcbergeben,
                              • ist die Implementierung einer universellen Filterlogik f\u00fcr eine allgemeine Sammlung, bei der eine Funktion als Delegat in einem Parameter \u00fcbergeben wird, um zu entscheiden, ob ein Element in die gefilterte Liste aufgenommen werden soll,
                              • Implementierung des Publish-Subscribe-Musters, bei dem bestimmte Objekte andere Objekte \u00fcber sich selbst betreffender Ereignisse informieren.

                              Im folgenden Beispiel werden wir Objekten der zuvor erstellten Klasse Person erlauben, Objekte anderer Klassen frei zu benachrichtigen, wenn sich das Alter einer Person ge\u00e4ndert hat. Zu diesem Zweck f\u00fchren wir einen Delegatentyp (AgeChangingDelegate) ein, der den aktuellen und neuen Wert des Alters der Person in seiner Parameterliste \u00fcbergeben kann. Als N\u00e4chstes erstellen wir eine \u00f6ffentliche Mitgliedsvariable des Typs AgeChangingDelegate in der Klasse Person, die es einer externen Partei erm\u00f6glicht, die Funktion anzugeben, \u00fcber die sie die Benachrichtigung \u00fcber \u00c4nderungen an der Instanz Person anfordern wird.

                              1. Erstellen wir einen neuen Delegatentyp, der auf solche Funktionen verweisen kann, die void zur\u00fcckgeben und zwei int Parameter annehmen. \u00dcberpr\u00fcfen wir, dass der neue Typ vor der Klasse Person definiert ist, direkt im G\u00fcltigkeitsbereich des Namespaces!

                                namespace PropertyDemo\n{\n    public delegate void AgeChangingDelegate(int oldAge, int newAge);\n\n    public class Person\n    {\n        // ...\n

                                AgeChangingDelegate ist ein Typ (man beachte auch die VS-F\u00e4rbung), der \u00fcberall dort verwendet werden kann, wo ein Typ gesetzt werden kann (z.B. kann man eine Membervariable, eine lokale Variable, einen Funktionsparameter, etc. auf dieser Basis erstellen).

                              2. Erm\u00f6glichen wir Objekten in Person, auf jede Funktion zu zeigen, die der obigen Signatur entspricht. Erstellen wir dazu eine Membervariable vom Typ AgeChangingDelegate in der Klasse Person!

                                public class Person\n{\n    public AgeChangingDelegate AgeChanging;\n

                                Wie objektorientiert ist das?

                                Die Methodenreferenz, die als \u00f6ffentliche Membervariable erstellt wurde, verst\u00f6\u00dft (vorerst) gegen die Grunds\u00e4tze der objektorientierten Einheitsbegrenzung/Informationsverschleierung. Wir werden sp\u00e4ter darauf zur\u00fcckkommen.

                              3. Rufen wir die Funktion jedes Mal auf, wenn sich das Alter unseres Person \u00e4ndert. Dazu f\u00fcgen wir dem Setter der Eigenschaft Age Folgendes hinzu.

                                public int Age\n{\n    get { return age; }\n    set \n    {\n        if (value < 0)\n            throw new ArgumentException(\"Ung\u00fcltiges Alter!\");\n        if (AgeChanging != null)\n            AgeChanging(age, value);\n        age = value; \n    }\n}\n

                                Die obige Codezeile veranschaulichen mehrere wichtige Regeln:

                                • Die Validierungslogik geht in der Regel der Meldungslogik voraus.
                                • Es h\u00e4ngt von der Art der Meldelogik ab, ob sie vor oder nach der Auswertung ausgef\u00fchrt wird (in diesem Fall, da sich das Wort \"changing\" auf etwas in Arbeit befindliches bezieht, geht die Meldung der Auswertung voraus, das Vorkommen wird durch die Vergangenheitsform angezeigt: \"changed\")
                                • Beachten wir, dass noch niemand der Mitgliedsvariablen vom Typ Delegat einen Wert zugewiesen hat (kein Abonnent/Teilnehmer). In solchen F\u00e4llen w\u00fcrde der Aufruf zu einer Ausnahme f\u00fchren. \u00dcberpr\u00fcfen wir daher immer, ob die Mitgliedsvariable null ist, bevor wir sie aufrufen.
                                • Wenn das Ereignis ausgel\u00f6st wird, k\u00f6nnen wir auch die \u00dcberpr\u00fcfung von null und die Ausl\u00f6sung des Ereignisses auf elegantere, kompaktere und thread-sichere Weise mit dem \"?.\" Null-Bedingungs-Operator durchf\u00fchren (C# 6 und h\u00f6her):

                                statt

                                if (AgeChanging != null)\n    AgeChanging(age, value);\n

                                k\u00f6nnen wir

                                AgeChanging?.Invoke(age, value);\n

                                schreiben.

                                Das Ereignis wird nur ausgel\u00f6st, wenn es nicht null ist, ansonsten geschieht nichts.

                              4. Genauer gesehen, sollte das Ereignis nur ausgel\u00f6st werden, wenn sich das Alter tats\u00e4chlich \u00e4ndert, d. h. die Verzweigung der Eigenschaft set sollte pr\u00fcfen, ob der neue Wert mit dem alten \u00fcbereinstimmt. Eine L\u00f6sung k\u00f6nnte darin bestehen, in der ersten Zeile des Setters sofort zur\u00fcckzukehren, wenn der neue Wert mit dem alten \u00fcbereinstimmt:

                                if (age == value) \n    return;\n\u2026\n
                              5. Wir sind fertig mit dem Code f\u00fcr die Klasse Person. Kommen wir zum Abonnenten! Als erstes m\u00fcssen wir der Klasse Program eine neue Funktion hinzuf\u00fcgen.

                                class Program\n{\n    // ...\n\n    private static void PersonAgeChanging(int oldAge, int newAge)\n    {\n        Console.WriteLine(oldAge + \" => \" + newAge);\n    }\n}\n

                                Tipp

                                \u00dcberpr\u00fcfen Sie, dass die neue Funktion im richtigen Bereich platziert ist! W\u00e4hrend der Delegatentyp au\u00dferhalb der Klasse (aber innerhalb des Namespace) platziert ist, befindet sich die Funktion innerhalb der Klasse!

                              6. Melden wir uns schlie\u00dflich f\u00fcr die \u00c4nderungsverfolgung in der Funktion Main an!

                                static void Main(string[] args)\n{\n  Person p = new Person();\n  p.AgeChanging = new AgeChangingDelegate(PersonAgeChanging);\n  // ...\n
                              7. Starten wir das Programm!

                                Wenn wir z. B. einen Haltepunkt in der Zeile AgeChanging?.Invoke(age, value); setzen, die Anwendung debuggen und den Code schrittweise ausf\u00fchrem, k\u00f6nnen wir feststellen, dass das Ereignis bei jedem Setter-Durchlauf ausgef\u00fchrt wird, sowohl bei der ersten Wertzuweisung als auch beim Inkrement.

                              8. F\u00fcgen wir der Funktion Main mehrere Abonnenten hinzu (mit dem Operator += k\u00f6nnen wir neue Abonnenten zu den bereits vorhandenen hinzuf\u00fcgen) und f\u00fchren wir das Programm dann aus.

                                p.AgeChanging = new AgeChangingDelegate(PersonAgeChanging);\np.AgeChanging += new AgeChangingDelegate(PersonAgeChanging);\np.AgeChanging += PersonAgeChanging; // Kompaktere Syntax\n

                                Es ist zu erkennen, dass alle drei registrierten/\"abonnierten\" Funktionen bei jeder Wert\u00e4nderung ausgef\u00fchrt werden. Dies ist m\u00f6glich, weil die Mitgliedsvariablen des Delegatentyps nicht nur eine Funktionsreferenz, sondern eine Funktionsreferenzliste enthalten (und pflegen).

                                Beachten wir in der dritten Zeile oben, dass wir Funktionsreferenzen mit einer kompakteren Syntax schreiben k\u00f6nnen, als wir sie beim ersten Mal gesehen haben: Geben wir einfach den Namen der Funktion nach dem += Operator an, ohne das new AgeChangingDelegate(...). Unabh\u00e4ngig davon wird ein AgeChangingDelegate -Objekt die PersonAgeChanging -Funktionen hinter den Kulissen umh\u00fcllen. In der Praxis verwenden wir diese kompaktere Syntax.

                              9. Versuchen wir auch, uns abzumelden (an einem Punkt unserer Wahl) und starten wir dann das Programm.

                                p.AgeChanging -= PersonAgeChanging;\n
                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#3-aufgabe-ereignis-event","title":"3. Aufgabe - Ereignis (event)","text":"

                              So wie Eigenschaften eine syntaktisch schlankere Alternative zu Getter- und Setter-Methoden sind, bietet der oben beschriebene Delegat-Mechanismus eine schlankere Alternative zu den aus Java bekannten Event Listenern. Allerdings verst\u00f6\u00dft unsere obige L\u00f6sung immer noch erheblich gegen einige OO-Prinzipien (Einheiteneinschr\u00e4nkung, Verbergen von Informationen). Wir k\u00f6nnen dies anhand der folgenden zwei Beispiele veranschaulichen.

                              1. Das Ereignis kann auch von au\u00dfen ausgel\u00f6st werden (durch die Operationen anderer Klassen). Das ist ungl\u00fccklich, denn so kann das Ereignis f\u00e4lschlicherweise ausgel\u00f6st werden, auch wenn es in Wirklichkeit nicht eingetreten ist, und alle Teilnehmer werden get\u00e4uscht. Um dies zu demonstrieren, f\u00fcgen wir die folgende Zeile am Ende der Funktion Main ein.

                                p.AgeChanging(67, 12);\n

                                Hier haben wir ein gef\u00e4lschtes Alters\u00e4nderungsereignis f\u00fcr das Objekt p Person ausgel\u00f6st und damit alle Abonnenten get\u00e4uscht. Eine gute L\u00f6sung w\u00e4re, wenn das Ereignis nur durch Aktionen der Klasse Person ausgel\u00f6st werden k\u00f6nnte.

                              2. Ein weiteres Problem ist das folgende. W\u00e4hrend += und -= andere Funktionen, die die Liste abonniert haben, respektieren, k\u00f6nnen wir die Abonnements anderer jederzeit mit dem Operator = \u00fcberschreiben (l\u00f6schen). Versuchen wir dies, indem wir die folgende Zeile einf\u00fcgen (direkt nach den An- und Abmeldungen).

                                p.AgeChanging = null;\n
                              3. F\u00fcgen wir das Schl\u00fcsselwort event zur AgeChanging Member-Variable Person.cshinzu!

                                Person.cs
                                public event AgeChangingDelegate AgeChanging;\n

                                Das Schl\u00fcsselwort event ist eigentlich dazu gedacht, unser Programm zur\u00fcck auf den objektorientierten Weg zu zwingen und die beiden oben genannten Probleme auszuschlie\u00dfen.

                              4. Lassen wir uns versuchen, das Programm zu \u00fcbersetzen. wir werden sehen, dass der \u00dcbersetzer unsere fr\u00fcheren \u00dcbertretungen jetzt als \u00dcbersetzungsfehler behandelt.

                              5. Entfernen wir die drei fehlerhaften Codezeilen (beachten wir, dass die erste direkte Wertzuweisung bereits ein Fehler ist), kompilieren wir dann und f\u00fchren wir unsere Anwendung aus!

                              "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#4-aufgabe-attribute","title":"4. Aufgabe - Attribute","text":""},{"location":"labor/2-nyelvi-eszkozok/index_ger/#anpassen-der-serialisierung-nach-attribut","title":"Anpassen der Serialisierung nach Attribut","text":"

                              Attribute sind ein deklarativer Weg, um Metadaten f\u00fcr Ihren Quellcode bereitzustellen. Ein Attribut ist eigentlich eine Klasse, die an ein bestimmtes Element des Programms (Typ, Klasse, Schnittstelle, Methode usw.) angeh\u00e4ngt ist. Diese Metainformationen k\u00f6nnen von jedem (auch von uns selbst) gelesen werden, w\u00e4hrend das Programm l\u00e4uft, und zwar \u00fcber einen Mechanismus, der Reflection genannt wird. Die Attribute k\u00f6nnen auch als das .NET-\u00c4quivalent zu den Java-Annotationen betrachtet werden.

                              property vs. attribute vs. static

                              Es stellt sich die Frage, welche Klasseneigenschaften in properties und welche in attributes einer Klasse untergebracht werden sollten. Eigenschaften beziehen sich auf die Objektinstanz selbst, w\u00e4hrend sich ein Attribut auf die Klasse (oder ein Mitglied der Klasse) bezieht, die das Objekt beschreibt.

                              In dieser Hinsicht sind Attribute n\u00e4her an statischen Eigenschaften, aber es lohnt sich immer noch eine \u00dcberlegung, ob man ein bestimmtes Datenelement als statisches Mitglied oder als Attribut definiert. Mit einem Attribut ist die Beschreibung deklarativer, und wir verschmutzen den Code nicht mit Details, die nicht in der \u00f6ffentlichen Schnittstelle der Klasse erscheinen sollten.

                              .NET definiert viele eingebaute Attribute, die eine gro\u00dfe Vielfalt an Funktionen haben k\u00f6nnen. Die im folgenden Beispiel verwendeten Attribute kommunizieren beispielsweise verschiedene Metainformationen mit dem XML-Serialisierer.

                              1. F\u00fcgen wir den folgenden Zeilen am Ende der Funktion Main ein und f\u00fchren wir dann unser Programm aus!

                                var serializer = new XmlSerializer(typeof(Person));\nvar stream = new FileStream(\"person.txt\", FileMode.Create);\nserializer.Serialize(stream, p);\nstream.Close();\nProcess.Start(new ProcessStartInfo\n{\n    FileName = \"person.txt\",\n    UseShellExecute = true,\n});\n

                                Der letzte Funktionsaufruf Process.Start im obigen Beispiel ist nicht Teil der Serialisierungslogik, sondern lediglich sondern nur eine kluge Methode, um die resultierende Datendatei mit dem Windows-Standardtextdateibetrachter zu \u00f6ffnen. Wir k\u00f6nnen dies versuchen, aber es h\u00e4ngt davon ab, welche .NET-Laufzeitumgebung wir verwenden und ob diese von unserem Betriebssystem unterst\u00fctzt wird. Ist dies nicht der Fall, erhalten wir bei der Ausf\u00fchrung eine Fehlermeldung. In diesem Fall lassen wir es unkommentiert und \u00f6ffnen wir die Datei person.txt manuell im Dateisystem (sie befindet sich in unserem Visual Studio Ordner unter \\bin\\Debug\\ neben unserer .exe Anwendung).

                              2. Schauen wir uns die Struktur der resultierenden Datei an. Beachten wir, dass jede Eigenschaft auf das XML-Element abgebildet wird, das ihrem Namen entspricht.

                              3. .NET-Attribute erm\u00f6glichen es uns, unsere Klasse Person mit Metadaten zu versehen, die das Verhalten der Serialisierung direkt ver\u00e4ndern. Das Attribut XmlRoot bietet die M\u00f6glichkeit, das Wurzelelement umzubenennen. Platzieren wir es \u00fcber der Klasse Person!

                                [XmlRoot(\"deutsche Person\")]\npublic class Person \n{\n    // ...\n}\n
                              4. Das XmlAttribute -Attribut zeigt dem Serialisier an, dass die markierte Eigenschaft auf ein xml-Attribut und nicht auf ein xml-Element abgebildet werden soll. Machen wir daraus die Eigenschaft Age (und nicht die Member-Variable!)!

                                [XmlAttribute(\"Alter\")]\npublic int Age\n
                              5. Das Attribut XmlIgnore zeigt dem Serialiser an, dass die markierte Eigenschaft vollst\u00e4ndig aus dem Ergebnis ausgelassen werden soll. Versuchen wir es \u00fcber die Eigenschaft Name.

                                [XmlIgnore]\npublic string Name { get; set; }\n
                              6. F\u00fchren wir unsere App aus! Vergleichen wir die Ergebnisse mit den vorherigen Ergebnissen.

                              7. "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#5-aufgabe-delegaten-2","title":"5. Aufgabe - Delegaten 2.","text":"

                                In den Aufgaben 2 und 3 haben wir ereignisbasierte Nachrichten\u00fcbermittlung mit Delegaten implementiert. Als einer anderen typischen Verwendung von Delegaten ist ihre Verwendung als Funktionsreferenzen, um eine Implementierung eines undefinierten Schritts an einen Algorithmus oder eine komplexere Operation zu \u00fcbergeben.

                                Zum Beispiel kann die eingebaute generische Listenklasse (List<T>) mit der Funktion FindAll eine neue Liste mit allen Elementen zur\u00fcckgeben, die eine bestimmte Bedingung erf\u00fcllen. Die spezifische Filterbedingung kann als Funktion angegeben werden, genauer gesagt als Delegate-Parameter (dies ruft FindAll f\u00fcr jedes Element auf), der f\u00fcr jedes Element, das wir in der Ergebnisliste sehen wollen, true zur\u00fcckgibt. Der Typ des Funktionsparameters ist der folgende vordefinierte Delegatentyp (er muss nicht eingegeben/erstellt werden, er existiert bereits):

                                public delegate bool Predicate<T>(T obj)\n

                                Note

                                Um die vollst\u00e4ndige Definition oben anzuzeigen, geben Sie einfach Predicate irgendwo ein, z. B. am Ende der Funktion Main, klicken Sie mit der Maus darauf, und verwenden Sie F12, um zur Definition zu navigieren.

                                Das hei\u00dft, sie nimmt als Eingabe eine Variable des gleichen Typs wie der Typ des Listenelements und als Ausgabe einen logischen (booleschen) Wert. Um dies zu veranschaulichen, f\u00fcgen wir unserem vorherigen Programm einen Filter hinzu, der nur die ungeraden Eintr\u00e4ge in der Liste beh\u00e4lt.

                                1. Stellen wir in unserer Anwendung eine Filterfunktion bereit, die ungerade Zahlen zur\u00fcckgibt:

                                  private static bool MyFilter(int n)\n{\n    return n % 2 == 1;\n}\n
                                2. Vervollst\u00e4ndigen wir den Code, den wir zuvor geschrieben haben, mit unserer Filterfunktion:

                                  var list = new List<int>();\nlist.Add(1);\nlist.Add(2);\nlist.Add(3);\nlist = list.FindAll(MyFilter);\n\nforeach (int n in list)\n{\n    Console.WriteLine($\"Wert: {n}\");\n}\n
                                3. F\u00fchren wir die Anwendung aus. Beachten wir, dass in der Konsole nur ungerade Zahlen angezeigt werden.

                                4. Als Kuriosit\u00e4t k\u00f6nnen wir einen Haltepunkt innerhalb unserer Funktion MyFilter setzen und beobachten, dass die Funktion tats\u00e4chlich f\u00fcr jedes Listenelement einzeln aufgerufen wird.

                                Collection initializer syntax

                                F\u00fcr alle Klassen (typischerweise Sammlungen) mit der Methode Add, die die Schnittstelle IEnumerable implementieren, lautet die Syntax f\u00fcr die Sammlungsinitialisierung wie folgt:

                                var list = new List<int>() { 1, 2, 3 };\n

                                Ab C# 12 kann eine noch einfachere Syntax (sogenannte collection expression) verwendet werden, um eine Sammlung zu initialisieren, wenn der Compiler aus dem Typ der Variablen schlie\u00dfen kann, dass es sich um eine Sammlung handelt. Z.B.:

                                List<int> list = [1, 2, 3];\n
                                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#6-aufgabe-lambda-begriffe","title":"6. Aufgabe - Lambda-Begriffe","text":"

                                Die entsprechenden Themen werden in dem Vorlesungsmaterial ausf\u00fchrlich behandelt, sie werden hier nicht wiederholt. Siehe das Kapitel \"Lambda-Ausdruck\" im Dokument \"Vorlesung 02 - Sprachwerkzeuge.pdf\". Das Schl\u00fcsselelement ist => (Lambda-Operator), das die Definition von Lambda-Ausdr\u00fccken, d. h. anonymen Funktionen, erm\u00f6glicht.

                                Action und Func

                                Die in .NET eingebauten generischen Delegatentypen Func und Action werden hier aus Zeitgr\u00fcnden nicht behandelt. Sie sind immer noch Teil des grundlegende Kenntnisse!

                                Die vorherige Aufgabe 5 wird wie folgt gel\u00f6st: Geben wir keine separate Filterfunktion an, sondern spezifizieren wir die Filterlogik in Form eines Lambda-Ausdrucks f\u00fcr die Operation FindAll.

                                Wir brauchen nur eine Zeile zu \u00e4ndern:

                                list = list.FindAll((int n) => { return n % 2 == 1; });\n

                                Eine unbenannte Funktion wird definiert und an die Funtkion FindAll \u00fcbergeben:

                                • dies ist ein Lambda-Term,
                                • auf der linken Seite von => haben wir die Parameter der Operation angegeben (hier gab es nur einen),
                                • auf der rechten Seite von => haben wir der Stamm der Operation angegeben (die gleiche wie der Stamm der vorherigen MyFilter ).

                                Die obige Zeile kann in einer viel einfacheren und klareren Form geschrieben werden:

                                list = list.FindAll(n => n % 2 == 1);\n

                                Es wurden die folgenden Vereinfachungen vorgenommen:

                                • wird der Typ des Parameters nicht geschrieben: der Compiler kann ihn aus dem Typ des Delegatenparameters von FindAll ableiten, der Predicateist.
                                • die Klammern um den Parameter k\u00f6nnen weggelassen werden (da es nur einen Parameter gibt)
                                • auf der rechten Seite von => k\u00f6nnten wir die Klammern und return weglassen (weil es nur einen Ausdruck im Funktionsrumpf gab, der von der Funktion zur\u00fcckgegeben wird).
                                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#7-andere-sprachkonstruktionen","title":"7. Andere Sprachkonstruktionen","text":"

                                Im Folgenden werfen wir einen Blick auf einige der C#-Sprachelemente, die bei allt\u00e4glichen Programmieraufgaben immer h\u00e4ufiger verwendet werden. W\u00e4hrend der \u00dcbung kann es sein, dass keine Zeit bleibt, diese zu \u00fcberpr\u00fcfen.

                                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#ausdruckskorpermember-expression-bodied-members","title":"Ausdrucksk\u00f6rpermember (Expression-bodied members)","text":"

                                Manchmal schreiben wir kurze Funktionen oder, im Falle von Eigenschaften, sehr oft kurze get/set/init-Definitionen, die aus einem einzigen Ausdruck bestehen. In diesem Fall kann der get/set/init-Stamm einer Funktion oder Eigenschaft unter Verwendung der Syntax f\u00fcr sogenannten Ausdrucksk\u00f6rpermember (expression-bodied members) angegeben werden, unter =>. Dies kann unabh\u00e4ngig davon geschehen, ob es im Kontext einen R\u00fcckgabewert (Return-Anweisung) gibt oder nicht.

                                In den Beispielen werden wir sehen, dass die Verwendung von Ausdrucks-Tags nichts weiter als eine kleine syntaktische \"Wendung\" ist, um die Notwendigkeit zu minimieren, so viel umgebenden Code wie m\u00f6glich in solch einfachen F\u00e4llen zu schreiben.

                                Schauen wir uns zun\u00e4chst ein Funktionsbeispiel an (angenommen, die Klasse hat eine Mitgliedsvariable oder eine Eigenschaft Age ):

                                public int GetAgeInDogYear() => Age * 7; \npublic void DisplayName() => Console.WriteLine(ToString());\n
                                Wie wir sehen k\u00f6nnen, haben wir die Klammern und die Anweisung return entfernt, so dass die Syntax kompakter ist.

                                Wichtig

                                Obwohl hier das Token => verwendet wird, hat dies nichts mit den zuvor besprochenen Lambda-Ausdr\u00fccken zu tun: Es ist einfach so, dass dasselbe => Token (Symbolpaar) von C# f\u00fcr zwei v\u00f6llig unterschiedliche Dinge verwendet wird.

                                Beispiel f\u00fcr die Angabe eines Property Getters:

                                public int AgeInDogYear { get => Age * 7; }\n

                                Wenn wir nur einen Getter f\u00fcr die Eigenschaft haben, k\u00f6nnen wir sogar das Schl\u00fcsselwort get und die Klammern weglassen.

                                public int AgeInDogYear => Age * 7;\n

                                Der Unterschied zur \u00e4hnlichen Syntax der bisherigen Funktionen ist, dass wir die geschweifte Klammern nicht ausgeschrieben haben.

                                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#objektinitialisierer-object-initializer","title":"Objektinitialisierer (Object initializer)","text":"

                                Die Initialisierung von \u00f6ffentlichen Eigenschaften/Mitgliedsvariablen und der Aufruf des Konstruktors k\u00f6nnen mit einer Syntax kombiniert werden, die als Objektinitialisierung bezeichnet wird. Dazu wird nach dem Konstruktoraufruf ein Block mit geschweifte Klammern ge\u00f6ffnet, in dem der Wert der \u00f6ffentlichen Eigenschaften/Mitgliedsvariablen unter Verwendung der folgenden Syntax angegeben werden kann.

                                var p = new Person()\n{\n    Age = 17,\n    Name = \"Lukas\",\n};\n

                                Eigenschaften/Mitglieder werden initialisiert, nachdem der Konstruktor ausgef\u00fchrt wurde (wenn die Klasse einen Konstruktor hat). Diese Syntax ist auch deshalb vorteilhaft, weil sie als ein Ausdruck z\u00e4hlt (im Gegensatz zu drei Ausdr\u00fccken, wenn wir ein nicht initialisiertes Objekt Person erstellen und dann in zwei weiteren Schritten Werte an Age und Name \u00fcbergeben). Auf diese Weise k\u00f6nnen wir ein initialisiertes Objekt direkt als Parameter f\u00fcr einen Funktionsaufruf \u00fcbergeben, ohne eine separate Variable deklarieren zu m\u00fcssen.

                                void Foo(Person p)\n{\n    // etwas mit p machen\n}\n
                                Foo(new Person() { Age = 17, Name = \"Lukas\" });\n

                                Die Syntax ist auch zum Kopieren und Einf\u00fcgen geeignet, denn wie wir in den obigen Beispielen sehen k\u00f6nnen, spielt es keine Rolle, ob nach der letzten Eigenschaft ein Komma steht oder nicht.

                                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#eigenschaften-init-only-setter","title":"Eigenschaften - Init only setter","text":"

                                Die Syntax f\u00fcr die Objektinitialisierung im vorigen Abschnitt ist sehr praktisch, erfordert aber, dass die Eigenschaft \u00f6ffentlich ist. Wenn wir m\u00f6chten, dass eine Eigenschaft nur bei der Erstellung des Objekts auf einen Wert gesetzt wird, m\u00fcssen wir einen Konstruktorparameter einf\u00fchren und ihn auf eine Nur-Lesbare-Eigenschaft (Getter-Only) setzen. Eine einfachere L\u00f6sung f\u00fcr dieses Problem ist die so genannte Init only setter-Syntax, bei der wir mit dem Schl\u00fcsselwort init einen \"Setter\" erstellen k\u00f6nnen, der nur im Konstruktor und in der im vorigen Kapitel beschriebenen Syntax f\u00fcr die Objektinitialisierung gesetzt werden darf, nicht aber danach.

                                public string Name { get; init; }\n
                                var p = new Person()\n{\n    Age = 17,\n    Name = \"Lukas\",\n};\n\np.Name = \"Test\"; // Erstellungsfehler, kann nicht nachtr\u00e4glich ge\u00e4ndert werden\n

                                Wir k\u00f6nnen auch den init only setter als obligatorisch festlegen, indem wir das Schl\u00fcsselwort required f\u00fcr die Eigenschaft verwenden. In diesem Fall muss der Wert der Eigenschaft in der Syntax der Objektinitialisierung angegeben werden, da sonst ein \u00dcbersetzungsfehler auftritt.

                                public required string Name { get; init; }\n

                                Dies ist auch deshalb n\u00fctzlich, weil wir die obligatorischen Konstruktorparameter speichern k\u00f6nnen, wenn wir die Eigenschaften der Klasse ohnehin ver\u00f6ffentlichen und die Syntax der Objektinitialisierung unterst\u00fctzen wollen.

                                "},{"location":"labor/2-nyelvi-eszkozok/index_ger/#8-aufgabe-generische-klassen","title":"8. Aufgabe - Generische Klassen","text":"

                                Hinweis: Die Zeit f\u00fcr diese \u00dcbung reicht wahrscheinlich nicht aus. In diesem Fall ist es ratsam, die \u00dcbung zu Hause zu machen.

                                Generische Klassen in .NET \u00e4hneln den Template-Klassen in C++, sind aber n\u00e4her an den bereits bekannten generischen Klassen in Java. Sie k\u00f6nnen verwendet werden, um generische (Multi-Typ), aber typsichere Klassen zu erstellen. Wenn wir ohne generische Klassen ein Problem allgemein behandeln wollen, verwenden wir Daten des Typs object (da in .NET alle Klassen von der Klasse object abgeleitet sind). Dies ist z. B. bei ArrayListder Fall, einer Allzwecksammlung zum Speichern beliebiger Elemente des Typs object. Schauen wir uns ein Beispiel f\u00fcr die Verwendung von ArrayList an:

                                var list = new ArrayList();\nlist.Add(1);\nlist.Add(2);\nlist.Add(3);\nfor (int n = 0; n < list.Count; n++)\n{\n    //cast ist n\u00f6tig, sonder es kann nicht kompiliert werden\n    int i = (int)list[n];\n    Console.WriteLine($\"Wert: {i}\");\n}\n

                                Bei der obigen L\u00f6sung ergeben sich folgende Probleme:

                                • ArrayList speichert jedes Element als object.
                                • Wenn wir auf ein Element in der Liste zugreifen wollen, m\u00fcssen wir es immer in den richtigen Typ umwandeln.
                                • Nicht typsicher. Im obigen Beispiel hindert wir nichts (und keine Fehlermeldung) daran, ein Objekt eines anderen Typs in die Liste neben dem Typ int einzuf\u00fcgen. In diesem Fall w\u00fcrden wir nur dann einen Fehler erhalten, wenn wir versuchen, den Typ, der nicht int ist, auf int zu \u00fcbertragen. Bei der Verwendung generischer Sammlungen werden solche Fehler w\u00e4hrend der \u00dcbersetzung erkannt.
                                • Bei der Speicherung von Daten des Typs \"Wert\" ist die Liste langsamer, da der Typ \"Wert\" zun\u00e4chst in eine Box eingeschlossen werden muss, um als object(d. h. als Referenztyp) gespeichert werden zu k\u00f6nnen.

                                Die L\u00f6sung des obigen Problems unter Verwendung einer allgemeinen Liste sieht wie folgt aus (in der \u00dcbung wird nur die hervorgehobene Zeile im zuvor eingegebenen Beispiel ge\u00e4ndert):

                                var list = new List<int>();\nlist.Add(1);\nlist.Add(2);\nlist.Add(3);\nfor (int n = 0; n < list.Count; n++)\n{\n    int i = list[n]; // Kein cast erforderlich\n    Console.WriteLine($\"Wert: {i}\");\n}\n
                                "},{"location":"labor/3-felhasznaloi-felulet/","title":"3. A felhaszn\u00e1l\u00f3i fel\u00fclet kialak\u00edt\u00e1sa","text":""},{"location":"labor/3-felhasznaloi-felulet/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                                A gyakorlat c\u00e9lja megismerkedni a vastagkliens alkalmaz\u00e1sok fejleszt\u00e9s\u00e9nek alapjaival a deklarat\u00edv XAML fel\u00fcletle\u00edr\u00f3 technol\u00f3gi\u00e1n kereszt\u00fcl. Az itt tanult alapok az \u00f6sszes XAML dialektusra (WinUI, WPF, UWP, Xamarin.Forms, MAUI) igazak lesznek, vagy nagyon hasonl\u00f3an lehet \u0151ket alkalmazni, mi viszont a mai \u00f3r\u00e1n specifikusan a WinAppSDK / WinUI 3 keretrendszeren kereszt\u00fcl fogjuk haszn\u00e1lni a XAML-t.

                                "},{"location":"labor/3-felhasznaloi-felulet/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                                A labor elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                                • Windows 10 vagy Windows 11 oper\u00e1ci\u00f3s rendszer (Linux \u00e9s macOS nem alkalmas)

                                A sz\u00fcks\u00e9ges fejleszt\u0151k\u00f6rnyezetr\u0151l itt tal\u00e1lhat\u00f3 le\u00edr\u00e1s.

                                Fejleszt\u0151k\u00f6rnyezet WinUI3 fejleszt\u00e9shez

                                A kor\u00e1bbi laborokhoz k\u00e9pest plusz komponensek telep\u00edt\u00e9se sz\u00fcks\u00e9ges. A fenti oldal eml\u00edti, hogy sz\u00fcks\u00e9g van a \".NET desktop development\" Visual Studio Workload telep\u00edt\u00e9s\u00e9re, valamint ugyanitt az oldal alj\u00e1n van egy \"WinUI t\u00e1mogat\u00e1s\" fejezet, az itt megadott l\u00e9p\u00e9seket is mindenk\u00e9ppen meg kell tenni!

                                "},{"location":"labor/3-felhasznaloi-felulet/#megoldas","title":"Megold\u00e1s","text":"A k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se

                                L\u00e9nyeges, hogy a labor sor\u00e1n a laborvezet\u0151t k\u00f6vetve kell dolgozni, tilos (\u00e9s \u00e9rtelmetlen) a k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se. Ugyanakkor az ut\u00f3lagos \u00f6n\u00e1ll\u00f3 gyakorl\u00e1s sor\u00e1n hasznos lehet a k\u00e9sz megold\u00e1s \u00e1ttekint\u00e9se, \u00edgy ezt el\u00e9rhet\u0151v\u00e9 tessz\u00fck.

                                A megold\u00e1s GitHubon \u00e9rhet\u0151 el a megoldas \u00e1gon. A legegyszer\u0171bb m\u00f3d a let\u00f6lt\u00e9s\u00e9re, ha parancssorb\u00f3l a git clone utas\u00edt\u00e1ssal lekl\u00f3nozzuk a g\u00e9p\u00fcnkre a megoldas \u00e1gat:

                                git clone https://github.com/bmeviauab00/lab-xaml-kiindulo -b megoldas

                                Ehhez telep\u00edtve kell legyen a g\u00e9pre a parancssori git, b\u0151vebb inform\u00e1ci\u00f3 itt.

                                "},{"location":"labor/3-felhasznaloi-felulet/#kiindulo-projekt","title":"Kiindul\u00f3 projekt","text":"

                                Az els\u0151 feladatban kialak\u00edtjuk a k\u00f6rnyezetet, amelyben a tov\u00e1bbiakban a XAML nyelv \u00e9s a WinUI keretrendszer m\u0171k\u00f6d\u00e9s\u00e9t vizsg\u00e1ljuk. A kiindul\u00f3 projektet a Visual Studi\u00f3val is legener\u00e1lhatn\u00e1nk (WinUI 3 projekt, Blank App, Packaged (WinUI 3 in Desktop) t\u00edpus), de az \u00f3ra g\u00f6rd\u00fcl\u00e9kenys\u00e9ge \u00e9rdek\u00e9ben az el\u0151re elk\u00e9sz\u00edtett projektet fogjuk haszn\u00e1lni.

                                A projektet a k\u00f6vetkez\u0151 parancs kiad\u00e1s\u00e1val tudjuk lekl\u00f3nozni a g\u00e9p\u00fcnkre:

                                git clone https://github.com/bmeviauab00/lab-xaml-kiindulo.git\n

                                Nyissuk meg a HelloXaml.sln-t.

                                Tekints\u00fck \u00e1t milyen f\u00e1jlokat tartalmaz a projekt:

                                • App
                                  • K\u00e9t f\u00e1jl App.xaml \u00e9s App.xaml.cs(k\u00e9s\u0151bb tiszt\u00e1zzuk, k\u00e9t f\u00e1jl tartozik hozz\u00e1)
                                  • Alkalmaz\u00e1s bel\u00e9p\u00e9si pontja: OnLaunched fel\u00fcldefini\u00e1lt met\u00f3dus az App.xaml.cs-ben
                                  • Eset\u00fcnkben itt inicializ\u00e1ljuk az alkalmaz\u00e1s egyetlen ablak\u00e1t a MainWindow-t
                                • MainWindow
                                  • Alkalmaz\u00e1sunk f\u0151ablak\u00e1hoz tartoz\u00f3 .xaml \u00e9s .xaml.cs f\u00e1jlok.
                                Tov\u00e1bbi solution elemek

                                A kiindul\u00f3 VS solution a k\u00f6vetkez\u0151 elemeket tartalmazza m\u00e9g:

                                • Dependencies
                                  • Frameworks
                                    • Microsoft.AspNetCore.App: .NET SDK metapackage (Microsoft .NET \u00e9s SDK alapcsomagokat hivatkozza be)
                                    • Windows specifikus .NET SDK
                                  • Packages
                                    • Windows SDK Build Tools
                                    • WindowsAppSDK
                                • Assets
                                  • Alkalmaz\u00e1s log\u00f3i
                                • app.manifest, Package.appxmanifest
                                  • Az alkalmaz\u00e1s metaadatait tartalmaz\u00f3 XML \u00e1llom\u00e1ny, melyben t\u00f6bbek k\u00f6z\u00f6tt megadhatjuk a log\u00f3kat, vagy pl. Androidhoz hasonl\u00f3an itt kell jogot k\u00e9rj\u00fcnk a biztons\u00e1gkritikus rendszerer\u0151forr\u00e1sokhoz.

                                Futtassuk az alkalmaz\u00e1st!

                                "},{"location":"labor/3-felhasznaloi-felulet/#xaml-bevezetes","title":"XAML bevezet\u00e9s","text":"

                                A fel\u00fclet le\u00edr\u00e1s\u00e1t egy XML alap\u00fa le\u00edr\u00f3 nyelvben, XAML-ben (ejtsd: zem\u00f6l) fogjuk megadni.

                                Grafikus designer fel\u00fclet

                                Bizonyos XAML dialektusok eset\u00e9ben (pl.: WPF) rendelkez\u00e9s\u00fcnkre \u00e1ll grafikus designer eszk\u00f6z is a fel\u00fclet kialak\u00edt\u00e1s\u00e1hoz, de az \u00e1ltal\u00e1ban kev\u00e9sb\u00e9 hat\u00e9kony XAML le\u00edr\u00e1st szokott gener\u00e1lni. R\u00e1ad\u00e1sul m\u00e1r a Visual Studio is t\u00e1mogatja a Hot Reload m\u0171k\u00f6d\u00e9st XAML esetben, \u00edgy nem sz\u00fcks\u00e9ges le\u00e1ll\u00edtani az alkalmaz\u00e1st a XAML szerkeszt\u00e9se k\u00f6zben, a v\u00e1ltoztat\u00e1sokat pedig azonnal l\u00e1thatjuk a fut\u00f3 alkalmaz\u00e1sban. Ez\u00e9rt WinUI eset\u00e9ben m\u00e1r nem is kapunk designer t\u00e1mogat\u00e1st a Visual Studioban. A tapasztalatok alapj\u00e1n vannak limit\u00e1ci\u00f3i, \"nagyobb\" l\u00e9pt\u00e9k\u0171 v\u00e1ltoztat\u00e1sok eset\u00e9n sz\u00fcks\u00e9g van az alkalmaz\u00e1s \u00fajraind\u00edt\u00e1s\u00e1ra.

                                "},{"location":"labor/3-felhasznaloi-felulet/#xaml-nyelvi-alapok","title":"XAML nyelvi alapok","text":"

                                A XAML nyelv:

                                • Objektump\u00e9ld\u00e1nyos\u00edt\u00f3 nyelv
                                • Szabv\u00e1nyos XML
                                • XML elemek/tagek: objektumokat p\u00e9ld\u00e1nyos\u00edtanak, melyek oszt\u00e1lyai szabv\u00e1nyos .NET oszt\u00e1lyok
                                • XML attrib\u00fatumok: tulajdons\u00e1gokat (dependency property-ket) \u00e1ll\u00edtanak be
                                • Deklarat\u00edv

                                N\u00e9zz\u00fck meg, milyen XAML-t gener\u00e1lt a projekt sablon (MainWindow.xaml). L\u00e1thatjuk, hogy a XAML-ben minden vez\u00e9rl\u0151h\u00f6z l\u00e9trehozott egy XML elemet/taget. A vez\u00e9rl\u0151k tagjein pedig be vannak \u00e1ll\u00edtva a vez\u00e9rl\u0151 tulajdons\u00e1gai. Pl. HorizontalAlignment: igaz\u00edt\u00e1s a kont\u00e9neren (eset\u00fcnkben ablakon) bel\u00fcl. Vez\u00e9rl\u0151k tartalmazhatnak m\u00e1s vez\u00e9rl\u0151ket, \u00edgy vez\u00e9rl\u0151kb\u0151l \u00e1ll\u00f3 fa j\u00f6n l\u00e9tre.

                                N\u00e9zz\u00fck meg r\u00e9szletesebben a MainWindow.xaml-t:

                                • Gy\u00f6k\u00e9r tagen n\u00e9vterek: meghat\u00e1rozz\u00e1k, hogy az XML-ben milyen tageket \u00e9s attrib\u00fatumokat haszn\u00e1lhatunk
                                  • Alap\u00e9rtelmezett n\u00e9vt\u00e9r: XAML elemek/vez\u00e9rl\u0151k (pl. Button, TextBox stb.) n\u00e9vtere
                                  • x n\u00e9vt\u00e9r: XAML parser n\u00e9vtere (pl.: x:Class, x:Name)
                                  • Egy\u00e9b tetsz\u0151leges n\u00e9vterek hivatkozhat\u00f3k
                                • Window gy\u00f6k\u00e9r tag
                                  • Az ablakunk/oldalunk alapj\u00e1n egy .NET oszt\u00e1ly j\u00f6n l\u00e9tre, mely a Window oszt\u00e1lyb\u00f3l sz\u00e1rmazik.
                                  • A lesz\u00e1rmaztatott oszt\u00e1lyunk nev\u00e9t az x:Class attrib\u00fatum hat\u00e1rozza meg: az x:Class=\"HelloXaml.MainWindow\" alapj\u00e1n egy HelloXaml n\u00e9vt\u00e9rben egy MainWindow nev\u0171 oszt\u00e1ly lesz.
                                  • Ez egy partial class, az oszt\u00e1ly \"m\u00e1sik fele\" az ablakhoz/oldalhoz tartoz\u00f3 \u00fan. a code-behind f\u00e1jlban (MainWindow.xaml.cs) tal\u00e1lhat\u00f3. L\u00e1sd k\u00f6vetkez\u0151 pont.
                                • Code-behind f\u00e1jl (MainWindow.xaml.cs):
                                  • A partial classunk m\u00e1sik \"fele\": ellen\u0151rizz\u00fck, hogy itt az oszt\u00e1ly neve \u00e9s n\u00e9vtere megegyezik a .xaml f\u00e1jlban megadottal (partial class!).
                                  • Esem\u00e9nykezel\u0151 \u00e9s seg\u00e9df\u00fcggv\u00e9nyeket tessz\u00fck ide (t\u00f6bbek k\u00f6z\u00f6tt).
                                  • this.InitializeComponent();: a konstruktorban mindig meg kell h\u00edvni, ez olvassa majd be fut\u00e1s k\u00f6zben a XAML-t, ez p\u00e9ld\u00e1nyos\u00edtja, inicializ\u00e1lja az ablak/oldal tartalm\u00e1t (vagyis a XAML-f\u00e1jlban megadott vez\u00e9rl\u0151ket az ott meghat\u00e1rozott tulajdons\u00e1gokkal).

                                T\u00f6r\u00f6lj\u00fck ki a Window tartalm\u00e1t \u00e9s a code-behind f\u00e1jlb\u00f3l az esem\u00e9nykezel\u0151t (myButton_Click f\u00fcggv\u00e9ny). Most k\u00e9zzel fogunk XAML-t \u00edrni \u00e9s ezzel a fel\u00fcletet kialak\u00edtani. Vegy\u00fcnk fel egy Grid-et a Window-ba, mellyel a k\u00e9s\u0151bbiekben egy t\u00e1bl\u00e1zatos elrendez\u00e9st (layout) fogunk tudunk kialak\u00edtani:

                                <?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Window\n    x:Class=\"HelloXaml.MainWindow\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:local=\"using:HelloXaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\">\n\n    <Grid>\n\n    </Grid>\n</Window>\n

                                Futtassuk az alkalmaz\u00e1st (pl. az F5 billenty\u0171vel). A Grid most kit\u00f6lti a teljes ablakot, a sz\u00edne megegyezik az ablak h\u00e1tt\u00e9rsz\u00edn\u00e9vel, ez\u00e9rt szemmel nem tudjuk megk\u00fcl\u00f6nb\u00f6ztetni.

                                A k\u00f6vetkez\u0151 feladatok sor\u00e1n hagyjuk futni az alkalmaz\u00e1st, hogy azonnal l\u00e1thassuk a fel\u00fcleten eszk\u00f6z\u00f6lt m\u00f3dos\u00edt\u00e1sainkat.

                                Hot Reload limit\u00e1ci\u00f3k

                                Tartsuk szem el\u0151tt a Hot Reload limit\u00e1ci\u00f3it: ha egy v\u00e1ltoz\u00e1sunk nem akar a fut\u00f3 alkalmaz\u00e1s fel\u00fclet\u00e9n megjelenni, akkor ind\u00edtsuk majd \u00fajra az alkalmaz\u00e1st!

                                "},{"location":"labor/3-felhasznaloi-felulet/#objektum-peldanyok-es-tulajdonsagaik","title":"Objektum p\u00e9ld\u00e1nyok \u00e9s tulajdons\u00e1gaik","text":"

                                Most azt n\u00e9zz\u00fck meg, hogyan tudunk XAML alapokon objektumokat p\u00e9ld\u00e1nyos\u00edtani \u00e9s ezen objektumok tulajdons\u00e1gait be\u00e1ll\u00edtani.

                                Vegy\u00fcnk fel a Grid belsej\u00e9be egy Button-t. A Content tulajdons\u00e1ggal adhatjuk meg a gomb sz\u00f6veg\u00e9t, pontosabban a tartalm\u00e1t.

                                <Button Content=\"Hello WinUI App!\"/>\n

                                Ez azon a helyen, ahol deklar\u00e1ltuk, fut\u00e1s k\u00f6zben l\u00e9trehoz egy Button objektumot, \u00e9s a Content tulajdons\u00e1g\u00e1t a \"Hello WinUI App!\" sz\u00f6vegre \u00e1ll\u00edtja. Ezt megtehett\u00fck volna a code-behind f\u00e1jlban C# nyelven is k\u00f6vetkez\u0151k\u00e9ppen (de ez kev\u00e9sb\u00e9 olvashat\u00f3 k\u00f3dot eredm\u00e9nyezne):

                                // Pl. a konstruktor v\u00e9g\u00e9re be\u00edrva:\n\nButton b = new Button();\nb.Content = \"Hello WinUI App!\";\nrootGrid.Children.Add(b); \n// Az el\u0151z\u0151 a sorhoz XAML f\u00e1jlban a Gridnek meg kellene adni az x:Name=\"rootGrid\" \n// attrib\u00fatumot, hogy rootGrid n\u00e9ven el\u00e9rhet\u0151 legyen a code-behind f\u00e1jlban\n

                                Ez a p\u00e9lda nagyon j\u00f3l szeml\u00e9lteti, hogy a XAML alapvet\u0151en egy objektump\u00e9ld\u00e1nyos\u00edt\u00f3 nyelv, \u00e9s t\u00e1mogatja objektumok tulajdons\u00e1gainak be\u00e1ll\u00edt\u00e1s\u00e1t.

                                A Content tulajdons\u00e1g k\u00fcl\u00f6nleges, nem csak XML attrib\u00fatumban lehet megadni, hanem tagen (XML elemen) bel\u00fcl is.

                                <Button>Hello WinUI App!</Button>\n

                                S\u0151t! A gombra nem csak feliratot rakhatunk, hanem tetsz\u0151leges m\u00e1s elemet. Pl. rakjunk bele egy piros k\u00f6rt. A k\u00f6r 10 pixel sz\u00e9les, 10 pixel magas, a sz\u00edn (Fill) pedig piros.

                                <Button>\n    <Ellipse Width=\"10\" Height=\"10\" Fill=\"Red\" />\n</Button>\n

                                Ezt kor\u00e1bbi .NET UI technol\u00f3gi\u00e1k eset\u00e9ben (pl. Windows Forms) nem lett volna ilyen egyszer\u0171 megval\u00f3s\u00edtani.

                                Legyen most a piros k\u00f6r mellett a Record felirat (hogy \u00e9rtelme is legyen a piros k\u00f6r\u00f6s gombnak). A gombnak csak egy gyereke lehet, ez\u00e9rt egy layout vez\u00e9rl\u0151be (pl. egy StackPanel-be) kell beraknunk a k\u00f6rt \u00e9s a sz\u00f6veget (TextBlock). Adjunk egy bal oldali marg\u00f3t is a TextBlock-nak, hogy ne \u00e9rjenek \u00f6ssze.

                                <Button>\n    <StackPanel Orientation=\"Horizontal\">\n        <Ellipse Width=\"10\" Height=\"10\" Fill=\"Red\" />\n        <TextBlock Text=\"Record\" Margin=\"10,0,0,0\" />\n    </StackPanel>\n</Button>\n

                                A StackPanel egy egyszer\u0171, vez\u00e9rl\u0151k elrendez\u00e9s\u00e9re szolg\u00e1l\u00f3 layout panel: a tartalmazott vez\u00e9rl\u0151ket Horizontal Orientation megad\u00e1sa eset\u00e9n egym\u00e1s mell\u00e9, Vertical Orientation eset\u00e9n egym\u00e1s al\u00e1 helyezi el. \u00cdgy a p\u00e9ld\u00e1nkban egyszer\u0171en egym\u00e1s mell\u00e9 teszi a k\u00e9t vez\u00e9rl\u0151t.

                                Az eredm\u00e9ny a k\u00f6vetkez\u0151:

                                XAML vektorgrafikus vez\u00e9rl\u0151k

                                L\u00e9nyeges, hogy a XAML vez\u00e9rl\u0151k nagy r\u00e9sze vektorgrafikus. Ez a gomb ugyanolyan \u00e9lesen fog kin\u00e9zni (nem tapasztalunk \"pixelesed\u00e9st\") b\u00e1rmilyen b\u00e1rmilyen DPI ill. nagy\u00edt\u00e1s mellett n\u00e9zz\u00fck.

                                A XAML-ben p\u00e9ld\u00e1nyos\u00edtott vez\u00e9rl\u0151k tulajdons\u00e1gainak megad\u00e1s\u00e1ra h\u00e1rom lehet\u0151s\u00e9g van (ezeket r\u00e9szben haszn\u00e1ltuk is m\u00e1r):

                                • Property ATTRIBUTE syntax
                                • Property ELEMENT syntax
                                • Property CONTENT syntax

                                Tekints\u00fck \u00e1t most r\u00e9szletesebben ezeket a lehet\u0151s\u00e9geket:

                                1. Property ATTRIBUTE syntax. M\u00e1r alkalmaztuk, m\u00e9gpedig a legels\u0151 p\u00e9ld\u00e1nkban:

                                  <Button Content=\"Hello WinUI App!\"/>\n

                                  Az elnevez\u00e9s onnan ered, hogy a tulajdons\u00e1got XML attrib\u00fatum form\u00e1j\u00e1ban adjuk meg. Seg\u00edts\u00e9g\u00e9vel - mivel XML attrib\u00fatum csak string lehet! - csak sztring form\u00e1ban megadott egyszer\u0171 sz\u00e1m/sztring/stb. \u00e9rt\u00e9k, ill. code-behind f\u00e1jlban defini\u00e1lt tagv\u00e1ltoz\u00f3, esem\u00e9nykezel\u0151 \u00e9rhet\u0151 el. De t\u00edpuskonverterek seg\u00edts\u00e9g\u00e9vel \"\u00f6sszetett\" objektumok is megadhat\u00f3k. Err\u0151l sok sz\u00f3 nem lesz, de a be\u00e9p\u00edtett t\u00edpuskonvertereket sokszor haszn\u00e1ljuk, gyakorlatilag \"\u00f6szt\u00f6n\u00f6sen\". P\u00e9lda:

                                  Vegy\u00fcnk fel a Grid-re egy h\u00e1tt\u00e9rsz\u00ednt:

                                  <Grid Background=\"Azure\">\n

                                  Vagy megadhatjuk hex\u00e1ban is:

                                  <Grid Background=\"#FFF0FFFF\">\n

                                  A marg\u00f3 (Margin) is egy \u00f6sszetett \u00e9rt\u00e9k, a hozz\u00e1 tartoz\u00f3 t\u00edpuskonveter vessz\u0151vel (vagy sz\u00f3k\u00f6zzel) elv\u00e1lasztva v\u00e1rja a n\u00e9gy oldalra vonatkoz\u00f3 \u00e9rt\u00e9keket (bal, fent, jobb, lent). M\u00e1r haszn\u00e1ltuk is a Record felirat\u00fa TextBlockunk eset\u00e9ben. Megjegyz\u00e9s: marg\u00f3nak egyetlen sz\u00e1m is megadhat\u00f3, akkor mind a n\u00e9gy oldalra ugyanazt fogja alkalmazni.

                                2. Property ELEMENT syntax. Seg\u00edts\u00e9g\u00e9vel egy tulajdons\u00e1got t\u00edpuskonverterek n\u00e9lk\u00fcl tudjuk egy \u00f6sszetett m\u00f3don p\u00e9ld\u00e1nyos\u00edtott/felparam\u00e9terezett objektumra \u00e1ll\u00edtani. N\u00e9zz\u00fck egy p\u00e9ld\u00e1n kereszt\u00fcl.

                                  • A fenti p\u00e9ld\u00e1ban Background tulajdons\u00e1g be\u00e1ll\u00edt\u00e1sakor az Azure val\u00f3j\u00e1ban egy SolidColorBrush-t hoz l\u00e9tre, melynek a sz\u00edn\u00e9t vil\u00e1gosk\u00e9kre \u00e1ll\u00edtja. Ezt t\u00edpuskonverter alkalmaz\u00e1sa n\u00e9lk\u00fcl az al\u00e1bbi m\u00f3don lehet megadni:
                                  <Grid>\n    <Grid.Background>\n        <SolidColorBrush Color=\"Azure\" />\n    </Grid.Background>\n    ...\n

                                  Ez a Grid Background tulajdons\u00e1g\u00e1t \u00e1ll\u00edtja be a megadott SolidColorBrush-ra. Ez az \u00fan. \"property element syntax\" alap\u00fa tulajdons\u00e1gmegad\u00e1s.

                                  • A n\u00e9v onnan ered, hogy a tulajdons\u00e1got egy XML elem (\u00e9s pl. nem XML attrib\u00fatum) form\u00e1j\u00e1ban adjuk meg.
                                  • Itt a <Grid.Background> elem nem objektump\u00e9ld\u00e1nyt hoz l\u00e9tre, hanem az adott (eset\u00fcnkben Background) property \u00e9rt\u00e9k\u00e9t \u00e1ll\u00edtja be a megfelel\u0151 objektum p\u00e9ld\u00e1ny\u00e1ra (eset\u00fcnkben egy SolidColorBrush-ra). Ezt az XML elem nev\u00e9ben lev\u0151 pont alapj\u00e1n lehet tudni.
                                  • Ez \"terjeng\u0151sebb\" forma tulajdons\u00e1g megad\u00e1s\u00e1ra, de teljes rugalmass\u00e1got biztos\u00edt.

                                  Cser\u00e9lj\u00fck le a SolidColorBrush-t egy sz\u00edn\u00e1tmenetes Brush-ra (LinearGradientBrush):

                                  <Grid>\n    <Grid.Background>\n        <LinearGradientBrush>\n            <LinearGradientBrush.GradientStops>\n                <GradientStop Color=\"Black\" Offset=\"0\" />\n                <GradientStop Color=\"White\" Offset=\"1\" />\n            </LinearGradientBrush.GradientStops>\n        </LinearGradientBrush>\n    </Grid.Background>\n    ...\n

                                  LinearGradientBrush-ra nincs t\u00edpuskonverter, ezt csak az element syntax seg\u00edts\u00e9g\u00e9vel tudtuk megadni!

                                  K\u00e9rd\u00e9s, hogyan lehets\u00e9ges az, hogy a Grid vez\u00e9rl\u0151 Background tulajdons\u00e1g\u00e1nak SolidColorBrush \u00e9s LinearGradientBrush t\u00edpus\u00fa ecsetet is meg tudtunk adni? A v\u00e1lasz nagyon egyszer\u0171, a polimorfizmus teszi ezt lehet\u0151v\u00e9:

                                  • A SolidColorBrush \u00e9s LinearGradientBrush oszt\u00e1lyok a be\u00e9p\u00edtett Brush oszt\u00e1ly lesz\u00e1rmazottai.
                                  • A Background tulajdons\u00e1g egy Brush t\u00edpus\u00fa property, \u00edgy a polimorfizmus miatt b\u00e1rmely lesz\u00e1rmazottj\u00e1t lehet haszn\u00e1lni.
                                  Note
                                  • A fenti p\u00e9ld\u00e1kban a Color (sz\u00edn) megad\u00e1s\u00e1n\u00e1l pl. a Color=\"Azure\" esetben az Azure sz\u00f3b\u00f3l is t\u00edpuskonverter k\u00e9sz\u00edt k\u00e9k Color p\u00e9ld\u00e1nyt. \u00cdgy n\u00e9zne a kor\u00e1bbi, SolidColorBrush alap\u00fa p\u00e9ld\u00e1nk teljesen kifejtve:
                                    <Grid>\n    <Grid.Background>\n        <SolidColorBrush>\n            <SolidColorBrush.Color>\n                <Color>#FFF0FFFF</Color>\n            </SolidColorBrush.Color>\n        </SolidColorBrush>\n    </Grid.Background>\n    ...\n
                                  • Ahol t\u00e1mogatott, \u00e9rdemes kihaszn\u00e1lni a t\u00edpuskonvertereket, \u00e9s attribute syntaxot haszn\u00e1lni, hogy ne legyen terjeng\u0151s a XAML le\u00edr\u00e1sunk.
                                  • \u00c9rt\u00e9kt\u00edpusokn\u00e1l (struct), mint amilyen a Color is, m\u00e1r az objektum p\u00e9ld\u00e1nyos\u00edt\u00e1sakor (\"konstruktor id\u0151ben\") kell megadni az \u00e9rt\u00e9ket, ez\u00e9rt itt nem lehet a propertyket k\u00fcl\u00f6n \u00e1ll\u00edtgatni, musz\u00e1j t\u00edpuskonverterre b\u00edzni magunkat.
                                3. Property CONTENT syntax. Annak \u00e9rdek\u00e9ben, hogy jobban meg\u00e9rts\u00fck, n\u00e9zz\u00fck meg, milyen h\u00e1romf\u00e9le m\u00f3don tudjuk be\u00e1ll\u00edtani egy gomb Content tulajdons\u00e1g\u00e1t valamilyen sz\u00f6vegre (ezt laboron nem kell megtenni, el\u00e9g, ha jelen \u00fatmutat\u00f3ban n\u00e9zz\u00fck k\u00f6z\u00f6sen):

                                  • Property attribute syntax (m\u00e1r haszn\u00e1ltuk):
                                    <Button Content=\"Hello WinUI App!\"/>\n
                                  • \u00c1ll\u00edtsuk be az el\u0151z\u0151 pontban tanult property element syntax alapj\u00e1n:
                                    <Button>\n    <Button.Content>\n    Hello WinUI App!\n    </Button.Content>\n</Button>\n
                                  • Minden vez\u00e9rl\u0151 meghat\u00e1rozhat mag\u00e1r\u00f3l egy kit\u00fcntetett \"Content\" tulajdons\u00e1got, melyn\u00e9l nem kell ki\u00edrni a nyit\u00f3 \u00e9s csuk\u00f3 tag-eket. Vagyis az el\u0151z\u0151 p\u00e9ld\u00e1ban alkalmazott <Button.Content> nyit\u00f3 \u00e9s z\u00e1r\u00f3 tag-ek enn\u00e9l az egy tulajdons\u00e1gn\u00e1l elhagyhat\u00f3k:
                                    <Button>\n    Hello WinUI App!\n</Button>\n
                                    Vagy egy sorba \u00edrva:
                                    <Button>Hello WinUI App!</Button>\n
                                    Ez ismer\u0151s, l\u00e1ttuk a bevezet\u0151 p\u00e9ld\u00e1nkban: ez az \u00fan. Property CONTENT syntax alap\u00fa tulajdons\u00e1gmegad\u00e1s. Az elnevez\u00e9s is sugallja, hogy ezt az egy tulajdons\u00e1got a vez\u00e9rl\u0151 \"tartalmi\" r\u00e9sz\u00e9ben, contentj\u00e9ben is megadhatjuk. Nem minden vez\u00e9rl\u0151 eset\u00e9ben Content ezen kit\u00fcntetett tulajdons\u00e1g neve: StackPanel-n\u00e9l \u00e9s Grid-n\u00e9l Children a neve. Eml\u00e9kezz\u00fcnk vissza, ill. n\u00e9zz\u00fck meg a k\u00f3dot: ezeket m\u00e1r haszn\u00e1ltuk is: ugyanakkor, nem \u00edrtuk ki a StackPanel.Children, ill. Grid.Children XML elemeket a StackPanel, ill. Grid belsej\u00e9nek megad\u00e1sakor (de megtehett\u00fck volna!)

                                \u00cdrjuk vissza a Grid h\u00e1tter\u00e9t valami szimpatikusan egyszer\u0171re, vagy t\u00f6r\u00f6lj\u00fck ki a h\u00e1tt\u00e9rsz\u00edn megad\u00e1s\u00e1t.

                                "},{"location":"labor/3-felhasznaloi-felulet/#esemenykezeles","title":"Esem\u00e9nykezel\u00e9s","text":"

                                A XAML applik\u00e1ci\u00f3k esem\u00e9nyvez\u00e9relt alkalmaz\u00e1sok. Minden felhaszn\u00e1l\u00f3i interakci\u00f3r\u00f3l esem\u00e9nyek seg\u00edts\u00e9g\u00e9vel \u00e9rtes\u00fcl\u00fcnk, ezek hat\u00e1s\u00e1ra friss\u00edthetj\u00fck a fel\u00fcletet.

                                Most kezelj\u00fck le a gombon t\u00f6rt\u00e9n\u0151 kattint\u00e1st.

                                El\u0151k\u00e9sz\u00edt\u0151 l\u00e9p\u00e9sk\u00e9nt adjunk nevet a TextBlock vez\u00e9rl\u0151nknek, hogy a code-behind f\u00e1jlb\u00f3l hivatkozni tudjunk majd r\u00e1 a k\u00e9s\u0151bbiekben:

                                <TextBlock x:Name=\"recordTextBlock\" Text=\"Record\" Margin=\"10,0,0,0\" />\n

                                Az x:Name a XAML parsernek sz\u00f3l, \u00e9s ezen a n\u00e9ven fog l\u00e9trehozni egy tagv\u00e1ltoz\u00f3t az oszt\u00e1lyunkban, mely az adott vez\u00e9rl\u0151 referenci\u00e1j\u00e1t tartalmazza. Gondoljuk \u00e1t: mivel tagv\u00e1ltoz\u00f3 lesz, a code-behind f\u00e1jlban el tudjuk \u00e9rni, hiszen az egy \"partial r\u00e9sze\" ugyanazon oszt\u00e1lynak!

                                Elnevezett vez\u00e9rl\u0151k

                                Ne adjunk nevet azoknak a vez\u00e9rl\u0151knek, melyekre nem akarunk hivatkozni. (Szoktassuk magunkat arra, hogy csak arra hivatkozunk k\u00f6zvetlen\u00fcl, amire nagyon musz\u00e1j. Ebben az adatk\u00f6t\u00e9s is seg\u00edt majd.)

                                Kiv\u00e9tel: Ha nagyon bonyolult a vez\u00e9rl\u0151hierarchi\u00e1nk, seg\u00edthetnek a nevek a k\u00f3d \u00e1tl\u00e1that\u00f3bb\u00e1 t\u00e9tel\u00e9ben, mivel a Live Visual Tree ablakban megjelennek, illetve a gener\u00e1lt esem\u00e9nykezel\u0151-nevek is ehhez igazodnak.

                                Kezelj\u00fck le a gomb Click esem\u00e9ny\u00e9t, majd pr\u00f3b\u00e1ljuk ki a k\u00f3dot.

                                MainWindow.xaml-be
                                <Button Click=\"RecordButton_Click\">\n
                                MainWindow.xaml.cs-be
                                private void RecordButton_Click(object sender, RoutedEventArgs e)\n{\n    recordTextBlock.Text = \"Recording...\";\n}\n

                                Esem\u00e9nykezel\u0151k l\u00e9trehoz\u00e1sa

                                Ha az esem\u00e9nykezel\u0151kn\u00e9l nem a New Event Handler-t v\u00e1lasztjuk, hanem be\u00edrjuk k\u00e9zzel a k\u00edv\u00e1nt nevet, majd F12-t nyomunk, vagy a jobb gomb / Go to Definition-t v\u00e1lasztjuk, az esem\u00e9nykezel\u0151 legener\u00e1l\u00e1sra ker\u00fcl a code-behind f\u00e1jlban.

                                Az esem\u00e9nykezel\u0151nek k\u00e9t param\u00e9tere van: a k\u00fcld\u0151 objektum (object sender) \u00e9s az esem\u00e9ny param\u00e9tereit/k\u00f6r\u00fclm\u00e9nyeit tartalmaz\u00f3 param\u00e9ter (EventArgs e). N\u00e9zz\u00fck ezeket r\u00e9szletesebben:

                                • object sender: Az esem\u00e9ny kiv\u00e1lt\u00f3ja. Eset\u00fcnkben ez maga a gomb, Button-ra kasztolva haszn\u00e1lhatn\u00e1nk is. Ritk\u00e1n haszn\u00e1ljuk ez a param\u00e9tert.
                                • A m\u00e1sodik param\u00e9ter mindig EventArgs t\u00edpus\u00fa, vagy annak lesz\u00e1rmazottja (ez az esem\u00e9ny t\u00edpus\u00e1t\u00f3l f\u00fcgg), melyben az esem\u00e9ny param\u00e9tereit kapjuk meg. A Click esem\u00e9ny eset\u00e9ben ez RoutedEventArgs t\u00edpus\u00fa.

                                Esem\u00e9nyargumentumok

                                N\u00e9h\u00e1ny esem\u00e9nyargumentum t\u00edpus:

                                • RoutedEventArgs: pl. a Click esem\u00e9ny est\u00e9ben haszn\u00e1land\u00f3, ahogy a p\u00e9ld\u00e1nkban is volt. Az OriginalSource tulajdons\u00e1gban megkapjuk azt a vez\u00e9rl\u0151t, melyn\u00e9l el\u0151sz\u00f6r kiv\u00e1lt\u00f3dott az esem\u00e9ny.
                                  • Megjegyz\u00e9s: a fenti esetben ez maga a gomb, de ha pl. egy eg\u00e9rlenyom\u00e1s esem\u00e9nyt (nem a Click, hanem PointerPressed) kezeln\u00e9nk pl. a StackPanel-en, akkor lehet, hogy az egyik gyerekelem\u00e9t kapn\u00e1nk meg, ha arra kattintottak.
                                • KeyRoutedEventArgs: pl. KeyDown (billenty\u0171 lenyom\u00e1sa) esem\u00e9ny eset\u00e9ben megkapjuk benne a lenyomott billenty\u0171t.
                                • PointerRoutedEventArgs: pl. PointerPressed (eg\u00e9r/toll lenyom\u00e1sa) esem\u00e9ny eset\u00e9ben haszn\u00e1ljuk, rajta kereszt\u00fcl lek\u00e9rdezhet\u0151k - t\u00f6bbek k\u00f6z\u00f6tt - a kattint\u00e1s koordin\u00e1t\u00e1i.

                                A XAML esem\u00e9nykezel\u0151k teljes eg\u00e9sz\u00e9ben a C# nyelv esem\u00e9nyeire \u00e9p\u00fclnek (event kulcssz\u00f3, l\u00e1sd el\u0151z\u0151 gyakorlat):

                                Pl. a

                                <Button Click=\"RecordButton_Click\">\n

                                erre k\u00e9pz\u0151dik le:

                                Button b = new Button();\nb.Click += RecordButton_Click;\n
                                "},{"location":"labor/3-felhasznaloi-felulet/#layout-elrendezes","title":"Layout, elrendez\u00e9s","text":"

                                A vez\u00e9rl\u0151k elrendez\u00e9s\u00e9t k\u00e9t dolog hat\u00e1rozza meg:

                                1. Layout (panel) vez\u00e9rl\u0151k \u00e9s kapcsolhat\u00f3 tulajdons\u00e1gaik (attached property)
                                2. Sz\u00fcl\u0151 vez\u00e9rl\u0151n bel\u00fcli \u00e1ltal\u00e1nos poz\u00edci\u00f3 tulajdons\u00e1gok (pl. marg\u00f3, igaz\u00edt\u00e1s f\u00fcgg\u0151legesen vagy v\u00edzszintesen)

                                Be\u00e9p\u00edtett layout vez\u00e9rl\u0151k p\u00e9ld\u00e1ul:

                                • StackPanel: elemek egym\u00e1s alatt vagy mellett
                                • Grid: defini\u00e1lhatunk egy r\u00e1csot, melyhez igazodnak az elemek
                                • Canvas: explicit poz\u00edcion\u00e1lhat\u00f3k az elemek az X \u00e9s Y koordin\u00e1t\u00e1juk megad\u00e1s\u00e1val
                                • RelativePanel: elemek egym\u00e1shoz k\u00e9pesti viszony\u00e1t hat\u00e1rozhatjuk meg k\u00e9nyszerekkel

                                A Grid-et fogjuk kipr\u00f3b\u00e1lni (\u00e1ltal\u00e1ban ezt haszn\u00e1ljuk az ablakunk/oldalunk alapelrendez\u00e9s\u00e9nek kialak\u00edt\u00e1s\u00e1ra). Egy olyan fel\u00fcletet k\u00e9sz\u00edt\u00fcnk el, melyen szem\u00e9lyeket lehet egy list\u00e1ba felvenni, nev\u00fck \u00e9s \u00e9letkoruk megad\u00e1s\u00e1val. A k\u00f6vetkez\u0151 elrendez\u00e9s kialak\u00edt\u00e1sa a v\u00e9gs\u0151 c\u00e9lunk:

                                P\u00e1r l\u00e9nyeges viselked\u00e9sbeli megk\u00f6t\u00e9s:

                                • Az ablak \u00e1tm\u00e9retez\u00e9sekor az \u0171rlap fix sz\u00e9less\u00e9g\u0171 legyen, \u00e9s maradjon k\u00f6z\u00e9pre igaz\u00edtva.
                                • Az Age sorban a + gombbal n\u00f6velhet\u0151, a - gombbal cs\u00f6kkenthet\u0151 az \u00e9letkor.
                                • Az Add gombbal a fent meghat\u00e1rozott adatokkal felveszi a szem\u00e9lyt az als\u00f3 list\u00e1ba (az \u00e1br\u00e1n az als\u00f3 list\u00e1ban k\u00e9t szem\u00e9ly adatai l\u00e1that\u00f3k).

                                Defini\u00e1ljunk a gy\u00f6k\u00e9r Grid-en 4 sort \u00e9s 2 oszlopot. Az els\u0151 oszlop\u00e1ba ker\u00fcljenek a c\u00edmk\u00e9k, a m\u00e1sodik oszlopba pedig a beviteli mez\u0151k. A megl\u00e9v\u0151 gombunkat is rakjuk a 3. sorba, \u00e9s \u00edrjuk \u00e1t a tartalm\u00e1t Add-ra, a k\u00f6r helyett pedig vegy\u00fcnk fel egy SymbolIcon-t. A 4. sorban pedig list\u00e1t helyezz\u00fcnk el, ami 2 oszlopot is foglaljon el.

                                <Grid x:Name=\"rootGrid\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n    <Grid.ColumnDefinitions>\n        <ColumnDefinition Width=\"Auto\" />\n        <ColumnDefinition Width=\"*\" />\n    </Grid.ColumnDefinitions>\n\n    <TextBlock Grid.Row=\"0\" Grid.Column=\"0\" Text=\"Name\"/>\n    <TextBox Grid.Row=\"0\" Grid.Column=\"1\" x:Name=\"tbName\"/>\n    <TextBlock Grid.Row=\"1\" Grid.Column=\"0\" Text=\"Age\"/>\n    <TextBox Grid.Row=\"1\" Grid.Column=\"1\" x:Name=\"tbAge\"/>\n\n    <Button Grid.Row=\"2\" Grid.Column=\"1\">\n        <StackPanel Orientation=\"Horizontal\">\n            <SymbolIcon Symbol=\"Add\" />\n            <TextBlock Text=\"Add\" Margin=\"5,0,0,0\"/>\n        </StackPanel>\n    </Button>\n\n    <ListView Grid.Row=\"3\" Grid.Column=\"0\" Grid.ColumnSpan=\"2\"/>\n</Grid>\n

                                A sor- \u00e9s oszlopdefin\u00edci\u00f3k eset\u00e9ben megadhatjuk, hogy az adott sor vegye fel a tartalm\u00e1nak a m\u00e9ret\u00e9t (Auto), vagy t\u00f6ltse ki a marad\u00e9k helyet (*), de ak\u00e1r fix sz\u00e9less\u00e9get is megadhatn\u00e1nk pixelben (Width tulajdons\u00e1g). Ha t\u00f6bb * is szerepel a defin\u00edci\u00f3kban, akkor azok ar\u00e1nyos\u00edthat\u00f3ak pl.: * \u00e9s * 1:1-es ar\u00e1nyt jelent, m\u00edg a * \u00e9s 3* 1:3-at.

                                A Grid.Row, Grid.Column \u00fagynevezett Attached Property-k (csatolt tulajdons\u00e1gok). Ez azt jelenti, hogy a vez\u00e9rl\u0151, melyn\u00e9l alkalmazzuk, nem rendelkezik ilyen tulajdons\u00e1ggal, \u00e9s ezt az inform\u00e1ci\u00f3t csak \u201ehozz\u00e1csatoljuk\u201d. Ez az inform\u00e1ci\u00f3 eset\u00fcnkben a Grid-nek lesz fontos, hogy el tudja helyezni a gyerekeit. A Grid.Row \u00e9s Grid.Column alap\u00e9rtelmezett \u00e9rt\u00e9ke a 0, teh\u00e1t ezt ki sem k\u00e9ne \u00edrnunk.

                                Imperat\u00edv UI le\u00edr\u00e1s

                                M\u00e1s UI keretrendszerekben, ahol imperat\u00edv a fel\u00fclet \u00f6ssze\u00e1ll\u00edt\u00e1sa, ezt egyszer\u0171en megoldj\u00e1k f\u00fcggv\u00e9nyparam\u00e9terekkel \u2013 pl.: myPanel.Add(new TextBox(), 0, 1).

                                M\u00e9g magyar\u00e1zatra szorulhat a ListView-n\u00e1l megadott Grid.ColumnSpan=\"2\" csatolt tulajdons\u00e1g: a ColumnSpan \u00e9s RowSpan azt hat\u00e1rozz\u00e1k meg, h\u00e1ny oszlopon illetve soron \"\u00e1t\u00edvel\u0151en\" helyezkedjen el a vez\u00e9rl\u0151. A p\u00e9ld\u00e1nkban a ListView mindk\u00e9t oszlopot kit\u00f6lti.

                                Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st (ha nem fordul a k\u00f3d, akkor t\u00f6r\u00f6lj\u00fck a code behind f\u00e1jlban a RecordButton_Click esem\u00e9nykezel\u0151t).

                                Jelen \u00e1llapot\u00e1ban a Grid kit\u00f6lti a teljes teret v\u00edzszintesen \u00e9s f\u00fcgg\u0151legesen is. Mi ennek az oka? A vez\u00e9rl\u0151k elrendez\u00e9s\u00e9nek egyik alapil\u00e9re a HorizontalAlignment \u00e9s VerticalAlignment tulajdons\u00e1guk. Ezek azt hat\u00e1rozz\u00e1k meg, hogy v\u00edzszintesen \u00e9s f\u00fcgg\u0151legesen hol helyezkedjen el az adott vez\u00e9rl\u0151 az \u0151t tartalmaz\u00f3 kont\u00e9nerben (vagyis a sz\u00fcl\u0151 vez\u00e9rl\u0151ben). A lehets\u00e9ges \u00e9rt\u00e9kek:

                                • VerticalAlignment: Top, Center, Bottom, Stretch (fel\u00fclre, k\u00f6z\u00e9pre, alulra igaz\u00edtva, vagy t\u00e9r kit\u00f6lt\u00e9se f\u00fcgg\u0151legesen)
                                • HorizontalAlignment: Left, Center, Right, Stretch (balra, k\u00f6z\u00e9pre, jobbra igaz\u00edtva, vagy t\u00e9r kit\u00f6lt\u00e9se v\u00edzszintesen)

                                (Megjegyz\u00e9s: a Stretch eset\u00e9ben sz\u00fcks\u00e9ges, hogy ne legyen a Height ill. Width tujadons\u00e1g megadva a vez\u00e9rl\u0151re.)

                                A Grid-\u00fcnknek nem adtunk meg HorizontalAlignment \u00e9s VerticalAlignment tulajdons\u00e1got, \u00edgy annak \u00e9rt\u00e9ke a Grid eset\u00e9ben alap\u00e9rtelmezett Stretch, emiatt a Grid mindk\u00e9t ir\u00e1nyban kit\u00f6lti a teret a sz\u00fcl\u0151 kont\u00e9ner\u00e9ben, vagyis az ablakban.

                                A fel\u00fclet\u00fcnk m\u00e9g nem \u00fagy n\u00e9z ki, mint amit szeretn\u00e9nk, finom\u00edtsunk kicsit a kin\u00e9zet\u00e9n. Az eszk\u00f6zlend\u0151 v\u00e1ltoz\u00e1sok:

                                • Ne t\u00f6ltse ki az eg\u00e9sz k\u00e9perny\u0151t a t\u00e1bl\u00e1zat, hanem legyen v\u00edzszintesen k\u00f6z\u00e9pen
                                  • HorizontalAlignment=\"Center\"
                                • Legyen 300px sz\u00e9les
                                  • Width=\"300\"
                                • Legyen a sorok k\u00f6z\u00f6tt 10px, az oszlopok k\u00f6z\u00f6tt 5px t\u00e1vols\u00e1g \u00e9s tartsunk 20px t\u00e1vols\u00e1got a kont\u00e9ner sz\u00e9l\u00e9t\u0151l
                                  • RowSpacing=\"5\" ColumnSpacing=\"10\" Margin=\"20\"
                                • Igaz\u00edtsuk a c\u00edmk\u00e9ket (TextBlock) f\u00fcgg\u0151legesen k\u00f6z\u00e9pre
                                  • VerticalAlignment=\"Center\"
                                • Igaz\u00edtsuk a gombot jobbra
                                  • HorizontalAlignment=\"Right\"
                                • Tegy\u00fck beazonos\u00edthat\u00f3v\u00e1 a list\u00e1t
                                  • BorderThickness=\"1\" \u00e9s BorderBrush=\"DarkGray\"
                                <Grid x:Name=\"rootGrid\"\n      Width=\"300\"\n      HorizontalAlignment=\"Center\"\n      Margin=\"20\"\n      RowSpacing=\"5\"\n      ColumnSpacing=\"10\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n    <Grid.ColumnDefinitions>\n        <ColumnDefinition Width=\"Auto\" />\n        <ColumnDefinition Width=\"*\" />\n    </Grid.ColumnDefinitions>\n\n    <TextBlock Grid.Row=\"0\" Grid.Column=\"0\" Text=\"Name\" VerticalAlignment=\"Center\"/>\n    <TextBox Grid.Row=\"0\" Grid.Column=\"1\" x:Name=\"tbName\" />\n    <TextBlock Grid.Row=\"1\" Grid.Column=\"0\" Text=\"Age\" VerticalAlignment=\"Center\"/>\n    <TextBox Grid.Row=\"1\" Grid.Column=\"1\" x:Name=\"tbAge\"/>\n\n    <Button Grid.Row=\"2\" Grid.Column=\"1\" HorizontalAlignment=\"Right\">\n        <StackPanel Orientation=\"Horizontal\">\n            <SymbolIcon Symbol=\"Add\"/>\n            <TextBlock Text=\"Add\" Margin=\"5,0,0,0\" />\n        </StackPanel>\n    </Button>\n\n    <ListView Grid.Row=\"3\"\n              Grid.Column=\"0\"\n              Grid.ColumnSpan=\"2\"\n              BorderThickness=\"1\"\n              BorderBrush=\"DarkGray\"/>\n</Grid>\n

                                B\u0151v\u00edts\u00fck ki m\u00e9g k\u00e9t gombbal az \u0171rlapunkat (\u00b1 gombok az \u00e9letkorhoz, l\u00e1sd kor\u00e1bbi anim\u00e1lt k\u00e9perny\u0151k\u00e9p):

                                • \u2019-\u2019: a TextBox bal oldal\u00e1n
                                • \u2019+\u2019 aTextBox jobb oldal\u00e1n

                                Ehhez vegy\u00fcnk fel a

                                <TextBox Grid.Row=\"1\" Grid.Column=\"1\" x:Name=\"tbAge\"/>\n

                                sor hely\u00e9re (azt kit\u00f6r\u00f6lve) egy 1 soros, 3 oszloppal rendelkez\u0151 Grid-et:

                                <Grid Grid.Row=\"1\" Grid.Column=\"1\" ColumnSpacing=\"5\">\n    <Grid.ColumnDefinitions>\n        <ColumnDefinition Width=\"Auto\" />\n        <ColumnDefinition Width=\"*\" />\n        <ColumnDefinition Width=\"Auto\" />\n    </Grid.ColumnDefinitions>\n\n    <Button Grid.Row=\"0\" Grid.Column=\"0\" Content=\"-\" />\n    <TextBox Grid.Row=\"0\" Grid.Column=\"1\" x:Name=\"tbAge\" />\n    <Button Grid.Row=\"0\" Grid.Column=\"2\" Content=\"+\" />\n</Grid>\n

                                T\u00f6bb layout vez\u00e9rl\u0151 egym\u00e1sba \u00e1gyaz\u00e1sa

                                Feltehetj\u00fck a k\u00e9rd\u00e9st, hogy mi\u00e9rt nem a k\u00fcls\u0151 Grid-ben vett\u00fcnk fel plusz oszlopokat \u00e9s sorokat (a ColumnSpan megfelel\u0151 alkalmaz\u00e1s\u00e1val a megl\u00e9v\u0151 vez\u00e9rl\u0151kre). Helyette egys\u00e9gbez\u00e1r\u00e1s elv\u00e9t k\u00f6vett\u00fck: az \u00fajonnan bevezetett vez\u00e9rl\u0151k alapvet\u0151en egybe tartoz\u00f3 elemek, \u00edgy \u00e1tl\u00e1that\u00f3bb megold\u00e1st kaptunk az\u00e1ltal, hogy k\u00fcl\u00f6n Grid vez\u00e9rl\u0151be tett\u00fck \u0151ket. A k\u00fcls\u0151 Grid b\u0151v\u00edt\u00e9se akkor lenne indokolt, ha sp\u00f3rolni akarn\u00e1nk a vez\u00e9rl\u0151k l\u00e9trehoz\u00e1s\u00e1val, teljes\u00edtm\u00e9nyokok miatt. Eset\u00fcnkben ez nem indokolt.

                                K\u00e9szen is vagyunk az egyszer\u0171 \u0171rlapunk kin\u00e9zet\u00e9nek kialak\u00edt\u00e1s\u00e1val.

                                "},{"location":"labor/3-felhasznaloi-felulet/#adatkotes","title":"Adatk\u00f6t\u00e9s","text":""},{"location":"labor/3-felhasznaloi-felulet/#binding","title":"Binding","text":"

                                A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben azt oldjuk meg, hogy az el\u0151bb elk\u00e9sz\u00edtett kis \u0171rlapon egy szem\u00e9ly adatait lehessen megadni, m\u00f3dos\u00edtani. Ehhez m\u00e1r el\u0151 van k\u00e9sz\u00edtve egy Person oszt\u00e1ly a projekt Models mapp\u00e1j\u00e1ban, n\u00e9zz\u00fcnk ezt meg.

                                public class Person\n{\n    public string Name { get; set; }\n    public int Age { get; set; }\n}\n

                                Azt itt l\u00e9v\u0151 k\u00e9t tulajdons\u00e1got akarjuk a TextBox vez\u00e9rl\u0151kh\u00f6z k\u00f6tni, ehhez adatk\u00f6t\u00e9st fogunk alkalmazni. Az ablakunk code-behind f\u00e1jlj\u00e1ban vezess\u00fcnk be egy propertyt, mely egy Person objektumra hivatkozik, \u00e9s adjunk ennek kezd\u0151\u00e9rt\u00e9ket a konstruktorban:

                                public Person NewPerson { get; set; }\n\npublic MainWindow()\n{\n    InitializeComponent();\n\n    NewPerson = new Person()\n    {\n        Name = \"Eric Cartman\",\n        Age = 8\n    };\n}\n

                                A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben a fenti NewPerson objektum

                                • Name tulajdons\u00e1g\u00e1t k\u00f6ss\u00fck hozz\u00e1 a tbName Textbox Text tulajdons\u00e1g\u00e1hoz
                                • Age tulajdons\u00e1g\u00e1t k\u00f6ss\u00fck hozz\u00e1 a tbAge Textbox Text tulajdons\u00e1g\u00e1hoz , m\u00e9gpedig adatk\u00f6t\u00e9ssel (data binding):

                                Text=\"{x:Bind NewPerson.Name}\"\nText=\"{x:Bind NewPerson.Age}\"\n
                                (a tbName ill. tbAge TextBox-ok soraiba vegy\u00fck fel a fenti 1-1 tulajdons\u00e1g be\u00e1ll\u00edt\u00e1st)

                                Fontos

                                Az adatk\u00f6t\u00e9snek az a l\u00e9nyege, hogy nem k\u00e9zzel, a code-behind f\u00e1jlb\u00f3l \u00e1ll\u00edtgatjuk a fel\u00fcleten megjelen\u0151 vez\u00e9rl\u0151k tulajdons\u00e1gait (eset\u00fcnkben a sz\u00f6veg\u00e9t), hanem \u00f6sszerendelj\u00fck/ \u00f6sszek\u00f6tj\u00fck a tulajdons\u00e1gokat a platform adatk\u00f6t\u00e9s mechanizmus\u00e1val. \u00cdgy azt is el\u00e9rhetj\u00fck, hogyha az egyik tulajdons\u00e1g megv\u00e1ltozik, akkor a m\u00e1sik is automatikusan v\u00e1ltozzon meg!

                                A Text=\"{x:Bind}\" szintaktika az \u00fagynevezett markup extension: ez speci\u00e1lis jelent\u00e9ssel rendelkezik a XAML feldolgoz\u00f3 sz\u00e1m\u00e1ra. Els\u0151sorban emiatt haszn\u00e1lunk XAML \u00e9s nem sima XML-t. Lehet\u0151s\u00e9g\u00fcnk van ak\u00e1r saj\u00e1t Markup Extension-t is k\u00e9sz\u00edteni, de ez nem tananyag.

                                Futtassuk! L\u00e1that\u00f3, hogy az adatk\u00f6t\u00e9s miatt automatikusan beker\u00fclt a k\u00e9t TextBox Text tulajdons\u00e1g\u00e1ba a NewPerson objektum (mint adatforr\u00e1s) Name \u00e9s Age tulajdons\u00e1gaiban megadott n\u00e9v \u00e9s \u00e9letkor.

                                "},{"location":"labor/3-felhasznaloi-felulet/#valtozasertesites","title":"V\u00e1ltoz\u00e1s\u00e9rtes\u00edt\u00e9s","text":"

                                Implement\u00e1ljuk a \u00b1 gombok Click esem\u00e9nykezel\u0151it.

                                <Button Grid.Row=\"1\" Grid.Column=\"0\" Content=\"-\" Click=\"DecreaseButton_Click\"/>\n<!-- ... -->\n<Button Grid.Row=\"1\" Grid.Column=\"2\" Content=\"+\" Click=\"IncreaseButton_Click\"/>\n
                                private void DecreaseButton_Click(object sender, RoutedEventArgs e)\n{\n    NewPerson.Age--;\n}\n\nprivate void IncreaseButton_Click(object sender, RoutedEventArgs e)\n{\n    NewPerson.Age++;\n}\n

                                A kor\u00e1bbi pontban bevezetett adatk\u00f6t\u00e9s miatt azt v\u00e1rn\u00e1nk, hogy ha a NewPerson adatforr\u00e1s Age tulajdons\u00e1g\u00e1t v\u00e1ltoztatjuk a fenti esem\u00e9nykezel\u0151kben, akkor a fel\u00fclet\u00fcnk\u00f6n a tbAge Textbox vez\u00e9rl\u0151nk ezt lek\u00f6veti. Pr\u00f3b\u00e1ljuk ki! Ez m\u00e9g egyel\u0151re nem m\u0171k\u00f6dik, ugyanis ehhez sz\u00fcks\u00e9g van m\u00e9g az INotifyPropertyChanged interf\u00e9sz megval\u00f3s\u00edt\u00e1s\u00e1ra is.

                                1. Implement\u00e1ljuk az INotifyPropertyChanged interf\u00e9szt a Person oszt\u00e1lyunkban. Ha adatk\u00f6t\u00fcnk ehhez az oszt\u00e1lyhoz, akkor a rendszer a PropertyChanged esem\u00e9nyre fog feliratkozni, ennek az esem\u00e9nynek a els\u00fct\u00e9s\u00e9vel tudjuk \u00e9rtes\u00edteni a bindingot, ha egy property megv\u00e1ltozott.

                                  public class Person : INotifyPropertyChanged\n{\n    public event PropertyChangedEventHandler PropertyChanged;\n\n    private string name;\n    public string Name\n    {\n        get { return name; }\n        set\n        {\n            if (name != value)\n            {\n                name = value;\n                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));\n            }\n        }\n    }\n\n    private int age;\n    public int Age\n    {\n        get { return age; }\n        set\n        {\n            if (age != value)\n            {\n                age = value;\n                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Age)));\n            }\n        }\n    }\n}\n

                                  Terjeng\u0151s a k\u00f3d?

                                  A k\u00e9s\u0151bbiekben ezt a logik\u00e1t ki is szervezhetn\u00e9nk egy \u0151soszt\u00e1lyba, de ez m\u00e1r az MVVM mint\u00e1t vezetn\u00e9 el\u0151, mely egy k\u00e9s\u0151bbi tematik\u00e1hoz kapcsol\u00f3dik. Teh\u00e1t ne ijedj\u00fcnk meg ett\u0151l a kiss\u00e9 cs\u00fany\u00e1cska k\u00f3dt\u00f3l.

                                2. Az adatk\u00f6t\u00e9sen kapcsoljuk be a v\u00e1ltoz\u00e1s\u00e9rtes\u00edt\u00e9st a Mode OneWay-re t\u00f6rt\u00e9n\u0151 m\u00f3dos\u00edt\u00e1s\u00e1val, mivel az x:Bind alap\u00e9rtelmezett m\u00f3dja a OneTime, mely csak egyszeri adatk\u00f6t\u00e9st jelent.

                                  Text=\"{x:Bind NewPerson.Age, Mode=OneWay}\"\n

                                Pr\u00f3b\u00e1ljuk ki! Az esem\u00e9nykezel\u0151k v\u00e1ltoztatj\u00e1k az adatforr\u00e1st (NewPerson), ennek hat\u00e1s\u00e1ra most m\u00e1r v\u00e1ltozik a fel\u00fclet is a megfelel\u0151en el\u0151k\u00e9sz\u00edtett adatk\u00f6t\u00e9s miatt.

                                "},{"location":"labor/3-felhasznaloi-felulet/#ketiranyu-adatkotes","title":"K\u00e9tir\u00e1ny\u00fa adatk\u00f6t\u00e9s","text":"

                                Az Age mint\u00e1j\u00e1ra, a Name tulajdons\u00e1gra vonatkoz\u00f3 adatk\u00f6t\u00e9st is \u00e1ll\u00edtsuk egyir\u00e1ny\u00fara:

                                Text=\"{x:Bind NewPerson.Name, Mode=OneWay}\"\n

                                Ind\u00edtsuk el az alkalmaz\u00e1st, majd ezt k\u00f6vet\u0151en tegy\u00fcnk egy t\u00f6r\u00e9spontot a Person oszt\u00e1ly Name tulajdons\u00e1g\u00e1nak setter\u00e9be (if (name != value) sor) , \u00e9s pr\u00f3b\u00e1ljuk, hogy vissza ir\u00e1nyba is m\u0171k\u00f6dik-e az adatk\u00f6t\u00e9s: ha megv\u00e1ltoztatjuk az egyik TextBox \u00e9rt\u00e9k\u00e9t, megv\u00e1ltozik-e a NewPerson objektum Name tulajdons\u00e1ga? G\u00e9pelj\u00fcnk valamit a Name-hez tartoz\u00f3 sz\u00f6vegdobozba, majd kattintsunk \u00e1t egy m\u00e1sik mez\u0151be: ekkor a Textbox tartalma \"v\u00e9gleges\u00edt\u0151dik\", tartalma vissza kellene \u00edr\u00f3djon az adatforr\u00e1sba, de m\u00e9gsem t\u00f6rt\u00e9nik meg, nem fut r\u00e1 a k\u00f3d a t\u00f6r\u00e9spontunkra.

                                Ez az\u00e9rt van \u00edgy, mert fentebb OneWay adatk\u00f6t\u00e9st haszn\u00e1ltunk, mely csak az adatforr\u00e1sb\u00f3l a fel\u00fcletre ir\u00e1ny\u00fa adatk\u00f6t\u00e9st jelent. Ha azt szeretn\u00e9nk, hogy az adatk\u00f6t\u00e9s a m\u00e1sik ir\u00e1nyba is m\u0171k\u00f6dj\u00f6n (vez\u00e9rl\u0151b\u0151l adatforr\u00e1sba), ahhoz TwoWay-re kell \u00e1ll\u00edtsuk az adatk\u00f6t\u00e9s m\u00f3dj\u00e1t. Ezt k\u00e9tir\u00e1ny\u0171 adatk\u00f6t\u00e9snek nevezz\u00fck.

                                Text=\"{x:Bind Name, Mode=TwoWay}\"\nText=\"{x:Bind Age, Mode=TwoWay}\"\n

                                Pr\u00f3b\u00e1ljuk ki! \u00cdgy az adatk\u00f6t\u00e9s m\u00e1r mindk\u00e9t ir\u00e1nyba m\u0171k\u00f6dik:

                                • Ha a forr\u00e1stulajdons\u00e1g (pl. NewPerson.Name) v\u00e1ltozik, akkor a vez\u00e9rl\u0151 k\u00f6t\u00f6tt tulajdons\u00e1ga (pl. TextBox.Text) ezzel szinkronban marad.
                                • Ha a c\u00e9l (vez\u00e9rl\u0151) tulajdons\u00e1g v\u00e1ltozik (pl. TextBox.Text), akkor az forr\u00e1stulajdons\u00e1g (pl. NewPerson.Name) ezzel szinkronban marad.
                                "},{"location":"labor/3-felhasznaloi-felulet/#listak","title":"List\u00e1k","text":"

                                A k\u00f6vetkez\u0151kben a list\u00e1s adatk\u00f6t\u00e9s alkalmaz\u00e1s\u00e1t fogjuk gyakorolni. Vegy\u00fck fel a Person-\u00f6k list\u00e1j\u00e1t a n\u00e9zet\u00fcnk code-behind f\u00e1jlj\u00e1ba, a konstruktor elej\u00e9n pedig adjunk neki kezd\u0151\u00e9rt\u00e9ket.

                                public List<Person> People { get; set; }\n\npublic MainWindow()\n{\n    InitializeComponent();\n\n    NewPerson = new Person()\n    {\n        Name = \"Eric Cartman\",\n        Age = 8\n    };\n\n    People = new List<Person>()\n    {\n      new Person() { Name = \"Peter Griffin\", Age = 40 },\n      new Person() { Name = \"Homer Simpson\", Age = 42 },\n    };\n}\n

                                Adatk\u00f6t\u00e9ssel \u00e1ll\u00edtsuk be a ListView vez\u00e9rl\u0151 ItemsSource tulajdons\u00e1g\u00e1n kereszt\u00fcl, milyen adatforr\u00e1sb\u00f3l dolgozzon.

                                <ListView Grid.Row=\"3\" Grid.ColumnSpan=\"2\" ItemsSource=\"{x:Bind People}\"/>\n

                                Pr\u00f3b\u00e1ljuk ki!

                                L\u00e1tjuk, hogy megjelent k\u00e9t elem a list\u00e1ban. Persze nem az van ki\u00edrva, amit mi szeretn\u00e9nk, de ezen k\u00f6nnyen seg\u00edthet\u00fcnk. Alap\u00e9rtelmezetten ugyanis a ListView ToString()-et h\u00edv a listaelemeken, ami ha nem defini\u00e1ljuk fel\u00fcl, akkor az oszt\u00e1ly t\u00edpus\u00e1nak FullName tulajdons\u00e1ga (vagyis a t\u00edpus neve).

                                \u00c1ll\u00edtsunk be a ListView-unk ItemTemplate tulajdons\u00e1g\u00e1t (a m\u00e1r j\u00f3l ismert property element syntax-szal), mely a listaelem megjelen\u00e9s\u00e9t adja meg egy sablon seg\u00edts\u00e9g\u00e9vel: eset\u00fcnkben legyen ez egycell\u00e1s Grid, ahol a TextBlock-ok a Person tulajdons\u00e1gait jelen\u00edtik meg, a nevet balra, az \u00e9letkort jobbra igaz\u00edtva.

                                <ListView Grid.Row=\"3\" Grid.ColumnSpan=\"2\" ItemsSource=\"{x:Bind People}\">\n    <ListView.ItemTemplate>\n        <DataTemplate x:DataType=\"model:Person\">\n            <Grid>\n                <TextBlock Text=\"{x:Bind Name}\" />\n                <TextBlock Text=\"{x:Bind Age}\" HorizontalAlignment=\"Right\" />\n            </Grid>\n        </DataTemplate>\n    </ListView.ItemTemplate>\n</ListView>\n

                                A DataTemplate egy olyan fel\u00fcletsablon, melyet a ListView (he megadjuk az ItemTemplate tulajdons\u00e1g\u00e1nak) minden elem\u00e9re alkalmazni fog a megjelen\u00edt\u00e9s sor\u00e1n.

                                Mivel az x:Bind ford\u00edt\u00e1s idej\u0171 adatk\u00f6t\u00e9s, ez\u00e9rt az adatok t\u00edpus\u00e1t is meg kell adnunk az adatsablonban az x:DataType attrib\u00fatummal. A fenti p\u00e9ld\u00e1ban a model:Person-t adtuk meg, vagyis azt szeretn\u00e9nk, hogy a model prefix a k\u00f3dunk HelloXaml.Models n\u00e9vter\u00e9re k\u00e9pz\u0151dj\u00f6n le (hiszen ebben van a Person oszt\u00e1ly). Ehhez a XAML f\u00e1jlunk elej\u00e9n a Window tag attrib\u00fatumaihoz fel kell vegy\u00fck a k\u00f6vetkez\u0151 n\u00e9vt\u00e9r deklar\u00e1ci\u00f3t is: xmlns:model=\"using:HelloXaml.Models\" (ezt k\u00f6vet\u0151en a model prefix haszn\u00e1lhat\u00f3 lesz). Ezt megtehetj\u00fck k\u00e9zzel, vagy a Visual Studio seg\u00edts\u00e9g\u00e9vel is: csak kattintsunk bele az al\u00e1h\u00fazott (hib\u00e1snak megjel\u00f6lt) model:Personsz\u00f6vegbe, majd kattintsuk a sor elej\u00e9n megjelen\u0151 l\u00e1mp\u00e1csk\u00e1n (vagy Ctrl + . billenty\u0171kombin\u00e1ci\u00f3), \u00e9s v\u00e1lasszuk ki a megjelen\u0151 \"Add xmlns using:HelloXaml.Models\" elemet.

                                Pr\u00f3b\u00e1ljuk ki! Most m\u00e1r j\u00f3l jelennek meg a list\u00e1ban az elemek.

                                Az Add gomb hat\u00e1s\u00e1ra rakjuk bele a list\u00e1ba az \u0171rlapon tal\u00e1lhat\u00f3 szem\u00e9ly adataival egy \u00faj Person m\u00e1solat\u00e1t, majd t\u00f6r\u00f6lj\u00fck ki az \u0171rlap adatait a NewPerson objektumunkban.

                                Ehhez vezess\u00fcnk be egy Click esem\u00e9nykezel\u0151t az Add gombunkra:

                                <Button ... Click=\"AddButton_Click\">\n
                                private void AddButton_Click(object sender, RoutedEventArgs e)\n{\n    People.Add(new Person()\n    { \n        Name = NewPerson.Name,\n        Age = NewPerson.Age,\n    });\n\n    NewPerson.Name = string.Empty;\n    NewPerson.Age = 0;\n}\n

                                Nem jelenik meg a list\u00e1ban az \u00faj elem, mert a ListView nem \u00e9rtes\u00fcl arr\u00f3l, hogy \u00faj elem ker\u00fclt a list\u00e1ba. Ezt k\u00f6nnyen orvosolhatjuk: a List<Persont>-t cser\u00e9lj\u00fck le ObservableCollection<Person>-re:

                                public ObservableCollection<Person> People { get; set; }\n

                                ObservableCollection<T>

                                Fontos, hogy itt nem maga a People tulajdons\u00e1g \u00e9rt\u00e9ke v\u00e1ltozott, hanem a List<Person> objektum tartalma, ez\u00e9rt nem az INotifyPropertyChanged interf\u00e9sz a megold\u00e1s, hanem az INotifyCollectionChanged interf\u00e9sz, melyet az ObservableCollection implement\u00e1l.

                                Teh\u00e1t m\u00e1r k\u00e9t v\u00e1ltoz\u00e1skezel\u00e9st t\u00e1mogat\u00f3 interf\u00e9szt ismer\u00fcnk \u00e9s haszn\u00e1lunk, melyek az adatk\u00f6t\u00e9st t\u00e1mogatj\u00e1k: INotifyPropertyChanged \u00e9s INotifyCollectionChanged.

                                "},{"location":"labor/3-felhasznaloi-felulet/#kitekintes-klasszikus-binding","title":"Kitekint\u00e9s: Klasszikus Binding","text":"

                                Az adatk\u00f6t\u00e9snek a klasszikus form\u00e1j\u00e1t a Binding markup extension jelenti.

                                A legfontosabb k\u00fcl\u00f6nbs\u00e9gek az x:Bind-hoz k\u00e9pest:

                                • A Binding alap\u00e9rtelmezett m\u00f3dja a OneWay \u00e9s nem a OneTime: teh\u00e1t figyeli a v\u00e1ltoz\u00e1sokat alap\u00e9rtelmezetten, m\u00edg az x:Bind-n\u00e9l ezt explicit meg kell adni.
                                • A Binding alap\u00e9rtelmezetten a DataContext-b\u0151l dolgozik, de lehet\u0151s\u00e9g van \u00e1ll\u00edtani az adatk\u00f6t\u00e9s forr\u00e1s\u00e1t. M\u00edg az x:Bind alap\u00e9rtelmezetten a n\u00e9zet\u00fcnk oszt\u00e1ly\u00e1b\u00f3l (xaml.cs) k\u00f6t.
                                • A Binding fut\u00e1sid\u0151ben dolgozik reflection seg\u00edts\u00e9g\u00e9vel, \u00edgy egyr\u00e9szt nem kapunk ford\u00edt\u00e1s idej\u0171 hib\u00e1kat, ha valamit el\u00edrtunk volna, m\u00e1sr\u00e9szt pedig sok adatk\u00f6t\u00e9s (1000-es nagys\u00e1grend) jelent\u0151sen lass\u00edthatja az alkalmaz\u00e1sunkat.
                                • Az x:Bind ford\u00edt\u00e1s idej\u0171, \u00edgy a ford\u00edt\u00f3 ellen\u0151rzi, hogy a megadott tulajdons\u00e1gok l\u00e9teznek-e. Adatsablonokban nyilatkozni kell a DataTemplate megad\u00e1sa sor\u00e1n, hogy az milyen adatokon fog dolgozni az x:DataType attrib\u00fatummal.
                                • Az x:Bind eset\u00e9ben lehet\u0151s\u00e9g van met\u00f3dusokat is k\u00f6tni, m\u00edg a Binding-n\u00e9l csak konvertereket lehet haszn\u00e1lni. F\u00fcggv\u00e9nyek k\u00f6t\u00e9se eset\u00e9n a v\u00e1ltoz\u00e1s\u00e9rtes\u00edt\u00e9s a param\u00e9terek v\u00e1ltoz\u00e1s\u00e1ra is m\u0171k\u00f6dik.

                                Aj\u00e1nl\u00e1s

                                \u00d6k\u00f6lszab\u00e1lyk\u00e9nt elmondhat\u00f3, hogy pr\u00f3b\u00e1ljunk prefer\u00e1ltan x:Bind-ot haszn\u00e1lni, mert gyorsabb, \u00e9s ford\u00edt\u00e1s idej\u0171 hib\u00e1kat kapunk, viszont ha valami\u00e9rt probl\u00e9m\u00e1ba \u00fctk\u00f6zn\u00e9nk az x:Bind-dal, akkor Binding-ra \u00e9rdemes \u00e1tt\u00e9rni.

                                "},{"location":"labor/3-felhasznaloi-felulet/index_ger/","title":"3. Entwurf der Benutzeroberfl\u00e4che","text":""},{"location":"labor/3-felhasznaloi-felulet/index_ger/#das-ziel-der-ubung","title":"Das Ziel der \u00dcbung","text":"

                                Das Ziel der \u00dcbung ist, die Grundlagen der Entwicklung von Thick-Client-Anwendungen unter Verwendung der deklarativen XAML-Oberfl\u00e4chebeschreibungstechnologie zu erlernen. Die hier gelernten Grundlagen gelten f\u00fcr alle XAML-Dialekte (WinUI, WPF, UWP, Xamarin.Forms, MAUI) oder k\u00f6nnen auf sehr \u00e4hnliche Weise angewendet werden, aber wir werden XAML in der heutigen \u00dcbung speziell \u00fcber das WinAppSDK / WinUI 3-Framework verwenden.

                                "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#voraussetzungen","title":"Voraussetzungen","text":"

                                Die f\u00fcr die Durchf\u00fchrung des Labors ben\u00f6tigten Werkzeuge:

                                • Betriebssystem Windows 10 oder Windows 11 (Linux und macOS nicht geeignet)
                                • Visual Studio 2022
                                • Windows Desktop Entwicklung Workload

                                "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#losung","title":"L\u00f6sung","text":"Laden Sie die fertige L\u00f6sung herunter

                                Es ist wichtig, dass Sie sich w\u00e4hrend des Praktikums an die Anleitung halten. Es ist verboten (und sinnlos), die fertige L\u00f6sung herunterzuladen. Allerdings kann es bei der anschlie\u00dfenden Selbstein\u00fcbung n\u00fctzlich sein, die fertige L\u00f6sung zu \u00fcberpr\u00fcfen, daher stellen wir sie zur Verf\u00fcgung.

                                Die L\u00f6sung ist auf GitHub im megoldas-Zweig verf\u00fcgbar. Der einfachste Weg, es herunterzuladen, ist, mit dem git clone-Befehl von der Kommandozeile aus zu klonen:

                                git clone https://github.com/bmeviauab00/lab-xaml-kiindulo -b megoldas

                                Sie m\u00fcssen Git auf Ihrem Rechner installiert haben, weitere Informationen hier.

                                "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#ursprungliches-projekt","title":"Urspr\u00fcngliches Projekt","text":"

                                In der ersten Aufgabe werden wir die Umgebung einrichten, in der wir die Funktionalit\u00e4t der XAML-Sprache und des WinUI-Frameworks untersuchen werden. Das anf\u00e4ngliche Projekt k\u00f6nnte mit Visual Studio erstellt werden (WinUI 3 Projekt, Blank App, Packaged (WinUI 3 in Desktop) type), aber um den Ablauf der \u00dcbung zu vereinfachen, werden wir das vorgefertigte Projekt verwenden.

                                Wir k\u00f6nnen das Projekt auf unseren Rechner klonen, mit dem folgenden Befehl:

                                git clone https://github.com/bmeviauab00/lab-xaml-kiindulo.git\n

                                \u00d6ffnen wir HelloXaml.sln.

                                Schauen wir uns an, welche Dateien in dem Projekt enthalten sind:

                                • App
                                  • Zwei Dateien App.xaml und App.xaml.cs(sp\u00e4ter zu kl\u00e4rende zwei Dateien geh\u00f6ren dazu)
                                  • Eintrittspunkt in die Anwendung: OnLaunched \u00fcberschriebene Method in App.xaml.cs
                                  • In unserem Fall initialisieren wir das einzige Fenster der Anwendung hier MainWindow
                                • MainWindow
                                  • Zu dem Hauptfenster unserer Anwendung geh\u00f6rende .xaml und .xaml.cs Dateien.
                                Zus\u00e4tzliche L\u00f6sungselemente

                                Die urspr\u00fcngliche VS-L\u00f6sung enth\u00e4lt auch die folgenden Elemente:

                                • Dependencies
                                  • Frameworks
                                    • Microsoft.AspNetCore.App: .NET SDK-Metapaket (verweist auf Microsoft .NET und SDK-Basispakete)
                                    • Windows-spezifisches .NET SDK
                                  • Packages
                                    • Windows SDK Build Tools
                                    • WindowsAppSDK
                                • Assets
                                  • Anwendungslogos
                                • app.manifest, Package.appxmanifest
                                  • Eine XML-Datei mit den Metadaten der Anwendung, in der wir, unter anderem, Logos angeben oder, wie bei Android, den Zugriff auf sicherheitskritische Systemressourcen anfordern k\u00f6nnen.

                                Starten wir die Anwendung!

                                "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#xaml-einfuhrung","title":"XAML-Einf\u00fchrung","text":"

                                Die Schnittstelle wird in einer XML-basierten Beschreibungssprache, XAML (ausgesprochen: zem\u00f6l), beschrieben.

                                Grafische Designeroberfl\u00e4che

                                Bei einigen XAML-Dialekten (z.B.: WPF) steht auch ein grafisches Designer-Tool f\u00fcr die Gestaltung der Oberfl\u00e4che zur Verf\u00fcgung, das jedoch in der Regel eine weniger effiziente XAML-Beschreibung erzeugt. Dar\u00fcber hinaus unterst\u00fctzt Visual Studio bereits Hot Reload f\u00fcr XAML, so dass die Anwendung w\u00e4hrend der Bearbeitung der XAML nicht angehalten werden muss und die \u00c4nderungen sofort in der laufenden Anwendung sichtbar sind. Daher gibt es f\u00fcr WinUI keine Designer-Unterst\u00fctzung mehr in Visual Studio. Die Erfahrung hat gezeigt, dass es Grenzen gibt, wobei \"gr\u00f6\u00dfere\" \u00c4nderungen einen Neustart der Anwendung erfordern.

                                "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#grundlagen-der-xaml-sprache","title":"Grundlagen der XAML-Sprache","text":"

                                Die XAML-Sprache:

                                • Sprache f\u00fcr Objektinstanziierung
                                • Standard-XML
                                • XML-Elemente/Tags: instanziieren Objekte, deren Klassen Standard-.NET-Klassen sind
                                • XML-Attribute: Eigenschaften (dependency properties) werden festgelegt
                                • Deklarativ

                                Schauen wir uns die von der Projektvorlage generierte XAML (MainWindow.xaml) an. Wir k\u00f6nnen sehen, dass f\u00fcr jedes Steuerelement in der XAML ein XML-Element/Tag erstellt wurde. Und die Eigenschaften der Steuerelementen werden auf die Tags der Steuerelementen gesetzt. Z.B. HorizontalAlignment: Ausrichtung innerhalb eines Containers (in unserem Fall Fenster). Steuerelemente k\u00f6nnen andere Steuerelemente enthalten, wodurch ein Baum von Steuerelementen entsteht.

                                Schauen wir uns MainWindow.xamlgenauer an:

                                • Namensr\u00e4ume auf dem Root-Tag: definieren, welche Tags und Attribute in XML verwendet werden k\u00f6nnen
                                  • Standardnamensraum: Namensraum der XAML-Elemente/Steuerelemente (z. B. Button, TextBox usw.)
                                  • x Namensraum: XAML-Parser-Namensraum (z. B.: x:Class, x:Name)
                                  • Andere beliebige Namespaces k\u00f6nnen referenziert werden
                                • Window Wurzelelement:
                                  • Auf der Grundlage unseres Fensters/unserer Seite erstellen wir eine .NET-Klasse, die von der Klasse Window abgeleitet ist.
                                  • Der Name unserer abgeleiteten Klasse wird durch das Attribut x:Class definiert: Auf der Grundlage von x:Class=\"HelloXaml.MainWindow\" wird eine Klasse namens MainWindow im Namensraum HelloXaml erstellt.
                                  • Dies ist eine Teilklasse, die \"andere H\u00e4lfte\" der Klasse befindet sich in der Code-Behind-Datei (MainWindow.xaml.cs) f\u00fcr das Fenster/die Seite. Siehe n\u00e4chster Punkt.
                                • Code-Behind-Datei (MainWindow.xaml.cs):
                                  • Die andere \"H\u00e4lfte\" unserer partiellen Klasse: \u00dcberpr\u00fcfen wir, ob der Name und der Namensraum der Klasse hier derselbe ist wie in der .xaml-Datei (partielle Klasse!).
                                  • Hier werden u.a. Ereignishandler und Hilfsfunktionen untergebracht.
                                  • this.InitializeComponent(); muss immer im Konstruktor aufgerufen werden, er liest die XAML zur Laufzeit ein, er initialisiert den Inhalt des Fensters/der Seite (d.h. die in der XAML-Datei angegebenen Controls mit den dort definierten Eigenschaften).

                                L\u00f6schen wir den Inhalt von Window und den Ereignishandler aus der Code-Behind-Datei (FunktionmyButton_Click ). Jetzt werden wir XAML manuell schreiben, um die Oberfl\u00e4che0 zu erstellen. F\u00fcgen wir ein Gridzu Windowhinzu, mit dem wir sp\u00e4ter ein Tabellenlayout erstellen k\u00f6nnen:

                                <?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Window\n    x:Class=\"HelloXaml.MainWindow\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:local=\"using:HelloXaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    mc:Ignorable=\"d\">\n\n    <Grid>\n\n    </Grid>\n</Window>\n

                                F\u00fchren wir die Anwendung aus (z. B. mit F5 ). Die Grid f\u00fcllt das gesamte Fenster aus, ihre Farbe ist dieselbe wie die Hintergrundfarbe des Fensters, so dass man sie mit dem Auge nicht mehr unterscheiden kann.

                                In den folgenden Aufgaben lassen wir die Anwendung laufen, damit wir die \u00c4nderungen, die wir an der Schnittstelle vorgenommen haben, sofort sehen k\u00f6nnen.

                                Hot Reload Limitations

                                Beachten wir die Einschr\u00e4nkungen von Hot Reload: Wenn eine \u00c4nderung nicht in der laufenden Anwendung erscheinen soll, m\u00fcssen wir die Anwendung neu starten!

                                "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#objektinstanzen-und-ihre-eigenschaften","title":"Objektinstanzen und ihre Eigenschaften","text":"

                                Sehen wir uns an, wie wir Objekte auf der Grundlage von XAML instanziieren und die Eigenschaften dieser Objekte festlegen k\u00f6nnen.

                                F\u00fcgen wir Buttoninnerhalb der Grid hinzu. Die Eigenschaft Content wird verwendet, um den Text des Knopfs, genauer gesagt seinen Inhalt, anzugeben.

                                <Button Content=\"Hello WinUI App!\"/>\n

                                Dadurch wird zur Laufzeit ein Objekt Button an der Stelle erzeugt, an der es deklariert ist, und die Eigenschaft Content auf \"Hello WinUI App!\" gesetzt. Dies h\u00e4tte in C# in der Code-Behind-Datei wie folgt geschehen k\u00f6nnen (was jedoch zu weniger lesbarem Code f\u00fchren w\u00fcrde):

                                // z.B. am Ende des Konstruktors geschrieben:\n\nButton b = new Button();\nb.Content = \"Hello WinUI App!\";\nrootGrid.Children.Add(b); \n// F\u00fcr die vorherige Zeile sollte das Attribut x:Name=\"rootGrid\" des Grids in der XAML-Datei angegeben\n// werden, um das Grid mit dem Namen rootGrid aus dem Code-Behind-Datai zu erreichen.\n

                                Dieses Beispiel verdeutlicht sehr gut, dass XAML im Grunde eine Objektinstanziirungs-Sprache ist und das Setzen von Eigenschaften von Objekten unterst\u00fctzt.

                                Die Eigenschaft Content ist eine Besonderheit: Sie kann nicht nur in einem XML-Attribut, sondern auch innerhalb eines Tags (XML-Element) angegeben werden.

                                <Button>Hello WinUI App!</Button>\n

                                Sogar! Wir k\u00f6nnen nicht nur eine Beschriftung auf die Taste setzen, sondern auch jedes andere Element, das wir m\u00f6chten. F\u00fcgen wir zum Beispiel einen roten Kreis ein. Der Kreis ist 10 Pixel breit, 10 Pixel hoch und die Farbe (Fill) ist rot.

                                <Button>\n    <Ellipse Width=\"10\" Height=\"10\" Fill=\"Red\" />\n</Button>\n

                                Dies konnte mit fr\u00fcheren .NET UI-Technologien (z. B. Windows Forms) nicht so einfach erreichen.

                                Neben dem roten Kreis steht nun Record (um den Sinn der roten Kreistaste zu verdeutlichen). Die Taste kann nur ein untergeordnetes Element haben, daher m\u00fcssen wir den Kreis und den Text (TextBlock) in ein Layout-Steuerelement (z. B. ein StackPanel) einf\u00fcgen. F\u00fcgen wir au\u00dferdem einen linken Rand zu TextBlockhinzu, damit sie sich nicht ber\u00fchren.

                                <Button>\n    <StackPanel Orientation=\"Horizontal\">\n        <Ellipse Width=\"10\" Height=\"10\" Fill=\"Red\" />\n        <TextBlock Text=\"Record\" Margin=\"10,0,0,0\" />\n    </StackPanel>\n</Button>\n

                                StackPanel ist ein einfaches Layout-Panel f\u00fcr die Anordnung von Steuerelementen: Die darin enthaltenen Steuerelemente werden nebeneinander angeordnet, wenn Horizental Orientation angegeben ist, und untereinander, wenn Vertical Orientation angegeben ist. In unserem Beispiel legen wir also einfach die beiden Steuerelemente nebeneinander.

                                Das Ergebnis ist:

                                XAML-Vektorgrafik-Controller

                                Es ist wichtig zu beachten, dass die meisten XAML-Controller Vektorgrafiken sind. Diese Taste sieht bei jeder DPI oder Vergr\u00f6\u00dferung genauso scharf aus (keine \"Verpixelung\").

                                Es gibt drei Optionen f\u00fcr die Angabe von Eigenschaften von XAML-instanziierten Steuerelementen (von denen wir einige bereits verwendet haben):

                                • Property ATTRIBUTE syntax
                                • Property ELEMENT syntax
                                • Property CONTENT syntax

                                Schauen wir uns diese Optionen nun genauer an:

                                1. Property ATTRIBUTE syntax. Wir haben sie bereits in unserem allerersten Beispiel verwendet:

                                  <Button Content=\"Hello WinUI App!\"/>\n

                                  Der Name kommt daher, dass die Eigenschaft als XML-Attribut angegeben wird. Da XML-Attribute nur Strings sein k\u00f6nnen, k\u00f6nnen sie nur f\u00fcr den Zugriff auf einfache Zahlen-, String- usw. Werte in Stringform oder auf Mitgliedsvariablen und Ereignishandler, die in einer Code-Behind-Datei definiert sind, verwendet werden. Wir k\u00f6nnen aber auch \"komplexe\" Objekte mit Hilfe von Typkonvertern angeben. Wir werden nicht viel dar\u00fcber reden, aber wir benutzen die eingebauten Typkonverter sehr oft, praktisch \"instinktiv\". Beispiel:

                                  F\u00fcgen wir eine Hintergrundfarbe zu Gridhinzu:

                                  <Grid Background=\"Azure\">\n

                                  Oder wir k\u00f6nnen es in Hexadezimal angeben:

                                  <Grid Background=\"#FFF0FFFF\">\n

                                  Der Rand (Margin) ist ebenfalls ein zusammengesetzter Wert, wobei der zugeh\u00f6rige Typkonverter durch ein Komma (oder ein Leerzeichen) getrennt ist und Werte f\u00fcr die vier Seiten (links, oben, rechts, unten) erwartet werden. Wir haben es bereits f\u00fcr unseren TextBlock mit Record verwendet. Hinweis: wir k\u00f6nnen eine einzige Zahl f\u00fcr den Rand angeben, die dann f\u00fcr alle vier Seiten gleich ist.

                                2. Property ELEMENT syntax. Es erm\u00f6glicht uns, eine Eigenschaft auf ein komplex instanziiertes/parametrisiertes Objekt zu setzen, ohne Typkonverter zu verwenden. Schauen wir uns das anhand eines Beispiels an.

                                  • Im obigen Beispiel wird durch die Einstellung der Eigenschaft Background auf Azure tats\u00e4chlich ein SolidColorBrushmit der Farbe hellblau erstellt. Dies kann ohne Verwendung eines Typkonverters wie folgt angegeben werden:
                                  <Grid>\n    <Grid.Background>\n        <SolidColorBrush Color=\"Azure\" />\n    </Grid.Background>\n    ...\n

                                  Damit wird die Eigenschaft Grid Background auf die angegebene SolidColorBrushgesetzt. Dabei handelt es sich um die so genannte \"property element syntax\"-basierte Eigenschafts\u00fcbermittlung.

                                  • Der Name kommt daher, dass die Eigenschaft in Form eines XML-Elements (und nicht eines XML-Attributs) angegeben wird.
                                  • Hier erstellt <Grid.Background> keine Objektinstanz, sondern setzt den Wert der angegebenen Eigenschaft (in diesem Fall Background) auf die entsprechende Objektinstanz (in diesem Fall SolidColorBrush). Sie erkennen dies an dem Punkt im Namen des XML-Elements.
                                  • Dadurch erh\u00e4lt man eine \"expansivere\" Formeigenschaft, jedoch mit voller Flexibilit\u00e4t.

                                  Ersetzen wir SolidColorBrushdurch eine Brush mit Farb\u00fcbergang (LinearGradientBrush):

                                  <Grid>\n    <Grid.Background>\n        <LinearGradientBrush>\n            <LinearGradientBrush.GradientStops>\n                <GradientStop Color=\"Black\" Offset=\"0\" />\n                <GradientStop Color=\"White\" Offset=\"1\" />\n            </LinearGradientBrush.GradientStops>\n        </LinearGradientBrush>\n    </Grid.Background>\n    ...\n

                                  F\u00fcr LinearGradientBrush gibt es keinen Typkonverter, er kann nur mit der Elementsyntax angegeben werden!

                                  Es ist eine Frage, wie ist es m\u00f6glich, dass die Background Eigenschaft des Grid Steuerelements sowohl SolidColorBrush und LinearGradientBrush Pinsel haben k\u00f6nnte? Die Antwort ist ganz einfach: Polymorphismus macht dies m\u00f6glich:

                                  • Die Klassen SolidColorBrush und LinearGradientBrush sind beide aus der eingebauten Klasse Brush abgeleitet.
                                  • Die Eigenschaft Background ist eine Eigenschaft des Typs Brush, so dass aufgrund der Polymorphie jeder Nachkomme dieser Eigenschaft verwendet werden kann.
                                  Note
                                  • Wenn in den obigen Beispielen Color (Farbe) angegeben ist, z. B. Color=\"Azure\", erstellt der Typkonverter auch eine blaue Color -Instanz von Azure. So w\u00fcrde unser vorheriges Beispiel, das auf SolidColorBrush basiert, vollst\u00e4ndig erkl\u00e4rt aussehen:
                                    <Grid>\n    <Grid.Background>\n        <SolidColorBrush>\n            <SolidColorBrush.Color>\n                <Color>#FFF0FFFF</Color>\n            </SolidColorBrush.Color>\n        </SolidColorBrush>\n    </Grid.Background>\n    ...\n
                                  • Wo unterst\u00fctzt, lohnt es sich, die Vorteile von Typkonvertern zu nutzen und die Attributsyntax zu verwenden, um eine ausf\u00fchrliche XAML-Beschreibung zu vermeiden.
                                  • Bei Werttypen (struct), wie z. B. Color, muss der Wert bei der Instanziierung des Objekts (\"Konstruktorzeit\") angegeben werden, d. h. hier k\u00f6nnen wir die Eigenschaften nicht separat festlegen, sondern m\u00fcssen sich auf die Typkonverter verlassen.
                                3. Property CONTENT syntax. Um das besser zu verstehen, schauen wir uns die drei M\u00f6glichkeiten an, die Content Eigenschaft einer Taste auf einen Text zu setzen (wir m\u00fcssen das nicht im Labor machen, schauen wir es sich einfach zusammen in diesem Leitfaden an):

                                  • Property attribute syntax (bereits verwendet):
                                    <Button Content=\"Hello WinUI App!\"/>\n
                                  • Richten wir sie mit der property element syntax ein, die wir im vorigen Abschnitt gelernt haben:
                                    <Button>\n    <Button.Content>\n    Hello WinUI App!\n    </Button.Content>\n</Button>\n
                                  • Jedes Steuerelement kann f\u00fcr sich selbst eine spezielle Eigenschaft \"Content\" definieren, f\u00fcr die die \u00f6ffnenden und schlie\u00dfenden Tags nicht gedruckt werden m\u00fcssen. Das hei\u00dft, die \u00f6ffnenden und schlie\u00dfenden Tags <Button.Content>, die im vorigen Beispiel verwendet wurden, k\u00f6nnen f\u00fcr diese eine Eigenschaft weggelassen werden:
                                    <Button>\n    Hello WinUI App!\n</Button>\n
                                    Oder in einer einzigen Zeile geschrieben werden:
                                    <Button>Hello WinUI App!</Button>\n
                                    Dies ist bekannt, wir haben es in unserem Einf\u00fchrungsbeispiel gesehen: dies ist die so genannte Property CONTENT syntax-basierte Eigenschaftsdeklaration. Der Name deutet auch darauf hin, dass diese eine Eigenschaft im \"Content\"-Teil des Steuerelements angegeben werden kann. Nicht alle Steuerelemente haben Content als Namen f\u00fcr diese besondere Eigenschaft: StackPanelund Gridhaben Children als Namen. Erinnern wir uns, oder schauen wir uns den Code an: wir haben diese bereits verwendet: allerdings haben wir die XML-Elemente StackPanel.Children oder Grid.Children nicht ausgeschrieben, wenn wir das Innere von StackPanel oder Grid angegeben haben (aber wir h\u00e4tten es tun k\u00f6nnen!)

                                \u00c4ndern wir den Hintergrund von Grid wieder in etwas sympathisch Einfaches, oder l\u00f6schen wir die Hintergrundfarbe.

                                "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#ereignis-management","title":"Ereignis-Management","text":"

                                XAML-Anwendungen sind ereignisgesteuerte Anwendungen. Alle Benutzerinteraktionen werden durch Ereignisse gemeldet, die zur Aktualisierung der Oberfl\u00e4che verwendet werden k\u00f6nnen.

                                Jetzt geht es um das Klicken auf die Taste.

                                Als vorbereitenden Schritt geben wir unserem TextBlock Steuerelement einen Namen, damit wir sp\u00e4ter in der Code-Behind-Datei darauf verweisen k\u00f6nnen:

                                <TextBlock x:Name=\"recordTextBlock\" Text=\"Record\" Margin=\"10,0,0,0\" />\n

                                Die x:Name ist f\u00fcr den XAML-Parser und erstellt eine Member-Variable in unserer Klasse mit diesem Namen, die den Verweis auf das angegebene Steuerelement enth\u00e4lt. Denken wir dar\u00fcber nach: da es sich um eine Membervariable ist, k\u00f6nnen wir es in der Code-Behind-Datei erreichen, da es sich einen \"partiellen Teil\" der gleichen Klasse ist!

                                Benannte Steuerelemente

                                Benennen wir keine Steuerelemente, auf die wir nicht verweisen wollen. (Wir sollten uns angew\u00f6hnen, nur auf das zu verweisen, was wir wirklich brauchen. Auch die Datenverkn\u00fcpfung ist hilfreich)

                                Eine Ausnahme: Wenn wir eine sehr komplexe Kontrollhierarchie haben, k\u00f6nnen Namen helfen, den Code transparenter zu machen, da sie im Live Visual Tree-Fenster erscheinen und die generierten Ereignishandlernamen ebenfalls daran ausgerichtet sind.

                                Behandeln wir das Ereignis Click der Taste und probieren wir dann den Code aus.

                                MainWindow.xaml
                                <Button Click=\"RecordButton_Click\">\n
                                MainWindow.xaml.cs
                                private void RecordButton_Click(object sender, RoutedEventArgs e)\n{\n    recordTextBlock.Text = \"Recording...\";\n}\n

                                Erstellen von Ereignishandlern

                                Wenn wir f\u00fcr die Ereignishandler nicht New Event Handler w\u00e4hlen, sondern manuell den gew\u00fcnschten Namen eingeben und F12dr\u00fccken oder Rechtsklick / Go to Definition w\u00e4hlen, wird der Ereignishandler in der Code-Behind-Datei generiert.

                                Der Ereignishandler hat zwei Parameter: das sendende Objekt (object sender) und den Parameter, der die Parameter/Bedingungen des Ereignisses enth\u00e4lt (EventArgs e). Schauen wir uns diese im Detail an:

                                • object sender: Der Ausl\u00f6ser des Ereignisses. In diesem Fall handelt es sich um die Taste selbst, die unter Buttonzu finden ist. Wir verwenden diesen Parameter nur selten.
                                • Der zweite Parameter ist immer vom Typ EventArgs oder dessen Nachkomme (je nach Art des Ereignisses), in dem die Parameter des Ereignisses zur\u00fcckgegeben werden. F\u00fcr das Ereignis Click ist dies der Typ RoutedEventArgs.

                                Ereignisargumente

                                Einige Ereignisargumenttypen:

                                • routedEventArgs\": wird z. B. im Falle des Ereignisses \"Click\" verwendet, wie in unserem Beispiel. In der Eigenschaft \"OriginalSource\" wird das Steuerelement angegeben, in dem das Ereignis zuerst ausgel\u00f6st wurde.
                                  • Beachten wir, dass es im obigen Fall die Taste selbst ist, aber wenn wir ein Mausklick-Ereignis (nicht Click, sondern PointerPressed) auf z.B. StackPanel behandeln w\u00fcrden, k\u00f6nnten wir eines seiner Kindelemente erhalten, wenn es angeklickt wird.
                                • keyRoutedEventArgs\": z.B. f\u00fcr ein \"KeyDown\"-Ereignis (Tastendruck), erhalten wir die gedr\u00fcckte Taste darin.
                                • pointerRoutedEventArgs\": wird z.B. f\u00fcr das \"PointerPressed\"-Ereignis (Maus-/Stiftdruck) verwendet und kann u.a. dazu verwendet werden, die Koordinaten des Klicks zu ermitteln.

                                Die XAML-Ereignishandler basieren vollst\u00e4ndig auf C#-Ereignissen (Schl\u00fcsselwortevent, siehe vorherige \u00dcbung):

                                Z.B. eine

                                <Button Click=\"RecordButton_Click\">\n

                                ist daf\u00fcr ausgebildet:

                                Button b = new Button();\nb.Click += RecordButton_Click;\n
                                "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#layout-gestaltung","title":"Layout, Gestaltung","text":"

                                Die Anordnung der Steuerelemente wird durch zwei Faktoren bestimmt:

                                1. Layout-Steuerelemente (panel) und ihre angef\u00fcgte Eigenschaften (attached property)
                                2. Allgemeine Positionseigenschaften innerhalb des \u00fcbergeordneten Steuerelements (z. B. Rand, vertikale oder horizontale Ausrichtung)

                                Eingebaute Layout-Steuerelemente zum Beispiel:

                                • StackPanel: Elemente untereinander oder nebeneinander
                                • Grid: Wir k\u00f6nnen ein Raster festlegen, an dem sich die Elemente ausrichten
                                • Canvas: Wir positionieren die Elemente explizit durch Angabe ihrer X- und Y-Koordinaten
                                • RelativePanel: Die Beziehung der Elemente zueinander kann durch Nebenbedingungen definiert werden

                                Versuchen wir es mit Grid(wir verwenden dies normalerweise, um das grundlegende Layout unseres Fensters/unserer Seite einzurichten). Wir werden eine Oberfl\u00e4che erstellen, \u00fcber die man Personen zu einer Liste hinzuf\u00fcgen kann, indem man ihren Namen und ihr Alter eingeben kann. Unser Ziel ist es, das folgende Layout zu erstellen:

                                Einige wichtige Verhaltensbeschr\u00e4nkungen:

                                • Wenn die Gr\u00f6\u00dfe des Fensters ge\u00e4ndert wird, sollte das Formular eine feste Breite haben und zentriert bleiben.
                                • In der Zeile Alter erh\u00f6ht die Taste + das Alter, die Taste - verringert es.
                                • Die Taste Hinzuf\u00fcgen f\u00fcgt die Person mit den oben angegebenen Daten zur unteren Liste hinzu (die Abbildung zeigt die Daten von zwei Personen in der unteren Liste).

                                Definieren wir die Wurzel Gridals 4 Zeilen und 2 Spalten. Die erste Spalte sollte die Bezeichnungen und die zweite Spalte die Eingabefelder enthalten. Setzen wir unsere vorhandene Taste in Zeile 3 und \u00e4ndern wir ihren Inhalt auf Add, und ersetzen wir den Kreis durch SymbolIcon. Geben wir in Zeile 4 eine Liste ein, die 2 Spalten einnehmen sollte.

                                <Grid x:Name=\"rootGrid\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n    <Grid.ColumnDefinitions>\n        <ColumnDefinition Width=\"Auto\" />\n        <ColumnDefinition Width=\"*\" />\n    </Grid.ColumnDefinitions>\n\n    <TextBlock Grid.Row=\"0\" Grid.Column=\"0\" Text=\"Name\"/>\n    <TextBox Grid.Row=\"0\" Grid.Column=\"1\" />\n    <TextBlock Grid.Row=\"1\" Grid.Column=\"0\" Text=\"Age\"/>\n    <TextBox Grid.Row=\"1\" Grid.Column=\"1\" />\n\n    <Button Grid.Row=\"2\" Grid.Column=\"1\">\n        <StackPanel Orientation=\"Horizontal\">\n            <SymbolIcon Symbol=\"Add\" />\n            <TextBlock Text=\"Add\" Margin=\"5,0,0,0\"/>\n        </StackPanel>\n    </Button>\n\n    <ListView Grid.Row=\"3\" Grid.Column=\"0\" Grid.ColumnSpan=\"2\"/>\n</Grid>\n

                                F\u00fcr die Zeilen- und Spaltendefinitionen k\u00f6nnen wir angeben, ob die Zeile die Gr\u00f6\u00dfe ihres Inhalts einnehmen soll (Auto) oder den verbleibenden Platz ausf\u00fcllen soll (*), oder sogar eine feste Breite in Pixeln (Width Eigenschaft). Wenn es mehrere * in den Definitionen gibt, k\u00f6nnen sie skaliert werden, z.B. * und * haben ein Verh\u00e4ltnis von 1:1, w\u00e4hrend * und 3* ein Verh\u00e4ltnis von 1:3 haben.

                                Die Grid.Row, Grid.Column werden als Attached Properties (angef\u00fcgte Eigneschaften) bezeichnet. Das bedeutet, dass der Controller, auf den sie angewendet wird, diese Eigenschaft nicht besitzt und diese Information nur \"angeh\u00e4ngt\" wird. In unserem Fall sind diese Informationen f\u00fcr Gridwichtig, um Ihre Kinder unterzubringen. Der Standardwert f\u00fcr Grid.Row und Grid.Column ist 0, so dass wir dies gar nicht schreiben sollten.

                                Imperative UI-Beschreibung

                                In anderen UI-Frameworks, in denen die UI imperativ ist, wird dies einfach mit Funktionsparametern gel\u00f6st - z.B.: myPanel.Add(new TextBox(), 0, 1).

                                Die angef\u00fcgte Eigenschaft Grid.ColumnSpan=\"2\" unter ListViewbedarf vielleicht einer Erkl\u00e4rung: ColumnSpan und RowSpan definieren die Anzahl der Spalten und Zeilen, die das Steuerelement \"umspannen\". In unserem Beispiel f\u00fcllt ListView beide Spalten.

                                Probieren wir die Anwendung aus (wenn der Code nicht funktioniert, l\u00f6schen wir den Ereignishandler im Code hinter der Datei RecordButton_Click ).

                                In seinem derzeitigen Zustand f\u00fcllt Grid den gesamten Raum sowohl horizontal als auch vertikal aus. Was ist der Grund daf\u00fcr? Eines der grundlegenden Merkmale des Layouts der Steuerelemente sind ihre Eigenschaften HorizontalAlignment und VerticalAlignment. Diese bestimmen, wo der Controller horizontal und vertikal in dem ihn enthaltenden Container (d. h. dem \u00fcbergeordneten Controller) positioniert werden soll. Die m\u00f6glichen Werte:

                                • VerticalAlignment: Top, Center, Bottom, Stretch(oben, mittig, unten ausgerichtet oder vertikal ausf\u00fcllen)
                                • HorizontalAlignment: Left, Center, Right, Stretch (links-, zentriert-, rechtsb\u00fcndig oder horizontal ausf\u00fcllen)

                                (Hinweis: F\u00fcr Stretch ist es erforderlich, dass die Eigenschaft Height oder Width f\u00fcr den Controller nicht angegeben ist)

                                Unserem Gridwurden die Eigenschaften HorizontalAlignment und VerticalAlignment nicht zugewiesen, so dass sein Wert standardm\u00e4\u00dfig Stretch f\u00fcr das Raster ist, weshalb Grid den Raum im \u00fcbergeordneten Container, dem Fenster, in beide Richtungen f\u00fcllt.

                                Unsere Oberfl\u00e4che sieht nicht so aus, wie wir sie haben wollen, also m\u00fcssen wir sie noch ein wenig optimieren. Die vorzunehmenden \u00c4nderungen:

                                • Die Tabelle muss nicht den ganzen Bildschirm ausf\u00fcllen, sondern sollte horizontal in der Mitte liegen
                                  • HorizontalAlignment=\"Center\"
                                • 300px breit machen
                                  • Width=\"300\"
                                • Halten wir 10px zwischen den Zeilen, 5px zwischen den Spalten und 20px vom Rand des Containers
                                  • RowSpacing=\"5\" ColumnSpacing=\"10\" Margin=\"20\"
                                • Richten wir die Bezeichnungen (TexBlock) vertikal in der Mitte aus
                                  • VerticalAlignment=\"Center\"
                                • Richten wir die Taste nach rechts aus
                                  • HorizontalAlignment=\"Right\"
                                • Machen wir die Liste identifizierbar
                                  • BorderThickness=\"1\" und BorderBrush=\"DarkGray\"
                                <Grid x:Name=\"rootGrid\"\n      Width=\"300\"\n      HorizontalAlignment=\"Center\"\n      Margin=\"20\"\n      RowSpacing=\"5\"\n      ColumnSpacing=\"10\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n    <Grid.ColumnDefinitions>\n        <ColumnDefinition Width=\"Auto\" />\n        <ColumnDefinition Width=\"*\" />\n    </Grid.ColumnDefinitions>\n\n    <TextBlock Grid.Row=\"0\" Grid.Column=\"0\" Text=\"Name\" VerticalAlignment=\"Center\"/>\n    <TextBox Grid.Row=\"0\" Grid.Column=\"1\" x:Name=\"tbName\" />\n    <TextBlock Grid.Row=\"1\" Grid.Column=\"0\" Text=\"Age\" VerticalAlignment=\"Center\"/>\n    <TextBox Grid.Row=\"1\" Grid.Column=\"1\" x:Name=\"tbAge\"/>\n\n    <Button Grid.Row=\"2\" Grid.Column=\"1\" HorizontalAlignment=\"Right\">\n        <StackPanel Orientation=\"Horizontal\">\n            <SymbolIcon Symbol=\"Add\"/>\n            <TextBlock Text=\"Add\" Margin=\"5,0,0,0\" />\n        </StackPanel>\n    </Button>\n\n    <ListView Grid.Row=\"3\"\n              Grid.Column=\"0\"\n              Grid.ColumnSpan=\"2\"\n              BorderThickness=\"1\"\n              BorderBrush=\"DarkGray\"/>\n</Grid>\n

                                Erweitern wir unser Formular um zwei weitere Tasten (\u00b1 Tasten f\u00fcr das Alter, siehe vorheriges animiertes Bildschirmfoto):

                                • -': auf der linken Seite von TextBox
                                • +' auf der rechten Seite vonTextBox

                                Dazu nehmen wir anstatt die Zeile (mit L\u00f6schen)

                                <TextBox Grid.Row=\"1\" Grid.Column=\"1\" x:Name=\"tbAge\"/>\n

                                ein Grid mit 1 Zeile und 3 Spalten :

                                <Grid Grid.Row=\"1\" Grid.Column=\"1\" ColumnSpacing=\"5\">\n    <Grid.ColumnDefinitions>\n        <ColumnDefinition Width=\"Auto\" />\n        <ColumnDefinition Width=\"*\" />\n        <ColumnDefinition Width=\"Auto\" />\n    </Grid.ColumnDefinitions>\n\n    <Button Grid.Row=\"0\" Grid.Column=\"0\" Content=\"-\" />\n    <TextBox Grid.Row=\"0\" Grid.Column=\"1\" x:Name=\"tbAge\" />\n    <Button Grid.Row=\"0\" Grid.Column=\"2\" Content=\"+\" />\n</Grid>\n

                                Verschachtelung mehrerer Layout-Steuerelemente

                                Sie fragen sich vielleicht, warum wir nicht zus\u00e4tzliche Spalten und Zeilen in das externe Grid(durch Anwendung von ColumnSpan auf die vorhandenen Steuerelemente) eingef\u00fcgt haben. Stattdessen folgten wir dem Prinzip der Vereinheitlichung: Die neu eingef\u00fchrten Steuerelemente sind im Wesentlichen ein Element, so dass wir eine transparentere L\u00f6sung erhielten, indem wir sie in ein separates Grid Steuerelement einf\u00fcgten. Die Erweiterung des externen Grid w\u00e4re gerechtfertigt, wenn wir aufgrund von Leistungsproblemen bei der Erstellung von Steuerelementen sparen wollten. In unserem Fall ist dies nicht gerechtfertigt.

                                Wir sind fertig mit dem Aussehen unseres einfachen Formulars.

                                "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#datenverbindung","title":"Datenverbindung","text":""},{"location":"labor/3-felhasznaloi-felulet/index_ger/#binding","title":"Binding","text":"

                                Im n\u00e4chsten Schritt soll es m\u00f6glich sein, die Daten einer Person in das soeben erstellte kleine Formular einzugeben und zu \u00e4ndern. Erstellen wir dazu zun\u00e4chst eine Datenklasse namens Person in einem neu erstellten Ordner Models im Projekt.

                                public class Person\n{\n    public string Name { get; set; }\n    public int Age { get; set; }\n}\n

                                Wir wollen die beiden Eigenschaften hier an die TextBox Steuerelemente binden, also verwenden wir die Datenbindung. F\u00fchren wir in der Code-Behind-Datei unseres Fensters eine Eigenschaft ein, die auf ein Objekt Person verweist, und geben wir ihr im Konstruktor einen Anfangswert:

                                public Person NewPerson { get; set; }\n\npublic MainWindow()\n{\n    InitializeComponent();\n\n    NewPerson = new Person()\n    {\n        Name = \"Eric Cartman\",\n        Age = 8\n    };\n}\n

                                Im n\u00e4chsten Schritt werden die Eigenschaften des oben genannten Objekts NewPerson zu die Text Eigenschaft der geigneten Textfelder gebunden:

                                • die Eigenschaft Name zu die Text Eigenschaft von tbName Textbox
                                • die Eigenschaft Age zu die Text Eigenschaft von tbAge Textbox

                                Wir verwenden Datenverbindung (data binding) daf\u00fcr:

                                Text=\"{x:Bind NewPerson.Name}\"\nText=\"{x:Bind NewPerson.Age}\"\n
                                (f\u00fcgen wir die oben genannten 1-1 Eigenschaftseinstellungen in die Zeilen von tbName und tbAge TextBoxein)

                                Wichtig

                                Bei der Datenverbindung geht es darum, dass anstatt die Eigenschaften (in unserem Fall den Text) der Steuerelemente in der Oberfl\u00e4che von der Code-Behind-Datei aus manuell einstellen, werden die Eigenschaften mit dem Datenverbindungsmechanismus der Plattform zusammengesetzt/verbunden. So k\u00f6nnen wir auch daf\u00fcr sorgen, dass sich bei einer \u00c4nderung einer Eigenschaft die andere automatisch \u00e4ndert!

                                Die Syntax Text=\"{x:Bind}\" wird als Markup Extension bezeichnet: Sie hat eine besondere Bedeutung f\u00fcr den XAML-Prozessor. Dies ist der Hauptgrund, warum wir XAML und nicht einfaches XML verwenden. Es ist auch m\u00f6glich, eine eigene Markup Extension zu erstellen, aber dies ist kein Material des Kurses.

                                Laufen wir die Anwendung! Es ist zu erkennen, dass den Namen und das Alter, die in den Eigenschaften Name und Age des Objekts NewPerson (als Datenquelle) angegeben sind, wegem der Datenverbindung automatisch in die Text Eigenschaften beider TextBox \u00fcbernommen wurden.

                                "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#benachrichtigung-uber-anderungen","title":"Benachrichtigung \u00fcber \u00c4nderungen","text":"

                                Implementieren wir die Click Ereignishandler f\u00fcr die Tasten \u00b1 .

                                <Button Grid.Row=\"1\" Grid.Column=\"0\" Content=\"-\" Click=\"DecreaseButton_Click\"/>\n<!-- ... -->\n<Button Grid.Row=\"1\" Grid.Column=\"2\" Content=\"+\" Click=\"IncreaseButton_Click\"/>\n
                                private void DecreaseButton_Click(object sender, RoutedEventArgs e)\n{\n    NewPerson.Age--;\n}\n\nprivate void IncreaseButton_Click(object sender, RoutedEventArgs e)\n{\n    NewPerson.Age++;\n}\n

                                Aufgrund der Datenverbindung, die im vorherigen Abschnitt eingef\u00fchrt wurde, w\u00fcrden wir erwarten, dass, wenn wir die Eigenschaft Age der Datenquelle NewPerson in den obigen Ereignishandlern \u00e4ndern, unser Steuerelement tbAge Textbox auf unserer Oberfl\u00e4che dies verfolgen w\u00fcrde. Probieren wir es aus! Dies funktioniert noch nicht, da es die Implementierung der Schnittstelle INotifyPropertyChanged erfordert.

                                1. Implementieren wir die Schnittstelle INotifyPropertyChanged in unserer Klasse Person. Wenn wir Daten an diese Klasse binden, abonniert das System das Ereignis PropertyChanged. Durch Ausl\u00f6sen dieses Ereignisses k\u00f6nnen wir die Verbindung benachrichtigen, wenn sich eine Eigenschaft ge\u00e4ndert hat.

                                  public class Person : INotifyPropertyChanged\n{\n    public event PropertyChangedEventHandler PropertyChanged;\n\n    private string name;\n    public string Name\n    {\n        get { return name; }\n        set\n        {\n            if (name != value)\n            {\n                name = value;\n                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));\n            }\n        }\n    }\n\n    private int age;\n    public int Age\n    {\n        get { return age; }\n        set\n        {\n            if (age != value)\n            {\n                age = value;\n                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Age)));\n            }\n        }\n    }\n}\n

                                  Ist der Code zu viel?

                                  In Zukunft k\u00f6nnte diese Logik in einer Klasse von Vorg\u00e4ngern organisiert werden, aber das w\u00fcrde zum MVVM-Muster f\u00fchren, das mit einem sp\u00e4teren Thema verkn\u00fcpft ist. Lassen wir uns also nicht von diesem etwas h\u00e4sslichen Code abschrecken.

                                2. Bei der Datenverbindung schalten wir die \u00c4nderungsbenachrichtigung ein, indem wir sie auf Mode OneWay\u00e4ndern, da der Standardmodus f\u00fcr x:Bind OneTime ist, was eine einmalige Datenbindung darstellt.

                                  Text=\"{x:Bind NewPerson.Age, Mode=OneWay}\"\n

                                Probieren wir es aus! Die Ereignishandler \u00e4ndern die Datenquelle (NewPerson), die nun auch die Oberfl\u00e4che aufgrund der richtig vorbereiteten Datenverbindung \u00e4ndert.

                                "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#ruckwartige-datenbindung-vom-controller-zur-datenquelle","title":"R\u00fcckw\u00e4rtige Datenbindung (vom Controller zur Datenquelle)","text":"

                                Wie Age sollte auch die Datenbindung f\u00fcr die Eigenschaft Name auf einseitig eingestellt werden:

                                Text=\"{x:Bind NewPerson.Name, Mode=OneWay}\"\n

                                Starten wir die Anwendung und setzen wir dann einen Haltepunkt im Setter der Eigenschaft Name der Klasse Person (Zeileif (name != value) ), und sehen wir nach, ob die Datenverbindung in umgekehrter Richtung funktioniert: Wenn wir den Wert eines der TextBox \u00e4ndern, \u00e4ndert sich dann die Eigenschaft Name des Objekts NewPerson? Geben wir etwas in das Textfeld ein, das mit dem Namen verkn\u00fcpft ist, und klicken wir dann auf ein anderes Feld: Der Inhalt des Textfelds wird dann \"abgeschlossen\", sein Inhalt sollte in die Datenquelle zur\u00fcckgeschrieben werden, wird aber nicht, der Code l\u00e4uft nicht an unserem Haltepunkt.

                                Das liegt daran, dass wir oben die Datenverbindung OneWay verwendet haben, die nur eine Datenbindung von der Datenquelle zur Oberfl\u00e4che ist. F\u00fcr den Weg zur\u00fcck soll der Datenbindungsmodus auf TwoWay eingestellt werden.

                                Text=\"{x:Bind Name, Mode=TwoWay}\"\nText=\"{x:Bind Age, Mode=TwoWay}\"\n

                                Probieren wir es aus! Auf diese Weise funktioniert die R\u00fcckw\u00e4rts-Datenverbindung: Die angegebene Eigenschaft des Controllers (in unserem Fall Text) und die Datenquelle bleiben bei jeder Richtungs\u00e4nderung synchron.

                                "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#listen","title":"Listen","text":"

                                Im Folgenden werden wir die Listenverbindung \u00fcben. F\u00fcgen wir eine Liste von Person-Objekten in die Code-Behind-Datei unserer Ansicht ein und geben wir ihr am Ende des Konstruktors einen Anfangswert.

                                public List<Person> People { get; set; }\n\npublic MainWindow()\n{\n    InitializeComponent();\n\n    NewPerson = new Person()\n    {\n        Name = \"Eric Cartman\",\n        Age = 8\n    };\n\n    People = new List<Person>()\n    {\n      new Person() { Name = \"Peter Griffin\", Age = 40 },\n      new Person() { Name = \"Homer Simpson\", Age = 42 },\n    };\n}\n

                                Verwenden wir die Datenverbindung, um die Datenquelle des Steuerelements ListView festzulegen. Dazu sollen wir die Eigenschaft ItemsSource des Steuerelements ListView einstellen.

                                <ListView Grid.Row=\"3\" Grid.ColumnSpan=\"2\" ItemsSource=\"{x:Bind People}\"/>\n

                                Probieren wir es aus!

                                Wir sehen, dass zwei Eintr\u00e4ge in der Liste erschienen sind. Nat\u00fcrlich ist es nicht das, was wir wollen, aber das ist leicht zu \u00e4ndern. Standardm\u00e4\u00dfig ruft ListView ToString()bei Listenelementen auf, was die Eigenschaft FullName des Klassentyps (d.h. der Typname) ist, wenn ToString() nicht \u00fcberschrieben wird.

                                Legen wir die Eigenschaft ItemTemplate von ListViewfest (unter Verwendung der bekannten property element syntax), die das Aussehen des Listenelementes unter Verwendung einer Vorlage verleiht: In unserem Fall machen wir daraus ein einzelliges Grid, wobei TextBlocks die Eigenschaften von Person anzeigt, wobei der Name links und das Alter rechts ausgerichtet ist.

                                <ListView Grid.Row=\"3\" Grid.ColumnSpan=\"2\" ItemsSource=\"{x:Bind People}\">\n    <ListView.ItemTemplate>\n        <DataTemplate x:DataType=\"model:Person\">\n            <Grid>\n                <TextBlock Text=\"{x:Bind Name}\" />\n                <TextBlock Text=\"{x:Bind Age}\" HorizontalAlignment=\"Right\" />\n            </Grid>\n        </DataTemplate>\n    </ListView.ItemTemplate>\n</ListView>\n

                                DataTemplate ist eine Oberfl\u00e4chenschablone, die von der ListView (er ist gegeben durch ItemTemplate eigenschaft) auf alle Elemente w\u00e4hrend der Anzeige angewendet wird.

                                Da x:Bind eine Datenverbindung zur \u00dcbersetzungszeit ist, m\u00fcssen wir auch den Datentyp in der Datenvorlage mit dem Attribut x:DataType angeben. Im obigen Beispiel haben wir model:Personangegeben, so dass das Pr\u00e4fix model dem Namensraum HelloXaml.Models unseres Codes zugeordnet werden soll (der die Klasse Person enth\u00e4lt). Dazu m\u00fcssen wir die folgende Namensraumdeklaration zu den Attributen des Tags Window am Anfang unserer XAML-Datei hinzuf\u00fcgen: xmlns:model=\"using:HelloXaml.Models\" (danach wird das Pr\u00e4fix model verwendet). Dies kann manuell oder mit Visual Studio erfolgen: Klicken wir einfach auf den unterstrichenen (als fehlerhaft markierten) model:PersonText, dann auf die Lampe am Anfang der Zeile (oder die Tastenkombination Ctrl + . ) und w\u00e4hlen wir das angezeigte Element \"Add xmlns using:HelloXaml.Models\".

                                Probieren wir es aus! Die Eintr\u00e4ge erscheinen nun gut in der Liste.

                                Klicken wir auf die Taste Add, um eine neue Kopie von Person mit den Daten der Person des Formilar zur Liste hinzuzuf\u00fcgen, und l\u00f6schen wir dann die Formulardaten in unserem Objekt NewPerson.

                                F\u00fcgen wir dazu unserer Taste Add einen Click Ereignishandler hinzu:

                                <Button ... Click=\"AddButton_Click\">\n
                                private void AddButton_Click(object sender, RoutedEventArgs e)\n{\n    People.Add(new Person()\n    { \n        Name = NewPerson.Name,\n        Age = NewPerson.Age,\n    });\n\n    NewPerson.Name = string.Empty;\n    NewPerson.Age = 0;\n}\n

                                Der neue Eintrag erscheint nicht in der Liste, da ListView nicht dar\u00fcber informiert wird, dass ein neuer Eintrag in die Liste aufgenommen wurde. Dies kann leicht behoben werden, indem List<Person>durch ObservableCollection<Person>ersetzt wird:

                                public ObservableCollection<Person> People { get; set; }\n

                                ObservableCollection<T>

                                Es ist wichtig zu beachten, dass sich hier nicht der Wert der Eigenschaft People selbst ge\u00e4ndert hat, sondern der Inhalt des Objekts List<Person>. Die L\u00f6sung ist also nicht die Schnittstelle INotifyPropertyChanged, sondern die Schnittstelle INotifyCollectionChanged, die von ObservableCollection implementiert wird.

                                Wir kennen und verwenden also bereits zwei Schnittstellen, die die Datenverbindung unterst\u00fctzen: INotifyPropertyChanged und INotifyCollectionChanged.

                                "},{"location":"labor/3-felhasznaloi-felulet/index_ger/#ausblick-klassische-bindung","title":"Ausblick: Klassische Bindung","text":"

                                Die klassische Form der Datenverbindung ist die Binding Markup Extension.

                                Die wichtigsten Unterschiede im Vergleich zu x:Bindsind:

                                • Der Standardmodus f\u00fcr Binding ist OneWay und nicht OneTime: Er \u00fcberwacht also standardm\u00e4\u00dfig \u00c4nderungen, w\u00e4hrend dies f\u00fcr x:Bindausdr\u00fccklich angegeben werden muss.
                                • Binding arbeitet standardm\u00e4\u00dfig mit DataContext, aber es ist m\u00f6glich, die Quelle f\u00fcr die Datenbindung festzulegen. W\u00e4hrend x:Bind standardm\u00e4\u00dfig von unserer Ansichtsklasse (xaml.cs) gebunden wird.
                                • Binding arbeitet zur Laufzeit mit Reflection, so dass Sie einerseits keine Kompilierfehler bekommen, wenn Sie etwas falsch schreiben, und andererseits k\u00f6nnen viele Datenbindungen (in der Gr\u00f6\u00dfenordnung von 1000) Ihre Anwendung verlangsamen.
                                • x:Bind ist kompilierbar, d. h. der Compiler pr\u00fcft, ob die angegebenen Eigenschaften vorhanden sind. In Datenvorlagen m\u00fcssen Sie bei der Angabe von DataTemplate mit dem Attribut x:DataType angeben, mit welchen Daten sie arbeiten werden.
                                • F\u00fcr x:Bind ist es m\u00f6glich, Methoden zu binden, w\u00e4hrend f\u00fcr Bindingnur Konverter verwendet werden k\u00f6nnen. Bei gebundenen Funktionen funktioniert die \u00c4nderungsbenachrichtigung auch bei \u00c4nderungen von Parametern.

                                Empfehlung

                                Als Faustregel gilt, dass Sie vorzugsweise x:Bindverwenden sollten, da Sie so schneller und zeitnaher Fehler erhalten. Wenn Sie jedoch aus irgendeinem Grund Probleme mit x:Bindhaben, sollten Sie zu Bindingwechseln.

                                "},{"location":"labor/4-tobbszalu/","title":"4. T\u00f6bbsz\u00e1l\u00fa alkalmaz\u00e1sok k\u00e9sz\u00edt\u00e9se","text":""},{"location":"labor/4-tobbszalu/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                                A gyakorlat c\u00e9lja, hogy megismertesse a hallgat\u00f3kat a t\u00f6bbsz\u00e1las programoz\u00e1s sor\u00e1n k\u00f6vetend\u0151 alapelvekkel. \u00c9rintett t\u00e9mak\u00f6r\u00f6k (t\u00f6bbek k\u00f6z\u00f6tt):

                                • Sz\u00e1lak ind\u00edt\u00e1sa (Thread)
                                • Sz\u00e1lak le\u00e1ll\u00edt\u00e1sa
                                • Sz\u00e1lbiztos (thread safe) oszt\u00e1lyok k\u00e9sz\u00edt\u00e9se a lock kulcssz\u00f3 alkalmaz\u00e1s\u00e1val
                                • ThreadPool haszn\u00e1lata
                                • Jelz\u00e9s \u00e9s jelz\u00e9sre v\u00e1rakoz\u00e1s sz\u00e1l szinkroniz\u00e1ci\u00f3 ManualResetEvent seg\u00edts\u00e9g\u00e9vel (WaitHandle)
                                • WinUI sz\u00e1lkezel\u00e9si saj\u00e1toss\u00e1gok (DispatcherQueue)

                                Term\u00e9szetesen, mivel a t\u00e9mak\u00f6r hatalmas, csak alapszint\u0171 tud\u00e1st fogunk szerezni, de e tud\u00e1s birtok\u00e1ban m\u00e1r k\u00e9pesek lesz\u00fcnk \u00f6n\u00e1ll\u00f3an is elindulni a bonyolultabb feladatok megval\u00f3s\u00edt\u00e1s\u00e1ban.

                                A kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sok: Konkurens (t\u00f6bbsz\u00e1l\u00fa) alkalmaz\u00e1sok fejleszt\u00e9se.

                                "},{"location":"labor/4-tobbszalu/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                                A gyakorlat elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                                • Visual Studio 2022
                                  • Windows Desktop Development Workload
                                • Windows 10 vagy Windows 11 oper\u00e1ci\u00f3s rendszer (Linux \u00e9s macOS nem alkalmas)
                                "},{"location":"labor/4-tobbszalu/#megoldas","title":"Megold\u00e1s","text":"A k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se

                                L\u00e9nyeges, hogy a labor sor\u00e1n a laborvezet\u0151t k\u00f6vetve kell dolgozni, tilos (\u00e9s \u00e9rtelmetlen) a k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se. Ugyanakkor az ut\u00f3lagos \u00f6n\u00e1ll\u00f3 gyakorl\u00e1s sor\u00e1n hasznos lehet a k\u00e9sz megold\u00e1s \u00e1ttekint\u00e9se, \u00edgy ezt el\u00e9rhet\u0151v\u00e9 tessz\u00fck.

                                A megold\u00e1s GitHubon \u00e9rhet\u0151 el. A legegyszer\u0171bb m\u00f3d a let\u00f6lt\u00e9s\u00e9re, ha parancssorb\u00f3l a git clone utas\u00edt\u00e1ssal lekl\u00f3nozzuk a g\u00e9p\u00fcnkre a megoldas \u00e1gat:

                                git clone https://github.com/bmeviauab00/lab-tobbszalu-kiindulo -b megoldas

                                Ehhez telep\u00edtve kell legyen a g\u00e9pre a parancssori git, b\u0151vebb inform\u00e1ci\u00f3 itt.

                                "},{"location":"labor/4-tobbszalu/#bevezeto","title":"Bevezet\u0151","text":"

                                A p\u00e1rhuzamosan fut\u00f3 sz\u00e1lak kezel\u00e9se kiemelt fontoss\u00e1g\u00fa ter\u00fclet, melyet minden szoftverfejleszt\u0151nek legal\u00e1bb alapszinten ismernie kell. A gyakorlat sor\u00e1n alapszint\u0171, de kiemelt fontoss\u00e1g\u00fa probl\u00e9m\u00e1kat oldunk meg, ez\u00e9rt t\u00f6rekedn\u00fcnk kell arra, hogy ne csak a v\u00e9geredm\u00e9nyt, hanem az elv\u00e9gzett m\u00f3dos\u00edt\u00e1sok \u00e9rtelm\u00e9t \u00e9s indokait is meg\u00e9rts\u00fck.

                                A feladat sor\u00e1n egyszer\u0171 WinUI alkalmaz\u00e1st fogunk felruh\u00e1zni t\u00f6bbsz\u00e1las k\u00e9pess\u00e9gekkel, egyre komplexebb feladatokat megoldva. Az alapprobl\u00e9ma a k\u00f6vetkez\u0151: van egy f\u00fcggv\u00e9ny\u00fcnk, mely hossz\u00fa ideig fut, s mint l\u00e1tni fogjuk, ennek \u201edirektben\u201d t\u00f6rt\u00e9n\u0151 h\u00edv\u00e1sa a fel\u00fcletr\u0151l kellemetlen k\u00f6vetkezm\u00e9nyekkel j\u00e1r. A megold\u00e1s sor\u00e1n egy megl\u00e9v\u0151 alkalmaz\u00e1st fogunk kieg\u00e9sz\u00edteni saj\u00e1t k\u00f3dr\u00e9szletekkel. Az \u00fajonnan besz\u00farand\u00f3 sorokat az \u00fatmutat\u00f3ban kiemelt h\u00e1tt\u00e9r jelzi.

                                "},{"location":"labor/4-tobbszalu/#0-feladat-ismerkedes-a-kiindulo-alkalmazassal-elokeszites","title":"0. Feladat - Ismerked\u00e9s a kiindul\u00f3 alkalmaz\u00e1ssal, el\u0151k\u00e9sz\u00edt\u00e9s","text":"

                                Kl\u00f3nozzuk le a 4. gyakorlathoz tartoz\u00f3 kiindul\u00f3 alkalmaz\u00e1s repositoryj\u00e1t:

                                • Nyissunk egy command prompt-ot
                                • Navig\u00e1ljunk el egy tetsz\u0151leges mapp\u00e1ba, p\u00e9ld\u00e1ul c:\\work\\NEPTUN
                                • Adjuk ki a k\u00f6vetkez\u0151 parancsot: git clone https://github.com/bmeviauab00/lab-tobbszalu-kiindulo.git
                                • Nyissuk meg a SuperCalculator.sln solutiont Visual Studio-ban.

                                A feladatunk az, hogy egy bin\u00e1ris form\u00e1ban megkapott algoritmus futtat\u00e1s\u00e1hoz WinUI technol\u00f3gi\u00e1val felhaszn\u00e1l\u00f3i fel\u00fcletet k\u00e9sz\u00edts\u00fcnk. A bin\u00e1ris forma .NET eset\u00e9ben egy .dll kiterjeszt\u00e9s\u0171 f\u00e1jlt jelent, ami programoz\u00f3i szemmel egy oszt\u00e1lyk\u00f6nyvt\u00e1r. A f\u00e1jl neve eset\u00fcnkben Algorithms.dll, megtal\u00e1lhat\u00f3 a lekl\u00f3nozott Git repositoryban.

                                A kiindul\u00f3 alkalmaz\u00e1sban a felhaszn\u00e1l\u00f3i fel\u00fclet el\u0151 is van k\u00e9sz\u00edtve. Futtassuk az alkalmaz\u00e1st:

                                Az alkalmaz\u00e1s fel\u00fclet\u00e9n meg tudjuk adni az algoritmus bemen\u0151 param\u00e9tereit (double sz\u00e1mok t\u00f6mbje): a p\u00e9ld\u00e1nkban mindig k\u00e9t double sz\u00e1m param\u00e9terrel h\u00edvjuk az algoritmust, ezt a k\u00e9t fels\u0151 sz\u00f6vegmez\u0151ben lehet megadni. A feladatunk az, hogy a Calculate Result gombra kattint\u00e1s sor\u00e1n futtassuk az algoritmust a megadott param\u00e9terekkel, majd, ha v\u00e9gzett, akkor a Result alatti list\u00e1z\u00f3 mez\u0151 \u00faj sor\u00e1ban jelen\u00edts\u00fck meg a kapott eredm\u00e9nyt a bemen\u0151 param\u00e9terekkel egy\u00fctt.

                                K\u00f6vetkez\u0151 l\u00e9p\u00e9sben ismerkedj\u00fcnk meg a let\u00f6lt\u00f6tt Visual Studio solutionnel:

                                A keretalkalmaz\u00e1s egy WinUI 3 alap\u00fa alkalmaz\u00e1s. A fel\u00fclet alapvet\u0151en k\u00e9sz, defin\u00edci\u00f3ja a MainWindow.xaml f\u00e1jlban tal\u00e1lhat\u00f3. Ez sz\u00e1munkra a gyakorlat c\u00e9lj\u00e1t tekintve kev\u00e9sb\u00e9 izgalmas, de otthon a gyakorl\u00e1s kedv\u00e9\u00e9rt \u00e9rdemes \u00e1ttekinteni.

                                Fel\u00fclet kialak\u00edt\u00e1sa a MainWindow.xaml-ben

                                Az ablakfel\u00fclet kialak\u00edt\u00e1s\u00e1nak alapjai:

                                • A gy\u00f6k\u00e9relem (root) \"szok\u00e1sosan\" egy Grid.
                                • A gy\u00f6k\u00e9r Grid fels\u0151 sor\u00e1ban tal\u00e1lhat\u00f3 a k\u00e9t TextBox-ot \u00e9s a Button-t tartalmaz\u00f3 StackPanel.
                                • A gy\u00f6k\u00e9r Grid als\u00f3 sor\u00e1ban egy m\u00e1sik Grid tal\u00e1lhat\u00f3. A TextBox-szal ellent\u00e9tben a ListBox nem rendelkezik Header tulajdons\u00e1ggal, \u00edgy ezt nek\u00fcnk kellett egy k\u00fcl\u00f6n\u00e1ll\u00f3 \"Result\" sz\u00f6veg\u0171 TextBlock form\u00e1j\u00e1ban bevezetni. Ezt a Grid-et az\u00e9rt vezett\u00fck be (egy \"egyszer\u0171bb\" StackPanel helyett), mert \u00edgy lehetett el\u00e9rni, hogy a fels\u0151 sor\u00e1ban a \"Result\" TextBlock fix magass\u00e1g\u00fa legyen, az als\u00f3 sorban pedig a ListBox t\u00f6ltse ki a teljes marad\u00f3 helyet (a fels\u0151 sor magass\u00e1ga Auto, az als\u00f3 sor magass\u00e1ga *).
                                • A \"Calculate Result\" sz\u00f6veg\u0171 gomb sz\u00e9p p\u00e9lda arra, hogy a Button Content-j\u00e9nek sokszor nemcsak egy egyszer\u0171 sz\u00f6veget adunk meg. A p\u00e9ld\u00e1ban egy SymbolIcon \u00e9s a TextBlock kompoz\u00edci\u00f3ja (StackPanel seg\u00edts\u00e9g\u00e9vel megval\u00f3s\u00edtva), ez\u00e1ltal tudjunk a egy megfelel\u0151 ikont/szimb\u00f3lumot rendelni, mely feldobja a megjelen\u00e9s\u00e9t.
                                • Arra is l\u00e1tunk p\u00e9ld\u00e1t, hogy a ListBox hogyan tehet\u0151 g\u00f6rgethet\u0151v\u00e9, ha m\u00e1r sok elem van benne (vagy t\u00fal sz\u00e9lesek az elemek). Ehhez a ScrollViewer-\u00e9t kell megfelel\u0151en param\u00e9terezni.
                                • A ListBox ItemContainerStyle tulajdons\u00e1g\u00e1val a ListBox elemre adhatunk meg st\u00edlusokat. A p\u00e9ld\u00e1ban a Padding-et vett\u00fck kisebbre az alap\u00e9rtelmezettn\u00e9l, en\u00e9lk\u00fcl a ListBox elemek magass\u00e1ga helypazarl\u00f3an nagy lenne.

                                A MainWindow.xaml.cs forr\u00e1sf\u00e1jl a f\u0151ablakhoz tartoz\u00f3 code behind f\u00e1jl, ezt tekints\u00fck \u00e1t, f\u0151bb elemei a k\u00f6vetkez\u0151k:

                                • Az eredm\u00e9ny \u00e9s a param\u00e9terek ListBox-ba t\u00f6rt\u00e9n\u0151 napl\u00f3z\u00e1s\u00e1hoz tal\u00e1lunk egy ShowResult nev\u0171 seg\u00e9df\u00fcggv\u00e9nyt.
                                • A CalculateResultButton_Click a gomb a Calculate Result gomb kattint\u00e1s\u00e1hoz tartoz\u00f3 esem\u00e9nykezel\u0151. Azt l\u00e1tjuk, hogy a k\u00e9t sz\u00f6vegdobozb\u00f3l kiolvassa a param\u00e9terek \u00e9rt\u00e9k\u00e9t, \u00e9s megpr\u00f3b\u00e1lja sz\u00e1mm\u00e1 alak\u00edtani. Ha siker\u00fcl, akkor itt t\u00f6rt\u00e9nik majd az algoritmus h\u00edv\u00e1sa (ez nincs m\u00e9g megval\u00f3s\u00edtva), illetve, ha nem siker\u00fcl, akkor a DisplayInvalidElementDialog seg\u00edts\u00e9g\u00e9vel egy \u00fczenetablakban t\u00e1j\u00e9koztatja a felhaszn\u00e1l\u00f3t az \u00e9rv\u00e9nytelen param\u00e9terekr\u0151l.
                                • A konstruktorb\u00f3l h\u00edvott AddKeyboardAcceleratorToChangeTheme f\u00fcggv\u00e9ny sz\u00e1munkra nem relev\u00e1ns, a vil\u00e1gos \u00e9s s\u00f6t\u00e9t t\u00e9ma k\u00f6z\u00f6tti v\u00e1lt\u00e1st teszi lehet\u0151v\u00e9 (fut\u00e1s k\u00f6zben \u00e9rdemes kipr\u00f3b\u00e1lni, Ctrl+T billenty\u0171kombin\u00e1ci\u00f3).
                                "},{"location":"labor/4-tobbszalu/#a-dll-ben-levo-kod-felhasznalasa","title":"A DLL-ben lev\u0151 k\u00f3d felhaszn\u00e1l\u00e1sa","text":"

                                A kiindul\u00f3 projektben megtal\u00e1ljuk a Algorithm.dll-t. Ebben leford\u00edtott form\u00e1ban egy Algorithms n\u00e9vt\u00e9rben lev\u0151 SuperAlgorithm nev\u0171 oszt\u00e1ly tal\u00e1lhat\u00f3, melynek egy Calculate nev\u0171 statikus m\u0171velete van. Ahhoz, hogy egy projektben fel tudjuk haszn\u00e1lni a DLL-ben lev\u0151 oszt\u00e1lyokat, a DLL-re a projekt\u00fcnkben egy \u00fan. referenci\u00e1t kell felvegy\u00fcnk.

                                1. Solution Explorerben a projekt\u00fcnk Dependencies node-j\u00e1ra jobbklikkelve v\u00e1lasszuk az Add Project reference opci\u00f3t!

                                  K\u00fcls\u0151 referenci\u00e1k

                                  Itt val\u00f3j\u00e1ban nem egy m\u00e1sik Visual Studio projektre adunk referenci\u00e1t, de \u00edgy a legegyszer\u0171bb el\u0151hozni ezt az ablakot.

                                  Megeml\u00edtend\u0151 m\u00e9g, hogy k\u00fcls\u0151 oszt\u00e1lyk\u00f6nyvt\u00e1rak eset\u00e9ben m\u00e1r nem DLL-eket szoktunk refer\u00e1lni egy rendes projektben, hanem a .NET csomagkezel\u0151 rendeszer\u00e9b\u0151l a NuGet-r\u0151l szok\u00e1s a k\u00fcls\u0151 csomagokat beszerezni. Most az Algorithm.dll eset\u00fcnkben nincs NuGet-en publik\u00e1lva, ez\u00e9rt kell k\u00e9zzel felvegy\u00fck azt.

                                2. Az el\u0151ugr\u00f3 ablak jobb als\u00f3 sarokban tal\u00e1lhat\u00f3 Browse gomb seg\u00edts\u00e9g\u00e9vel keress\u00fck meg \u00e9s v\u00e1lasszuk ki projekt External almapp\u00e1j\u00e1ban tal\u00e1lhat\u00f3 Algorithms.dll f\u00e1jlt, majd hagyjuk j\u00f3v\u00e1 a hozz\u00e1ad\u00e1st az OK gombbal!

                                A Solution Explorerben egy projekt alatti Dependencies csom\u00f3pontot lenyitva l\u00e1thatjuk a hivatkozott k\u00fcls\u0151 f\u00fcgg\u0151s\u00e9geket. Itt most m\u00e1r megjelenik az Assemblyk k\u00f6z\u00f6tt el\u0151bb felvett Algorithms referencia is. A Frameworks kateg\u00f3ri\u00e1ban a .NET keretrendszer csomagjait tal\u00e1ljuk. Az Analyzerek pedig statikus k\u00f3delemz\u0151 eszk\u00f6z\u00f6k ford\u00edt\u00e1s id\u0151ben. Illetve itt lenn\u00e9nek m\u00e9g a projekt vagy a NuGet referenci\u00e1k is.

                                Kattintsunk Algorithms referenci\u00e1n jobb gombbal \u00e9s v\u00e1lasszuk a View in Object Browser funkci\u00f3t. Ekkor megny\u00edlik az Object Browser tabf\u00fcl, ahol megtekinthetj\u00fck, hogy az adott DLL-ben milyen n\u00e9vterek, oszt\u00e1lyok tal\u00e1lhat\u00f3k, illetve ezeknek milyen tagjaik (tagv\u00e1ltoz\u00f3, tagf\u00fcggv\u00e9ny, property, event) vannak. Ezeket a Visual Studio a DLL metaadataib\u00f3l az \u00fan. reflection mechanizmus seg\u00edts\u00e9g\u00e9vel olvassa ki (ilyen k\u00f3dot ak\u00e1r mi is \u00edrhatunk).

                                Az al\u00e1bbi \u00e1br\u00e1nak megfelel\u0151en az Object Browserben baloldalt keress\u00fck ki az Algorithms csom\u00f3pontot, nyissuk le, \u00e9s l\u00e1that\u00f3v\u00e1 v\u00e1lik, hogy egy Algorithms n\u00e9vt\u00e9r van benne, abban pedig egy SuperAlgorithm oszt\u00e1ly. Ezt kiv\u00e1lasztva k\u00f6z\u00e9pen megjelennek az oszt\u00e1ly f\u00fcggv\u00e9nyei, itt egy f\u00fcggv\u00e9nyt kiv\u00e1lasztva pedig az adott f\u00fcggv\u00e9ny pontos szignat\u00far\u00e1ja:

                                "},{"location":"labor/4-tobbszalu/#1-feladat-muvelet-futtatasa-a-foszalon","title":"1. Feladat \u2013 M\u0171velet futtat\u00e1sa a f\u0151sz\u00e1lon","text":"

                                Most m\u00e1r r\u00e1t\u00e9rhet\u00fcnk az algoritmus futtat\u00e1s\u00e1ra. Els\u0151 l\u00e9p\u00e9sben ezt az alkalmaz\u00e1sunk f\u0151 sz\u00e1l\u00e1n tessz\u00fck meg.

                                1. A f\u0151ablakon l\u00e9v\u0151 gomb Click esem\u00e9nykezel\u0151j\u00e9ben h\u00edvjuk meg a sz\u00e1mol\u00f3 f\u00fcggv\u00e9ny\u00fcnket. Ehhez a Solution Explorerben nyissuk meg a MainWindow.xaml.cs code behind f\u00e1jlt, \u00e9s keress\u00fck meg a CalculateResultButton_Click esem\u00e9nykezel\u0151t. Eg\u00e9sz\u00edts\u00fck ki a k\u00f3dot az \u00fajonnan behivatkozott algoritmus megh\u00edv\u00e1s\u00e1val.

                                  private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        var result = Algorithms.SuperAlgorithm.Calculate(parameters);\n        ShowResult(parameters, result);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n
                                2. Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st, \u00e9s vegy\u00fck \u00e9szre, hogy az ablak a sz\u00e1mol\u00e1s ideje alatt nem reag\u00e1l a mozgat\u00e1sra, \u00e1tm\u00e9retez\u00e9sre, a fel\u00fclet gyakorlatilag befagy.

                                Az alkalmaz\u00e1sunk esem\u00e9nyvez\u00e9relt, mint minden Windows alkalmaz\u00e1s. Az oper\u00e1ci\u00f3s rendszer a k\u00fcl\u00f6nb\u00f6z\u0151 interakci\u00f3kr\u00f3l (pl. mozgat\u00e1s, \u00e1tm\u00e9retez\u00e9s, eg\u00e9rkattint\u00e1s) \u00e9rtes\u00edti az alkalmaz\u00e1sunkat: mivel a gombnyom\u00e1st k\u00f6vet\u0151en az alkalmaz\u00e1sunk egyetlen sz\u00e1la a kalkul\u00e1ci\u00f3val van elfoglalva, nem tudja azonnal feldolgozni a tov\u00e1bbi felhaszn\u00e1l\u00f3i utas\u00edt\u00e1sokat. Amint a sz\u00e1m\u00edt\u00e1s lefutott (\u00e9s az eredm\u00e9nyek megjelennek a list\u00e1ban) a kor\u00e1bban kapott parancsok is v\u00e9grehajt\u00e1sra ker\u00fclnek.

                                "},{"location":"labor/4-tobbszalu/#2-feladat-vegezzuk-a-szamitast-kulon-szalban","title":"2. Feladat \u2013 V\u00e9gezz\u00fck a sz\u00e1m\u00edt\u00e1st k\u00fcl\u00f6n sz\u00e1lban","text":"

                                K\u00f6vetkez\u0151 l\u00e9p\u00e9sben a sz\u00e1m\u00edt\u00e1s elv\u00e9gz\u00e9s\u00e9re egy k\u00fcl\u00f6n sz\u00e1lat fogunk ind\u00edtani, hogy az ne blokkolja a felhaszn\u00e1l\u00f3i fel\u00fcletet.

                                1. K\u00e9sz\u00edts\u00fcnk egy \u00faj f\u00fcggv\u00e9nyt a MainWindow oszt\u00e1lyban, mely a feldolgoz\u00f3 sz\u00e1l bel\u00e9p\u00e9si pontja lesz.

                                  private void CalculatorThread(object arg)\n{\n    var parameters = (double[])arg;\n    var result = Algorithms.SuperAlgorithm.Calculate(parameters);\n    ShowResult(parameters, result);\n}\n
                                2. Ind\u00edtsuk el a sz\u00e1lat a gomb Click esem\u00e9nykezel\u0151j\u00e9ben. Ehhez cser\u00e9lj\u00fck le a kor\u00e1bban hozz\u00e1adott k\u00f3dot:

                                  private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        var th = new Thread(CalculatorThread);\n        th.Start(parameters);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n

                                  A Thread objektum Start m\u0171velet\u00e9ben \u00e1tadott param\u00e9tert kapja meg a CalculatorThread sz\u00e1lf\u00fcggv\u00e9ny\u00fcnk.

                                3. Futtassuk az alkalmaz\u00e1st F5-tel (most fontos, hogy \u00edgy, a debuggerben futtassuk)! The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD)) hiba\u00fczenetet kapunk a ShowResult met\u00f3dusban, ugyanis nem abb\u00f3l a sz\u00e1lb\u00f3l pr\u00f3b\u00e1lunk hozz\u00e1f\u00e9rni a UI elemhez / vez\u00e9rl\u0151h\u00f6z, amelyik l\u00e9trehozta (a vez\u00e9rl\u0151t). A k\u00f6vetkez\u0151 feladatban ezt a probl\u00e9m\u00e1t analiz\u00e1ljuk \u00e9s oldjuk meg.

                                "},{"location":"labor/4-tobbszalu/#3-feladat-a-dispatcherqueuehasthreadaccess-es-dispatcherqueuetryenqueue-hasznalata","title":"3. Feladat \u2013 a DispatcherQueue.HasThreadAccess \u00e9s DispatcherQueue.TryEnqueue haszn\u00e1lata","text":"

                                Az el\u0151z\u0151 pontban a probl\u00e9m\u00e1t a k\u00f6vetkez\u0151 okozza. WinUI alkalmaz\u00e1sokn\u00e1l \u00e9l az al\u00e1bbi szab\u00e1ly: az ablakok/fel\u00fcletelemek/vez\u00e9rl\u0151elemek alapvet\u0151en nem sz\u00e1lv\u00e9dett (thread safe) objektumok, \u00edgy egy ablakhoz/fel\u00fcletelemhez/vez\u00e9rl\u0151h\u00f6z csak abb\u00f3l a sz\u00e1lb\u00f3l szabad hozz\u00e1f\u00e9rni (pl. propertyj\u00e9t olvasni, \u00e1ll\u00edtani, m\u0171velet\u00e9t megh\u00edvni), amelyik sz\u00e1l az adott ablakot/fel\u00fcletelemet/vez\u00e9rl\u0151t l\u00e9trehozta, m\u00e1sk\u00fcl\u00f6nben kiv\u00e9telt kapunk. Alkalmaz\u00e1sunkban az\u00e9rt kaptunk kiv\u00e9telt, mert a resultListBox vez\u00e9rl\u0151t a f\u0151 sz\u00e1lban hoztuk l\u00e9tre, a ShowResult met\u00f3dusban az eredm\u00e9ny megjelen\u00edt\u00e9sekor viszont egy m\u00e1sik sz\u00e1lb\u00f3l f\u00e9r\u00fcnk hozz\u00e1 (resultListBox.Items.Add m\u0171velet h\u00edv\u00e1sa).

                                K\u00e9rd\u00e9s, hogyan lehet m\u00e9gis valamilyen m\u00f3don ezekhez a fel\u00fcletelemekhez/vez\u00e9rl\u0151kh\u00f6z egy m\u00e1sik sz\u00e1lb\u00f3l hozz\u00e1f\u00e9rni. A megold\u00e1st a DispatcherQueue alkalmaz\u00e1sa jelenti, mely abban ny\u00fajt seg\u00edts\u00e9get, hogy a vez\u00e9rl\u0151kh\u00f6z mindig a megfelel\u0151 sz\u00e1lb\u00f3l t\u00f6rt\u00e9njen a hozz\u00e1f\u00e9r\u00e9s:

                                • DispatcherQueue objektum TryEnqueue f\u00fcggv\u00e9nye a vez\u00e9rl\u0151elemet l\u00e9trehoz\u00f3 sz\u00e1lon futtatja le a sz\u00e1m\u00e1ra param\u00e9terk\u00e9nt megadott f\u00fcggv\u00e9nyt (mely f\u00fcggv\u00e9nyb\u0151l \u00edgy m\u00e1r k\u00f6zvetlen\u00fcl hozz\u00e1f\u00e9rhet\u00fcnk a vez\u00e9rl\u0151h\u00f6z).
                                • A DispatcherQueue objektum HasThreadAccess tulajdons\u00e1ga azt seg\u00edt eld\u00f6nteni, sz\u00fcks\u00e9g van-e egy\u00e1ltal\u00e1n az el\u0151z\u0151 pontban eml\u00edtett TryEnqueue alkalmaz\u00e1s\u00e1ra. Ha a tulajdons\u00e1g \u00e9rt\u00e9ke
                                  • igaz, akkor a vez\u00e9rl\u0151h\u00f6z k\u00f6zvetlen\u00fcl is hozz\u00e1f\u00e9rhet\u00fcnk (mert az aktu\u00e1lis sz\u00e1l megegyezik a vez\u00e9rl\u0151t l\u00e9trehoz\u00f3 sz\u00e1llal), ellenben ha
                                  • hamis, akkor a vez\u00e9rl\u0151h\u00f6z csak \"ker\u00fcl\u0151 \u00faton\", a DispatcherQueue objektum TryEnqueue seg\u00edts\u00e9g\u00e9vel f\u00e9rhet\u00fcnk hozz\u00e1 (mert az aktu\u00e1lis sz\u00e1l NEM egyezik a vez\u00e9rl\u0151t l\u00e9trehoz\u00f3 sz\u00e1llal).

                                A DispatcherQueue seg\u00edts\u00e9g\u00e9vel teh\u00e1t el tudjuk ker\u00fclni kor\u00e1bbi kiv\u00e9tel\u00fcnket (a vez\u00e9rl\u0151h\u00f6z, eset\u00fcnkben a resultListBox-hoz val\u00f3 hozz\u00e1f\u00e9r\u00e9st a megfelel\u0151 sz\u00e1lra tudjuk \"ir\u00e1ny\u00edtani\"). Ezt fogjuk a k\u00f6vetkez\u0151kben megtenni.

                                Note

                                A DispatcherQueue objektum a Window oszt\u00e1ly lesz\u00e1rmazottakban \u00e9rhet\u0151 el aDispatcherQueue tulajdons\u00e1g\u00e1n kereszt\u00fcl (m\u00e1s oszt\u00e1lyokban pedig a DispatcherQueue.GetForCurrentThread() statikus m\u0171velet seg\u00edts\u00e9g\u00e9vel szerezhet\u0151 meg).

                                M\u00f3dos\u00edtanunk kell a ShowResult met\u00f3dust annak \u00e9rdek\u00e9ben, hogy mell\u00e9ksz\u00e1lb\u00f3l t\u00f6rt\u00e9n\u0151 h\u00edv\u00e1s eset\u00e9n se dobjon kiv\u00e9telt.

                                private void ShowResult(double[] parameters, double result)\n{\n    // Closing the window the DispatcherQueue property may return null, so we have to perform a null check\n    if (this.DispatcherQueue == null)\n        return;\n\n    if (this.DispatcherQueue.HasThreadAccess)\n    {\n        var item = new ListBoxItem()\n        {\n            Content = $\"{parameters[0]} #  {parameters[1]} = {result}\"\n        };\n        resultListBox.Items.Add(item);\n        resultListBox.ScrollIntoView(item);\n    }\n    else\n    {\n        this.DispatcherQueue.TryEnqueue( () => ShowResult(parameters, result) );\n    }\n}\n

                                Pr\u00f3b\u00e1ljuk ki!

                                Ez a megold\u00e1s m\u00e1r m\u0171k\u00f6d\u0151k\u00e9pes, f\u0151bb elemei a k\u00f6vetkez\u0151k:

                                • A DispatcherQueue null vizsg\u00e1lat szerepe: a f\u0151ablak bez\u00e1r\u00e1sa ut\u00e1n a DispatcherQueue m\u00e1r null, nem haszn\u00e1lhat\u00f3.
                                • A DispatcherQueue.HasThreadAccess seg\u00edts\u00e9g\u00e9vel megn\u00e9zz\u00fck, hogy a h\u00edv\u00f3 sz\u00e1l hozz\u00e1f\u00e9rhet-e k\u00f6zvetlen\u00fcl a vez\u00e9rl\u0151kh\u00f6z (eset\u00fcnkben a ListBox-hoz):
                                  • Ha igen, minden \u00fagy t\u00f6rt\u00e9nik, mint eddig, a ListBox-ot kezel\u0151 k\u00f3d v\u00e1ltozatlan.
                                  • Ha nem, a DispatcherQueue.TryEnqueue seg\u00edts\u00e9g\u00e9vel f\u00e9r\u00fcnk hozz\u00e1 a vez\u00e9rl\u0151h\u00f6z. A k\u00f6vetkez\u0151 tr\u00fckk\u00f6t alkalmazzuk. A TryEnqueue f\u00fcggv\u00e9nynek egy olyan param\u00e9ter n\u00e9lk\u00fcli, egysoros f\u00fcggv\u00e9nyt adunk meg lambda kifejez\u00e9s form\u00e1j\u00e1ban, mellyel a ShowResult f\u00fcggv\u00e9ny\u00fcnket h\u00edvja meg (gyakorlatilag rekurz\u00edvan), a param\u00e9tereket tov\u00e1bb passzolva sz\u00e1m\u00e1ra. Ez nek\u00fcnk az\u00e9rt j\u00f3, mert ez a ShowResult h\u00edv\u00e1s m\u00e1r azon a sz\u00e1lon t\u00f6rt\u00e9nik, mely a vez\u00e9rl\u0151t l\u00e9trehozta (az alkalmaz\u00e1s f\u0151 sz\u00e1la), ebben a HasThreadAccess \u00e9rt\u00e9ke m\u00e1r igaz, \u00e9s hozz\u00e1 tudunk f\u00e9rni k\u00f6zvetlen\u00fcl a ListBox-unkhoz. Ez a rekurz\u00edv megk\u00f6zel\u00edt\u00e9s egy bevett minta a redund\u00e1ns k\u00f3dok elker\u00fcl\u00e9s\u00e9re.

                                Tegy\u00fcnk t\u00f6r\u00e9spontot a ShowResult m\u0171velet els\u0151 sor\u00e1ra, \u00e9s az alkalmaz\u00e1st futtatva gy\u0151z\u0151dj\u00fcnk meg arr\u00f3l, hogy a ShowResult m\u0171velet els\u0151 h\u00edv\u00e1sakor HasThreadAccess m\u00e9g hamis (\u00edgy megt\u00f6rt\u00e9nik a TryEnqueue h\u00edv\u00e1sa), majd ennek hat\u00e1s\u00e1ra m\u00e9g egyszer megh\u00edv\u00f3dik a ShowResult, de ekkor a HasThreadAccess \u00e9rt\u00e9ke m\u00e1r igaz.

                                Vegy\u00fck ki a t\u00f6r\u00e9spontot, \u00edgy futtassuk az alkalmaz\u00e1st: vegy\u00fck \u00e9szre, hogy am\u00edg egy sz\u00e1m\u00edt\u00e1s fut, \u00fajabbakat is ind\u00edthatunk, hiszen a fel\u00fclet\u00fcnk v\u00e9gig reszponz\u00edv maradt (a kor\u00e1bban tapasztalt hiba pedig m\u00e1r nem jelentkezik).

                                "},{"location":"labor/4-tobbszalu/#4-feladat-muvelet-vegzese-threadpool-szalon","title":"4. feladat \u2013 M\u0171velet v\u00e9gz\u00e9se Threadpool sz\u00e1lon","text":"

                                Az el\u0151z\u0151 megold\u00e1s egy jellemz\u0151je, hogy mindig \u00faj sz\u00e1lat hoz l\u00e9tre a m\u0171velethez. Eset\u00fcnkben ennek nincs k\u00fcl\u00f6n\u00f6sebb jelent\u0151s\u00e9ge, de ez a megk\u00f6zel\u00edt\u00e9s egy olyan kiszolg\u00e1l\u00f3 alkalmaz\u00e1s eset\u00e9ben, amely nagysz\u00e1m\u00fa k\u00e9r\u00e9st szolg\u00e1l ki \u00fagy, hogy minden k\u00e9r\u00e9shez k\u00fcl\u00f6n sz\u00e1lat ind\u00edt, m\u00e1r probl\u00e9m\u00e1s lehet. K\u00e9t okb\u00f3l is:

                                • Ha a sz\u00e1lf\u00fcggv\u00e9ny gyorsan lefut (egy kliens kiszolg\u00e1l\u00e1sa gyors), akkor a CPU nagy r\u00e9sz\u00e9t arra pazaroljuk, hogy sz\u00e1lakat ind\u00edtsunk \u00e9s \u00e1ll\u00edtsunk le, ezek ugyanis \u00f6nmagukban is er\u0151forr\u00e1sig\u00e9nyesek.
                                • T\u00fal nagy sz\u00e1m\u00fa sz\u00e1l is l\u00e9trej\u00f6het, ennyit kell \u00fctemeznie az oper\u00e1ci\u00f3s rendszernek, ami feleslegesen pazarolja az er\u0151forr\u00e1sokat.

                                Egy m\u00e1sik probl\u00e9ma jelen megold\u00e1sunkkal: mivel a sz\u00e1m\u00edt\u00e1s \u00fan. el\u0151t\u00e9rsz\u00e1lon fut (az \u00fajonnan l\u00e9trehozott sz\u00e1lak alap\u00e9rtelmez\u00e9sben el\u0151t\u00e9rsz\u00e1lak), hi\u00e1ba z\u00e1rjuk be az alkalmaz\u00e1st, a program tov\u00e1bb fut a h\u00e1tt\u00e9rben mindaddig, am\u00edg v\u00e9gre nem hajt\u00f3dik az utolj\u00e1ra ind\u00edtott sz\u00e1mol\u00e1s is: egy processz fut\u00e1sa ugyanis akkor fejez\u0151dik csak be, ha m\u00e1r nincs fut\u00f3 el\u0151t\u00e9rsz\u00e1la.

                                M\u00f3dos\u00edtsuk a gomb esem\u00e9nykezel\u0151j\u00e9t, hogy \u00faj sz\u00e1l ind\u00edt\u00e1sa helyett threadpool sz\u00e1lon futtassa a sz\u00e1m\u00edt\u00e1st. Ehhez csak a gombnyom\u00e1s esem\u00e9nykezel\u0151j\u00e9t kell ism\u00e9t \u00e1t\u00edrni.

                                private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        ThreadPool.QueueUserWorkItem(CalculatorThread, parameters);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n

                                Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st, \u00e9s vegy\u00fck \u00e9szre, hogy az alkalmaz\u00e1s az ablak bez\u00e1r\u00e1sakor azonnal le\u00e1ll, nem foglalkozik az esetlegesen m\u00e9g fut\u00f3 sz\u00e1lakkal (mert a threadpool sz\u00e1lak h\u00e1tt\u00e9r sz\u00e1lak).

                                "},{"location":"labor/4-tobbszalu/#5-feladat-termelo-fogyaszto-alapu-megoldas","title":"5. Feladat \u2013 Termel\u0151-fogyaszt\u00f3 alap\u00fa megold\u00e1s","text":"

                                Az el\u0151z\u0151 feladatok megold\u00e1sa sor\u00e1n \u00f6nmag\u00e1ban egy j\u00f3l m\u0171k\u00f6d\u0151 komplett megold\u00e1s\u00e1t kaptuk az eredeti probl\u00e9m\u00e1nak, mely lehet\u0151v\u00e9 teszi, hogy ak\u00e1r t\u00f6bb munkasz\u00e1l is p\u00e1rhuzamosan dolgozzon a h\u00e1tt\u00e9rben a sz\u00e1m\u00edt\u00e1son, ha a gombot sokszor egym\u00e1s ut\u00e1n megnyomjuk. A k\u00f6vetkez\u0151kben \u00fagy fogjuk m\u00f3dos\u00edtani az alkalmaz\u00e1sunkat, hogy a gombnyom\u00e1sra ne mindig keletkezzen \u00faj sz\u00e1l, hanem a feladatok beker\u00fcljenek egy feladatsorba, ahonnan t\u00f6bb, a h\u00e1tt\u00e9rben folyamatosan fut\u00f3 sz\u00e1l egym\u00e1s ut\u00e1n fogja kivenni \u0151ket \u00e9s v\u00e9grehajtani. Ez a feladat a klasszikus termel\u0151-fogyaszt\u00f3 probl\u00e9ma, mely a gyakorlatban is sokszor el\u0151fordul, a m\u0171k\u00f6d\u00e9s\u00e9t az al\u00e1bbi \u00e1bra szeml\u00e9lteti.

                                Termel\u0151 fogyaszt\u00f3 vs ThreadPool

                                Ha belegondolunk, a ThreadPool is egy speci\u00e1lis, a .NET \u00e1ltal sz\u00e1munkra biztos\u00edtott termel\u0151-fogyaszt\u00f3 \u00e9s \u00fctemez\u0151 mechanizmus. A k\u00f6vetkez\u0151kben egy m\u00e1s jelleg\u0171 termel\u0151-fogyaszt\u00f3 megold\u00e1st dolgozunk ki annak \u00e9rdek\u00e9ben, hogy bizonyos sz\u00e1lkezel\u00e9ssel kapcsolatos konkurencia probl\u00e9m\u00e1kkal tal\u00e1lkozhassunk.

                                A f\u0151sz\u00e1lunk a termel\u0151, a Calculate result gombra kattintva hoz l\u00e9tre egy \u00faj feladatot. Fogyaszt\u00f3/feldolgoz\u00f3 munkasz\u00e1lb\u00f3l az\u00e9rt ind\u00edtunk majd t\u00f6bbet, mert \u00edgy t\u00f6bb CPU magot is ki tudunk haszn\u00e1lni, valamint a feladatok v\u00e9grehajt\u00e1s\u00e1t p\u00e1rhuzamos\u00edtani tudjuk.

                                A feladatok ideiglenes t\u00e1rol\u00e1s\u00e1ra a kiindul\u00f3 projekt\u00fcnkben m\u00e1r n\u00e9mik\u00e9ppen el\u0151k\u00e9sz\u00edtett DataFifo oszt\u00e1lyt tudjuk haszn\u00e1lni (a Solution Explorerben a Data mapp\u00e1ban tal\u00e1lhat\u00f3). N\u00e9zz\u00fck meg a forr\u00e1sk\u00f3dj\u00e1t. Egy egyszer\u0171 FIFO sort val\u00f3s\u00edt meg, melyben double[] elemeket t\u00e1rol. A Put met\u00f3dus hozz\u00e1f\u0171zi a bels\u0151 lista v\u00e9g\u00e9hez az \u00faj p\u00e1rokat, m\u00edg a TryGet met\u00f3dus visszaadja (\u00e9s elt\u00e1vol\u00edtja) a bels\u0151 lista els\u0151 elem\u00e9t. Amennyiben a lista \u00fcres, a f\u00fcggv\u00e9ny nem tud visszaadni elemet. Ilyenkor a false visszat\u00e9r\u00e9si \u00e9rt\u00e9kkel jelzi ezt.

                                1. M\u00f3dos\u00edtsuk a gomb esem\u00e9nykezel\u0151j\u00e9t, hogy ne ThreadPoolba dolgozzon, hanem a FIFO-ba:

                                  private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        _fifo.Put(parameters);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n
                                2. K\u00e9sz\u00edts\u00fck el az \u00faj sz\u00e1lkezel\u0151 f\u00fcggv\u00e9ny na\u00edv implement\u00e1ci\u00f3j\u00e1t az \u0171rlap oszt\u00e1lyunkban:

                                  private void WorkerThread()\n{\n    while (true)\n    {\n        if (_fifo.TryGet(out var data))\n        {\n            double result = Algorithms.SuperAlgorithm.Calculate(data);\n            ShowResult(data, result);\n        }\n\n        Thread.Sleep(500);\n    }\n}\n

                                  A Thread.Sleep bevezet\u00e9s\u00e9re az\u00e9rt van sz\u00fcks\u00e9g, mert e n\u00e9lk\u00fcl a munkasz\u00e1lak \u00fcres FIFO eset\u00e9n folyamatosan feleslegesen p\u00f6r\u00f6gn\u00e9nek, semmi hasznos m\u0171veletet nem v\u00e9gezve is 100%-ban kiterheln\u00e9nek egy-egy CPU magot. Megold\u00e1sunk nem ide\u00e1lis, k\u00e9s\u0151bb tov\u00e1bbfejlesztj\u00fck.

                                3. Hozzuk l\u00e9tre, \u00e9s ind\u00edtsuk el a feldolgoz\u00f3 sz\u00e1lakat a konstruktorban:

                                  new Thread(WorkerThread) { Name = \"Worker thread 1\" }.Start();\nnew Thread(WorkerThread) { Name = \"Worker thread 2\" }.Start();\nnew Thread(WorkerThread) { Name = \"Worker thread 3\" }.Start();\n
                                4. Ind\u00edtsuk el az alkalmaz\u00e1st, majd z\u00e1rjuk is be azonnal an\u00e9lk\u00fcl, hogy a Calculate Result gombra kattintan\u00e1nk. Az tapasztaljuk, hogy az ablakunk bez\u00e1r\u00f3dik ugyan, de a processz\u00fcnk tov\u00e1bb fut, az alkalmaz\u00e1s bez\u00e1r\u00e1s\u00e1ra csak a Visual Studiob\u00f3l, vagy a Task Managerb\u0151l van lehet\u0151s\u00e9g:

                                  A feldolgoz\u00f3 sz\u00e1lak el\u0151t\u00e9rsz\u00e1lak, kil\u00e9p\u00e9skor megakad\u00e1lyozz\u00e1k a processz megsz\u0171n\u00e9s\u00e9t. Az egyik megold\u00e1s az lehetne, ha a sz\u00e1lak IsBackground tulajdons\u00e1g\u00e1t true-ra \u00e1ll\u00edtan\u00e1nk a l\u00e9trehoz\u00e1sukat k\u00f6vet\u0151en. A m\u00e1sik megold\u00e1s, hogy kil\u00e9p\u00e9skor gondoskodunk a feldolgoz\u00f3 sz\u00e1lak kil\u00e9ptet\u00e9s\u00e9r\u0151l. Egyel\u0151re tegy\u00fck f\u00e9lre ezt a probl\u00e9m\u00e1t, k\u00e9s\u0151bb visszat\u00e9r\u00fcnk r\u00e1.

                                5. Ind\u00edtsuk el az alkalmaz\u00e1st azt tapasztaljuk, hogy miut\u00e1n kattintunk a Calculate Result gombon (csak egyszer kattintsunk rajta) nagy val\u00f3sz\u00edn\u0171s\u00e9ggel kiv\u00e9telt fogunk kapni. A probl\u00e9ma az, hogy a DataFifo nem sz\u00e1lbiztos, inkonzisztens\u00e9 v\u00e1lt. K\u00e9t ered\u0151 ok is h\u00faz\u00f3dik a h\u00e1tt\u00e9rben:

                                "},{"location":"labor/4-tobbszalu/#problema-1","title":"Probl\u00e9ma 1","text":"

                                N\u00e9zz\u00fck a k\u00f6vetkez\u0151 forgat\u00f3k\u00f6nyvet:

                                1. A sor \u00fcres. A feldolgoz\u00f3 sz\u00e1lak egy while ciklusban folyamatosan pollozz\u00e1k a FIFO-t, vagyis h\u00edvj\u00e1k a TryGet met\u00f3dus\u00e1t.
                                2. A felhaszn\u00e1l\u00f3 egy feladatot tesz a sorba.
                                3. Az egyik feldolgoz\u00f3 sz\u00e1l a TryGet met\u00f3dusban azt l\u00e1tja, van adat a sorban, vagyis if ( _innerList.Count > 0 ) k\u00f3dsor felt\u00e9tele teljes\u00fcl, \u00e9s r\u00e1l\u00e9p a k\u00f6vetkez\u0151 k\u00f3dsorra. Tegy\u00fck fel, hogy ez a sz\u00e1l ebben a pillanatban elveszti a fut\u00e1si jog\u00e1t, m\u00e1r nincs ideje kivenni az adatot a sorb\u00f3l.
                                4. Egy m\u00e1sik feldolgoz\u00f3 sz\u00e1l is \u00e9ppen ekkor ejti meg az if ( _innerList.Count > 0 ) vizsg\u00e1latot, n\u00e1la is teljes\u00fcl a felt\u00e9tel, \u00e9s ez a sz\u00e1l ki is veszi az adatot a sorb\u00f3l.
                                5. Az els\u0151 sz\u00e1lunk \u00fajra \u00fctemez\u00e9sre ker\u00fcl, fel\u00e9bred, \u0151 is megpr\u00f3b\u00e1lja kivenni az adatot a sorb\u00f3l: a sor viszont m\u00e1r \u00fcres, a m\u00e1sik sz\u00e1lunk kivette az egyetlen adatot a sorb\u00f3l az orra el\u0151tt. \u00cdgy az _innerList[0] hozz\u00e1f\u00e9r\u00e9s kiv\u00e9telt eredm\u00e9nyez.

                                Ezt a probl\u00e9m\u00e1t csak \u00fagy tudjuk elker\u00fclni, ha a sor \u00fcress\u00e9g\u00e9nek a vizsg\u00e1lat\u00e1t \u00e9s az elem kiv\u00e9tel\u00e9t \"oszthatatlann\u00e1\" tessz\u00fck: ez azt jelenti, hogy am\u00edg az egyik sz\u00e1l nem v\u00e9gzett mindkett\u0151vel, addig a t\u00f6bbi sz\u00e1lnak v\u00e1rnia kell!

                                Thread.Sleep(500)

                                Az \u00fcress\u00e9gvizsg\u00e1latot figyel\u0151 k\u00f3dsort k\u00f6vet\u0151 Thread.Sleep(500); k\u00f3dsornak csak az a szerepe a p\u00e9ldak\u00f3dunkban, hogy a fenti peches forgat\u00f3k\u00f6nyv bek\u00f6vetkez\u00e9s\u00e9nek a val\u00f3sz\u00edn\u0171s\u00e9g\u00e9t megn\u00f6velje, s \u00edgy a p\u00e9ld\u00e1t szeml\u00e9letesebb\u00e9 tegye (mivel ilyenkor szinte biztos, hogy \u00e1t\u00fctemez\u0151dik a sz\u00e1l). A k\u00e9s\u0151bbiekben ezt ki is fogjuk venni, egyel\u0151re hagyjuk benne.

                                "},{"location":"labor/4-tobbszalu/#problema-2","title":"Probl\u00e9ma 2","text":"

                                A DataFifo oszt\u00e1ly egyid\u0151ben t\u00f6bb sz\u00e1lb\u00f3l is hozz\u00e1f\u00e9rhet a List<double[]> t\u00edpus\u00fa _innerList tagv\u00e1ltoz\u00f3hoz. Ugyanakkor, ha megn\u00e9zz\u00fck a List<T> dokument\u00e1ci\u00f3j\u00e1t, azt tal\u00e1ljuk, hogy az oszt\u00e1ly nem sz\u00e1lbiztos (not thread safe). Ez esetben viszont ezt nem tehetj\u00fck meg, nek\u00fcnk kell a k\u00f6lcs\u00f6n\u00f6s kiz\u00e1r\u00e1st z\u00e1rakkal biztos\u00edtanunk: meg kell oldjuk, hogy a sz\u00e1laink egyid\u0151ben csak egy met\u00f3dus\u00e1hoz / tulajdons\u00e1g\u00e1hoz / tagv\u00e1ltoz\u00f3j\u00e1hoz f\u00e9rjenek hozz\u00e1 (pontosabban inkonzisztencia csak egyidej\u0171 \u00edr\u00e1s, illetve egyidej\u0171 \u00edr\u00e1s \u00e9s olvas\u00e1s eset\u00e9n l\u00e9phet fel, de az \u00edr\u00f3kat \u00e9s az olvas\u00f3kat a legt\u00f6bb esetben nem szoktuk megk\u00fcl\u00f6nb\u00f6ztetni, itt sem tessz\u00fck).

                                A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben a DataFifo oszt\u00e1lyunkat sz\u00e1lbiztoss\u00e1 tessz\u00fck, amivel megakad\u00e1lyozzuk, hogy a fenti k\u00e9t probl\u00e9ma bek\u00f6vetkezhessen.

                                "},{"location":"labor/4-tobbszalu/#6-feladat-tegyuk-szabiztossa-a-datafifo-osztalyt","title":"6. feladat \u2013 Tegy\u00fck sz\u00e1biztoss\u00e1 a DataFifo oszt\u00e1lyt","text":"

                                A DataFifo oszt\u00e1ly sz\u00e1lbiztoss\u00e1 t\u00e9tel\u00e9hez sz\u00fcks\u00e9g\u00fcnk van egy objektumra (ez b\u00e1rmilyen referencia t\u00edpus\u00fa objektum lehet), melyet kulcsk\u00e9nt haszn\u00e1lhatunk a z\u00e1rol\u00e1sn\u00e1l. Ezt k\u00f6vet\u0151en a lock kulcssz\u00f3 seg\u00edts\u00e9g\u00e9vel el tudjuk \u00e9rni, hogy egyszerre mindig csak egy sz\u00e1l tart\u00f3zkodjon az adott kulccsal v\u00e9dett blokkokban.

                                1. Vegy\u00fcnk fel egy object t\u00edpus\u00fa mez\u0151t _syncRoot n\u00e9ven a DataFifo oszt\u00e1lyba.

                                  private object _syncRoot = new object();\n
                                2. Eg\u00e9sz\u00edts\u00fck ki a Put \u00e9s a TryGet f\u00fcggv\u00e9nyeket a z\u00e1rol\u00e1ssal.

                                  public void Put(double[] data)\n{\n    lock (_syncRoot)\n    {\n        _innerList.Add(data); \n    }\n}\n
                                  public bool TryGet(out double[] data)\n{\n    lock (_syncRoot)\n    {\n        if (_innerList.Count > 0)\n        {\n            Thread.Sleep(500);\n\n            data = _innerList[0];\n            _innerList.RemoveAt(0);\n            return true;\n        }\n\n        data = null;\n        return false;\n    }\n}\n

                                  Surround with

                                  Haszn\u00e1ljuk a Visual Studio Surround with funkci\u00f3j\u00e1t a CTRL + K, CTRL + S billenty\u0171 kombin\u00e1ci\u00f3j\u00e1val a k\u00f6r\u00fclvenni k\u00edv\u00e1nt kijel\u00f6lt k\u00f3dr\u00e9szleten.

                                Most m\u00e1r nem szabad kiv\u00e9telt kapnunk.

                                Ki is vehetj\u00fck a TryGet met\u00f3dusb\u00f3l a mesters\u00e9ges k\u00e9sleltet\u00e9st (Thread.Sleep(500); sor).

                                Lockol\u00e1s this-en

                                Felmer\u00fclhet a k\u00e9rd\u00e9s, hogy mi\u00e9rt vezett\u00fcnk be egy k\u00fcl\u00f6n _syncRoot tagv\u00e1ltoz\u00f3t \u00e9s haszn\u00e1ltuk ezt z\u00e1rol\u00e1sra a lock param\u00e9terek\u00e9nt, amikor a this-t is haszn\u00e1lhattuk volna helyette (a DataFifo referencia t\u00edpus, \u00edgy ennek nem lenne akad\u00e1lya). A this alkalmaz\u00e1sa azonban s\u00e9rten\u00e9 az oszt\u00e1lyunk egys\u00e9gbez\u00e1r\u00e1s\u00e1t! Ne feledj\u00fck: a this egy referencia az objektumunkra, de m\u00e1s oszt\u00e1lyoknak is van ugyanerre az objektumra referenci\u00e1juk (pl. eset\u00fcnkben a MainWindow-nak van referenci\u00e1ja a DataFifo-ra), \u00e9s ha ezek a k\u00fcls\u0151 oszt\u00e1lyok z\u00e1rat tesznek a lock seg\u00edts\u00e9g\u00e9vel az objektumra, akkor az \"interfer\u00e1l\" az \u00e1ltalunk az oszt\u00e1lyon bel\u00fck haszn\u00e1lt z\u00e1rol\u00e1ssal (mivel this alkalmaz\u00e1sa miatt a k\u00fcls\u0151 \u00e9s bels\u0151 lock-ok param\u00e9tere ugyanaz lesz). \u00cdgy pl. egy k\u00fcls\u0151 z\u00e1rral teljesen meg lehet \"b\u00e9n\u00edtani\" a TryGet \u00e9s Put m\u0171velet m\u0171k\u00f6d\u00e9s\u00e9t. Ezzel szemben az \u00e1ltalunk v\u00e1lasztott megold\u00e1sban a lock param\u00e9tere, a _syncRoot v\u00e1ltoz\u00f3 priv\u00e1t, ehhez m\u00e1r k\u00fcls\u0151 oszt\u00e1lyok nem f\u00e9rhetnek hozz\u00e1, \u00edgy nem is zavarhatj\u00e1k meg az oszt\u00e1lyunk bels\u0151 m\u0171k\u00f6d\u00e9s\u00e9t.

                                "},{"location":"labor/4-tobbszalu/#7-feladat-hatekony-jelzes-megvalositasa","title":"7. feladat \u2013 Hat\u00e9kony jelz\u00e9s megval\u00f3s\u00edt\u00e1sa","text":""},{"location":"labor/4-tobbszalu/#manualresetevent-hasznalata","title":"ManualResetEvent haszn\u00e1lata","text":"

                                A WorkerThread-ben folyamatosan fut\u00f3 while ciklus \u00fan. akt\u00edv v\u00e1rakoz\u00e1st val\u00f3s\u00edt meg, ami mindig ker\u00fclend\u0151. Ha a Thread.Sleep-et nem tett\u00fck volna a ciklusmagba, akkor ezzel maximumra ki is terheln\u00e9 a processzort. A Thread.Sleep megoldja ugyan a processzor terhel\u00e9s probl\u00e9m\u00e1t, de bevezet egy m\u00e1sikat: ha mindh\u00e1rom munkasz\u00e1lunk \u00e9ppen alv\u00f3 \u00e1llapotba l\u00e9pett, mikor be\u00e9rkezik egy \u00faj adat, akkor feleslegesen v\u00e1runk 500 ms-ot az adat feldolgoz\u00e1s\u00e1nak megkezd\u00e9s\u00e9ig.

                                A k\u00f6vetkez\u0151kben \u00fagy fogjuk m\u00f3dos\u00edtani az alkalmaz\u00e1st, hogy blokkolva v\u00e1rakozzon, am\u00edg adat nem ker\u00fcl a FIFO-ba (amikor viszont adat ker\u00fcl bele, azonnal kezdje meg a feldolgoz\u00e1st). Annak jelz\u00e9s\u00e9re, hogy van-e adat a sorban egy ManualResetEvent-et fogunk haszn\u00e1lni.

                                1. Adjunk hozz\u00e1 egy MaunalResetEvent p\u00e9ld\u00e1nyt a DataFifo oszt\u00e1lyunkhoz _hasData n\u00e9ven.

                                  // A false konstruktor param\u00e9ter eredm\u00e9nyek\u00e9ppen kezdetben az esem\u00e9ny nem jelzett (kapu csukva)\nprivate ManualResetEvent _hasData = new ManualResetEvent(false);\n
                                2. A _hasData alkalmaz\u00e1sunkban kapuk\u00e9nt viselkedik. Amikor adat ker\u00fcl a list\u00e1ba \u201ekinyitjuk\u201d, m\u00edg amikor ki\u00fcr\u00fcl a lista \u201ebez\u00e1rjuk\u201d.

                                  Az esem\u00e9ny szemantik\u00e1ja \u00e9s elnevez\u00e9se

                                  L\u00e9nyeges, hogy j\u00f3 v\u00e1lasszuk meg az esem\u00e9ny\u00fcnk szemantik\u00e1j\u00e1t \u00e9s ezt a v\u00e1ltoz\u00f3nk nev\u00e9vel pontosan ki is fejezz\u00fck. A p\u00e9ld\u00e1nkban a _hasData n\u00e9v j\u00f3l kifejezi, hogy pontosan akkor \u00e9s csak akkor jelzett az esem\u00e9ny\u00fcnk (nyitott a kapu), amikor van feldolgozand\u00f3 adat. Most m\u00e1r \"csak\" az a dolgunk, hogy ezt a szemantik\u00e1t megval\u00f3s\u00edtsuk: jelzettbe tegy\u00fck az esem\u00e9nyt, mikor adat ker\u00fcl a FIFO-ba, \u00e9s jelzetlenbe, amikor ki\u00fcr\u00fcl a FIFO.

                                  public void Put(double[] data)\n{\n    lock (_syncRoot)\n    {\n        _innerList.Add(data);\n        _hasData.Set();\n    }\n}\n
                                  public bool TryGet(out double[] data)\n{\n    lock (_syncRoot)\n    {\n        if (_innerList.Count > 0)\n        {\n            data = _innerList[0];\n            _innerList.RemoveAt(0);\n            if (_innerList.Count == 0)\n            {\n                _hasData.Reset();\n            }\n\n            return true;\n        }\n\n        data = null;\n        return false;\n    }\n}\n
                                "},{"location":"labor/4-tobbszalu/#jelzesre-varakozas-blokkolo-a-get","title":"Jelz\u00e9sre v\u00e1rakoz\u00e1s (blokkol\u00f3 a Get)","text":"

                                Az el\u0151z\u0151 pontban megoldottuk a jelz\u00e9st, \u00e1m ez \u00f6nmag\u00e1ban nem sokat \u00e9r, hiszen nem v\u00e1rakoznak r\u00e1. Ennek megval\u00f3s\u00edt\u00e1sa j\u00f6n most.

                                1. M\u00f3dos\u00edtsuk a met\u00f3dust az al\u00e1bbiak szerint: sz\u00farjuk be a _hasData esem\u00e9nyre v\u00e1rakoz\u00e1st.

                                  public bool TryGet(out double[] data)\n{\n    lock (_syncRoot)\n    {\n        _hasData.WaitOne();\n\n        if (_innerList.Count > 0)\n            // ...\n

                                  A WaitOne m\u0171velet visszat\u00e9r\u00e9si \u00e9rt\u00e9ke

                                  A WaitOne m\u0171velet egy bool \u00e9rt\u00e9kkel t\u00e9r vissza, mely igaz, ha a WaitOne param\u00e9ter\u00e9ben megadott id\u0151korl\u00e1t el\u0151tt jelzett \u00e1llapotba ker\u00fcl az esem\u00e9ny (ill. ennek megfelel\u0151en hamis, ha lej\u00e1rt az id\u0151korl\u00e1t). A p\u00e9ld\u00e1nkban nem adtunk meg id\u0151korl\u00e1tot param\u00e9terben, mely v\u00e9gtelen id\u0151korl\u00e1t alkalmaz\u00e1s\u00e1t jelenti. Ennek megfelel\u0151en nem is vizsg\u00e1ljuk a visszat\u00e9r\u00e9si \u00e9rt\u00e9k\u00e9t (mert v\u00e9gtelen ideig v\u00e1r jelz\u00e9sre).

                                2. Ezzel a Thread.Sleep a WorkerThread-ben feleslegess\u00e9 v\u00e1lt, kommentezz\u00fck ki!

                                  A fenti megold\u00e1s futtat\u00e1sakor azt tapasztaljuk, hogy az alkalmaz\u00e1sunk fel\u00fclete az els\u0151 gombnyom\u00e1st k\u00f6vet\u0151en befagy. Az el\u0151z\u0151 megold\u00e1sunkban ugyanis egy amat\u0151r hib\u00e1t k\u00f6vett\u00fcnk el. A lock-olt k\u00f3dr\u00e9szleten bel\u00fcl v\u00e1rakozunk a _hasData jelz\u00e9s\u00e9re, \u00edgy a f\u0151sz\u00e1lnak lehet\u0151s\u00e9ge sincs arra, hogy a Put m\u0171veletben (egy szint\u00e9n lock-kal v\u00e9dett r\u00e9szen bel\u00fcl) jelz\u00e9st k\u00fcldj\u00f6n _hasData-val. Gyakorlatilag egy holtpont (deadlock) helyzet alakult ki. Fontos, hogy a k\u00f3dot n\u00e9zve gondoljuk \u00e1t r\u00e9szleteiben:

                                  • A TryGet-ben az egyik munkasz\u00e1l (mely bejutott a lock blokkba a h\u00e1rom k\u00f6z\u00fcl), a _hasData.WaitOne() sorban arra v\u00e1r, hogy a f\u0151 sz\u00e1l Put-ban a _hasData-t jelzettbe \u00e1ll\u00edtsa.
                                  • A Put-ban a lock sorban f\u0151 sz\u00e1l arra v\u00e1r, hogy az el\u0151z\u0151 pontban eml\u00edtett munkasz\u00e1l a TryGet-ben kil\u00e9pjen a lock blokkb\u00f3l.

                                  K\u00f6lcs\u00f6n\u00f6sen egym\u00e1sra v\u00e1rnak v\u00e9gtelen ideig, ez a holtpont/deadlock klasszikus esete.

                                  Pr\u00f3b\u00e1lkozhatn\u00e1nk egy id\u0151korl\u00e1t megad\u00e1s\u00e1val (ms) a v\u00e1rakoz\u00e1sn\u00e1l (ez nem kell megval\u00f3s\u00edtani):

                                  if (_hasData.WaitOne(100))\n

                                  Ez \u00f6nmag\u00e1ban sem lenne eleg\u00e1ns megold\u00e1s, r\u00e1ad\u00e1sul a folyamatosan polloz\u00f3 munkasz\u00e1lak jelent\u0151sen ki\u00e9heztetn\u00e9k a Put-ot h\u00edv\u00f3 sz\u00e1lat! Helyette, az eleg\u00e1ns \u00e9s k\u00f6vetend\u0151 minta az, hogy lock-on bel\u00fcl ker\u00fclj\u00fck a blokkolva v\u00e1rakoz\u00e1st.

                                  Jav\u00edt\u00e1sk\u00e9nt cser\u00e9lj\u00fck meg a lock-ot \u00e9s a WaitOne-t:

                                  public bool TryGet(out double[] data)\n{\n    _hasData.WaitOne();\n\n    lock (_syncRoot)\n    {\n        if (_innerList.Count > 0)\n        {\n            data = _innerList[0];\n            _innerList.RemoveAt(0);\n            if (_innerList.Count == 0)\n            {\n                _hasData.Reset();\n            }\n\n            return true;\n        }\n\n        data = null;\n        return false;\n    }\n}\n

                                  Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st, most m\u00e1r j\u00f3l m\u0171k\u00f6dik.

                                3. A lock-on bel\u00fcli \u00fcress\u00e9g-vizsg\u00e1lat szerepe.

                                  Az el\u0151z\u0151 l\u00e9p\u00e9sben a TryGet-ben bevezett\u00fcnk _hasData n\u00e9ven egy MaunalResetEvent objektumot. Ez pontosan akkor van jelzett \u00e1llapotban, amikor a FIFO-ban van adat. K\u00e9rd\u00e9s, sz\u00fcks\u00e9g van-e m\u00e9g most is a lock blokkban az sor \u00fcress\u00e9g vizsg\u00e1latra (if (_innerList.Count > 0)). Els\u0151 \u00e9rz\u00e9sre redund\u00e1nsnak gondolhatjuk. De pr\u00f3b\u00e1ljuk ki, az if-ben az \u00fcress\u00e9gvizsg\u00e1lat helyett adjunk meg egy fix true \u00e9rt\u00e9ket, ezzel semleges\u00edtve az if hat\u00e1s\u00e1t (az\u00e9rt dolgozunk \u00edgy, hogy k\u00f6nny\u0171 legyen visszacsin\u00e1lni):

                                      ...\n    lock (_syncRoot)\n    {\n        if (true)\n        {\n            data = _innerList[0];\n            ...\n}\n

                                  Pr\u00f3b\u00e1ljuk ki. Egy kiv\u00e9telt fogunk kapni, amikor kattintunk a gombon: \u00edgy m\u00e1r nem sz\u00e1lbiztos a megold\u00e1sunk. Vezess\u00fck le, mi\u00e9rt:

                                  • Amikor elindul az alkalmaz\u00e1s, mindh\u00e1rom feldolgoz\u00f3 sz\u00e1l a TryGet _hasData.WaitOne(); sor\u00e1n\u00e1l v\u00e1r arra, hogy adat ker\u00fclj\u00f6n a FIFO-ba.
                                  • A gombra kattint\u00e1skor a Put m\u0171velet _hasData-t jelzettre \u00e1ll\u00edtja.
                                  • A TryGet _hasData.WaitOne(); sor\u00e1n mindh\u00e1rom sz\u00e1l \u00e1tjut (ez egy ManualResetEvent, ha jelezett, minden sz\u00e1l mehet tov\u00e1bb).
                                  • A TryGet lock blokkj\u00e1ba egyetlen sz\u00e1l jut be, a m\u00e1sik kett\u0151 itt v\u00e1r (lock blokkban egyszerre egy sz\u00e1l lehet): ez a sz\u00e1l kiveszi az egyetlen elemet az _innerList list\u00e1b\u00f3l, majd elhagyja a lock blokkot.
                                  • Most m\u00e1r be tud jutni a lock-n\u00e1l v\u00e1rakoz\u00f3 k\u00e9t sz\u00e1lb\u00f3l (ezek m\u00e1r kor\u00e1bban t\u00faljutottak a hasData.WaitOne() h\u00edv\u00e1son!!!) egy m\u00e1sik is a lock blokkba, az is megpr\u00f3b\u00e1lja a 0. elemet kivenni az _innerList list\u00e1b\u00f3l. De az m\u00e1r nincs ott (az el\u0151z\u0151 l\u00e9p\u00e9sben az els\u0151nek bejut\u00f3 sz\u00e1l elcsente az orra el\u0151l): ebb\u0151l lesz a kiv\u00e9tel.

                                  A megold\u00e1s: biztos\u00edtani kell a lock blockban, hogy ha id\u0151k\u00f6zben egy m\u00e1sik sz\u00e1l ki\u00fcr\u00edtette a sort, akkor a sz\u00e1lunk m\u00e1r ne pr\u00f3b\u00e1ljon elemet kivenni bel\u0151le. Vagyis vissza kell tenni a kor\u00e1bbi \u00fcress\u00e9g vizsg\u00e1latot. Tegy\u00fck is ezt meg! A megold\u00e1sunk \u00edgy j\u00f3l m\u0171k\u00f6dik. El\u0151fordulhat ugyan, hogy feleslegesen fordulunk a list\u00e1hoz, de ezzel \u00edgy most megel\u00e9gsz\u00fcnk.

                                \u00d6sszefoglalva:

                                • Az \u00fcress\u00e9g vizsg\u00e1latra a ManualResetEvent bevezet\u00e9se ut\u00e1n is sz\u00fcks\u00e9g van.
                                • A ManualResetEvent az a c\u00e9lja, hogy feleslegesen ne pollozzuk gyakran a sort, ha az \u00fcres, vagyis az \u00fan. akt\u00edv v\u00e1rakoz\u00e1st ker\u00fclj\u00fck el a seg\u00edts\u00e9g\u00e9vel.

                                A konkurens, t\u00f6bbsz\u00e1l\u00fa k\u00f6rnyezetben val\u00f3 programoz\u00e1s neh\u00e9zs\u00e9gei

                                J\u00f3l illusztr\u00e1lja a feladat, hogy milyen alapos \u00e1tgondol\u00e1st ig\u00e9nyel a konkurens, t\u00f6bbsz\u00e1l\u00fa k\u00f6rnyezetben val\u00f3 programoz\u00e1s. Tulajdonk\u00e9ppen m\u00e9g szerencs\u00e9nk is volt az el\u0151z\u0151ekben, mert j\u00f3l reproduk\u00e1lhat\u00f3an el\u0151j\u00f6tt a hiba. A gyakorlatban azonban ez ritk\u00e1n van \u00edgy. Sajnos sokkal gyakoribb, hogy a konkurenciahib\u00e1k id\u0151nk\u00e9nti, nem reproduk\u00e1lhat\u00f3 probl\u00e9m\u00e1kat okoznak. Az ilyen jelleg\u0171 feladatok megold\u00e1s\u00e1t mindig nagyon \u00e1t kell gondolni, nem lehet az \"addig-pr\u00f3b\u00e1lkozom-m\u00edg-j\u00f3-nem-lesz-a-k\u00e9zi-teszt-sor\u00e1n\" elv ment\u00e9n leprogramozni.

                                System.Collections.Concurrent

                                A .NET keretrendszerben t\u00f6bb be\u00e9p\u00edtett sz\u00e1lbiztoss\u00e1gra felk\u00e9sz\u00edtett oszt\u00e1ly is tal\u00e1lhat\u00f3 a System.Collections.Concurrent n\u00e9vt\u00e9rben. A fenti p\u00e9ld\u00e1ban a DataFifo oszt\u00e1lyt a System.Collections.Concurrent.ConcurrentQueue oszt\u00e1llyal kiv\u00e1lthattuk volna.

                                "},{"location":"labor/4-tobbszalu/#8-feladat-kulturalt-leallas","title":"8. feladat \u2013 Kultur\u00e1lt le\u00e1ll\u00e1s","text":"

                                Kor\u00e1bban f\u00e9lretett\u00fck azt a probl\u00e9m\u00e1t, hogy az ablakunk bez\u00e1r\u00e1sakor a processz\u00fcnk \u201eberagad\u201d, ugyanis a feldolgoz\u00f3 munkasz\u00e1lak el\u0151t\u00e9rsz\u00e1lak, kil\u00e9ptet\u00e9s\u00fcket eddig nem oldottuk meg. C\u00e9lunk, hogy a v\u00e9gtelen while ciklust kiv\u00e1ltva a munkasz\u00e1laink az alkalmaz\u00e1s bez\u00e1r\u00e1sakor kultur\u00e1lt m\u00f3don \u00e1lljanak le.

                                1. Egy ManualResetEvent seg\u00edts\u00e9g\u00e9vel jelezz\u00fck a le\u00e1ll\u00edt\u00e1st a FIFO-ban a TryGet-ben t\u00f6rt\u00e9n\u0151 v\u00e1rakoz\u00e1s sor\u00e1n. A FIFO-ban vegy\u00fcnk fel egy \u00faj ManualResetEvent-et, \u00e9s vezess\u00fcnk be egy Release m\u0171veletet, amellyel a v\u00e1rakoz\u00e1sainkat z\u00e1rhatjuk r\u00f6vidre (\u00faj esem\u00e9ny\u00fcnk jelzett \u00e1llapotba \u00e1ll\u00edthat\u00f3).

                                  private ManualResetEvent _releaseTryGet = new ManualResetEvent(false);\n\npublic void Release()\n{\n    _releaseTryGet.Set();\n}\n
                                2. A TryGet-ben erre az esem\u00e9nyre is v\u00e1rakozzunk. A WaitAny met\u00f3dus akkor engedi tov\u00e1bb a futtat\u00e1st, ha a param\u00e9terk\u00e9nt megadott WaitHandle t\u00edpus\u00fa objektumok k\u00f6z\u00fcl valamelyik jelzett \u00e1llapotba ker\u00fcl, \u00e9s visszaadja annak t\u00f6mbb\u00e9li index\u00e9t. T\u00e9nyleges adatfeldolgoz\u00e1st pedig csak akkor szeretn\u00e9nk, ha a _hasData jelzett (amikor is a WaitAny 0-val t\u00e9r vissza).

                                  public bool TryGet(out double[] data)\n{\n    if (WaitHandle.WaitAny(new[] { _hasData, _releaseTryGet }) == 0)\n    {\n        lock (_syncRoot)\n        {\n
                                3. MainWindow.xaml.cs-ban vegy\u00fcnk fel egy flag tagv\u00e1ltoz\u00f3t a bez\u00e1r\u00e1s jelz\u00e9s\u00e9re:

                                  private bool _isClosed = false;\n
                                4. A f\u0151ablak bez\u00e1r\u00e1sakor \u00e1ll\u00edtsuk jelzettre az \u00faj esem\u00e9nyt \u00e9s billents\u00fcnk be a flag-et is: a MainWindow oszt\u00e1ly Closed esem\u00e9ny\u00e9re iratkozzunk fel a konstruktorban, \u00e9s \u00edrjuk meg a megfelel\u0151 esem\u00e9nykezel\u0151 f\u00fcggv\u00e9nyt:

                                  public MainWindow()\n{\n    ...\n\n    Closed += MainWindow_Closed;\n}\n\nprivate void MainWindow_Closed(object sender, WindowEventArgs args)\n{\n    _isClosed = true;\n    _fifo.Release();\n}\n
                                5. \u00cdrjuk \u00e1t a while ciklust az el\u0151z\u0151 pontban felvett flag figyel\u00e9s\u00e9re.

                                  private void WorkerThread()\n{\n    while (!_isClosed)\n    {\n
                                6. V\u00e9g\u00fcl biztos\u00edtsuk, hogy a m\u00e1r bez\u00e1r\u00f3d\u00f3 ablak eset\u00e9ben ne pr\u00f3b\u00e1ljunk \u00fczeneteket ki\u00edrni

                                  private void ShowResult(double[] parameters, double result)\n{\n    if (_isClosed)\n        return;\n
                                7. Futtassuk az alkalmaz\u00e1st, \u00e9s ellen\u0151rizz\u00fck, kil\u00e9p\u00e9skor az processz\u00fcnk val\u00f3ban befejezi-e a fut\u00e1s\u00e1t.

                                "},{"location":"labor/4-tobbszalu/#kitekintes-task-async-await","title":"Kitekint\u00e9s: Task, async, await","text":"

                                A gyakorlat sor\u00e1n az alacsonyabb szint\u0171 sz\u00e1lkezel\u00e9si technik\u00e1kkal k\u00edv\u00e1ntunk megismerkedni. Ugyanakkor megold\u00e1sunkat (legal\u00e1bbis r\u00e9szben) \u00e9p\u00edthett\u00fck volna a .NET aszinkron programoz\u00e1st t\u00e1mogat\u00f3 magasabb szint\u0171 eszk\u00f6zeire \u00e9s mechanizmusaira, \u00fagymint Task/Task<T> oszt\u00e1lyok \u00e9s async/await kulcsszavak.

                                "},{"location":"labor/4-tobbszalu/index_ger/","title":"4. Erstellung von mehrf\u00e4digen Anwendungen","text":""},{"location":"labor/4-tobbszalu/index_ger/#das-ziel-der-ubung","title":"Das Ziel der \u00dcbung","text":"

                                Ziel der \u00dcbung ist, dass die Studenten mit den Grunds\u00e4tzen kennenzulernen, die bei der Programmierung von mehreren Threads beachtet werden m\u00fcssen. Behandelte Themen (unter anderem):

                                • Einen Thread starten (Thread)
                                • Einen Thread beenden
                                • Erstellen von faedensicheren (thread safe) Klassen mit dem Schl\u00fcsselwort lock
                                • ThreadPool verwenden
                                • Signalisieren und Synchronisation von auf Signal wartenden Threads mit der Hilfe von ManualResetEvent (WaitHandle)
                                • Besonderheiten des WinUI-Threadings (DispatcherQueue)

                                Da das Thema sehr umfangreich ist, werden Sie nat\u00fcrlich nur Grundkenntnisse erwerben, aber mit diesem Wissen werden Sie in der Lage sein, komplexere Aufgaben selbst\u00e4ndig zu bearbeiten.

                                Zugeh\u00f6rige Vorlesungen: Entwicklung konkurrierender (meghrf\u00e4digen) Anwendungen.

                                "},{"location":"labor/4-tobbszalu/index_ger/#voraussetzungen","title":"Voraussetzungen","text":"

                                Die f\u00fcr die Durchf\u00fchrung der \u00dcbung ben\u00f6tigten Werkzeuge:

                                • Visual Studio 2022
                                  • Windows Desktop Development Workload
                                • Betriebssystem Windows 10 oder Windows 11 (Linux und macOS nicht geeignet)
                                "},{"location":"labor/4-tobbszalu/index_ger/#losung","title":"L\u00f6sung","text":"Laden Sie die fertige L\u00f6sung herunter

                                Es ist wichtig, dass Sie sich w\u00e4hrend des Praktikums an die Anleitung halten. Es ist verboten (und sinnlos), die fertige L\u00f6sung herunterzuladen. Allerdings kann es bei der anschlie\u00dfenden Selbstein\u00fcbung n\u00fctzlich sein, die fertige L\u00f6sung zu \u00fcberpr\u00fcfen, daher stellen wir sie zur Verf\u00fcgung.

                                Die L\u00f6sung ist [verf\u00fcgbar auf GitHub] (https://github.com/bmeviauab00/lab-tobbszalu-kiindulo/tree/megoldas). Der einfachste Weg, es herunterzuladen, ist, den git clone-Zweig von der Kommandozeile aus zu klonen:

                                git clone https://github.com/bmeviauab00/lab-tobbszalu-kiindulo -b solved

                                Sie m\u00fcssen Git auf Ihrem Rechner installiert haben, weitere Informationen hier.

                                "},{"location":"labor/4-tobbszalu/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"

                                Die Verwaltung parallel laufender Threads ist ein Bereich mit hoher Priorit\u00e4t, den alle Softwareentwickler zumindest in den Grundlagen kennen sollten. In der \u00dcbung l\u00f6sen wir grundlegende, aber vorrangige Probleme, so dass wir uns bem\u00fchen sollten, nicht nur das Endergebnis, sondern auch die Bedeutung und die Gr\u00fcnde f\u00fcr die von uns vorgenommenen \u00c4nderungen zu verstehen.

                                In dieser \u00dcbung werden wir einer einfachen WinUI-Anwendung mehrf\u00e4dige F\u00e4higkeiten hinzuf\u00fcgen und zunehmend komplexere Aufgaben l\u00f6sen. Das Grundproblem ist folgendes: Wir haben eine Funktion, die lange l\u00e4uft, und wie wir sehen werden, hat der \"direkte\" Aufruf \u00fcber die Benutzeroberfl\u00e4che unangenehme Folgen. W\u00e4hrend dem L\u00f6sen werden wir eine bestehende Anwendung mit eigenen Codezeile erg\u00e4nzen. Neue Zeilen, die eingef\u00fcgt werden sollen, sind in der Anleitung durch einen hervorgehobenen Hintergrund gekennzeichnet.

                                "},{"location":"labor/4-tobbszalu/index_ger/#0-aufgabe-kennenlernen-des-anfangsprojekt-vorbereitung","title":"0. Aufgabe - Kennenlernen des Anfangsprojekt, Vorbereitung","text":"

                                Klonen wir das Repository der urspr\u00fcnglichen Anwendung f\u00fcr \u00dcbung 4:

                                • \u00d6ffnen wir ein command prompt
                                • Navigieren wir zu einem Ordner unserer Wahl, zum Beispiel c:\\work\\NEPTUN
                                • Geben wir den folgenden Befehl ein: git clone https://github.com/bmeviauab00/lab-tobbszalu-kiindulo.git
                                • \u00d6ffnen wir das Solution SuperCalculator.sln in Visual Studio.

                                Unsere Aufgabe ist es, eine Benutzeroberfl\u00e4che unter Verwendung der WinUI-Technologie zu erstellen, um einen in bin\u00e4rer Form erreichbaren Algorithmus auszuf\u00fchren. Die bin\u00e4re Form von .NET ist eine Datei mit der Erweiterung .dll, die in der Programmiersprache eine Klassenbibliothek darstellt. In unserem Fall lautet der Dateiname Algorithms.dll, der sich im geklonten Git-Repository befindet.

                                In der Anfangsprojekt ist die Benutzeroberfl\u00e4che bereits vorbereitet. F\u00fchren wir die Anwendung aus:

                                In der Benutzeroberfl\u00e4che der Anwendung k\u00f6nnen wir die Eingabeparametern des Algorithmus angeben (double array of numbers): in unserem Beispiel rufen wir den Algorithmus immer mit zwei double Zahlenparametern auf, die in den zwei oberen Textfeldern angegeben werden k\u00f6nnen. Unsere Aufgabe ist es, den Algorithmus mit den angegebenen Parametern auszuf\u00fchren, falls wir auf die Taste Calculate Result klicken, und wenn er fertig ist, das Ergebnis mit den Eingabeparametern in einer neuen Zeile des Listenfeldes unterhalb des Results anzuzeigen.

                                In der n\u00e4chsten Schritten schauen wir zuerst das heruntergeladene Visual Studio Solution an:

                                Die Rahmenanwendung ist eine auf WinUI 3 basierte Anwendung. Die Oberfl\u00e4che ist grunds\u00e4tzlich fertig, ihre Definition ist in der Datei MainWindow.xaml zu finden. Dies ist f\u00fcr uns im Hinblick auf den Zweck der \u00dcbung weniger aufregend, aber es lohnt sich, sie zu Hause zu \u00fcben.

                                Gestaltung der Oberfl\u00e4che in MainWindow.xaml

                                Grundlagen der Gestaltung von Fensterfl\u00e4chen:

                                • Die Wurzel (root) ist \"normalerweise\" ein Grid.
                                • In der obersten Zeile des Wurzel-Grid befindet sich das StackPanel, das die zwei Texteingabefelder (TextBox) und die Taste (Button) enth\u00e4lt.
                                • Die unterste Zeile des Wurzel-Grid enth\u00e4lt ein weiteres Grid. Im Gegensatz zur TextBox hat die ListBox keine Header-Eigenschaft, so dass wir diese als separaten TextBlock mit dem Text \"Result\" einf\u00fchren mussten. Dieses Grid wurde eingef\u00fchrt (anstelle eines \"einfacheren\" StackPanel), weil es m\u00f6glich war, den TextBlock in der oberen Zeile mit einer festen H\u00f6he f\u00fcr das \"Result\" und die ListBox in der unteren Zeile so zu haben, dass sie den gesamten verbleibenden Platz ausf\u00fcllt (die H\u00f6he der oberen Zeile ist Auto, die H\u00f6he der unteren Zeile ist *).
                                • Die Taste mit dem Text \"Calculate Result\" ist ein gutes Beispiel daf\u00fcr, dass der Content eines Button Elementes oft nicht nur ein einfacher Text ist. Das Beispiel zeigt eine Komposition aus einem SymbolIcon und einem TextBlock (implementiert mit StackPanel), so dass wir ein geeignetes Icon/Symbol zuweisen k\u00f6nnen, um sein Aussehen zu verbessern.
                                • Wir sehen auch ein Beispiel daf\u00fcr, wie man eine ListBox scrollbar macht, wenn sie bereits viele Elemente enth\u00e4lt (oder die Elemente zu breit sind). Dazu muss der ScrollViewer richtig parametrisiert werden.
                                • Die Eigenschaft ItemContainerStyle der ListBox wird verwendet, um Stile f\u00fcr das Element ListBox festzulegen. Im Beispiel ist Padding auf einen kleineren Wert als den Standardwert eingestellt, da sonst die H\u00f6he der ListBox-Elemente \u00fcberfl\u00fcssig gro\u00df w\u00e4re.

                                Die Quelldatei MainWindow.xaml.cs ist der Code hinter der Datei f\u00fcr das Hauptfenster, lassen wir uns diese \u00fcberpr\u00fcfen, ihre Hauptelemente sind wie folgt:

                                • Um das Ergebnis und die Parameter auf ListBoxzu loggen, gibt es eine Hilfsfunktion namens ShowResult.
                                • CalculateResultButton_Click ist der Ereignishandler f\u00fcr das Anklicken der Taste \" Calculate Result \". Wir sehen, dass er den Wert der Parameter aus den beiden Textfeldern liest und versucht, ihn in eine Zahl umzuwandeln. Wenn er erfolgreich ist, wird der Algorithmus hier aufgerufen (dies ist noch nicht implementiert), oder wenn er fehlschl\u00e4gt, wird der Benutzer \u00fcber DisplayInvalidElementDialog in einem Nachrichtenfenster \u00fcber ung\u00fcltige Parameter informiert.
                                • Die Funktion AddKeyboardAcceleratorToChangeTheme, die vom Konstruktor aufgerufen wird, ist f\u00fcr uns nicht relevant, sie erm\u00f6glicht das Umschalten zwischen hellen und dunklen Themen (Sie sollten es zur Laufzeit ausprobieren, Ctrl+T ).
                                "},{"location":"labor/4-tobbszalu/index_ger/#verwendung-des-codes-in-der-dll","title":"Verwendung des Codes in der DLL","text":"

                                Im urspr\u00fcnglichen Projekt finden wir die Datei Algorithm.dll. In dieser kompilierten Form gibt es eine Klasse SuperAlgorithm im Namensraum Algorithms, die eine statische Operation namens Calculate hat. Um die Klassen einer DLL in einem Projekt verwenden zu k\u00f6nnen, m\u00fcssen wir in unsrem Projekt einen Verweis auf die DLL hinzuf\u00fcgen.

                                1. Klicken wir im Solution Explorer mit der rechten Maustaste auf den Knoten Dependencies unseres Projekts und w\u00e4hlen wir Add Project reference!

                                  Externe Referenzen

                                  Hier verweisen wir eigentlich nicht auf ein anderes Visual Studio-Projekt, aber dies ist der einfachste Weg, dieses Fenster aufzurufen.

                                  Es sollte auch erw\u00e4hnt werden, dass wir f\u00fcr externe Klassenbibliotheken keine DLLs mehr in einem regul\u00e4ren Projekt referenzieren, sondern die externen Pakete aus dem Paketmanager von .NET, aus dem NuGet beziehen. Jetzt ist Algorithm.dll in unserem Fall nicht in NuGet ver\u00f6ffentlicht, so dass wir sie manuell hinzuf\u00fcgen m\u00fcssen.

                                2. Verwenden wir die Taste Browse in der rechten unteren Ecke des Popup-Fensters, w\u00e4hlen wir die Datei Algorithms.dll im Unterordner External unseres Projekts aus und klicken wir auf OK, um das Hinzuf\u00fcgen zu best\u00e4tigen!

                                Im Solution Explorer k\u00f6nnen wir auf den Knoten Dependencies unter einem Projekt klicken, um die referenzierten externen Abh\u00e4ngigkeiten anzuzeigen. Der Verweis auf Algorithmen, der zuvor addiert war, wird auch hier unter Assemblys angezeigt. Die Kategorie Frameworks enth\u00e4lt die .NET Framework-Pakete. Und die Elemente unter Analyzer sind Werkzeuge f\u00fcr die statische Codeanalyse zur Kompilierzeit. Und es g\u00e4be hier auch die Projekt- oder NuGet-Referenzen.

                                Klicken wir mit der rechten Maustaste auf die Referenz Algorithms und w\u00e4hlen wir View in Object Browser. Dies \u00f6ffnet die Registerkarte Object Browser, in der wir sehen k\u00f6nnen, welche Namensr\u00e4ume, Klassen und deren Mitglieder (Membervariable, Memberfunktion, Eigenschaft, Ereignis) in der angegebenen DLL enthalten sind. Visual Studio liest diese aus den DLL-Metadaten mit Hilfe des so genannten Reflection-Mechanismus (wir k\u00f6nnen diesen Code selbst schreiben).

                                Wie in der Abbildung unten dargestellt ist, suchen wir im Object Browser den Knoten Algorithmen auf der linken Seite, \u00f6ffnen ihn und sehen, dass er einen Namensraum Algorithms und eine Klasse SuperAlgorithm enth\u00e4lt. Wenn wir dies ausw\u00e4hlen, werden die Funktionen der Klasse in der Mitte angezeigt, und wenn wir hier eine Funktion ausw\u00e4hlen, wird die genaue Signatur dieser Funktion angezeigt:

                                "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-1-ausfuhren-einer-operation-auf-dem-hauptthread","title":"Aufgabe 1 - Ausf\u00fchren einer Operation auf dem Hauptthread","text":"

                                Jetzt k\u00f6nnen wir mit der Ausf\u00fchrung des Algorithmus fortfahren. Zun\u00e4chst tun wir dies im Hauptthread unserer Anwendung.

                                1. Im Ereignishandler der Taste Click im Hauptfenster rufen wir unsere Z\u00e4hlerfunktion auf. \u00d6ffnen wir dazu die code behind Datei MainWindow.xaml.cs im Solution Explorer und suchen wir nach dem Ereignishandler CalculateResultButton_Click. Vervollst\u00e4ndigen wir den Code durch den Aufruf des neu referenzierten Algorithmus.

                                  private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        var result = Algorithms.SuperAlgorithm.Calculate(parameters);\n        ShowResult(parameters, result);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n
                                2. Probieren wir die Anwendung aus und stellen fest, dass das Fenster w\u00e4hrend der Berechnung nicht auf Verschieben oder Gr\u00f6\u00dfen\u00e4nderung reagiert, die Oberfl\u00e4che friert praktisch ein.

                                Unsere Anwendung ist ereignisgesteuert, wie alle Windows-Anwendungen. Das Betriebssystem benachrichtigt unsere Anwendung \u00fcber die verschiedenen Interaktionen (z. B. Verschieben, Gr\u00f6\u00dfen\u00e4nderung, Mausklick): Da der einzige Thread unserer Anwendung nach dem Tastendruck mit der Berechnung besch\u00e4ftigt ist, kann er nicht sofort weitere Benutzeranweisungen verarbeiten. Sobald die Berechnung abgeschlossen ist (und die Ergebnisse in der Liste angezeigt werden), werden die zuvor erhaltenen Befehle ausgef\u00fchrt.

                                "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-2-durchfuhrung-der-berechnung-in-einem-separaten-thread","title":"Aufgabe 2 - Durchf\u00fchrung der Berechnung in einem separaten Thread","text":"

                                Im n\u00e4chsten Schritt werden wir einen separaten Thread starten, um die Berechnung durchzuf\u00fchren, damit die Benutzeroberfl\u00e4che nicht blockiert wird.

                                1. Erstellen wir eine neue Funktion in der Klasse MainWindow, die der Eintrittspunkt f\u00fcr den VerarbeitungsFaden sein wird.

                                  private void CalculatorThread(object arg)\n{\n    var parameters = (double[])arg;\n    var result = Algorithms.SuperAlgorithm.Calculate(parameters);\n    ShowResult(parameters, result);\n}\n
                                2. Starten wir den Thread in dem Ereignishandler der Taste Click. Ersetzen wir dazu den Code, den wir zuvor hinzugef\u00fcgt haben:

                                  private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        var th = new Thread(CalculatorThread);\n        th.Start(parameters);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n

                                  Der in der Operation Start des Fadenobjekts \u00fcbergebene Parameter wird an unsere Fadenfunktion CalculatorThread \u00fcbergeben.

                                3. F\u00fchren wir die Anwendung mit F5 aus (jetzt ist es wichtig, sie so auszuf\u00fchren, im Debugger)! The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD)) Fehlermeldung bekommen wir in der Methode ShowResult, weil wir nicht versuchen, auf das UI-Element/Controller von dem Thread aus zuzugreifen, der es erstellt hat (der Controller). In der n\u00e4chsten \u00dcbung werden wir dieses Problem analysieren und l\u00f6sen.

                                "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-3-verwendung-von-dispatcherqueuehasthreadaccess-und-dispatcherqueuetryenqueue","title":"Aufgabe 3 - Verwendung von DispatcherQueue.HasThreadAccess und DispatcherQueue.TryEnqueue","text":"

                                Das Problem im vorigen Aufgabe hat folgende Ursachen. F\u00fcr WinUI-Anwendungen gilt folgende Regel: Fenster/Oberfl\u00e4chen/Steuerelemente sind standardm\u00e4\u00dfig keine fadensicheren Objekte, so dass auf ein Fenster/Oberfl\u00e4che/Steuerelement nur von dem Thread aus zugegriffen werden darf (z.B. Eigenschaft lesen, einstellen, Operation aufrufen), der das gegebenen Fenster/Oberfl\u00e4che/Steuerelement erstellt hat, sondern gibt es eine Ausnahme. In unserer Anwendung haben wir eine Ausnahme bekommen, weil das resultListBox Steuerelement im Haupt-Thread erstellt wird, aber in der ShowResult Methode, wenn das Ergebnis angezeigt wird, wird von einem anderen Thread aus darauf zugegriffen (Aufruf derresultListBox.Items.Add Methode).

                                Die Frage ist, wie auf diese Oberfl\u00e4chenelemente/Steuerelemente von einem anderen Thread aus noch irgendwie zugegriffen werden kann. Die L\u00f6sung besteht in der Verwendung von DispatcherQueue, um sicherzustellen, dass der Zugriff auf die Steuerelemente immer \u00fcber den richtigen Thread erfolgt:

                                • Die Funktion TryEnqueue des Objekts DispatcherQueue f\u00fchrt die als Parameter angegebene Funktion auf dem Thread aus, der das Steuerelement erstellt (von dem aus man nun direkt auf das Steuerelement zugreifen kann).
                                • Die Eigenschaft HasThreadAccess des Objekts DispatcherQueue hilft bei der Entscheidung, ob es notwendig ist, TryEnqueue zu verwenden, wie im vorherigen Abschnitt erw\u00e4hnt. Wenn der Wert dieser Eigenschaft
                                  • wahr ist, kann auf den Controller direkt zugegriffen werden (weil der aktuelle Thread derselbe ist wie der Thread, der den Controller erstellt hat), aber wenn
                                  • falsch ist, kann auf den Controller nur \"unter Umgehung\", durch die Funktion TryEnqueue des Objekts DispatcherQueue zugegriffen werden (da der aktuelle Thread NICHT mit dem Thread identisch ist, der den Controller erstellt hat).

                                Mit DispatcherQueue k\u00f6nnen wir also unsere vorherige Ausnahme vermeiden (der Zugriff auf den Controller, in diesem Fall resultListBox, kann an den entsprechenden Thread \"geleitet\" werden). Wir werden dies im Folgenden tun.

                                Hinweis

                                Das Objekt DispatcherQueue ist in Nachkommen der Klasse Window \u00fcber die Eigenschaft DispatcherQueue verf\u00fcgbar (und in anderen Klassen \u00fcber die statische Operation DispatcherQueue.GetForCurrentThread() ).

                                Wir m\u00fcssen die Methode ShowResult so \u00e4ndern, dass sie keine Ausnahme ausl\u00f6st, wenn sie aus einem neuen, separaten Thread aufgerufen wird.

                                private void ShowResult(double[] parameters, double result)\n{\n    // Closing the window the DispatcherQueue property may return null, so we have to perform a null check\n    if (this.DispatcherQueue == null)\n        return;\n\n    if (this.DispatcherQueue.HasThreadAccess)\n    {\n        var item = new ListBoxItem()\n        {\n            Content = $\"{parameters[0]} #  {parameters[1]} = {result}\"\n        };\n        resultListBox.Items.Add(item);\n        resultListBox.ScrollIntoView(item);\n    }\n    else\n    {\n        this.DispatcherQueue.TryEnqueue( () => ShowResult(parameters, result) );\n    }\n}\n

                                Probieren wir es aus!

                                Diese L\u00f6sung ist bereits funktionsf\u00e4hig und ihre wichtigste Elemente sind die folgenden:

                                • Die Rolle der Pr\u00fcfung, ob DispatcherQueue null ist: Nach dem Schlie\u00dfen des Hauptfensters ist DispatcherQueue schon null, es kann nicht verwendet werden.
                                • Die DispatcherQueue.HasThreadAccess wird verwendet, um zu pr\u00fcfen, ob der aufrufende Thread direkt auf die Controller zugreifen kann (in unserem Fall ListBox):
                                  • Falls ja, wird alles wie bisher passieren, der Code f\u00fcr ListBoxbleibt unver\u00e4ndert.
                                  • Falls nicht, k\u00f6nnen wir durch DispatcherQueue.TryEnqueue auf den Controller zugreifen. Dabei wird der folgende Trick angewendet. Die Funktion TryEnqueue erh\u00e4lt eine parameterlose, einzeilige Funktion in Form eines Lambda-Ausdrucks, der unsere Funktion ShowResult aufruft (praktisch rekursiv) und ihr die Parameter \u00fcbergibt. Das ist gut f\u00fcr uns, weil dieser ShowResult-Aufruf bereits auf dem Thread erfolgt, der den Controller erstellt hat (dem Hauptthread der Anwendung), der Wert von HasThreadAccess ist jetzt wahr, und wir k\u00f6nnen direkt auf unser ListBoxzugreifen. Dieser rekursive Ansatz ist ein oft benutztes Muster, um redundanten Code zu vermeiden.

                                Setzen wir einen Haltepunkt in der ersten Zeile der Operation ShowResult, und f\u00fchren wir die Anwendung aus, um sicherzustellen, dass HasThreadAccess falsch ist, wenn ShowResult zum ersten Mal aufgerufen wird (also wird TryEnqueue aufgerufen), und dann wird ShowResult erneut aufgerufen, aber HasThreadAccess ist wahr.

                                Entfernen wir den Haltepunkt und f\u00fchren wir die Anwendung aus: Beachten wir, dass w\u00e4hrend eine Berechnung l\u00e4uft, eine andere gestartet werden kann, da unsere Benutzeroberfl\u00e4che durchgehend reaktionsf\u00e4hig bleibt (und der Fehler, der zuvor auftrat, nicht mehr auftritt).

                                "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-4-ausfuhren-einer-operation-auf-einem-threadpool-thread","title":"Aufgabe 4 - Ausf\u00fchren einer Operation auf einem Threadpool-Thread","text":"

                                Eine Merkmal der bisherigen L\u00f6sung ist, dass sie immer einen neuen Thread f\u00fcr die Operation erstellt. In unserem Fall ist dies nicht besonders wichtig, aber dieser Ansatz kann f\u00fcr eine Serveranwendung, die eine gro\u00dfe Anzahl von Anfragen bedient, problematisch sein, da f\u00fcr jede Anfrage ein eigener Thread gestartet wird. Aus zwei Gr\u00fcnden:

                                • Wenn die Fadenfunktion schnell l\u00e4uft (um einen Client schnell zu bedienen), dann wird ein gro\u00dfer Teil der CPU f\u00fcr das Starten und Stoppen von Threads verschwendet, was an sich schon ressourcenintensiv ist.
                                • Es k\u00f6nnen zu viele Threads erstellt werden, und das Betriebssystem muss zu viele planen, was unn\u00f6tig Ressourcen verschwendet.

                                Ein weiteres Problem mit unserer derzeitigen L\u00f6sung: Da die Berechnung auf einem so genannten Vordergrundfaden l\u00e4uft (neu erstellte Threads sind standardm\u00e4\u00dfig Vordergrundf\u00e4den), l\u00e4uft das Programm selbst dann im Hintergrund weiter, obwohl wir die Anwendung schlie\u00dfen, solange bis die letzte Berechnung ausgef\u00fchrt wurde: Ein Prozess h\u00f6rt erst auf zu laufen, wenn er keinen Vordergrundfaden mehr hat.

                                \u00c4ndern wir den Ereignishandler der Taste, um die Berechnung in einem Threadpool-Thread auszuf\u00fchren, anstatt einen neuen Thread zu starten. Um dies zu tun, schreiben wir einfach den Ereignishandler f\u00fcr das Dr\u00fccken der Taste um.

                                private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        ThreadPool.QueueUserWorkItem(CalculatorThread, parameters);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n

                                Probieren wir die Anwendung aus und stellen fest, dass die Anwendung sofort anh\u00e4lt, wenn das Fenster geschlossen wird, ohne sich um eventuell noch laufende Threads zu k\u00fcmmern (denn Threadpool-Threads sind Hintergrundf\u00e4den).

                                "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-5-hersteller-verbraucher-basierte-losung","title":"Aufgabe 5 - Hersteller-Verbraucher-basierte L\u00f6sung","text":"

                                Allein durch die L\u00f6sung der vorangegangenen Probleme erhielten wir eine vollst\u00e4ndige und gut funktionierende L\u00f6sung f\u00fcr das urspr\u00fcngliche Problem, die es erm\u00f6glicht, dass mehrere Threads parallel im Hintergrund arbeiten, wenn die Taste mehrmals nacheinander gedr\u00fcckt wird. Im Folgenden werden wir unsere Anwendung so modifizieren, dass ein Tastendruck nicht immer einen neuen Thread erzeugt, sondern die Aufgaben in eine Aufgabenwarteschlange stellt, aus der mehrere im Hintergrund laufende Threads sie nacheinander ausw\u00e4hlen und ausf\u00fchren. Bei dieser Aufgabe handelt es sich um das klassische Hersteller-Verbraucher-Problem, das in der Praxis h\u00e4ufig auftritt und in der folgenden Abbildung dargestellt ist.

                                Hersteller-Verbraucher vs ThreadPool

                                Wenn Sie dar\u00fcber nachdenken, ist ThreadPool auch ein spezieller Hersteller-Verbraucher und Scheduler-Mechanismus, der uns von .NET zur Verf\u00fcgung gestellt wird. Im Folgenden entwickeln wir eine andere Art von Hersteller-Verbraucher-L\u00f6sung, um einige mit der Fadenbehandlung verbundenen Wettbewerbsprobleme anzuschauen.

                                Der Hauptthread ist der Hersteller, der eine neue Aufgabe erstellt, falls die Taste Calculate result geklickt wird. Wir werden mehr Threads in der Verbraucher-/verarbeitenden Threads starten, da wir mehr CPU-Kerne verwenden und die Ausf\u00fchrung von Aufgaben parallelisieren k\u00f6nnen.

                                F\u00fcr die Zwischenspeicherung von Aufgaben k\u00f6nnen wir die Klasse DataFifo (im Ordner Data im Solution Explorer) verwenden, die in unserem urspr\u00fcnglichen Projekt bereits etwas vorbereitet ist. Schauen wir uns den Quellcode an. Es implementiert eine einfache FIFO-Warteschlange, um double[] zu speichern. Die Methode Put h\u00e4ngt die neuen Paare an das Ende der internen Liste an, w\u00e4hrend die Methode TryGet das erste Element der internen Liste zur\u00fcckgibt (und entfernt). Wenn die Liste leer ist, kann die Funktion kein Element zur\u00fcckgeben. In diesem Fall zeigt false dies durch einen R\u00fcckgabewert an.

                                1. \u00c4ndern wir den Ereignishandler der Taste so, dass er nicht in ThreadPool, sondern in FIFO arbeitet:

                                  private void CalculateResultButton_Click(object sender, RoutedEventArgs e)\n{\n    if (double.TryParse(param1TextBox.Text, out var p1) && double.TryParse(param2TextBox.Text, out var p2))\n    {\n        var parameters = new double[] { p1, p2 };\n\n        _fifo.Put(parameters);\n    }\n    else\n        DisplayInvalidElementDialog();\n}\n
                                2. Erstellen wir eine naive Implementierung der neuen Fadenbehandlungsfunktion in unserer Formularklasse:

                                  private void WorkerThread()\n{\n    while (true)\n    {\n        if (_fifo.TryGet(out var data))\n        {\n            double result = Algorithms.SuperAlgorithm.Calculate(data);\n            ShowResult(data, result);\n        }\n\n        Thread.Sleep(500);\n    }\n}\n

                                  Der Grund f\u00fcr die Einf\u00fchrung von Thread.Sleep ist, dass sich die Threads sonst unn\u00f6tigerweise die ganze Zeit mit einem leeren FIFO besch\u00e4ftigen w\u00fcrden, ohne irgendeine n\u00fctzliche Operation auszuf\u00fchren, und einen CPU-Kern zu 100% \u00fcberlasten w\u00fcrden. Unsere L\u00f6sung ist nicht ideal, wir werden sie sp\u00e4ter verbessern.

                                3. Erstellen und starten wir die Verarbeitungsf\u00e4den im Konstruktor:

                                  new Thread(WorkerThread) { Name = \"Worker thread 1\" }.Start();\nnew Thread(WorkerThread) { Name = \"Worker thread 2\" }.Start();\nnew Thread(WorkerThread) { Name = \"Worker thread 3\" }.Start();\n
                                4. Starten wir die Anwendung und schlie\u00dfen wir sie sofort, ohne auf die Taste Calculate Result zu klicken. Unser Fenster wird geschlossen, aber unser Prozess l\u00e4uft weiter, und die einzige M\u00f6glichkeit, die Anwendung zu schlie\u00dfen, ist \u00fcber Visual Studio oder den Task-Manager:

                                  Die Verarbeitungsf\u00e4den sind Vordergrundf\u00e4den, die verhindern das Beenden der Prozess beim Schlie\u00dfen des Fensters. Eine L\u00f6sung k\u00f6nnte darin bestehen, die Eigenschaft IsBackground der Threads auf truezu setzen, nachdem sie erstellt wurden. Die andere L\u00f6sung stellt sicher, dass die Verarbeitungsf\u00e4den beim Beenden beendet werden. Lassen wir dieses Thema erst einmal beiseite, wir kommen sp\u00e4ter darauf zur\u00fcck.

                                5. Starten wir die Anwendung und wir werden feststellen, dass wir nach dem Klicken auf die Taste Calculate Result (nur einmal klicken) h\u00f6chstwahrscheinlich eine Ausnahme erhalten. Das Problem ist, dass DataFifo nicht fadensicher ist, es ist inkonsistent geworden. Hierf\u00fcr gibt es zwei Ursachen:

                                "},{"location":"labor/4-tobbszalu/index_ger/#problem-1","title":"Problem 1","text":"

                                Betrachten wir das folgende Szenario:

                                1. Die Zeile ist leer. Die verarbeitenden Threads fragen den FIFO kontinuierlich in einer while-Schleife ab, d. h. sie rufen die Methode TryGet auf.
                                2. Der Benutzer f\u00fcgt der Warteschlange eine Aufgabe hinzu.
                                3. Einer der Verarbeitungsf\u00e4den in der Methode TryGet stellt fest, dass Daten in der Zeile vorhanden sind, d. h. die Bedingung der Codezeile if ( _innerList.Count > 0 ) ist erf\u00fcllt, und geht zur n\u00e4chsten Codezeile \u00fcber. Angenommen, dieser Thread verliert an dieser Stelle seine Durchf\u00fchrungsrecht, dann hat er keine Zeit mehr, die Daten aus der Warteschlange zu nehmen.
                                4. Ein anderer Verarbeitungsthread l\u00e4sst die Pr\u00fcfung von if ( _innerList.Count > 0 ) zu diesem Zeitpunkt ebenfalls fallen, die Bedingung ist ebenfalls erf\u00fcllt, und dieser Thread nimmt die Daten aus der Warteschlange.
                                5. Der erste Thread wird neu geplant, wacht auf und versucht, die Daten aus der Warteschlange zu nehmen: die Warteschlange ist leer, der andere Thread hat die einzigen Daten aus der Warteschlange vor ihm genommen. Der Zugriff auf _innerList[0] f\u00fchrt daher zu einer Ausnahme.

                                Die einzige M\u00f6glichkeit, dieses Problem zu vermeiden, ist die Pr\u00fcfung der Zeilenleere und die Elementausnahme unteilbar zu machen.

                                Thread.Sleep(500)

                                Die Rolle der Codezeile Thread.Sleep(500);, die auf die Codezeile folgt, die die Leere-Pr\u00fcfung in unserem Beispielcode \u00fcberwacht, besteht nur darin, die Wahrscheinlichkeit zu erh\u00f6hen, dass das obige ungl\u00fcckliche Szenario eintritt, und somit das Beispiel anschaulicher zu machen (da es fast sicher ist, dass der Thread neu geplant wird). Wir werden dies in Zukunft herausnehmen, aber vorl\u00e4ufig lassen wir es drin.

                                "},{"location":"labor/4-tobbszalu/index_ger/#problem-2","title":"Problem 2","text":"

                                Die Klasse DataFifo kann von mehreren Threads gleichzeitig auf die Mitgliedsvariable _innerList mit der Typ List<double[]> zugreifen. Wenn wir uns jedoch die Dokumentation zu List<T> ansehen, werden wir feststellen, dass die Klasse nicht fadensicher (not thread safe) ist. Aber in diesem Fall k\u00f6nnen wir das nicht tun, wir m\u00fcssen Sperren verwenden, um sicherzustellen, dass unser Code nur auf eine Methode/Eigenschaft/Mitgliedsvariable zur gleichen Zeit zugreifen kann (genauer gesagt, kann Inkonsistenz nur im Fall von gleichzeitigen Schreiben und Lesen auftreten, aber wir unterscheiden in den meisten F\u00e4llen nicht zwischen Lesern und Schreibern, und wir tun es hier auch nicht).

                                Der n\u00e4chste Schritt ist, unsere Klasse DataFifo fadensicher zu machen, wodurch die beiden oben genannten Probleme vermieden werden.

                                "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-6-die-datafifo-klasse-fadensicher-machen","title":"Aufgabe 6 - Die DataFifo-Klasse fadensicher machen","text":"

                                Um die Klasse DataFifo fadensicher zu machen, ben\u00f6tigen wir ein Objekt (dies kann ein beliebiges Objekt vom Referenztyp sein), das als Schl\u00fcssel zum Sperren verwendet wird. Mit dem Schl\u00fcsselwort lock k\u00f6nnen wir dann sicherstellen, dass sich jeweils nur ein Thread in den durch diesen Schl\u00fcssel gesch\u00fctzten Bl\u00f6cken aufh\u00e4lt.

                                1. F\u00fcgen wir ein Feld vom Typ object mit dem Namen _syncRoot zur Klasse DataFifo hinzu.

                                  private object _syncRoot = new object();\n
                                2. Erg\u00e4nzen wir die Funktionen Put und TryGet mit dem Sperre.

                                  public void Put(double[] data)\n{\n    lock (_syncRoot)\n    {\n        _innerList.Add(data); \n    }\n}\n
                                  public bool TryGet(out double[] data)\n{\n    lock (_syncRoot)\n    {\n        if (_innerList.Count > 0)\n        {\n            Thread.Sleep(500);\n\n            data = _innerList[0];\n            _innerList.RemoveAt(0);\n            return true;\n        }\n\n        data = null;\n        return false;\n    }\n}\n

                                  Surround with

                                  Verwenden wir die Funktion \"Surround with\" von Visual Studio, indem Sie STRG + K, STRG + S auf dem ausgew\u00e4hlten Codeschnipsel dr\u00fccken, den wir umschlie\u00dfen m\u00f6chten.

                                Jetzt d\u00fcrfen wir keine Ausnahme bekommen.

                                Wir k\u00f6nnen die k\u00fcnstliche Verz\u00f6gerung auch aus der Methode TryGet entfernen ( ZeileThread.Sleep(500); ).

                                Sperre auf this

                                Es stellt sich die Frage, warum wir eine separate Membervariable _syncRoot eingef\u00fchrt und diese als Sperrparameter f\u00fcr lock verwendet haben, wenn wir stattdessen auch this h\u00e4tten verwenden k\u00f6nnen ( DataFifo ist der Referenztyp, daher w\u00e4re dies kein Problem). Die Verwendung von this w\u00fcrde jedoch gegen die Einkapselung unserer Klasse versto\u00dfen! Erinnern wir uns: this ist ein Verweis auf unser Objekt, aber andere Klassen haben Verweise auf dasselbe Objekt (z.B. in unserem Fall MainWindowhat einen Verweis auf DataFifo), und wenn diese externen Klassen eine Sperre auf das Objekt setzen, indem sie lock verwenden, wird dies die Sperre \"st\u00f6ren\", die wir auf die Klasse darin verwenden (da die Verwendung von this dazu f\u00fchrt, dass die externen und internen lock denselben Parameter haben). Zum Beispiel kann eine externe Sperre verwendet werden, um die Operationen TryGet und Put vollst\u00e4ndig \"lahmzulegen\". Im Gegensatz dazu ist in unserer L\u00f6sung der Parameter lock, die Variable _syncRoot, privat und kann nicht von externen Klassen aufgerufen werden, so dass sie die internen Abl\u00e4ufe unserer Klasse nicht beeintr\u00e4chtigen kann.

                                "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-7-implementierung-einer-effektiven-signalisierung","title":"Aufgabe 7 - Implementierung einer effektiven Signalisierung","text":""},{"location":"labor/4-tobbszalu/index_ger/#verwendung-von-manualresetevent","title":"Verwendung von ManualResetEvent","text":"

                                Die Schleife while, die in WorkerThreadst\u00e4ndig l\u00e4uft, implementiert ein sogenanntes aktives Warten, das immer vermieden werden sollte. Falls Thread.Sleep nicht in den Schleifenkern eingebaut worden w\u00e4re, w\u00e4re der Prozessor \u00fcberlastet gewesen. Thread.Sleep l\u00f6st zwar das Problem der CPU-Belastung, f\u00fchrt aber ein weiteres ein: Wenn sich alle drei Arbeitsf\u00e4den im Ruhezustand befinden, wenn neue Daten empfangen werden, warten wir unn\u00f6tigerweise 500 ms, bevor wir mit der Verarbeitung der Daten beginnen.

                                Im Folgenden wird die Anwendung so ge\u00e4ndert, dass sie in einem blockierten Zustand wartet, bis Daten zum FIFO hinzugef\u00fcgt werden (aber wenn Daten hinzugef\u00fcgt werden, beginnt sie sofort mit der Verarbeitung). Um anzuzeigen, ob sich Daten in der Warteschlange befinden, wird ManualResetEventverwendet.

                                1. F\u00fcgen wir eine Instanz von MaunalResetEvent zu unserer Klasse DataFifo als _hasData hinzu.

                                  // Infolge des Konstruktorparameters false wird das Ereignis anf\u00e4nglich nicht signalisiert (Tor geschlossen)\nprivate ManualResetEvent _hasData = new ManualResetEvent(false);\n
                                2. _hasData funktioniert als ein Tor in unserer Anwendung. Wenn der Liste Daten hinzugef\u00fcgt werden, wird sie \"ge\u00f6ffnet\", und wenn die Liste geleert wird, wird sie \"geschlossen\".

                                  Semantik und Benennung des Ereignisses

                                  Es ist wichtig, die Semantik unseres Ereignisses gut zu w\u00e4hlen und wir im Namen unseres Ereignisses pr\u00e4zise auszudr\u00fccken. In unserem Beispiel dr\u00fcckt der Name _hasData aus, dass unser Ereignis genau dann und nur dann signalisiert wird, wenn es Daten zu verarbeiten gibt (Tor ge\u00f6ffnet). Jetzt m\u00fcssen wir \"nur\" noch diese Semantik implementieren: das Ereignis signalisiert setzen, wenn Daten in den FIFO eingegeben werden, und nicht signalisiert, wenn der FIFO geleert wird.

                                  public void Put(double[] data)\n{\n    lock (_syncRoot)\n    {\n        _innerList.Add(data);\n        _hasData.Set();\n    }\n}\n
                                  public bool TryGet(out double[] data)\n{\n    lock (_syncRoot)\n    {\n        if (_innerList.Count > 0)\n        {\n            data = _innerList[0];\n            _innerList.RemoveAt(0);\n            if (_innerList.Count == 0)\n            {\n                _hasData.Reset();\n            }\n\n            return true;\n        }\n\n        data = null;\n        return false;\n    }\n}\n
                                "},{"location":"labor/4-tobbszalu/index_ger/#warten-auf-signal-get-blockiert","title":"Warten auf Signal (Get blockiert)","text":"

                                In dem vorherigen Punkt wurde die Signalisierung gel\u00f6st, aber das sich selbst macht nicht viel, weil niemand auf das Signal wartet. Diese Erkenntnis kommt jetzt.

                                1. \u00c4ndern wir die Methode wie folgt: Entfernen wir den Leere-Test und ersetzen wir ihn durch Warten auf das Ereignis.

                                  public bool TryGet(out double[] data)\n{\n    lock (_syncRoot)\n    {\n        if (_hasData.WaitOne())\n        {\n            // ...\n

                                  Pr\u00fcfung des R\u00fcckgabewerts der Operation WaitOne

                                  Die Operation WaitOne gibt den Wert bool zur\u00fcck, der wahr ist, wenn sich das Ereignis vor der im Parameter von WaitOne angegebenen Zeitspanne signalisiert wird (und entsprechend falsch, wenn die Zeitspanne abgelaufen ist). In unserem Beispiel haben wir im Parameter kein Zeitlimit angegeben, was eine unendliche Zeitspanne bedeutet. Dementsprechend ist die Pr\u00fcfung der Bedingung if \u00fcberfl\u00fcssig, da in unserem Fall WaitOne() immer einen wahren Wert liefert. Dies ist der einzige Grund, warum wir dennoch die Konditionstests verwendet haben: Wir erfordern weniger \u00c4nderungen f\u00fcr die n\u00e4chste und eine zuk\u00fcnftige \u00dcbung.

                                2. Dies macht Thread.Sleep in WorkerThread \u00fcberfl\u00fcssig, kommentieren wir es aus!

                                  Wenn wir die obige L\u00f6sung ausf\u00fchren, werden wir feststellen, dass die Oberfl\u00e4che unserer Anwendung nach dem ersten Tastendruck einfriert. Bei unserer vorherigen L\u00f6sung haben wir einen Anf\u00e4ngerfehler gemacht. In dem gesperrten Codeschnipsel warten wir darauf, dass _hasData gesendet wird, so dass der Hauptthread keine Gelegenheit hat, _hasData in der Operation Put zu senden (ebenfalls gesch\u00fctzt durch lock). In der Praxis wurde eine Verklemmung (deadlock) gebildet.

                                  Wir k\u00f6nnten versuchen, ein Zeitlimit (ms) f\u00fcr die Wartezeit festzulegen:

                                  if (_hasData.WaitOne(100))\n

                                  Dies w\u00e4re an sich keine elegante L\u00f6sung, au\u00dferdem w\u00fcrden die st\u00e4ndig verschmutzenden Arbeitsf\u00e4den den Thread, der Put aufruft, erheblich aushungern! Stattdessen ist das elegante Muster zu folgen, um zu vermeiden, dass man innerhalb einer Sperre blockiert wartet.

                                  Tauschen wir lock und WaitOne um, und entfernen wir die Wartezeitbegrenzung, also den Parameter von WaitOne:

                                  public bool TryGet(out double[] data)\n{\n    if (_hasData.WaitOne())\n    {\n        lock (_syncRoot)\n        {\n            data = _innerList[0];\n            _innerList.RemoveAt(0);\n            if (_innerList.Count == 0)\n            {\n                _hasData.Reset();\n            }\n\n            return true; \n        }\n    }\n\n    data = null;\n    return false;\n}\n

                                  Probieren wir die App aus. Wenn wir die Taste zum ersten Mal dr\u00fccken, erhalten wir eine Ausnahme. Dadurch wird zwar ein Deadlock vermieden, aber die Fadensicherheit ist verletzt, weiles ist nicht sicher, dass wenn wir in lock eintreten k\u00f6nnen, noch Elemente in der Liste vorhanden sind. Es kann mehrere Threads geben, die mit _hasData.WaitOne() darauf warten, dass ein Element zu der Liste hinzugef\u00fcgt wird. Wenn dies geschieht, wird unser ManualResetEvent Objekt alle durchlassen (au\u00dfer wenn ein Thread schlie\u00dft es schnell, aber das ist nicht garantiert).

                                  Die Schwierigkeiten der Programmierung in einer konkurrierenden, mehrf\u00e4digen Umgebung

                                  Diese Aufgabe veranschaulicht, wie sorgf\u00e4ltig man bei der Programmierung in einer konkurrierenden, mehrf\u00e4digen Umgebung vorgehen muss. Bei den vorherigen hatten wir sogar noch Gl\u00fcck, denn der Fehler war reproduzierbar. In der Praxis ist dies jedoch selten der Fall. Leider ist es viel h\u00e4ufiger der Fall, dass Konkurenzprobleme gelegentliche, nicht reproduzierbare Probleme verursachen. Die L\u00f6sung einer solchen Aufgabe muss immer sehr sorgf\u00e4ltig durchdacht sein und kann nicht nach dem Motto \"wir-probieren-es-solange-es-wird-gut-im-per-Hand-Test\" programmiert werden.

                                3. Als Korrektur setzen wir den Leertest in lock zur\u00fcck.

                                  public bool TryGet(out double[] data)\n{\n    if (_hasData.WaitOne())\n    {\n        lock (_syncRoot)\n        {\n            if (_innerList.Count > 0)\n            {\n                data = _innerList[0];\n                _innerList.RemoveAt(0);\n                if (_innerList.Count == 0)\n                {\n                    _hasData.Reset();\n                }\n\n                return true;  \n            }\n        }\n    }\n\n    data = null;\n    return false;\n}\n

                                  Dies funktioniert bereits gut. Es ist m\u00f6glich, dass wir unn\u00f6tigerweise auf die Liste eingehen, aber wir belassen es vorerst dabei.

                                  Testen wir die App!

                                System.Collections.Concurrent

                                Im .NET-Framework gibt es mehrere eingebaute fadensichere Klassen im Namensraum System.Collections.Concurrent. In dem obigen Beispiel h\u00e4tte die Klasse DataFifo durch System.Collections.Concurrent.ConcurrentQueue ersetzt werden k\u00f6nnen.

                                "},{"location":"labor/4-tobbszalu/index_ger/#aufgabe-8-kulturelle-abschaltung","title":"Aufgabe 8 - Kulturelle Abschaltung","text":"

                                Bisher haben wir das Problem, dass unser Prozess beim Schlie\u00dfen des Fensters \"stecken bleibt\", weil die Verarbeitungsthreads Vordergrundf\u00e4den sind und wir das Problem des Beendens dieser Threads nicht gel\u00f6st haben. Unser Ziel ist es, den unendlichen while-Schleife auszul\u00f6sen, so dass unsere Arbeitsf\u00e4den auf zivilisierte Weise beendet werden, wenn die Anwendung geschlossen wird.

                                1. Ein ManualResetEvent wird verwendet, um das Beenden im FIFO anzuzeigen, w\u00e4hrend in TryGetgewartet wird. F\u00fcgen wir im FIFO ein neues ManualResetEvent hinzu und f\u00fchren wir eine Release-Operation ein, um unsere Wartezeiten zu verk\u00fcrzen (unser neues Ereignis kann auf einen signalisierten Zustand gesetzt werden).

                                  private ManualResetEvent _releaseTryGet = new ManualResetEvent(false);\n\npublic void Release()\n{\n    _releaseTryGet.Set();\n}\n
                                2. Warten wir auf diese Ereignis auch in TryGet. Die Methode WaitAny darf die Ausf\u00fchrung fortsetzen, wenn sich eines der als Parameter angegebenen Objekte vom Typ WaitHandle signalisiert ist, und gibt dessen Index innerhalb der Block zur\u00fcck. Und wir wollen die tats\u00e4chliche Verarbeitung nur, wenn _hasData signalisiert ist (wenn WaitAny 0 zur\u00fcckgibt).

                                  public bool TryGet(out double[] data)\n{\n    if (WaitHandle.WaitAny(new[] { _hasData, _releaseTryGet }) == 0)\n    {\n        lock (_syncRoot)\n        {\n
                                3. F\u00fcgen wir eine flag Variable in MainWindow.xaml.cs hinzu, um das Beenden anzuzeigen:

                                  private bool _isClosed = false;\n
                                4. Wenn das Hauptfenster geschlossen wird, setzen wir das neue Ereignis auf signalisiert und setzen wir auch das Flag auf true: abonnieren wir uns auf das Ereignis Closed der Klasse MainWindow im Konstruktor und schreiben wir die entsprechende Ereignishandler:

                                  public MainWindow()\n{\n    ...\n\n    Closed += MainWindow_Closed;\n}\n\nprivate void MainWindow_Closed(object sender, WindowEventArgs args)\n{\n    _isClosed = true;\n    _fifo.Release();\n}\n
                                5. Schreiben wir die while-Schleife so um, dass sie auf das im vorigen Punkt addierte Flag wartet.

                                  private void WorkerThread()\n{\n    while (!_isClosed)\n    {\n
                                6. Stellen wir sicher, dass wir nicht versuchen, Nachrichten f\u00fcr ein Fenster zu senden, das bereits geschlossen ist

                                  private void ShowResult(double[] parameters, double result)\n{\n    if (_isClosed)\n        return;\n
                                7. F\u00fchren wir die Anwendung aus und \u00fcberpr\u00fcfen wir, ob unser Prozess tats\u00e4chlich beendet wird, wenn wir ihn beenden.

                                "},{"location":"labor/4-tobbszalu/index_ger/#ausblick-task-async-await","title":"Ausblick: Task, async, await","text":"

                                Ziel der \u00dcbung war es, die Techniken f\u00fcr das Management von F\u00e4den auf unterer Ebene kennen zu lernen. Wir h\u00e4tten unsere L\u00f6sung jedoch (zumindest teilweise) auf den \u00fcbergeordneten Werkzeugen und Mechanismen aufbauen k\u00f6nnen, die die asynchrone Programmierung in .NET unterst\u00fctzen, z. B. die Klassen Task/Task<T> und die Schl\u00fcsselw\u00f6rter async/await.

                                "},{"location":"labor/5-mvvm/","title":"5. MVVM","text":""},{"location":"labor/5-mvvm/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                                A labor sor\u00e1n egy recept b\u00f6ng\u00e9sz\u0151 alkalmaz\u00e1st fogunk k\u00e9sz\u00edteni, amelyben alkalmazzuk az MVVM tervez\u00e9si mint\u00e1t.

                                "},{"location":"labor/5-mvvm/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                                A labor elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                                • Windows 10 vagy Windows 11 oper\u00e1ci\u00f3s rendszer (Linux \u00e9s macOS nem alkalmas)
                                • Visual Studio 2022
                                  • Windows Desktop Development Workload
                                "},{"location":"labor/5-mvvm/#kiindulo-projekt","title":"Kiindul\u00f3 projekt","text":"

                                Kl\u00f3nozzuk le a kiindul\u00f3 projektet az al\u00e1bbi paranccsal:

                                git clone https://github.com/bmeviauab00/lab-mvvm-kiindulo\n
                                A k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se

                                L\u00e9nyeges, hogy a labor sor\u00e1n a laborvezet\u0151t k\u00f6vetve kell dolgozni, tilos (\u00e9s \u00e9rtelmetlen) a k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se. Ugyanakkor az ut\u00f3lagos \u00f6n\u00e1ll\u00f3 gyakorl\u00e1s sor\u00e1n hasznos lehet a k\u00e9sz megold\u00e1s \u00e1ttekint\u00e9se, \u00edgy ezt el\u00e9rhet\u0151v\u00e9 tessz\u00fck.

                                A megold\u00e1s GitHubon \u00e9rhet\u0151 el a megoldas \u00e1gon. A legegyszer\u0171bb m\u00f3d a let\u00f6lt\u00e9s\u00e9re, ha parancssorb\u00f3l a git clone utas\u00edt\u00e1ssal lekl\u00f3nozzuk a g\u00e9p\u00fcnkre a megoldas \u00e1gat:

                                git clone https://github.com/bmeviauab00/lab-mvvm-kiindulo -b megoldas

                                "},{"location":"labor/5-mvvm/#az-mvvm-mintarol","title":"Az MVVM mint\u00e1r\u00f3l","text":"

                                Az MVVM (Model-View-ViewModel) egy architektur\u00e1lis tervez\u00e9si minta, amelyet a XAML alkalmaz\u00e1sok fejleszt\u00e9se sor\u00e1n haszn\u00e1lhatunk, de gyakran m\u00e1s kliens oldali technol\u00f3gi\u00e1k eset\u00e9ben is megjelenik. Az MVVM minta c\u00e9lja, hogy a felhaszn\u00e1l\u00f3i fel\u00fcletet \u00e9s a m\u00f6g\u00f6tte l\u00e9v\u0151 logik\u00e1t sz\u00e9tv\u00e1lassza, \u00e9s ezzel egy laz\u00e1bb csatol\u00e1s\u00fa alkalmaz\u00e1st hozzon l\u00e9tre, ami n\u00f6veli a tesztelhet\u0151s\u00e9get, a karbantarthat\u00f3s\u00e1got \u00e9s az \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1got.

                                Az MVVM minta h\u00e1rom (+1) f\u0151 r\u00e9szb\u0151l \u00e1ll:

                                • Model: Az alkalmaz\u00e1s \u00fczleti modellj\u00e9t tartalmazza, amelyet a ViewModel-ek haszn\u00e1lhatnak az adatok t\u00e1rol\u00e1s\u00e1ra.
                                • View: A felhaszn\u00e1l\u00f3i fel\u00fclet le\u00edr\u00e1s\u00e1t tartalmazza, \u00e9s a tiszt\u00e1n a n\u00e9zetekhez kapcsol\u00f3d\u00f3 logik\u00e1t (pl.: anim\u00e1ci\u00f3k kezel\u00e9s\u00e9t).
                                • ViewModel: A n\u00e9zet absztrakci\u00f3ja, mely tartalmazza a n\u00e9zet \u00e1llapot\u00e1t \u00e9s a n\u00e9zeten v\u00e9grehajthat\u00f3 m\u0171veleteket, n\u00e9zet f\u00fcggetlen\u00fcl. A laza csatol\u00e1st a ViewModel \u00e9s a n\u00e9zet k\u00f6z\u00f6tt az adatk\u00f6t\u00e9s biztos\u00edtja.
                                • Services (szolg\u00e1ltat\u00e1sok): Az alkalmaz\u00e1s \u00fczleti logik\u00e1j\u00e1t tartalmaz\u00f3 oszt\u00e1lyok, amelyeket a ViewModel-ek haszn\u00e1lnak. Ha minden \u00fczleti logika a ViewModel-ekben lenne, azok t\u00fal bonyolultak \u00e9s \u00e1tl\u00e1thatatlanok lenn\u00e9nek. Ez nem az MVVM minta r\u00e9sze, de itt eml\u00edtj\u00fck meg, mert mi is \u00edgy fogjuk haszn\u00e1lni az alkalmaz\u00e1s architekt\u00far\u00e1j\u00e1t.

                                \u00daj:

                                • Model: Domainspecifikus adatokat fog \u00f6ssze, melyet a ViewModel-ek haszn\u00e1lhatnak az adatok t\u00e1rol\u00e1s\u00e1ra. Pl. Recipe/Product/Order oszt\u00e1ly, egy recept/term\u00e9k/megrendel\u00e9s adatait fogja \u00f6ssze.
                                • View: A felhaszn\u00e1l\u00f3i fel\u00fclet le\u00edr\u00e1s\u00e1t tartalmazza, (\u00e9s a tiszt\u00e1n a n\u00e9zetekhez kapcsol\u00f3d\u00f3 logik\u00e1t, pl. anim\u00e1ci\u00f3k kezel\u00e9s\u00e9t). Tipikusan Window, Page, UserControl lesz\u00e1rmazott oszt\u00e1ly, XAML-beli deklarat\u00edv le\u00edr\u00e1ssal, a code-behind sokszor \u00fcres (mert a logika a ViewModel-ben van).
                                • ViewModel: A n\u00e9zethez tartoz\u00f3 logika van benne: tartalmazza a n\u00e9zet \u00e1llapot\u00e1t \u00e9s a n\u00e9zeten v\u00e9grehajthat\u00f3 m\u0171veleteket. F\u00fcggetlen a n\u00e9zett\u0151l, a laza csatol\u00e1st a ViewModel \u00e9s a n\u00e9zet k\u00f6z\u00f6tt adatk\u00f6t\u00e9s biztos\u00edtja (a n\u00e9zet vez\u00e9rl\u0151i k\u00f6tnek a ViewModel tulajdons\u00e1gaihoz). Unit tesztelhet\u0151!
                                • Services (szolg\u00e1ltat\u00e1sok): Az alkalmaz\u00e1s \u00fczleti/alkalmaz\u00e1s logik\u00e1j\u00e1t tartalmaz\u00f3 oszt\u00e1lyok, amelyeket a ViewModel-ek haszn\u00e1lnak. Ha minden \u00fczleti logika a ViewModel-ekben lenne, azok t\u00fal bonyolultak \u00e9s \u00e1tl\u00e1thatatlanok lenn\u00e9nek. Ez nem az MVVM minta r\u00e9sze, de itt eml\u00edtj\u00fck meg, mert mi is \u00edgy fogjuk fel\u00e9p\u00edteni az alkalmaz\u00e1s architekt\u00far\u00e1j\u00e1t.

                                Mihez k\u00e9sz\u00edt\u00fcnk ViewModel oszt\u00e1lyokat?

                                • Az egyes n\u00e9zetekhez (pl. Window, Page, Dialog, UserControl) mindig k\u00e9sz\u00edt\u00fcnk ViewModel oszt\u00e1lyt, \u00e9s bel\u0151le egy n\u00e9zethez egy objektumot hozunk l\u00e9tre. Pl. MainPage-hez MainPageViewModel, DancerDialog-hoz DancerDialogViewModel. Ezt a gyakorlat sor\u00e1n is alkalmazzuk.
                                • Az egyes modell oszt\u00e1lyokhoz (pl. Recipe, Product, Dancer stb.) opcion\u00e1lisan k\u00e9sz\u00edthet\u00fcnk csomagol\u00f3 ViewModel oszt\u00e1lyokat (pl. RecipeViewModel, ProductViewModel, DancerViewModel), ilyeneket a gyakorlat sor\u00e1n nem fogunk k\u00e9sz\u00edteni. Ez az\u00e9rt van, mert nem a Strict, hanem a Relaxed MVVM mint\u00e1t k\u00f6vetj\u00fck (l\u00e1sd el\u0151ad\u00e1s).
                                "},{"location":"labor/5-mvvm/#0-feladat-projekt-felepitese","title":"0. Feladat - Projekt fel\u00e9p\u00edt\u00e9se","text":"

                                Az alkalmaz\u00e1s v\u00e1za m\u00e1r el\u0151 van k\u00e9sz\u00edtve. Tekints\u00fck \u00e1t a projekt fel\u00e9p\u00edt\u00e9s\u00e9t.

                                Az MvvmLab a futtathat\u00f3 alkalmaz\u00e1s projektje, amely WinUI keretrendszert haszn\u00e1l a megjelen\u00edt\u00e9si r\u00e9teg\u00e9ben a m\u00e1r tanult XAML nyelvvel. Az MvvmLab.Core projekt (class library) a teljesen n\u00e9zet f\u00fcggetlen \u00fczleti logik\u00e1kat tartalmazza.

                                Ami sz\u00e1munkra fontos a kiindul\u00f3 projektben:

                                • App.xaml.cs: Az alkalmaz\u00e1s bel\u00e9p\u00e9si pontja, amely haszn\u00e1lja a modern .NET alkalmaz\u00e1sokban alkalmazott Host Builder \u00e9s Dependency Injection mint\u00e1kat. A f\u00e9l\u00e9vnek ez nem az anyaga, de a f\u00fcgg\u0151s\u00e9g injekt\u00e1l\u00e1sr\u00f3l m\u00e9g a labor sor\u00e1n lesz sz\u00f3.
                                • Views mappa: Az alkalmaz\u00e1s n\u00e9zeteit tartalmazza, jelenleg a MainPage-et
                                • ViewModels mappa: Az alkalmaz\u00e1s ViewModel-jeit tartalmazza, jelenleg a MainPageViewModel-t
                                • INagivationService (Services mapp\u00e1ban): oldalak k\u00f6z\u00f6tti navig\u00e1ci\u00f3hoz haszn\u00e1lt szolg\u00e1ltat\u00e1s

                                MVVM \u00e9s Boilerplate k\u00f6nyvt\u00e1rak

                                MVVM mint\u00e1t ritk\u00e1n szoktunk kiz\u00e1r\u00f3lag a .NET keretrendszerre t\u00e1maszkodva implement\u00e1lni. \u00c9rdemes haszn\u00e1lni valamilyen MVVM k\u00f6nyvt\u00e1rat, amelyek seg\u00edts\u00e9g\u00e9vel a k\u00f3dunk t\u00f6m\u00f6rebb, \u00e1tl\u00e1that\u00f3bb, \u00e9s kevesebb boilerplate k\u00f3dot fog tartalmazni. A k\u00f6nyvt\u00e1rak k\u00f6z\u00fcl a legelterjedtebbek a k\u00f6vetkez\u0151k:

                                • MVVM Toolkit: Microsoft \u00e1ltal gondozott MVVM k\u00f6nyvt\u00e1r
                                • Prism: R\u00e9gen Microsoft gondoz\u00e1s\u00e1ban \u00e1llt \u00e9s nagyon elterjedt volt, de m\u00e1r k\u00fcls\u0151 fejleszt\u0151k tartj\u00e1k karban \u00e9s fizet\u0151s lett id\u0151 k\u00f6zben.
                                • ReactiveUI: A Reactive Extensions (Rx) k\u00f6nyvt\u00e1rakat haszn\u00e1lja a ViewModel \u00e1llapot\u00e1nak kezel\u00e9s\u00e9re, \u00e9s a n\u00e9zet \u00e9s ViewModel k\u00f6z\u00f6tti adatk\u00f6t\u00e9sre. Ez a k\u00f6nyvt\u00e1r ny\u00fajtja a legt\u00f6bb szolg\u00e1ltat\u00e1st, de a legnehezebben tanulhat\u00f3 is.
                                • Uno.Extensions: MVVM Toolkitre \u00e9p\u00fcl, de t\u00f6bb olyan szolg\u00e1ltat\u00e1st is tartalmaz, amelyek a WinUI keretrendszer hi\u00e1nyoss\u00e1gait p\u00f3tolj\u00e1k.

                                A labor sor\u00e1n a Microsoft \u00e1ltal gondozott MVVM Toolkitet fogjuk haszn\u00e1lni.

                                A kiindul\u00f3 projekt pedig a Windows Template Studio Visual Studio kieg\u00e9sz\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel k\u00e9sz\u00fclt.

                                "},{"location":"labor/5-mvvm/#1-feladat-receptek-fooldal","title":"1. Feladat - Receptek f\u0151oldal","text":"

                                A megold\u00e1s sor\u00e1n \"alulr\u00f3l\", az adatok fel\u0151l fogunk \u00e9p\u00edtkezni \u00e9s fokozatosan fogunk eljutni a n\u00e9zetig. Ugyan a val\u00f3 \u00e9letben egy top-bottom fejleszt\u00e9s gyakran hasznosabb, de a labor sor\u00e1n az id\u0151 r\u00f6vids\u00e9ge miatt az alulr\u00f3l \u00e9p\u00edtkez\u00e9s gyorsabb \u00e9s egyszer\u0171bb, mert \u00edgy nem kell az adatokat mockolni. Az al\u00e1bbi \u00e1bra a f\u0151oldalhoz tartoz\u00f3 fontosabb oszt\u00e1lyokat tekinti \u00e1t.

                                A f\u0151oldal MMVM alap\u00fa megval\u00f3s\u00edt\u00e1sa

                                Fontosabb elemek:

                                • MainPage: ez a View, egy Page lesz\u00e1rmazott, a fel\u00fclet XAML alap\u00fa le\u00edr\u00e1sa.
                                • MainPageViewModel: a f\u0151oldalhoz (MainPage) tartoz\u00f3 ViewModel. Egy (gener\u00e1lt) RecipeGroups tulajdons\u00e1gban receptcsoportokat, a receptcsoportokban recepteket tartalmaz. A n\u00e9zet ezen a receptcsoportok fejl\u00e9c\u00e9t, illetve a csoportokban lev\u0151 receptek fejl\u00e9c\u00e9t \u00e9s k\u00e9peit jelen\u00edti meg adatk\u00f6t\u00e9ssel.
                                • RecipeGroup \u00e9s Recipe: a receptcsoportokat \u00e9s a recepteket reprezent\u00e1l\u00f3 modell oszt\u00e1lyok.
                                • RecipeService: alkalmaz\u00e1slogika/adatel\u00e9r\u00e9s a receptek kezel\u00e9s\u00e9hez (egy t\u00e1voli szolg\u00e1ltat\u00e1ssal kommunik\u00e1l), a ViewModel haszn\u00e1lja.
                                "},{"location":"labor/5-mvvm/#11-adateleresi-szolgaltatas","title":"1.1 Adatel\u00e9r\u00e9si szolg\u00e1ltat\u00e1s","text":"

                                Kezdj\u00fck az adatel\u00e9r\u00e9si r\u00e9teggel, amit most tekinthet\u00fcnk az MVVM mint\u00e1ban a modell r\u00e9tegnek is.

                                Az alkalmaz\u00e1sunk adatait egy webszerverr\u0151l k\u00e9rdezi le (\u00fan. REST API-n, HTTP-n kereszt\u00fcl \u00e9ri el). Az ehhez hasonl\u00f3 kliens-szerver architekt\u00far\u00e1j\u00fa alkalmaz\u00e1sok egy kifejezetten gyakori megold\u00e1snak sz\u00e1m\u00edtanak a modern alkalmaz\u00e1sok fejleszt\u00e9se sor\u00e1n. Err\u0151l b\u0151vebben a k\u00f6vetkez\u0151 f\u00e9l\u00e9vben a Mobil \u00e9s Webes szoftverek, illetve az Adatvez\u00e9relt alkalmaz\u00e1sok t\u00e1rgyakban lesz sz\u00f3. Most el\u00e9g annyit tudni, hogy a kliens alkalmaz\u00e1sunk HTTP k\u00e9r\u00e9seket fog k\u00fcldeni a szervernek, amelyekre a szerver v\u00e1laszolni fog, m\u00e9gpedig JSON form\u00e1tumban szolg\u00e1ltat adatokat.

                                Kliens-szerver architekt\u00fara

                                A t\u00e1voli szolg\u00e1ltat\u00e1s a k\u00f6vetkez\u0151 c\u00edmen \u00e9rhet\u0151 el: https://bmecookbook2.azurewebsites.net/api. A szolg\u00e1ltat\u00e1shoz pedig tartozik egy OpenApi alap\u00fa dokument\u00e1ci\u00f3 a https://bmecookbook2.azurewebsites.net/swagger c\u00edmen. Tanulm\u00e1nyozzuk ezt \u00e1t, vagy ak\u00e1r pr\u00f3b\u00e1ljuk ki a v\u00e9gpotokat a Swagger fel\u00fclet\u00e9n kereszt\u00fcl (ehhez \u00edrjuk be az el\u0151z\u0151 \"swagger\" v\u00e9gz\u0151d\u00e9s\u0171 URL-t egy b\u00f6ng\u00e9sz\u0151 c\u00edmsor\u00e1ba). Az els\u0151 feladathoz a /api/Recipes/Groups v\u00e9gpontot fogjuk haszn\u00e1lni, amely a receptek csoportos\u00edt\u00e1s\u00e1t adja vissza.

                                Vegy\u00fcnk fel az MvvmLab.Core projekt Models mapp\u00e1j\u00e1ba egy \u00faj oszt\u00e1lyt RecipeGroup n\u00e9ven.

                                A swagger seg\u00edts\u00e9g\u00e9vel h\u00edvjuk meg az \"api/Recipes/Groups\" v\u00e9gpontot (pontosabban egy http GET k\u00e9r\u00e9st k\u00fcldj\u00fc)

                                • A swagger fel\u00fcleten a \"Get api/Recipes/Groups\" v\u00e9gpont le\u00edr\u00e1st nyissuk le
                                • Kattintsunk az Execute gombon
                                • A szolg\u00e1ltat\u00e1s \u00e1ltal k\u00fcld\u00f6tt JSON v\u00e1lasz a \"Response body\" alatt jelenik meg: itt azt l\u00e1tjuk, hogy a v\u00e1laszban receptcsoportokat kaptunk. Minden csoportnak van egy \"title\"-je (pl. Chinese, Mexican, Italian), \u00e9s a csoportok alatt tal\u00e1lhat\u00f3k [] k\u00f6z\u00f6tt (JSON t\u00f6mb) a csoportban lev\u0151 receptek adatai.
                                • M\u00e1soljunk v\u00e1g\u00f3lapra egy RecipeGroup-nyi JSON adatot. Haszn\u00e1lhatjuk az \"Example Value\" alatti kimenetet is a v\u00e1g\u00f3lapra m\u00e1sol\u00e1skor (de a nyit\u00f3 [ \u00e9s z\u00e1r\u00f3 ] karatereket ne m\u00e1soljuk ki). Ha valami\u00e9rt elakadn\u00e1nk, az al\u00e1bbi leny\u00edl\u00f3 szakaszb\u00f3l is kim\u00e1solhatjuk a v\u00e1g\u00f3lapra a tartalmat:

                                  V\u00e1g\u00f3lapra m\u00e1soland\u00f3
                                  {\n    \"Title\": \"string\",\n    \"Recipes\": [\n        {\n            \"Id\": 0,\n            \"Title\": \"string\",\n            \"BackgroundImage\": \"string\"\n        }\n    ]\n}\n

                                Visual Studio-ban az Edit men\u00fc Paste Special men\u00fcpontj\u00e1ban a Paste JSON as Classes men\u00fcpontot v\u00e1lasztva illessz\u00fck be a v\u00e1g\u00f3lap tartalm\u00e1t. Ekkor olyan oszt\u00e1lyokat gener\u00e1l a VS, mely megfelel a beillesztett JSON szerkezet\u00e9nek.

                                A kapott oszt\u00e1lyokat \u00e1tnevezhetj\u00fck, hogy a C# k\u00f3dol\u00e1si konvenci\u00f3knak megfeleljenek. A Rootobject oszt\u00e1lyt nevezz\u00fck \u00e1t RecipeGroup-ra, a Recipe oszt\u00e1lyt pedig RecipeHeader-re.

                                public class RecipeGroup\n{\n    public string Title { get; set; }\n    public RecipeHeader[] Recipes { get; set; }\n}\n\npublic class RecipeHeader\n{\n    public int Id { get; set; }\n    public string Title { get; set; }\n    public string BackgroundImage { get; set; }\n}\n

                                List<T> haszn\u00e1lata

                                Eset\u00fcnkben nem volt r\u00e1 sz\u00fcks\u00e9g (mert nem b\u0151vj\u00fck receptgy\u0171jtem\u00e9nyeket), de ha k\u00e9nyelmesebb sz\u00e1munkra, akkor nyugodtan \u00edrjuk \u00e1t a gener\u00e1lt k\u00f3dban a t\u00f6mb\u00f6ket List<T>-re.

                                K\u00e9sz\u00edts\u00fcnk egy IRecipeService interf\u00e9szt az MvvmLab.Core.Services n\u00e9vt\u00e9rbe, amelyen kereszt\u00fcl el fogjuk \u00e9rni a t\u00e1voli szolg\u00e1ltat\u00e1st. Az interf\u00e9szben egy GetRecipeGroupsAsync met\u00f3dust hozzunk l\u00e9tre, amely a recept csoportokat k\u00e9rdezi le \u00e9s adja vissza.

                                public interface IRecipeService\n{\n    public Task<RecipeGroup[]> GetRecipeGroupsAsync();\n}\n

                                Task visszat\u00e9r\u00e9si \u00e9rt\u00e9k

                                Az interf\u00e9szben a t\u00e9nyleges visszat\u00e9r\u00e9si \u00e9rt\u00e9ket (RecipeGroup[]) egy Task<T> objektumba csomagoljuk, mivel a h\u00e1l\u00f3zati m\u0171veleteket aszinkron c\u00e9lszer\u0171 implement\u00e1lni. .NET-ben az aszinkron megval\u00f3s\u00edt\u00e1s legkorszer\u0171bb \u00e9s legegyszer\u0171bb m\u00f3dja a Task-ok alkalmaz\u00e1sa. Az aszinkronit\u00e1s pedig azt biztos\u00edtja itt sz\u00e1munkra, hogy ha a h\u00e1l\u00f3zati k\u00e9r\u00e9s sok\u00e1ig tart, akkor se fagyjon be a felhaszn\u00e1l\u00f3i fel\u00fclet (\u00e9s mindezt k\u00fcl\u00f6n sz\u00e1lak ind\u00edt\u00e1sa n\u00e9lk\u00fcl).

                                Az interf\u00e9sz implement\u00e1ci\u00f3j\u00e1t a MvvmLab.Core.Services n\u00e9vt\u00e9rben hozzuk l\u00e9tre RecipeService n\u00e9ven. A szolg\u00e1ltat\u00e1sunk a HttpClient be\u00e9p\u00edtett .NET oszt\u00e1lyt fogja haszn\u00e1lni a REST API h\u00edv\u00e1sokhoz. A GetFromJsonAsync ind\u00edt egy HTTP GET aszinkron k\u00e9r\u00e9st a megadott c\u00edmre, \u00e9s a v\u00e1laszt JSON form\u00e1tumb\u00f3l deszerializ\u00e1lja a megadott t\u00edpusra.

                                public class RecipeService : IRecipeService\n{\n    private readonly string _baseUrl = \"https://bmecookbook2.azurewebsites.net/api\";\n\n    public async Task<RecipeGroup[]> GetRecipeGroupsAsync()\n    {\n        using var client = new HttpClient();\n        return await client.GetFromJsonAsync<RecipeGroup[]>($\"{_baseUrl}/Recipes/Groups\");\n    }\n}\n

                                A GetFromJsonAsync m\u0171velet aszinkron, \u00edgy Task-kal t\u00e9r vissza, ezt nem blokkol\u00f3 m\u00f3don bev\u00e1rni \u00e9s az eredm\u00e9ny\u00e9t el\u00e9rni az await kulcssz\u00f3val tudjuk.

                                async-await

                                Az async \u00e9s await kulcsszavak a legt\u00f6bb modern nyelvben az aszinkron f\u00fcggv\u00e9nyh\u00edv\u00e1s nyelvi szint\u0171 kezel\u00e9s\u00e9re szolg\u00e1lnak. A m\u0171k\u00f6d\u00e9s\u00e9r\u0151l a f\u00e9l\u00e9v v\u00e9g\u00e9n lesz m\u00e9g sz\u00f3 r\u00e9szletesen, de most a haszn\u00e1lathoz az al\u00e1bbiakat \u00e9rdemes tudni:

                                • Az await kulcssz\u00f3val tudunk bev\u00e1rni aszinkron v\u00e9grehajt\u00e1s\u00fa m\u0171veletet, an\u00e9lk\u00fcl, hogy blokkoln\u00e1nk a h\u00edv\u00f3t.
                                • Az await kulcssz\u00f3t, csak async kulcssz\u00f3val ell\u00e1tott f\u00fcggv\u00e9nyekben haszn\u00e1lhatjuk.
                                • Az async f\u00fcggv\u00e9nyeknek csak Task vagy Task<T> vagy void visszat\u00e9r\u00e9si \u00e9rt\u00e9k\u00fck lehet. (Illetve \"Task szer\u0171\", de ezt nem itt vessz\u00fck.)
                                  • Ha egy async f\u00fcggv\u00e9nyt k\u00edv\u00fclr\u0151l be szeretn\u00e9nk v\u00e1rni, akkor az voiddal nem tudjuk megtenni, mindenk\u00e9ppen Task vagy Task<T> visszat\u00e9r\u00e9si \u00e9rt\u00e9kkel kell rendelkeznie.
                                  • az async f\u00fcggv\u00e9nyekben a return utas\u00edt\u00e1s szintaktik\u00e1ja megv\u00e1ltozik: nem a Task objektummal kell visszat\u00e9rj\u00fcnk, hanem az \u00e1ltala tartalmazott adattal (Task eset\u00e9ben void, Task<T> eset\u00e9ben T).
                                "},{"location":"labor/5-mvvm/#12-fooldal-viewmodel","title":"1.2 F\u0151oldal ViewModel","text":"

                                K\u00f6vetkez\u0151 l\u00e9p\u00e9sben a f\u0151oldal ViewModelj\u00e9t fogjuk elk\u00e9sz\u00edteni, amely az el\u0151bb elk\u00e9sz\u00edtett szolg\u00e1ltat\u00e1st fogja haszn\u00e1lni a recept csoportok lek\u00e9rdez\u00e9s\u00e9hez, \u00e9s \u00e1llapotk\u00e9nt t\u00e1rolja azokat a n\u00e9zet sz\u00e1m\u00e1ra.

                                "},{"location":"labor/5-mvvm/#dependency-injection","title":"Dependency Injection","text":"

                                Nyissuk meg a MainPageViewModel oszt\u00e1lyt az MvvmLab.ViewModels mapp\u00e1b\u00f3l. A ViewModel-\u00fcnknek sz\u00fcks\u00e9ge lesz egy IRecipeService interf\u00e9szt implement\u00e1l\u00f3 oszt\u00e1lyra, amelyen kereszt\u00fcl le tudja k\u00e9rdezni a recept csoportokat. A MainPageViewModel konstruktor\u00e1ban f\u00fcgg\u0151s\u00e9g injekt\u00e1l\u00e1son kereszt\u00fcl szerezz\u00fck be a sz\u00fcks\u00e9ges f\u00fcgg\u0151s\u00e9get. Eset\u00fcnkben ez annyit tesz, hogy v\u00e1runk egy IRecipeService t\u00edpus\u00fa param\u00e9tert, amelyet majd a ViewModel p\u00e9ld\u00e1nyos\u00edt\u00e1skor fog megkapni, a param\u00e9tert pedig elmentj\u00fck egy priv\u00e1t v\u00e1ltoz\u00f3ba.

                                private readonly IRecipeService _recipeService;\n\npublic MainPageViewModel(IRecipeService recipeService)\n{\n    _recipeService = recipeService;\n}\n
                                F\u00fcgg\u0151s\u00e9g Injekt\u00e1l\u00e1s - Dependency Injection - DI

                                Alapesetben az oszt\u00e1lyok szoros csatol\u00e1st alak\u00edtanak ki a f\u00fcgg\u0151s\u00e9geikkel (referencia, p\u00e9ld\u00e1nyos\u00edt\u00e1s).

                                Er\u0151s csatol\u00e1s DI n\u00e9lk\u00fcl

                                Ez a szoros csatol\u00e1s nehez\u00edti a tesztelhet\u0151s\u00e9get, a karbantarthat\u00f3s\u00e1got \u00e9s az \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1got. Ezen seg\u00edt a Dependency Injection (\u00e9s a Strategy) alkalamaz\u00e1sa. A t\u00e1rgy keret\u00e9ben a tervez\u00e9si mint\u00e1khoz kapcsol\u00f3d\u00f3an tanulunk a Dependency Injection (DI) tervez\u00e9si mint\u00e1r\u00f3l, melyet mindig a Strategy mint\u00e1val egy\u00fctt alkalmazunk. A l\u00e9nyege az, hogy egy oszt\u00e1ly nem maga hozza l\u00e9tre a f\u00fcgg\u0151s\u00e9geit (azon oszt\u00e1lyokat, melyekt\u0151l f\u00fcgg, melyeket felhaszn\u00e1l), hanem k\u00edv\u00fclr\u0151l kapja meg, pl. konstruktor param\u00e9terben. A Strategy mint\u00e1b\u00f3l ad\u00f3d\u00f3an pedig az k\u00f6vetkezik, hogy csak \"interf\u00e9szk\u00e9nt\" f\u00fcgg t\u0151l\u00fck.

                                A mai legt\u00f6bb platform egy plusz szolg\u00e1ltat\u00e1st, \u00fan. DI (m\u00e1s nev\u00e9n IoC) kont\u00e9nert is biztos\u00edt a f\u00fcggg\u0151s\u00e9gek k\u00e9nyelmes kezel\u00e9s\u00e9hez. A f\u00fcgg\u0151s\u00e9gek \u00e9letciklus\u00e1t ez esetben egy kit\u00fcntetett komponens kezeli, a DI kont\u00e9ner. A DI kont\u00e9ner (\u00e1br\u00e1n Builder) felel\u0151s az oszt\u00e1lyok p\u00e9ld\u00e1nyos\u00edt\u00e1s\u00e1\u00e9rt \u00e9s a f\u00fcgg\u0151s\u00e9gek beinjekt\u00e1l\u00e1s\u00e1\u00e9rt rekurz\u00edvan.

                                DI oszt\u00e1lydiagramm

                                Ahhoz, hogy a p\u00e9ld\u00e1nyos\u00edt\u00e1s sor\u00e1n a f\u00fcgg\u0151s\u00e9gi gr\u00e1fot bej\u00e1rva beinjekt\u00e1lja a megfelel\u0151 implement\u00e1ci\u00f3kat a kont\u00e9ner, a DI kont\u00e9nerbe be kell regisztr\u00e1lni a f\u00fcgg\u0151s\u00e9gi lek\u00e9pez\u00e9seket. Alkalmaz\u00e1sunkban ezt az App.xaml.cs f\u00e1jlban a ConfigureServices met\u00f3dusban tessz\u00fck meg. Vegy\u00fck fel az al\u00e1bbi sort, pl. a // Core Services szakasz al\u00e1:

                                services.AddTransient<IRecipeService, RecipeService>();\n

                                Ez azt mondja meg, hogy ahol egy oszt\u00e1lyunk IRecipeService f\u00fcgg\u0151s\u00e9get v\u00e1r (pl. MainPageViewModel konstruktora), a DI keretrendszer egy RecipeService implement\u00e1ci\u00f3t sz\u00far be (\u00e9s mivel itt Tranziens \u00e9lettartam\u00fak\u00e9nt regisztr\u00e1ltuk, minden egyes IRecipeService f\u00fcgg\u0151s\u00e9g ig\u00e9nyt egy \u00faj RecipeService p\u00e9ld\u00e1ny fog kiel\u00e9g\u00edteni).

                                Ahhoz, hogy a Dependency Injection az alkalmaz\u00e1sunkban m\u0171k\u00f6dj\u00f6n, a MainPageViewModel oszt\u00e1lyt is be kell regisztr\u00e1lni a kont\u00e9nerbe, ezt is megtal\u00e1ljuk a ConfigureServices alatt.

                                DI kont\u00e9nerekr\u0151l r\u00e9szletesen

                                A DI kont\u00e9nerek haszn\u00e1lat\u00e1val \u00e9s m\u0171k\u00f6d\u00e9s\u00e9vel Adatvez\u00e9relt rendszerek t\u00e1rgy keret\u00e9ben fogunk k\u00e9s\u0151bb r\u00e9szletesen megismerkedni.

                                "},{"location":"labor/5-mvvm/#viewmodel-allapot","title":"ViewModel \u00e1llapot","text":"

                                K\u00f6vetkez\u0151 l\u00e9p\u00e9sben a ViewModel \u00e1llapot\u00e1nak felt\u00f6lt\u00e9s\u00e9t implement\u00e1ljuk.

                                A c\u00e9lunk az, hogy

                                • a MainPageViewModel-ben legyen RecipeGroupsnev\u0171 tulajdons\u00e1g, melyben receptcsoportok vannak (ezt akarjuk a fel\u00fclethez k\u00f6tni),
                                • a RecipeGroups v\u00e1ltoz\u00e1sait k\u00f6vesse le a fel\u00fclet, melyhez sz\u00fcks\u00e9g van az INotifyPropertyChanged megval\u00f3s\u00edt\u00e1s\u00e1ra \u00e9s a PropertyChanged megfelel\u0151 els\u00fct\u00e9s\u00e9re (ahogy a kor\u00e1bbi laboron/h\u00e1zi feladatban m\u00e1r l\u00e1ttuk).

                                Ehhez viszonylag \"sokat\" kellene dolgoznunk, de az MVVM toolkit leegyszer\u0171s\u00edti az \u00e9let\u00fcnket, mind\u00f6ssze a k\u00f6vetkez\u0151t kell megtenn\u00fcnk:

                                • A MainPageViewModel-ben hozzunk l\u00e9tre egy _recipeGroups nev\u0171 RecipeGroup[] tagv\u00e1ltoz\u00f3t (vagyis nem tulajdons\u00e1got).
                                • A v\u00e1ltoz\u00f3t l\u00e1ssuk el a ObservableProperty attrib\u00fatummal.
                                [ObservableProperty]\nprivate RecipeGroup[] _recipeGroups = Array.Empty<RecipeGroup>();\n

                                K\u00e9sz is vagyunk. De mi t\u00f6rt\u00e9nik ennek hat\u00e1s\u00e1ra?

                                • Ez alapj\u00e1n az MVVM Toolkit automatikusan gener\u00e1lni fog egy RecipeGroups nev\u0171 property-t az oszt\u00e1ly gener\u00e1lt m\u00e1sik (partial) fel\u00e9ben.
                                • Ez a gener\u00e1lt property kihaszn\u00e1lja az INotifyPropertyChanged interf\u00e9szt, \u00edgy a RecipeGroups property \u00e9rt\u00e9k\u00e9nek megv\u00e1ltoz\u00e1sakor a PropertyChanged esem\u00e9nyt kiv\u00e1ltva \u00e9rtes\u00edti a n\u00e9zetet, az adatk\u00f6t\u00e9sek ment\u00e9n.
                                • A MainPageViewModel-\u00fcnk m\u00e1r megval\u00f3s\u00edtja az INotifyPropertyChanged interf\u00e9szt, mert az MVVM Toolkit ObservableObject oszt\u00e1ly\u00e1b\u00f3l sz\u00e1rmazik.

                                A MainPageViewModel-ben implement\u00e1ljuk az el\u0151k\u00e9sz\u00edtett INavigationAware interf\u00e9szt, amelynek seg\u00edts\u00e9g\u00e9vel a n\u00e9zetek k\u00f6z\u00f6tti navig\u00e1ci\u00f3s \u00e9letciklus esem\u00e9nyt tudjuk lekezelni, \u00e9s ak\u00e1r adatokat is tudunk \u00e1tadni a ViewModel-ek k\u00f6z\u00f6tt. A OnNavigatedTo met\u00f3dusban k\u00e9rdezz\u00fck le a recept csoportokat az IRecipeService-en kereszt\u00fcl, majd t\u00e1roljuk el a RecipeGroups v\u00e1ltoz\u00f3ban.

                                public partial class MainPageViewModel : ObservableObject, INavigationAware\n{\n    // ...\n\n    public async void OnNavigatedTo(object parameter)\n    {\n        RecipeGroups = await _recipeService.GetRecipeGroupsAsync();\n    }\n\n    public void OnNavigatedFrom()\n    {\n    }\n}\n
                                "},{"location":"labor/5-mvvm/#13-fooldal-nezet","title":"1.3 F\u0151oldal n\u00e9zet","text":"

                                A MainPage-en k\u00e9sz\u00edts\u00fck el a n\u00e9zetet, amelyen megjelen\u00edtj\u00fck a recept csoportokat.

                                Ahhoz, hogy a csoportos\u00edt\u00e1st kezelni tudja a GridView, sz\u00fcks\u00e9g\u00fcnk van egy olyan list\u00e1ra, mely elv\u00e9gzi a csoportos\u00edt\u00e1st. Ezt a CollectionViewSource oszt\u00e1ly seg\u00edts\u00e9g\u00e9vel tudjuk megval\u00f3s\u00edtani, ami bizonyos szempontb\u00f3l UI specifikus burkol\u00f3 feladatokat l\u00e1t el gy\u0171jtem\u00e9nyeken. A CollectionViewSource-nak meg kell adnunk a csoportos\u00edtand\u00f3 elemeket, valamint azt, hogy a csoportokat milyen property alapj\u00e1n hozza l\u00e9tre. Tov\u00e1bb\u00e1 meg kell adnunk azt is, hogy a csoportokon bel\u00fcl milyen property alapj\u00e1n jelen\u00edtse meg az elemeket.

                                Hozzuk l\u00e9tre az oldal er\u0151forr\u00e1sai k\u00f6z\u00f6tt a CollectionViewSource p\u00e9ld\u00e1nyt (az al\u00e1bbi k\u00f3dot a MainPage.xaml-be, a Grid f\u00f6l\u00e9 tegy\u00fck be, vele egy szintre).

                                <Page.Resources>\n    <CollectionViewSource x:Name=\"RecipeGroupsCollectionSource\"\n                            IsSourceGrouped=\"True\"\n                            ItemsPath=\"Recipes\"\n                            Source=\"{x:Bind ViewModel.RecipeGroups, Mode=OneWay}\" />\n</Page.Resources>\n

                                Note

                                Vegy\u00fck \u00e9szre, hogy az adatk\u00f6t\u00e9s sor\u00e1n a ViewModel tulajdons\u00e1ghoz k\u00f6t\u00fcnk, mely a MainPage.xaml.cs-ben tal\u00e1lhat\u00f3, \u00e9s egyszer\u0171en csak \u00e1tkasztolja a DataContext property-t a ViewModel t\u00edpusunkra.

                                public MainPageViewModel ViewModel => DataContext as MainPageViewModel;\n

                                Az, hogy a vez\u00e9rl\u0151k (oldalak) DataContext tulajdons\u00e1g\u00e1ban a ViewModel-t t\u00e1roljuk tipikus az MVVM mint\u00e1ban. Eset\u00fcnkben ezt a gener\u00e1lt projekt NavigationService oszt\u00e1lya teszi meg nek\u00fcnk.

                                Er\u0151forr\u00e1sok

                                XAML k\u00f6rnyezetben minden vez\u00e9rl\u0151 (fenti p\u00e9ld\u00e1ban Page) \u00e9s az Application oszt\u00e1ly is, rendelkezik egy Resources property-vel, mely egy kulcs \u00e9rt\u00e9k t\u00e1rol\u00f3 (Dictionary<string, object>), alap esetben. Ebbe tudunk t\u00f6bbsz\u00f6r felhaszn\u00e1lhat\u00f3 objektumokat rakni, ak\u00e1r alkalmaz\u00e1s szinten is. Ha ehhez az er\u0151forr\u00e1sok p\u00e9ld\u00e1nyos\u00edt\u00e1sakor megadjuk az x:Key attrib\u00fatumot, akkor az er\u0151forr\u00e1sokat a kulcs alapj\u00e1n tudjuk lek\u00e9rdezni pl.: a {StaticResource Key} markup extensionnel.

                                Mi viszont itt kifejezetten x:Key helyett x:Name-et adtunk meg, mert az x:Bind-ban n\u00e9v szerint szeretn\u00e9nk majd hivatkozni r\u00e1 (eml\u00e9kezz\u00fcnk: az x:Name attrib\u00fatum seg\u00edts\u00e9g\u00e9vel azt tudjuk el\u00e9rni, hogy gener\u00e1l\u00f3dik ilyen n\u00e9ven egy tagv\u00e1ltoz\u00f3 az oszt\u00e1lyunkban, \u00edgy a code behind f\u00e1jlb\u00f3l, vagy x:Bind adatk\u00f6t\u00e9s sor\u00e1n ilyen n\u00e9ven el tudjuk \u00e9rni).

                                A receptek list\u00e1z\u00e1s\u00e1hoz, most egy speci\u00e1lis GridView lesz\u00e1rmazott vez\u00e9rl\u0151t haszn\u00e1ljunk, m\u00e9gpedig az AdaptiveGridView-t a CommunityToolkit csomagb\u00f3l, amely a n\u00e9zet m\u00e9ret\u00e9nek megfelel\u0151en v\u00e1ltoztatja a megjelen\u00edtett elemek sz\u00e1m\u00e1t \u00e9s m\u00e9ret\u00e9t, illetve t\u00e1mogatja a Command-okat az elem kattint\u00e1s eset\u00e9ben. A k\u00fcls\u0151 vez\u00e9rl\u0151k hivatkoz\u00e1s\u00e1hoz vegy\u00fck fel az oldalra a k\u00f6vetkez\u0151 n\u00e9vteret:

                                xmlns:controls=\"using:CommunityToolkit.WinUI.UI.Controls\"\n

                                K\u00e9sz\u00edts\u00fck el a GridView-t, amelynek a ItemsSource property-j\u00e9t a fenti er\u0151forr\u00e1sban l\u00e9v\u0151 RecipeGroupsCollectionSource.View-ra k\u00f6tj\u00fck.

                                A GridView-en bel\u00fcl a megszokott m\u00f3don az ItemTemplate property-n kereszt\u00fcl tudjuk megadni, hogy az egyes elemeket hogyan kell megjelen\u00edteni. Eset\u00fcnkben egy k\u00e9pet \u00e9s egy sz\u00f6veget rakunk ki a receptek c\u00edme alapj\u00e1n egy \"k\u00e1rtya\" szer\u0171 layoutra.

                                A GroupStyle property-n kereszt\u00fcl pedig meg tudjuk adni, hogy a csoportokat hogyan kell megjelen\u00edteni. Eset\u00fcnkben a fejl\u00e9cet akarjuk testreszabni.

                                A MainPage.xaml-ben a <Grid x:Name=\"ContentArea\"> ... grid-et cser\u00e9lj\u00fck le a k\u00f6vetkez\u0151re:

                                <Grid x:Name=\"ContentArea\" Padding=\"10\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n\n    <TextBlock Text=\"Recipes\"\n               Grid.Row=\"0\"\n               Style=\"{StaticResource TitleLargeTextBlockStyle}\" />\n\n    <controls:AdaptiveGridView Grid.Row=\"1\"\n                               DesiredWidth=\"180\"\n                               IsItemClickEnabled=\"True\"\n                               ItemHeight=\"160\"\n                               ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                               SelectionMode=\"None\"\n                               StretchContentForSingleRow=\"False\">\n        <GridView.ItemTemplate>\n            <DataTemplate x:DataType=\"models:RecipeHeader\">\n                <Grid MaxWidth=\"300\">\n                    <Image Source=\"{x:Bind BackgroundImage}\" />\n                    <Border Height=\"40\"\n                            Padding=\"10,0,0,0\"\n                            VerticalAlignment=\"Bottom\"\n                            Background=\"#88000000\">\n                        <TextBlock VerticalAlignment=\"Center\"\n                                   Foreground=\"White\"\n                                   Text=\"{x:Bind Title}\" />\n                    </Border>\n                </Grid>\n            </DataTemplate>\n        </GridView.ItemTemplate>\n        <GridView.GroupStyle>\n            <GroupStyle>\n                <GroupStyle.HeaderTemplate>\n                    <DataTemplate x:DataType=\"models:RecipeGroup\">\n                        <TextBlock Margin=\"0\"\n                                   Style=\"{ThemeResource TitleTextBlockStyle}\"\n                                   Text=\"{x:Bind Title}\" />\n                    </DataTemplate>\n                </GroupStyle.HeaderTemplate>\n            </GroupStyle>\n        </GridView.GroupStyle>\n    </controls:AdaptiveGridView>\n</Grid>\n

                                Vegy\u00fck fel a k\u00f6vetkez\u0151 n\u00e9vteret (ebben vannak a modell oszt\u00e1lyaink):

                                `xmlns:models=\"using:MvvmLab.Core.Models\"`\n

                                Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st! Gy\u0151z\u0151dj\u00fcnk meg r\u00f3la, hogy a recept csoportok megjelennek a f\u0151oldalon.

                                "},{"location":"labor/5-mvvm/#2-feladat-recept-reszletes-oldal","title":"2. Feladat - Recept r\u00e9szletes oldal","text":"

                                A receptek r\u00e9szletes oldal\u00e1nak elk\u00e9sz\u00edt\u00e9se a k\u00f6vetkez\u0151 l\u00e9p\u00e9sekb\u0151l fog \u00e1llni:

                                1. Kieg\u00e9sz\u00edtj\u00fck az IRecipeService interf\u00e9szt egy GetRecipeAsync met\u00f3dussal, \u00e9s l\u00e9trehozzuk a sz\u00fcks\u00e9ges oszt\u00e1lyokat
                                2. L\u00e9trehozzuk a RecipeDetailPageViewModel ViewModel-t, amiben lek\u00e9rdezz\u00fck a recept adatait a RecipeDetailPageViewModel-ben az IRecipeService-en kereszt\u00fcl (a VM az azonos\u00edt\u00f3t kapja meg a navig\u00e1ci\u00f3 sor\u00e1n)
                                3. L\u00e9trehozzuk a RecipeDetailPage n\u00e9zetet, \u00e9p\u00edtve a ViewModel adataira
                                4. Regisztr\u00e1ljuk a ViewModel-t \u00e9s a n\u00e9zetet a Dependency Injection konfigur\u00e1ci\u00f3hoz \u00e9s a navig\u00e1ci\u00f3hoz
                                5. \u00c1tnavig\u00e1lunk a RecipeDetailPage-re a MainPageViewModel-b\u0151l a receptre t\u00f6rt\u00e9n\u0151 kattint\u00e1sra az INavigationService seg\u00edts\u00e9g\u00e9vel, \u00e9s \u00e1tadjuk a kiv\u00e1lasztott recept azonos\u00edt\u00f3j\u00e1t a r\u00e9szletes oldalnak
                                "},{"location":"labor/5-mvvm/#21-recept-lekerdezese","title":"2.1 Recept lek\u00e9rdez\u00e9se","text":"

                                Hozzuk l\u00e9tre a Recipe oszt\u00e1lyt a MvvmLab.Core.Model n\u00e9vt\u00e9rbe, \u00e9s gener\u00e1ljuk le a tartalm\u00e1t a /api/recipes/{id} v\u00e9gpont \u00e1ltal visszaadott p\u00e9lda JSON adatokb\u00f3l, a fent megismert m\u00f3dszerrel (Paste special).

                                public class Recipe\n{\n    public int Id { get; set; }\n    public string BackgroundImage { get; set; }\n    public string Title { get; set; }\n    public string[] ExtraImages { get; set; }\n    public string[] Ingredients { get; set; }\n    public string Directions { get; set; }\n    public Comment[] Comments { get; set; }\n}\n\npublic class Comment\n{\n    public string Name { get; set; }\n    public string Text { get; set; }\n}\n

                                Warning

                                A \"Paste Special\" sor\u00e1n fontos, hogy olyan receptet tegy\u00fcnk el\u0151tte a v\u00e1g\u00f3lapra, melyhez tartozik megjegyz\u00e9s (k\u00fcl\u00f6nben a Comment oszt\u00e1ly nem fog legener\u00e1l\u00f3dni, illetve a Recipe oszt\u00e1lyban a Comments t\u00edpus\u00e1nak object[] t\u00edpus gener\u00e1l\u00f3dik). \u00c9rdemes ehhez a swagger le\u00edr\u00e1s \"Example value\" mez\u0151j\u00e9b\u0151l a v\u00e1g\u00f3lapra m\u00e1solni a mint\u00e1t!

                                A IRecipeService interf\u00e9szt \u00e9s implement\u00e1ci\u00f3j\u00e1t eg\u00e9sz\u00edts\u00fck ki egy GetRecipeAsync met\u00f3dussal, mely egy receptet ad vissza az azonos\u00edt\u00f3ja alapj\u00e1n.

                                IRecipeService
                                public Task<Recipe> GetRecipeAsync(int id);\n
                                RecipeService
                                public async Task<Recipe> GetRecipeAsync(int id)\n{\n    using var client = new HttpClient();\n    return await client.GetFromJsonAsync<Recipe>($\"{_baseUrl}/Recipes/{id}\");\n}\n
                                "},{"location":"labor/5-mvvm/#22-recept-reszletes-viewmodel","title":"2.2 Recept r\u00e9szletes ViewModel","text":"

                                A ViewModel k\u00e9sz\u00edt\u00e9se a f\u0151oldalhoz k\u00e9pest m\u00e1r ujjgyakorlat (alapvet\u0151en annak mint\u00e1j\u00e1ra lehet dolgozni). Hozzuk l\u00e9tre a RecipeDetailPageViewModel oszt\u00e1lyt az MvvmLab.ViewModels mapp\u00e1ban.

                                A ViewModel-nek sz\u00fcks\u00e9ge lesz egy IRecipeService interf\u00e9szt implement\u00e1l\u00f3 oszt\u00e1lyra, amelyen kereszt\u00fcl le tudja k\u00e9rdezni a receptet. A RecipeDetailPageViewModel konstruktor\u00e1ban DI seg\u00edts\u00e9g\u00e9vel szerezz\u00fck be a sz\u00fcks\u00e9ges f\u00fcgg\u0151s\u00e9get.

                                private readonly IRecipeService _recipeService;\n\npublic RecipeDetailPageViewModel(IRecipeService recipeService)\n{\n    _recipeService = recipeService;\n}\n

                                A RecipeDetailPageViewModel-ben hozzunk l\u00e9tre egy _recipe nev\u0171 Recipe t\u00edpus\u00fa v\u00e1ltoz\u00f3t, amelyben t\u00e1rolni fogjuk a receptet. A v\u00e1ltoz\u00f3t attribut\u00e1ljuk fel a ObservableProperty attrib\u00fatummal, mely alapj\u00e1n az MVVM Toolkit automatikusan gener\u00e1lni fogja a Recipe nev\u0171 property-t az oszt\u00e1ly m\u00e1sik gener\u00e1lt partial fel\u00e9ben. Ehhez sz\u00fcks\u00e9ges, hogy az oszt\u00e1ly az ObservableObject oszt\u00e1lyb\u00f3l sz\u00e1rmazzon, publikus legyen \u00e9s a partial kulcssz\u00f3val legyen ell\u00e1tva.

                                public partial class RecipeDetailPageViewModel : ObservableObject\n{\n    // ...\n\n    [ObservableProperty]\n    private Recipe _recipe = new();\n

                                Implement\u00e1ljuk a RecipeDetailPageViewModel-ben az el\u0151k\u00e9sz\u00edtett INavigationAware interf\u00e9szt. Arra k\u00e9sz\u00fcl\u00fcnk, hogy a navig\u00e1ci\u00f3s param\u00e9terk\u00e9nt a megjelen\u00edteni k\u00edv\u00e1nt recept azonos\u00edt\u00f3j\u00e1t fogjuk megkapni. A OnNavigatedTo met\u00f3dusban k\u00e9rdezz\u00fck le a receptet a RecipeService-en kereszt\u00fcl, majd t\u00e1roljuk el a Recipe tulajdons\u00e1gban.

                                public partial class RecipeDetailPageViewModel : ObservableObject, INavigationAware\n{\n    // ...\n\n    public async void OnNavigatedTo(object parameter)\n    {\n        Recipe = await _recipeService.GetRecipeAsync((int)parameter);\n    }\n\n    public void OnNavigatedFrom()\n    {\n    }\n}\n

                                Note

                                A OnNavigatedTo m\u0171velet fejl\u00e9c\u00e9ben haszn\u00e1lni kellett az async kulcssz\u00f3t, mert haszn\u00e1ltuk az await-et a t\u00f6rzs\u00e9ben.

                                "},{"location":"labor/5-mvvm/#23-recept-reszletes-oldal-navigacio","title":"2.3 Recept r\u00e9szletes oldal, navig\u00e1ci\u00f3","text":"

                                Hozzunk l\u00e9tre egy \u00faj oldalt RecipeDetailPage n\u00e9ven a Views mapp\u00e1ba (Views mapp\u00e1n jobb gomb / Add New Item / Blank Page (WinUI 3)), amelyen megjelen\u00edtj\u00fck a receptet. Els\u0151 k\u00f6rben csak a recept c\u00edm\u00e9t jelen\u00edts\u00fck meg egy TextBlock-ban.

                                <Grid x:Name=\"ContentArea\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"48\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n\n    <TextBlock Grid.Row=\"0\"\n               Style=\"{StaticResource PageTitleStyle}\"\n               Text=\"{x:Bind ViewModel.Recipe.Title, Mode=OneWay}\" />\n</Grid>\n

                                Az adatk\u00f6t\u00e9shez vegy\u00fck fel a RecipeDetailPage.xaml.cs-ben a ViewModel property-t a f\u0151oldal mint\u00e1j\u00e1ra.

                                public RecipeDetailPageViewModel ViewModel => (RecipeDetailPageViewModel)DataContext;\n

                                Ford\u00edt\u00e1si hib\u00e1k

                                Ha valami\u00e9rt egzotikus hib\u00e1kat kapn\u00e1nk az \u00faj oldal felv\u00e9tele ut\u00e1n t\u00f6r\u00f6lj\u00fck ki a projekt f\u00e1jlb\u00f3l az al\u00e1bbi sorokat:

                                <ItemGroup>\n    <None Remove=\"Views\\RecipeDetailPage.xaml\" />\n</ItemGroup>\n
                                <Page Update=\"Views\\RecipeDetailPage.xaml\">\n    <Generator>MSBuild:Compile</Generator>\n</Page>\n

                                A navig\u00e1ci\u00f3 t\u00e1mogat\u00e1s\u00e1hoz a Services mapp\u00e1ban l\u00e9v\u0151 PageService-ben regisztr\u00e1ljuk be a RecipeDetailPage-et az al\u00e1bbi 3 l\u00e9p\u00e9sben:

                                1. Vegy\u00fck fel a n\u00e9zet kulcs\u00e1t a Pages oszt\u00e1lyba.

                                  public static class Pages\n{\n    public static string Main { get; } = \"Main\";\n    public static string Detail { get; } = \"Detail\";\n}\n
                                2. Regisztr\u00e1ljuk a n\u00e9zetet \u00e9s ViewModel kapcsolatot a PageService-ben.

                                  public PageService()\n{\n    Configure<MainPageViewModel, MainPage>(Pages.Main);\n    Configure<RecipeDetailPageViewModel, RecipeDetailPage>(Pages.Detail);\n}\n
                                3. Az App.xaml.cs f\u00e1jlban a ConfigureServices met\u00f3dusban regisztr\u00e1ljuk be a ViewModel-t \u00e9s a n\u00e9zetet a Dependency Injection kont\u00e9nerbe.

                                  services.AddTransient<RecipeDetailPage>();\nservices.AddTransient<RecipeDetailPageViewModel>();\n

                                Ezekre az\u00e9rt van sz\u00fcks\u00e9g, mert a projekt sablonban l\u00e9v\u0151 INavigationService alapvet\u0151en egy kulccsal azonos\u00edtja a n\u00e9zeteket, annak \u00e9rdek\u00e9ben, hogy a ViewModel-ben ne legyen sz\u00fcks\u00e9g a n\u00e9zet t\u00edpus\u00e1nak ismeret\u00e9re. A kulcs alapj\u00e1n pedig ki tudja keresni, hogy pontosan melyik Viewt kell megjelen\u00edteni, \u00e9s melyik ViewModel-t kell p\u00e9ld\u00e1nyos\u00edtani a n\u00e9zet DataContext-j\u00e9be a DI kont\u00e9nerb\u0151l.

                                A MainPageViewModel-ben injekt\u00e1ljuk be az INavigationService-t, amelyen kereszt\u00fcl navig\u00e1lni fogunk a RecipeDetailPage-re.

                                private readonly INavigationService _navigationService;\n\npublic MainPageViewModel(IRecipeService recipeService, INavigationService navigationService)\n{\n    _recipeService = recipeService;\n    _navigationService = navigationService;\n}\n
                                "},{"location":"labor/5-mvvm/#command","title":"Command","text":"

                                Eddig az MVVM minta egyik oldal\u00e1val foglalkoztunk: hogyan \u00e9ri el adatk\u00f6t\u00e9ssel \u00e9s jelen\u00edti meg a View a ViewModel-ben lev\u0151 adatokat. Ugyanakkor, a View \u00e9s ViewModel k\u00f6z\u00f6tt \u00e1ltal\u00e1ban van egy m\u00e1sik kapcsolat is: ez arr\u00f3l sz\u00f3l, hogy a View esem\u00e9nyei (pl. kattint\u00e1s) hogyan hatnak vissza a ViewModel-re. Most ezzel fogunk foglalkozni.

                                Eset\u00fcnkben pl. meg kell oldani, hogy a f\u0151oldali n\u00e9zeten egy Recepten t\u00f6rt\u00e9n\u0151 kattint\u00e1s eljusson a MainPageViewModel-hez, \u00e9s az ennek hat\u00e1s\u00e1ra \u00e1tnavig\u00e1ljon az adott recept r\u00e9szletes n\u00e9zet\u00e9re.

                                A ViewModel a v\u00e9grehajthat\u00f3 m\u0171veleteket az MVVM mint\u00e1ban tipikusan ICommand interf\u00e9szt megval\u00f3s\u00edt\u00f3 objektumokon kereszt\u00fcl publik\u00e1lja (amelyek a konkr\u00e9t m\u0171velet v\u00e9grehajt\u00e1s\u00e1n t\u00fal kezelhetik a m\u0171velet v\u00e9grehajt\u00e1s\u00e1nak felt\u00e9teleit is).

                                A MainPageViewModel-ben k\u00e9sz\u00edts\u00fcnk egy Commandot, mely a receptre kattintva fog lefutni. A Command param\u00e9terk\u00e9nt megkapja a kiv\u00e1lasztott recept fejl\u00e9cet, \u00e9s \u00e1tnavig\u00e1l a RecipeDetailPage-re, ahol \u00e1tad\u00e1sra ker\u00fcl a kiv\u00e1lasztott recept azonos\u00edt\u00f3ja.

                                Most l\u00e9tre kellene hozzunk egy \u00fagy, ICommand interf\u00e9szt implement\u00e1l\u00f3 oszt\u00e1lyt, majd ebb\u0151l fel kellene vegy\u00fcnk egy p\u00e9ld\u00e1nyt (tulajdons\u00e1got) a ViewModel-be. Ezt a k\u00e9t l\u00e9p\u00e9st az MVVM toolkit leegyszer\u0171s\u00edti, csak egy [RelayCommand] attrib\u00fatummal ell\u00e1tott f\u00fcggv\u00e9nyt kell felvegy\u00fcnk a ViewModelbe:

                                [RelayCommand]\nprivate void RecipeSelected(RecipeHeader recipe)\n{\n    _navigationService.NavigateTo(Pages.Detail, recipe.Id);\n}\n

                                Ennek hat\u00e1s\u00e1ra a compiler legener\u00e1lja a command oszt\u00e1lyt \u00e9s a tulajdons\u00e1got a ViewModel-be RecipeSelectedCommand n\u00e9ven.

                                A parancs \u00e9s a ViewModel el\u0151 van k\u00e9sz\u00edtve, de a View m\u00e9g semmit nem tud a parancsr\u00f3l. A ViewModel-ben lev\u0151 commandunkat a szok\u00e1sos technik\u00e1kkal r\u00e1 kell k\u00f6ss\u00fck a View megfelel\u0151 esem\u00e9ny\u00e9re. MVVM eset\u00e9n mindig \u00edgy haszn\u00e1ljuk a Command mint\u00e1t! A megk\u00f6zel\u00edt\u00e9s sz\u00e9ps\u00e9ge az, hogy ez teljesen a szok\u00e1sos, View->ViewModel ir\u00e1ny\u00fa adatk\u00f6t\u00e9ssel t\u00f6rt\u00e9nik (amit m\u00e1r eddig is t\u00f6bbsz\u00f6r haszn\u00e1ltunk).

                                Ennek megfelel\u0151en a MainPage-en k\u00f6ss\u00fck a AdaptiveGridView ItemClickCommand tulajdons\u00e1g\u00e1t a RecipeSelectedCommand-ra.

                                ItemClickCommand=\"{x:Bind ViewModel.RecipeSelectedCommand}\"\n

                                Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st! Gy\u0151z\u0151dj\u00fcnk meg r\u00f3la, hogy a receptekre kattintva megjelenik a recept r\u00e9szletes oldala.

                                Kitekint\u00e9s: Ha nincs a haszn\u00e1lni k\u00edv\u00e1nt esem\u00e9nyre Command?

                                Ha a vez\u00e9rl\u0151 bizonyos esem\u00e9nyekre biztos\u00edt Commandot, akkor viszonylag egyszer\u0171 dolgunk van, amire fentebb l\u00e1thattunk egy p\u00e9ld\u00e1t. Azonban, ha a vez\u00e9rl\u0151 nem biztos\u00edt Commandot (pl.: a be\u00e9p\u00edtett GridView.ItemClicked), akkor t\u00f6bb lehet\u0151s\u00e9g\u00fcnk is van:

                                1. Code-Behind \"ragaszt\u00f3 k\u00f3d\": A vez\u00e9rl\u0151 esem\u00e9ny\u00e9t kezelj\u00fck le, \u00e9s a code-behindban (xaml.cs) ViewModel-ben h\u00edvjuk meg a megfelel\u0151 met\u00f3dust/commadot.

                                  <controls:AdaptiveGridView x:Name=\"gridView\"\n                            ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                            IsItemClickEnabled=\"True\"\n                            ItemClick=\"GridView_ItemClick\">\n
                                  private void GridView_ItemClick(object sender, ItemClickEventArgs e)\n{\n    ViewModel.RecipeSelectedCommand.Execute((RecipeHeader)e.ClickedItem);\n}\n
                                2. x:Bind esem\u00e9ny k\u00f6t\u00e9s: haszn\u00e1ljuk az x:Bind met\u00f3dus k\u00f6t\u00e9si lehet\u0151s\u00e9g\u00e9t, amelynek seg\u00edts\u00e9g\u00e9vel a vez\u00e9rl\u0151 esem\u00e9ny\u00e9t tudjuk k\u00f6tni a ViewModel-ben l\u00e9v\u0151 met\u00f3dusra. A met\u00f3dusnak viszont ilyenkor vagy param\u00e9ter n\u00e9lk\u00fclinek kell lennie, vagy olyan param\u00e9tereket kell fogadnia, amely az esem\u00e9ny szignat\u00far\u00e1j\u00e1ra illeszkedik.

                                  View - MainPage.xaml
                                  <controls:AdaptiveGridView x:Name=\"gridView\"\n                            ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                            IsItemClickEnabled=\"True\"\n                            ItemClick=\"{x:Bind ViewModel.RecipeSelected\">\n</controls:AdaptiveGridView>\n
                                  ViewModel - MainPageViewModel
                                  public void RecipeSelected(object sender, ItemClickEventArgs e)\n{\n   ...\n}\n

                                  Ennek a m\u00f3dszernek a h\u00e1tr\u00e1nya, hogy a esem\u00e9ny param\u00e9tereivel a ViewModel-be a n\u00e9zet keretrendszer f\u00fcgg\u0151s\u00e9geit is beviszi (esem\u00e9nykezel\u0151 param\u00e9ter t\u00edpusok), pedig az alap gondolatunk az volt, hogy a ViewModel f\u00fcggetlen legyen a n\u00e9zett\u0151l. Term\u00e9szetesen ez a m\u00f3dszer is j\u00f3l tud m\u0171k\u00f6dni, ha r\u00e9szben feladjuk az MVVM minta szigor\u00fa betart\u00e1s\u00e1t.

                                3. A Behavior-\u00f6k seg\u00edts\u00e9g\u00e9vel, azon bel\u00fcl is az EventTriggerBehavior \u00e9s InvokeCommandAction oszt\u00e1lyokkal tudunk Commandot k\u00f6tni tetsz\u0151leges vez\u00e9rl\u0151 esem\u00e9nyre.

                                  <controls:AdaptiveGridView x:Name=\"gridView\"\n                            ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                            IsItemClickEnabled=\"True\">\n    <i:Interaction.Behaviors>\n        <c:EventTriggerBehavior EventName=\"ItemClick\">\n            <c:InvokeCommandAction Command=\"{x:Bind ViewModel.RecipeSelectedCommand}\" \n                                   InputConverter=\"{StaticResource ItemClickedInputConverter}\" />\n        </c:EventTriggerBehavior>\n    </i:Interaction.Behaviors>\n

                                  Ezzel szinte teljesen deklarat\u00edvv\u00e1 tudjuk tenni hagyni a n\u00e9zetet, de m\u00e9g \u00edgy is k\u00e9sz\u00edten\u00fcnk kell egy ItemClickedInputConverter oszt\u00e1lyt, amely az esem\u00e9ny param\u00e9tereit \u00e1talak\u00edtja a megfelel\u0151 t\u00edpusra az IValueConverter interf\u00e9sz seg\u00edts\u00e9g\u00e9vel.

                                  public class ItemClickedInputConverter : IValueConverter\n{\n    public object Convert(object value, Type targetType, object parameter, string language)\n    {\n        return (RecipeHeader)((value as ItemClickEventArgs)?.ClickedItem);\n    }\n\n    public object ConvertBack(object value, Type targetType, object parameter, string language)\n    {\n        throw new NotImplementedException();\n    }\n}\n

                                  A behavior-\u00f6k egy\u00e9bk\u00e9nt egy teljesen \u00e1ltal\u00e1nos mechanizmus a XAML vil\u00e1gban, amelyek seg\u00edts\u00e9g\u00e9vel a n\u00e9zetekhez tudunk \u00fajrafelhaszn\u00e1lhat\u00f3 viselked\u00e9st hozz\u00e1adni (b\u0151vebben itt).

                                "},{"location":"labor/5-mvvm/#24-recept-reszletes-nezet","title":"2.4 Recept r\u00e9szletes n\u00e9zet","text":"

                                A recept r\u00e9szletes adatainak megjelen\u00edt\u00e9s\u00e9hez egy Grid-et haszn\u00e1ljunk, amelynek k\u00e9t oszlopa van. Az els\u0151 oszlopban egy ScrollViewer-t helyezz\u00fcnk el, amelybe egy StackPanel ker\u00fcl. A StackPanel-ben helyezz\u00fcnk el egy FlipView-t, amelyben a recept k\u00e9peit fogjuk megjelen\u00edteni. A FlipView egy listak\u00e9nt m\u0171k\u00f6dik, de az elemeit egy lapozhat\u00f3 fel\u00fcleten jelen\u00edti meg.

                                A FlipView alatt lesz tal\u00e1lhat\u00f3 el egy ItemsControl (egyszer\u0171 lista, mely nem t\u00e1mogat g\u00f6rget\u00e9st, kiv\u00e1laszt\u00e1st, kattint\u00e1st stb.), amelyben a recept hozz\u00e1val\u00f3it fogjuk megjelen\u00edteni.

                                Ez al\u00e1 ker\u00fcl egy TextBlock, amelybe a recept elk\u00e9sz\u00edt\u00e9s\u00e9nek l\u00e9p\u00e9sei ker\u00fclnek.

                                A m\u00e1sodik oszlopba helyezz\u00fcnk el egy Grid-et, amelybe kommentek list\u00e1ja \u00e9s beviteli mez\u0151i fognak ker\u00fclni.

                                Az al\u00e1bbi k\u00f3dot a labor sor\u00e1n nyugodtan m\u00e1solhatjuk a RecipeDetailPage.xaml f\u00e1jlba, \u00fajdons\u00e1g ebben a k\u00f3dban nincs az eddigiekhez k\u00e9pest.

                                <?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<Page x:Class=\"MvvmLab.Views.RecipeDetailPage\"\n      xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n      xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n      xmlns:local=\"using:MvvmLab.Views\"\n      xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n      xmlns:models=\"using:MvvmLab.Core.Models\"\n      Background=\"{ThemeResource ApplicationPageBackgroundThemeBrush}\"\n      mc:Ignorable=\"d\">\n\n    <Grid x:Name=\"ContentArea\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n\n        <TextBlock Grid.Row=\"0\" Padding=\"10\"\n                   Style=\"{StaticResource TitleTextBlockStyle}\"\n                   Text=\"{x:Bind ViewModel.Recipe.Title, Mode=OneWay}\" />\n\n        <Grid Grid.Row=\"1\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"3*\" />\n                <ColumnDefinition Width=\"*\" />\n            </Grid.ColumnDefinitions>\n\n            <ScrollViewer Grid.Column=\"0\" Padding=\"20 10 0 20\">\n                <StackPanel Orientation=\"Vertical\">\n                    <StackPanel x:Name=\"images\"\n                                Margin=\"0,0,24,0\"\n                                Orientation=\"Vertical\">\n                        <TextBlock Margin=\"0,0,0,12\"\n                                   Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                                   Text=\"Images\" />\n                        <FlipView x:Name=\"flipView\"\n                                  MaxHeight=\"250\"\n                                  VerticalAlignment=\"Top\"\n                                  ItemsSource=\"{x:Bind ViewModel.Recipe.ExtraImages, Mode=OneWay}\">\n                            <FlipView.ItemTemplate>\n                                <DataTemplate>\n                                    <Image Source=\"{Binding}\" Stretch=\"Uniform\" />\n                                </DataTemplate>\n                            </FlipView.ItemTemplate>\n                        </FlipView>\n                    </StackPanel>\n\n                    <StackPanel x:Name=\"ingredients\"\n                                Margin=\"0,0,24,0\"\n                                Orientation=\"Vertical\">\n                        <TextBlock Margin=\"0,0,0,12\"\n                                   Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                                   Text=\"Ingredients\" />\n                        <ItemsControl HorizontalAlignment=\"Left\" ItemsSource=\"{x:Bind ViewModel.Recipe.Ingredients, Mode=OneWay}\">\n                            <ItemsControl.ItemTemplate>\n                                <DataTemplate>\n                                    <TextBlock Margin=\"0,0,0,10\"\n                                               Text=\"{Binding}\"\n                                               TextWrapping=\"Wrap\" />\n                                </DataTemplate>\n                            </ItemsControl.ItemTemplate>\n                        </ItemsControl>\n                    </StackPanel>\n\n                    <StackPanel x:Name=\"directions\"\n                                Margin=\"0,0,24,0\"\n                                Orientation=\"Vertical\"\n                                RelativePanel.RightOf=\"ingredients\">\n                        <TextBlock Margin=\"0,0,0,12\"\n                                   Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                                   Text=\"Directions\" />\n                        <TextBlock HorizontalAlignment=\"Left\"\n                                   Text=\"{x:Bind ViewModel.Recipe.Directions, Mode=OneWay}\"\n                                   TextWrapping=\"Wrap\" />\n                    </StackPanel>\n                </StackPanel>\n            </ScrollViewer>\n\n            <Grid Grid.Column=\"1\" RowSpacing=\"12\">\n                <Grid.RowDefinitions>\n                    <RowDefinition Height=\"Auto\" />\n                    <RowDefinition Height=\"*\" />\n                    <RowDefinition Height=\"Auto\" />\n                </Grid.RowDefinitions>\n\n                <TextBlock Grid.Row=\"0\"\n                           Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                           Text=\"Comments\" />\n\n                <ListView Grid.Row=\"1\" ItemsSource=\"{x:Bind ViewModel.Recipe.Comments, Mode=OneWay}\">\n                    <ListView.ItemTemplate>\n                        <DataTemplate x:DataType=\"models:Comment\">\n                            <StackPanel Orientation=\"Vertical\" Padding=\"0 5 0 5\">\n                                <TextBlock FontWeight=\"Bold\" Text=\"{x:Bind Name}\" />\n                                <TextBlock Text=\"{x:Bind Text}\" />\n                            </StackPanel>\n                        </DataTemplate>\n                    </ListView.ItemTemplate>\n                </ListView>\n\n                <StackPanel x:Name=\"comments\"\n                            Grid.Row=\"2\"\n                            Margin=\"24,0,24,0\"\n                            Orientation=\"Vertical\">\n                    <!-- TODO input fields for comments -->\n                </StackPanel>\n            </Grid>\n        </Grid>\n    </Grid>\n</Page>\n

                                Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st!

                                "},{"location":"labor/5-mvvm/#3-feladat-kommentek-hozzaadasa","title":"3. Feladat - Kommentek hozz\u00e1ad\u00e1sa","text":"

                                Ha j\u00f3l \u00e1llunk id\u0151vel, k\u00e9sz\u00edts\u00fcnk funkci\u00f3t a kommentek hozz\u00e1ad\u00e1s\u00e1hoz a recept r\u00e9szletes oldal\u00e1n.

                                "},{"location":"labor/5-mvvm/#webszolgaltatas","title":"Webszolg\u00e1ltat\u00e1s","text":"

                                Az IRecipeService interf\u00e9szt \u00e9s implement\u00e1ci\u00f3t eg\u00e9sz\u00edts\u00fck ki egy SendCommentAsync met\u00f3dussal, mely egy kommentet k\u00fcld a szervernek a POST /Recipes/{recipeId}/Comments v\u00e9gpontra.

                                IRecipeService
                                public Task SendCommentAsync(int recipeId, Comment comment);\n
                                RecipeService
                                public async Task SendCommentAsync(int recipeId, Comment comment)\n{\n    using var client = new HttpClient();\n    await client.PostAsJsonAsync($\"{_baseUrl}/Recipes/{recipeId}/Comments\", comment);\n}\n
                                "},{"location":"labor/5-mvvm/#viewmodel","title":"ViewModel","text":"

                                A RecipeDetailPageViewModel-ben hozzunk l\u00e9tre egy NewCommentText nev\u0171 string t\u00edpus\u00fa tulajdons\u00e1got \u00e9s egy NewCommentName string tulajdons\u00e1got, melyekben t\u00e1rolni fogjuk a felhaszn\u00e1l\u00f3 \u00e1ltal megadott komment adatait. Haszn\u00e1ljuk az ObservableProperty attrib\u00fatumot!

                                [ObservableProperty]\nprivate string _newCommentName = string.Empty;\n\n[ObservableProperty]\nprivate string _newCommentText = string.Empty;\n

                                A RecipeDetailPageViewModel-ben hozzunk l\u00e9tre egy SendComment nev\u0171 f\u00fcggv\u00e9nyt, amelyen kereszt\u00fcl a felhaszn\u00e1l\u00f3 \u00e1ltal megadott kommentet tudjuk elk\u00fcldeni a szervernek. A f\u00fcggv\u00e9nyb\u0151l gener\u00e1ltassunk egy Commandot az MVVM Toolkit seg\u00edts\u00e9g\u00e9vel ([RelayCommand]).

                                Az implement\u00e1ci\u00f3 egyszer\u0171: elk\u00fcldj\u00fck a kommentet a szervernek, majd friss\u00edtj\u00fck a receptet.

                                [RelayCommand]\nprivate async Task SendComment()\n{\n    await _recipeService.SendCommentAsync(Recipe.Id, new Comment\n    {\n        Name = NewCommentName,\n        Text = NewCommentText\n    });\n\n    NewCommentName = string.Empty;\n    NewCommentText = string.Empty;\n\n    Recipe = await _recipeService.GetRecipeAsync(Recipe.Id);\n}\n

                                A n\u00e9zeten a k\u00f6vetkez\u0151 elemeket helyezz\u00fck el a kommentek hozz\u00e1ad\u00e1s\u00e1hoz:

                                <StackPanel x:Name=\"comments\"\n            Grid.Row=\"2\"\n            Margin=\"24,0,24,0\"\n            Orientation=\"Vertical\">\n    <TextBox Margin=\"0,0,0,16\"\n             Header=\"Name\"\n             Text=\"{x:Bind ViewModel.NewCommentName, Mode=TwoWay}\" />\n    <TextBox Margin=\"0,0,0,16\"\n             Header=\"Comment\"\n             Text=\"{x:Bind ViewModel.NewCommentText, Mode=TwoWay}\" />\n    <Button Margin=\"0,0,0,16\"\n            HorizontalAlignment=\"Right\"\n            Command=\"{x:Bind ViewModel.SendCommentCommand}\"\n            Content=\"Send\" />\n</StackPanel>\n

                                Vegy\u00fck \u00e9szre, hogy a TextBox-ok Text property-j\u00e9t k\u00e9tir\u00e1ny\u00fa k\u00f6t\u00e9ssel k\u00f6t\u00f6tt\u00fck a ViewModel-ben l\u00e9v\u0151 NewCommentName \u00e9s NewCommentText tulajdons\u00e1gokhoz, \u00e9s a gomb Command-j\u00e1t is a ViewModel-ben l\u00e9v\u0151 SendCommentCommand tulajdons\u00e1ghoz k\u00f6t\u00f6tt\u00fck.

                                "},{"location":"labor/5-mvvm/#kitekintes-commandok-vegrehajtasanak-feltetelei","title":"Kitekint\u00e9s: Commandok v\u00e9grehajt\u00e1s\u00e1nak felt\u00e9telei","text":"

                                A SendCommentCommand Command v\u00e9grehajt\u00e1s\u00e1nak felt\u00e9tele, hogy a NewCommentName \u00e9s a NewCommentText tulajdons\u00e1gok ne legyenek \u00fcresek. A Commandok lehet\u0151s\u00e9get adnak arra, hogy a v\u00e9grehajt\u00e1sukat felt\u00e9telekhez k\u00f6ss\u00fck, amelyeket a CanExecute met\u00f3dusban tudunk megadni. Eset\u00fcnkben egy bool-lal visszat\u00e9r\u0151 met\u00f3dus/property nevet kell megadnunk a Command gener\u00e1tor attrib\u00fatumnak.

                                RecipeDetailPageViewModel-ben:
                                private bool CanExecuteSendComment => !string.IsNullOrEmpty(NewCommentName) && !string.IsNullOrEmpty(NewCommentText);\n\n[RelayCommand(CanExecute = nameof(CanExecuteSendComment))]\nprivate async Task SendComment()\n

                                Pr\u00f3b\u00e1ljuk ki. Azt tapasztaljuk, hogy a gomb nem lesz enged\u00e9lyezve, viszont a TextBox-ok m\u00f3dos\u00edt\u00e1sa ut\u00e1n sem v\u00e1ltozik a gomb \u00e1llapota.

                                A CanExecute met\u00f3dus akkor h\u00edv\u00f3dik meg (akkor h\u00edvj\u00e1k a vez\u00e9rl\u0151k), amikor a Command els\u00fcti a CanExecuteChanged esem\u00e9nyt. Eset\u00fcnkben ezt az esem\u00e9nyt a NewCommentName \u00e9s a NewCommentText tulajdons\u00e1gok PropertyChanged esem\u00e9ny\u00e9nek kiv\u00e1lt\u00e1sakor kell kiv\u00e1ltani. Erre az MVVM Toolkit egy k\u00fcl\u00f6n attrib\u00fatumot biztos\u00edt ([NotifyCanExecuteChangedFor]), amelyet a NewCommentName \u00e9s a NewCommentText tulajdons\u00e1gokra kell r\u00e1rakni.

                                Teh\u00e1t, ha a NewCommentName vagy a NewCommentText tulajdons\u00e1g \u00e9rt\u00e9ke megv\u00e1ltozik, akkor a SendCommentCommand Command CanExecuteChanged esem\u00e9ny\u00e9t is kiv\u00e1ltjuk, ami miatt a CanExecute met\u00f3dus \u00fajra lefut, \u00e9s a gomb \u00e1llapota is friss\u00fcl.

                                [ObservableProperty]\n[NotifyCanExecuteChangedFor(nameof(SendCommentCommand))]\nprivate string _newCommentName = string.Empty;\n\n[ObservableProperty]\n[NotifyCanExecuteChangedFor(nameof(SendCommentCommand))]\nprivate string _newCommentText = string.Empty;\n

                                Pr\u00f3b\u00e1ljuk ki.

                                M\u00e1r csak egy dolog van h\u00e1tra: jelenleg a TextBox \u00e1llapota csak akkor v\u00e1ltozik, ha a felhaszn\u00e1l\u00f3 elhagyja a TextBox-ot. Ezt a viselked\u00e9st az adatk\u00f6t\u00e9s UpdateSourceTrigger tulajdons\u00e1g\u00e1n kereszt\u00fcl tudjuk m\u00f3dos\u00edtani.

                                Text=\"{x:Bind ViewModel.NewCommentName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n\nText=\"{x:Bind ViewModel.NewCommentText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n

                                Pr\u00f3b\u00e1ljuk ki.

                                "},{"location":"labor/5-mvvm/index_ger/","title":"5. MVVM","text":""},{"location":"labor/5-mvvm/index_ger/#das-ziel-der-ubung","title":"Das Ziel der \u00dcbung","text":"

                                In dieser \u00dcbung werden wir eine Rezept-Browser-Anwendung unter Verwendung des MVVM-Entwurfsmusters erstellen.

                                "},{"location":"labor/5-mvvm/index_ger/#voraussetzungen","title":"Voraussetzungen","text":"

                                Die f\u00fcr die Durchf\u00fchrung des Labors ben\u00f6tigten Werkzeuge:

                                • Betriebssystem Windows 10 oder Windows 11 (Linux und macOS nicht geeignet)
                                • Visual Studio 2022
                                  • Windows Desktop Development Workload
                                "},{"location":"labor/5-mvvm/index_ger/#ursprungliches-projekt","title":"Urspr\u00fcngliches Projekt","text":"

                                Klonen Sie das urspr\u00fcngliche Projekt mit dem folgenden Befehl:

                                git clone https://github.com/bmeviauab00/lab-mvvm-kiindulo\n
                                Laden Sie die fertige L\u00f6sung herunter

                                Es ist wichtig, dass Sie sich w\u00e4hrend des Praktikums an die Anleitung halten. Es ist verboten (und sinnlos), die fertige L\u00f6sung herunterzuladen. Allerdings kann es bei der anschlie\u00dfenden Selbstein\u00fcbung n\u00fctzlich sein, die fertige L\u00f6sung zu \u00fcberpr\u00fcfen, daher stellen wir sie zur Verf\u00fcgung.

                                Die L\u00f6sung ist [auf GitHub] (https://github.com/bmeviauab00/lab-mvvm-kiindulo) im solve-Zweig verf\u00fcgbar. Der einfachste Weg, es herunterzuladen, ist, den git clone-Zweig von der Kommandozeile aus zu klonen:

                                git clone https://github.com/bmeviauab00/lab-mvvm-kiindulo -b solved

                                "},{"location":"labor/5-mvvm/index_ger/#uber-das-mvvm-muster","title":"\u00dcber das MVVM-Muster","text":"

                                Das MVVM (Model-View-ViewModel) ist ein Architekturentwurfsmuster, das bei der Entwicklung von XAML-Anwendungen eingesetzt werden kann, aber auch h\u00e4ufig in anderen clientseitigen Technologien verwendet wird. Das MVVM-Muster wurde entwickelt, um die Benutzeroberfl\u00e4che und die zugrunde liegende Logik zu entkoppeln und so eine lose gekoppelte Anwendung zu schaffen, die die Testbarkeit, Wartbarkeit und Wiederverwendbarkeit erh\u00f6ht.

                                Das MVVM-Muster besteht aus drei (+1) Hauptteilen:

                                • Modell: Enth\u00e4lt das Gesch\u00e4ftsmodell/Dom\u00e4nenmodell der Anwendung, das ViewModels zum Speichern von Daten verwenden k\u00f6nnen.
                                • View (Ansicht): Sie enth\u00e4lt eine Beschreibung der Benutzeroberfl\u00e4che und der rein auf die Ansichten bezogenen Logik (z.B.: Behandlung von Animationen).
                                • ViewModel (Ansichtsmodell): Eine Abstraktion der Ansicht, die den Zustand der Ansicht und die Operationen enth\u00e4lt, die mit der Ansicht durchgef\u00fchrt werden k\u00f6nnen, unabh\u00e4ngig von der Ansicht. Die lose Kopplung zwischen dem ViewModel und dem View wird durch die Datenverbindung gew\u00e4hrleistet.
                                • Services (Dienstleistungen): Klassen, die die Gesch\u00e4ftslogik der Anwendung enthalten und von ViewModels verwendet werden. W\u00e4re die gesamte Gesch\u00e4ftslogik in ViewModels enthalten, w\u00e4ren diese zu komplex und undurchsichtig. Dies ist nicht Teil des MVVM-Musters, aber wir erw\u00e4hnen es hier, weil wir die Anwendungsarchitektur auf diese Weise nutzen werden.

                                Neu:

                                • Modell: Erfasst dom\u00e4nenspezifische Daten, die ViewModels zum Speichern von Daten verwenden k\u00f6nnen. Z.B. die Klasse Recipe/Product/Order, die die Daten eines Rezepts/Produkts/Bestellung zusammenfasst.
                                • View (Ansicht): Sie enth\u00e4lt eine Beschreibung der Benutzeroberfl\u00e4che (und der rein auf die Ansichten bezogenen Logik, z. B. die Behandlung von Animationen). Typischerweise eine von Window, Page, UserControl abgeleitete Klasse, mit einer deklarativen Beschreibung in XAML, der Code-Behind ist oft leer (weil die Logik im ViewModel ist).
                                • ViewModel (Ansichtsmodell): Sie enth\u00e4lt die Logik f\u00fcr die Ansicht: Sie enth\u00e4lt den Zustand der Ansicht und die Operationen, die mit der Ansicht durchgef\u00fchrt werden k\u00f6nnen. Sie ist unabh\u00e4ngig von der Ansicht, wird eine lose Kopplung zwischen dem ViewModel und der Ansicht durch Datenverbindung erreicht (die Steuerelemente der Ansicht binden an die Eigenschaften des ViewModels). Einheitlich testbar (unit test)!
                                • Services (Dienstleistungen): Klassen, die die Gesch\u00e4fts-/Anwendungslogik enthalten und von ViewModels verwendet werden. W\u00e4re die gesamte Gesch\u00e4ftslogik in ViewModels enthalten, w\u00e4ren diese zu komplex und undurchsichtig. Dies ist nicht Teil des MVVM-Musters, aber wir erw\u00e4hnen es hier, weil wir auf diese Weise die Architektur der Anwendung aufbauen werden.

                                Wozu erstellen wir ViewModel-Klassen?

                                • Wir erstellen immer eine ViewModel-Klasse f\u00fcr jede Ansicht (z.B. Window, Page, Dialog, UserControl) und erzeugen daraus je ein Objekt f\u00fcr jede Ansicht. Z.B. MainPage f\u00fcr MainPageViewModel, DancerDialog f\u00fcr DancerDialogViewModel. Wir wenden dies in der Praxis an.
                                • F\u00fcr jede Modellklasse (z.B. Recipe, Product, Dancer usw.) k\u00f6nnen Sie optional Wrapper-ViewModel-Klassen erstellen (z.B. RecipeViewModel, ProductViewModel, DancerViewModel), aber wir werden sie in dieser \u00dcbung nicht erstellen. Das liegt daran, dass wir nicht dem Strict MVVM-Muster folgen, sondern dem Relaxed MVVM-Muster (siehe Vorlesung).
                                "},{"location":"labor/5-mvvm/index_ger/#aufgabe-0-projektstruktur","title":"Aufgabe 0. - Projektstruktur","text":"

                                Der Anwendungsrahmen ist bereits vorbereitet. Schauen wir uns die Projektstruktur an.

                                MvvmLab ist ein Projekt f\u00fcr eine ausf\u00fchrbare Anwendung, die das WinUI-Framework in seiner Anzeigeschicht mit der bereits erlernten XAML-Sprache verwendet. Das Projekt MvvmLab.Core (Klassenbibliothek) enth\u00e4lt die vollst\u00e4ndig ansichtsunabh\u00e4ngige Gesch\u00e4ftslogik.

                                Was ist f\u00fcr uns in der Anfangsphase des Projekts wichtig?

                                • App.xaml.cs: Ein Anwendungseintrittspunkt, der die in modernen .NET-Anwendungen verwendeten Muster Host Builder und Dependency Injection verwendet. Dies ist nicht das Thema dieses Semesters, aber die Injektion von Abh\u00e4ngigkeit wird im Labor behandelt werden.
                                • Views-Ordner: Enth\u00e4lt Ansichten der Anwendung, derzeit MainPage
                                • ViewModels-Ordner: Enth\u00e4lt die ViewModels der Anwendung, derzeit MainPageViewModel
                                • INagivationService ( im Ordner Services ): Dienst f\u00fcr die Navigation zwischen Seiten

                                MVVM und Boilerplate-Bibliotheken

                                MVVM-Muster wird selten allein auf der Grundlage des .NET-Frameworks implementiert. Es lohnt sich, einige MVVM-Bibliotheken zu verwenden, die Ihren Code kompakter und \u00fcbersichtlicher machen und weniger Boilerplate-Code enthalten. Die am h\u00e4ufigsten verwendeten Bibliotheken sind:

                                • MVVM Toolkit: MVVM-Bibliothek, die von Microsoft gepflegt wird
                                • [Prism] (https://prismlibrary.com/): Fr\u00fcher wurde es von Microsoft gewartet und war sehr weit verbreitet, aber jetzt wird es von externen Entwicklern gewartet und ist mit der Zeit kostenpflichtig geworden.
                                • ReactiveUI: Es verwendet die Reactive Extensions (Rx)-Bibliotheken, um den Zustand des ViewModels zu verwalten und Daten zwischen der Ansicht und dem ViewModel zu binden. Diese Bibliothek bietet die meisten Dienste, ist aber auch am schwierigsten zu erlernen.
                                • Uno.Extensions: Es basiert auf dem MVVM-Toolkit, enth\u00e4lt aber auch mehrere Funktionen, die M\u00e4ngel im WinUI-Framework ausf\u00fcllen.

                                W\u00e4hrend des Praktikums werden wir das MVVM-Toolkit von Microsoft verwenden.

                                Das urspr\u00fcngliche Projekt wurde mit dem Visual Studio Add-on Windows Template Studio erstellt.

                                "},{"location":"labor/5-mvvm/index_ger/#aufgabe-1-rezepte-hauptseite","title":"Aufgabe 1. - Rezepte Hauptseite","text":"

                                Die L\u00f6sung werden wir \"von unten\" aufbauen, von den Daten ausgehend werden wir schrittweise zur Ansicht. Die Entwicklung von oben nach unten ist zwar in der Praxis oft n\u00fctzlicher, aber aufgrund der zeitlichen Beschr\u00e4nkungen im Labor ist die Entwicklung von unten nach oben schneller und einfacher, weil man die Daten so nicht mocken muss. Die folgende Abbildung gibt einen \u00dcberblick \u00fcber die wichtigsten Klassen, die mit der Hauptseite verbunden sind.

                                MMVM-basierte Implementierung der Homepage

                                Schl\u00fcsselelemente:

                                • MainPage: Diese Ansicht, ein Nachkomme der Seite, ist eine XAML-basierte Beschreibung der Benutzeroberfl\u00e4che.
                                • MainPageViewModel: das ViewModel f\u00fcr die Hauptseite (MainPage). Es enth\u00e4lt Rezeptgruppen in einer (generierten) RecipeGroups Eigenschaft, und Rezepte in den Rezeptgruppen. Diese Ansicht zeigt die Kopfzeile der Rezeptgruppen sowie die Kopfzeile und die Bilder der Rezepte in den Gruppen mit Datenverbindung.
                                • RecipeGroup und Recipe: Modellklassen f\u00fcr Rezeptgruppen und Rezepte.
                                • RecipeService: Anwendungslogik/Datenzugriff zur Verwaltung von Rezepten (kommuniziert mit einem entfernten Dienst) unter Verwendung von ViewModel.
                                "},{"location":"labor/5-mvvm/index_ger/#11-datenzugangsdienst","title":"1.1 Datenzugangsdienst","text":"

                                Beginnen wir mit der Datenzugriffsschicht, die nun als Modellschicht im MVVM-Muster betrachtet werden kann.

                                Unsere Anwendung ruft Daten von einem Webserver ab (\u00fcber die sogenannte REST-API, HTTP). Client-Server-Architekturen wie diese sind eine sehr verbreitete L\u00f6sung in der modernen Anwendungsentwicklung. Dies wird im n\u00e4chsten Semester in Mobile und Web Software, und Data Driven Applications ausf\u00fchrlicher behandelt. F\u00fcr den Moment gen\u00fcgt es zu wissen, dass unsere Client-Anwendung HTTP-Anfragen an den Server sendet, der mit der R\u00fcckgabe von Daten im JSON-Format antwortet.

                                Client-Server-Architektur

                                Der Ferndienst ist verf\u00fcgbar unter: https://bmecookbook2.azurewebsites.net/api. Der Dienst umfasst eine OpenApi-basierte Dokumentation \u00fcber die https://bmecookbook2.azurewebsites.net/swagger am. Schauen wir uns dies an oder probieren wir die Endpunkte auch \u00fcber die Oberfl\u00e4che von Swagger aus (indem man die URL mit der Endung \"swagger\" in die Adresszeile eines Browsers eingibt). F\u00fcr die erste Aufgabe werden wir den Endpunkt /api/Recipes/Groups verwenden, der die Gruppierung von Rezepten zur\u00fcckgibt.

                                F\u00fcgen wir eine neue Klasse namens RecipeGroup in den Ordner Models des Projekts MvvmLab.Core ein.

                                Rufen wir mit Swagger den Endpunkt \"api/Recipes/Groups\" auf (genauer gesagt, senden wir eine HTTP-GET-Anfrage)

                                • \u00d6ffnen wir in der Swagger-Oberfl\u00e4che die Beschreibung des Endpunktes \"Get api/Recipes/Groups\".
                                • Klicken wir auf die Taste Execute.
                                • Die vom Dienst gesendete JSON-Antwort wird unter \"Response body\" angezeigt: Hier sehen wir, dass die Antwort Rezeptgruppen enth\u00e4lt. Jede Gruppe hat einen \"Titel\" (z.B. Chinese, Mexican, Italian), und unter jeder Gruppe finden wir zwischen [] (JSON-Array) die Daten der Rezepte in der Gruppe.
                                • Kopieren wir die JSON-Daten von RecipeGroup in die Zwischenablage. Wir k\u00f6nnen auch die Ausgabe unter \"Example Value\" verwenden, wenn wir sie in die Zwischenablage kopieren (kopieren wir jedoch nicht die \u00f6ffnenden [ und schlie\u00dfenden ] Schriftzeichen). Wenn wir aus irgendeinem Grund nicht weiterkommen k\u00f6nnen, k\u00f6nnen wir den Inhalt auch aus das folgende Dropdown-Men\u00fc in die Zwischenablage kopieren:

                                  In die Zwischenablage zu kopieren
                                  {\n    \"Title\": \"string\",\n    \"Recipes\": [\n        {\n            \"Id\": 0,\n            \"Title\": \"string\",\n            \"BackgroundImage\": \"string\"\n        }\n    ]\n}\n

                                In Visual Studio w\u00e4hlen wir im Men\u00fc Edit / Paste Special / Paste JSON as Classes aus, um den Inhalt der Zwischenablage einf\u00fcgen. VS generiert dann Klassen, die der Struktur des von uns eingef\u00fcgten JSON entsprechen.

                                Die entstehenden Klassen k\u00f6nnen umbenannt werden, um den C#-Codierungskonventionen zu entsprechen. Benennen wir die Klasse Rootobject in RecipeGroup und die Klasse Recipe in RecipeHeader um.

                                public class RecipeGroup\n{\n    public string Title { get; set; }\n    public RecipeHeader[] Recipes { get; set; }\n}\n\npublic class RecipeHeader\n{\n    public int Id { get; set; }\n    public string Title { get; set; }\n    public string BackgroundImage { get; set; }\n}\n

                                Verwenden von List<T>

                                In unserem Fall war es nicht notwendig (weil wir die Rezeptgruppen nicht erweitern), aber wenn es bequemer f\u00fcr uns ist, k\u00f6nnen wir die Bl\u00f6cke in den generierten Code im List<T> umwandeln.

                                Erstellen wir eine Schnittstelle IRecipeService zum Namespace MvvmLab.Core.Services, \u00fcber die auf den Remote-Dienst zugegriffen werden soll. In der Schnittstelle erstellen wir eine Methode GetRecipeGroupsAsync, die die Rezeptgruppen abfragt und zur\u00fcckgibt.

                                public interface IRecipeService\n{\n    public Task<RecipeGroup[]> GetRecipeGroupsAsync();\n}\n

                                Task-R\u00fcckgabewert

                                In der Schnittstelle ist der eigentliche R\u00fcckgabewert (RecipeGroup[]) in ein Objekt Task<T> verpackt, da es vorzuziehen ist, Netzwerkoperationen asynchron zu implementieren. In .NET ist die modernste und einfachste Art, Asynchronit\u00e4t zu implementieren, die Verwendung von Tasks. Und die Asynchronit\u00e4t sorgt daf\u00fcr, dass die Benutzeroberfl\u00e4che nicht einfriert, wenn die Netzwerkanforderung lange dauert (und das alles, ohne separate Threads zu starten).

                                Die Implementierung der Schnittstelle wird im Namespace MvvmLab.Core.Services unter RecipeService erstellt. Unser Dienst wird die integrierte .NET-Klasse HttpClient f\u00fcr REST-API-Aufrufe verwenden. GetFromJsonAsync stellt eine asynchrone HTTP GET-Anfrage an die angegebene Adresse und deserialisiert die Antwort von JSON in den angegebenen Typ.

                                public class RecipeService : IRecipeService\n{\n    private readonly string _baseUrl = \"https://bmecookbook2.azurewebsites.net/api\";\n\n    public async Task<RecipeGroup[]> GetRecipeGroupsAsync()\n    {\n        using var client = new HttpClient();\n        return await client.GetFromJsonAsync<RecipeGroup[]>($\"{_baseUrl}/Recipes/Groups\");\n    }\n}\n

                                Die Operation GetFromJsonAsync ist asynchron, sie gibt also Task zur\u00fcck, wir k\u00f6nnen dies nicht blockierend erwarten und mit dem Schl\u00fcsselwort await auf das Ergebnis zugreifen.

                                async-await

                                Die Schl\u00fcsselw\u00f6rter async und await werden in den meisten modernen Sprachen verwendet, um asynchrone Funktionsaufrufe auf Sprachebene zu behandeln. Wir werden am Ende des Semesters mehr dar\u00fcber sprechen, wie es funktioniert, aber bis dahin m\u00fcssen Sie Folgendes wissen, um es zu nutzen:

                                • Mit dem Schl\u00fcsselwort await k\u00f6nnen wir auf eine asynchrone Ausf\u00fchrung warten, ohne den Aufrufer zu blockieren.
                                • Das Schl\u00fcsselwort await kann nur in Funktionen mit dem Schl\u00fcsselwort async verwendet werden.
                                • async-Funktionen k\u00f6nnen nur den R\u00fcckgabewert Task oder Task<T> oder void haben. (Oder \"Task-\u00e4hnlich\", aber das nehmen wir hier nicht.)
                                  • Wenn man eine async-Funktion von au\u00dfen abwarten will, kann man das nicht mit void tun, sondern man muss einen R\u00fcckgabewert von Task oder Task<T> haben.
                                  • In async-Funktionen wird die Syntax der return-Anweisung ge\u00e4ndert: es muss nicht das Task-Objekt zur\u00fcckgegeben werden, sondern die darin enthaltenen Daten (void f\u00fcr Task, Task<T> f\u00fcr T).
                                "},{"location":"labor/5-mvvm/index_ger/#12-startseite-viewmodel","title":"1.2 Startseite ViewModel","text":"

                                Im n\u00e4chsten Schritt erstellen wir das ViewModel der Hauptseite, das den soeben erstellten Dienst verwendet, um die Rezeptgruppen abzurufen und sie als Status f\u00fcr die Ansicht zu speichern.

                                "},{"location":"labor/5-mvvm/index_ger/#dependency-injection","title":"Dependency Injection\u00b6","text":"

                                \u00d6ffnen wir die Klasse MainPageViewModel aus dem Ordner MvvmLab.ViewModels. Unser ViewModel ben\u00f6tigt eine Klasse, die die Schnittstelle IRecipeService implementiert, \u00fcber die es die Rezeptgruppen abfragen kann. Im MainPageViewModel Konstruktor erhalten wir die erforderliche Abh\u00e4ngigkeit \u00fcber Dependency Injection. In unserem Fall bedeutet dies, dass wir einen Parameter vom Typ IRecipeService erwarten, der vom ViewModel empfangen wird, wenn es instanziiert wird, und der Parameter wird in einer privaten Variablen gespeichert.

                                private readonly IRecipeService _recipeService;\n\npublic MainPageViewModel(IRecipeService recipeService)\n{\n    _recipeService = recipeService;\n}\n
                                Dependency Injection - DI

                                Standardm\u00e4\u00dfig sind Klassen eng mit ihren Abh\u00e4ngigkeiten gekoppelt (Referenz, Instanziierung).

                                Starke Kopplung ohne DI

                                Diese enge Kopplung erschwert die Pr\u00fcfung, Wartung und Wiederverwendung. Dies wird durch den Einsatz von Dependency Injection (und Strategy) unterst\u00fctzt. In diesem Kurs lernen wir das Dependency Injection (DI) Entwurfmuster kennen, das immer in Verbindung mit dem Strategy-Muster verwendet wird. Die Idee ist, dass eine Klasse ihre Abh\u00e4ngigkeiten (die Klassen, von denen sie abh\u00e4ngt und die sie verwendet) nicht selbst erzeugt, sondern sie von au\u00dfen erh\u00e4lt, z.B. in einem Konstruktorparameter. Das Strategy-Muster impliziert, dass sie nur als \"Schnittstelle\" von ihnen abh\u00e4ngt.

                                Die meisten Plattformen bieten heute auch einen zus\u00e4tzlichen Dienst, einen so genannten DI-Container (auch IoC-Container genannt), zur bequemen Verwaltung von Abh\u00e4ngigkeiten. Der Lebenszyklus von Abh\u00e4ngigkeiten wird dann von einer speziellen Komponente, dem DI-Container, verwaltet. Der DI-Container (dargestellt als Builder) ist f\u00fcr die Instanziierung von Klassen und die rekursive Injektion von Abh\u00e4ngigkeiten zust\u00e4ndig.

                                DI-Klassendiagramm

                                Um die entsprechenden Implementierungen zu injektieren, w\u00e4hrend des Durchlaufens der Abh\u00e4ngigkeitsgraph w\u00e4hrend der Instanziierung, m\u00fcssen die Abh\u00e4ngigkeitszuordnungen im DI-Container registriert werden. In unserer Anwendung tun wir dies in der Datei App.xaml.cs in der Methode ConfigureServices. F\u00fcgen wir die folgende Zeile hinzu, z.B. unter dem Abschnitt // Core Services:

                                services.AddTransient<IRecipeService, RecipeService>();\n

                                Dies sagt uns, dass das DI-Framework eine RecipeService-Implementierung injektiert, wenn eine Klasse eine IRecipeService-Abh\u00e4ngigkeit erwartet (z.B. den Konstruktor von MainPageViewModel). (Da wir sie hier als Transient Lifetime registriert haben, wird jede IRecipeService-Abh\u00e4ngigkeitsanforderung durch eine neue RecipeService-Instanz erf\u00fcllt).

                                Damit Dependency Injection in unserer Anwendung funktioniert, muss die Klasse MainPageViewModel auch im Container registriert sein, der ebenfalls unter ConfigureServices zu finden ist.

                                \u00dcber DI-Container im Detail

                                Die Verwendung und Funktionsweise von DI-Containern wird sp\u00e4ter im Kurs Datengesteuerte Systeme ausf\u00fchrlich behandelt.

                                "},{"location":"labor/5-mvvm/index_ger/#viewmodel-status","title":"ViewModel-Status","text":"

                                Im n\u00e4chsten Schritt werden wir das Hochladen des ViewModel-Status implementieren.

                                Unser Ziel ist, dass

                                • MainPageViewModel hat eine Eigenschaft namens RecipeGroups, die Rezeptgruppen enth\u00e4lt (wir wollen diese an die Oberfl\u00e4che binden),
                                • die \u00c4nderungen von RecipeGroups von der Schnittstelle verfolgt werden, was die Implementierung von INotifyPropertyChanged und das korrekte Ausl\u00f6sen von PropertyChanged erfordert (wie wir bereits in der vorherigen \u00dcbung/Hausaufgabe gesehen haben).

                                Dies w\u00fcrde relativ \"viel\" Arbeit erfordern, aber das MVVM-Toolkit vereinfacht unser Leben, denn wir m\u00fcssen nur das Folgendes tun:

                                • Erstellen wir in MainPageViewModel eine RecipeGroup[] Member-Variable (keine Eigenschaft) mit dem Namen _recipeGroups.
                                • Die Variable wird mit dem Attribut ObservableProperty versehen.
                                [ObservableProperty]\nprivate RecipeGroup[] _recipeGroups = Array.Empty<RecipeGroup>();\n

                                Hier sind wir nun. Aber was passiert dann?

                                • Auf dieser Grundlage erzeugt MVVM Toolkit automatisch eine Eigenschaft namens RecipeGroups in der generierten (partiellen) H\u00e4lfte der Klasse.
                                • Diese generierte Eigenschaft nutzt die Vorteile der Schnittstelle INotifyPropertyChanged. Wenn sich der Wert der Eigenschaft RecipeGroups \u00e4ndert, wird das Ereignis PropertyChanged ausgel\u00f6st, um die Ansicht entlang der Datenverbindungen zu benachrichtigen.
                                • Unser MainPageViewModel implementiert bereits die Schnittstelle INotifyPropertyChanged, da es von der Klasse ObservableObject des MVVM-Toolkits stammt.

                                In MainPageViewModel implementieren wir die vorbereitete Schnittstelle INavigationAware, die es uns erm\u00f6glicht, das Navigations-Lebenszyklus-Ereignis zwischen Ansichten zu handhaben und sogar Daten zwischen ViewModels zu \u00fcbergeben. In der Methode OnNavigatedTo werden die Rezeptgruppen \u00fcber IRecipeService abgefragt und in der Variablen RecipeGroups gespeichert.

                                public partial class MainPageViewModel : ObservableObject, INavigationAware\n{\n    // ...\n\n    public async void OnNavigatedTo(object parameter)\n    {\n        RecipeGroups = await _recipeService.GetRecipeGroupsAsync();\n    }\n\n    public void OnNavigatedFrom()\n    {\n    }\n}\n
                                "},{"location":"labor/5-mvvm/index_ger/#13-ansicht-der-hauptseite","title":"1.3 Ansicht der Hauptseite","text":"

                                Erstellen wir die Ansicht auf MainPage, in der die Rezeptgruppen angezeigt werden.

                                Damit GridView die Gruppierung behandeln kann, brauchen wir eine Liste, die die Gruppierung vornimmt. Wir k\u00f6nnen dies mit der Klasse CollectionViewSource tun, die in gewisser Weise UI-spezifische Wrapping-Aufgaben f\u00fcr Sammlungen \u00fcbernimmt. CollectionViewSource muss die zu gruppierenden Elemente und die Eigenschaft, auf der die Gruppen basieren, angegeben werden. Wir m\u00fcssen auch die Eigenschaft angeben, auf der die Elemente innerhalb der Gruppen angezeigt werden sollen.

                                Erstellen wir die Instanz CollectionViewSource in den Ressourcen der Seite (f\u00fcgen wir den Code unten in MainPage.xaml ein, oberhalb des Grids, auf der gleichen Ebene wo es liegt).

                                <Page.Resources>\n    <CollectionViewSource x:Name=\"RecipeGroupsCollectionSource\"\n                            IsSourceGrouped=\"True\"\n                            ItemsPath=\"Recipes\"\n                            Source=\"{x:Bind ViewModel.RecipeGroups, Mode=OneWay}\" />\n</Page.Resources>\n

                                Note

                                Beachten Sie, dass wir in der Datenverbindung an die Eigenschaft ViewModel binden, die sich in MainPage.xaml.cs befindet, und einfach die Eigenschaft DataContext an unseren ViewModel-Typ \u00fcbergeben.

                                public MainPageViewModel ViewModel => DataContext as MainPageViewModel;\n

                                Die Speicherung des ViewModels in der Eigenschaft DataContext der Steuerelemente (Seiten) ist typisch f\u00fcr das MVVM-Muster. In unserem Fall \u00fcbernimmt die Klasse \"NavigationService\" des generierten Projekts diese Aufgabe f\u00fcr uns.

                                Ressourcen

                                In der XAML-Umgebung hat jedes Steuerelement (im obigen Beispiel die Seite) und die Klasse Application standardm\u00e4\u00dfig eine Eigenschaft Resources, die ein Schl\u00fcssel-Wert-Speicher ist (Dictionary<string, object>). Sie k\u00f6nnen wiederverwendbare Objekte einf\u00fcgen, sogar auf der Anwendungsebene. Wenn Sie bei der Instanziierung von Ressourcen das Attribut x:Key angeben, k\u00f6nnen Sie Ressourcen nach Schl\u00fcsseln abfragen, z.B. mit der Markup-Erweiterung {StaticResource Key}.

                                Aber hier haben wir explizit x:Name anstelle von x:Key angegeben, weil wir uns in x:Bind auf den Namen beziehen wollen (zur Erinnerung: das Attribut x:Name wird verwendet, um eine Mitgliedsvariable in unserer Klasse mit diesem Namen zu erzeugen, so dass wir sie aus dem code behind Datei oder w\u00e4hrend der Verwendung von x:Bind Datenverbindung, mit diesem Namen erreichen k\u00f6nnen).

                                F\u00fcr die Auflistung der Rezepte verwenden wir nun ein spezielles, von GridView abgeleitetes Steuerelement, n\u00e4mlich AdaptiveGridViewaus dem CommunityToolkit-Paket, das die Anzahl und Gr\u00f6\u00dfe der angezeigten Elemente in Abh\u00e4ngigkeit von der Gr\u00f6\u00dfe der Ansicht \u00e4ndert und die Benutzung von Commands f\u00fcr Elementklicks unterst\u00fctzt. Um auf externe Steuerelemente zu verweisen, f\u00fcgen wir zu der Seite den folgenden Namespace hinzu:

                                xmlns:controls=\"using:CommunityToolkit.WinUI.UI.Controls\"\n

                                Erstellen wir die GridView mit der Eigenschaft ItemsSource, die in der obigen Ressource an RecipeGroupsCollectionSource.View gebunden ist.

                                Innerhalb von GridView k\u00f6nnen wir wie gewohnt \u00fcber die Eigenschaft ItemTemplate festlegen, wie jedes Element angezeigt werden soll. In unserem Fall haben wir ein Bild und einen Text, der auf dem Titel des Rezepts basiert, in ein \"karten\u00e4hnliches\" Layout gesetzt.

                                Und \u00fcber die Eigenschaft GroupStyle k\u00f6nnen wir festlegen, wie die Gruppen angezeigt werden sollen. In diesem Fall wollen wir die Kopfzeile anpassen.

                                Ersetzen wir in MainPage.xaml das Gitter <Grid x:Name=\"ContentArea\"> ... durch das folgende:

                                <Grid x:Name=\"ContentArea\" Padding=\"10\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"Auto\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n\n    <TextBlock Text=\"Recipes\"\n               Grid.Row=\"0\"\n               Style=\"{StaticResource TitleLargeTextBlockStyle}\" />\n\n    <controls:AdaptiveGridView Grid.Row=\"1\"\n                               DesiredWidth=\"180\"\n                               IsItemClickEnabled=\"True\"\n                               ItemHeight=\"160\"\n                               ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                               SelectionMode=\"None\"\n                               StretchContentForSingleRow=\"False\">\n        <GridView.ItemTemplate>\n            <DataTemplate x:DataType=\"models:RecipeHeader\">\n                <Grid MaxWidth=\"300\">\n                    <Image Source=\"{x:Bind BackgroundImage}\" />\n                    <Border Height=\"40\"\n                            Padding=\"10,0,0,0\"\n                            VerticalAlignment=\"Bottom\"\n                            Background=\"#88000000\">\n                        <TextBlock VerticalAlignment=\"Center\"\n                                   Foreground=\"White\"\n                                   Text=\"{x:Bind Title}\" />\n                    </Border>\n                </Grid>\n            </DataTemplate>\n        </GridView.ItemTemplate>\n        <GridView.GroupStyle>\n            <GroupStyle>\n                <GroupStyle.HeaderTemplate>\n                    <DataTemplate x:DataType=\"models:RecipeGroup\">\n                        <TextBlock Margin=\"0\"\n                                   Style=\"{ThemeResource TitleTextBlockStyle}\"\n                                   Text=\"{x:Bind Title}\" />\n                    </DataTemplate>\n                </GroupStyle.HeaderTemplate>\n            </GroupStyle>\n        </GridView.GroupStyle>\n    </controls:AdaptiveGridView>\n</Grid>\n

                                Nehmen wir den folgenden Namespace (hier befinden sich unsere Modellklassen) auf:

                                `xmlns:models=\"using:MvvmLab.Core.Models\"`\n

                                Probieren wir die App aus! Achten Sie darauf, dass die Rezeptgruppen auf der Hauptseite erscheinen.

                                "},{"location":"labor/5-mvvm/index_ger/#aufgabe-2-rezept-detailseite","title":"Aufgabe 2. - Rezept-Detailseite","text":"

                                Die Erstellung der detaillierten Rezeptseite erfolgt in folgenden Schritten:

                                1. F\u00fcgen wir der Schnittstelle IRecipeService eine Methode GetRecipeAsync hinzu und erstellen wir die erforderlichen Klassen
                                2. Erstellen wir ein Ansichtsmodell RecipeDetailPageViewModel, in dem wir die Rezeptdaten in RecipeDetailPageViewModel \u00fcber IRecipeService abfragen (die VM erh\u00e4lt die ID bei der Navigation)
                                3. Erstellen wir die Ansicht RecipeDetailPage, die auf den Daten des ViewModel aufbaut
                                4. Registrieren wir das ViewModel und View f\u00fcr Dependency Injection Konfiguration und Navigation
                                5. Navigieren wir von MainPageViewModel zu RecipeDetailPage durch INavigationService, falls es auf das Rezept angeklickt wird und die ID des ausgew\u00e4hlten Rezepts wird an die Detailseite \u00fcbergegeben
                                "},{"location":"labor/5-mvvm/index_ger/#21-abfrage-eines-rezepts","title":"2.1 Abfrage eines Rezepts","text":"

                                Erstellen wir die Klasse Recipe im Namensraum MvvmLab.Core.Model und generieren wir ihren Inhalt aus den JSON-Beispieldaten, die vom Endpunkt /api/recipes/{id} zur\u00fcckgegeben werden, unter Verwendung der oben beschriebenen Methode (Paste special).

                                public class Recipe\n{\n    public int Id { get; set; }\n    public string BackgroundImage { get; set; }\n    public string Title { get; set; }\n    public string[] ExtraImages { get; set; }\n    public string[] Ingredients { get; set; }\n    public string Directions { get; set; }\n    public Comment[] Comments { get; set; }\n}\n\npublic class Comment\n{\n    public string Name { get; set; }\n    public string Text { get; set; }\n}\n

                                Warning

                                W\u00e4hrend des \"Paste Special\" ist es wichtig, ein Rezept in die Zwischenablage zu legen, das einen Kommentar enth\u00e4lt (andernfalls wird die Klasse Comment nicht erzeugt, und die Klasse Recipe erzeugt den Typ object[] des Typs Comments). Es lohnt sich, das Beispiel aus dem Feld \"Example value\" der Swagger-Beschreibung in die Zwischenablage zu kopieren!

                                Die Schnittstelle IRecipeService und ihre Implementierung werden mit einer Methode GetRecipeAsync erweitert, die ein Rezept auf der Grundlage seiner Identifizierungsnummer zur\u00fcckgibt.

                                IRecipeService
                                public Task<Recipe> GetRecipeAsync(int id);\n
                                RecipeService
                                public async Task<Recipe> GetRecipeAsync(int id)\n{\n    using var client = new HttpClient();\n    return await client.GetFromJsonAsync<Recipe>($\"{_baseUrl}/Recipes/{id}\");\n}\n
                                "},{"location":"labor/5-mvvm/index_ger/#22-rezept-detailliertes-viewmodel","title":"2.2 Rezept detailliertes ViewModel","text":"

                                Die Erstellung eines ViewModels ist im Vergleich zur Hauptseite eine Finger\u00fcbung (wir k\u00f6nnen grunds\u00e4tzlich auf seinem Muster arbeiten). Erstellen wir die Klasse RecipeDetailPageViewModel im Ordner MvvmLab.ViewModels.

                                Das ViewModel ben\u00f6tigt eine Klasse, die die Schnittstelle IRecipeService implementiert, \u00fcber die es das Rezept abfragen kann. Im RecipeDetailPageViewModel Konstruktor wird DI verwendet, um die notwendige Abh\u00e4ngigkeit zu erhalten.

                                private readonly IRecipeService _recipeService;\n\npublic RecipeDetailPageViewModel(IRecipeService recipeService)\n{\n    _recipeService = recipeService;\n}\n

                                Erstellen wir in RecipeDetailPageViewModel eine Variable des Typs Recipe mit dem Namen _recipe, in der das Rezept gespeichert werden soll. Die Variable wird mit dem Attribut ObservableProperty versehen, wodurch MVVM Toolkit automatisch die Eigenschaft Recipe in der anderen generierten partiellen H\u00e4lfte der Klasse erzeugen kann. Dies setzt voraus, dass die Klasse von der Klasse ObservableObject abgeleitet ist, \u00f6ffentlich ist und das Schl\u00fcsselwort partial enth\u00e4lt.

                                public partial class RecipeDetailPageViewModel : ObservableObject\n{\n    // ...\n\n    [ObservableProperty]\n    private Recipe _recipe = new();\n

                                Implementieren wir die vorbereitete Schnittstelle INavigationAware in RecipeDetailPageViewModel. Wir bereiten uns darauf vor, dass wir die ID des Rezepts als Navigationsparameter erhalten, das wir anzeigen wollen. In der Methode OnNavigatedTo rufen wir das Rezept \u00fcber RecipeService ab und speichern es in der Eigenschaft Recipe.

                                public partial class RecipeDetailPageViewModel : ObservableObject, INavigationAware\n{\n    // ...\n\n    public async void OnNavigatedTo(object parameter)\n    {\n        Recipe = await _recipeService.GetRecipeAsync((int)parameter);\n    }\n\n    public void OnNavigatedFrom()\n    {\n    }\n}\n

                                Note

                                In der Kopfzeile der Aktion OnNavigatedTo mussten wir das Schl\u00fcsselwort async verwenden, weil wir await in der Wurzel verwendet haben.

                                "},{"location":"labor/5-mvvm/index_ger/#23-rezeptdetailseite-navigation","title":"2.3 Rezeptdetailseite, Navigation","text":"

                                Erstellen wir eine neue Seite mit dem Namen RecipeDetailPage im Ordner Views (Rechtsklick auf den Ordner Views / Add New Item / Blank Page (WinUI 3)), auf der wir das Rezept anzeigen k\u00f6nnen. Zeigen wir zun\u00e4chst nur den Titel des Rezepts in einer TextBlock an.

                                <Grid x:Name=\"ContentArea\">\n    <Grid.RowDefinitions>\n        <RowDefinition Height=\"48\" />\n        <RowDefinition Height=\"*\" />\n    </Grid.RowDefinitions>\n\n    <TextBlock Grid.Row=\"0\"\n               Style=\"{StaticResource PageTitleStyle}\"\n               Text=\"{x:Bind ViewModel.Recipe.Title, Mode=OneWay}\" />\n</Grid>\n

                                Zu der Datenverbingung f\u00fcgen wir die Eigenschaft ViewModel in RecipeDetailPage.xaml.cs zur Hauptseite hinzu.

                                public RecipeDetailPageViewModel ViewModel => (RecipeDetailPageViewModel)DataContext;\n

                                \u00dcbersetzungsfehler

                                Wenn Sie aus irgendeinem Grund exotische Fehler erhalten, nachdem Sie eine neue Seite hinzugef\u00fcgt haben, l\u00f6schen Sie die folgenden Zeilen in der Projektdatei:

                                <ItemGroup>\n    <None Remove=\"ViewsRecipeDetailPage.xaml\" />\n</ItemGroup>\n
                                <Page Update=\"ViewsRecipeDetailPage.xaml\">\n    <Generator>MSBuild:Compile</Generator>\n</Page>\n

                                Um die Navigation zu unterst\u00fctzen, registrieren wir RecipeDetailPage in PageService im Ordner Services in den folgenden 3 Schritten:

                                1. Nehmen wir den Ansichtsschl\u00fcssel in die Klasse Pages auf.

                                  public static class Pages\n{\n    public static string Main { get; } = \"Main\";\n    public static string Detail { get; } = \"Detail\";\n}\n
                                2. Registrieren wir die Ansicht und ViewModel-Verbindung in PageService.

                                  public PageService()\n{\n    Configure<MainPageViewModel, MainPage>(Pages.Main);\n    Configure<RecipeDetailPageViewModel, RecipeDetailPage>(Pages.Detail);\n}\n
                                3. In der Datei App.xaml.cs registrieren wir das ViewModel und den View im Dependency Injection Container in der Methode ConfigureServices.

                                  services.AddTransient<RecipeDetailPage>();\nservices.AddTransient<RecipeDetailPageViewModel>();\n

                                Diese werden ben\u00f6tigt, weil die INavigationService in der Projektvorlage die Ansichten grunds\u00e4tzlich mit einem Schl\u00fcssel identifiziert, so dass das ViewModel den Ansichtstyp nicht kennen muss. Und anhand des Schl\u00fcssels kann man genau herausfinden, welche View angezeigt und welches ViewModel in der View DataContext aus dem DI-Container instanziiert werden soll.

                                In das MainPageViewModel injektieren wir den INavigationService, \u00fcber den wir zur RecipeDetailPage navigieren werden.

                                private readonly INavigationService _navigationService;\n\npublic MainPageViewModel(IRecipeService recipeService, INavigationService navigationService)\n{\n    _recipeService = recipeService;\n    _navigationService = navigationService;\n}\n
                                "},{"location":"labor/5-mvvm/index_ger/#command","title":"Command","text":"

                                Bisher haben wir uns mit einem Aspekt des MVVM-Musters besch\u00e4ftigt: wie die View auf die Daten im ViewModel zugreift und diese anzeigt, indem sie Daten bindet. Gleichzeitig besteht in der Regel eine weitere Beziehung zwischen View und ViewModel: Hier geht es darum, wie sich Ereignisse in der View (z.B. Klicks) auf das ViewModel auswirken. Damit werden wir uns jetzt befassen.

                                In unserem Fall m\u00fcssen wir zum Beispiel daf\u00fcr sorgen, dass ein Klick auf ein Rezept in der Hauptseitenansicht zu MainPageViewModel f\u00fchrt und dann zur Detailansicht dieses Rezepts navigiert.

                                Das ViewModel ver\u00f6ffentlicht die ausf\u00fchrbaren Operationen im MVVM-Muster durch Objekte, die typischerweise die Schnittstelle ICommand implementieren (die neben der Ausf\u00fchrung der spezifischen Operation auch die Bedingungen f\u00fcr die Ausf\u00fchrung der Operation verwalten k\u00f6nnen).

                                Erstellen wir unter MainPageViewModel einen Command, der ausgef\u00fchrt wird, wenn wir auf das Rezept klicken. Der Command erh\u00e4lt die Kopfzeile des ausgew\u00e4hlten Rezepts als Parameter und wird an RecipeDetailPage weitergeleitet, wo die ID des ausgew\u00e4hlten Rezepts \u00fcbergeben wird.

                                Jetzt sollten wir eine Klasse erstellen, die die Schnittstelle ICommand implementiert, und dann eine Instanz (Eigenschaft) davon in das ViewModel aufnehmen. Diese beiden Schritte werden durch das MVVM-Toolkit vereinfacht, wir m\u00fcssen nur eine Funktion mit dem Attribut [RelayCommand] zum ViewModel hinzuf\u00fcgen:

                                [RelayCommand]\nprivate void RecipeSelected(RecipeHeader recipe)\n{\n    _navigationService.NavigateTo(Pages.Detail, recipe.Id);\n}\n

                                Dies veranlasst den Compiler, die Commandsklasse und die Eigenschaft im ViewModel als RecipeSelectedCommand zu generieren.

                                Der Befehl und das ViewModel sind vorbereitet, aber die View wei\u00df noch nichts \u00fcber den Befehl. Unser Befehl im ViewModel muss mit den \u00fcblichen Techniken an das entsprechende Ereignis in der View gebunden werden. Verwenden wir f\u00fcr MVVM immer das Command-Muster wie dieses! Das Sch\u00f6ne an diesem Ansatz ist, dass er vollst\u00e4ndig mit der standardm\u00e4\u00dfigen direktionalen Datenverbindung von View->ViewModel durchgef\u00fchrt wird (die wir bereits mehrfach verwendet haben).

                                Binden wir daher auf MainPage die Eigenschaft AdaptiveGridView ItemClickCommand an RecipeSelectedCommand.

                                ItemClickCommand=\"{x:Bind ViewModel.RecipeSelectedCommand}\"\n

                                Probieren wir die App aus! Klicken wir auf die Rezepte, um die Rezeptdetailseite zu sehen.

                                Ausblick: Gibt es keinen Befehl f\u00fcr das Ereignis, das Sie verwenden m\u00f6chten?

                                Wenn der Controller einen Befehl f\u00fcr bestimmte Ereignisse bereitstellt, ist dies relativ einfach zu bewerkstelligen, wie im obigen Beispiel gezeigt. Wenn das Steuerelement jedoch keinen Befehl bereitstellt (z.B. das eingebaute GridView.ItemClicked), haben wir mehrere M\u00f6glichkeiten:

                                1. Code-Behind \"Klebercode\": Behandeln Sie das Ereignis des Controllers und rufen Sie die entsprechende Methode/Befehl des Code-Behind im ViewModel (xaml.cs) auf.

                                  <controls:AdaptiveGridView x:Name=\"gridView\"\n                            ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                            IsItemClickEnabled=\"True\"\n                            ItemClick=\"GridView_ItemClick\">\n
                                  private void GridView_ItemClick(object sender, ItemClickEventArgs e)\n{\n    ViewModel.RecipeSelectedCommand.Execute((RecipeHeader)e.ClickedItem);\n}\n
                                2. x:Bind-Ereignisbindung: Verwenden Sie die Bindungsoption der Methode x:Bind, um das Ereignis des Steuerelements an die Methode im ViewModel zu binden. Die Methode muss dann entweder parameterlos sein oder einen Parameter annehmen, der der Signatur des Ereignisses entspricht.

                                  View - MainPage.xaml
                                  <controls:AdaptiveGridView x:Name=\"gridView\"\n                            ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                            IsItemClickEnabled=\"True\"\n                            ItemClick=\"{x:Bind ViewModel.RecipeSelected\">\n</controls:AdaptiveGridView>\n
                                  ViewModel - MainPageViewModel
                                  public void RecipeSelected(object sender, ItemClickEventArgs e)\n{\n   ...\n}\n

                                  Der Nachteil dieser Methode ist, dass sie die Framework-Abh\u00e4ngigkeiten des View (Eventhandler-Parametertypen) mit den Ereignisparametern in das ViewModel einf\u00fchrt, obwohl die Idee war, das ViewModel unabh\u00e4ngig von der View zu machen. Nat\u00fcrlich kann diese Methode auch gut funktionieren, wenn wir die strikte Einhaltung des MVVM-Musters teilweise aufgeben.

                                3. Mit Hilfe von Behavior, ganz konkret EventTriggerBehavior und InvokeCommandAction Klassen, k\u00f6nnen Sie einen Command an ein Ereignis eines beliebigen Steuererelementes binden.

                                  <controls:AdaptiveGridView x:Name=\"gridView\"\n                            ItemsSource=\"{x:Bind RecipeGroupsCollectionSource.View, Mode=OneWay}\"\n                            IsItemClickEnabled=\"True\">\n    <i:Interaction.Behaviors>\n        <c:EventTriggerBehavior EventName=\"ItemClick\">\n            <c:InvokeCommandAction Command=\"{x:Bind ViewModel.RecipeSelectedCommand}\" \n                                   InputConverter=\"{StaticResource ItemClickedInputConverter}\" />\n        </c:EventTriggerBehavior>\n    </i:Interaction.Behaviors>\n

                                  Dies erm\u00f6glicht es uns, die Ansicht fast vollst\u00e4ndig deklarativ zu gestalten, aber wir m\u00fcssen immer noch eine Klasse ItemClickedInputConverter erstellen, die die Ereignisparameter mithilfe der Schnittstelle IValueConverter in den entsprechenden Typ umwandelt.

                                  public class ItemClickedInputConverter : IValueConverter\n{\n    public object Convert(object value, Type targetType, object parameter, string language)\n    {\n        return (RecipeHeader)((value as ItemClickEventArgs)?.ClickedItem);\n    }\n\n    public object ConvertBack(object value, Type targetType, object parameter, string language)\n    {\n        throw new NotImplementedException();\n    }\n}\n

                                  Behaviors sind in der XAML-Welt weit verbreiteter Mechanismus, um wiederverwendbare Verhaltensweisen zu Views hinzuzuf\u00fcgen (weitere Informationen hier).

                                "},{"location":"labor/5-mvvm/index_ger/#24-rezept-detailansicht","title":"2.4 Rezept-Detailansicht","text":"

                                Um die Details des Rezepts anzuzeigen, verwenden wir eine Grid mit zwei Spalten. Legen wir in die erste Spalte ein ScrollViewer, in das ein StackPanel eingef\u00fcgt wird. Legen wir auf StackPanel eine FlipView, an der die Bilder des Rezepts angezeigt werden sollen. FlipView funktioniert wie eine Liste, zeigt aber ihre Elemente in einer bl\u00e4tterbaren Oberfl\u00e4che an.

                                Unter FlipView finden wir ItemsControl (eine einfache Liste, die kein Scrollen, Ausw\u00e4hlen, Anklicken usw. unterst\u00fctzt), in der die Zutaten des Rezepts angezeigt werden.

                                Darunter befindet sich eine TextBlock, die die Schritte zur Zubereitung des Rezepts enth\u00e4lt.

                                In der zweiten Spalte platzieren wir ein Grid, wo die Liste der Kommentare und ihre Eingabefelder platziert werden.

                                Wir k\u00f6nnen den folgenden Code w\u00e4hrend des Praktikums auf RecipeDetailPage.xaml kopieren. Dieser Code ist im Vergleich zu den vorherigen nicht neu.

                                <?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<Page x:Class=\"MvvmLab.Views.RecipeDetailPage\"\n      xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n      xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n      xmlns:local=\"using:MvvmLab.Views\"\n      xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n      xmlns:models=\"using:MvvmLab.Core.Models\"\n      Background=\"{ThemeResource ApplicationPageBackgroundThemeBrush}\"\n      mc:Ignorable=\"d\">\n\n    <Grid x:Name=\"ContentArea\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n\n        <TextBlock Grid.Row=\"0\" Padding=\"10\"\n                   Style=\"{StaticResource TitleTextBlockStyle}\"\n                   Text=\"{x:Bind ViewModel.Recipe.Title, Mode=OneWay}\" />\n\n        <Grid Grid.Row=\"1\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"3*\" />\n                <ColumnDefinition Width=\"*\" />\n            </Grid.ColumnDefinitions>\n\n            <ScrollViewer Grid.Column=\"0\" Padding=\"20 10 0 20\">\n                <StackPanel Orientation=\"Vertical\">\n                    <StackPanel x:Name=\"images\"\n                                Margin=\"0,0,24,0\"\n                                Orientation=\"Vertical\">\n                        <TextBlock Margin=\"0,0,0,12\"\n                                   Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                                   Text=\"Images\" />\n                        <FlipView x:Name=\"flipView\"\n                                  MaxHeight=\"250\"\n                                  VerticalAlignment=\"Top\"\n                                  ItemsSource=\"{x:Bind ViewModel.Recipe.ExtraImages, Mode=OneWay}\">\n                            <FlipView.ItemTemplate>\n                                <DataTemplate>\n                                    <Image Source=\"{Binding}\" Stretch=\"Uniform\" />\n                                </DataTemplate>\n                            </FlipView.ItemTemplate>\n                        </FlipView>\n                    </StackPanel>\n\n                    <StackPanel x:Name=\"ingredients\"\n                                Margin=\"0,0,24,0\"\n                                Orientation=\"Vertical\">\n                        <TextBlock Margin=\"0,0,0,12\"\n                                   Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                                   Text=\"Ingredients\" />\n                        <ItemsControl HorizontalAlignment=\"Left\" ItemsSource=\"{x:Bind ViewModel.Recipe.Ingredients, Mode=OneWay}\">\n                            <ItemsControl.ItemTemplate>\n                                <DataTemplate>\n                                    <TextBlock Margin=\"0,0,0,10\"\n                                               Text=\"{Binding}\"\n                                               TextWrapping=\"Wrap\" />\n                                </DataTemplate>\n                            </ItemsControl.ItemTemplate>\n                        </ItemsControl>\n                    </StackPanel>\n\n                    <StackPanel x:Name=\"directions\"\n                                Margin=\"0,0,24,0\"\n                                Orientation=\"Vertical\"\n                                RelativePanel.RightOf=\"ingredients\">\n                        <TextBlock Margin=\"0,0,0,12\"\n                                   Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                                   Text=\"Directions\" />\n                        <TextBlock HorizontalAlignment=\"Left\"\n                                   Text=\"{x:Bind ViewModel.Recipe.Directions, Mode=OneWay}\"\n                                   TextWrapping=\"Wrap\" />\n                    </StackPanel>\n                </StackPanel>\n            </ScrollViewer>\n\n            <Grid Grid.Column=\"1\" RowSpacing=\"12\">\n                <Grid.RowDefinitions>\n                    <RowDefinition Height=\"Auto\" />\n                    <RowDefinition Height=\"*\" />\n                    <RowDefinition Height=\"Auto\" />\n                </Grid.RowDefinitions>\n\n                <TextBlock Grid.Row=\"0\"\n                           Style=\"{StaticResource SubtitleTextBlockStyle}\"\n                           Text=\"Comments\" />\n\n                <ListView Grid.Row=\"1\" ItemsSource=\"{x:Bind ViewModel.Recipe.Comments, Mode=OneWay}\">\n                    <ListView.ItemTemplate>\n                        <DataTemplate x:DataType=\"models:Comment\">\n                            <StackPanel Orientation=\"Vertical\" Padding=\"0 5 0 5\">\n                                <TextBlock FontWeight=\"Bold\" Text=\"{x:Bind Name}\" />\n                                <TextBlock Text=\"{x:Bind Text}\" />\n                            </StackPanel>\n                        </DataTemplate>\n                    </ListView.ItemTemplate>\n                </ListView>\n\n                <StackPanel x:Name=\"comments\"\n                            Grid.Row=\"2\"\n                            Margin=\"24,0,24,0\"\n                            Orientation=\"Vertical\">\n                    <!-- TODO input fields for comments -->\n                </StackPanel>\n            </Grid>\n        </Grid>\n    </Grid>\n</Page>\n

                                Probieren wir die App aus!

                                "},{"location":"labor/5-mvvm/index_ger/#aufgabe-3-kommentare-hinzufugen","title":"Aufgabe 3. - Kommentare hinzuf\u00fcgen","text":"

                                Wenn wir einen engen Zeitplan haben, k\u00f6nnen wir eine Funktion zum Hinzuf\u00fcgen von Kommentaren auf der Rezeptdetailseite erstellen.

                                "},{"location":"labor/5-mvvm/index_ger/#webdienst","title":"Webdienst","text":"

                                F\u00fcgen wir der Schnittstelle IRecipeService und der Implementierung eine Methode SendCommentAsync hinzu, die einen Kommentar an den Server unter dem Endpunkt POST /Recipes/{recipeId}/Comments sendet.

                                IRecipeService
                                public Task SendCommentAsync(int recipeId, Comment comment);\n
                                RecipeService
                                public async Task SendCommentAsync(int recipeId, Comment comment)\n{\n    using var client = new HttpClient();\n    await client.PostAsJsonAsync($\"{_baseUrl}/Recipes/{recipeId}/Comments\", comment);\n}\n
                                "},{"location":"labor/5-mvvm/index_ger/#viewmodel","title":"ViewModel","text":"

                                Erstellen wir in RecipeDetailPageViewModeleine Eigenschaft string mit dem Namen NewCommentText und eine Eigenschaft NewCommentName string mit dem Namen, in denen die vom Benutzer bereitgestellten Kommentarinformationen gespeichert werden sollen. Verwenden wir das Attribut ObservableProperty!

                                [ObservableProperty]\nprivate string _newCommentName = string.Empty;\n\n[ObservableProperty]\nprivate string _newCommentText = string.Empty;\n

                                Erstellen wir in RecipeDetailPageViewModel eine Funktion namens SendComment, mit der der Kommentar des Benutzers an den Server gesendet werden kann. Generieren wir einen Befehl aus der Funktion mit dem MVVM Toolkit ([RelayCommand]).

                                Die Umsetzung ist einfach: Wir senden den Kommentar an den Server und aktualisieren dann das Rezept.

                                [RelayCommand]\nprivate async Task SendComment()\n{\n    await _recipeService.SendCommentAsync(Recipe.Id, new Comment\n    {\n        Name = NewCommentName,\n        Text = NewCommentText\n    });\n\n    NewCommentName = string.Empty;\n    NewCommentText = string.Empty;\n\n    Recipe = await _recipeService.GetRecipeAsync(Recipe.Id);\n}\n

                                Die folgenden Elemente werden in der Ansicht platziert, um Kommentare hinzuzuf\u00fcgen:

                                <StackPanel x:Name=\"comments\"\n            Grid.Row=\"2\"\n            Margin=\"24,0,24,0\"\n            Orientation=\"Vertical\">\n    <TextBox Margin=\"0,0,0,16\"\n             Header=\"Name\"\n             Text=\"{x:Bind ViewModel.NewCommentName, Mode=TwoWay}\" />\n    <TextBox Margin=\"0,0,0,16\"\n             Header=\"Comment\"\n             Text=\"{x:Bind ViewModel.NewCommentText, Mode=TwoWay}\" />\n    <Button Margin=\"0,0,0,16\"\n            HorizontalAlignment=\"Right\"\n            Command=\"{x:Bind ViewModel.SendCommentCommand}\"\n            Content=\"Send\" />\n</StackPanel>\n

                                Beachten wir, dass die Eigenschaft Text von TextBox an die Eigenschaften NewCommentName und NewCommentText im ViewModel mit einer bidirektionalen Bindung gebunden ist, und dass die Eigenschaft Command der Taste an die Eigenschaft SendCommentCommand im ViewModel gebunden ist.

                                "},{"location":"labor/5-mvvm/index_ger/#ausblick-bedingungen-fur-die-ausfuhrung-von-befehlen","title":"Ausblick: Bedingungen f\u00fcr die Ausf\u00fchrung von Befehlen","text":"

                                Der Befehl SendCommentCommand erfordert, dass die Eigenschaften NewCommentName und NewCommentText nicht leer sind. Befehle bieten die M\u00f6glichkeit, ihre Ausf\u00fchrung an Bedingungen zu kn\u00fcpfen, die in der Methode CanExecute angegeben werden k\u00f6nnen. In unserem Fall m\u00fcssen wir dem Attribut Command generator einen Methoden-/Eigenschaftsnamen geben, der bool zur\u00fcckgibt.

                                RecipeDetailPageViewModel-ben:
                                private bool CanExecuteSendComment => !string.IsNullOrEmpty(NewCommentName) && !string.IsNullOrEmpty(NewCommentText);\n\n[RelayCommand(CanExecute = nameof(CanExecuteSendComment))]\nprivate async Task SendComment()\n

                                Probieren wir es aus. Wir stellen fest, dass die Taste nicht aktiviert wird, aber nach der \u00c4nderung von TextBox \u00e4ndert sich der Zustand der Taste nicht.

                                Die Methode CanExecute wird aufgerufen (von den Steuerelementen), wenn Command das Ereignis CanExecuteChanged ausl\u00f6st. In unserem Fall soll dieses Ereignis ausgel\u00f6st werden, wenn das Ereignis PropertyChanged der Eigenschaften NewCommentName und NewCommentText ausgel\u00f6st wird. Zu diesem Zweck bietet das MVVM Toolkit ein eigenes Attribut ([NotifyCanExecuteChangedFor]), das zu den Eigenschaften NewCommentName und NewCommentText hinzugef\u00fcgt werden muss.

                                Wenn sich also der Wert der Eigenschaft NewCommentName oder NewCommentText \u00e4ndert, wird auch das Ereignis SendCommentCommand Befehl CanExecuteChanged ausgel\u00f6st, wodurch die Methode CanExecute erneut ausgef\u00fchrt und der Zustand der Taste aktualisiert wird.

                                [ObservableProperty]\n[NotifyCanExecuteChangedFor(nameof(SendCommentCommand))]\nprivate string _newCommentName = string.Empty;\n\n[ObservableProperty]\n[NotifyCanExecuteChangedFor(nameof(SendCommentCommand))]\nprivate string _newCommentText = string.Empty;\n

                                Probieren wir es aus.

                                Es gibt nur noch eine Sache: Derzeit \u00e4ndert sich der Zustand von TextBox nur, wenn der Benutzer TextBox verl\u00e4sst. Dieses Verhalten kann \u00fcber die Eigenschaft UpdateSourceTrigger der Datenverbindung ge\u00e4ndert werden.

                                Text=\"{x:Bind ViewModel.NewCommentName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n\nText=\"{x:Bind ViewModel.NewCommentText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n

                                Probieren wir es aus.

                                "},{"location":"labor/6-tervezesi-mintak/","title":"6. Tervez\u00e9si mint\u00e1k (kiterjeszthet\u0151s\u00e9g)","text":""},{"location":"labor/6-tervezesi-mintak/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                                A gyakorlat c\u00e9ljai (egy \u00f6sszetettebb, \u00e9letszer\u0171 p\u00e9lda alapj\u00e1n):

                                • Kiterjeszthet\u0151s\u00e9get, \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1got, k\u00f3d \u00e1tl\u00e1that\u00f3s\u00e1got \u00e9s karbantarthat\u00f3s\u00e1got seg\u00edt\u0151 n\u00e9h\u00e1ny tervez\u00e9si alapelv gyakorl\u00e1sa: SRP, OPEN-CLOSED, DRY, KISS stb.
                                • N\u00e9h\u00e1ny, a kiterjeszthet\u0151s\u00e9ghez legink\u00e1bb kapcsol\u00f3d\u00f3 tervez\u00e9si minta alkalmaz\u00e1sa (Template Method, Strategy, Dependency Injection).
                                • Kiterjeszthet\u0151s\u00e9get \u00e9s \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1got t\u00e1mogat\u00f3 tov\u00e1bbi technik\u00e1k (pl. delegate/lambda kifejez\u00e9s) gyakorl\u00e1sa \u00e9s kombin\u00e1l\u00e1sa tervez\u00e9si mint\u00e1kkal.
                                • K\u00f3d refaktor\u00e1l\u00e1s gyakorl\u00e1sa.

                                Kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sok:

                                • Tervez\u00e9si mint\u00e1k: kiterjeszthet\u0151s\u00e9ghez kapcsol\u00f3d\u00f3 mint\u00e1k (bevezet\u0151, Template Method, Strategy), valamint a Dependency Injection \"minta\".
                                "},{"location":"labor/6-tervezesi-mintak/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                                A gyakorlat elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                                • Visual Studio 2022

                                Gyakorlat Linuxon vagy macOS alatt

                                A gyakorlat anyag alapvet\u0151en Windowsra \u00e9s Visual Studiora k\u00e9sz\u00fclt, de az elv\u00e9gezhet\u0151 m\u00e1s oper\u00e1ci\u00f3s rendszereken is m\u00e1s fejleszt\u0151eszk\u00f6z\u00f6kkel (pl. VS Code, Rider, Visual Studio for Mac), vagy ak\u00e1r egy sz\u00f6vegszerkeszt\u0151vel \u00e9s CLI (parancssori) eszk\u00f6z\u00f6kkel. Ezt az teszi lehet\u0151v\u00e9, hogy a p\u00e9ld\u00e1k egy egyszer\u0171 Console alkalmaz\u00e1s kontextus\u00e1ban ker\u00fclnek ismertet\u00e9sre (nincsenek Windows specifikus elemek), a .NET 8 SDK pedig t\u00e1mogatott Linuxon \u00e9s macOS alatt. Hello World Linuxon.

                                "},{"location":"labor/6-tervezesi-mintak/#elmeleti-hatter-szemleletmod","title":"Elm\u00e9leti h\u00e1tt\u00e9r, szeml\u00e9letm\u00f3d *","text":"

                                A komplexebb alkalmaz\u00e1sok fejleszt\u00e9se sor\u00e1n sz\u00e1mos tervez\u0151i d\u00f6nt\u00e9st kell meghoznunk, melyek sor\u00e1n t\u00f6bb lehet\u0151s\u00e9g k\u00f6z\u00fcl is v\u00e1laszthatunk. Amennyiben ezen pontokban nem tartjuk szem el\u0151tt az alkalmaz\u00e1sunk k\u00f6nny\u0171 karbantarthat\u00f3s\u00e1g\u00e1t, illetve egyszer\u0171en megval\u00f3s\u00edthat\u00f3 tov\u00e1bbfejleszt\u00e9si lehet\u0151s\u00e9g\u00e9t, k\u00f6nnyen hamar r\u00e9m\u00e1lomm\u00e1 v\u00e1lhat a fejleszt\u00e9s. A megrendel\u0151i v\u00e1ltoztat\u00e1si \u00e9s b\u0151v\u00edt\u00e9si ig\u00e9nyek a k\u00f3d nagym\u00e9rt\u00e9k\u0171 folyamatos \u00e1t\u00edr\u00e1s\u00e1t/m\u00f3dos\u00edt\u00e1s\u00e1t ig\u00e9nylik: ennek sor\u00e1n \u00faj hib\u00e1k sz\u00fcletnek, illetve jelent\u0151s munk\u00e1t kell fektetni a k\u00f3d nagy l\u00e9pt\u00e9k\u0171 \u00fajratesztel\u00e9s\u00e9be is!

                                A c\u00e9lunk az, hogy az ilyen v\u00e1ltoztat\u00e1si \u00e9s b\u0151v\u00edt\u00e9si ig\u00e9nyeket a k\u00f3d p\u00e1r j\u00f3l meghat\u00e1rozott pontj\u00e1ban t\u00f6rt\u00e9n\u0151 b\u0151v\u00edt\u00e9s\u00e9vel - a megl\u00e9v\u0151 k\u00f3d \u00e9rdemi m\u00f3dos\u00edt\u00e1sa n\u00e9lk\u00fcl - meg tudjuk val\u00f3s\u00edtani. A kulcssz\u00f3: m\u00f3dos\u00edt\u00e1ssal szemben b\u0151v\u00edt\u00e9s. Ehhez kapcsol\u00f3d\u00f3an: amennyiben bizonyos logik\u00e1ink kiterjeszthet\u0151k, akkor azok \u00e1ltal\u00e1nosabbak is leszek, t\u00f6bb kontextusban k\u00f6nnyebben is fel tudjuk ezeket haszn\u00e1lni. \u00cdgy hosszabb t\u00e1von gyorsabban haladunk, r\u00f6videbb a k\u00f3d, elker\u00fclj\u00fck a k\u00f3dduplik\u00e1ci\u00f3t (ez\u00e1ltal k\u00f6nnyebben karbantarthat\u00f3 is a k\u00f3d).

                                A tervez\u00e9si mint\u00e1k j\u00f3l bev\u00e1lt megold\u00e1sokat mutatnak bizonyos gyakran el\u0151fordul\u00f3 tervez\u00e9si probl\u00e9m\u00e1kra: ezen megold\u00e1sok abban seg\u00edtenek, hogy k\u00f3dunk k\u00f6nnyebben b\u0151v\u00edthet\u0151, karbantarthat\u00f3 \u00e9s min\u00e9l nagyobb m\u00e9rt\u00e9kben \u00fajrafelhaszn\u00e1lhat\u00f3 legyen. Jelen gyakorlat keret\u00e9ben azon mint\u00e1kra, tervez\u00e9si elvekre \u00e9s n\u00e9h\u00e1ny programoz\u00f3i eszk\u00f6zre f\u00f3kusz\u00e1lunk, melyek a fenti probl\u00e9m\u00e1kon seg\u00edtenek. Ugyanakkor ne ess\u00fcnk \u00e1t a l\u00f3 t\u00faloldal\u00e1ra: csak akkor \u00e9rdemes egy adott tervez\u00e9si mint\u00e1t bevetni, ha adott esetben val\u00f3s el\u0151nyt jelent az alkalmaz\u00e1sa. Ellenkez\u0151 esetben csak a megval\u00f3s\u00edt\u00e1s komplexit\u00e1s\u00e1t n\u00f6veli feleslegesen. Ennek t\u00fckr\u00e9ben nem is c\u00e9lunk (\u00e9s sokszor nincs is r\u00e1 lehet\u0151s\u00e9g\u00fcnk), hogy minden j\u00f6v\u0151beli kiterjeszthet\u0151s\u00e9gi ig\u00e9nyt el\u0151re meg\u00e9rezz\u00fcnk, illetve nagyon el\u0151re \u00e1tgondoljunk. A l\u00e9nyeg az, hogy ak\u00e1r egy egyszer\u0171 megold\u00e1sb\u00f3l kiindulva, az egyes probl\u00e9m\u00e1kat felismerve, a k\u00f3dunkat folyamatosan refaktor\u00e1ljuk \u00fagy, hogy az aktu\u00e1lis (funkcion\u00e1lis \u00e9s nemfunkcion\u00e1lis) k\u00f6vetelm\u00e9nyeknek \u00e9s el\u0151rel\u00e1t\u00e1sunk szerint a megfelel\u0151 pontokban tegy\u00fck k\u00f3dunkat k\u00f6nnyebben kiterjeszthet\u0151v\u00e9 \u00e9s \u00fajrafelhaszn\u00e1lhat\u00f3v\u00e1.

                                Meg kell eml\u00edteni, hogy kapcsol\u00f3d\u00f3 tervez\u00e9si mint\u00e1k \u00e9s nyelvi eszk\u00f6z\u00f6k a k\u00f3dunk egys\u00e9gtesztelhet\u0151v\u00e9 t\u00e9tel\u00e9ben is nagym\u00e9rt\u00e9kben seg\u00edtenek: sok c\u00e9gn\u00e9l egy szoftverterm\u00e9k fejleszt\u00e9se eset\u00e9n (jogos) alapelv\u00e1r\u00e1s a fejleszt\u0151kt\u0151l, hogy nagy k\u00f3dlefedetts\u00e9g\u0171 egys\u00e9gteszteket (unit test) k\u00e9sz\u00edtsenek. Ennek kivitelez\u00e9se viszont gyakorlatilag lehetetlen, ha a k\u00f3dunk egyes egys\u00e9gei/oszt\u00e1lyai t\u00fal szoros csatol\u00e1sban vannak egym\u00e1ssal.

                                "},{"location":"labor/6-tervezesi-mintak/#0-feladat-ismerkedes-a-feladattal-es-a-kiindulo-alkalmazassal","title":"0. Feladat - Ismerked\u00e9s a feladattal \u00e9s a kiindul\u00f3 alkalmaz\u00e1ssal","text":"

                                Kl\u00f3nozzuk le a 6. laborhoz tartoz\u00f3 kiindul\u00f3 alkalmaz\u00e1s repositoryj\u00e1t:

                                • Nyissunk egy command prompt-ot
                                • Navig\u00e1ljunk el egy tetsz\u0151leges mapp\u00e1ba, p\u00e9ld\u00e1ul c:\\work\\NEPTUN
                                • Adjuk ki a k\u00f6vetkez\u0151 parancsot: git clone https://github.com/bmeviauab00/lab-patterns-extensibility-kiindulo.git
                                • Nyissuk meg a Lab-Patterns-Extensibility.sln solutiont Visual Studio-ban.
                                "},{"location":"labor/6-tervezesi-mintak/#a-feladat-ismertetese","title":"A feladat ismertet\u00e9se","text":"

                                A labor sor\u00e1n egy konzol alap\u00fa, adatfeldolgoz\u00f3 (pontosabban anonimiz\u00e1l\u00f3) alkalmaz\u00e1st fogunk a folyamatosan alakul\u00f3 ig\u00e9nyeknek megfelel\u0151en - k\u00fcl\u00f6nb\u00f6z\u0151 pontok ment\u00e9n \u00e9s k\u00fcl\u00f6nb\u00f6z\u0151 technik\u00e1kat alkalmazva - kiterjeszthet\u0151v\u00e9 tenni. Az els\u0151 feladat keret\u00e9ben az anonimiz\u00e1l\u00e1s fogalm\u00e1val is megismerked\u00fcnk.

                                Az alkalmaz\u00e1s bemenete egy CSV sz\u00f6vegf\u00e1jl, mely minden sora egy adott szem\u00e9lyre vonatkoz\u00f3an tartalmaz adatokat. A f\u00e1jlrendszerben nyissuk meg a Data mapp\u00e1ban lev\u0151 us-500.csv f\u00e1jlt (duplakattal, vagy ak\u00e1r a Jegyzett\u00f6mb/Notepad alkalmaz\u00e1sban). Az l\u00e1tjuk, hogy \"\" k\u00f6z\u00f6tt, vessz\u0151vel elv\u00e1lasztva tal\u00e1lhat\u00f3k az egyes szem\u00e9lyekre vonatkoz\u00f3 adatok (ezek nem val\u00f3sak). N\u00e9zz\u00fck az els\u0151 sort:

                                \"James\",\"Rhymes\",\"Benton, John B Jr\",\"6649 N Blue Gum St\",\"New Orleans \",\"Orleans\",\"LA\",\"70116\",\"504-621-8927\",\"504-845-1427\",\"30\",\"65\",\"Heart-related\",\"jRhymes@gmail.com\"\n

                                Az els\u0151 sorban lev\u0151 szem\u00e9lyt James Rhymesnak nevezik, a \"Benton, John B Jr\" c\u00e9gn\u00e9l dolgozik, majd n\u00e9h\u00e1ny c\u00edmre vonatkoz\u00f3 mez\u0151 tal\u00e1lhat\u00f3, 30 \u00e9ves, 65 kg a tests\u00falya. Az ezt k\u00f6vet\u0151 mez\u0151 azt mondja meg, milyen s\u00falyosabb betegs\u00e9ge van (a fenti sorban ez \"Heart-related\"). Az utols\u00f3 oszlop pedig a szem\u00e9ly e-mail c\u00edm\u00e9t tartalmazza.

                                Adatok forr\u00e1sa \u00e9s pontos form\u00e1tuma *

                                Az adatok forr\u00e1sa: https://www.briandunning.com/sample-data/, p\u00e1r oszloppal (kor, s\u00faly, betegs\u00e9g) kieg\u00e9sz\u00edtve. A mez\u0151k sorrendje: First Name, Last Name, Company, Address, City, County (where applicable), State/Province (where applicable), ZIP/Postal Code, Phone 1, Phone 2, Age, Weight, Illness, Email

                                Az alkalmaz\u00e1s alapfeladata, hogy ezeket az adatokat az aktu\u00e1lis ig\u00e9nyeknek megfelel\u0151en anonimiz\u00e1lja, majd egy kimeneti CSV sz\u00f6vegf\u00e1jlba ki\u00edrja. Az anonimiz\u00e1l\u00e1s c\u00e9lja, hogy az adatok \u00e1talak\u00edt\u00e1s\u00e1val adathalmazban lev\u0151 szem\u00e9lyeket beazonos\u00edthatatlann\u00e1 tegye, de olyan m\u00f3don, hogy az adatokb\u00f3l m\u00e9gis lehessen kimutat\u00e1sokat k\u00e9sz\u00edteni. Az anonimiz\u00e1l\u00e1s egy k\u00fcl\u00f6n\u00e1ll\u00f3, nagyon komoly \u00e9s sok kih\u00edv\u00e1st rejt\u0151 adatfeldolgoz\u00e1si szakter\u00fclet. A gyakorlat keret\u00e9ben nem c\u00e9lunk, hogy val\u00f3s k\u00f6rnyezetben is haszn\u00e1lhat\u00f3, vagy ak\u00e1r minden tekintetben \u00e9rtelmes megold\u00e1sokat dolgozzunk ki. Sz\u00e1munkra tulajdonk\u00e9ppen csak egy valamilyen adatfeldolgoz\u00f3 algoritmus \"bevet\u00e9se\" a fontos a mint\u00e1k bemutat\u00e1s\u00e1hoz. Ez tal\u00e1n kicsit \"izgalmasabb\" keretet ad, mint egy egyszer\u0171 adatsz\u0171r\u00e9s/sorrendez\u00e9s/stb. alap\u00fa adatfeldolgoz\u00e1s (melyeket r\u00e1ad\u00e1sul a .NET m\u00e1r eleve be\u00e9p\u00edtve t\u00e1mogat).

                                P\u00e1r gondolat az anonimiz\u00e1l\u00e1sr\u00f3l

                                Azt gondolhatn\u00e1nk, hogy az anonimiz\u00e1l\u00e1s egy egyszer\u0171 probl\u00e9mak\u00f6r. Pl. csak el kell t\u00e1vol\u00edtani, vagy ki kell \"csillagozni\" a szem\u00e9lyek neveit, lakc\u00edm\u00e9b\u0151l az utca-h\u00e1zsz\u00e1mot, telefonsz\u00e1mokat, e-mail c\u00edmet, \u00e9s meg is vagyunk. P\u00e9ld\u00e1ul a bemenet\u00fcnk els\u0151 sor\u00e1ra ez lenne a kimenet:

                                \"***\",\"***\",\"Benton, John B Jr\",\"***\",\"New Orleans \",\"Orleans\",\"LA\",\"70116\",\"***\",\"***\",\"30\",\"65\",\"Heart-related\",\"***\"\n

                                De ez kor\u00e1nt sincs \u00edgy, k\u00fcl\u00f6n\u00f6sen, ha igaz\u00e1n sok adatr\u00f3l van sz\u00f3. Gondoljunk arra, hogy van egy kisebb falu, ahol nem laknak sokan. Tegy\u00fck fel, hogy az egyik fenti m\u00f3don anonimiz\u00e1lt szem\u00e9ly \u00e9letkora 14 \u00e9v, de rendk\u00edv\u00fcl t\u00fals\u00falyos, 95 kg. Ez egy ritka \"kombin\u00e1ci\u00f3\", m\u00e1s szem\u00e9ly j\u00f3 es\u00e9llyel nem \u00e9l ilyen param\u00e9terekkel a faluban. Ha az \u0151 oszt\u00e1lyt\u00e1rsai k\u00f6z\u00fcl (nyolcadikos, hiszen 14 \u00e9ves) valaki megn\u00e9zi az \"anonimiz\u00e1lt\" adatokat, tudni fogja ki \u0151 (nincs m\u00e1s ennyire t\u00fals\u00falyos nyolcadikos az iskol\u00e1ban), beazonos\u00edtja a szem\u00e9lyt. \u00cdgy pl. tudni fogja, milyen betegs\u00e9ge van az illet\u0151nek. Tanuls\u00e1g: az adatok \u00f6sszef\u00fcgg\u00e9sben \u00e1rulkod\u00f3k lehetnek.

                                Mi a megold\u00e1s? A v\u00e1rost, az \u00e9letkort \u00e9s a testt\u00f6meget nem t\u00f6r\u00f6lhetj\u00fck/csillagozhatjuk, mert ezekre vonatkoz\u00f3an kell kimutat\u00e1st k\u00e9sz\u00edteni. Egy tipikus megold\u00e1s: nem pontos \u00e9letkort/tests\u00falyt adunk meg az anonimiz\u00e1l\u00e1st k\u00f6vet\u0151en, hanem s\u00e1vokat (vagyis \u00e1ltal\u00e1nos\u00edtjuk az adatokat): pl. a fenti szem\u00e9ly eset\u00e9ben az \u00e9letkora 10..20 \u00e9v, tests\u00falya 80..100 kg, \u00e9s ezeket adjuk meg erre a szem\u00e9lyre vonatkoz\u00f3an a kimeneti f\u00e1jlban. \u00cdgy m\u00e1r nem lehet beazonos\u00edtani a szem\u00e9lyeket. Ezt a technik\u00e1t mi is fogjuk k\u00e9s\u0151bb alkalmazni.

                                "},{"location":"labor/6-tervezesi-mintak/#kiindulo-kovetelmenyek","title":"Kiindul\u00f3 k\u00f6vetelm\u00e9nyek","text":"

                                Az alkalmaz\u00e1ssal szemben t\u00e1masztott kiindul\u00f3 k\u00f6vetelm\u00e9nyek:

                                1. Egy adott \u00fcgyf\u00e9lt\u0151l kapott f\u00e1jlokat (mindnek ugyanaz a form\u00e1tuma) kell ugyanazzal az anonimiz\u00e1l\u00f3 algoritmussal, ugyanabba a kimeneti form\u00e1tumba konvert\u00e1lni. Az anonimiz\u00e1l\u00e1s egyszer\u0171en a keresztn\u00e9v \u00e9s vezet\u00e9kn\u00e9v \"kicsillagoz\u00e1s\u00e1b\u00f3l\" \u00e1lljon.
                                2. Sz\u00fcks\u00e9g van n\u00e9mi adattiszt\u00edt\u00e1sra. A bemeneti adatokban a v\u00e1rost tartalmaz\u00f3 oszlop elej\u00e9n/v\u00e9g\u00e9n lehetnek felesleges _ \u00e9s # karakterek, ezeket el kell t\u00e1vol\u00edtani (trim m\u0171velet).
                                3. Ki kell \u00edrni minden sor feldolgoz\u00e1sa ut\u00e1n a konzolra, hogy a sor feldolgoz\u00e1sa megt\u00f6rt\u00e9nt, ill. a minden adat feldolgoz\u00e1s ut\u00e1n n\u00e9mi \u00f6sszes\u00edt\u0151 inform\u00e1ci\u00f3t (Summary) is meg kell jelen\u00edteni: h\u00e1ny sort dolgoztunk fel, \u00e9s mennyin\u00e9l kellett a v\u00e1rosnevet trimmelni.
                                4. L\u00e9nyeges szempont: az alkalmaz\u00e1sra csak r\u00f6vid id\u0151re lesz sz\u00fcks\u00e9g, nem a k\u00edv\u00e1njuk k\u00e9s\u0151bbiekben b\u0151v\u00edteni.

                                Megjegyz\u00e9s: annak \u00e9rdek\u00e9ben, hogy a k\u00f3dban kevesebb mez\u0151vel kelljen dolgozni, \u00e9s a kimenet is \u00e1tl\u00e1that\u00f3bb legyen, elhagyunk m\u00e9g n\u00e9h\u00e1ny mez\u0151t a feldolgoz\u00e1s sor\u00e1n.

                                P\u00e9ldak\u00e9nt a bemeneti f\u00e1jlunk els\u0151 sor\u00e1ra a v\u00e1rt kimenet:

                                ***; ***; LA; New Orleans; 30; 65; Heart-related\n
                                "},{"location":"labor/6-tervezesi-mintak/#1-megoldas-minden-egyben-1-startstart","title":"1. Megold\u00e1s - minden egyben (1-Start/Start)","text":"

                                A Visual Studio Solution Explorer\u00e9ben mapp\u00e1kat l\u00e1tunk, 1-t\u0151l 4-ig sz\u00e1mmal kezd\u0151d\u0151 n\u00e9vvel. Ezek az egyes munkaiter\u00e1ci\u00f3khoz tartoz\u00f3 megold\u00e1sokat tartalmazz\u00e1k. Az els\u0151 k\u00f6r\u00f6s megold\u00e1s az \"1-Start\" mapp\u00e1ban, \"Start\" projektn\u00e9v alatt tal\u00e1lhat\u00f3. N\u00e9zz\u00fck meg a projektben tal\u00e1lhat\u00f3 f\u00e1jlokat:

                                • Person.cs - Egy szem\u00e9ly sz\u00e1munkra \u00e9rdekes adatai tartalmazza, ennek objektumaiba olvassuk be egy-egy szem\u00e9ly adatait.
                                • Program.cs - Ennek Main f\u00fcggv\u00e9ny\u00e9ben van megval\u00f3s\u00edtva minden logika, k\u00f3dmegjegyz\u00e9sekkel \"elv\u00e1lasztva\". Amennyiben kicsit is bonyolultabb\u00e1 v\u00e1lik a logika, m\u00e1r egy-k\u00e9t nap (\u00f3ra?) ut\u00e1n mi magunk is csak nehezen fogjuk \u00e1ttekinteni \u00e9s meg\u00e9rteni a saj\u00e1t k\u00f3dunkat. Ezt a megold\u00e1st ne is n\u00e9zz\u00fck.

                                \u00d6sszeg\u00e9sz\u00e9ben minden nagyon egyszer\u0171 a megold\u00e1sban, hiszen a k\u00f3dnak nem j\u00f3solunk hossz\u00fa j\u00f6v\u0151t. De az egy f\u00fcggv\u00e9nybe \u00f6nt\u00f6tt \"szkriptszer\u0171\", \"minden egybe\" megold\u00e1s ekkor sem j\u00f3 ir\u00e1ny, nagyon neh\u00e9zz\u00e9 teszi a k\u00f3d \u00e1tl\u00e1t\u00e1s\u00e1t, meg\u00e9rt\u00e9s\u00e9t. Ne is n\u00e9zz\u00fck ezt tov\u00e1bb.

                                "},{"location":"labor/6-tervezesi-mintak/#2-megoldas-2-organizedtofunctionsorganizedtofunctions-1","title":"2. Megold\u00e1s (2-OrganizedToFunctions/OrganizedToFunctions-1)","text":"

                                T\u00e9rj\u00fcnk \u00e1t Visual Studioban a \"2-OrganizedToFunctions\" mapp\u00e1ban tal\u00e1lhat\u00f3 \"OrganizedToFunctions-1\" projektben tal\u00e1lhat\u00f3 megold\u00e1sra. Ez m\u00e1r sokkal szimpatikusabb, mert f\u00fcggv\u00e9nyekre bontottuk a logik\u00e1t. Tekints\u00fck \u00e1t a k\u00f3dot r\u00f6viden:

                                Anonymizer.cs

                                • A Run f\u00fcggv\u00e9ny a \"gerince\", ez tartalmazza a vez\u00e9rl\u00e9si logik\u00e1t, ez h\u00edvja az egyes l\u00e9p\u00e9sek\u00e9rt felel\u0151s f\u00fcggv\u00e9nyeket.
                                • ReadFromInput m\u0171velet: beolvassa a forr\u00e1sf\u00e1jlt, minden sorhoz k\u00e9sz\u00edt egy Person objektumot, \u00e9s visszat\u00e9r a beolvasott Person objektumok list\u00e1j\u00e1val.
                                • TrimCityNames: Az adattiszt\u00edt\u00e1st v\u00e9gzi (v\u00e1rosnevek trimmel\u00e9se).
                                • Anonymize: Minden egyes beolvasott Person objektummal megh\u00edv\u00e1sra ker\u00fcl, \u00e9s feladata, hogy visszaadjon egy \u00faj Person objektumot, mely m\u00e1r az anonimiz\u00e1lt adatokat tartalmazza.
                                • WriteToOutput: m\u00e1r anonimiz\u00e1lt Person objektumokat ki\u00edrja a kimeneti f\u00e1jlba.
                                • PrintSummary: ki\u00edrja az \u00f6sszes\u00edt\u00e9st a feldolgoz\u00e1s v\u00e9g\u00e9n a konzolra.

                                Program.cs

                                • L\u00e9trehoz egy Anonymizer objektumot \u00e9s a Run h\u00edv\u00e1s\u00e1val futtatja. L\u00e1that\u00f3, hogy az anonimiz\u00e1l\u00e1s sor\u00e1n maszkol\u00e1sra haszn\u00e1lt stringet konstruktor param\u00e9terben kell megadni.

                                Pr\u00f3b\u00e1ljuk ki, futtassuk! Ehhez a \"OrganizedToFunctions-1\" legyen Visual Studioban a startup projekt (jobb katt rajta, \u00e9s Set as Startup Project), majd futtassuk:

                                A kimeneti f\u00e1jt f\u00e1jlkezel\u0151ben tudjuk megn\u00e9zni, az \"OrganizedToFunctions-1\\bin\\Debug\\net8.0\\\" vagy hasonl\u00f3 nev\u0171 mapp\u00e1ban tal\u00e1ljuk, \"us-500.processed.txt\" n\u00e9ven. Nyissuk meg, \u00e9s vess\u00fcnk egy pillant\u00e1st az adatokra.

                                "},{"location":"labor/6-tervezesi-mintak/#a-megoldas-ertekelese","title":"A megold\u00e1s \u00e9rt\u00e9kel\u00e9se","text":"
                                • A megold\u00e1s alapvet\u0151en j\u00f3l struktur\u00e1lt, k\u00f6nnyen meg\u00e9rthet\u0151.
                                • K\u00f6veti a KISS (Keep It Stupid Simple) elvet, nem haszn\u00e1l felesleges bonyol\u00edt\u00e1sokat. Ez \u00edgy j\u00f3, hiszen nem mer\u00fcltek fel potenci\u00e1lis j\u00f6v\u0151beli tov\u00e1bbfejleszt\u00e9si ig\u00e9nyek, nem kell k\u00fcl\u00f6nb\u00f6z\u0151 form\u00e1tumokat, logik\u00e1kat stb. t\u00e1mogatni.
                                • A megold\u00e1sunk ugyanakkor nem k\u00f6veti az egyik legalapvet\u0151bb \u00e9s legh\u00edresebb tervez\u00e9si elvet, mely Single Responsibility Principle (r\u00f6viden SRP) n\u00e9ven k\u00f6zismert. Ez - n\u00e9mi egyszer\u0171s\u00edt\u00e9ssel \u00e9lve - azt v\u00e1rja el, hogy egy oszt\u00e1lynak egy felel\u0151ss\u00e9ge legyen (alapvet\u0151en egy dologgal foglalkozzon).

                                  • K\u00e9ts\u00e9gtelen, hogy az Anonymizer oszt\u00e1lyunknak sz\u00e1mos felel\u0151ss\u00e9ge van: bemenet feldolgoz\u00e1sa, adattiszt\u00edt\u00e1s, anonimiz\u00e1l\u00e1s, kimenet el\u0151\u00e1ll\u00edt\u00e1sa stb.
                                  • Ez a probl\u00e9ma n\u00e1lunk az\u00e9rt nem felt\u0171n\u0151, illetve az\u00e9rt nem okoz gondot, mert mindegyik felel\u0151ss\u00e9g megval\u00f3s\u00edt\u00e1sa egyszer\u0171, \"belef\u00e9rt\" egy-egy r\u00f6videbb f\u00fcggv\u00e9nybe. De ha b\u00e1rmelyik is \u00f6sszetettebb lenne, t\u00f6bb f\u00fcggv\u00e9nyben lenn\u00e9nek megval\u00f3s\u00edtva, akkor mindenk\u00e9ppen k\u00fcl\u00f6n oszt\u00e1lyba illene szervezni.
                                  Mi\u00e9rt probl\u00e9ma, ha egy oszt\u00e1lynak t\u00f6bb felel\u0151ss\u00e9ge van? *
                                  • Nehezebb meg\u00e9rteni a m\u0171k\u00f6d\u00e9s\u00e9t, mert nem egy dologra f\u00f3kusz\u00e1l.
                                  • Ha b\u00e1rmelyik felel\u0151ss\u00e9g ment\u00e9n is j\u00f6n be v\u00e1ltoz\u00e1si ig\u00e9ny, egy nagy, sok mindennel foglalkoz\u00f3 oszt\u00e1lyt kell v\u00e1ltoztatni \u00e9s \u00fajra tesztelni.
                                • A megold\u00e1shoz lehet \u00edrni automatiz\u00e1lt integr\u00e1ci\u00f3s (input-output) teszteket, de \"igazi\" egys\u00e9gteszteket nem.

                                "},{"location":"labor/6-tervezesi-mintak/#3-megoldas-organizedtofunctions-2-twoalgorithms","title":"3. Megold\u00e1s (OrganizedToFunctions-2-TwoAlgorithms)","text":"

                                A kor\u00e1bbi \"tervekkel\" ellent\u00e9tben \u00faj felhaszn\u00e1l\u00f3i ig\u00e9nyek mer\u00fcltek fel. Az \u00fcgyfel\u00fcnk meggondolta mag\u00e1t, egy m\u00e1sik adathalmazn\u00e1l m\u00e1sf\u00e9le anonimiz\u00e1l\u00f3 algoritmus megval\u00f3s\u00edt\u00e1s\u00e1t k\u00e9ri: a szem\u00e9lyek \u00e9letkor\u00e1t kell s\u00e1vosan menteni, nem der\u00fclhet ki a szem\u00e9lyek pontos \u00e9letkora. Az egyszer\u0171s\u00e9g \u00e9rdek\u00e9ben ez esetben a szem\u00e9lyek nev\u00e9t nem fogjuk anonimiz\u00e1lni, \u00edgy tekints\u00fck ezt egyfajta \"pszeudo\" anonimiz\u00e1l\u00e1snak (ett\u0151l m\u00e9g lehet \u00e9rtelme, csak nem teljesen korrekt ezt anonimiz\u00e1l\u00e1snak nevezni).

                                A megold\u00e1sunkat - mely egyar\u00e1nt t\u00e1mogatja a r\u00e9gi \u00e9s az \u00faj algoritmust (egyszerre csak az egyiket) - a VS solution OrganizedToFunctions-2-TwoAlgorithms nev\u0171 projektj\u00e9ben tal\u00e1ljuk. N\u00e9zz\u00fcnk r\u00e1 az Anonymizer oszt\u00e1lyra, a megold\u00e1s alapelve (ezeket tekints\u00fck \u00e1t a k\u00f3dban):

                                • Bevezett\u00fcnk egy AnonymizerMode enum t\u00edpust, mely meghat\u00e1rozza, hogy melyik \u00fczemm\u00f3dban (algoritmussal) haszn\u00e1ljuk az Anonymizer oszt\u00e1lyt.
                                • Az Anonymizer oszt\u00e1lynak k\u00e9t anonimiz\u00e1l\u00f3 m\u0171velete van: Anonymize_MaskName, Anonymize_AgeRange
                                • Az Anonymizer oszt\u00e1ly a _anonymizerMode tagj\u00e1ban t\u00e1rolja, melyik algoritmust kell haszn\u00e1lni: a k\u00e9t \u00fczemm\u00f3dhoz k\u00e9t k\u00fcl\u00f6n konstruktort vezett\u00fcnk be, ezek \u00e1ll\u00edtj\u00e1k be az _anonymizerMode \u00e9rt\u00e9k\u00e9t.
                                • Az Anonymizer oszt\u00e1ly t\u00f6bb helyen is megvizsg\u00e1lja (pl. Run, GetAnonymizerDescription m\u0171veletek), hogy mi az _anonymizerMode \u00e9rt\u00e9ke, \u00e9s ennek f\u00fcggv\u00e9ny\u00e9ben el\u00e1gazik.
                                • A GetAnonymizerDescription-ben az\u00e9rt kell ezt megtenni, mert ennek a m\u0171veletnek a feladata az anonimiz\u00e1l\u00f3 algoritmusr\u00f3l egy egysoros le\u00edr\u00e1s el\u0151\u00e1ll\u00edt\u00e1sa, melyet a feldolgoz\u00e1s v\u00e9g\u00e9n a \"summary\"-ben megjelen\u00edt. N\u00e9zz\u00fcnk r\u00e1 a PintSummary k\u00f3dj\u00e1ra, ez a m\u0171velet h\u00edvja. Pl. ez jelenik meg a konzolon \u00f6sszefoglal\u00f3k\u00e9nt, ha \u00e9letkor anonimiz\u00e1l\u00f3t haszn\u00e1lunk 20-as range-dzsel:

                                  Summary - Anonymizer (Age anonymizer with range size 20): Persons: 500, trimmed: 2

                                "},{"location":"labor/6-tervezesi-mintak/#a-megoldas-ertekelese_1","title":"A megold\u00e1s \u00e9rt\u00e9kel\u00e9se","text":"

                                \u00d6sszeg\u00e9sz\u00e9ben megold\u00e1sunk k\u00f3dmin\u0151s\u00e9g tekintet\u00e9ben a kor\u00e1bbin\u00e1l rosszabb lett. Kor\u00e1bban nem volt probl\u00e9ma, hogy anonimiz\u00e1l\u00f3 algoritmusok tekintet\u00e9ben nem volt kiterjeszthet\u0151, hiszen nem volt r\u00e1 ig\u00e9ny. De ha m\u00e1r egyszer felmer\u00fclt az ig\u00e9ny \u00faj algoritmus bevezet\u00e9s\u00e9re, akkor hiba ebben a tekintetben nem kiterjeszthet\u0151v\u00e9 tenni a megold\u00e1sunkat: ett\u0151l kezdve sokkal ink\u00e1bb sz\u00e1m\u00edtunk arra, hogy \u00fajabb tov\u00e1bbi algoritmusokat kell bevezetni a j\u00f6v\u0151ben.

                                Mi\u00e9rt \u00e1ll\u00edtjuk azt, hogy a k\u00f3dunk nem kiterjeszthet\u0151, amikor \"csak\" egy \u00faj enum \u00e9rt\u00e9ket, \u00e9s egy-egy plusz if/switch \u00e1gat kell a k\u00f3d n\u00e9h\u00e1ny pontj\u00e1ra bevezetni, amikor \u00faj algoritmust kell majd bevezetni?

                                Open/Closed principle Kulcsfontoss\u00e1g\u00fa, hogy egy oszt\u00e1lyt akkor tekint\u00fcnk kiterjeszthet\u0151nek, ha annak b\u00e1rmilyen nem\u0171 m\u00f3dos\u00edt\u00e1sa n\u00e9lk\u00fcl, puszt\u00e1n a k\u00f3d kiterjeszt\u00e9s\u00e9vel/b\u0151v\u00edt\u00e9s\u00e9vel lehet \u00faj viselked\u00e9st (eset\u00fcnkben \u00faj algoritmust) bevezetni. Vagyis eset\u00fcnkben az Anonymizer k\u00f3dj\u00e1hoz nem szabadna hozz\u00e1ny\u00falni, ami egy\u00e9rtelm\u0171en nem teljes\u00fcl. Ez a h\u00edres Open/Closed principle/elv: the class should be Open for Extension, Closed for Modification. A k\u00f3d m\u00f3dos\u00edt\u00e1sa az\u00e9rt probl\u00e9ma, mert annak sor\u00e1n j\u00f3 es\u00e9llyel \u00faj bugokat vezet\u00fcnk be, ill. a m\u00f3dos\u00edtott k\u00f3dot mindig \u00fajra kell tesztelni, ez pedig jelent\u0151s id\u0151/k\u00f6lts\u00e9gr\u00e1ford\u00edt\u00e1si ig\u00e9nyt jelenthet.

                                Mi is a pontos c\u00e9l, \u00e9s hogyan \u00e9rj\u00fck ezt el? Vannak olyan r\u00e9szek az oszt\u00e1lyunkban, melyeket nem szeretn\u00e9nk be\u00e9getni:

                                • Ezek nem adatok, hanem viselked\u00e9sek (k\u00f3d, logika).
                                • Nem if/switch utas\u00edt\u00e1sokkal oldjuk meg: \"kiterjeszt\u00e9si pontokat\" vezet\u00fcnk be, \u00e9s valamilyen m\u00f3don megoldjuk, hogy ezekben \"tetsz\u0151leges\" k\u00f3d lefuthasson.
                                • Ezek v\u00e1ltoz\u00f3/esetf\u00fcgg\u0151 r\u00e9szek k\u00f3dj\u00e1t m\u00e1s oszt\u00e1lyokba tessz\u00fck (az oszt\u00e1lyunk szempontj\u00e1b\u00f3l \"lecser\u00e9lhet\u0151\" m\u00f3don)!

                                Note

                                Ne gondoljunk semmif\u00e9le var\u00e1zslatra, a m\u00e1r ismert eszk\u00f6z\u00f6ket fogjuk erre haszn\u00e1lni: \u00f6r\u00f6kl\u00e9st absztrakt/virtu\u00e1lis f\u00fcggv\u00e9nyekkel, vagy interf\u00e9szeket, vagy delegate-eket.

                                Keress\u00fck meg azokat a r\u00e9szeket, melyek esetf\u00fcgg\u0151, v\u00e1ltoz\u00f3 logik\u00e1k, \u00edgy nem j\u00f3 be\u00e9getni az Anonymizer oszt\u00e1lyba:

                                • Az egyik maga az anonimiz\u00e1l\u00e1si logika: Anonymize_MaskName/Anonymize_AgeRange
                                • A m\u00e1sik a GetAnonymizerDescription

                                Ezeket kell lev\u00e1lasztani az oszt\u00e1lyr\u00f3l, ezekben a pontokban kell kiterjeszthet\u0151v\u00e9 tenni az oszt\u00e1lyt. Az al\u00e1bbi \u00e1bra illusztr\u00e1lja a c\u00e9lt \u00e1ltal\u00e1noss\u00e1g\u00e1ban *:

                                Az \u00e1ltal\u00e1nos megold\u00e1si elv illusztr\u00e1l\u00e1sa

                                A h\u00e1rom konkr\u00e9t tervez\u00e9si mint\u00e1t, ill. technik\u00e1t n\u00e9z\u00fcnk meg a fentiek megval\u00f3s\u00edt\u00e1s\u00e1ra:

                                • Template Method tervez\u00e9si minta
                                • Strategy tervez\u00e9si minta (Dependency Injectionnel egyetemben)
                                • Delegate (opcion\u00e1lisan Lambda kifejez\u00e9ssel)

                                Val\u00f3j\u00e1ban mind haszn\u00e1ltuk m\u00e1r a tanulm\u00e1nyaink sor\u00e1n, de most m\u00e9lyebben megismerked\u00fcnk vel\u00fck, \u00e9s \u00e1tfog\u00f3bban be fogjuk gyakorolni ezek alkalmaz\u00e1s\u00e1t. Az els\u0151 kett\u0151t a labor keret\u00e9ben, a harmadikat pedig majd egy kapcsol\u00f3d\u00f3 h\u00e1zi feladat keret\u00e9ben.

                                "},{"location":"labor/6-tervezesi-mintak/#4-megoldas-3-templatemethodtemplatemethod-1","title":"4. Megold\u00e1s (3-TemplateMethod/TemplateMethod-1)","text":"

                                Ebben a l\u00e9p\u00e9sben a Template Method tervez\u00e9si minta alkalmaz\u00e1s\u00e1val fogjuk a megold\u00e1sunkat a sz\u00fcks\u00e9ges pontokban kiterjeszthet\u0151v\u00e9 tenni.

                                Note

                                A minta neve \"megt\u00e9veszt\u0151\": semmi k\u00f6ze nincs a C++-ban tanult sablonmet\u00f3dusokhoz!

                                Template Method alap\u00fa megold\u00e1s oszt\u00e1lydiagram

                                Az al\u00e1bbi UML oszt\u00e1lydiagram illusztr\u00e1lja a Template Method alap\u00fa megold\u00e1st, a l\u00e9nyegre f\u00f3kusz\u00e1lva:

                                A mint\u00e1ban a k\u00f6vetkez\u0151 elvek ment\u00e9n val\u00f3sul meg a \"v\u00e1ltozatlan\" \u00e9s \"v\u00e1ltoz\u00f3\" r\u00e9szek k\u00fcl\u00f6nv\u00e1laszt\u00e1sa (\u00e9rdemes a fenti oszt\u00e1lydiagram alapj\u00e1n - a p\u00e9ld\u00e1nkra vet\u00edtve - ezeket meg\u00e9rteni):

                                • A \"k\u00f6z\u00f6s/v\u00e1ltozatlan\" r\u00e9szeket egy \u0151soszt\u00e1lyba tessz\u00fck.
                                • Ebben a kiterjeszt\u00e9si pontokat absztrakt/virtu\u00e1lis f\u00fcggv\u00e9nyek bevezet\u00e9se jelenti, ezeket h\u00edvjuk a kiterjeszt\u00e9si pontokban.
                                • Ezek esetf\u00fcgg\u0151 megval\u00f3s\u00edt\u00e1sa a lesz\u00e1rmazott oszt\u00e1lyokba ker\u00fcl.

                                A j\u00f3l ismert \"tr\u00fckk\" a dologban az, hogy amikor az \u0151s megh\u00edvja az absztrakt/virtu\u00e1lis f\u00fcggv\u00e9nyeket, akkor a lesz\u00e1rmazottb\u00e9li, esetf\u00fcgg\u0151 k\u00f3d h\u00edv\u00f3dik meg.

                                A k\u00f6vetkez\u0151kben a kor\u00e1bbi enum, illetve if/switch alap\u00fa megold\u00e1st alak\u00edtjuk \u00e1t Template Method alap\u00fara (ebben m\u00e1r nem lesz enum). Egy \u0151soszt\u00e1lyt \u00e9s k\u00e9t, algoritmusf\u00fcgg\u0151 lesz\u00e1rmazottat vezet\u00fcnk be.

                                Alak\u00edtsuk \u00e1t a k\u00f3dunkat ennek megfelel\u0151en. A VS solution-ben a \"3-TemplateMethod\" mapp\u00e1ban a \"TemplateMethod-0-Begin\" projekt tartalmazza a kor\u00e1bbi megold\u00e1sunk k\u00f3dj\u00e1t (annak \"m\u00e1solat\u00e1t\"), ebben a projektben dolgozzunk:

                                1. Nevezz\u00fck \u00e1t az Anonymizer oszt\u00e1lyt AnonymizerBase-re (pl. az oszt\u00e1ly nev\u00e9re \u00e1llva a forr\u00e1sf\u00e1jlban \u00e9s F2-t nyomva).
                                2. Vegy\u00fcnk fel az projektbe egy NameMaskingAnonymizer \u00e9s egy AgeAnonymizer oszt\u00e1lyt (projekten jobb katt, Add/Class).
                                3. Sz\u00e1rmaztassuk az AnonymizerBase-b\u0151l \u0151ket
                                4. Az AnonymizerBase-b\u0151l mozgassuk \u00e1t a NameMaskingAnonymizer-be az ide tartoz\u00f3 r\u00e9szeket:

                                  1. A _mask tagv\u00e1ltoz\u00f3t.
                                  2. A string inputFileName, string mask param\u00e9terez\u00e9s\u0171 konstruktort, \u00e1tnevezve NameMaskingAnonymizer-re,
                                    1. _anonymizerMode = AnonymizerMode.Name; sort t\u00f6r\u00f6lve,
                                    2. a this konstruktorh\u00edv\u00e1s helyett base konstruktorh\u00edv\u00e1ssal.

                                      A konstruktor k\u00f3dja
                                      public NameMaskingAnonymizer(string inputFileName, string mask): base(inputFileName)\n{\n    _mask = mask;\n}\n
                                5. Az AnonymizerBase-b\u0151l mozgassuk \u00e1t az AgeAnonymizer-be az ide tartoz\u00f3 r\u00e9szeket:

                                  1. A _rangeSize tagv\u00e1ltoz\u00f3t.
                                  2. A string inputFileName, string rangeSize param\u00e9terez\u00e9s\u0171 konstruktort, \u00e1tnevezve AgeAnonymizer-re,
                                    1. _anonymizerMode = AnonymizerMode.Age; sort t\u00f6r\u00f6lve,
                                    2. a this konstruktorh\u00edv\u00e1s helyett base konstruktorh\u00edv\u00e1ssal.

                                      A konstruktor k\u00f3dja
                                      public AgeAnonymizer(string inputFileName, int rangeSize): base(inputFileName)\n{\n    _rangeSize = rangeSize;\n}\n
                                6. Az AnonymizerBase-ben:

                                  1. T\u00f6r\u00f6lj\u00fck az AnonymizerMode enum t\u00edpust.
                                  2. T\u00f6r\u00f6lj\u00fck a _anonymizerMode tagot.

                                Keress\u00fck meg azokat a r\u00e9szeket, melyek esetf\u00fcgg\u0151, v\u00e1ltoz\u00f3 logik\u00e1k, \u00edgy nem akarjuk be\u00e9getni az \u00fajrafelhaszn\u00e1lhat\u00f3nak sz\u00e1nt AnonymizerBase oszt\u00e1lyba:

                                • Az egyik az Anonymize_MaskName/Anonymize_AgeRange,
                                • a m\u00e1sik a GetAnonymizerDescription.

                                A mint\u00e1t k\u00f6vetve ezekre az \u0151sben absztrakt (vagy esetleg virtu\u00e1lis) f\u00fcggv\u00e9nyeket vezet\u00fcnk be, \u00e9s ezeket h\u00edvjuk, az esetf\u00fcgg\u0151 implement\u00e1ci\u00f3ikat pedig a lesz\u00e1rmazott oszt\u00e1lyokba tessz\u00fck (override):

                                1. Tegy\u00fck az AnonymizerBase oszt\u00e1lyt absztraktt\u00e1 (a class el\u00e9 abstract kulcssz\u00f3).
                                2. Vezess\u00fcnk be az AnonymizerBase-ben egy

                                  protected abstract Person Anonymize(Person person);\n

                                  m\u0171veletet (ennek feladata lesz az anonimiz\u00e1l\u00e1s v\u00e9grehajt\u00e1sa).

                                3. Az Anonymize_MaskName m\u0171veletet mozgassuk \u00e1t a NameMaskingAnonymizer oszt\u00e1lyba, \u00e9s alak\u00edtsuk \u00e1t a szignat\u00far\u00e1j\u00e1t \u00fagy, hogy override-olja az \u0151sbeli Anonymize absztrakt f\u00fcggv\u00e9nyt:

                                  protected override Person Anonymize(Person person)\n{\n    return new Person(_mask, _mask, person.CompanyName,\n        person.Address, person.City, person.State, person.Age, person.Weight, person.Decease);\n}\n

                                  A f\u00fcggv\u00e9ny t\u00f6rzs\u00e9t csak annyiban kell \u00e1t\u00edrni, hogy ne a megsz\u00fcntetett mask param\u00e9tert, hanem a _mask tagv\u00e1ltoz\u00f3t haszn\u00e1lja.

                                4. Az el\u0151z\u0151 l\u00e9p\u00e9ssel teljesen anal\u00f3g m\u00f3don az Anonymize_AgeRange m\u0171veletet mozgassuk \u00e1t a AgeAnonymizer oszt\u00e1lyba, \u00e9s alak\u00edtsuk \u00e1t a szignat\u00far\u00e1j\u00e1t \u00fagy, hogy override-olja az \u0151sbeli Anonymize absztrakt f\u00fcggv\u00e9nyt:

                                  protected override Person Anonymize(Person person)\n{\n    ...\n}\n

                                  A f\u00fcggv\u00e9ny t\u00f6rzs\u00e9t csak annyiban kell \u00e1t\u00edrni, hogy ne a megsz\u00fcntetett rangeSize param\u00e9tert, hanem a _rangeSize tagv\u00e1ltoz\u00f3t haszn\u00e1lja.

                                5. A AnonymizerBase oszt\u00e1ly Run f\u00fcggv\u00e9ny\u00e9ben az if/else kifejez\u00e9sben tal\u00e1lhat\u00f3 Anonymize h\u00edv\u00e1sokat most m\u00e1r le tudjuk cser\u00e9lni egy egyszer\u0171 absztrakt f\u00fcggv\u00e9ny h\u00edv\u00e1sra:

                                  Person person;\nif (_anonymizerMode == AnonymizerMode.Name)\n    person = Anonymize_MaskName(persons[i], _mask);\nelse if (_anonymizerMode == AnonymizerMode.Age)\n    person = Anonymize_AgeRange(persons[i], _rangeSize);\nelse\n    throw new NotSupportedException(\"The requested anonymization mode is not supported.\");\n

                                  helyett:

                                  var person = Anonymize(persons[i]);\n

                                Az egyik kiterjeszt\u00e9si pontunkkal el is k\u00e9sz\u00fclt\u00fcnk. De maradt m\u00e9g egy, a GetAnonymizerDescription, mely kezel\u00e9se szint\u00e9n esetf\u00fcgg\u0151. Ennek \u00e1talak\u00edt\u00e1sa nagyon hasonl\u00f3 az el\u0151z\u0151 l\u00e9p\u00e9ssorozathoz:

                                1. Az AnonymizerBase oszt\u00e1ly GetAnonymizerDescription m\u0171velet\u00e9t m\u00e1soljuk \u00e1t a NameMaskingAnonymizer-be, a szignat\u00far\u00e1ba belev\u00e9ve az override kulcssz\u00f3t, a f\u00fcggv\u00e9ny t\u00f6rzs\u00e9ben csak a NameMaskingAnonymizer-re vonatkoz\u00f3 logik\u00e1t meghagyva:

                                  protected override string GetAnonymizerDescription()\n{\n    return $\"NameMasking anonymizer with mask {_mask}\";\n}\n
                                2. A AnonymizerBase GetAnonymizerDescription m\u0171velet\u00e9t m\u00e1soljuk \u00e1t az AgeAnonymizer-be is, a szignat\u00far\u00e1ba belev\u00e9ve az override kulcssz\u00f3t, a f\u00fcggv\u00e9ny t\u00f6rzs\u00e9ben most csak a AgeAnonymizer-re vonatkoz\u00f3 logik\u00e1t meghagyva:

                                  protected override string GetAnonymizerDescription()\n{\n    return $\"Age anonymizer with range size {_rangeSize}\";\n}\n
                                3. K\u00e9rd\u00e9s, mi legyen AnonymizerBase-ben a GetAnonymizerDescription m\u0171velettel. Ezt nem absztrakt\u00e1, hanem virtu\u00e1lis f\u00fcggv\u00e9nny\u00e9 alak\u00edtjuk, hiszen itt tudunk \u00e9rtelmes alap\u00e9rtelmezett viselked\u00e9st biztos\u00edtani: egyszer\u0171en visszaadjuk az oszt\u00e1ly nev\u00e9t (mely pl. a NameMaskingAnonymizer oszt\u00e1ly eset\u00e9ben \"NameMaskingAnonymizer\" lenne). Mindenesetre a rugalmatlan switch szerkezett\u0151l ezzel megszabadulunk:

                                  protected virtual string GetAnonymizerDescription()\n{\n    return GetType().Name;\n}\n

                                  Reflexi\u00f3

                                  Az object \u0151sb\u0151l \u00f6r\u00f6k\u00f6lt GetType() m\u0171velettel egy Type t\u00edp\u00fas\u00fa objektumot szerz\u00fcnk az oszt\u00e1lyunkra vonatkoz\u00f3an. Ez a refelexi\u00f3 t\u00e9mak\u00f6rh\u00f6z tartozik, err\u0151l a f\u00e9l\u00e9v v\u00e9g\u00e9n fogunk el\u0151ad\u00e1son r\u00e9szletesebben tanulni.

                                Egy dolog van m\u00e1r csak h\u00e1tra: a Program.cs Main f\u00fcggv\u00e9ny\u00e9ben most az AnonymizerBase \u0151st pr\u00f3b\u00e1ljuk p\u00e9ld\u00e1nyos\u00edtani (a kor\u00e1bbi \u00e1tnevez\u00e9s miatt). Helyette a k\u00e9t lesz\u00e1rmazott valamelyik\u00e9t kellene. Pl.:

                                NameMaskingAnonymizer anonymizer = new(\"us-500.csv\", \"***\");\nanonymizer.Run();\n

                                El is k\u00e9sz\u00fclt\u00fcnk. Pr\u00f3b\u00e1ljuk ki, hogy jobban \"\u00e9rezz\u00fck\", val\u00f3ban m\u0171k\u00f6dnek az kiterjeszt\u00e9si pontok (de ha kev\u00e9s az id\u0151nk a labor sor\u00e1n, ez k\u00fcl\u00f6n\u00f6sebben nem fontos, hasonl\u00f3t m\u00e1r kor\u00e1bbi f\u00e9l\u00e9vekben C++/Java nyelvek kontextus\u00e1ban is csin\u00e1ltunk):

                                • Visual Studioban a TemplateMethod-0-Begin projekt legyen a startup projekt, ha ezt eddig m\u00e9g nem \u00e1ll\u00edtottuk be.
                                • Tegy\u00fcnk egy t\u00f6r\u00e9spontot az AnonymizerBase oszt\u00e1ly var person = Anonymize(persons[i]); sor\u00e1ra.
                                • Amikor fut\u00e1s k\u00f6zben itt meg\u00e1ll a debugger, F11-gyel l\u00e9pj\u00fcnk bele.
                                • Az tapasztaljuk, hogy a lesz\u00e1rmazott AgeAnonymizer m\u0171velete h\u00edv\u00f3dik.

                                Vethet\u00fcnk egy pillant\u00e1st a megold\u00e1s oszt\u00e1lydiagramj\u00e1ra:

                                Template Method alap\u00fa megold\u00e1s oszt\u00e1lydiagram *

                                Az eddigi munk\u00e1nk megold\u00e1sa a 3-TemplateMethod/TemplateMethod-1 projektben megtal\u00e1lhat\u00f3, ha esetleg sz\u00fcks\u00e9g lenne r\u00e1.

                                Mi\u00e9rt Template Method a minta neve *

                                A minta az\u00e9rt kapta a Template Method nevet, mert - alkalmaz\u00e1sunkat p\u00e9ldak\u00e9nt haszn\u00e1lva - a Run \u00e9s a PrintSummary olyan \"sablon met\u00f3dusok\", melyek meghat\u00e1roznak egy sablonszer\u0171 logik\u00e1t, v\u00e1zat, melyben bizonyos l\u00e9p\u00e9sek nincsenek megk\u00f6tve. Ezek \"k\u00f3dj\u00e1t\" absztrakt/virtu\u00e1lis f\u00fcggv\u00e9nyekre b\u00edzzuk, \u00e9s a lesz\u00e1rmazott oszt\u00e1lyok hat\u00e1rozz\u00e1k meg a megval\u00f3s\u00edt\u00e1sukat.

                                "},{"location":"labor/6-tervezesi-mintak/#a-megoldas-ertekelese_2","title":"A megold\u00e1s \u00e9rt\u00e9kel\u00e9se","text":"

                                Ellen\u0151rizz\u00fck a megold\u00e1st, megval\u00f3s\u00edtja-e a c\u00e9ljainkat:

                                • Az AnonymizerBase egy \u00fajrafelhaszn\u00e1lhat\u00f3(bb) oszt\u00e1ly lett.
                                • Ha \u00faj anonimiz\u00e1l\u00f3 logik\u00e1ra van sz\u00fcks\u00e9g a j\u00f6v\u0151ben, csak sz\u00e1rmaztatunk bel\u0151le. Ez nem m\u00f3dos\u00edt\u00e1s, hanem b\u0151v\u00edt\u00e9s.
                                • Ennek megfelel\u0151en teljes\u00fcl az OPEN/CLOSED elv, vagyis a k\u00f3dj\u00e1nak m\u00f3dos\u00edt\u00e1sa n\u00e9lk\u00fcl tudjuk az \u0151sben megadott k\u00e9t pontban a logik\u00e1t testre szabni, kiterjeszteni.

                                Legyen minden pontban kiterjeszthet\u0151 az oszt\u00e1lyunk?

                                Figyelj\u00fck meg, hogy nem tett\u00fcnk az AnonymizerBase minden m\u0171velet\u00e9t virtu\u00e1liss\u00e1 (\u00edgy sok pontban kiterjeszthet\u0151v\u00e9 az oszt\u00e1lyt). Csak ott tett\u00fck meg, ahol azt gondoljuk, hogy a j\u00f6v\u0151ben sz\u00fcks\u00e9g lehet a logika kiterjeszt\u00e9s\u00e9re.

                                "},{"location":"labor/6-tervezesi-mintak/#5-megoldas-3-templatemethodtemplatemethod-2-progress","title":"5. Megold\u00e1s (3-TemplateMethod/TemplateMethod-2-Progress)","text":"

                                T.f.h \u00faj - viszonylag egyszer\u0171 - ig\u00e9ny mer\u00fcl fel:

                                • A NameMaskinAnonimizer eset\u00e9n marad ugyan a kor\u00e1bbi egyszer\u0171 progress kijelz\u00e9s (minden sor ut\u00e1n ki\u00edrjuk, h\u00e1nyadikn\u00e1l tartottunk),

                                  Egyszer\u0171 progress illusztr\u00e1l\u00e1sa

                                • de az AgeAnonymizer eset\u00e9n a progress kijelz\u00e9s m\u00e1s kell legyen: azt kell ki\u00edrni - minden sor ut\u00e1n friss\u00edtve -, hogy h\u00e1ny sz\u00e1zal\u00e9kn\u00e1l tart a feldolgoz\u00e1s.

                                  Sz\u00e1zal\u00e9kos progress illusztr\u00e1l\u00e1sa

                                  (Mivel jelenleg kev\u00e9s az adatunk (mind\u00f6ssze 500 sor), ezt a megold\u00e1sunk v\u00e9g\u00e9n nem \u00edgy l\u00e1tjuk majd, pillanatok alatt 100%-ra ugrik)

                                A megold\u00e1s nagyon egyszer\u0171: a Run m\u0171veletben sz\u00e9lesebb k\u00f6rben alkalmazva a Template Method mint\u00e1t, a progress ki\u00edr\u00e1skor is egy kiterjeszt\u00e9si pontot vezet\u00fcnk be, egy virtu\u00e1lis f\u00fcggv\u00e9nyre b\u00edzzuk a megval\u00f3s\u00edt\u00e1st.

                                Ugorjunk egyb\u0151l a k\u00e9sz megold\u00e1sra (3-TemplateMethod/TemplateMethod-2-Progress projekt):

                                • AnonymizerBase oszt\u00e1lyban \u00faj PrintProgress virtu\u00e1lis f\u00fcggv\u00e9ny (alap\u00e9rtelmez\u00e9sben nem \u00edr ki semmit)
                                • Run-ban ennek h\u00edv\u00e1sa
                                • NameMaskingAnonymizer-ben \u00e9s NameMaskingAnonymizer-ben megfelel\u0151 megval\u00f3s\u00edt\u00e1s (override)

                                Ennek egyel\u0151re k\u00fcl\u00f6n\u00f6sebb tanuls\u00e1ga nincs, de a k\u00f6vetkez\u0151 l\u00e9p\u00e9sben m\u00e1r lesz.

                                "},{"location":"labor/6-tervezesi-mintak/#6-megoldas-3-templatemethodtemplatemethod-3-progressmultiple","title":"6. Megold\u00e1s (3-TemplateMethod/TemplateMethod-3-ProgressMultiple)","text":"

                                \u00daj - \u00e9s teljesen logikus - ig\u00e9ny mer\u00fclt fel: a j\u00f6v\u0151ben b\u00e1rmely anonimiz\u00e1l\u00f3 algoritmust b\u00e1rmely progress megjelen\u00edt\u00e9ssel lehessen haszn\u00e1lni. Ez jelen pillanatban n\u00e9gy keresztkombin\u00e1ci\u00f3t jelent:

                                Anonimiz\u00e1l\u00f3 Progress N\u00e9v anonimiz\u00e1l\u00f3 Egyszer\u0171 progress N\u00e9v anonimiz\u00e1l\u00f3 Sz\u00e1zal\u00e9k progress Kor anonimiz\u00e1l\u00f3 Egyszer\u0171 progress Kor anonimiz\u00e1l\u00f3 Sz\u00e1zal\u00e9k progress

                                Ugorjunk a k\u00e9sz megold\u00e1sra (3-TemplateMethod/TemplateMethod-3-ProgressMultiple projekt). K\u00f3d helyett a Main.cd oszt\u00e1lydiagramot nyissuk meg a projektben, \u00e9s a megold\u00e1st az alapj\u00e1n tekintj\u00fck \u00e1t (vagy n\u00e9zhetj\u00fck a diagramot al\u00e1bb az \u00fatmutat\u00f3ban).

                                Template Method alap\u00fa megold\u00e1s (k\u00e9t aspektus) oszt\u00e1lydiagram

                                \u00c9rezhet\u0151, hogy valami \"baj van\", minden keresztkombin\u00e1ci\u00f3nak k\u00fcl\u00f6n lesz\u00e1rmazottat kellett l\u00e9trehozni. S\u0151t, a k\u00f3dduplik\u00e1ci\u00f3 cs\u00f6kkent\u00e9s\u00e9re m\u00e9g plusz, k\u00f6ztes oszt\u00e1lyok is vannak a hierarchi\u00e1ban. R\u00e1ad\u00e1sul:

                                • Ha a j\u00f6v\u0151ben \u00faj anonimiz\u00e1l\u00f3 algoritmust vezet\u00fcnk be, annyi \u00faj oszt\u00e1lyt kell \u00edrni (legal\u00e1bb), ah\u00e1ny progress t\u00edpust t\u00e1mogatunk.
                                • Ha a j\u00f6v\u0151ben \u00faj progress t\u00edpust vezet\u00fcnk be, annyi \u00faj oszt\u00e1lyt kell \u00edrni (legal\u00e1bb), ah\u00e1ny anonimiz\u00e1l\u00f3 t\u00edpust t\u00e1mogatunk.

                                Mi okozta a probl\u00e9m\u00e1t? Az, hogy az oszt\u00e1lyunk viselked\u00e9s\u00e9t t\u00f6bb aspektus/dimenzi\u00f3 ment\u00e9n (p\u00e9ld\u00e1nkban az anonimiz\u00e1l\u00e1s \u00e9s progress) kell kiterjeszthet\u0151v\u00e9 tenni, \u00e9s ezeket sok keresztkombin\u00e1ci\u00f3ban kell t\u00e1mogatni. Ha \u00fajabb aspektusok ment\u00e9n kellene ezt megtenni (pl. beolvas\u00e1s m\u00f3dja, kimenet gener\u00e1l\u00e1sa), akkor a probl\u00e9ma exponenci\u00e1lisan tov\u00e1bb \"robbanna\". Ilyen esetekben a Template Method tervez\u00e9si minta nem alkalmazhat\u00f3.

                                "},{"location":"labor/6-tervezesi-mintak/#7-megoldas-4-strategystrategy-1","title":"7. Megold\u00e1s (4-Strategy/Strategy-1)","text":"

                                Ebben a l\u00e9p\u00e9sben a Strategy tervez\u00e9si minta alkalmaz\u00e1s\u00e1val fogjuk a kezdeti megold\u00e1sunkat a sz\u00fcks\u00e9ges pontokban kiterjeszthet\u0151v\u00e9 tenni. A mint\u00e1ban a k\u00f6vetkez\u0151 elvek ment\u00e9n val\u00f3sul meg a \"v\u00e1ltozatlan/\u00fajrafelhaszn\u00e1lhat\u00f3\" \u00e9s \"v\u00e1ltoz\u00f3\" r\u00e9szek k\u00fcl\u00f6nv\u00e1laszt\u00e1sa:

                                • A \"k\u00f6z\u00f6s/v\u00e1ltozatlan\" r\u00e9szeket egy adott oszt\u00e1lyba tessz\u00fck (de ez most nem egy \"\u0151soszt\u00e1ly\" lesz).
                                • A Template Methoddal szemben nem \u00f6r\u00f6kl\u00e9st, hanem kompoz\u00edci\u00f3t (tartalmaz\u00e1st) alkalmazunk: interf\u00e9szk\u00e9nt tartalmazott m\u00e1s objektumokra b\u00edzzuk a viselked\u00e9s megval\u00f3s\u00edt\u00e1s\u00e1t a kiterjeszt\u00e9si pontokban (\u00e9s nem absztrakt/virtu\u00e1lis f\u00fcggv\u00e9nyekre).
                                • Mindezt az oszt\u00e1ly viselked\u00e9s\u00e9nek minden olyan aspektus\u00e1ra/dimenzi\u00f3j\u00e1ra, melyet lecser\u00e9lhet\u0151v\u00e9/b\u0151v\u00edthet\u0151v\u00e9 szeretn\u00e9nk tenni, egym\u00e1st\u00f3l f\u00fcggetlen\u00fcl megtessz\u00fck. Mint l\u00e1tni fogjuk, ezzel az el\u0151z\u0151 fejezetben tapasztalt kombinatorikus robban\u00e1s elker\u00fclhet\u0151.

                                Ez sokkal egyszer\u0171bb a gyakorlatban, mint amilyennel le\u00edrva \u00e9rz\u0151dik (m\u00e1r haszn\u00e1ltuk is p\u00e1rszor kor\u00e1bbi tanulm\u00e1nyaink sor\u00e1n). \u00c9rts\u00fck meg a p\u00e9ld\u00e1nkra vet\u00edtve.

                                A k\u00f6vetkez\u0151kben tekints\u00fck \u00e1t a Strategy alap\u00fa megold\u00e1st illusztr\u00e1l\u00f3 oszt\u00e1lydiagramot (a diagramot k\u00f6vet\u0151 magyar\u00e1zatra \u00e9p\u00edtve).

                                Strategy alap\u00fa megold\u00e1s oszt\u00e1lydiagram

                                Az al\u00e1bbi UML oszt\u00e1lydiagram illusztr\u00e1lja a Strategy alap\u00fa megold\u00e1st, a l\u00e9nyegre f\u00f3kusz\u00e1lva:

                                A Strategy minta alkalmaz\u00e1s\u00e1nak els\u0151 l\u00e9p\u00e9se, hogy meghat\u00e1rozzuk, az oszt\u00e1ly viselked\u00e9s\u00e9nek h\u00e1ny k\u00fcl\u00f6nb\u00f6z\u0151 aspektusa van, melyet kiterjeszthet\u0151v\u00e9 szeretn\u00e9nk tenni. A p\u00e9ld\u00e1nkban ebb\u0151l - egyel\u0151re legal\u00e1bbis - kett\u0151 van:

                                • Anonimiz\u00e1l\u00e1shoz k\u00f6t\u0151d\u0151 viselked\u00e9s, melyhez k\u00e9t m\u0171velet tartozik:
                                  • Anonimiz\u00e1l\u00f3 logika
                                  • Anonimiz\u00e1l\u00f3 logika le\u00edr\u00e1s\u00e1nak meghat\u00e1roz\u00e1sa (description string el\u0151\u00e1ll\u00edt\u00e1sa)
                                • Progress kezel\u00e9s, melyhez egy m\u0171velet tartozik:
                                  • Progress megjelen\u00edt\u00e9se

                                A nehez\u00e9vel meg is vagyunk, ett\u0151l kezdve alapvet\u0151en mechanikusan lehet dolgozni a Strategy mint\u00e1t k\u00f6vetve:

                                1. A fenti aspektusok mindegyik\u00e9hez egy-egy strategy interf\u00e9szt kell bevezetni, a fent meghat\u00e1rozott m\u0171veletekkel, \u00e9s ezekhez el kell k\u00e9sz\u00edteni a megfelel\u0151 implement\u00e1ci\u00f3kat.
                                2. Az Anonymizer oszt\u00e1lyba be kell vezetni egy-egy strategy interf\u00e9sz tagv\u00e1ltoz\u00f3t, \u00e9s a kiterjeszt\u00e9si pontokban ezen tagv\u00e1ltoz\u00f3kon kereszt\u00fcl haszn\u00e1lni az aktu\u00e1lisan be\u00e1ll\u00edtott strategy implement\u00e1ci\u00f3s objektumokat.

                                A fenti oszt\u00e1lydiagramon meg is jelennek ezek az elemek. Most t\u00e9rj\u00fcnk \u00e1t a k\u00f3dra. Kiindul\u00f3 k\u00f6rnyezet\u00fcnk a \"4-Strategy\" mapp\u00e1ban a \"Strategy-0-Begin\" projektben tal\u00e1lhat\u00f3, ebben dolgozzunk. Ez ugyanaz, az enum-ot haszn\u00e1l\u00f3 megold\u00e1s, mint amelyet a Template Method minta eset\u00e9ben is kiindul\u00e1sk\u00e9nt haszn\u00e1ltunk.

                                "},{"location":"labor/6-tervezesi-mintak/#anonimizalasi-strategia","title":"Anonimiz\u00e1l\u00e1si strat\u00e9gia","text":"

                                Az anonimiz\u00e1l\u00e1si strat\u00e9gia/aspektus kezel\u00e9s\u00e9vel kezd\u00fcnk. Vezess\u00fck be az ehhez tartoz\u00f3 interf\u00e9szt:

                                1. Hozzunk l\u00e9tre a projektben egy AnonymizerAlgorithms nev\u0171 mapp\u00e1t (jobb katt a \"Strategy-0-Begin\" projekten, majd Add/New Folder men\u00fc). A k\u00f6vetkez\u0151 l\u00e9p\u00e9sekben minden interf\u00e9szt \u00e9s oszt\u00e1lyt egy k\u00fcl\u00f6n, a nev\u00e9nek megfelel\u0151 forr\u00e1sf\u00e1jlba tegy\u00fcnk a szok\u00e1sos m\u00f3don!
                                2. Vegy\u00fcnk fel ebben a mapp\u00e1ban egy IAnonymizerAlgorithm interf\u00e9szt az al\u00e1bbi k\u00f3ddal:

                                  IAnonymizerAlgorithm.cs
                                  public interface IAnonymizerAlgorithm\n{\n    Person Anonymize(Person person);\n    string GetAnonymizerDescription() => GetType().Name;\n}\n

                                  Azt is megfigyelhetj\u00fck a GetAnonymizerDescription m\u0171velet eset\u00e9ben, hogy a modern C# nyelven, amennyiben akarunk, tudunk az egyes interf\u00e9sz m\u0171veleteknek alap\u00e9rtelmezett implement\u00e1ci\u00f3t adni!

                                Most ennek az interf\u00e9sznek a n\u00e9v anonimiz\u00e1l\u00e1shoz tartoz\u00f3 megval\u00f3s\u00edt\u00e1s\u00e1t k\u00e9sz\u00edtj\u00fck el (vagyis egy strategy implement\u00e1ci\u00f3t k\u00e9sz\u00edt\u00fcnk).

                                1. Vegy\u00fcnk fel egy NameMaskingAnonymizerAlgorithm oszt\u00e1lyt ugyenebbe a mapp\u00e1ba.
                                2. Az Anonymizer oszt\u00e1lyb\u00f3l mozgassuk \u00e1t a NameMaskingAnonymizerAlgorithm-be az ide tartoz\u00f3 _mask tagv\u00e1ltoz\u00f3t:
                                3. A NameMaskingAnonymizerAlgorithm-be vegy\u00fck fel a k\u00f6vetkez\u0151 konstruktort:

                                  public NameMaskingAnonymizerAlgorithm(string mask)\n{\n    _mask = mask;\n}\n
                                4. Val\u00f3s\u00edtsuk meg a IAnonymizerAlgorithm interf\u00e9szt. Miut\u00e1n az oszt\u00e1ly neve ut\u00e1n be\u00edrjuk a : IAnonymizerAlgorithm interf\u00e9szt, c\u00e9lszer\u0171 a m\u0171veletek v\u00e1z\u00e1t a Visual Studioval legener\u00e1ltatni: tegy\u00fck a kurzort a interf\u00e9sz nev\u00e9re (kattintsunk r\u00e1 a forr\u00e1sk\u00f3dban), haszn\u00e1ljuk a 'ctrl' + '.' billenty\u0171kombin\u00e1ci\u00f3t, majd a megjelen\u0151 men\u00fcben \"Implement interface\" kiv\u00e1laszt\u00e1sa. Megjegyz\u00e9s: mivel a GetAnonymizerDescription m\u0171velethez van alap\u00e9rtelmezett implement\u00e1ci\u00f3 az interf\u00e9szben, csak az Anonymize m\u0171velet gener\u00e1l\u00f3dik le, de ez most nek\u00fcnk egyel\u0151re rendben van \u00edgy.

                                5. Az Anonymizer oszt\u00e1lyb\u00f3l vegy\u00fck \u00e1t a Anonymize_MaskName m\u0171velet t\u00f6rzs\u00e9t a NameMaskingAnonymizerAlgorithm.Anonymize-be. A f\u00fcggv\u00e9ny t\u00f6rzs\u00e9t csak annyiban kell \u00e1t\u00edrni, hogy ne a m\u00e1r nem l\u00e9tez\u0151 mask param\u00e9tert, hanem a _mask tagv\u00e1ltoz\u00f3t haszn\u00e1lja. Az Anonymize oszt\u00e1ly Anonymize_MaskName-et pedig t\u00f6r\u00f6lj\u00fck.
                                6. A stategy interf\u00e9sz GetAnonymizerDescriptionm\u0171velet\u00e9nek megval\u00f3s\u00edt\u00e1s\u00e1ra t\u00e9r\u00fcnk most \u00e1t. Az Anonymizer oszt\u00e1ly GetAnonymizerDescription m\u0171velet\u00e9t m\u00e1soljuk \u00e1t a NameMaskingAnonymizerAlgorithm-be, a f\u00fcggv\u00e9ny t\u00f6rzs\u00e9ben csak a n\u00e9v anonimiz\u00e1l\u00f3ra vonatkoz\u00f3 logik\u00e1t meghagyva, a m\u0171veletet publikuss\u00e1 t\u00e9ve:

                                  public string GetAnonymizerDescription()\n{\n    return $\"NameMasking anonymizer with mask {_mask}\";\n}  \n
                                7. Ezzel a n\u00e9v anonimiz\u00e1l\u00e1shoz tartoz\u00f3 strategy implement\u00e1ci\u00f3nk elk\u00e9sz\u00fclt, a teljes k\u00f3dja a k\u00f6vetkez\u0151 lett NameMaskingAnonymizerAlgorithm.cs
                                  public class NameMaskingAnonymizerAlgorithm: IAnonymizerAlgorithm\n{\n    private readonly string _mask;\n\n    public NameMaskingAnonymizerAlgorithm(string mask)\n    {\n        _mask = mask;\n    }\n\n    public Person Anonymize(Person person)\n    {\n        return new Person(_mask, _mask, person.CompanyName,\n            person.Address, person.City, person.State, person.Age, person.Weight, person.Decease);\n    }\n\n    public string GetAnonymizerDescription()\n    {\n        return $\"NameMasking anonymizer with mask {_mask}\";\n    }\n}\n

                                A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben az IAnonymizerAlgorithm strategy interf\u00e9sz\u00fcnk \u00e9letkor anonimiz\u00e1l\u00e1shoz tartoz\u00f3 megval\u00f3s\u00edt\u00e1s\u00e1t k\u00e9sz\u00edtj\u00fck el.

                                1. Vegy\u00fcnk fel egy AgeAnonymizerAlgorithm oszt\u00e1lyt ugyenebbe a mapp\u00e1ba (AnonymizerAlgorithms).
                                2. Az Anonymizer oszt\u00e1lyb\u00f3l mozgassuk \u00e1t a AgeAnonymizerAlgorithm-be az ide tartoz\u00f3 _rangeSize tagv\u00e1ltoz\u00f3t:
                                3. A AgeAnonymizerAlgorithm-be vegy\u00fck fel a k\u00f6vetkez\u0151 konstruktort:

                                  public AgeAnonymizerAlgorithm(int rangeSize)\n{\n    _rangeSize = rangeSize;\n}\n
                                4. Val\u00f3s\u00edtsuk meg a IAnonymizerAlgorithm interf\u00e9szt. Miut\u00e1n az oszt\u00e1ly neve ut\u00e1n be\u00edrjuk a : IAnonymizerAlgorithm interf\u00e9szt, most is c\u00e9lszer\u0171 az Anonymize m\u0171velet v\u00e1z\u00e1t a Visual Studioval a kor\u00e1bbihoz hasonl\u00f3 m\u00f3don legener\u00e1ltatni.

                                5. Az Anonymizer oszt\u00e1lyb\u00f3l vegy\u00fck \u00e1t az Anonymize_AgeRange m\u0171velet t\u00f6rzs\u00e9t a AgeAnonymizerAlgorithm.Anonymize-be. A f\u00fcggv\u00e9ny t\u00f6rzs\u00e9t csak annyiban kell \u00e1t\u00edrni, hogy ne a m\u00e1r nem l\u00e9tez\u0151 rangeSize param\u00e9tert, hanem a _rangeSize tagv\u00e1ltoz\u00f3t haszn\u00e1lja. Az Anonymize oszt\u00e1ly Anonymize_AgeRange-et pedig t\u00f6r\u00f6lj\u00fck.
                                6. A stategy interf\u00e9sz GetAnonymizerDescriptionm\u0171velet\u00e9nek megval\u00f3s\u00edt\u00e1s\u00e1ra t\u00e9r\u00fcnk most \u00e1t. Az Anonymizer oszt\u00e1ly GetAnonymizerDescription m\u0171velet\u00e9t m\u00e1soljuk \u00e1t az AgeAnonymizerAlgorithm-be, a f\u00fcggv\u00e9ny t\u00f6rzs\u00e9ben csak a kor anonimiz\u00e1l\u00f3ra vonatkoz\u00f3 logik\u00e1t meghagyva, a m\u0171veletet publikuss\u00e1 t\u00e9ve:

                                  public string GetAnonymizerDescription()\n{\n    return $\"Age anonymizer with range size {_rangeSize}\";\n} \n
                                7. Ezzel a kor anonimiz\u00e1l\u00e1shoz tartoz\u00f3 strategy implement\u00e1ci\u00f3nk elk\u00e9sz\u00fclt, a teljes k\u00f3dja a k\u00f6vetkez\u0151 lett AgeAnonymizerAlgorithm.cs
                                  public class AgeAnonymizerAlgorithm: IAnonymizerAlgorithm\n{\n    private readonly int _rangeSize;\n\n    public AgeAnonymizerAlgorithm(int rangeSize)\n    {\n        _rangeSize = rangeSize;\n    }\n\n    public Person Anonymize(Person person)\n    {\n        // This is whole number integer arithmetics, e.g for 55 / 20 we get 2\n        int rangeIndex = int.Parse(person.Age) / _rangeSize;\n        string newAge = $\"{rangeIndex * _rangeSize}..{(rangeIndex + 1) * _rangeSize}\";\n\n        return new Person(person.FirstName, person.LastName, person.CompanyName,\n            person.Address, person.City, person.State, newAge,\n            person.Weight, person.Decease);\n    }\n\n    public string GetAnonymizerDescription()\n    {\n        return $\"Age anonymizer with range size {_rangeSize}\";\n    }\n}\n

                                Mindenk\u00e9ppen figyelj\u00fck meg, hogy az interf\u00e9sz \u00e9s a megval\u00f3s\u00edt\u00e1sai kiz\u00e1r\u00f3lag az anonimiz\u00e1l\u00e1ssal foglalkoznak, semmif\u00e9le m\u00e1s logika (pl. progress kezel\u00e9s) nincs itt!

                                "},{"location":"labor/6-tervezesi-mintak/#progress-strategia","title":"Progress strat\u00e9gia","text":"

                                A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben vezess\u00fck be a progress kezel\u00e9shez tartoz\u00f3 interf\u00e9szt \u00e9s implement\u00e1ci\u00f3kat:

                                1. Hozzunk l\u00e9tre a projektben egy Progresses nev\u0171 mapp\u00e1t. A k\u00f6vetkez\u0151 l\u00e9p\u00e9sekben minden interf\u00e9szt \u00e9s oszt\u00e1lyt egy k\u00fcl\u00f6n, a nev\u00e9nek megfelel\u0151 forr\u00e1sf\u00e1jlba tegy\u00fcnk a szok\u00e1sos m\u00f3don.
                                2. Vegy\u00fcnk fel ebben a mapp\u00e1ban egy IProgress interf\u00e9szt az al\u00e1bbi k\u00f3ddal:

                                  Megold\u00e1s IProgress.cs
                                  public interface IProgress\n{\n    void Report(int count, int index);\n}\n
                                3. Vegy\u00fck fel ennek az interf\u00e9sznek az egyszer\u0171 progresshez tartoz\u00f3 megval\u00f3s\u00edt\u00e1s\u00e1t ugyanebbe a mapp\u00e1ba. Az implement\u00e1ci\u00f3 az Anonymizer oszt\u00e1lyunk PrintProgress m\u0171velet\u00e9b\u0151l lett \"levezetve\":

                                  Megold\u00e1s SimpleProgress.cs
                                  public class SimpleProgress: IProgress\n{\n    public void Report(int count, int index)\n    {\n        Console.WriteLine($\"{index + 1}. person processed\");\n    }\n}\n
                                4. Vegy\u00fck fel ennek az interf\u00e9sznek a sz\u00e1zal\u00e9kos progresshez tartoz\u00f3 megval\u00f3s\u00edt\u00e1s\u00e1t ugyanebbe a mapp\u00e1ba. A k\u00f3d \u00e9rtelmez\u00e9s\u00e9vel ne foglalkozzunk. Erre megold\u00e1s az Anonymizer oszt\u00e1lyunkban nincs, hiszen ezt csak a template method alap\u00fa megold\u00e1sunkn\u00e1l vezett\u00fck be (ott nem n\u00e9zt\u00fck a k\u00f3dj\u00e1t, de azzal gyakorlatilag megegyezik a l\u00e9nyege):

                                  Megold\u00e1s PercentProgress.cs
                                  public class PercentProgress: IProgress\n{\n    public void Report(int count, int index)\n    {\n        int percentage = (int)((double)(index+1) / count * 100);\n\n        var pos = Console.GetCursorPosition();\n        Console.SetCursorPosition(0, pos.Top);\n\n        Console.Write($\"Processing: {percentage} %\");\n\n        if (index == count - 1)\n            Console.WriteLine();\n    }\n}\n

                                Mindenk\u00e9ppen figyelj\u00fck meg, hogy az interf\u00e9sz \u00e9s a megval\u00f3s\u00edt\u00e1sai kiz\u00e1r\u00f3lag a progress kezel\u00e9ssel foglalkoznak, semmif\u00e9le m\u00e1s logika (pl. anonimiz\u00e1l\u00e1s) nincs itt!

                                "},{"location":"labor/6-tervezesi-mintak/#a-strategiak-alkalmazasa","title":"A strat\u00e9gi\u00e1k alkalmaz\u00e1sa","text":"

                                A k\u00f6vetkez\u0151 fontos l\u00e9p\u00e9s az anonimiz\u00e1l\u00f3 alaposzt\u00e1ly \u00fajrafelhaszn\u00e1lhat\u00f3v\u00e1 \u00e9s kiterjeszthet\u0151v\u00e9 t\u00e9tele a fent bevezetett strategy-k seg\u00edts\u00e9g\u00e9vel. Az Anonymizer.cs f\u00e1jlban:

                                1. T\u00f6r\u00f6lj\u00fck a k\u00f6vetkez\u0151ket:

                                  • AnonymizerMode enum t\u00edpus
                                  • _anonymizerMode tag (illetve a _mask \u00e9s _rangeSize tagok, ha esetleg itt maradtak kor\u00e1bban)
                                2. Vezess\u00fcnk be egy-egy strategy interf\u00e9sz t\u00edpus\u00fa tagot:

                                  private readonly IProgress _progress;\nprivate readonly IAnonymizerAlgorithm _anonymizerAlgorithm;\n
                                3. A f\u00e1jl elej\u00e9re sz\u00farjunk be a megfelel\u0151 usingokat:

                                  using Lab_Extensibility.AnonymizerAlgorithms;\nusing Lab_Extensibility.Progresses;\n
                                4. Az el\u0151z\u0151 pontban bevezetett _progress \u00e9s _anonymizerAlgorithm kezd\u0151\u00e9rt\u00e9ke null, a konstruktorban \u00e1ll\u00edtsuk ezeket a referenci\u00e1kat az ig\u00e9nyeinknek megfelel\u0151 implement\u00e1ci\u00f3ra. Pl.:

                                  public Anonymizer(string inputFileName, string mask) : this(inputFileName)\n{\n    _progress = new PercentProgress();\n    _anonymizerAlgorithm = new NameMaskingAnonymizerAlgorithm(mask);\n}\n\npublic Anonymizer(string inputFileName, int rangeSize) : this(inputFileName)\n{\n    _progress = new PercentProgress();\n    _anonymizerAlgorithm = new AgeAnonymizerAlgorithm(rangeSize);\n}\n

                                Az Anonymizer oszt\u00e1lyban a jelenleg be\u00e9getett, de anonimiz\u00e1l\u00e1s f\u00fcgg\u0151 logik\u00e1kat b\u00edzzuk a _anonymizerAlgorithm tagv\u00e1ltoz\u00f3 \u00e1ltal hivatkozott strategy implement\u00e1ci\u00f3ra:

                                1. Az oszt\u00e1ly Run f\u00fcggv\u00e9ny\u00e9ben az if/else kifejez\u00e9sben tal\u00e1lhat\u00f3 Anonymize h\u00edv\u00e1sokat most m\u00e1r deleg\u00e1ljuk a _anonymizerAlgorithm objektumnak:

                                  Person person;\nif (_anonymizerMode == AnonymizerMode.Name)\n    person = Anonymize_MaskName(persons[i], _mask);\nelse if (_anonymizerMode == AnonymizerMode.Age)\n    person = Anonymize_AgeRange(persons[i], _rangeSize);\nelse\n    throw new NotSupportedException(\"The requested anonymization mode is not supported.\");\n

                                  helyett:

                                  Person person = _anonymizerAlgorithm.Anonymize(persons[i]);\n
                                2. Ha esetleg kor\u00e1bban nem tett\u00fck meg, t\u00f6r\u00f6lj\u00fck a Anonymize_MaskName \u00e9s Anonymize_AgeRange f\u00fcggv\u00e9nyeket, hiszen ezek k\u00f3dja m\u00e1r a strategy implement\u00e1ci\u00f3kba ker\u00fclt, az oszt\u00e1lyr\u00f3l lev\u00e1lasztva.

                                3. A PrintSummary f\u00fcggv\u00e9ny\u00fcnk a rugalmatlan, switch alapokon m\u0171k\u00f6d\u0151 GetAnonymizerDescription-t h\u00edvja. Ezt a GetAnonymizerDescription h\u00edv\u00e1st cser\u00e9lj\u00fck le, deleg\u00e1ljuk a _anonymizerAlgorithm objektumnak. A PrintSummary f\u00fcggv\u00e9nyben (csak a l\u00e9nyeget kiemelve):

                                      ... GetAnonymizerDescription() ...\n

                                  helyett:

                                      ... _anonymizerAlgorithm.GetAnonymizerDescription() ...\n

                                  P\u00e1r sorral lejjebb a GetAnonymizerDescription f\u00fcggv\u00e9nyt t\u00f6r\u00f6lj\u00fck is az oszt\u00e1lyb\u00f3l (ennek k\u00f3dja megfelel\u0151 strategy implement\u00e1ci\u00f3kba bek\u00fclt).

                                Az utols\u00f3 l\u00e9p\u00e9s az Anonymizer oszt\u00e1lyba be\u00e9getett progress kezel\u00e9s lecser\u00e9l\u00e9se:

                                1. Itt is deleg\u00e1ljuk a k\u00e9r\u00e9st, m\u00e9gpedig a kor\u00e1bban bevezetett _progress objektumunknak. A Run f\u00fcggv\u00e9nyben egy sort kell ehhez lecser\u00e9lni:

                                  PrintProgress(i);\n

                                  helyett:

                                  _progress.Report(persons.Count, i);\n
                                2. T\u00f6r\u00f6lj\u00fck a PrintProgress f\u00fcggv\u00e9nyt, hiszen ennek k\u00f3dja m\u00e1r egy megfelel\u0151 strategy implement\u00e1ci\u00f3ba ker\u00fclt, az oszt\u00e1lyr\u00f3l lev\u00e1lasztva.

                                Elk\u00e9sz\u00fclt\u00fcnk, a k\u00e9sz megold\u00e1s a \"4-Strategy/Strategy-1\" projektben meg is tal\u00e1lhat\u00f3 (ha valahol elakadtunk, vagy nem fordul a k\u00f3d, ezzel \u00f6ssze lehet n\u00e9zni).

                                "},{"location":"labor/6-tervezesi-mintak/#a-megoldas-ertekelese_3","title":"A megold\u00e1s \u00e9rt\u00e9kel\u00e9se","text":"

                                A strategy minta bevezet\u00e9s\u00e9vel elk\u00e9sz\u00fclt\u00fcnk. Jelen form\u00e1j\u00e1ban ugyanakkor szinte soha nem haszn\u00e1ljuk. Ellen\u0151rizz\u00fck a megold\u00e1sunkat: val\u00f3ban \u00fajrafelhaszn\u00e1lhat\u00f3, \u00e9s az Anomymizer oszt\u00e1ly m\u00f3dos\u00edt\u00e1sa n\u00e9lk\u00fcl lehet\u0151s\u00e9g van-e az anonimiz\u00e1l\u00f3 algoritmus, illetve a progress kezel\u00e9s megv\u00e1ltoztat\u00e1s\u00e1ra? Ehhez azt kell megn\u00e9zni, b\u00e1rhol az oszt\u00e1lyban van-e olyan k\u00f3d, mely implement\u00e1ci\u00f3 f\u00fcgg\u0151.

                                Sajnos tal\u00e1lunk ilyet. A konstruktorba be van \u00e9getve, milyen algoritmus implement\u00e1ci\u00f3t \u00e9s progress implement\u00e1ci\u00f3t hozunk l\u00e9tre. Ezt mindenk\u00e9ppen n\u00e9zz\u00fck meg a k\u00f3dban! Ha algoritmus vagy progress m\u00f3dot akarunk v\u00e1ltoztatni, ezekben a sorokban \u00e1t kell \u00edrni a new oper\u00e1tor ut\u00e1ni t\u00edpust, mely \u00edgy az oszt\u00e1ly m\u00f3dos\u00edt\u00e1s\u00e1val j\u00e1r.

                                Sokan - teljesen jogosan - ezt jelen form\u00e1j\u00e1ban nem is tekintik igazi Strategy alap\u00fa megold\u00e1snak. A teljes k\u00f6r\u0171 megold\u00e1st a k\u00f6vetkez\u0151 l\u00e9p\u00e9sben val\u00f3s\u00edtjuk meg.

                                "},{"location":"labor/6-tervezesi-mintak/#8-megoldas-4-strategystrategy-2-di","title":"8. Megold\u00e1s (4-Strategy/Strategy-2-DI)","text":"

                                Dependency Injection (DI) A megold\u00e1st a Dependency Injection (r\u00f6viden DI) alkalmaz\u00e1sa jelenti. Ennek l\u00e9nyege az, hogy nem maga az oszt\u00e1ly p\u00e9ld\u00e1nyos\u00edtja a viselked\u00e9sbeli f\u00fcgg\u0151s\u00e9geit (ezek a strategy implement\u00e1ci\u00f3k), hanem ezeket k\u00edv\u00fclr\u0151l adjuk \u00e1t neki, pl. konstruktor param\u00e9terekben, vagy ak\u00e1r property-k vagy setter m\u0171veletek form\u00e1j\u00e1ban. Term\u00e9szetesen interf\u00e9sz t\u00edpusk\u00e9nt hivatkozva!

                                Alak\u00edtsuk \u00e1t ennek megfelel\u0151en az Anonymizer oszt\u00e1lyt \u00fagy, hogy ne maga p\u00e9ld\u00e1nyos\u00edtsa a strategy implement\u00e1ci\u00f3it, hanem konstruktor param\u00e9terekben kapja meg azokat:

                                1. T\u00f6r\u00f6lj\u00fck mindh\u00e1rom konstruktor\u00e1t
                                2. Vegy\u00fck fel a k\u00f6vetkez\u0151 konstruktort:

                                  public Anonymizer(string inputFileName, IAnonymizerAlgorithm anonymizerAlgorithm, IProgress progress = null)\n{\n    ArgumentException.ThrowIfNullOrEmpty(inputFileName);\n    ArgumentNullException.ThrowIfNull(anonymizerAlgorithm);\n\n    _inputFileName = inputFileName;\n    _anonymizerAlgorithm = anonymizerAlgorithm;\n    _progress = progress;\n}\n

                                  Mint l\u00e1that\u00f3, a progress param\u00e9ter megad\u00e1sa nem k\u00f6telez\u0151, hiszen lehet, hogy az oszt\u00e1ly haszn\u00e1l\u00f3ja nem k\u00edv\u00e1ncsi semmif\u00e9le progress inform\u00e1ci\u00f3ra.

                                3. Mivel a _progress strategy null is lehet, egy null vizsg\u00e1latot be kell vezess\u00fcnk a haszn\u00e1lata sor\u00e1n. A \".\" oper\u00e1tor helyett a \"?.\" oper\u00e1tort haszn\u00e1ljuk:

                                  _progress?.Report(persons.Count,i);\n
                                4. Most m\u00e1r elk\u00e9sz\u00fclt\u00fcnk, az Anonymizer oszt\u00e1ly teljesen f\u00fcggetlen lett a strategy implement\u00e1ci\u00f3kt\u00f3l. Lehet\u0151s\u00e9g\u00fcnk van az Anonymizer oszt\u00e1lyt b\u00e1rmilyen anonimiz\u00e1l\u00f3 algoritmus \u00e9s b\u00e1rmilyen progress kezel\u00e9s kombin\u00e1ci\u00f3val haszn\u00e1lni (annak m\u00f3dos\u00edt\u00e1sa n\u00e9lk\u00fcl). Hozzunk is l\u00e9tre h\u00e1rom Anonymizer k\u00fcl\u00f6nb\u00f6z\u0151 kombin\u00e1ci\u00f3kkal a Program.cs f\u00e1jl Main f\u00fcggv\u00e9ny\u00e9ben (a megl\u00e9v\u0151 k\u00f3dot el\u0151tte t\u00f6r\u00f6lj\u00fck a Main f\u00fcggv\u00e9nyb\u0151l):

                                  Anonymizer p1 = new(\"us-500.csv\",\n    new NameMaskingAnonymizerAlgorithm(\"***\"),\n    new SimpleProgress());\np1.Run();\n\nConsole.WriteLine(\"--------------------\");\n\nAnonymizer p2 = new(\"us-500.csv\",\n    new NameMaskingAnonymizerAlgorithm(\"***\"),\n    new PercentProgress());\np2.Run();\n\nConsole.WriteLine(\"--------------------\");\n\nAnonymizer p3 = new(\"us-500.csv\",\n    new AgeAnonymizerAlgorithm(20),\n    new SimpleProgress());\np3.Run();\n
                                5. Ahhoz, hogy a k\u00f3d foruljon, sz\u00farjuk be a f\u00e1jl elej\u00e9re a sz\u00fcks\u00e9ges using-okat

                                  using Lab_Extensibility.AnonymizerAlgorithms;\nusing Lab_Extensibility.Progresses;\n

                                Elk\u00e9sz\u00fclt\u00fcnk, a k\u00e9sz megold\u00e1s a \"4-Strategy/Strategy-2-DI\" projektben meg is tal\u00e1lhat\u00f3 (ha valahol elakadtunk, vagy nem fordul a k\u00f3d, ezzel \u00f6ssze lehet n\u00e9zni).

                                A m\u0171k\u00f6d\u00e9s ellen\u0151rz\u00e9se

                                A gyakorlat sor\u00e1n erre val\u00f3sz\u00edn\u0171leg nem lesz id\u0151, de aki bizonytalan abban, \"mit\u0151l is m\u0171k\u00f6dik\" a strategy minta, mit\u0151l lesz m\u00e1s a viselked\u00e9s a fenti n\u00e9gy esetre: \u00e9rdemes t\u00f6r\u00e9spontokat tenni a Program.cs f\u00e1jlban a n\u00e9gy Run f\u00fcggv\u00e9nyh\u00edv\u00e1sra, \u00e9s a f\u00fcggv\u00e9nyekbe a debuggerben belel\u00e9pkedve kipr\u00f3b\u00e1lni, hogy mindig a megfelel\u0151 strategy implement\u00e1ci\u00f3 h\u00edv\u00f3dik meg.

                                A projektben tal\u00e1lhat\u00f3 egy oszt\u00e1lydiagram (Main.cd), ezen is megtekinthet\u0151 a k\u00e9sz megold\u00e1s:

                                Strategy alap\u00fa megold\u00e1s oszt\u00e1lydiagram

                                Az al\u00e1bbi UML oszt\u00e1lydiagram illusztr\u00e1lja a Strategy alap\u00fa megold\u00e1sunkat:

                                "},{"location":"labor/6-tervezesi-mintak/#a-megoldas-ertekelese_4","title":"A megold\u00e1s \u00e9rt\u00e9kel\u00e9se","text":"

                                Ellen\u0151rizz\u00fck a megold\u00e1st, megval\u00f3s\u00edtja-e a c\u00e9ljainkat:

                                • Az Anonymizer egy \u00fajrafelhaszn\u00e1lhat\u00f3(bb) oszt\u00e1ly lett.
                                • Ha \u00faj anonimiz\u00e1l\u00f3 logik\u00e1ra van sz\u00fcks\u00e9g a j\u00f6v\u0151ben, csak egy \u00faj IAnonymizerAlgorithm implement\u00e1ci\u00f3t kell bevezetni. Ez nem m\u00f3dos\u00edt\u00e1s, hanem kiterjeszt\u00e9s/b\u0151v\u00edt\u00e9s.
                                • Ha \u00faj progress logik\u00e1ra van sz\u00fcks\u00e9g a j\u00f6v\u0151ben, csak egy \u00faj IProgress implement\u00e1ci\u00f3t kell bevezetni. Ez nem m\u00f3dos\u00edt\u00e1s, hanem b\u0151v\u00edt\u00e9s.
                                • A fenti k\u00e9t pontban teljes\u00fcl az OPEN/CLOSED elv, vagyis az Anonymizer k\u00f3dj\u00e1nak m\u00f3dos\u00edt\u00e1sa n\u00e9lk\u00fcl tudjuk a logik\u00e1j\u00e1t testre szabni, kiterjeszteni.
                                • Itt nem kell tartani a Template Methodn\u00e1l tapasztalt kombinatorikus robban\u00e1st\u00f3l: b\u00e1rmely IAnonymizerAlgorithm implement\u00e1ci\u00f3 b\u00e1rmely IProgress implement\u00e1ci\u00f3val k\u00e9nyelmesen haszn\u00e1lhat\u00f3, nem kell a kombin\u00e1ci\u00f3khoz \u00faj oszt\u00e1lyokat bevezetni (ezt l\u00e1ttuk a Program.cs f\u00e1jlban).

                                Tov\u00e1bbi Strategy el\u0151ny\u00f6k a Template Methoddal szemben *

                                • Fut\u00e1s k\u00f6zben lecser\u00e9lhet\u0151 viselked\u00e9s is megval\u00f3s\u00edthat\u00f3. Ha sz\u00fcks\u00e9g lenne arra, hogy egy adott Anonymizer objektumra vonatkoz\u00f3an a l\u00e9trehoz\u00e1sa ut\u00e1n meg tudjuk v\u00e1ltoztatni az anonimiz\u00e1l\u00f3 vagy progress viselked\u00e9st, akkor azt k\u00f6nnyen meg tudn\u00e1nk tenni (csak egy SetAnonimizerAlgorithm, ill. SetProgress m\u0171veletet kellene bevezetni, melyben a param\u00e9terben megkapott implement\u00e1ci\u00f3ra lehetne \u00e1ll\u00edtani az oszt\u00e1ly \u00e1ltal haszn\u00e1lt strategy-t).
                                • Egys\u00e9gtesztelhet\u0151s\u00e9g t\u00e1mogat\u00e1sa (laboron ezt nem n\u00e9zz\u00fck).
                                "},{"location":"labor/7-adatkezeles/","title":"7. Adatkezel\u00e9s","text":""},{"location":"labor/7-adatkezeles/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                                A gyakorlat c\u00e9lja az ADO.NET programoz\u00e1si modellj\u00e9nek megismer\u00e9se \u00e9s a leggyakoribb adatkezel\u00e9si probl\u00e9m\u00e1k, buktat\u00f3k szeml\u00e9ltet\u00e9se alapvet\u0151 CRUD m\u0171veletek meg\u00edr\u00e1s\u00e1n kereszt\u00fcl.

                                Kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sok: Adatkezel\u00e9s, ADO.NET alapismeretek.

                                "},{"location":"labor/7-adatkezeles/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                                A gyakorlat elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                                • Visual Studio 2022
                                • Windows 10 vagy Windows 11 oper\u00e1ci\u00f3s rendszer
                                • A gyakorlat sor\u00e1n Visual Studio-ban az SQL Server Object Explorer-t fogjuk haszn\u00e1lni az adatb\u00e1zis objektumok k\u00f6z\u00f6tti navig\u00e1l\u00e1s\u00e1ra \u00e9s a lek\u00e9rdez\u00e9sek futtat\u00e1s\u00e1ra. Ehhez sz\u00fcks\u00e9g lehet az SQL Server Data Tools komponensre, melyet legegyszer\u0171bben az Individual Components oldalon tudunk telep\u00edteni a Visual Studio Installer-ben, de a Data Storage and Processing workload is tartalmazza ezt.

                                Gyakorlat Linuxon vagy Macen

                                A gyakorlat anyag alapvet\u0151en Windowsra \u00e9s Visual Studio-ra k\u00e9sz\u00fclt, de - n\u00e9mik\u00e9ppen m\u00e1s \u00faton - elv\u00e9gezhet\u0151 m\u00e1s oper\u00e1ci\u00f3s rendszereken is, mivel a .NET SDK t\u00e1mogatott Linuxon \u00e9s Mac-en is, Linuxon:

                                • Visual Studio helyett, sz\u00f6vegszerkeszt\u0151vel (pl.: VSCode) \u00e9s CLI eszk\u00f6z\u00f6kkel.
                                • Az SQL szervernek van Linuxos v\u00e1ltozata, Mac-en pedig Dockerben futtathat\u00f3 (de Linuxon is tal\u00e1n a Docker legk\u00e9nyelmesebb m\u00f3d a futtat\u00e1s\u00e1ra).
                                • Az adatok vizualiz\u00e1ci\u00f3j\u00e1ra haszn\u00e1lhat\u00f3 a szint\u00e9n keresztplatformos Azure Data Studio eszk\u00f6z.
                                "},{"location":"labor/7-adatkezeles/#megoldas","title":"Megold\u00e1s","text":"A k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se

                                L\u00e9nyeges, hogy a labor sor\u00e1n a laborvezet\u0151t k\u00f6vetve kell dolgozni, tilos (\u00e9s \u00e9rtelmetlen) a k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se. Ugyanakkor az ut\u00f3lagos \u00f6n\u00e1ll\u00f3 gyakorl\u00e1s sor\u00e1n hasznos lehet a k\u00e9sz megold\u00e1s \u00e1ttekint\u00e9se, \u00edgy ezt el\u00e9rhet\u0151v\u00e9 tessz\u00fck.

                                A megold\u00e1s GitHubon \u00e9rhet\u0151 el itt. A legegyszer\u0171bb m\u00f3d a let\u00f6lt\u00e9s\u00e9re, ha parancssorb\u00f3l a git clone utas\u00edt\u00e1ssal lekl\u00f3nozzuk a g\u00e9p\u00fcnkre:

                                git clone https://github.com/bmeviauab00/lab-adatkezeles-megoldas

                                Ehhez telep\u00edtve kell legyen a g\u00e9pre a parancssori git, b\u0151vebb inform\u00e1ci\u00f3 itt.

                                "},{"location":"labor/7-adatkezeles/#bevezeto","title":"Bevezet\u0151","text":"Megjegyz\u00e9s gyakorlatvezet\u0151knek

                                Ezt a fejezetet gyakorlaton nem kell a le\u00edrtaknak megfelel\u0151 r\u00e9szletess\u00e9ggel ismertetni, a fontosabb fogalmakat azonban mindenk\u00e9ppen ismertess\u00fck r\u00f6viden.

                                "},{"location":"labor/7-adatkezeles/#adonet","title":"ADO.NET","text":"

                                Alacsony szint\u0171 adatb\u00e1zis-kezel\u00e9sre a .NET platformon az ADO.NET \u00e1ll rendelkez\u00e9sre, seg\u00edts\u00e9g\u00e9vel rel\u00e1ci\u00f3s adatb\u00e1zisokat tudunk el\u00e9rni.

                                Az ADO.NET haszn\u00e1lata sor\u00e1n k\u00e9t elt\u00e9r\u0151 adathozz\u00e1f\u00e9r\u00e9si modellt alkalmazhatunk:

                                • Kapcsolatalap\u00fa modell
                                • Kapcsolat n\u00e9lk\u00fcli modell

                                Az al\u00e1bbi k\u00e9t blokkot lenyitva \u00e1ttekint\u00e9st kaphatunk a k\u00e9t modell alapelv\u00e9r\u0151l.

                                A Kapcsolatalap\u00fa modell alapelvei

                                L\u00e9nyege az, hogy az adatb\u00e1zis-kapcsolatot v\u00e9gig nyitva tartjuk, am\u00edg az adatokat lek\u00e9rdezz\u00fck, m\u00f3dos\u00edtjuk, majd a v\u00e1ltoztat\u00e1sokat az adatb\u00e1zisba vissza\u00edrjuk. A megold\u00e1sra DataReader objektumokat haszn\u00e1lhatunk (l\u00e1sd k\u00e9s\u0151bb). A megold\u00e1s el\u0151nye az egyszer\u0171s\u00e9g\u00e9ben rejlik (egyszer\u0171bb programoz\u00e1si modell \u00e9s konkurenciakezel\u00e9s). A megold\u00e1s h\u00e1tr\u00e1nya, hogy a folyamatosan fenntartott h\u00e1l\u00f3zati kapcsolat miatt sk\u00e1l\u00e1zhat\u00f3s\u00e1gi probl\u00e9m\u00e1k ad\u00f3dhatnak. Ez azt jelenti, hogy az adatkezel\u0151h\u00f6z t\u00f6rt\u00e9n\u0151 nagysz\u00e1m\u00fa p\u00e1rhuzamos felhaszn\u00e1l\u00f3i hozz\u00e1f\u00e9r\u00e9s eset\u00e9n folyamatosan nagysz\u00e1m\u00fa adatb\u00e1zis kapcsolat \u00e9l, ami adatkezel\u0151 rendszerek eset\u00e9n a teljes\u00edtm\u00e9ny szempontj\u00e1b\u00f3l k\u00f6lts\u00e9ges er\u0151forr\u00e1snak sz\u00e1m\u00edt. \u00cdgy a fejleszt\u00e9s sor\u00e1n c\u00e9lszer\u0171 arra t\u00f6rekedni, hogy az adatb\u00e1zis kapcsolatokat miel\u0151bb z\u00e1rjuk le.

                                A modell el\u0151nyei:

                                • Egyszer\u0171bb a konkurencia kezel\u00e9se
                                • Az adatok mindenhol a legfrissebbek

                                Megjegyz\u00e9s: ezek az el\u0151ny\u00f6k akkor jelentkeznek, ha az adatb\u00e1zis hozz\u00e1f\u00e9r\u00e9shez az adatkezel\u0151 szigor\u00fa z\u00e1rakat haszn\u00e1l \u2013 ezt mi a hozz\u00e1f\u00e9r\u00e9s sor\u00e1n megfelel\u0151 tranzakci\u00f3 izol\u00e1ci\u00f3s szint megad\u00e1s\u00e1val tudjuk szab\u00e1lyozni. (Ennek technik\u00e1i k\u00e9s\u0151bbi tanulm\u00e1nyok sor\u00e1n ker\u00fclnek ismertet\u00e9sre.)

                                H\u00e1tr\u00e1nyok:

                                • Folyamatos h\u00e1l\u00f3zati kapcsolat
                                • Sk\u00e1l\u00e1zhat\u00f3s\u00e1g hi\u00e1nya
                                A Kapcsolat-n\u00e9lk\u00fcli modell alapelvei

                                A kapcsolatalap\u00fa modellel ellent\u00e9tben az adatok megjelen\u00edt\u00e9se \u00e9s mem\u00f3ri\u00e1ban t\u00f6rt\u00e9n\u0151 m\u00f3dos\u00edt\u00e1sa sor\u00e1n nem tartunk fent adatb\u00e1zis kapcsolatot. Ennek megfelel\u0151en a f\u0151bb l\u00e9p\u00e9sek a k\u00f6vetkez\u0151k: a kapcsolat felv\u00e9tel\u00e9t \u00e9s az adatok lek\u00e9rdez\u00e9s\u00e9t k\u00f6vet\u0151en azonnal bontjuk a kapcsolatot. Az adatokat ezt k\u00f6vet\u0151en tipikusan megjelen\u00edtj\u00fck \u00e9s lehet\u0151s\u00e9get biztos\u00edtunk a felhaszn\u00e1l\u00f3nak az adatok m\u00f3dos\u00edt\u00e1s\u00e1ra (rekordok felv\u00e9tele, m\u00f3dos\u00edt\u00e1sa, t\u00f6rl\u00e9se ig\u00e9ny szerint). A m\u00f3dos\u00edt\u00e1sok ment\u00e9se sor\u00e1n \u00fajra felvessz\u00fck az adatkapcsolatot, mentj\u00fck az adatb\u00e1zisba a v\u00e1ltoztat\u00e1sokat \u00e9s z\u00e1rjuk a kapcsolatot. Term\u00e9szetesen a modell megk\u00f6veteli, hogy a lek\u00e9rdez\u00e9se \u00e9s a m\u00f3dos\u00edt\u00e1sok vissza\u00edr\u00e1sa k\u00f6z\u00f6tt \u2013 amikor nincs kapcsolatunk az adatb\u00e1zissal \u2013 az adatokat \u00e9s a v\u00e1ltoztat\u00e1sokat a mem\u00f3ri\u00e1ban nyilv\u00e1ntartsuk. Erre az ADO.NET k\u00f6rnyezetben nagyon k\u00e9nyelmes megold\u00e1st ny\u00fajt a DataSet objektumok alkalmaz\u00e1sa.

                                A modell el\u0151nyei:

                                • Nem sz\u00fcks\u00e9ges folyamatos h\u00e1l\u00f3zati kapcsolat
                                • Sk\u00e1l\u00e1zhat\u00f3s\u00e1g

                                H\u00e1tr\u00e1nyok

                                • Az adatok nem mindig a legfrissebbek
                                • \u00dctk\u00f6z\u00e9sek lehets\u00e9gesek

                                Megjegyz\u00e9s: Sz\u00e1mos lehet\u0151s\u00e9g\u00fcnk van arra, hogy az objektumokat \u00e9s kapcsol\u00f3d\u00f3 v\u00e1ltoz\u00e1sokat nyilv\u00e1ntartsuk a mem\u00f3ri\u00e1ban. A DataSet csak az egyik lehets\u00e9ges technika. De haszn\u00e1lhatunk erre a c\u00e9lra k\u00f6z\u00f6ns\u00e9ges objektumokat, illetve ezek menedzsel\u00e9s\u00e9t megk\u00f6nny\u00edt\u0151 - az ADO.NET-n\u00e9l korszer\u0171bb - .NET technol\u00f3gi\u00e1kat (pl. Entity Framework Core).

                                "},{"location":"labor/7-adatkezeles/#a-kapcsolatalapu-modell","title":"A kapcsolatalap\u00fa modell","text":"

                                A labor keret\u00e9ben a kapcsolatalap\u00fa modellt ismerj\u00fck meg.

                                Az alapfolyamat a k\u00f6vetkez\u0151:

                                1. Kapcsolat l\u00e9trehoz\u00e1sa az alkalmaz\u00e1s, illetve az adatb\u00e1zis kezel\u0151 rendszer k\u00f6z\u00f6tt (Connection objektum felhaszn\u00e1l\u00e1s\u00e1val).
                                2. A futtatand\u00f3 SQL utas\u00edt\u00e1s \u00f6ssze\u00e1ll\u00edt\u00e1sa (Command objektum felhaszn\u00e1l\u00e1s\u00e1val).
                                3. Utas\u00edt\u00e1s futtat\u00e1sa (Command objektum felhaszn\u00e1l\u00e1s\u00e1val).
                                4. Lek\u00e9rdez\u00e9sek eset\u00e9n a visszakapott rekordhalmaz feldolgoz\u00e1sa (DataReader objektum felhaszn\u00e1l\u00e1s\u00e1val). Erre a m\u00f3dos\u00edt\u00f3 parancsok eset\u00e9n \u00e9rtelemszer\u0171en nincs sz\u00fcks\u00e9g.
                                5. Kapcsolat lez\u00e1r\u00e1sa.

                                Mint a fentiekb\u0151l kider\u00fcl, az adatb\u00e1zissal val\u00f3 kommunik\u00e1ci\u00f3nak ebben a modellben h\u00e1rom f\u0151 \u00f6sszetev\u0151je van:

                                • Connection
                                • Command
                                • Data Reader

                                Ezek az \u00f6sszetev\u0151k egy-egy oszt\u00e1lyk\u00e9nt jelennek meg, adatb\u00e1zis-kezel\u0151-f\u00fcggetlen r\u00e9sz\u00fck a BCL System.Data.Common n\u00e9vter\u00e9ben tal\u00e1lhat\u00f3 DbConnection, DbCommand, illetve DbDataReader n\u00e9ven. Ezek absztrakt oszt\u00e1lyok, az adatb\u00e1zis-kezel\u0151k gy\u00e1rt\u00f3inak feladata, hogy ezekb\u0151l lesz\u00e1rmazva meg\u00edrj\u00e1k a konkr\u00e9t adatb\u00e1zis-kezel\u0151ket t\u00e1mogat\u00f3 v\u00e1ltozatokat.

                                Mindh\u00e1rom ADO.NET \u00f6sszetev\u0151 t\u00e1mogatja a Dispose mint\u00e1t, \u00edgy using blokkban haszn\u00e1lhat\u00f3k \u2013 haszn\u00e1ljuk is \u00edgy, amikor csak tudjuk. Az adatb\u00e1zis-kezel\u0151 \u00e1ltal\u00e1ban m\u00e1sik g\u00e9pen tal\u00e1lhat\u00f3, mint ahol a k\u00f3dunk fut (a labor sor\u00e1n pont nem :)), \u00edgy tekints\u00fcnk ezekre, mint t\u00e1voli h\u00e1l\u00f3zati er\u0151forr\u00e1sokra.

                                A Microsoft SQL Server-t t\u00e1mogat\u00f3 v\u00e1ltozat a Microsoft.Data.SqlClient NuGet csomagban, az \u201eSql\u201d prefix\u0171 oszt\u00e1lyokban tal\u00e1lhat\u00f3k (SqlConnection, SqlCommand \u00e9s SqlDataReader).

                                A t\u00f6bbi gy\u00e1rt\u00f3 k\u00fcl\u00f6n dll-(ek)be teszi a saj\u00e1t v\u00e1ltozat\u00e1t, az \u00edgy l\u00e9trej\u00f6tt komponenst data provider-nek nevezik. Teljess\u00e9g ig\u00e9nye n\u00e9lk\u00fcl n\u00e9h\u00e1ny p\u00e9lda:

                                • PostgreSQL
                                • SQLite
                                • Oracle
                                "},{"location":"labor/7-adatkezeles/#connection","title":"Connection","text":"

                                Ez teremti meg a kapcsolatot a programunk, illetve az adatb\u00e1zis-kezel\u0151-rendszer k\u00f6z\u00f6tt. Inicializ\u00e1l\u00e1s\u00e1hoz sz\u00fcks\u00e9g van egy connection string-re, mely a kapcsolat fel\u00e9p\u00edt\u00e9s\u00e9hez sz\u00fcks\u00e9ges adatokat adja meg a driver sz\u00e1m\u00e1ra. Adatb\u00e1zisgy\u00e1rt\u00f3nk\u00e9nt elt\u00e9r\u0151 a bels\u0151 form\u00e1tuma (b\u0151vebben).

                                \u00daj Connection p\u00e9ld\u00e1nyos\u00edt\u00e1sakor nem biztos, hogy t\u00e9nyleg \u00faj kapcsolat fog l\u00e9trej\u00f6nni az adatb\u00e1zis fel\u00e9, a driverek \u00e1ltal\u00e1ban connection pooling-ot alkalmaznak, hasonl\u00f3an, mint a thread pool eset\u00e9ben, \u00fajrahaszn\u00e1lhatj\u00e1k a kor\u00e1bbi (\u00e9ppen nem haszn\u00e1lt) kapcsolatokat.

                                A Connection k\u00fcl\u00f6n\u00f6sen k\u00f6lts\u00e9ges nem fel\u00fcgyelt er\u0151forr\u00e1sokat haszn\u00e1l, \u00edgy kiemelten fontos, hogy a lehet\u0151 leghamarabb gondoskodjunk lez\u00e1r\u00e1s\u00e1r\u00f3l, amikor m\u00e1r nincs r\u00e1 sz\u00fcks\u00e9g (pl. a Dispose() h\u00edv\u00e1s\u00e1val, amit az esetek t\u00f6bbs\u00e9g\u00e9ben legegyszer\u0171bben a using blokk alkalmaz\u00e1s\u00e1val tehet\u00fcnk meg).

                                "},{"location":"labor/7-adatkezeles/#command","title":"Command","text":"

                                Ennek seg\u00edts\u00e9g\u00e9vel vagyunk k\u00e9pesek \u201eutas\u00edt\u00e1sokat\u201d megfogalmazni az adatb\u00e1zis kezel\u0151 sz\u00e1m\u00e1ra. Ezeket SQL nyelven kell megfogalmaznunk. A Command-nak be kell \u00e1ll\u00edtani egy kapcsolatot \u2013 ezen kereszt\u00fcl fog a parancs v\u00e9grehajt\u00f3dni. A parancsnak k\u00fcl\u00f6nb\u00f6z\u0151 eredm\u00e9nye lehet, ennek megfelel\u0151en k\u00fcl\u00f6nb\u00f6z\u0151 f\u00fcggv\u00e9nyekkel futtatjuk a parancsot:

                                • ExecuteReader: Eredm\u00e9nyhalmaz (result set) lek\u00e9rdez\u00e9se
                                • ExecuteScalar: Skal\u00e1r \u00e9rt\u00e9k lek\u00e9rdez\u00e9se
                                • ExecuteNonQuery: Nincs visszat\u00e9r\u00e9si \u00e9rt\u00e9k (Pl: INSERT, UPDATE \u00e9s DELETE), viszont a m\u0171velet k\u00f6vetkezt\u00e9ben \u00e9rintett rekordok sz\u00e1m\u00e1t visszakapjuk
                                "},{"location":"labor/7-adatkezeles/#data-reader","title":"Data Reader","text":"

                                Ha a parancs eredm\u00e9nye eredm\u00e9nyhalmaz, akkor ennek a komponensnek a seg\u00edts\u00e9g\u00e9vel tudjuk az adatokat kiolvasni. Az eredm\u00e9nyhalmaz egy t\u00e1bl\u00e1zatnak tekinthet\u0151, a Data Reader ezen tud soronk\u00e9nt v\u00e9gignavig\u00e1lni (csak egyes\u00e9vel el\u0151refel\u00e9!). A kurzor egyszerre egy soron \u00e1ll, ha a sorb\u00f3l a sz\u00fcks\u00e9ges adatokat kiolvastuk, a kurzort egy sorral el\u0151re l\u00e9ptethetj\u00fck. Csak az aktu\u00e1lis sorb\u00f3l tudunk olvasni. Kezdetben a kurzor nem az els\u0151 soron \u00e1ll, azt egyszer l\u00e9ptetn\u00fcnk kell, hogy az els\u0151 sorra \u00e1lljon.

                                Megjegyz\u00e9s: navig\u00e1l\u00e1s kliens oldalon t\u00f6rt\u00e9nik a mem\u00f3ri\u00e1ban, nincs k\u00f6ze az egyes adatkezel\u0151k \u00e1ltal t\u00e1mogatott kiszolg\u00e1l\u00f3 oldali kurzorokhoz.

                                "},{"location":"labor/7-adatkezeles/#1-feladat-adatbazis-elokeszitese","title":"1. Feladat \u2013 Adatb\u00e1zis el\u0151k\u00e9sz\u00edt\u00e9se","text":"

                                Els\u0151k\u00e9nt sz\u00fcks\u00e9g\u00fcnk van egy adatb\u00e1zis-kezel\u0151re. Ezt val\u00f3s k\u00f6rnyezetben dedik\u00e1lt szerveren fut\u00f3, adatb\u00e1zis adminisztr\u00e1torok \u00e1ltal fel\u00fcgyelt, teljes-\u00e9rt\u00e9k\u0171 adatb\u00e1zis-kezel\u0151k jelentik. Fejleszt\u00e9si id\u0151ben, lok\u00e1lis tesztel\u00e9shez azonban k\u00e9nyelmesebb egy fejleszt\u0151i adatb\u00e1zis-kezel\u0151 haszn\u00e1lata. A Visual Studio telep\u00edt\u00e9s\u00e9nek r\u00e9szek\u00e9nt kapunk is egy ilyen adatb\u00e1zismotort, ez a LocalDB, mely a teljes-\u00e9rt\u00e9k\u0171 SQL Server egyszer\u0171s\u00edtett v\u00e1ltozata. F\u0151bb tulajdons\u00e1gai:

                                • nem csak a Visual Studio-val, hanem k\u00fcl\u00f6n is telep\u00edthet\u0151,
                                • az adatb\u00e1zismotor szinte teljes m\u00e9rt\u00e9kben kompatibilis a teljes-\u00e9rt\u00e9k\u0171 Microsoft SQL Server-rel,
                                • alapvet\u0151en arr\u00f3l a g\u00e9pr\u0151l \u00e9rhet\u0151 el, melyre telep\u00edtett\u00fck,
                                • t\u00f6bb p\u00e9ld\u00e1ny is l\u00e9trehozhat\u00f3 ig\u00e9ny szerint, a p\u00e9ld\u00e1nyok alapvet\u0151en a l\u00e9trehoz\u00f3 oper\u00e1ci\u00f3s rendszer felhaszn\u00e1l\u00f3ja sz\u00e1m\u00e1ra \u00e9rhet\u0151k el (ig\u00e9ny eset\u00e9n megoszthat\u00f3 egy p\u00e9ld\u00e1ny a felhaszn\u00e1l\u00f3k k\u00f6z\u00f6tt),
                                • a saj\u00e1t p\u00e9ld\u00e1nyok kezel\u00e9se (l\u00e9trehoz\u00e1s, t\u00f6rl\u00e9s stb.) nem ig\u00e9nyel adminisztr\u00e1tori jogokat.
                                ssqllocaldb parancssori eszk\u00f6z

                                A gyakorlat sor\u00e1n nincs sz\u00fcks\u00e9g\u00fcnk erre, de a p\u00e9ld\u00e1nyok kezel\u00e9s\u00e9re az sqllocaldb parancssori eszk\u00f6z haszn\u00e1lhat\u00f3. N\u00e9h\u00e1ny parancs, melyet az sqllocaldb ut\u00e1n be\u00edrva alkalmazhatunk:

                                Paracs Le\u00edr\u00e1s info az aktu\u00e1lis felhaszn\u00e1l\u00f3 sz\u00e1m\u00e1ra l\u00e1that\u00f3 p\u00e9ld\u00e1nyok list\u00e1ja create \u201elocdb\u201d \u00faj p\u00e9ld\u00e1ny l\u00e9trehoz\u00e1sa \u201elocdb\u201d n\u00e9vvel delete \u201elocdb\u201d \u201elocdb\u201d nev\u0171 p\u00e9ld\u00e1ny t\u00f6rl\u00e9se start \u201elocdb\u201d \u201elocdb\u201d nev\u0171 p\u00e9ld\u00e1ny ind\u00edt\u00e1sa stop \u201elocdb\u201d \u201elocdb\u201d nev\u0171 p\u00e9ld\u00e1ny le\u00e1ll\u00edt\u00e1sa

                                A Visual Studio is telep\u00edt, illetve ind\u00edt LocalDB p\u00e9ld\u00e1nyokat, ez\u00e9rt \u00e9rdemes megn\u00e9zni, hogy a Visual Studio alapesetben milyen p\u00e9ld\u00e1nyokat l\u00e1t.

                                1. Ind\u00edtsuk el a Visual Studio-t, a View men\u00fcb\u0151l v\u00e1lasszuk az SQL Server Object Explorer-t (SSOE).
                                2. Nyissuk ki az SQL Server csom\u00f3pontot, ha alatta l\u00e1tunk tov\u00e1bbi csom\u00f3pontokat, akkor nyert \u00fcgy\u00fcnk van, nyissuk ki valamelyiket (ilyenkor indul el a p\u00e9ld\u00e1ny, ha nincs elind\u00edtva, \u00edgy lehet, hogy v\u00e1rni kell kicsit).
                                3. Ha nem jelent meg semmi, akkor parancssorb\u00f3l az mssqllocaldb info parancs megadja a l\u00e9tez\u0151 p\u00e9ld\u00e1nyokat. V\u00e1lasszuk az SQL Server csom\u00f3ponton jobbklikkelve az Add SQL Server opci\u00f3t, majd adjuk meg valamelyik l\u00e9tez\u0151 p\u00e9ld\u00e1nyt, pl.: (localdb)\\MSSQLLocalDB
                                4. A megjelen\u0151 Databases csom\u00f3ponton v\u00e1lasszuk a New Database opci\u00f3t, itt adjunk meg egy adatb\u00e1zisnevet. (Laboron, mivel t\u00f6bb hallgat\u00f3 is haszn\u00e1lhatja ugyanazt az oper\u00e1ci\u00f3s rendszer felhaszn\u00e1l\u00f3t, javasolt a Neptun k\u00f3d, mint n\u00e9v haszn\u00e1lata).
                                5. Az \u00faj adatb\u00e1zis csom\u00f3pontj\u00e1n jobbklikkelve v\u00e1lasszuk a New Query opci\u00f3t, ami egy \u00faj query ablakot nyit.
                                6. Nyissuk meg vagy t\u00f6lts\u00fck le a Northwind adatb\u00e1zis inicializ\u00e1l\u00f3 szkriptet.
                                7. M\u00e1soljuk be a teljes szkriptet a query ablakba.
                                8. Futtassuk le a szkriptet a kis z\u00f6ld ny\u00edllal (Execute). Figyelj\u00fcnk oda, hogy j\u00f3 adatb\u00e1zis (melyet fenti 4. l\u00e9p\u00e9sben hoztunk l\u00e9tre) legyen kiv\u00e1lasztva a query ablak tetej\u00e9n a leg\u00f6rd\u00fcl\u0151ben!.
                                9. Ellen\u0151rizz\u00fck, hogy az adatb\u00e1zisunkban megjelentek-e t\u00e1bl\u00e1k, n\u00e9zetek.
                                10. Fedezz\u00fck fel az SSOE legfontosabb funkci\u00f3it (t\u00e1bl\u00e1k adatainak, s\u00e9m\u00e1j\u00e1nak lek\u00e9rdez\u00e9se stb.).

                                MSSQL menedzsment eszk\u00f6z\u00f6k

                                A Visual Studio-ban k\u00e9t eszk\u00f6zzel is kezelhet\u00fcnk adatb\u00e1zisokat: a Server Explorer-rel \u00e9s az SQL Server Object Explorer-rel is. El\u0151bbi egy \u00e1ltal\u00e1nosabb eszk\u00f6z, mely nem csak adatb\u00e1zis, hanem egy\u00e9b szerver er\u0151forr\u00e1sok (pl. Azure szerverek) kezel\u00e9s\u00e9re is alkalmas, m\u00edg a m\u00e1sik kifejezetten csak adatb\u00e1zis-kezel\u00e9sre van kihegyezve. Mindkett\u0151 el\u00e9rhet\u0151 a View men\u00fcb\u0151l \u00e9s mindkett\u0151 hasonl\u00f3 funkci\u00f3kat ad adatb\u00e1zis-kezel\u00e9shez, ez\u00e9rt ebben a m\u00e9r\u00e9sben csak az egyiket (SQL Server Object Explorer) haszn\u00e1ljuk.

                                Amikor nem \u00e1ll rendelkez\u00e9s\u00fcnkre a Visual Studio fejleszt\u0151k\u00f6rnyezet, akkor az adatb\u00e1zisunk menedzsel\u00e9s\u00e9re az (ingyenes) SQL Server Management Studio-t vagy a szint\u00e9n ingyenes \u00e9s multiplatform Azure Data Studio-t tudjuk haszn\u00e1lni.

                                "},{"location":"labor/7-adatkezeles/#2-feladat-lekerdezes-adonet-sqldatareader-rel","title":"2. Feladat \u2013 Lek\u00e9rdez\u00e9s ADO.NET SqlDataReader-rel","text":"

                                A feladat egy olyan C# nyelv\u0171 konzol alkalmaz\u00e1s elk\u00e9sz\u00edt\u00e9se, amely haszn\u00e1lja a Northwind adatb\u00e1zis Shippers t\u00e1bl\u00e1j\u00e1nak rekordjait.

                                1. Hozzunk l\u00e9tre egy C# nyelv\u0171 konzolos alkalmaz\u00e1st. A projekt t\u00edpusa Console App legyen, \u00e9s NE a Console App (.NET Framework):

                                  • A projekt neve legyen AdoExample
                                  • A Target Framework legyen .NET 8
                                  • Pip\u00e1ljuk be a Do not use top-level statements kapcsol\u00f3t
                                2. Keress\u00fck ki a connection string-et az SSOE-b\u0151l: jobbklikk az adatb\u00e1zis-kapcsolatunkon (pirossal jel\u00f6lve az al\u00e1bbi \u00e1br\u00e1n) / Properties.

                                3. M\u00e1soljuk a Properties ablakb\u00f3l a Connection String tulajdons\u00e1g \u00e9rt\u00e9k\u00e9t egy v\u00e1ltoz\u00f3ba, a Program oszt\u00e1lyba.

                                  private const string ConnString = @\"Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=neptun;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False\";\n

                                  SQL Server connection string form\u00e1tuma

                                  MSSQL eset\u00e9ben a connection string kulcs \u00e9rt\u00e9kp\u00e1rokat tartalmaz pontosvessz\u0151vel elv\u00e1lasztva. A Data Source kulcs alatt az SQL szerver p\u00e9ld\u00e1ny neve, azInitial Catalog kulcs alatt pedig az adatb\u00e1zis neve szerepel. Az Integrated Security=true kapcsol\u00f3 pedig a Windows autentik\u00e1ci\u00f3t jelenti.

                                  @-os string (C# verbatim string)

                                  A @ egy speci\u00e1lis karakter (verbatim identifier), amit itt arra haszn\u00e1lunk, hogy a connection string-ben megjelen\u0151 backslash karakter (\\) ne felold\u00f3jelk\u00e9nt (escape character) ker\u00fclj\u00f6n \u00e9rtelmez\u00e9sre.

                                4. Vegy\u00fck fel a projektbe a Microsoft.Data.SqlClient NuGet csomagot. Ezt k\u00e9tf\u00e9lek\u00e9ppen tehetj\u00fck meg:

                                  • A) Visual Studio NuGet kezel\u0151ben:
                                    1. Projekten jobb gomb / Manage NuGet Packages..., a megjelen\u0151 oldalon Browse oldalra v\u00e1lt\u00e1s.
                                    2. A keres\u0151be Microsoft.Data.SqlClient be\u00edr\u00e1sa.
                                    3. A Version mez\u0151ben az 5.0.1 kiv\u00e1laszt\u00e1sa (laboron az\u00e9rt v\u00e1lasztjuk ki ezt a verzi\u00f3t, mert ez szerepel a g\u00e9peken a NuGet cache-ben, otthoni gyakorl\u00e1s sor\u00e1n v\u00e1lasszuk ink\u00e1bb a Latest stable-t).
                                  • B) Bem\u00e1soljuk az al\u00e1bbi csomag referenci\u00e1t a a projektf\u00e1jlba:

                                    <ItemGroup>\n    <PackageReference Include=\"Microsoft.Data.SqlClient\" Version=\"5.0.1\" />\n</ItemGroup>\n

                                  NuGet csomagkezel\u0151

                                  A NuGet egy olyan online csomagkezel\u0151 rendszer, ahonnan .NET alap\u00fa projektjeinkbe tudunk k\u00fcls\u0151 f\u00fcgg\u0151s\u00e9geket, oszt\u00e1lyk\u00f6nyvt\u00e1rakat egyszer\u0171en, verzi\u00f3zott form\u00e1ban behivatkozni. B\u0151vebben az els\u0151 el\u0151ad\u00e1son szerepel.

                                5. \u00cdrjunk lek\u00e9rdez\u0151 f\u00fcggv\u00e9nyt, mely lek\u00e9rdezi az \u00f6sszes sz\u00e1ll\u00edt\u00f3t:

                                  private static void GetShippers()\n{\n    using (var conn = new SqlConnection(ConnString))\n    using (var command = new SqlCommand(\"SELECT ShipperID, CompanyName, Phone FROM Shippers\", conn))\n    {\n        conn.Open();\n        Console.WriteLine(\"{0,-10}{1,-20}{2,-20}\", \"ShipperID\", \"CompanyName\", \"Phone\");\n        Console.WriteLine(new string('-', 60));\n        using (SqlDataReader reader = command.ExecuteReader())\n        {\n            while (reader.Read())\n            {\n                Console.WriteLine(\n                    $\"{reader[\"ShipperID\"],-10}\" +\n                    $\"{reader[\"CompanyName\"],-20}\" +\n                    $\"{reader[\"Phone\"],-20}\");\n            }\n        }\n    }\n}\n

                                  A kapcsolat alap\u00fa modell folyamata:

                                  • Kapcsolat, parancs inicializ\u00e1l\u00e1sa
                                  • Kapcsolat megnyit\u00e1sa
                                  • Parancs futtat\u00e1sa
                                  • Eredm\u00e9ny feldolgoz\u00e1sa
                                  • Kapcsolat bont\u00e1sa, takar\u00edt\u00e1s

                                  N\u00e9h\u00e1ny megjegyz\u00e9s a k\u00f3dhoz

                                  • A DataReader-t a parancs futtat\u00e1s\u00e1nak eredm\u00e9nyek\u00e9nt kapjuk meg, nem pedig k\u00f6zvetlen\u00fcl p\u00e9ld\u00e1nyos\u00edtjuk
                                  • A parancs futtat\u00e1sa el\u0151tt meg kell nyitnunk a kapcsolatot
                                  • A DbConnection p\u00e9ld\u00e1nyos\u00edt\u00e1sakor nem nyit\u00f3dik meg a kapcsolat (nem t\u00f6rt\u00e9nik h\u00e1l\u00f3zati kommunik\u00e1ci\u00f3)
                                  • A DataReader.Read() f\u00fcggv\u00e9nye mutatja, hogy van-e m\u00e9g adat az eredm\u00e9nyhalmazban
                                  • A DataReader-t az eredm\u00e9nyhalmazban tal\u00e1lhat\u00f3 oszlopok nev\u00e9vel indexelhetj\u00fck \u2013 az eredm\u00e9ny object lesz, \u00edgy, ha konkr\u00e9tabb t\u00edpusra van sz\u00fcks\u00e9g\u00fcnk cast-olni kell
                                  • A ford\u00edt\u00f3 nem \u00e9rtelmezi az SQL parancs sz\u00f6veg\u00e9t (az csak egy string), hanem majd csak az adatb\u00e1zis, \u00edgy hib\u00e1s SQL eset\u00e9n csak fut\u00e1si idej\u0171 kiv\u00e9telt kapunk
                                  • Figyelj\u00fck meg, hogy az adatb\u00e1zis s\u00e9ma v\u00e1ltoz\u00e1sa eset\u00e9n, pl. egy oszlop \u00e1tnevez\u00e9se ut\u00e1n, h\u00e1ny helyen kell k\u00e9zzel \u00e1t\u00edrni string-eket a k\u00f3dban
                                  • $-ral prefixelve string interpol\u00e1ci\u00f3t alkalmazhatunk, azaz k\u00f6zvetlen\u00fcl a string-be \u00e1gyazhatunk ki\u00e9rt\u00e9kelend\u0151 kifejez\u00e9seket (C# 6-os k\u00e9pess\u00e9g). A $@ seg\u00edts\u00e9g\u00e9vel t\u00f6bbsoros string interpol\u00e1ci\u00f3s kifejez\u00e9seket \u00edrhatunk (a sort\u00f6r\u00e9st a {}-k k\u00f6z\u00f6tt kell betenn\u00fcnk, k\u00fcl\u00f6nben a kimeneten is megjelenik). \u00c9rdekess\u00e9g: C# 8-t\u00f3l f\u00f6lfele b\u00e1rmilyen sorrendben \u00edrhatjuk a $ \u00e9s @ karaktereket, teh\u00e1t a $@ \u00e9s a @$ is helyesnek sz\u00e1m\u00edt.
                                  • A using kulcssz\u00f3 blokk utas\u00edt\u00e1s helyett egysoros kifejez\u00e9sk\u00e9nt is haszn\u00e1lhat\u00f3. Ilyen esetben a using blokk v\u00e9ge a tartalmaz\u00f3 blokkig tart (eset\u00fcnkben a f\u00fcggv\u00e9ny v\u00e9g\u00e9ig). Ezzel cs\u00f6kkenthet\u0151 a beh\u00faz\u00e1sok sz\u00e1ma, de ne legyen automatikus reflex a haszn\u00e1lata, mert el\u0151fordulhat, hogy hamarabb c\u00e9lszer\u0171 kik\u00e9nyszer\u00edteni az er\u0151forr\u00e1sok felszabad\u00edt\u00e1s\u00e1t, mint a tartalmaz\u00f3 blokk v\u00e9ge.

                                    private static void GetShippers()\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\"SELECT ShipperID, CompanyName, Phone FROM Shippers\", conn);\n\n    conn.Open();\n\n    Console.WriteLine(\"{0,-10}{1,-20}{2,-20}\",\"ShipperID\", \"CompanyName\", \"Phone\");\n    Console.WriteLine(new string('-', 60));\n\n    using var reader = command.ExecuteReader();\n    while (reader.Read())\n    {\n        Console.WriteLine(\n            $\"{reader[\"ShipperID\"],-10}\" +\n            $\"{reader[\"CompanyName\"],-20}\" +\n            $\"{reader[\"Phone\"],-20}\");\n    }\n}\n

                                    A tov\u00e1bbiakban ezt a m\u00f3dszert haszn\u00e1ljuk a beh\u00faz\u00e1sok \u00e9s z\u00e1r\u00f3jelek megsp\u00f3rol\u00e1sa \u00e9rdek\u00e9ben.

                                6. H\u00edvjuk meg \u00faj f\u00fcggv\u00e9ny\u00fcnket a Main f\u00fcggv\u00e9nyb\u0151l.

                                  private static void Main(string[] args)\n{\n    GetShippers();\n}\n
                                7. Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st. Rontsuk el az SQL-t, \u00e9s \u00fagy is pr\u00f3b\u00e1ljuk ki.

                                "},{"location":"labor/7-adatkezeles/#3-feladat-beszuras-sql-utasitassal","title":"3. Feladat \u2013 Besz\u00far\u00e1s SQL utas\u00edt\u00e1ssal","text":"
                                1. \u00cdrjunk f\u00fcggv\u00e9nyt, mely \u00faj sz\u00e1ll\u00edt\u00f3t sz\u00far be az adatb\u00e1zisba:

                                  private static void InsertShipper(string companyName, string phone)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\n        \"INSERT INTO Shippers(CompanyName, Phone) VALUES(@name,@phone)\", conn);\n    command.Parameters.AddWithValue(\"@name\", companyName);\n    command.Parameters.AddWithValue(\"@phone\", phone);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n\n    Console.WriteLine($\"{affectedRows} rows affected\");\n}\n

                                  Itt olyan SQL-t kell \u00edrnunk, melynek az \u00f6ssze\u00e1ll\u00edt\u00e1s\u00e1n\u00e1l k\u00edv\u00fclr\u0151l kapott v\u00e1ltoz\u00f3k \u00e9rt\u00e9keit is felhaszn\u00e1ltuk. A string \u00f6sszerak\u00e1s\u00e1hoz egyszer\u0171en a string \u00f6sszef\u0171z\u00e9s oper\u00e1tort, string interpol\u00e1ci\u00f3t vagy string.Format-ot is haszn\u00e1lhattunk volna, de ez biztons\u00e1gi kock\u00e1zatot (SQL Injection \u2013 b\u0151vebben l\u00e1sd lentebb) rejt \u2013 SOHA!!! ne rakjuk \u00f6ssze az SQL-t string m\u0171velettel. Helyette \u00edrjuk meg \u00fagy az SQL-t, hogy ahov\u00e1 a v\u00e1ltoz\u00f3k \u00e9rt\u00e9keit \u00edrn\u00e1nk, oda param\u00e9terhivatkoz\u00e1sokat tesz\u00fcnk. SQL Server eset\u00e9ben a hivatkoz\u00e1s szintaxisa: @param\u00e9tern\u00e9v.

                                  A parancs futtat\u00e1s\u00e1hoz a param\u00e9terek \u00e9rt\u00e9keit is \u00e1t kell adnunk az adatb\u00e1zisnak, ugyanis az fogja elv\u00e9gezni a param\u00e9terek hely\u00e9re az \u00e9rt\u00e9kek behelyettes\u00edt\u00e9s\u00e9t.

                                  A besz\u00far\u00e1si parancs kimenete nem eredm\u00e9nyhalmaz, \u00edgy az ExecuteNonQuery m\u0171velettel kell futtatnuk, mely visszaadja besz\u00fart sorok sz\u00e1m\u00e1t.

                                2. H\u00edvjuk meg \u00faj f\u00fcggv\u00e9ny\u00fcnket a Main f\u00fcggv\u00e9nyb\u0151l.

                                  GetShippers();\nInsertShipper(\"Super Shipper\",\"49-98562\");\nGetShippers();\n
                                3. Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st, ellen\u0151rizz\u00fck a konzolban \u00e9s az SSOE-ben is, hogy beker\u00fclt-e az \u00faj sor. Az SSOE-ben val\u00f3 gyors \u00e9s k\u00e9nyelmes ellen\u0151rz\u00e9shez a Shippers t\u00e1bla context men\u00fcj\u00e9b\u0151l v\u00e1lasszuk a View Data lehet\u0151s\u00e9get.

                                "},{"location":"labor/7-adatkezeles/#4-feladat-modositas-tarolt-eljarassal","title":"4. Feladat - M\u00f3dos\u00edt\u00e1s t\u00e1rolt elj\u00e1r\u00e1ssal","text":"
                                1. Tanulm\u00e1nyozzuk SSOE-ben a Product_Update t\u00e1rolt elj\u00e1r\u00e1s k\u00f3dj\u00e1t. Ehhez nyissuk le a Programmability alatt tal\u00e1lhat\u00f3 Stored Procedures csom\u00f3pontot, majd a Product_Update t\u00e1rolt elj\u00e1r\u00e1s context men\u00fcj\u00e9b\u0151l v\u00e1lasszuk a View Code lehet\u0151s\u00e9get.

                                  Programk\u00f3d az adatb\u00e1zisban

                                  A nagyobb adatkezel\u0151 rendszerek lehet\u0151s\u00e9get biztos\u00edtanak arra, hogy programk\u00f3dot defini\u00e1ljunk mag\u00e1ban az adatkezel\u0151 adatb\u00e1zis\u00e1ban. Ezeket t\u00e1rol elj\u00e1r\u00e1soknak (stored procedure) nevezz\u00fck. A nyelve adatkezel\u0151 f\u00fcgg\u0151, de MSSQL eset\u00e9ben ez T-SQL.

                                  Manaps\u00e1g m\u00e1r egyre ink\u00e1bb kezd kikopni az a gyakorlat az iparb\u00f3l, hogy komolyabb \u00fczleti logik\u00e1t az adatb\u00e1zisban helyezz\u00fcnk el, mivel ezeknek az SQL dialektusoknak az eszk\u00f6zk\u00e9szlete ma m\u00e1r j\u00f3val korl\u00e1tosabb, mint egy magas szint\u0171 programoz\u00e1si nyelv\u00e9 (C#, Java). R\u00e1ad\u00e1sul a rendszer tesztelhet\u0151s\u00e9g\u00e9t nagyban rontja a t\u00e1rolt elj\u00e1r\u00e1sok haszn\u00e1lata. Ennek ellen\u00e9re n\u00e9ha indokolt lehet az adatb\u00e1zisban tartani valamennyi logik\u00e1t, amikor ki szeretn\u00e9nk azt haszn\u00e1lni, hogy az adatokhoz k\u00f6zel futnak a programk\u00f3djaink, pl. ha nem akarjuk megutaztatni a h\u00e1l\u00f3zaton az adatot egy egyszer\u0171 t\u00f6meges adatkarbantart\u00e1s \u00e9rdek\u00e9ben.

                                2. \u00cdrjunk f\u00fcggv\u00e9nyt, mely ezt a t\u00e1rolt elj\u00e1r\u00e1st h\u00edvja

                                  private static void UpdateProduct(int productID, string productName, decimal price)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\"Product_Update\", conn);\n\n    command.CommandType = CommandType.StoredProcedure;\n\n    command.Parameters.AddWithValue(\"@ProductID\", productID);\n    command.Parameters.AddWithValue(\"@ProductName\", productName);\n    command.Parameters.AddWithValue(\"@UnitPrice\", price);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n\n    Console.WriteLine($\"{affectedRows} rows affected\");\n}\n

                                  A Command-nak a t\u00e1rolt elj\u00e1r\u00e1s nev\u00e9t kellett megadni, \u00e9s a parancs t\u00edpus\u00e1t kellett \u00e1t\u00e1ll\u00edtani, egy\u00e9bk\u00e9nt szerkezetileg hasonl\u00edt a kor\u00e1bbi besz\u00far\u00f3 k\u00f3dra.

                                3. H\u00edvjuk meg az \u00faj f\u00fcggv\u00e9ny\u00fcnket a Main f\u00fcggv\u00e9nyb\u0151l, p\u00e9ld\u00e1ul az al\u00e1bbi param\u00e9terez\u00e9ssel:

                                  UpdateProduct(1, \"MyProduct\", 50);\n
                                4. Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st, ellen\u0151rizz\u00fck a konzolban \u00e9s az SSOE-ben is, hogy m\u00f3dosult-e az 1-es azonos\u00edt\u00f3j\u00fa term\u00e9k.

                                "},{"location":"labor/7-adatkezeles/#5-feladat-sql-injection","title":"5. Feladat - SQL Injection","text":"
                                1. \u00cdrjuk meg a besz\u00far\u00f3 f\u00fcggv\u00e9nyt \u00fagy, hogy string interpol\u00e1ci\u00f3val rakja \u00f6ssze az SQL-t.

                                  private static void InsertShipper2(string companyName, string phone)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\n        $\"INSERT INTO Shippers(CompanyName, Phone) VALUES('{companyName}','{phone}')\",\n        conn);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n    Console.WriteLine($\"{affectedRows} row(s) inserted\");\n}\n
                                2. H\u00edvjuk meg \u00faj f\u00fcggv\u00e9ny\u00fcnket a Main f\u00fcggv\u00e9nyb\u0151l \u201especi\u00e1lisan\u201d param\u00e9terezve.

                                  InsertShipper2(\"Super Shipper\", \"49-98562'); DELETE FROM Shippers;--\");\n

                                  \u00dagy \u00e1ll\u00edtottuk \u00f6ssze a m\u00e1sodik param\u00e9tert, hogy az lez\u00e1rja az eredeti utas\u00edt\u00e1st, ezut\u00e1n tetsz\u0151leges (!!!) SQL-t \u00edrhatunk, v\u00e9g\u00fcl kikommentezz\u00fck az eredeti utas\u00edt\u00e1s marad\u00e9k\u00e1t (--).

                                3. Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st, hib\u00e1t kell kapjunk, mely arra utal, hogy valamelyik sz\u00e1ll\u00edt\u00f3 nem t\u00f6r\u00f6lhet\u0151 idegen kulcs hivatkoz\u00e1s miatt.

                                  Teh\u00e1t a DELETE FROM is lefutott! N\u00e9zz\u00fck meg debugger-rel (pl. a conn.Open utas\u00edt\u00e1son \u00e1llva), hogy mi a v\u00e9gleges SQL (command.CommandText).

                                  Tanuls\u00e1gok:

                                  • SOSE f\u0171zz\u00fcnk \u00f6ssze programozottan SQL-t (semmilyen m\u00f3dszerrel), mert azzal kitessz\u00fck a k\u00f3dunkat SQL Injection alap\u00fa t\u00e1mad\u00e1snak.
                                  • Az adatb\u00e1zis \u00e1ll\u00edtsa \u00f6ssze a v\u00e9gleges SQL-t az SQL param\u00e9terek alapj\u00e1n, mert ilyenkor biztos\u00edtott, hogy a param\u00e9ter \u00e9rt\u00e9kek nem fognak SQL-k\u00e9nt \u00e9rtelmez\u0151dni (hi\u00e1ba \u00edrunk be SQL-t). Haszn\u00e1ljunk param\u00e9terezett SQL-t vagy t\u00e1rolt elj\u00e1r\u00e1st.
                                  • Haszn\u00e1ljunk adatb\u00e1zis k\u00e9nyszereket, pl. a v\u00e9letlen t\u00f6rl\u00e9s ellen is v\u00e9d.
                                  • Konfigur\u00e1ljunk adatb\u00e1zisban felhaszn\u00e1l\u00f3kat k\u00fcl\u00f6nb\u00f6z\u0151 jogosults\u00e1gokkal, a programunk connection string-j\u00e9ben megadott felhaszn\u00e1l\u00f3 csak a m\u0171k\u00f6d\u00e9shez sz\u00fcks\u00e9ges minim\u00e1lis jogokkal rendelkezzen. A mi eset\u00fcnkben nem adtunk meg felhaszn\u00e1l\u00f3t, a windows-os felhaszn\u00e1l\u00f3k\u00e9nt fogunk csatlakozni.
                                4. H\u00edvjuk meg az eredeti (vagyis a biztons\u00e1gos, SQL param\u00e9tereket haszn\u00e1l\u00f3) besz\u00far\u00f3 f\u00fcggv\u00e9nyt a \u201especi\u00e1lis\u201d param\u00e9terez\u00e9ssel, hogy l\u00e1ssuk, m\u0171k\u00f6dik-e a v\u00e9delem:

                                  InsertShipper(\"Super Shipper\", \"49-98562'); DELETE FROM Shippers;--\");\nInsertShipper(\"XXX');DELETE FROM Shippers;--\", \"49-98562\");\n

                                  Az els\u0151n\u00e9l nem f\u00e9r\u00fcnk bele a m\u00e9retkorl\u00e1tba, a m\u00e1sodik lefut, de csak egy \u201efurcsa\u201d nev\u0171 sz\u00e1ll\u00edt\u00f3 ker\u00fclt be. A param\u00e9ter \u00e9rt\u00e9ke t\u00e9nyleg \u00e9rt\u00e9kk\u00e9nt \u00e9rtelmez\u0151d\u00f6tt nem pedig SQL-k\u00e9nt. Nem \u00fagy mint itt:

                                "},{"location":"labor/7-adatkezeles/#6-feladat-torles","title":"6. Feladat - T\u00f6rl\u00e9s","text":"
                                1. \u00cdrjunk egy \u00faj f\u00fcggv\u00e9nyt, mely kit\u00f6r\u00f6l egy adott sz\u00e1ll\u00edt\u00f3t.

                                  private static void DeleteShipper(int shipperID)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\"DELETE FROM Shippers WHERE ShipperID = @ShipperID\", conn);\n    command.Parameters.AddWithValue(\"@ShipperID\", shipperID);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n\n    Console.WriteLine($\"{affectedRows} row(s) affected\");\n}\n
                                2. H\u00edvjuk meg \u00faj f\u00fcggv\u00e9ny\u00fcnket a Main f\u00fcggv\u00e9nyb\u0151l, pl. 1-gyel param\u00e9terezve.

                                3. Pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1st. Val\u00f3sz\u00edn\u0171leg kiv\u00e9telt kapunk, ugyanis van hivatkoz\u00e1s (idegen kulcs k\u00e9nyszerrel) az adott rekordra.
                                4. SSOE-b\u0151l n\u00e9zz\u00fck ki az azonos\u00edt\u00f3j\u00e1t egy olyan sz\u00e1ll\u00edt\u00f3nak, melyet mi vett\u00fcnk fel. Adjuk \u00e1t ezt az azonos\u00edt\u00f3t a t\u00f6rl\u0151 f\u00fcggv\u00e9nynek \u2013 ezt m\u00e1r ki tudja t\u00f6r\u00f6lni, hiszen nincs r\u00e1 hivatkoz\u00e1s.

                                T\u00f6rl\u00e9si strat\u00e9gi\u00e1k

                                L\u00e1that\u00f3, hogy a t\u00f6rl\u00e9s igen kock\u00e1zatos \u00e9s kisz\u00e1m\u00edthatatlan m\u0171velet az idegen kulcs k\u00e9nyszerek miatt. N\u00e9h\u00e1ny m\u00f3dszer a t\u00f6rl\u00e9s kezel\u00e9s\u00e9re:

                                • nem enged\u00e9lyezz\u00fck a t\u00f6rl\u00e9st: Ha hivatkoznak a t\u00f6rlend\u0151 rekordra, az adatb\u00e1zis hib\u00e1val t\u00e9r vissza (ahogy fent is l\u00e1thattuk).
                                • kaszk\u00e1d t\u00f6rl\u00e9s \u2013 az idegen kulcs k\u00e9nyszeren be\u00e1ll\u00edthat\u00f3, hogy a hivatkozott rekord t\u00f6rl\u00e9sekor a hivatkoz\u00f3 rekord is t\u00f6rl\u0151dj\u00f6n. Gyakran ez oda vezet, hogy minden idegen kulcs k\u00e9nyszer\u00fcnk ilyen lesz, \u00e9s egy (v\u00e9letlen) t\u00f6rl\u00e9ssel v\u00e9gigt\u00f6r\u00f6lhetj\u00fck ak\u00e1r a teljes adatb\u00e1zist, azaz nehezen j\u00f3solhat\u00f3 a t\u00f6rl\u00e9s hat\u00e1sa.
                                • hivatkoz\u00e1s NULL-oz\u00e1sa \u2013 az idegen kulcs k\u00e9nyszeren be\u00e1ll\u00edthat\u00f3, hogy a hivatkozott rekord t\u00f6rl\u00e9sekor a hivatkoz\u00f3 rekord idegen kulcs mez\u0151je NULL \u00e9rt\u00e9k\u0171 legyen. Csak akkor alkalmazhat\u00f3, ha a modell\u00fcnkben az adott idegen kulcs mez\u0151 NULL-ozhat\u00f3.
                                • logikai t\u00f6rl\u00e9s (soft delete) \u2013 t\u00f6rl\u00e9s m\u0171velet helyett csak egy flag oszlopot (pl. IsDeleted) \u00e1ll\u00edtunk be. El\u0151nye, hogy nem kell az idegen kulcs k\u00e9nyszerekkel foglalkoznunk, a t\u00f6r\u00f6lt adat rendelkez\u00e9sre \u00e1ll, ha sz\u00fcks\u00e9g lenne r\u00e1 (pl. undelete m\u0171velet). \u00c1m a m\u0171k\u00f6d\u00e9s bonyol\u00f3dik, mert foglalkozni kell azzal, hogy hogyan \u00e9s mikor sz\u0171rj\u00fck a t\u00f6r\u00f6lt rekordokat (pl. hogy ne jelenjenek meg a fel\u00fcleten, statisztik\u00e1kban), vagy hogyan kezelj\u00fck, ha egy nem t\u00f6r\u00f6lt rekord t\u00f6r\u00f6lt rekordra hivatkozik.
                                "},{"location":"labor/7-adatkezeles/#kitekintes","title":"Kitekint\u00e9s","text":"

                                A fenti ADO.NET alapm\u0171veleteket ebben az itt l\u00e1tott alapform\u00e1ban ritk\u00e1n haszn\u00e1lj\u00e1k k\u00e9t okb\u00f3l kifoly\u00f3an (m\u00e9g akkor is, ha ez a megk\u00f6zel\u00edt\u00e9s adja a legjobb teljes\u00edtm\u00e9nyt):

                                • Gyenge t\u00edpusoss\u00e1g (egy rekord adatait beolvasni egy oszt\u00e1ly property-jeibe igen k\u00f6r\u00fclm\u00e9nyes, cast-olni kell stb.)
                                • String-be k\u00f3dolt SQL (az elg\u00e9pel\u00e9sb\u0151l ered\u0151 hib\u00e1k csak fut\u00e1si id\u0151ben der\u00fclnek ki)

                                Az el\u0151bbire megold\u00e1st jelenthetnek a k\u00fcl\u00f6nb\u00f6z\u0151 ADO.NET-et kieg\u00e9sz\u00edt\u0151 komponensek, pl.:

                                • Dapper
                                • PetaPoco

                                Ezek a megold\u00e1sok egy minim\u00e1lis teljes\u00edtm\u00e9nyvesztes\u00e9g\u00e9rt cser\u00e9be nagyobb k\u00e9nyelmet k\u00edn\u00e1lnak.

                                Mindk\u00e9t probl\u00e9m\u00e1ra megold\u00e1st jelentenek az ORM (Object-Relational-Mapping) rendszerek, cser\u00e9be ezek nagyobb overheaddel j\u00e1rnak, mint az el\u0151bb eml\u00edtett megold\u00e1sok. Az ORM-ek lek\u00e9pez\u00e9st alak\u00edtanak ki az adatb\u00e1zis \u00e9s az OO oszt\u00e1lyaink k\u00f6z\u00f6tt, \u00e9s ennek a lek\u00e9pez\u00e9snek a seg\u00edts\u00e9g\u00e9vel egyszer\u0171s\u00edtik az adatb\u00e1zis m\u0171veleteket. Az oszt\u00e1lyainkon v\u00e9gzett, t\u00edpusos k\u00f3ddal le\u00edrt m\u0171veleteinket automatikusan \u00e1tford\u00edtj\u00e1k a megfelel\u0151 adatb\u00e1zis m\u0171veletekre, \u00edgy a mem\u00f3riabeli objektummodell\u00fcnket szinkronban tartj\u00e1k az adatb\u00e1zissal. Az ORM-ek ebb\u0151l k\u00f6vetkez\u0151en kapcsolat n\u00e9lk\u00fcli modellt haszn\u00e1lnak. Ismertebb .NET-es ORM-ek:

                                • ADO.NET DataSet \u2013 els\u0151 gener\u00e1ci\u00f3s ORM, ma m\u00e1r nagyon ritk\u00e1n haszn\u00e1ljuk
                                • Entity Framework 6.x \u2013 (r\u00e9gi) .NET Framework leggyakrabban haszn\u00e1lt ORM keretrendszere
                                • Entity Framework Core (EF Core) \u2013 a jelenleg els\u0151dlegesen haszn\u00e1lt .NET ORM (open source)
                                • NHibernate \u2013 a Java-s Hibernate .NET-es portja (open source)

                                Az Entity Framework Core-ral r\u00e9szletesebben foglalkozunk az Adatvez\u00e9relt rendszerek specializ\u00e1ci\u00f3 t\u00e1rgyban illetve a Szoftverfejleszt\u00e9s .NET platformon v\u00e1laszthat\u00f3 t\u00e1rgyban.

                                "},{"location":"labor/7-adatkezeles/index_ger/","title":"7. Verwaltung der Daten","text":""},{"location":"labor/7-adatkezeles/index_ger/#das-ziel-der-ubung","title":"Das Ziel der \u00dcbung","text":"

                                Ziel der \u00dcbung ist es, das ADO.NET-Programmiermodell zu erlernen und die h\u00e4ufigsten Datenverwaltungsprobleme und Fallstricke durch das Schreiben grundlegender CRUD-Operationen zu veranschaulichen.

                                Verwandte Pr\u00e4sentationen: Datenverwaltung, ADO.NET-Grundlagen.

                                "},{"location":"labor/7-adatkezeles/index_ger/#voraussetzungen","title":"Voraussetzungen","text":"

                                Die f\u00fcr die Durchf\u00fchrung der \u00dcbung ben\u00f6tigten Werkzeuge:

                                • Visual Studio 2022
                                • Betriebssystem Windows 10 oder Windows 11
                                • In dieser \u00dcbung werden wir den SQL Server Object Explorer in Visual Studio verwenden, um zwischen Datenbankobjekten zu navigieren und Abfragen auszuf\u00fchren. Dazu ist m\u00f6glicherweise die Komponente SQL Server Data Tools erforderlich, die am einfachsten auf der Seite Individuelle Komponenten im Visual Studio Installer installiert wird, aber auch im Workload Datenspeicherung und -verarbeitung enthalten ist.

                                \u00dcbung unter Linux oder Mac

                                Das \u00dcbungsmaterial ist grunds\u00e4tzlich f\u00fcr Windows und Visual Studio gedacht, kann aber - in leicht abgewandelter Form - auch auf anderen Betriebssystemen durchgef\u00fchrt werden, da das .NET SDK auch unter Linux und Mac sowie Linux unterst\u00fctzt wird:

                                • Verwenden Sie anstelle von Visual Studio einen Texteditor (z. B.: VSCode) und CLI-Tools.
                                • Es gibt eine Linux-Version von SQL Server, und auf dem Mac kann er in Docker ausgef\u00fchrt werden (aber Docker ist wahrscheinlich der bequemste Weg, um ihn unter Linux auszuf\u00fchren).
                                • Zur Visualisierung der Daten kann das ebenfalls plattform\u00fcbergreifende Tool Azure Data Studio verwendet werden.
                                "},{"location":"labor/7-adatkezeles/index_ger/#losung","title":"L\u00f6sung","text":"Laden Sie die fertige L\u00f6sung herunter

                                Es ist wichtig, dass Sie sich w\u00e4hrend des Praktikums an die Anleitung halten. Es ist verboten (und sinnlos), die fertige L\u00f6sung herunterzuladen. Allerdings kann es bei der anschlie\u00dfenden Selbstein\u00fcbung n\u00fctzlich sein, die fertige L\u00f6sung zu \u00fcberpr\u00fcfen, daher stellen wir sie zur Verf\u00fcgung.

                                Die L\u00f6sung ist auf GitHub [hier] verf\u00fcgbar (https://github.com/bmeviauab00/lab-adatkezeles-megoldas). Der einfachste Weg, es herunterzuladen, ist, es von der Kommandozeile aus mit dem Befehl git clone auf Ihren Computer zu klonen:

                                git clone https://github.com/bmeviauab00/lab-adatkezeles-megoldas

                                Sie m\u00fcssen Git auf Ihrem Rechner installiert haben, weitere Informationen hier.

                                "},{"location":"labor/7-adatkezeles/index_ger/#einfuhrung","title":"Einf\u00fchrung","text":"Hinweis f\u00fcr Praktiker

                                Dieses Kapitel muss in einer Praxis nicht ausf\u00fchrlich genug erkl\u00e4rt werden, aber die wichtigsten Begriffe sollten kurz erl\u00e4utert werden.

                                "},{"location":"labor/7-adatkezeles/index_ger/#adonet","title":"ADO.NET","text":"

                                F\u00fcr die Low-Level-Datenbankverwaltung auf der .NET-Plattform ist ADO.NET f\u00fcr den Zugriff auf relationale Datenbanken verf\u00fcgbar.

                                Bei der Verwendung von ADO.NET k\u00f6nnen Sie zwei verschiedene Datenzugriffsmodelle verwenden:

                                • Switch-basiertes Modell
                                • Unverbundenes Modell

                                Wenn Sie auf die beiden Bl\u00f6cke unten klicken, k\u00f6nnen Sie sich einen \u00dcberblick \u00fcber die Grunds\u00e4tze der beiden Modelle verschaffen.

                                Principles of the Connection-based Model

                                Die Idee ist, die Datenbankverbindung die ganze Zeit \u00fcber offen zu halten, w\u00e4hrend die Daten abgefragt und ge\u00e4ndert werden und die \u00c4nderungen dann in die Datenbank zur\u00fcckgeschrieben werden. DataReader-Objekte k\u00f6nnen zur L\u00f6sung dieses Problems verwendet werden (siehe sp\u00e4ter). Der Vorteil dieser L\u00f6sung liegt in ihrer Einfachheit (einfacheres Programmiermodell und Wettbewerbsmanagement). Der Nachteil dieser L\u00f6sung ist, dass aufgrund der st\u00e4ndig aufrechtzuerhaltenden Netzverbindung Skalierbarkeitsprobleme auftreten k\u00f6nnen. Dies bedeutet, dass bei einer gro\u00dfen Anzahl von gleichzeitigen Benutzerzugriffen auf den Data Controller eine gro\u00dfe Anzahl von Datenbankverbindungen st\u00e4ndig aktiv ist, was eine kostspielige Ressource in Bezug auf die Leistung von Data Controller-Systemen darstellt. Daher ist es ratsam, w\u00e4hrend der Entwicklung zu versuchen, die Datenbankverbindungen so bald wie m\u00f6glich zu schlie\u00dfen.

                                Vorteile des Modells:

                                • Leichtere Verwaltung des Wettbewerbs
                                • Die Daten sind \u00fcberall auf dem neuesten Stand

                                Hinweis: Diese Vorteile gelten nur, wenn der Datenbankzugriff strengen Sperren unterliegt, die von dem f\u00fcr die Datenverarbeitung Verantwortlichen verwendet werden - wir k\u00f6nnen dies kontrollieren, indem wir den entsprechenden Transaktionsisolierungsgrad w\u00e4hrend des Zugriffs einstellen. (Die Techniken werden in sp\u00e4teren Studien beschrieben.)

                                Benachteiligungen:

                                • Kontinuierliche Netzwerkverbindung
                                • Mangelnde Skalierbarkeit
                                Principles of the Connectionless Model

                                Im Gegensatz zum verbindungsbasierten Modell wird keine Datenbankverbindung aufrechterhalten, wenn Daten im Speicher angezeigt und ge\u00e4ndert werden. Die wichtigsten Schritte sind demnach folgende: Nach dem Verbindungsaufbau und dem Abruf der Daten wird die Verbindung sofort wieder beendet. Die Daten werden dann in der Regel angezeigt, und der Benutzer hat die M\u00f6glichkeit, die Daten zu \u00e4ndern (Datens\u00e4tze hinzuzuf\u00fcgen, zu \u00e4ndern oder zu l\u00f6schen, je nach Bedarf). Wenn wir \u00c4nderungen speichern, stellen wir die Datenverbindung wieder her, speichern die \u00c4nderungen in der Datenbank und schlie\u00dfen die Verbindung. Nat\u00fcrlich setzt das Modell voraus, dass Sie zwischen der Abfrage und dem Festschreiben von \u00c4nderungen - wenn Sie nicht mit der Datenbank verbunden sind - die Daten und \u00c4nderungen im Speicher halten. Eine sehr bequeme L\u00f6sung daf\u00fcr ist in der ADO.NET-Umgebung die Verwendung von DataSet -Objekten.

                                Vorteile des Modells:

                                • Keine st\u00e4ndige Netzwerkverbindung erforderlich
                                • Skalierbarkeit

                                Benachteiligungen

                                • Die Daten sind nicht immer auf dem neuesten Stand
                                • Zusammenst\u00f6\u00dfe sind m\u00f6glich

                                Kommentar: Es gibt mehrere M\u00f6glichkeiten, Objekte und damit verbundene \u00c4nderungen im Ged\u00e4chtnis zu speichern. Das DataSet ist nur eine der m\u00f6glichen Techniken. Sie k\u00f6nnen aber auch gew\u00f6hnliche Objekte und .NET-Technologien (z. B. Entity Framework Core) verwenden, die die Verwaltung dieser Objekte erleichtern und fortschrittlicher sind als ADO.NET.

                                "},{"location":"labor/7-adatkezeles/index_ger/#das-beziehungsorientierte-modell","title":"Das beziehungsorientierte Modell","text":"

                                Im Labor werden wir das beziehungsbasierte Modell kennenlernen.

                                Das grundlegende Verfahren ist wie folgt:

                                1. Erstellen Sie eine Verbindung zwischen der Anwendung und dem Datenbankmanagementsystem (mit dem ObjektConnection ).
                                2. Erstellen Sie die auszuf\u00fchrende SQL-Anweisung (unter Verwendung des ObjektsCommand ).
                                3. F\u00fchren Sie einen Befehl aus (unter Verwendung des ObjektsCommand ).
                                4. Verarbeitung des zur\u00fcckgegebenen Datensatzes f\u00fcr Abfragen (unter Verwendung desDataReader Objekts). F\u00fcr Modifikatorbefehle ist dies nat\u00fcrlich nicht notwendig.
                                5. Schlie\u00dfen einer Verbindung.

                                Wie oben zu sehen ist, hat die Kommunikation mit der Datenbank in diesem Modell drei Hauptkomponenten:

                                • Verbindung
                                • Befehl
                                • Datenleser

                                Diese Komponenten werden als Klasse dargestellt, deren datenbankunabh\u00e4ngiger Teil im BCL-Namensraum System.Data.Common unter DbConnection, DbCommand bzw. DbDataReader zu finden ist. Es handelt sich um abstrakte Klassen, und es ist die Aufgabe der Anbieter von Datenbankmanagern, Versionen zu schreiben, die bestimmte von ihnen abgeleitete Datenbankmanager unterst\u00fctzen.

                                Alle drei ADO.NET-Komponenten unterst\u00fctzen das Dispose-Muster, so dass sie im using -Block verwendet werden k\u00f6nnen - lassen Sie uns sie auf diese Weise verwenden, wann immer wir k\u00f6nnen. Der Datenbankmanager befindet sich in der Regel auf einem anderen Rechner als der, auf dem unser Code l\u00e4uft (nicht im Labor :)), also betrachten Sie sie als entfernte Netzwerkressourcen.

                                Die Version, die Microsoft SQL Server unterst\u00fctzt, finden Sie im NuGet-Paket Microsoft.Data.SqlClient in Klassen mit dem Pr\u00e4fix \"Sql\" (SqlConnection, SqlCommand und SqlDataReader).

                                Andere Anbieter packen ihre eigene Version in eine separate DLL(s), die daraus resultierende Komponente wird als Datenanbieter bezeichnet. Einige Beispiele ohne Anspruch auf Vollst\u00e4ndigkeit:

                                • PostgreSQL
                                • SQLite
                                • Oracle
                                "},{"location":"labor/7-adatkezeles/index_ger/#verbindung","title":"Verbindung","text":"

                                Dies ist die Verbindung zwischen unserem Programm und dem Datenbankmanagementsystem. Um sie zu initialisieren, ben\u00f6tigen wir einen Verbindungsstring, der dem Treiber die notwendigen Informationen zum Aufbau der Verbindung gibt. Das interne Format variiert von Datenbankanbieter zu Datenbankanbieter(weitere Informationen).

                                Wenn eine neue Connection instanziiert wird, ist nicht garantiert, dass tats\u00e4chlich eine neue Verbindung zur Datenbank hergestellt wird. Die Treiber verwenden in der Regel Connection Pooling, \u00e4hnlich wie Thread Pooling, um fr\u00fchere (derzeit nicht verwendete) Verbindungen wieder zu verwenden.

                                Connection ist eine besonders teure, nicht verwaltete Ressource, daher muss sichergestellt werden, dass sie so schnell wie m\u00f6glich geschlossen wird, wenn sie nicht mehr ben\u00f6tigt wird (z. B. durch den Aufruf von Dispose(), was in den meisten F\u00e4llen am einfachsten mit dem using -Block geschieht).

                                "},{"location":"labor/7-adatkezeles/index_ger/#befehl","title":"Befehl","text":"

                                So k\u00f6nnen wir \"Anweisungen\" f\u00fcr den Datenbankmanager formulieren. Diese m\u00fcssen wir in SQL formulieren. Commandmuss einen Link-Set haben - hier wird der Befehl ausgef\u00fchrt. Der Befehl kann verschiedene Ergebnisse haben, also f\u00fchren wir den Befehl mit verschiedenen Funktionen aus:

                                • ExecuteReader: Abfrage einer Ergebnismenge
                                • ExecuteScalar: Abfrage des Einzelwerts
                                • ExecuteNonQuery: Kein R\u00fcckgabewert (z.B: INSERT, UPDATE und DELETE), aber die Anzahl der von der Operation betroffenen Datens\u00e4tze wird zur\u00fcckgegeben
                                "},{"location":"labor/7-adatkezeles/index_ger/#datenleser","title":"Datenleser","text":"

                                Wenn das Ergebnis des Befehls eine Ergebnismenge ist, k\u00f6nnen wir diese Komponente verwenden, um die Daten zu lesen. Die Ergebnismenge kann als Tabelle angezeigt werden, Data Reader kann Zeile f\u00fcr Zeile (eine nach der anderen!) durch sie navigieren. Der Cursor befindet sich jeweils in einer Zeile. Sobald die gew\u00fcnschten Daten aus der Zeile gelesen wurden, kann der Cursor eine Zeile weiterbewegt werden. Wir k\u00f6nnen nur aus der aktuellen Zeile lesen. Zu Beginn steht der Cursor nicht in der ersten Zeile, Sie m\u00fcssen ihn einmal bewegen, um ihn in die erste Zeile zu setzen.

                                Hinweis: Die clientseitige Navigation erfolgt im Speicher, sie hat nichts mit den serverseitigen Cursors zu tun, die von jedem Controller unterst\u00fctzt werden.

                                "},{"location":"labor/7-adatkezeles/index_ger/#1-aufgabe-vorbereitung-der-datenbank","title":"1. Aufgabe - Vorbereitung der Datenbank","text":"

                                Zun\u00e4chst brauchen wir einen Datenbankmanager. Dies wird durch voll funktionsf\u00e4hige Datenbankmanager erreicht, die in einer realen Umgebung auf speziellen Servern laufen und von Datenbankadministratoren \u00fcberwacht werden. W\u00e4hrend der Entwicklungszeit, f\u00fcr lokale Tests, ist es jedoch bequemer, einen Datenbankmanager f\u00fcr Entwickler zu verwenden. Als Teil der Visual Studio-Installation erhalten Sie eine solche Datenbank-Engine, LocalDB, die eine vereinfachte Version des voll funktionsf\u00e4higen SQL Servers ist. Seine Hauptmerkmale sind:

                                • kann nicht nur mit Visual Studio, sondern auch separat installiert werden,
                                • die Datenbank-Engine ist fast vollst\u00e4ndig kompatibel mit dem vollwertigen Microsoft SQL Server,
                                • ist grunds\u00e4tzlich auf dem Rechner verf\u00fcgbar, auf dem es installiert ist,
                                • mehrere Instanzen k\u00f6nnen bei Bedarf erstellt werden, die Instanzen stehen im Wesentlichen dem Benutzer des erstellenden Betriebssystems zur Verf\u00fcgung (eine Instanz kann bei Bedarf von mehreren Benutzern gemeinsam genutzt werden),
                                • f\u00fcr die Verwaltung Ihrer eigenen Kopien (Erstellen, L\u00f6schen usw.) sind keine Administratorrechte erforderlich.
                                ssqllocaldb command line tool

                                In der Praxis brauchen wir das nicht, aber wir k\u00f6nnen das sqllocaldb command line tool verwenden, um Instanzen zu verwalten. Einige Befehle, die durch Eingabe nach sqllocaldb verwendet werden k\u00f6nnen:

                                Paracs Beschreibung info Liste der Instanzen, die f\u00fcr den aktuellen Benutzer sichtbar sind create \"locdb\" create a new instance named \"locdb\" l\u00f6schen \"locdb\" l\u00f6schen \"locdb\" \"locdb\" starten eine Instanz von \"locdb\" starten Stopp \"locdb\" Stopp \"locdb\"

                                Visual Studio installiert und startet auch LocalDB-Instanzen, so dass es sich lohnt, zu \u00fcberpr\u00fcfen, was Visual Studio standardm\u00e4\u00dfig sieht.

                                1. Starten Sie Visual Studio und w\u00e4hlen Sie SQL Server Object Explorer (SSOE) aus dem Men\u00fc Ansicht.
                                2. \u00d6ffnen Sie den SQL Server-Knoten. Wenn Sie andere Knoten darunter sehen, haben Sie einen erfolgreichen Fall, \u00f6ffnen Sie einen davon (dadurch wird die Instanz gestartet, falls sie noch nicht gestartet ist, Sie m\u00fcssen also m\u00f6glicherweise etwas warten).
                                3. Wenn nichts erscheint, gibt der Befehl mssqllocaldb info in der Befehlszeile die vorhandenen Instanzen zur\u00fcck. Klicken Sie mit der rechten Maustaste auf den Knoten SQL Server und w\u00e4hlen Sie SQL Server hinzuf\u00fcgen, dann geben Sie eine vorhandene Instanz an, z. B. (localdb)MSSQLLocalDB
                                4. W\u00e4hlen Sie im erscheinenden Knoten Datenbanken die Option Neue Datenbank und geben Sie einen Datenbanknamen ein. (Da in Praktika mehrere Sch\u00fcler denselben Betriebssystembenutzer verwenden k\u00f6nnen, empfiehlt es sich, den Neptun-Code als Namen zu verwenden).
                                5. Klicken Sie mit der rechten Maustaste auf den neuen Datenbankknoten und w\u00e4hlen Sie Neue Abfrage, wodurch sich ein neues Abfragefenster \u00f6ffnet.
                                6. \u00d6ffnen Sie das Skriptzur Initialisierung der Northwind-Datenbank oder laden Sie es herunter.
                                7. Kopieren Sie das vollst\u00e4ndige Skript in das Abfragefenster.
                                8. F\u00fchren Sie das Skript aus, indem Sie auf den kleinen gr\u00fcnen Pfeil*(Ausf\u00fchren*) klicken. Vergewissern Sie sich, dass eine gute Datenbank (die in Schritt 4 oben erstellt wurde) oben im Abfragefenster in der Dropdown-Liste ausgew\u00e4hlt ist!
                                9. Pr\u00fcfen Sie, ob Tabellen und Ansichten in unserer Datenbank ver\u00f6ffentlicht worden sind.
                                10. Sehen wir uns die wichtigsten Funktionen von SSOE an (Abruf von Daten aus Tabellen, Schemata usw.).

                                MSSQL-Verwaltungstools

                                In Visual Studio k\u00f6nnen Sie Datenbanken mit zwei Tools verwalten: dem Server Explorer und dem SQL Server Object Explorer. Ersteres ist ein allgemeineres Tool, das nicht nur Datenbanken, sondern auch andere Serverressourcen (z. B. Azure-Server) verwalten kann, w\u00e4hrend letzteres speziell auf die Datenbankverwaltung ausgerichtet ist. Auf beide kann \u00fcber das Men\u00fc Ansicht zugegriffen werden, und beide bieten \u00e4hnliche Datenbankverwaltungsfunktionen, weshalb wir in dieser Messung nur einen (SQL Server Object Explorer) verwenden werden.

                                Wenn Sie nicht \u00fcber die Visual Studio-Entwicklungsumgebung verf\u00fcgen, k\u00f6nnen Sie das (kostenlose) SQL Server Management Studio oder das kostenlose und plattform\u00fcbergreifende Azure Data Studio verwenden, um Ihre Datenbank zu verwalten.

                                "},{"location":"labor/7-adatkezeles/index_ger/#2-aufgabe-abfrage-mit-adonet-sqldatareader","title":"2. Aufgabe - Abfrage mit ADO.NET SqlDataReader","text":"

                                Die Aufgabe besteht darin, eine C#-Konsolenanwendung zu erstellen, die die Datens\u00e4tze der Northwind-Datenbanktabelle Shippers verwendet.

                                1. Erstellen Sie eine Konsolenanwendung in C#. Der Projekttyp sollte Console App und NICHT Console App (.NET Framework) sein:

                                  • Der Projektname sollte AdoExample lauten
                                  • Das Ziel-Framework sollte .NET 8 sein
                                  • Aktivieren Sie die Option Keine Top-Level-Anweisungen verwenden
                                2. Suchen Sie die Verbindungszeichenfolge aus der SSOE: Klicken Sie mit der rechten Maustaste auf unsere Datenbankverbindung (in der Abbildung unten rot markiert) / Eigenschaften.

                                3. Kopieren Sie die Eigenschaft Connection String aus dem Fenster Properties in eine Variable der Klasse Program.

                                  private const string ConnString = @\"Data Source=(localdb)MSSQLLocalDB;Initial Catalog=neptun;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False\";\n

                                  SQL Server-Verbindungsstringformat

                                  Bei MSSQL enth\u00e4lt der Schl\u00fcssel des Verbindungsstrings durch Semikolon getrennte Wertepaare. Unter dem Schl\u00fcssel Data Source steht der Name der SQL-Server-Instanz und unter dem Schl\u00fcsselInitial Catalog der Name der Datenbank. Die Option Integrated Security=true steht f\u00fcr die Windows-Authentifizierung.

                                  @- string (C# verbatim string)

                                  @ ist ein Sonderzeichen (verbatim identifier), das hier verwendet wird, um zu vermeiden, dass das Backslash-Zeichen (``) in der Verbindungszeichenfolge als Escape-Zeichen interpretiert wird.

                                4. F\u00fcgen Sie das NuGet-Paket Microsoft.Data.SqlClient zum Projekt hinzu. Es gibt zwei M\u00f6glichkeiten, dies zu tun:

                                  • A) Visual Studio in NuGet:
                                    1. Projekte rechte Taste / NuGet-Pakete verwalten..., auf der erscheinenden Seite wechseln Sie zu Durchsuchen.
                                    2. Geben Sie in das Suchfeld Microsoft.Data.SqlClient.
                                    3. W\u00e4hlen Sie im Feld Version die Version 5.0.1 aus (im Labor w\u00e4hlen wir diese Version, weil sie sich im NuGet-Cache auf den Rechnern befindet; in der Praxis bevorzugen wir die Version Latest stable).
                                  • B) Wir kopieren den folgenden Paketverweis in die Projektdatei:

                                    <ItemGroup>\n    <PackageReference Include=\"Microsoft.Data.SqlClient\" Version=\"5.0.1\" />\n</ItemGroup>\n

                                  NuGet Package Manager

                                  NuGet ist ein Online-Paketverwaltungssystem, mit dem Sie externe Abh\u00e4ngigkeiten und Klassenbibliotheken in versionierter Form mit Ihren .NET-basierten Projekten verkn\u00fcpfen k\u00f6nnen. Lesen Sie mehr \u00fcber die erste Pr\u00e4sentation.

                                5. Schreiben Sie eine Abfragefunktion, die alle Lieferanten abfragt:

                                  private static void GetShippers()\n{\n    using (var conn = new SqlConnection(ConnString))\n    using (var command = new SqlCommand(\"SELECT ShipperID, CompanyName, Phone FROM Shippers\", conn))\n    {\n        conn.Open();\n        Console.WriteLine(\"{0,-10}{1,-20}{2,-20}\", \"ShipperID\", \"CompanyName\", \"Phone\");\n        Console.WriteLine(new string('-', 60));\n        using (SqlDataReader reader = command.ExecuteReader())\n        {\n            while (reader.Read())\n            {\n                Console.WriteLine(\n                    $\"{reader[\"ShipperID\"],-10}\" +\n                    $\"{reader[\"CompanyName\"],-20}\" +\n                    $\"{reader[\"Phone\"],-20}\");\n            }\n        }\n    }\n}\n

                                  Der beziehungsorientierte Modellprozess:

                                  • Verbindung initialisieren, Befehl
                                  • Einen Kontakt \u00f6ffnen
                                  • Ausf\u00fchren eines Befehls
                                  • Verarbeitung des Ergebnisses
                                  • Abschaltung, Reinigung

                                  Einige Hinweise zum Code

                                  • DataReadererh\u00e4lt man als Ergebnis der Ausf\u00fchrung des Befehls, nicht durch direktes Kopieren
                                  • Sie m\u00fcssen die Verbindung \u00f6ffnen, bevor Sie den Befehl ausf\u00fchren
                                  • Wenn DbConnection kopiert wird, wird die Verbindung nicht ge\u00f6ffnet (keine Netzwerkkommunikation)
                                  • Die Funktion DataReader.Read() zeigt an, ob noch Daten in der Ergebnismenge vorhanden sind
                                  • Sie k\u00f6nnen DataReadermit den Namen der Spalten in der Ergebnismenge indizieren - das Ergebnis wird object sein, wenn Sie also einen spezifischeren Typ ben\u00f6tigen, m\u00fcssen Sie einen Cast durchf\u00fchren
                                  • Der Compiler interpretiert den SQL-Befehlstext nicht (es ist nur ein String), nur die Datenbank tut dies, daher erhalten Sie im Falle eines SQL-Fehlers eine Laufzeitausnahme
                                  • Beachten Sie, dass, wenn sich das Datenbankschema \u00e4ndert, z.B. wenn sich das Datenbankschema \u00e4ndert, z. B. nach der Umbenennung einer Spalte, m\u00fcssen Sie an vielen Stellen im Code Strings manuell umschreiben
                                  • Mit $k\u00f6nnen Sie String-Interpolation verwenden, d. h. Ausdr\u00fccke einbetten, die direkt im String ausgewertet werden (C# 6-F\u00e4higkeit). $@ erm\u00f6glicht es Ihnen, mehrzeilige String-Interpolationsausdr\u00fccke zu schreiben (Sie m\u00fcssen den Zeilenumbruch zwischen -k einf\u00fcgen, sonst wird er in der Ausgabe angezeigt). Interessante Tatsache: Ab C# 8 k\u00f6nnen Sie $- und @-Zeichen in beliebiger Reihenfolge schreiben, daher sind auch $@ und @$ korrekt.
                                  • Das using-Schl\u00fcsselwort kann als einzeiliger Ausdruck anstelle einer Blockanweisung verwendet werden. In diesem Fall reicht das Ende des using-Blocks bis zum enthaltenden Block (in unserem Fall das Ende der Funktion). Dies reduziert die Anzahl der Einr\u00fcckungen, sollte aber kein automatischer Reflex sein, da es sinnvoll sein kann, die Freigabe von Ressourcen fr\u00fcher als am Ende des enthaltenden Blocks zu erzwingen.

                                    private static void GetShippers()\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\"SELECT ShipperID, CompanyName, Phone FROM Shippers\", conn);\n\n    conn.Open();\n\n    Console.WriteLine(\"{0,-10}{1,-20}{2,-20}\",\"ShipperID\", \"CompanyName\", \"Phone\");\n    Console.WriteLine(new string('-', 60));\n\n    using var reader = command.ExecuteReader();\n    while (reader.Read())\n    {\n        Console.WriteLine(\n            $\"{reader[\"ShipperID\"],-10}\" +\n            $\"{reader[\"CompanyName\"],-20}\" +\n            $\"{reader[\"Phone\"],-20}\");\n    }\n}\n

                                    Diese Methode wird im Folgenden verwendet, um Einz\u00fcge und Klammern zu speichern.

                                6. Rufen Sie unsere neue Funktion von Main aus auf.

                                  private static void Main(string[] args)\n{\n    GetShippers();\n}\n
                                7. Probieren wir die App aus. Wir sollten SQL zerst\u00f6ren und es auf diese Weise versuchen.

                                "},{"location":"labor/7-adatkezeles/index_ger/#3-aufgabe-einfugen-mit-sql-anweisung","title":"3. Aufgabe - Einf\u00fcgen mit SQL-Anweisung","text":"
                                1. Schreiben Sie eine Funktion zum Einf\u00fcgen eines neuen Lieferanten in die Datenbank:

                                  private static void InsertShipper(string companyName, string phone)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\n        \"INSERT INTO Shippers(CompanyName, Phone) VALUES(@name,@phone)\", conn);\n    command.Parameters.AddWithValue(\"@name\", companyName);\n    command.Parameters.AddWithValue(\"@phone\", phone);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n\n    Console.WriteLine($\"{affectedRows} rows affected\");\n}\n

                                  Hier m\u00fcssen wir SQL schreiben, das unter Verwendung von Variablenwerten kompiliert wurde, die wir von au\u00dfen erhalten haben. Um die Zeichenkette zusammenzusetzen, h\u00e4tten wir einfach den Operator f\u00fcr die Zeichenkettenverkettung, die Zeichenketteninterpolation oder string.Formatverwenden k\u00f6nnen, aber das birgt ein Sicherheitsrisiko (SQL Injection - siehe unten f\u00fcr weitere Einzelheiten) - NIEMALS!!! SQL mit einer Zeichenkettenoperation zusammensetzen. Stattdessen sollten wir SQL so schreiben, dass wir an die Stelle der Werte von Variablen Parameterreferenzen setzen. Bei SQL Server lautet die Syntax des Verweises @parametername.

                                  Um den Befehl auszuf\u00fchren, m\u00fcssen wir auch die Werte der Parameter an die Datenbank \u00fcbergeben, da diese die Ersetzung der Werte f\u00fcr die Parameter vornimmt.

                                  Die Ausgabe des Einf\u00fcgebefehls ist keine Ergebnismenge, daher muss er mit ExecuteNonQuery ausgef\u00fchrt werden, das die Anzahl der eingef\u00fcgten Zeilen zur\u00fcckgibt.

                                2. Rufen Sie unsere neue Funktion von Main aus auf.

                                  GetShippers();\nInsertShipper(\"Super Shipper\",\"49-98562\");\nGetShippers();\n
                                3. Probieren wir die Anwendung aus und pr\u00fcfen wir in der Konsole und in der SSOE, ob die neue Zeile eingef\u00fcgt wurde. F\u00fcr eine schnelle und bequeme \u00dcberpr\u00fcfung in SSOE w\u00e4hlen Sie Daten anzeigen aus dem Kontextmen\u00fc der Tabelle Shippers.

                                "},{"location":"labor/7-adatkezeles/index_ger/#4-aufgabe-modifikation-durch-gespeicherte-prozedur","title":"4. Aufgabe - Modifikation durch gespeicherte Prozedur","text":"
                                1. Studieren Sie den in SSOE Product_Update gespeicherten Verfahrenscode. \u00d6ffnen Sie dazu den Knoten \"Gespeicherte Prozeduren\" unter \"Programmierbarkeit\" und w\u00e4hlen Sie dann \" Code anzeigen \" aus dem Kontextmen\u00fc der gespeicherten Prozedur unter Product_Update.

                                  Programmcode in der Datenbank

                                  Die gro\u00dfen Datenverwaltungssysteme bieten die M\u00f6glichkeit, Programmcode in der Datenbank des Datenverwalters selbst zu definieren. Diese werden als gespeicherte Verfahren bezeichnet. Die Sprache ist abh\u00e4ngig von der Datensteuerung, aber f\u00fcr MSSQL ist es T-SQL.

                                  Heutzutage wird die Praxis, ernsthafte Gesch\u00e4ftslogik in die Datenbank zu packen, immer mehr aus der Industrie verdr\u00e4ngt, da der Werkzeugsatz dieser SQL-Dialekte nun viel begrenzter ist als der einer h\u00f6heren Programmiersprache (C#, Java). Dar\u00fcber hinaus wird die Testbarkeit des Systems durch die Verwendung von gespeicherten Prozeduren stark beeintr\u00e4chtigt. Dennoch kann es manchmal sinnvoll sein, einen Teil der Logik in der Datenbank zu belassen, wenn wir den Vorteil nutzen wollen, dass unser Code in der N\u00e4he der Daten l\u00e4uft, z. B. wenn wir f\u00fcr eine einfache Massenpflege von Daten nicht \u00fcber das Netz gehen wollen.

                                2. Schreiben Sie eine Funktion, die diese gespeicherte Prozedur aufruft

                                  private static void UpdateProduct(int productID, string productName, decimal price)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\"Product_Update\", conn);\n\n    command.CommandType = CommandType.StoredProcedure;\n\n    command.Parameters.AddWithValue(\"@ProductID\", productID);\n    command.Parameters.AddWithValue(\"@ProductName\", productName);\n    command.Parameters.AddWithValue(\"@UnitPrice\", price);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n\n    Console.WriteLine($\"{affectedRows} rows affected\");\n}\n

                                  Der Commandmusste der Name der gespeicherten Prozedur gegeben werden und der Typ des Befehls musste ge\u00e4ndert werden, ansonsten ist er strukturell \u00e4hnlich wie der vorherige Einf\u00fcgecode.

                                3. Rufen Sie unsere neue Funktion von Main aus auf, z. B. mit den folgenden Parametern:

                                  UpdateProduct(1, \"MyProduct\", 50);\n
                                4. Probieren wir die Anwendung aus und pr\u00fcfen in der Konsole und in SSOE, ob das Produkt mit der ID 1 ge\u00e4ndert wurde.

                                "},{"location":"labor/7-adatkezeles/index_ger/#5-aufgabe-sql-injektion","title":"5. Aufgabe - SQL-Injektion","text":"
                                1. Schreiben wir die Einf\u00fcgefunktion, um SQL mit Hilfe der String-Interpolation zu kompilieren.

                                  private static void InsertShipper2(string companyName, string phone)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\n        $\"INSERT INTO Shippers(CompanyName, Phone) VALUES('{companyName}','{phone}')\",\n        conn);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n    Console.WriteLine($\"{affectedRows} row(s) inserted\");\n}\n
                                2. Rufen Sie unsere neue Funktion von Main mit \"speziellen\" Parametern auf.

                                  InsertShipper2(\"Super Shipper\", \"49-98562'); DELETE FROM Shippers;--\");\n

                                  Der zweite Parameter wird so gesetzt, dass die urspr\u00fcngliche Anweisung geschlossen wird, dann werden alle (!!!) Wir k\u00f6nnen SQL schreiben und schlie\u00dflich den Rest der urspr\u00fcnglichen Anweisung auskommentieren (--).

                                3. Versuchen Sie die Anwendung, sollten Sie einen Fehler erhalten, der angibt, dass ein Lieferant aufgrund eines Fremdschl\u00fcsselverweises nicht gel\u00f6scht werden kann.

                                  Also DELETE FROM ist auch gelaufen! Pr\u00fcfen wir mit dem Debugger (z. B. durch Anhalten bei der Anweisung conn.Open ), wie das endg\u00fcltige SQL (command.CommandText) lautet.

                                  Lektionen gelernt:

                                  • Splei\u00dfen Sie SQL NICHT programmatisch (egal mit welcher Methode), da dies Ihren Code f\u00fcr SQL-Injection-basierte Angriffe anf\u00e4llig macht.
                                  • Die Datenbank sollte das endg\u00fcltige SQL auf der Grundlage der SQL-Parameter kompilieren, denn dann ist gew\u00e4hrleistet, dass die Parameterwerte nicht als SQL interpretiert werden (selbst wenn SQL eingegeben wird). Verwenden Sie parametrisiertes SQL oder gespeicherte Prozeduren.
                                  • Verwenden Sie Datenbankbeschr\u00e4nkungen, z. B. zum Schutz vor versehentlichem L\u00f6schen.
                                  • Konfigurieren Sie Benutzer in der Datenbank mit unterschiedlichen Rechten. Der in der Verbindungszeichenfolge Ihres Programms angegebene Benutzer sollte nur die f\u00fcr den Betrieb erforderlichen Mindestrechte haben. In unserem Fall haben wir keinen Benutzer angegeben, wir werden uns als Windows-Benutzer verbinden.
                                4. Rufen wir nun die urspr\u00fcngliche (d.h. die sichere, mit SQL-Parametern versehene) Einf\u00fcgefunktion mit der \"speziellen\" Parametrisierung auf, um zu sehen, ob der Schutz funktioniert:

                                  InsertShipper(\"Super Shipper\", \"49-98562'); DELETE FROM Shippers;--\");\nInsertShipper(\"XXX');DELETE FROM Shippers;--\", \"49-98562\");\n

                                  Der erste passt nicht in die Gr\u00f6\u00dfenbeschr\u00e4nkung, der zweite l\u00e4uft, aber nur ein \"seltsam\" benannter Anbieter ist enthalten. Der Parameterwert wurde tats\u00e4chlich als Wert und nicht als SQL interpretiert. Nicht so wie hier:

                                "},{"location":"labor/7-adatkezeles/index_ger/#6-aufgabe-loschen","title":"6. Aufgabe - L\u00f6schen","text":"
                                1. Schreiben Sie eine neue Funktion zum L\u00f6schen eines bestimmten Lieferanten.

                                  private static void DeleteShipper(int shipperID)\n{\n    using var conn = new SqlConnection(ConnString);\n    using var command = new SqlCommand(\"DELETE FROM Shippers WHERE ShipperID = @ShipperID\", conn);\n    command.Parameters.AddWithValue(\"@ShipperID\", shipperID);\n\n    conn.Open();\n\n    int affectedRows = command.ExecuteNonQuery();\n\n    Console.WriteLine($\"{affectedRows} row(s) affected\");\n}\n
                                2. Rufen wir unsere neue Funktion von Main auf, parametrisiert mit, sagen wir, 1.

                                3. Probieren wir die App aus. Sie werden wahrscheinlich eine Ausnahme erhalten, da ein Verweis (mit Fremdschl\u00fcssel-Beschr\u00e4nkung) auf den Datensatz besteht.
                                4. In SSOE suchen wir nach der ID eines Lieferanten, den wir beauftragt haben. \u00dcbergeben wir diesen Bezeichner an die L\u00f6schfunktion - sie kann ihn l\u00f6schen, da es keinen Verweis auf ihn gibt.

                                L\u00f6schstrategien

                                Es zeigt sich, dass das L\u00f6schen aufgrund der Fremdschl\u00fcssel-Beschr\u00e4nkungen eine sehr riskante und unvorhersehbare Operation ist. Einige M\u00f6glichkeiten, die L\u00f6schung zu verwalten:

                                • Wir erlauben keine L\u00f6schung: Wenn der zu l\u00f6schende Datensatz referenziert ist, gibt die Datenbank einen Fehler zur\u00fcck (wie oben gezeigt).
                                • L\u00f6schkaskade - die Fremdschl\u00fcssel-Beschr\u00e4nkung kann so eingestellt werden, dass der referenzierte Datensatz gel\u00f6scht wird, wenn der referenzierte Datensatz gel\u00f6scht wird. Dies f\u00fchrt oft dazu, dass alle unsere Fremdschl\u00fcssel-Beschr\u00e4nkungen so aussehen, und eine (versehentliche) L\u00f6schung kann die gesamte Datenbank ausl\u00f6schen, d.h. die Auswirkungen der L\u00f6schung sind schwer vorherzusagen.
                                • NULL der Referenz - die Fremdschl\u00fcssel-Beschr\u00e4nkung kann so eingestellt werden, dass das Fremdschl\u00fcsselfeld des referenzierten Datensatzes auf NULL gesetzt wird, wenn der referenzierte Datensatz gel\u00f6scht wird. Nur anwendbar, wenn das Fremdschl\u00fcsselfeld in Ihrem Modell auf NULL gesetzt werden kann.
                                • logisches L\u00f6schen (soft delete) - anstelle eines L\u00f6schvorgangs wird nur eine Flaggenspalte (z.B. IsDeleted) gesetzt. Der Vorteil ist, dass Sie sich nicht mit Fremdschl\u00fcssel-Beschr\u00e4nkungen befassen m\u00fcssen und die gel\u00f6schten Daten bei Bedarf verf\u00fcgbar sind (z. B. beim R\u00fcckg\u00e4ngigmachen des L\u00f6schvorgangs). Der Vorgang ist jedoch kompliziert, da man sich damit befassen muss, wie und wann gel\u00f6schte Datens\u00e4tze gefiltert werden sollen (z. B. damit sie nicht in der Schnittstelle oder in der Statistik erscheinen) oder wie man damit umgeht, wenn ein nicht gel\u00f6schter Datensatz auf einen gel\u00f6schten Datensatz verweist.
                                "},{"location":"labor/7-adatkezeles/index_ger/#screenshots","title":"Screenshots","text":"

                                Die oben genannten grundlegenden ADO.NET-Operationen in der hier gezeigten Form werden aus zwei Gr\u00fcnden selten verwendet (auch wenn dieser Ansatz die beste Leistung bietet):

                                • Schlechte Typisierung (das Einlesen der Daten eines Datensatzes in die Eigenschaften einer Klasse ist sehr umst\u00e4ndlich, Cast usw.)
                                • SQL kodiert in Strings (Fehler aufgrund von Tippfehlern werden erst zur Laufzeit erkannt)

                                Ersteres kann durch verschiedene Komponenten gel\u00f6st werden, die ADO.NET erg\u00e4nzen, wie z.B.:

                                • Dapper
                                • PetaPoco

                                Diese L\u00f6sungen bieten mehr Komfort bei minimalen Leistungseinbu\u00dfen.

                                Beide Probleme werden durch ORM-Systeme (Object-Relational-Mapping) gel\u00f6st, die jedoch einen h\u00f6heren Overhead haben als die oben genannten L\u00f6sungen. ORMs erstellen ein Mapping zwischen der Datenbank und unseren OO-Klassen und verwenden dieses Mapping, um Datenbankoperationen zu vereinfachen. Unsere in Typcode geschriebenen Operationen mit unseren Klassen werden automatisch in die entsprechenden Datenbankoperationen \u00fcbersetzt, so dass unser In-Memory-Objektmodell mit der Datenbank synchronisiert wird. ORMs verwenden daher ein verbindungsloses Modell. Besser bekannte .NET ORMs:

                                • ADO.NET DataSet - ORM der ersten Generation, jetzt sehr selten verwendet
                                • Entity Framework 6.x - das am h\u00e4ufigsten verwendete ORM-Framework im (alten) .NET Framework
                                • Entity Framework Core (EF Core) - derzeit das wichtigste .NET ORM (Open Source)
                                • NHibernate - die .NET-Portierung von Hibernate f\u00fcr Java (Open Source)

                                Das Entity Framework Core wird in der Spezialisierung Data Driven Systems und im Wahlfach Software Development on .NET platform ausf\u00fchrlicher behandelt.

                                "},{"location":"labor/old-6-doc-view/","title":"6. Document-View architekt\u00fara","text":""},{"location":"labor/old-6-doc-view/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                                A gyakorlat c\u00e9ljai:

                                • UML alap\u00fa tervez\u00e9s \u00e9s n\u00e9h\u00e1ny tervez\u00e9si minta alkalmaz\u00e1sa
                                • A Document-View architekt\u00fara alkalmaz\u00e1sa a gyakorlatban
                                • UserControl szerep\u00e9nek bemutat\u00e1sa Window Forms alkalmaz\u00e1sokban, Document-View architekt\u00fara eset\u00e9n
                                • A grafikus megjelen\u00edt\u00e9s elveinek gyakorl\u00e1sa Window Forms alkalmaz\u00e1sokban (Paint esem\u00e9ny, Invalidate, Graphics haszn\u00e1lata)

                                A kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sok \u00e9s kor\u00e1bbi gyakorlatok anyaga:

                                • UML alap\u00fa modellez\u00e9s (1. gyakorlat)
                                • Windows Forms alkalmaz\u00e1sfejleszt\u00e9s
                                • Szoftverarchitekt\u00far\u00e1k (Document-View architekt\u00fara)
                                "},{"location":"labor/old-6-doc-view/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                                A gyakorlat elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                                • Visual Studio 2022
                                • Windows 10 vagy Windows 11 oper\u00e1ci\u00f3s rendszer (Linux \u00e9s macOS nem alkalmas)
                                "},{"location":"labor/old-6-doc-view/#a-gyakorlat-menete","title":"A gyakorlat menete","text":"

                                Az al\u00e1bbiak szerint fogunk dolgozni:

                                • A feladat/c\u00e9lok r\u00f6vid ismertet\u00e9se: egy interakt\u00edv fonteditor (bet\u0171t\u00edpus-szerkeszt\u0151) megtervez\u00e9se
                                • A k\u00e9sz alkalmaz\u00e1st futtatva a feladat (a k\u00e9sz alkalmaz\u00e1s m\u0171k\u00f6d\u00e9s\u00e9nek) ismertet\u00e9se
                                • Az alkalmaz\u00e1s architekt\u00far\u00e1j\u00e1nak megtervez\u00e9se (oszt\u00e1lydiagram elk\u00e9sz\u00edt\u00e9se)
                                • A k\u00e9sz alkalmaz\u00e1s forr\u00e1sk\u00f3dj\u00e1nak alapj\u00e1n n\u00e9h\u00e1ny fontosabb forgat\u00f3k\u00f6nyv megval\u00f3s\u00edt\u00e1s\u00e1nak \u00e1ttekint\u00e9se
                                Megjegyz\u00e9s gyakorlatvezet\u0151k sz\u00e1m\u00e1ra

                                A gyakorlat elej\u00e9n t\u00f6lts\u00fck le a k\u00e9sz alkalmaz\u00e1st (innen kl\u00f3nozzuk ki: https://github.com/bmeviauab00/lab-docview-megoldas). A hallgat\u00f3k ekkor m\u00e9g ne t\u00f6lts\u00e9k le, ne ezt kattintgass\u00e1k, majd csak a gyakorlat m\u00e1sodik r\u00e9sz\u00e9ben. A gyakorlatvezet\u0151knek viszont sz\u00fcks\u00e9ge lesz r\u00e1, mert ennek seg\u00edts\u00e9g\u00e9vel t\u00f6rt\u00e9nik a feladat bemutat\u00e1sa.

                                "},{"location":"labor/old-6-doc-view/#1-feladat-a-feladat-ismertetese","title":"1. Feladat - A feladat ismertet\u00e9se","text":"

                                Interakt\u00edv FontEditor (bet\u0171t\u00edpus szerkeszt\u0151) k\u00e9sz\u00edt\u00e9se, amelyben lehet szerkeszteni a karaktereket, \u00e9s az aktu\u00e1lis bet\u0171k\u00e9szlet alapj\u00e1n tetsz\u0151leges p\u00e9ldasz\u00f6veg megjelen\u00edthet\u0151. Az alkalmaz\u00e1s felhaszn\u00e1l\u00f3i fel\u00fclete fut\u00e1s k\u00f6zben:

                                A k\u00f6vetkez\u0151 funkci\u00f3kat kell t\u00e1mogatnia:

                                • T\u00f6bb bet\u0171t\u00edpus egyidej\u0171 szerkeszt\u00e9se. Ez egyes bet\u0171t\u00edpusok k\u00fcl\u00f6n tab oldalakon szerkeszthet\u0151k (MDI \u2013 Multiple Document Interface).
                                • \u00daj bet\u0171t\u00edpus a File/New men\u00fcelem kiv\u00e1laszt\u00e1s\u00e1val hozhat\u00f3 l\u00e9tre (meg kell adni a nev\u00e9t).
                                • Ez egyes bet\u0171t\u00edpusok elmenthet\u0151k (File/Save), bet\u00f6lthet\u0151k (File/Open), \u00e9s az aktu\u00e1lis dokumentum bez\u00e1rhat\u00f3 (File/Close). Ezek helye megvan az alkalmaz\u00e1sban, de nincsenek r\u00e9szleteiben implement\u00e1lva (a f\u00fcggv\u00e9nyek t\u00f6rzse nincs kit\u00f6ltve \u2013 opcion\u00e1lis HF).
                                • A felhaszn\u00e1l\u00f3i fel\u00fclet fel\u00e9p\u00edt\u00e9se
                                  • Az oldal tetej\u00e9n (Sample text) egy mintasz\u00f6veg adhat\u00f3 meg, melyet az aktu\u00e1lis bet\u0171t\u00edpussal az alkalmaz\u00e1s megjelen\u00edt.
                                  • Az oldalak k\u00f6zep\u00e9n egy karakters\u00e1v tal\u00e1lhat\u00f3. Egy adott karakteren dupl\u00e1n kattintva alatta megjelenik egy, az adott karakterhez tartoz\u00f3 szerkeszt\u0151n\u00e9zet.
                                  • Az oldal alj\u00e1n egym\u00e1s mellett az eddig szerkeszt\u00e9sre megnyitott karakterek szerkeszt\u0151n\u00e9zetei l\u00e1that\u00f3k. Egy karakter t\u00f6bbsz\u00f6r is megnyithat\u00f3 szerkeszt\u00e9sre, ez esetben t\u00f6bb szerkeszt\u0151n\u00e9zet j\u00f6n l\u00e9tre hozz\u00e1. Ennek az az \u00e9rtelme, hogy ugyanazt a karaktert k\u00fcl\u00f6nb\u00f6z\u0151 nagy\u00edt\u00e1ssal is l\u00e1thatjuk/szerkeszthetj\u00fck.
                                • A szerkeszt\u0151n\u00e9zetek fel\u00e9p\u00edt\u00e9se
                                  • Nagy r\u00e9sze (eltekintve a fels\u0151 s\u00e1v) a szerkeszt\u0151fel\u00fclet, ahol fekete h\u00e1tt\u00e9ren s\u00e1rg\u00e1val jelennek meg az akt\u00edv pixelek. Egy adott pixelen az eg\u00e9rrel kattintva a pixel invert\u00e1l\u00f3dik.
                                  • Bal fels\u0151 sarokban a megjelen\u00edtett karakter l\u00e1that\u00f3
                                  • \u2019c\u2019 gomb: Clear, minden akt\u00edv pixelt t\u00f6r\u00f6l
                                  • \u2019+\u2019 gomb: nagy\u00edt\u00e1s
                                  • \u2019-\u2019 gomb: kicsiny\u00edt\u00e9s

                                Futtassuk az alkalmaz\u00e1st, \u00e9s vizsg\u00e1ljuk meg a m\u0171k\u00f6d\u00e9s\u00e9t a fentieknek megfelel\u0151en. Azt mindenk\u00e9ppen n\u00e9zz\u00fck meg, hogy ha egy karakter szerepel a mintasz\u00f6vegben, valamint t\u00f6bbsz\u00f6r megnyitjuk szerkeszt\u00e9sre, akkor az egyik n\u00e9zetben v\u00e1ltoztatva (egy pixelt invert\u00e1lva) valamennyi n\u00e9zete friss\u00fcl.

                                Az alkalmaz\u00e1s a k\u00f3dmennyis\u00e9g minim\u00e1lis \u00e9rt\u00e9ken tart\u00e1sa \u00e9rdek\u00e9ben minimalisztikus, pl. a hibakezel\u00e9s nincs \u00e1ltal\u00e1noss\u00e1g\u00e1ban kidolgozva, hi\u00e1nyoznak ellen\u0151rz\u00e9sek. Ugyanakkor k\u00f3dmegjegyz\u00e9sekkel el van l\u00e1tva, mely seg\u00edti a k\u00f3d ut\u00f3lagos meg\u00e9rt\u00e9s\u00e9t.

                                "},{"location":"labor/old-6-doc-view/#2-feladat-az-alkalmazas-megtervezese","title":"2. Feladat - Az alkalmaz\u00e1s megtervez\u00e9se","text":"

                                A c\u00e9l az, hogy l\u00e1ssuk, milyen folyamatot k\u00f6vetve, milyen l\u00e9p\u00e9sekben dolgozunk, mikor milyen tervez\u0151i l\u00e9p\u00e9seket kell meghoznunk. T\u00f6rekedj\u00fcnk oktat\u00f3i \u00e9s hallgat\u00f3i r\u00e9szr\u0151l is az interaktivit\u00e1sra, k\u00f6z\u00f6sen hozzuk meg a d\u00f6nt\u00e9seket.

                                Hozzunk l\u00e9tre egy \u00faj C# nyelv\u0171 \u201eWindow Form App\u201d projektet (.NET 6-osat), legyen a neve FontEditor. Vegy\u00fcnk fel egy oszt\u00e1lydiagramot: projekten jobb katt, Add / New Item, majd a megjelen\u0151 ablakban Class Diagram kiv\u00e1laszt\u00e1sa, a neve maradhat az alap\u00e9rtelmezett. \u00c1ll\u00edtsuk be, hogy a diagram mutassa majd a m\u0171veletek szignat\u00far\u00e1it is (pl. jobb katt a h\u00e1tt\u00e9ren, Change Members Format / Display Full Signature). A gyakorlat nagy r\u00e9sz\u00e9ben ezt a diagramot fogjuk szerkeszteni.

                                A k\u00e9sz oszt\u00e1lydiagram a k\u00f6vetkez\u0151, eddig fogunk fokozatosan eljutni:

                                "},{"location":"labor/old-6-doc-view/#document-view-architektura","title":"Document-View architekt\u00fara","text":"

                                Az els\u0151 tervez\u0151i d\u00f6nt\u00e9s: architekt\u00far\u00e1t kell v\u00e1lasztani. A Document-View eset\u00fcnkben egy\u00e9rtelm\u0171 v\u00e1laszt\u00e1s: dokumentumokkal dolgozunk, \u00e9s t\u00f6bb n\u00e9zettel, melyeket szinkronban kell tartani. Az al\u00e1bbi \u00e1bra ismerteti a m\u0171k\u00f6d\u00e9st. A n\u00e9zetek az observerek, a document pedig a subject, melynek v\u00e1ltoz\u00e1saira az egyes n\u00e9zetek fel vannak iratkozva.

                                A D-V architekt\u00far\u00e1b\u00f3l ad\u00f3d\u00f3an sz\u00fcks\u00e9g\u00fcnk lesz dokumentum oszt\u00e1lyra, amely a dokumentum adatait t\u00e1rolja (tagv\u00e1ltoz\u00f3kban), mint pl. a n\u00e9v, el\u00e9r\u00e9si \u00fat, pixelm\u00e1trix. Tegy\u00fck fel, hogy a k\u00e9s\u0151bbiekben t\u00f6bb dokumentum t\u00edpust is t\u00e1mogatni kell majd: pl. megnyithatunk egy olyan tabf\u00fclet, melyen a BKK j\u00e1rm\u0171vekhez tudjuk rendelni a bet\u0171t\u00edpusokat (elektronikus kijelz\u0151). Vannak olyan dokumentum adatok, melyek minden dokumentum t\u00edpusban megjelennek (pl. n\u00e9v, el\u00e9r\u00e9si \u00fat). Az egyes dokumentum t\u00edpusoknak a k\u00f6z\u00f6s tulajdons\u00e1gait/m\u0171veleteit c\u00e9lszer\u0171 egy Document \u0151soszt\u00e1lyba kiszervezni, hogy ne legyenek duplik\u00e1lva az egyes dokumentum t\u00edpusokat reprezent\u00e1l\u00f3 dokumentum oszt\u00e1lyokban.

                                • Vegy\u00fck fel a Document oszt\u00e1lyt (ez az absztrakt \u0151s).
                                • Vegy\u00fcnk fel bele egy string Name property-t (ez jelenik meg a tabf\u00fcleken).

                                A Document-View architekt\u00far\u00e1b\u00f3l ad\u00f3d\u00f3an sz\u00fcks\u00e9g van egy n\u00e9zet interf\u00e9szre (egy Update m\u0171velettel a n\u00e9zet \u00e9rtes\u00edt\u00e9s\u00e9hez), valamint a dokumentumoknak nyilv\u00e1n kell tartaniuk egy list\u00e1ban a n\u00e9zeteiket:

                                • Vegy\u00fck fel az IView interf\u00e9szt.
                                • Vegy\u00fcnk fel bele egy Update m\u0171veletet.
                                • A Document oszt\u00e1lyba vegy\u00fcnk fel egy List<IView> views mez\u0151t (a Fields-n\u00e9l). Jobb gombbal kattintsunk a mez\u0151 nev\u00e9n a diagramon, \u00e9s a men\u00fcb\u0151l Show as collection association kiv\u00e1laszt\u00e1sa.
                                • A Document oszt\u00e1lyba vegy\u00fcnk fel a void AttachView(IView view) m\u0171veletet, mellyel \u00faj n\u00e9zetet lehet beregisztr\u00e1lni.
                                • V\u00e9g\u00fcl vegy\u00fcnk fel egy void DetachView(IView view)-t, mert n\u00e9zetet bez\u00e1rni is lehet.

                                T\u00e1mogatnunk kell az egyes dokumentumok tartalm\u00e1nak perziszt\u00e1l\u00e1s\u00e1t (ment\u00e9s/bet\u00f6lt\u00e9s). Ezekhez vegy\u00fcnk fel a Document \u0151sbe a megfelel\u0151 m\u0171veleteket:

                                • Document-be LoadDocument(string path) felv\u00e9tele.
                                • Document-be SaveDocument(string path) felv\u00e9tele.
                                • Mindkett\u0151 legyen absztrakt, hiszen csak az egyes dokumentum lesz\u00e1rmazottakban tudunk implement\u00e1ci\u00f3t megadni: szelekt\u00e1ljuk ki a k\u00e9t m\u0171veletet, \u00e9s a Properties ablakban az Inheritence modifier legyen Abstract.

                                Az egyes dokumentumoknak t\u00e1mogatniuk kell a n\u00e9zeteik friss\u00edt\u00e9s\u00e9t, ez minden dokumentum t\u00edpusra k\u00f6z\u00f6s:

                                • A Document-be vegy\u00fck fel az UpdateAllViews()-t (ez felel meg az Observer minta Notify m\u0171velet\u00e9nek).
                                "},{"location":"labor/old-6-doc-view/#konkret-dokumentum-es-adatai","title":"Konkr\u00e9t dokumentum \u00e9s adatai","text":"

                                Sz\u00fcks\u00e9g van egy olyan dokumentum t\u00edpusra, ami a bet\u0171t\u00edpusok szerkeszt\u00e9s\u00e9hez tartozik, amely a tagv\u00e1ltoz\u00f3iban nyilv\u00e1ntartja a sz\u00fcks\u00e9ges adatokat: legyen a neve FontEditorDocument.

                                • Vegy\u00fck fel a FontEditorDocument oszt\u00e1lyt.
                                • Sz\u00e1rmaztassuk a Document-b\u0151l (Toolbox \u2013 Inheritence kapcsolat).
                                • Ekkor a LoadDocument \u00e9s SaveDocument m\u0171veletekre automatikusan megsz\u00fcletik az override-ol\u00f3 m\u0171velet. Ha m\u00e9gsem lenne \u00edgy
                                  • Jel\u00f6lj\u00fck ki az \u0151sben a k\u00e9t m\u0171veletet.
                                  • Copy
                                  • Jel\u00f6lj\u00fck ki a FontEditorDocument oszt\u00e1lyt.
                                  • Paste
                                  • Jel\u00f6lj\u00fck itt ki a k\u00e9t m\u0171veletet, \u00e9s a Properties ablakban a Instance Modifier legyen override.

                                A dokumentumunk tagv\u00e1ltoz\u00f3kban t\u00e1rolja az adatokat. Gondoljuk \u00e1t, hogy ezt hogyan c\u00e9lszer\u0171 megval\u00f3s\u00edtani. Lehetne egy h\u00e1romdimenzi\u00f3s t\u00f6mb (karakter \u2013 x \u2013 y), de ink\u00e1bb emelj\u00fck ki egy k\u00fcl\u00f6n oszt\u00e1lyba az egy adott karakter pixeleinek t\u00e1rol\u00e1s\u00e1t/menedzsel\u00e9s\u00e9t: vezess\u00fck be a CharDef oszt\u00e1lyt.

                                Pixel t\u00f6mb helyett

                                Az\u00e9rt nem a pixelt\u00f6mb\u00f6t haszn\u00e1ljuk k\u00f6zvetlen\u00fcl, mert csak egy \u00faj oszt\u00e1ly bevezet\u00e9s\u00e9vel van lehet\u0151s\u00e9g\u00fcnk kifejezetten ide tartoz\u00f3 m\u0171veletek bevezet\u00e9s\u00e9re, vagyis az egys\u00e9gbez\u00e1r\u00e1s korrekt megval\u00f3s\u00edt\u00e1s\u00e1ra.

                                • Vegy\u00fck fel a CharDef oszt\u00e1lyt.
                                • CharDef-be bool[,] Pixels tulajdons\u00e1g felv\u00e9tele.

                                  t\u00f6bbdimenzo\u00f3s t\u00f6mb\u00f6k C#-ban

                                  A fenti p\u00e9ld\u00e1ban egy t\u00f6bbdimenzi\u00f3s t\u00f6mb\u00f6t haszn\u00e1ltunk bool[,] \u00e9s nem t\u00f6mb\u00f6k t\u00f6mbj\u00e9t bool[][], mivel ezt nyelvi szinten is t\u00e1mogatja a C# \u00e9s jobb teljes\u00edtm\u00e9nyt ny\u00fajt, mint a t\u00f6mb\u00f6k t\u00f6mbje, mert egy objektumk\u00e9nt t\u00f6rol\u00f3dik a heapen.

                                • CharDef-be char Character felv\u00e9tele: az egyes CharDef oszt\u00e1lyok t\u00e1rolj\u00e1k magukr\u00f3l, hogy mely karakter pixeleit reprezent\u00e1lj\u00e1k.

                                A dokumentumnak lesz egy gy\u0171jtem\u00e9nye CharDef objektumokb\u00f3l: minden karakterhez pontosan egy darab. Gondoljuk \u00e1t, hogy a legc\u00e9lszer\u0171bb ezt megval\u00f3s\u00edtani. Az egyes karakterdefin\u00edci\u00f3kat a karakterk\u00f3djukkal akarjuk c\u00edmezni, \u00edgy a Dictionary<char, CharDef> ide\u00e1lis v\u00e1laszt\u00e1s: a karakterk\u00f3d a kulcs, az hozz\u00e1 tartoz\u00f3 CharDef pedig az \u00e9rt\u00e9k.

                                • FontEditorDocument-be: Dictionary<char, CharDef> charDefs mez\u0151 felv\u00e9tele. Jobb katt, Show as collection association.
                                "},{"location":"labor/old-6-doc-view/#dokumentumok-menedzselese-app-singleton-osztaly","title":"Dokumentumok menedzsel\u00e9se - App Singleton oszt\u00e1ly","text":"

                                Az alkalmaz\u00e1sban nyilv\u00e1n kell tartani a megnyitott dokumentumok list\u00e1j\u00e1t. Mely oszt\u00e1ly felel\u0151ss\u00e9ge legyen? Vezess\u00fcnk be r\u00e1 egy alkalmaz\u00e1sszint\u0171 oszt\u00e1lyt: legyen a neve App (Windows Forms alatt m\u00e1r van Application, nem c\u00e9lszer\u0171 ezt a nevet v\u00e1lasztani). Ez lesz az alkalmaz\u00e1sunk \u201egy\u00f6k\u00e9roszt\u00e1lya\u201d.

                                • Vegy\u00fck fel az App oszt\u00e1lyt.
                                • App-ba List<FontEditorDocument> documents mez\u0151 felv\u00e9tele, majd Show as collection association.

                                Gondoljuk v\u00e9gig, hogyan t\u00f6rt\u00e9nik majd egy \u00faj dokumentum l\u00e9trehoz\u00e1sa (mi t\u00f6rt\u00e9nik a File/New men\u00fcelem kiv\u00e1laszt\u00e1sakor): be kell k\u00e9rni a felhaszn\u00e1l\u00f3t\u00f3l a dokumentum nev\u00e9t, l\u00e9tre kell hozni egy FontEditorDocument objektumot, fel kell venni a megnyitott dokumentumok list\u00e1j\u00e1ba stb. Ezt a logik\u00e1t ne tegy\u00fck a GUI-ba (men\u00fcelem click esem\u00e9nykezel\u0151): tegy\u00fck abba az oszt\u00e1lyba, melynek a felel\u0151ss\u00e9ge a megnyitott dokumentumok menedzsel\u00e9se, amely t\u00e1rolja a sz\u00fcks\u00e9ges adatokat hozz\u00e1 (dokumentum lista). \u00cdgy legyen ez az App oszt\u00e1lyunk feladata, benne vegy\u00fck fel a sz\u00fcks\u00e9ges m\u0171veleteket:

                                • App-ba NewDocument \u00e9s OpenDocument m\u0171veletek felv\u00e9tele.

                                Most a dokumentum ment\u00e9st gondoljuk v\u00e9gig: a File/Save mindig az akt\u00edv dokumentumra vonatkozik. Valakinek nyilv\u00e1n kell tartani, melyik az akt\u00edv dokumentum: legyen ez az App, hiszen \u0151 t\u00e1rolja a dokumentumok list\u00e1j\u00e1t is.

                                • A Toolbox-on v\u00e1lasszuk ki az Association kapcsolatot. Az App-b\u00f3l h\u00fazzunk egy nyilat a FontEditorDocument-be. V\u00e1lasszuk ki az \u00fajonnan l\u00e9trehozott kapcsolatot, \u00e9s nevezz\u00fck \u00e1t ActiveDocument-re.
                                • App-ba void SaveActiveDocument() felv\u00e9tele.
                                • App-ba void CloseActiveDocument\u00e1() felv\u00e9tele.

                                Konkr\u00e9t dokumentumra vagy absztrakt \u0151sre hivatkozzunk?

                                Mivel az App oszt\u00e1lyunk alkalmaz\u00e1s specifikus funkci\u00f3kat l\u00e1t el, nyugodtan hivatkozhat a konkr\u00e9t dokumentum t\u00edpusra, \u00e9s felesleges az absztrakt \u0151st\u0151l f\u00fcggen\u00fcnk, mert az csak nem k\u00edv\u00e1nt castol\u00e1sokhoz vezetne.

                                Az App objektumb\u00f3l \u00e9rtelemszer\u0171en csak egyet kell/szabad l\u00e9trehozni, amely a fut\u00f3 alkalmaz\u00e1st reprezent\u00e1lja. Van m\u00e9g egy probl\u00e9m\u00e1nk: a File/Save stb. men\u00fcelem click esem\u00e9nykezel\u0151ben el kell \u00e9rj\u00fck ezt az egy objektumot. Illetve, majd t\u00f6bb m\u00e1s helyen is. J\u00f3 lenne, ha nem kellene minden oszt\u00e1lyban k\u00fcl\u00f6n el\u00e9rhet\u0151v\u00e9 tenni (tagv\u00e1ltoz\u00f3 vagy f\u00fcggv\u00e9nyparam\u00e9ter form\u00e1j\u00e1ban), hanem b\u00e1rhonnan egyszer\u0171en el\u00e9rhet\u0151 lenne. Erre ny\u00fajt megold\u00e1st a Singleton tervez\u00e9si minta. Egy oszt\u00e1lyb\u00f3l csak egy objektumot enged l\u00e9trehozni, \u00e9s ahhoz glob\u00e1lis hozz\u00e1f\u00e9r\u00e9st biztos\u00edt, m\u00e9gpedig az oszt\u00e1ly nev\u00e9n \u00e9s egy statikus Instance property-n kereszt\u00fcl, pl. \u00edgy: App.Instance.SaveDocument stb. Nem val\u00f3s\u00edtjuk meg teljes \u00e9rt\u00e9k\u0171en, de tegy\u00fck meg az al\u00e1bbiakat:

                                • App-ba App Instance property felv\u00e9tele. Properties ablakban static: true.
                                • App-ba priv\u00e1t konstruktor felv\u00e9tele.

                                Az App-oszt\u00e1llyal v\u00e9gezt\u00fcnk.

                                "},{"location":"labor/old-6-doc-view/#nezetek","title":"N\u00e9zetek","text":"

                                A n\u00e9zetekkel eddig nem foglalkoztunk, ez a k\u00f6vetkez\u0151 l\u00e9p\u00e9s. Futtassuk a k\u00e9sz alkalmaz\u00e1st, \u00e9s n\u00e9zz\u00fck meg, hogy h\u00e1ny t\u00edpus\u00fa n\u00e9zetre van sz\u00fcks\u00e9g, melyikb\u0151l h\u00e1ny p\u00e9ld\u00e1ny lesz:

                                • K\u00e9t t\u00edpus\u00fa n\u00e9zetre van sz\u00fcks\u00e9g: az egyik a mintasz\u00f6veget jelen\u00edti meg, a m\u00e1sik egy adott karakter szerkeszt\u00e9s\u00e9t teszi lehet\u0151v\u00e9.
                                • Legyen az el\u0151z\u0151 neve SampleTextView, az ut\u00f3bbi\u00e9 FontEditorView.
                                • SampleTextView-b\u00f3l mindig egy van (egy adott dokumentumra vonatkoz\u00f3an), a FontEditorView objektumok ig\u00e9ny szerint j\u00f6nnek l\u00e9tre, 0..n p\u00e9ld\u00e1ny l\u00e9tezhet.
                                • Vegy\u00fck fel a k\u00e9t oszt\u00e1lyt.
                                • Implement\u00e1ltassuk vel\u00fck az IView interf\u00e9szt (Toolbox / Inheritence kapcsolat). Az Update m\u0171velet automatikusan implement\u00e1lva lesz.

                                Az egyes n\u00e9zetek a dokumentumukb\u00f3l \u201et\u00e1pl\u00e1lkoznak\u201d, a a dokumentumukban t\u00e1rolt adatokat jelen\u00edtik meg, azokat m\u00f3dos\u00edtj\u00e1k. Ehhez, a D-V architekt\u00far\u00e1nak megfelel\u0151en el kell \u00e9rj\u00e9k a dokumentumukat.

                                • A SampleTextView \u00e9s FontEditorView-ban vegy\u00fcnk fel egy FontEditorDocument t\u00edpus\u00fa document nev\u0171 mez\u0151t (ha felvett\u00fck az egyikben, lehet copy-paste-tel m\u00e1solni a m\u00e1sikba), majd \"Show as Association\". Megjegyz\u00e9s: az\u00e9rt nem c\u00e9lszer\u0171 \u00e1ltal\u00e1nos Document t\u00edpus\u00fat felvenni (\u00e9s az interf\u00e9szbe felvinni), mert a view-knak a konkr\u00e9t dokumentum adatait (l\u00e1sd al\u00e1bb) el kell \u00e9rni\u00fck.

                                Gondoljuk v\u00e9gig, milyen adattagokkal rendelkeznek az egyes n\u00e9zetek. Ehhez futtassuk az alkalmaz\u00e1st, \u00e9s n\u00e9zz\u00fck meg ism\u00e9t a felhaszn\u00e1l\u00f3i fel\u00fclet\u00e9t.

                                • A SampleTextView t\u00e1rolja a mintasz\u00f6veget, melyet meg kell jelen\u00edteni. Vegy\u00fcnk fel egy sampleText:string mez\u0151t. Ha el kellene menteni a mintasz\u00f6veget is, akkor a FontEditorDocument-ben kellene t\u00e1rolni (\u00e9s onnan mindig lek\u00e9rdezni), mert az adatok ment\u00e9s\u00e9\u00e9rt a dokumentum oszt\u00e1lyunk a felel\u0151s.
                                • A FontEditorView k\u00e9t dolgot t\u00e1rol:
                                  • A karakter k\u00f3dja, melynek pixeleit megjelen\u00edti. Vegy\u00fcnk fel egy editedChar: char mez\u0151t.
                                  • A nagy\u00edt\u00e1si t\u00e9nyez\u0151t (zoom: double felv\u00e9tele)

                                A n\u00e9zetek maguk felel\u0151sek a kirajzol\u00e1suk\u00e9rt:

                                • Draw (g:Graphics) felv\u00e9tele mindk\u00e9t n\u00e9zetbe.
                                "},{"location":"labor/old-6-doc-view/#fonteditordocument-muveletek","title":"FontEditorDocument m\u0171veletek","text":"

                                A FontEditorDocument-ben egy priv\u00e1t list\u00e1ban van egyel\u0151re jelen a CharDef-ek list\u00e1ja. A n\u00e9zetek \u00edgy nem tudj\u00e1k el\u00e9rni, pedig a megjelen\u00edt\u00e9shez sz\u00fcks\u00e9g\u00fck lenne r\u00e1. A dokumentumunkban be kell vezess\u00fcnk olyan m\u0171veleteket, melyek a dokumentum \u00e1ltal t\u00e1rolt adatokat a n\u00e9zetek sz\u00e1m\u00e1ra el\u00e9rhet\u0151v\u00e9 teszik, \u00e9s lehet\u0151s\u00e9get biztos\u00edtanak a m\u00f3dos\u00edt\u00e1sra is.

                                • Mindk\u00e9t n\u00e9zet el kell \u00e9rje a megjelen\u00edtett karakterek pixeleit t\u00e1rol\u00f3 CharDef objektumokat. Ehhez vezess\u00fck be a FontEditorDocument-ben a GetCharDef(c:char):CharDef m\u0171veletet. Ezt hossz\u00fa t\u00e1von majd \u00fagy lesz c\u00e9lszer\u0171 megval\u00f3s\u00edtani, hogy a GetCharDef nem az eredeti objektumot adja vissza, hanem annak egy m\u00e1solat\u00e1t (clone). Ha az eredetit adn\u00e1 vissza, akkor a n\u00e9zetek K\u00d6ZVETLEN\u00dcL tudn\u00e1k m\u00f3dos\u00edtani a pixelek \u00e9rt\u00e9k\u00e9t, ezt mi nem akarjuk (b\u00e1r a funkci\u00f3k b\u0151v\u00edt\u00e9s\u00e9vel r\u00e1k\u00e9nyszer\u00fclhet\u00fcnk).
                                • A FontEditorView-nak k\u00e9pesnek kell lennie egy adott CharDef adott koordin\u00e1t\u00e1ban lev\u0151 pixel \u00e9rt\u00e9k\u00e9t invert\u00e1lni (eg\u00e9r kattint\u00e1skor). Ehhez vezess\u00fck be a FontEditorDocument-ben az InvertCharDefPixel(c:char, x: int, y: int) m\u0171veletet.
                                "},{"location":"labor/old-6-doc-view/#a-tervezes-zarasa","title":"A tervez\u00e9s z\u00e1r\u00e1sa","text":"

                                Eljutottunk oda, hogy megtervezt\u00fck az architekt\u00far\u00e1t, minden igaz\u00e1n l\u00e9nyeges d\u00f6nt\u00e9st meghoztunk. Az UML diagram alapj\u00e1n megsz\u00fcletett az oszt\u00e1lyok v\u00e1za. Ezt term\u00e9szetesen jelent\u0151sen b\u0151v\u00edteni kell, m\u00e9g sz\u00fcletnek \u00faj oszt\u00e1lyok is (pl. Form-ok, vez\u00e9rl\u0151k).

                                "},{"location":"labor/old-6-doc-view/#3-feladat-a-kesz-alkalmazas-attekintese","title":"3. Feladat - A k\u00e9sz alkalmaz\u00e1s \u00e1ttekint\u00e9se","text":"

                                Id\u0151 hi\u00e1ny\u00e1ban nem val\u00f3s\u00edtjuk meg az alkalmaz\u00e1st, hanem a k\u00e9sz megold\u00e1st n\u00e9zz\u00fck \u00e1t (laboron kb. 15 percben), annak is csak n\u00e9h\u00e1ny l\u00e9nyeges haszn\u00e1lati eset\u00e9t.

                                T\u00f6lts\u00fck le a k\u00e9sz megold\u00e1st. Ehhez parancssorban navig\u00e1ljunk a c:\\work\\ mapp\u00e1ba (ha a laborban dolgozunk), \u00e9s adjuk ki a k\u00f6vetkez\u0151 parancsot:

                                git clone https://github.com/bmeviauab00/lab-docview-megoldas

                                Nyissuk meg a k\u00e9sz solution-t, futtassuk \u00e9s pr\u00f3b\u00e1ljuk ki az alkalmaz\u00e1s alapfunkci\u00f3it.

                                "},{"location":"labor/old-6-doc-view/#nezetek-megvalositasa","title":"N\u00e9zetek megval\u00f3s\u00edt\u00e1sa","text":"

                                Nyissuk meg a FontEditorView-t, el\u0151sz\u00f6r a k\u00f3dot n\u00e9zz\u00fck. A FontEditorView egyr\u00e9szt implement\u00e1lja az IView interf\u00e9szt, m\u00e1sr\u00e9szt a UserControl-b\u00f3l sz\u00e1rmazik. M\u00e9gpedig az\u00e9rt, mert \u00edgy a tervez\u0151ben (designer) tudjuk kialak\u00edtani a felhaszn\u00e1l\u00f3i fel\u00fclet\u00e9t, pont \u00fagy, mint egy \u0171rlapnak. A Visual Studio designer fel\u00fclet\u00e9n ak\u00e1r bele is m\u00f3dos\u00edthatn\u00e1nk a layoutba \u00e9s a vez\u00e9rl\u0151k tulajdons\u00e1gaiba. Ha k\u00edv\u00e1ncsiak vagyunk, ki is pr\u00f3b\u00e1lhatjuk ezt (pl. a nagy\u00edt\u00e1s \u00e9s a kicsiny\u00edt\u00e9s gombok hely\u00e9nek megv\u00e1ltoztat\u00e1s\u00e1val).

                                A SampleTextView is UserControl lesz\u00e1rmazott, b\u00e1r annak egyszer\u0171 a fel\u00fclete (nincsenek rajta m\u00e1s vez\u00e9rl\u0151k), \u00edgy lehetett volna k\u00f6z\u00f6ns\u00e9ges Control lesz\u00e1rmazott is.

                                Vonjuk le a tanuls\u00e1got: Windows Forms k\u00f6rnyezetben a n\u00e9zeteket tipikusan UserControl-k\u00e9nt (esetleg Control-k\u00e9nt) c\u00e9lszer\u0171 megval\u00f3s\u00edtani.

                                "},{"location":"labor/old-6-doc-view/#egy-oldal-tab-elrendezese","title":"Egy oldal (tab) elrendez\u00e9se","text":"

                                Futtassuk az alkalmaz\u00e1st. Valahogy ki kell alak\u00edtsuk egy adott oldal (tabpage) elrendez\u00e9s\u00e9t. Lehet\u0151leg tervez\u0151i n\u00e9zetben, \u00e9s nem fut\u00e1s k\u00f6zben, k\u00f3db\u00f3l poz\u00edcion\u00e1lva az elemeket (legal\u00e1bbis ahol nem musz\u00e1j). A UserControl-ok alkalmaz\u00e1sa jelenti sz\u00e1munkra a megold\u00e1st. Nyissuk meg a FontDocumentControl-t tervez\u0151i n\u00e9zetben. Ez egy olyan vez\u00e9rl\u0151, amely egy taboldalra ker\u00fcl fel, azt t\u00f6lti ki teljesen. Az oldalt a m\u00e1r ismert layout technik\u00e1kkal alak\u00edtottuk ki (Label, TextBox, Panel-ek Dock-kolva). Ha van id\u0151nk, akkor n\u00e9zz\u00fck meg a Document Outline ablakban. Az igazi \u00e9rdekess\u00e9g pedig az, hogy a SampleTextView-t is a Toolbox-r\u00f3l drag&drop-pal ker\u00fclt felhelyez\u00e9sre (pont \u00fagy, mintha egy be\u00e9p\u00edtett vez\u00e9rl\u0151 lenne). Annyit n\u00e9zz\u00fcnk meg, hogy a SampleTextView val\u00f3ban ott van a Toolbox tetej\u00e9n.

                                "},{"location":"labor/old-6-doc-view/#forgatokonyv-1-egy-pixel-invertalasa-nezetek-szinkronizalasa","title":"Forgat\u00f3k\u00f6nyv 1 \u2013 Egy pixel invert\u00e1l\u00e1sa, n\u00e9zetek szinkroniz\u00e1l\u00e1sa","text":"

                                Ez egy kiemelt jelent\u0151s\u00e9g\u0171 forgat\u00f3k\u00f6nyv, mert ezt illusztr\u00e1lja a D-V architekt\u00fara alapmechanizmus\u00e1t, a n\u00e9zetek friss\u00edt\u00e9s\u00e9t \u00e9s konzisztensen tart\u00e1s\u00e1t. Keress\u00fck meg azt a f\u00fcggv\u00e9nyt, ahol az eg\u00e9sz pixel invert\u00e1l\u00e1s folyamat elindul. A FontEditorView.FontEditorView_MouseClick a kiindul\u00f3pont. Itt az al\u00e1bb kiemelt sor a l\u00e9nyeg:

                                private void FontEditorView_MouseClick(object sender, MouseEventArgs e)\n{\n    int x = e.X / zoom;\n    int y = (e.Y - offsetY) / zoom;\n    if (x >= CharDef.FontSize.Width)\n        return;\n\n    document.InvertCharDefPixel(editedChar, x, y);\n}\n

                                N\u00e9zz\u00fck meg a FontEditorDocument.InvertCharDefPixel-t. Az invert\u00e1lja a megfelel\u0151 CharDef pixel\u00e9t, de a l\u00e9nyeg az utols\u00f3 sor:

                                public void InvertCharDefPixel(char c, int x, int y)\n{\n    var charDef = GetCharDefCore(c);\n    if (charDef == null)\n        return;\n\n    charDef.Pixels[x, y] = !charDef.Pixels[x, y];\n\n    UpdateAllViews();\n}\n

                                Az UpdateAllViews a Document \u0151sben van, Update-et h\u00edv minden n\u00e9zetre. Ami \u00e9rdekes, hogy az Update hogyan van meg\u00edrva az egyes n\u00e9zetekben. N\u00e9zz\u00fck meg pl. a FontEditView-t:

                                public void Update()\n{\n    Invalidate();\n}\n

                                Az Update hat\u00e1s\u00e1ra a n\u00e9zetek \u00fajra kell rajzolj\u00e1k magukat az aktu\u00e1lis dokumentum \u00e1llapot alapj\u00e1n. De az Update-ben nem tudunk rajzolni, csak az OnPaint-ben. \u00cdgy itt az Invalidate h\u00edv\u00e1ssal kiv\u00e1ltjuk a Paint esem\u00e9nyt. Ez megint egy tanuls\u00e1g: Windows Forms alkalmaz\u00e1sokban a n\u00e9zetek Update f\u00fcggv\u00e9ny\u00e9ben tipikusan egy Invalidate h\u00edv\u00e1s szokott lenni.

                                Z\u00e1r\u00e1sk\u00e9ppen n\u00e9zz\u00fck meg a FontEditView.OnPaint megval\u00f3s\u00edt\u00e1s\u00e1t. Egyetlen l\u00e9nyeges dolog van itt: a megjelen\u00edt\u00e9shez le kell k\u00e9rni a dokumentumt\u00f3l az aktu\u00e1lis CharDef-et (mert a n\u00e9zet a D-V architekt\u00fara alapelveinek megfelel\u0151en nem t\u00e1rolja), majd ki kell azt rajzolni.

                                protected override void OnPaint(PaintEventArgs e)\n{\n    base.OnPaint(e);\n\n    var editedCharDef = document.GetCharDef(editedChar);\n\n    CharDefViewModel.DrawFont(e.Graphics, editedCharDef, 0, offsetY, zoom);\n}\n

                                Kirajzol\u00e1s logik\u00e1ja

                                Mivel a kirajzol\u00e1s logik\u00e1ja a FontEditorView-ban \u00e9s a SampleTextView-ban is azonosan m\u0171k\u00f6dik a Graphics oszt\u00e1ly haszn\u00e1lat\u00e1val, kiszervezt\u00fck ezt egy CharDefViewModel seg\u00e9doszt\u00e1lyba az \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1g kedv\u00e9\u00e9rt.

                                A CharDef-be nem c\u00e9lszer\u0171 rakni ezt a logik\u00e1t, mivel az egy n\u00e9zet f\u00fcggetlen adatreprezent\u00e1ci\u00f3, \u00e9s sokkal ink\u00e1bb a dokumentumhoz tartozik, mint a n\u00e9zethez.

                                "},{"location":"labor/old-6-doc-view/#forgatokonyv-2-uj-dokumentum-letrehozasa-opcionalis","title":"Forgat\u00f3k\u00f6nyv 2 \u2013 \u00daj dokumentum l\u00e9trehoz\u00e1sa (opcion\u00e1lis)","text":"

                                Azt n\u00e9zz\u00fck meg, hogyan t\u00f6rt\u00e9nik egy \u00faj dokumentum l\u00e9trehoz\u00e1sa, vagyis mi t\u00f6rt\u00e9nik a File/New men\u00fcelem kiv\u00e1laszt\u00e1sakor.

                                Nyissuk meg a MainForm-ot tervez\u0151i n\u00e9zetben, v\u00e1laszuk a File/New men\u00fcelemet, majd ugorjunk el a Click esem\u00e9nykezel\u0151h\u00f6z. Arra l\u00e1tunk p\u00e9ld\u00e1t, hogy az App oszt\u00e1ly, mint Singleton, hogy \u00e9rhet\u0151 el:

                                App.Instance.NewDocument();\n

                                Az \u00f6sszes t\u00f6bbi men\u00fcelem esem\u00e9nykezel\u0151je hasonl\u00f3, nincs semmi logika a GUI-ban, csak egyszer\u0171 tov\u00e1bbh\u00edv\u00e1s az App-ba.

                                Tekints\u00fck \u00e1t az App.NewDocument t\u00f6rzs\u00e9t, \u00e9s egy-egy mondatban fussuk \u00e1t a fontosabb l\u00e9p\u00e9seket.

                                1. NewDocForm n\u00e9zet megnyit\u00e1sa \u00e9s v\u00e1rakoz\u00e1s a v\u00e1laszra.
                                2. Sikeres v\u00e1lasz eset\u00e9n \u00faj FontEditorDocument l\u00e9trehoz\u00e1sa \u00e9s felv\u00e9tele a dokumentumok k\u00f6z\u00e9, valamint akt\u00edvv\u00e1 t\u00e9tele.
                                3. \u00daj tab l\u00e9trehoz\u00e1sa a n\u00e9zetekkel.
                                public void NewDocument()\n{\n    // Bek\u00e9rj\u00fckk az \u00faj font t\u00edpus (dokumentum) nev\u00e9t a\n    // felhaszn\u00e1l\u00f3t\u00f3l egy mod\u00e1lis dial\u00f3gs ablakban.\n    var form = new NewDocForm(GetDocumentNames());\n    if (form.ShowDialog() != DialogResult.OK)\n        return;\n\n    // \u00daj dokumentum objektum l\u00e9trehoz\u00e1sa \u00e9s felv\u00e9tele a dokumentum list\u00e1ba.\n    var doc = new FontEditorDocument(form.FontName);\n    documents.Add(doc);\n\n    // Az \u00faj tab lesz az akt\u00edv, az activeDocument tagv\u00e1ltoz\u00f3t erre kell \u00e1ll\u00edtani.\n    UpdateActiveDocument(doc.Name);\n\n    CreateTabForNewDocument(doc);\n}\n

                                App oszt\u00e1ly felel\u0151ss\u00e9gi k\u00f6re

                                Az egyszer\u0171s\u00e9g \u00e9rdek\u00e9ben az App oszt\u00e1ly most t\u00f6bb felel\u0151ss\u00e9ggel is rendelkezik, de ide\u00e1lis esetben sz\u00e9t lenne szedve pl. a k\u00f6vetkez\u0151 oszt\u00e1lyokra a felel\u0151ss\u00e9gi k\u00f6r\u00f6knek megfelel\u0151en:

                                • DocumentManager: a megjelen\u00edt\u00e9st\u0151l f\u00fcggetlen\u00fcl a dokumentumokat t\u00e1roln\u00e1.
                                • ViewManager: feladata a n\u00e9zetek menedzsel\u00e9se, tabcontrolokhoz hozz\u00e1ad\u00e1sa stb. lenne.

                                Az App.OpenDocument m\u0171velet t\u00f6rzse nincs implement\u00e1lva, de a l\u00e9p\u00e9sek k\u00f3dmegjegyz\u00e9sek form\u00e1j\u00e1ban adottak, remek otthoni gyakorl\u00e1si lehet\u0151s\u00e9g a m\u0171velet t\u00e9nyleges megval\u00f3s\u00edt\u00e1sa.

                                "},{"location":"labor/old-7-tervezesi-mintak/","title":"7. Tervez\u00e9si mint\u00e1k","text":""},{"location":"labor/old-7-tervezesi-mintak/#a-gyakorlat-celja","title":"A gyakorlat c\u00e9lja","text":"

                                A gyakorlat c\u00e9ljai:

                                • Egy \u00f6sszetettebb p\u00e9lda alapj\u00e1n n\u00e9h\u00e1ny tervez\u00e9si minta gyakorlati alkalmaz\u00e1sa (els\u0151dlegesen Singleton, Command Processor \u00e9s Memento).
                                • A Document-View minta tov\u00e1bbi gyakorl\u00e1sa, illetve annak demonstr\u00e1l\u00e1sa, hogy a mint\u00e1nak t\u00f6bb vari\u00e1nsa l\u00e9tezik.
                                • Alapszint\u0171 betekint\u00e9st nyerni az \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1got t\u00e1mogat\u00f3 oszt\u00e1lyk\u00f6nyvt\u00e1rak/keretrendszerek fejleszt\u00e9s\u00e9nek vil\u00e1g\u00e1ba.
                                • Jelent\u0151s\u00e9g\u00fcknek megfelel\u0151en tov\u00e1bb gyakoroljuk az objektumorient\u00e1lt paradigma legfontosabb koncepci\u00f3it (pl. felel\u0151ss\u00e9gek k\u00fcl\u00f6nv\u00e1laszt\u00e1sa).

                                Kapcsol\u00f3d\u00f3 el\u0151ad\u00e1sok:

                                • Tervez\u00e9si mint\u00e1k
                                • Szoftver architekt\u00far\u00e1k t\u00e9mak\u00f6rb\u0151l a Document-View architekt\u00fara
                                • Windows Forms alkalmaz\u00e1sok fejleszt\u00e9se
                                "},{"location":"labor/old-7-tervezesi-mintak/#elofeltetelek","title":"El\u0151felt\u00e9telek","text":"

                                A gyakorlat elv\u00e9gz\u00e9s\u00e9hez sz\u00fcks\u00e9ges eszk\u00f6z\u00f6k:

                                • Visual Studio 2022
                                • Windows 10 vagy Windows 11 oper\u00e1ci\u00f3s rendszer (Linux \u00e9s macOS nem alkalmas)
                                "},{"location":"labor/old-7-tervezesi-mintak/#megoldas","title":"Megold\u00e1s","text":"A k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se

                                L\u00e9nyeges, hogy a labor sor\u00e1n a laborvezet\u0151t k\u00f6vetve kell dolgozni, tilos (\u00e9s \u00e9rtelmetlen) a k\u00e9sz megold\u00e1s let\u00f6lt\u00e9se. Ugyanakkor az ut\u00f3lagos \u00f6n\u00e1ll\u00f3 gyakorl\u00e1s sor\u00e1n hasznos lehet a k\u00e9sz megold\u00e1s \u00e1ttekint\u00e9se, \u00edgy ezt el\u00e9rhet\u0151v\u00e9 tessz\u00fck.

                                A megold\u00e1s GitHubon \u00e9rhet\u0151 el itt. A legegyszer\u0171bb m\u00f3d a let\u00f6lt\u00e9s\u00e9re, ha parancssorb\u00f3l a git clone utas\u00edt\u00e1ssal lekl\u00f3nozzuk a g\u00e9p\u00fcnkre:

                                git clone https://github.com/bmeviauab00/lab-designpattern-kiindulo -b megoldas-refactor-elott

                                Ehhez telep\u00edtve kell legyen a g\u00e9pre a parancssori git, b\u0151vebb inform\u00e1ci\u00f3 itt.

                                "},{"location":"labor/old-7-tervezesi-mintak/#bevezeto","title":"Bevezet\u0151","text":""},{"location":"labor/old-7-tervezesi-mintak/#elmeleti-hatter","title":"Elm\u00e9leti h\u00e1tt\u00e9r","text":"

                                A komplexebb alkalmaz\u00e1sok fejleszt\u00e9se sor\u00e1n sz\u00e1mos tervez\u0151i d\u00f6nt\u00e9st kell meghoznunk, melyek sor\u00e1n t\u00f6bb lehet\u0151s\u00e9g k\u00f6z\u00fcl is v\u00e1laszthatunk. Amennyiben ezen pontokban olyan d\u00f6nt\u00e9seket hozunk, melyek nem k\u00f6vetik az objektumorient\u00e1lt szeml\u00e9letm\u00f3d alapelveit, nem tartjuk szem el\u0151tt az alkalmaz\u00e1sunk k\u00f6nny\u0171 karbantarthat\u00f3s\u00e1g\u00e1t, illetve egyszer\u0171en megval\u00f3s\u00edthat\u00f3 tov\u00e1bbfejleszt\u00e9si lehet\u0151s\u00e9g\u00e9t, k\u00f6nnyen hamar r\u00e9m\u00e1lomm\u00e1 v\u00e1lhat a fejleszt\u00e9s. Az egyes hib\u00e1k jav\u00edt\u00e1sa folyamatosan \u00faj hib\u00e1kat sz\u00fcl. Ezen fel\u00fcl a megrendel\u0151i v\u00e1ltoztat\u00e1si \u00e9s b\u0151v\u00edt\u00e9si ig\u00e9nyek a k\u00f3d nagym\u00e9rt\u00e9k\u0171 folyamatos \u00e1t\u00edr\u00e1s\u00e1t ig\u00e9nylik ahelyett, hogy a k\u00f3d p\u00e1r j\u00f3l meghat\u00e1rozott pontj\u00e1ban t\u00f6rt\u00e9n\u0151 b\u0151v\u00edt\u00e9s\u00e9vel - a megl\u00e9v\u0151 k\u00f3d jelent\u0151s m\u00f3dos\u00edt\u00e1sa n\u00e9lk\u00fcl - el tudn\u00e1nk ezt \u00e9rni. A tervez\u00e9si mint\u00e1k j\u00f3l bev\u00e1lt megold\u00e1sokat mutatnak bizonyos gyakran el\u0151fordul\u00f3 tervez\u00e9si probl\u00e9m\u00e1kra: ezen megold\u00e1sok abban seg\u00edtenek, hogy k\u00f3dunk k\u00f6nnyebben b\u0151v\u00edthet\u0151, karbantarthat\u00f3 \u00e9s min\u00e9l nagyobb m\u00e9rt\u00e9kben \u00fajrafelhaszn\u00e1lhat\u00f3 legyen. Ugyanakkor ne ess\u00fcnk \u00e1t a l\u00f3 t\u00faloldal\u00e1ra: csak akkor \u00e9rdemes egy adott tervez\u00e9si mint\u00e1t bevetni, ha adott esetben val\u00f3s el\u0151nyt jelent az alkalmaz\u00e1sa. Ellenkez\u0151 esetben csak a megval\u00f3s\u00edt\u00e1s komplexit\u00e1s\u00e1t n\u00f6veli feleslegesen.

                                "},{"location":"labor/old-7-tervezesi-mintak/#a-feladat-ismertetese","title":"A feladat ismertet\u00e9se","text":"

                                A feladatunk egy vektorgrafikus rajzol\u00f3program kifejleszt\u00e9se:

                                • Az alkalmaz\u00e1sban vektorgrafikus alakzatokat lehet l\u00e9trehozni, \u00fagymint t\u00e9glalap, ellipszis stb.
                                • A m\u00e1r l\u00e9trehozott alakzatokat egy grafikus fel\u00fcleten meg kell jelen\u00edteni (ki kell rajzolni).
                                • A m\u00e1r l\u00e9trehozott alakzatok fontosabb param\u00e9tereit, \u00fagymint koordin\u00e1t\u00e1k, befoglal\u00f3 t\u00e9glalap meg kell jelen\u00edteni egy list\u00e1ban egy inform\u00e1ci\u00f3s panelen.
                                • Windows Forms technol\u00f3gi\u00e1ra \u00e9p\u00edtve dolgozunk.
                                • Document-View architekt\u00far\u00e1t k\u00f6vetj\u00fck, de egyszerre csak egy dokumentum lehet megnyitva (nincsenek dokumentumonk\u00e9nt tabf\u00fclek vagy ablakok).
                                • Egy adott pontig el\u0151k\u00e9sz\u00edtett k\u00f6rnyezetet visz\u00fcnk tov\u00e1bb. A munka mennyis\u00e9g\u00e9nek kezelhet\u0151 szinten tart\u00e1sa v\u00e9gett csak bizonyos pontig vissz\u00fck tov\u00e1bb a fejleszt\u00e9st, nem val\u00f3s\u00edtjuk meg a teljes \u00e9rt\u00e9k\u0171 megold\u00e1st. Az \u00fajonnan besz\u00farand\u00f3 sorokat az \u00fatmutat\u00f3ban kiemelt h\u00e1tt\u00e9r jelzi.
                                "},{"location":"labor/old-7-tervezesi-mintak/#1-feladat-a-kiindulasi-kornyezet-megismerese","title":"1. Feladat - A kiindul\u00e1si k\u00f6rnyezet megismer\u00e9se","text":"

                                Kl\u00f3nozzuk le a gyakorlathoz tartoz\u00f3 kiindul\u00f3 alkalmaz\u00e1s repositoryj\u00e1t:

                                • Nyissunk egy command prompt-ot,
                                • Navig\u00e1ljunk el egy tetsz\u0151leges mapp\u00e1ba, p\u00e9ld\u00e1ul c:\\work\\NEPTUN
                                • Adjuk ki a k\u00f6vetkez\u0151 parancsot: git clone https://github.com/bmeviauab00/lab-designpattern-kiindulo
                                • Nyissuk meg a DesignPatternApp.sln solutiont Visual Studio-ban.

                                Futtassuk az alkalmaz\u00e1st, az al\u00e1bbihoz hasonl\u00f3 fel\u00fcletet l\u00e1tunk (amennyiben a File/New men\u00fcelemet kiv\u00e1lasztjuk):

                                Ismerkedj\u00fcnk meg m\u0171k\u00f6d\u00e9s\u00e9nek n\u00e9h\u00e1ny aspektus\u00e1val:

                                • A legt\u00f6bb funkci\u00f3 nincs m\u00e9g megval\u00f3s\u00edtva.
                                • A File/New men\u00fcelem valamint a toolbar els\u0151 gombja egy \u00faj dokumentumot hoz l\u00e9tre. Ez m\u00e1r m\u0171k\u00f6dik, pr\u00f3b\u00e1ljuk ki.
                                • Mivel \u00faj alakzatot jelen pillanatban m\u00e9g nem tudunk l\u00e9trehozni, a dokumentum a l\u00e9trej\u00f6tt\u00e9t k\u00f6vet\u0151en nem \u00fcres, tartalmaz n\u00e9mi tesztel\u00e9st szolg\u00e1l\u00f3 adatot (k\u00e9t t\u00e9glalapot \u00e9s egy ellipszist).
                                • Az alakzatok kirajzol\u00e1sa is meg van val\u00f3s\u00edtva. Ezen fel\u00fcl a jobb oldali inform\u00e1ci\u00f3s panelen l\u00e1thatjuk a m\u00e1r l\u00e9tez\u0151 alakzatok param\u00e9tereit.
                                • Az alakzatok k\u00f6z\u00fcl egy ki lehet v\u00e1lasztva: ez piros sz\u00ednnel \u00e9s szaggatott k\u00e9k kerettel ker\u00fcl kirajzol\u00e1sra, illetve az inform\u00e1ci\u00f3s panelen ki is van v\u00e1lasztva az alakzathoz tartoz\u00f3 sor. \u00daj alakzat kijel\u00f6l\u00e9s\u00e9re az inform\u00e1ci\u00f3s panelen a megfelel\u0151 sor kiv\u00e1laszt\u00e1s\u00e1val van m\u00f3d. Ezt pr\u00f3b\u00e1ljuk is ki. Azt tapasztaljuk, hogy v\u00e1ltoztat\u00e1skor a bal oldali grafikus fel\u00fclet is friss\u00fcl, a kiv\u00e1lasztott alakzat sz\u00edne piros lesz. Hangs\u00falyozzuk, hogy ez bizony a klasszikus dokumentum-n\u00e9zet architekt\u00fara alap\u00fa megk\u00f6zel\u00edt\u00e9s ig\u00e9ny\u00e9t veti fel: a dokumentumunkhoz k\u00e9t n\u00e9zet kapcsol\u00f3dik, melyeket konzisztensen kell tartani. Megjegyz\u00e9s: a teljes \u00e9rt\u00e9k\u0171 megold\u00e1sban a bal oldali grafikus n\u00e9zetben is megval\u00f3s\u00edthatn\u00e1nk az eg\u00e9rkattint\u00e1sra t\u00f6rt\u00e9n\u0151 kijel\u00f6l\u00e9st. Ez jelent\u0151sen komplexebb\u00e9 tenn\u00e9 a k\u00e9s\u0151bbi feladataink megval\u00f3s\u00edt\u00e1s\u00e1t, \u00edgy ezt sz\u00e1nd\u00e9kosan kihagyjuk.
                                "},{"location":"labor/old-7-tervezesi-mintak/#megvalositando-funkciok","title":"Megval\u00f3s\u00edtand\u00f3 funkci\u00f3k","text":"

                                A k\u00f6vetkez\u0151 funkci\u00f3kat fogjuk a gyakorlat sor\u00e1n megval\u00f3s\u00edtani:

                                • \u00daj t\u00e9glalap \u00e9s \u00faj ellipszis l\u00e9trehoz\u00e1sa v\u00e9letlen poz\u00edci\u00f3ban. A funkci\u00f3k az eszk\u00f6zs\u00e1von (toolbar) \u00e9s a Tools men\u00fc alatt is el\u00e9rhet\u0151k.
                                • Dokumentum tartalm\u00e1nak t\u00f6rl\u00e9se. Minden alakzatot elt\u00e1vol\u00edt a dokumentumb\u00f3l. File/Clear men\u00fcvel el\u00e9rhet\u0151.
                                • Visszavon\u00e1s (Undo). Az utols\u00f3 parancs visszavon\u00e1sa, ak\u00e1rh\u00e1ny l\u00e9p\u00e9sig visszamen\u0151en. Visszavonja az utols\u00f3 parancsot, legyen az valamilyen \u00faj alakzat l\u00e9trehoz\u00e1sa, vagy a dokumentum tartalm\u00e1nak t\u00f6rl\u00e9se. Az eszk\u00f6zs\u00e1von \u00e9s \u00e9s az Edit/Undo men\u00fcvel is el\u00e9rhet\u0151.
                                "},{"location":"labor/old-7-tervezesi-mintak/#a-solution-felepitese","title":"A solution fel\u00e9p\u00edt\u00e9se","text":"

                                A k\u00f6vetkez\u0151 l\u00e9p\u00e9sben a kiindul\u00f3 k\u00f3db\u00e1zissal fogunk megismerkedni. Gyakorlatilag az architekt\u00far\u00e1nk komponens n\u00e9zet\u00e9t tekintj\u00fck \u00e1t ebben a l\u00e9p\u00e9sben. A Visual Studio Solution Explorer ablak\u00e1ban megfigyelhet\u0151, hogy solution\u00fcnk k\u00e9t projektet is tartalmaz.

                                • AppFx: Egy oszt\u00e1lyk\u00f6nyvt\u00e1r (egy DLL a kimenete). Az ebben tal\u00e1lhat\u00f3 oszt\u00e1lyok \u00e1ltal\u00e1nos dokumentumkezel\u00e9si \u00e9s parancskezel\u00e9si szolg\u00e1ltat\u00e1sokat val\u00f3s\u00edtanak meg, melyek ak\u00e1r t\u00f6bb alkalmaz\u00e1sban is felhaszn\u00e1lhat\u00f3k. Az oszt\u00e1lyk\u00f6nyvt\u00e1r bevezet\u00e9s\u00e9vel az els\u0151dleges c\u00e9lunk teh\u00e1t az \u00fajrafelhaszn\u00e1lhat\u00f3s\u00e1g el\u00e9r\u00e9se.
                                • DesignPatternApp: A futtathat\u00f3 (.exe) alkalmaz\u00e1sunk projektje, mely \u00e9p\u00edt az AppFx oszt\u00e1lyk\u00f6nyvt\u00e1rra.

                                F\u00fcgg\u0151s\u00e9g a projektek k\u00f6z\u00f6tt

                                Visual Studio projektek k\u00f6z\u00f6tt mindig csak egyir\u00e1ny\u00fa f\u00fcgg\u0151s\u00e9g lehet. Jelen esetben a DesignPatternApp \u00e9p\u00edt az AppFx projektre. Ezt a gyakorlatban \u00fagy val\u00f3s\u00edtottuk meg, hogy a DesignPatternApp projektben felvett\u00fcnk egy referenci\u00e1t az AppFx projektre. Ett\u0151l kezdve az DesignPatternApp-ban el\u00e9rhet\u0151k az AppFx (publikus) oszt\u00e1lyai. Ford\u00edtva viszont nem igaz az \u00e1ll\u00edt\u00e1s, \u00e9s ennek el\u00e9r\u00e9s\u00e9re nincs is m\u00f3d.

                                "},{"location":"labor/old-7-tervezesi-mintak/#document-view-architektura","title":"Document-View architekt\u00fara","text":"

                                Az alkalmaz\u00e1sunk a Document-View architekt\u00far\u00e1ra \u00e9p\u00fcl, annak n\u00e9mik\u00e9ppen tov\u00e1bbfejlesztett koncepci\u00f3j\u00e1t val\u00f3s\u00edtja meg: ahelyett, hogy a n\u00e9zeteknek egy Update m\u0171velete lenne, amelyen kereszt\u00fcl \u00e1ltal\u00e1nos v\u00e1ltoz\u00e1s \u00e9rtes\u00edt\u00e9st kapnak a dokumentumukt\u00f3l, a dokumentumok k\u00fcl\u00f6nb\u00f6z\u0151 v\u00e1ltoz\u00e1si esem\u00e9nyeket publik\u00e1lhatnak: minden n\u00e9zet arra az esem\u00e9nyre fizet el\u0151, \u00e9s arr\u00f3l kap \u00e9rtes\u00edt\u00e9st, mely sz\u00e1m\u00e1ra \u00e9rdekes.

                                • Az AppFx projekt DocView mapp\u00e1j\u00e1ban tal\u00e1lhat\u00f3 egy Document oszt\u00e1ly \u00e9s egy IView interf\u00e9sz. N\u00e9zz\u00fck meg \u0151ket sorban (nyissuk is meg a forr\u00e1sf\u00e1jlt):
                                  • Document: K\u00fcl\u00f6nb\u00f6z\u0151 dokumentum t\u00edpusok \u0151soszt\u00e1lyak\u00e9nt szolg\u00e1lhat. T\u00f6bbek k\u00f6z\u00f6tt van egy n\u00e9zet list\u00e1ja. (Megjegyz\u00e9s: mivel alkalmaz\u00e1sunk a Document-View architekt\u00fara egy speci\u00e1lis vari\u00e1ns\u00e1t haszn\u00e1lja, a n\u00e9zet list\u00e1t az \u0151sb\u0151l el is hagyhattuk volna).
                                  • IView: K\u00fcl\u00f6nb\u00f6z\u0151 n\u00e9zet implement\u00e1ci\u00f3k k\u00f6z\u00f6s interf\u00e9sze. Nincs Update m\u0171velet, helyette egy SetDocumentAndRegisterToDocEvents m\u0171veletet tal\u00e1lunk (ebben kell a n\u00e9zetnek a dokumentum megfelel\u0151 esem\u00e9nyeire beregisztr\u00e1lnia).

                                A k\u00f6vetkez\u0151kben a DesignPatternApp projekt kapcsol\u00f3d\u00f3 oszt\u00e1lyait tekintj\u00fck \u00e1t:

                                • DrawingDocument oszt\u00e1ly

                                  • Egy shapes nev\u0171 list\u00e1ban t\u00e1rolja az alakzatokat.
                                  • A selectedShape az aktu\u00e1lisan kiv\u00e1lasztott alakzatra mutat.
                                  • A dokumentum adatai a Shapes, SelectedShape \u00e9s SelectedShapeIndex tulajdons\u00e1gokon kereszt\u00fcl \u00e9rhet\u0151k el a k\u00fclvil\u00e1g (pl. n\u00e9zetek) sz\u00e1m\u00e1ra.
                                  • A kor\u00e1bban ismertetett koncepci\u00f3nknak megfelel\u0151en a dokumentumunk k\u00e9t esem\u00e9nyt is publik\u00e1l, melyek C# esem\u00e9nyk\u00e9nt vannak megval\u00f3s\u00edtva:
                                    • ShapesChanged: azt jelzi, hogy az alakzatok list\u00e1ja megv\u00e1ltozott, pl. \u00faj alakzattal b\u0151v\u00fclt, vagy kiker\u00fclt egy alakzat a list\u00e1b\u00f3l, vagy ak\u00e1r egy alakzat adatai v\u00e1ltoztak meg a list\u00e1ban.
                                    • SelectionChanged: azt jelzi, hogy egy kor\u00e1bbit\u00f3l elt\u00e9r\u0151 alakzat ker\u00fclt kiv\u00e1laszt\u00e1sra (mely piros sz\u00ednnel jelenik meg rajzol\u00e1skor).
                                  • A CreateRect \u00e9s CreateEllipse m\u0171veletek l\u00e9trehoznak egy megfelel\u0151 alakzatot, amit a dokumentum el is t\u00e1rol (\u00e9s term\u00e9szetesen el is s\u00fcti a ShapesChanged esem\u00e9nyt).
                                • ViewBase oszt\u00e1ly

                                  • A n\u00e9zeteink k\u00f6z\u00f6s \u0151soszt\u00e1lya, a k\u00f3dduplik\u00e1ci\u00f3 elker\u00fcl\u00e9s\u00e9re vezett\u00fck be. Implement\u00e1lja az IView interf\u00e9szt.
                                  • UserControl-b\u00f3l sz\u00e1rmazik (hasonl\u00f3 koncepci\u00f3t m\u00e1r l\u00e1ttunk a megel\u0151z\u0151 gyakorlat FontEditor p\u00e9ld\u00e1j\u00e1ban).
                                  • A document tagv\u00e1ltoz\u00f3ban t\u00e1rolja a n\u00e9zetet.
                                  • A dokumentum megfelel\u0151 esem\u00e9nyeire val\u00f3 fel/leiratkoz\u00e1shoz bevezeti a RegisterToDocEvents \u00e9s UnRegisterToDocEvents virtu\u00e1lis m\u0171veleteket, a lesz\u00e1rmazottakban ig\u00e9ny szerint kell implement\u00e1lni.
                                • GraphicsView oszt\u00e1ly
                                  • Az alkalmaz\u00e1sunk bal oldali, grafikus n\u00e9zet\u00e9nek implement\u00e1ci\u00f3ja.
                                  • A ViewBase-b\u0151l sz\u00e1rmazik, \u00edgy k\u00f6zvetve ezen oszt\u00e1lyunk is egy UserControl.
                                  • A RegisterToDocEvents m\u0171velet\u00e9ben a dokumentum mindk\u00e9t esem\u00e9ny\u00e9re (ShapesChanged \u00e9s SelectionChanged) el\u0151fizet, ugyanazt a DocumentOnShapesChanged esem\u00e9nykezel\u0151 f\u00fcggv\u00e9nyt regisztr\u00e1lja be. Az esem\u00e9nykezel\u0151ben egy egyszer\u0171 Invalidate h\u00edv\u00e1st tal\u00e1lunk, mely kik\u00e9nyszer\u00edti a n\u00e9zet\u00fcnk \u00fajrarajzol\u00e1s\u00e1t.
                                  • Az OnPaint megval\u00f3s\u00edt\u00e1s\u00e1nak alapelve: minden alakzatra megh\u00edvjuk a Draw m\u0171veletet, mely gondoskodik a t\u00e9nyleges megjelen\u00edt\u00e9sr\u0151l.
                                • InfoPanel oszt\u00e1ly
                                  • Az alkalmaz\u00e1sunk jobb oldali inform\u00e1ci\u00f3s panel n\u00e9zet\u00e9nek implement\u00e1ci\u00f3ja.
                                  • Szint\u00e9n a ViewBase-b\u0151l sz\u00e1rmazik, \u00edgy k\u00f6zvetve ezen oszt\u00e1lyunk is egy UserControl.
                                  • Az inform\u00e1ci\u00f3k megjelen\u00edt\u00e9s\u00e9re egy ListBox vez\u00e9rl\u0151t haszn\u00e1l.
                                  • A RegisterToDocEvents m\u0171velet\u00e9ben \u0151 is feliratkozik a dokumentum mindk\u00e9t esem\u00e9ny\u00e9re:
                                    • Amikor a dokumentum ShapesChanged esem\u00e9nye s\u00fcl el, az InfoPanel Document_ShapesChanged m\u0171velete h\u00edv\u00f3dik meg: ebben friss\u00edti a listbox tartalm\u00e1t a dokumentum aktu\u00e1lis \u00e1llapot\u00e1nak megfelel\u0151en.
                                    • Amikor a dokumentum SelectionChanged esem\u00e9nye s\u00fcl el, a Document_SelectionChanged h\u00edv\u00f3dik: ebben a listbox megfelel\u0151 elem\u00e9t \u00e1ll\u00edtjuk be kiv\u00e1lasztottnak.
                                  • Amikor a felhaszn\u00e1l\u00f3 egy \u00faj elemet v\u00e1laszt ki a listboxban, a listBox_SelectedIndexChanged esem\u00e9nykezel\u0151 h\u00edv\u00f3dik: ebben az App.Instance.SetSelectedShape() h\u00edv\u00e1ssal az aktu\u00e1lis dokumentumunkban \u00e1ll\u00edtjuk \u00e1t a kiv\u00e1lasztott alakzatot a listbox felhaszn\u00e1l\u00f3 \u00e1ltal kiv\u00e1lasztott sor\u00e1nak megfelel\u0151en.
                                • Shape, Rect, Ellipse oszt\u00e1lyok
                                  • A Shape a k\u00f6z\u00f6s \u0151s, az egyes alakzatok ennek lesz\u00e1rmazottai.
                                  • A gyakorlat sor\u00e1n ezek k\u00f3dj\u00e1ra \u00e9ppen csak n\u00e9zz\u00fcnk r\u00e1, ezek implement\u00e1ci\u00f3s r\u00e9szletei sz\u00e1munkra most kev\u00e9sb\u00e9 izgalmasak. Megjegyz\u00e9s: Az egyik feladat megval\u00f3s\u00edt\u00e1sa sor\u00e1n m\u00e1solatot k\u00e9sz\u00edt\u00fcnk majd az alakzatokr\u00f3l. Annak \u00e9rdek\u00e9ben, hogy az alakzatot \u00e9s a m\u00e1solatait \u00f6ssze tudjuk \u201etal\u00e1ltatni\u201d, az egyes alakzatokhoz egy sz\u00e1mazonos\u00edt\u00f3t rendel\u00fcnk (Id tag), mely az alakzat \u00e9s m\u00e1solatai eset\u00e9ben ugyanazt az \u00e9rt\u00e9ket veszi fel.
                                • App oszt\u00e1ly
                                  • Az alkalmaz\u00e1sunk \u201eroot\u201d oszt\u00e1lya, mag\u00e1t az alkalmaz\u00e1st reprezent\u00e1lja. Szerepe hasonl\u00f3, mint az el\u0151z\u0151 gyakorlat FontEditor p\u00e9ld\u00e1j\u00e1ban.
                                  • Megval\u00f3s\u00edt\u00e1sa a Singleton tervez\u00e9si minta fontosabb elveit k\u00f6veti: egy p\u00e9ld\u00e1ny l\u00e9tezhet bel\u0151le, mely egy statikus Instance nev\u0171 tulajdons\u00e1gon kereszt\u00fcl \u00e9rhet\u0151 el, a konstruktora pedig v\u00e9dett.
                                  • Mivel alkalmaz\u00e1sunkban egyszerre egy dokumentum lehet megnyitva (\u00fan. SDI, Single Document Interface application), egy jelent\u0151s egyszer\u0171s\u00edt\u00e9ssel \u00e9lhett\u00fcnk: a dokumentumunkb\u00f3l \u00e9s mindk\u00e9t n\u00e9zet t\u00edpusunkb\u00f3l egy-egy objektumra van csak sz\u00fcks\u00e9g. Ezekre az App oszt\u00e1lyunkban el is t\u00e1rolunk egy-egy hivatkoz\u00e1st az al\u00e1bbi tagv\u00e1ltoz\u00f3kban:
                                        private DrawingDocument document;\n    private GraphicsView graphicsView;\n    private InfoPanel infoPanel;\n
                                  • Az Initialize m\u0171veletben lev\u0151 CommandBindingManager h\u00edv\u00e1sokra k\u00e9s\u0151bb t\u00e9r\u00fcnk vissza.
                                "},{"location":"labor/old-7-tervezesi-mintak/#command-binding","title":"Command Binding","text":"

                                A komplex felhaszn\u00e1l\u00f3 fel\u00fclettel rendelkez\u0151 alkalmaz\u00e1sok eset\u00e9n gyakran el\u0151fordul, hogy ugyanazt a parancsot k\u00fcl\u00f6nb\u00f6z\u0151 felhaszn\u00e1l\u00f3i fel\u00fcletelemekhez is hozz\u00e1 szeretn\u00e9nk k\u00f6tni. P\u00e9ld\u00e1ul New/Open/Close/Cut/Copy/stb. parancsok a legt\u00f6bb alkalmaz\u00e1sban egyar\u00e1nt el\u00e9rhet\u0151k men\u00fcb\u0151l \u00e9s eszk\u00f6zs\u00e1vr\u00f3l is. Vagyis a parancs \u00e9s a kiv\u00e1lt\u00f3 fel\u00fcletelemek k\u00f6z\u00f6tt egy-t\u00f6bb kapcsolat van. Ilyen esetben egy adott parancs vonatkoz\u00e1s\u00e1ban a k\u00f6vetkez\u0151ket kell megval\u00f3s\u00edtani:

                                • Parancs futtat\u00e1sa. Ak\u00e1rmelyik fel\u00fcletelemet is aktiv\u00e1lja a felhaszn\u00e1l\u00f3, a parancshoz tartoz\u00f3 ugyanazon \u201eesem\u00e9nykezel\u0151\u201d k\u00f3dot kell futtatni. Ez a gyakorlatban egy adott f\u00fcggv\u00e9ny megh\u00edv\u00e1s\u00e1t jelenti.
                                • Fel\u00fcletelemek \u00e1llapotkezel\u00e9se. Adott parancs vonatkoz\u00e1s\u00e1ban valamennyi fel\u00fcletelem \u00e1llapot\u00e1t konzisztensen kell tartani. Vagyis ha pl. letiltjuk az Undo men\u00fct, mert nincs visszavonhat\u00f3 m\u0171velet, akkor a visszavon\u00e1sra szolg\u00e1l\u00f3 Undo toolbar gombot is le kell tiltani. Hasonl\u00f3k\u00e9ppen, ha egy men\u00fcelemet el akarunk rejteni, akkor a kapcsol\u00f3d\u00f3 toolbar gombot is rejteni kell. De m\u00e9g fel\u00fcletelemek kijel\u00f6lts\u00e9gi \u00e1llapot\u00e1t is akarhatjuk szab\u00e1lyozni, erre is \u00e9l a szab\u00e1lyunk.

                                N\u00e9zz\u00fcnk erre p\u00e9ld\u00e1kat az alkalmaz\u00e1sunkban:

                                1. Ind\u00edtsuk el az alkalmaz\u00e1st. Figyelj\u00fck meg, hogy a File men\u00fc alatt a Save/Save As/Close men\u00fcelemek \u00e9s az ezeknek megfelel\u0151 toolbar gombok (a Close a harmadik gomb a toolbaron) is le vannak tiltva: am\u00edg nem hoztunk l\u00e9tre vagy t\u00f6lt\u00f6tt\u00fcnk be dokumentumot, ezen parancsok futtat\u00e1s\u00e1nak nincs \u00e9rtelme.
                                2. Hozzunk l\u00e9tre egy \u00faj dokumentumot (File/New men\u00fc). Ekkor valamennyi, az el\u0151z\u0151 pontban eml\u00edtett fel\u00fcletelem konzisztens m\u00f3don enged\u00e9lyezett lesz.
                                3. Ha bez\u00e1rjuk a dokumentumot (File/Close men\u00fc), ism\u00e9t valamennyi fel\u00fcletelem tiltott lesz.

                                Egy komplex alkalmaz\u00e1sban a fenti probl\u00e9mak\u00f6r egyszer\u0171 kezel\u00e9s\u00e9re c\u00e9lszer\u0171 egy k\u00f6zponti megold\u00e1st bevezetni. Ezt sz\u00e1mos m\u00f3don lehet implement\u00e1lni, a legt\u00f6bb k\u00f6rnyezet Command Binding n\u00e9ven hivatkozik a koncepci\u00f3ra. Sajnos az elnevez\u00e9s tekintet\u00e9ben nincs egys\u00e9gess\u00e9g: van olyan technol\u00f3gia, mely Command n\u00e9ven neves\u00edti ezt a technik\u00e1t. Mi Command minta alatt \u2013 a tervez\u00e9si mint\u00e1k klasszikus nevez\u00e9ktan\u00e1t k\u00f6vetve \u2013 m\u00e1st fogunk \u00e9rteni, egy k\u00e9s\u0151bbi feladatban t\u00e9r\u00fcnk majd r\u00e1.

                                A Command Binding minta alapelvei:

                                • Minden felhaszn\u00e1l\u00f3i parancshoz egy k\u00f6zponti (CommandBinding) objektumot hozunk l\u00e9tre.
                                • Ehhez hozz\u00e1k\u00f6tj\u00fck a parancs aktiv\u00e1l\u00e1sakor futtatand\u00f3 esem\u00e9nykezel\u0151t.
                                • Hozz\u00e1k\u00f6tj\u00fck valamennyi aktiv\u00e1l\u00f3 fel\u00fcletelemet (men\u00fc, toolbar gomb stb.)
                                • Seg\u00e9dm\u0171veleteket vezet\u00fcnk be a parancsok tilt\u00e1s\u00e1ra/enged\u00e9lyez\u00e9s\u00e9re/elrejt\u00e9s\u00e9re/megjelen\u00edt\u00e9s\u00e9re.
                                • A parancsokat a k\u00f6nny\u0171 azonos\u00edt\u00e1s \u00e9rdek\u00e9ben valamilyen egyszer\u0171 m\u00f3don, tipikusan stringgel azonos\u00edtjuk.

                                A solution\u00fcnk AppFx projektj\u00e9ben tal\u00e1lunk t\u00e1mogat\u00e1st a Command Binding megval\u00f3s\u00edt\u00e1s\u00e1ra (CommandBinding mappa). A megval\u00f3s\u00edt\u00e1s r\u00e9szletei sz\u00e1munkra teljesen \u00e9rdektelenek, gyakorlaton ne is n\u00e9zz\u00fck a k\u00f3dj\u00e1t, ink\u00e1bb a felhaszn\u00e1l\u00e1s\u00e1nak m\u00f3dj\u00e1t tekints\u00fck \u00e1t r\u00f6viden DesignPatternApp projekt\u00fcnkben:

                                • N\u00e9zz\u00fck meg a CommandName oszt\u00e1lyt: minden parancshoz egy string nevet vezett\u00fcnk be, mely a parancsot azonos\u00edtja (pl. \"Open\", \"Undo\" stb.).
                                • Inicializ\u00e1l\u00e1s: a CommandBinding objektumokat a MainForm oszt\u00e1ly initCommandBindings m\u0171veletben hozzuk l\u00e9tre \u00e9s k\u00f6tj\u00fck hozz\u00e1 az egyes men\u00fcelemekhez/toolbar gombokhoz. El\u00e9g, ha itt egy p\u00e9ld\u00e1t megn\u00e9z\u00fcnk a sok el\u0151fordul\u00e1s k\u00f6z\u00fcl.
                                • Ezt k\u00f6vet\u0151en adott parancshoz tartoz\u00f3 fel\u00fcletelemek \u00e1llapotai a CommandBindingManager oszt\u00e1ly \u00e1llapot\u00e1ll\u00edt\u00f3 seg\u00e9df\u00fcggv\u00e9nyeivel b\u00e1rmikor k\u00e9nyelmesen \u00e1ll\u00edthat\u00f3k (pl. EnableCommandBinding tilt\u00e1shoz/enged\u00e9lyez\u00e9shez). N\u00e9zz\u00fck meg, hogyan t\u00f6rt\u00e9nik ez a Save/Save As/Close parancsok vonatkoz\u00e1s\u00e1ban:
                                  • Amikor az alkalmaz\u00e1s elindul, a parancsokat az App oszt\u00e1ly Initialize m\u0171velet\u00e9ben tiltjuk (a false m\u00e1sodik param\u00e9ter jelzi, hogy tiltani akarjuk a vez\u00e9rl\u0151t):
                                    CommandBindingManager.Instance.EnableCommandBinding(\n    CommandName.CloseDocument, false);\nCommandBindingManager.Instance.EnableCommandBinding(\n    CommandName.SaveDocument, false);\nCommandBindingManager.Instance.EnableCommandBinding(\n    CommandName.SaveAsDocument, false);\n
                                  • A parancsok enged\u00e9lyez\u00e9s\u00e9re az App.NewDocument-ben l\u00e1tunk p\u00e9ld\u00e1t. Ez a m\u0171velet egy parancs esem\u00e9nykezel\u0151je, a t\u00f6bbi esem\u00e9nykezel\u0151vel egy\u00fctt az App.CommandHandlers.cs f\u00e1jlban tal\u00e1lhat\u00f3 (az App oszt\u00e1ly \u201epartial\u201d, t\u00f6bb f\u00e1jlban van meg\u00edrva).
                                    CommandBindingManager.Instance.EnableCommandBinding(\n    CommandName.CloseDocument, true);\nCommandBindingManager.Instance.EnableCommandBinding(\n    CommandName.SaveDocument, true);\nCommandBindingManager.Instance.EnableCommandBinding(\n    CommandName.SaveAsDocument, true);\n

                                A tov\u00e1bbi feladatok megval\u00f3s\u00edt\u00e1sa sor\u00e1n is a CommandBindingManager oszt\u00e1lyunkat fogjuk haszn\u00e1lni a parancsok tilt\u00e1s\u00e1hoz \u00e9s enged\u00e9lyez\u00e9s\u00e9hez.

                                "},{"location":"labor/old-7-tervezesi-mintak/#2-feladat-command-processor-minta","title":"2. Feladat - Command Processor minta","text":"

                                A feladat sor\u00e1n a Command, pontosabban annak tov\u00e1bbfejlesztett v\u00e1ltozata, a Command Processor tervez\u00e9si minta megval\u00f3s\u00edt\u00e1s\u00e1t fogjuk gyakorolni. Mindk\u00e9t minta elm\u00e9leti h\u00e1tter\u00e9t a kapcsol\u00f3d\u00f3 el\u0151ad\u00e1s ismerteti r\u00e9szletesen, UML diagramokkal illusztr\u00e1lva. A gyakorlat sor\u00e1n, \u00e9s \u00edgy jelen \u00fatmutat\u00f3ban is csak az elm\u00e9leti h\u00e1tt\u00e9r legfontosabb elemeire t\u00e9r\u00fcnk ki. L\u00e9nyeges, hogy a mint\u00e1t ne keverj\u00fck a m\u00e1r kor\u00e1bban ismertetett Command Binding mint\u00e1val, mert att\u00f3l elt\u00e9r\u0151 probl\u00e9m\u00e1ra mutat megold\u00e1st.

                                "},{"location":"labor/old-7-tervezesi-mintak/#a-command-processor-minta-koncepcioja","title":"A Command Processor minta koncepci\u00f3ja","text":"

                                A minta alapelve az, hogy minden felhaszn\u00e1l\u00f3i k\u00e9r\u00e9st egy k\u00fcl\u00f6n parancs (Command) objektumk\u00e9nt z\u00e1r egys\u00e9gbe. Ezen t\u00falmen\u0151en a v\u00e9grehajtott parancs objektumok elt\u00e1rol\u00e1sra ker\u00fclnek, ami lehet\u0151v\u00e9 teszi a kor\u00e1bban v\u00e9grehajtott parancsok visszavon\u00e1s\u00e1t. A k\u00f6vetkez\u0151kben \u00e1ttekintj\u00fck a minta m\u0171k\u00f6d\u00e9s\u00e9t. Els\u0151 l\u00e9p\u00e9sben m\u00e9g nem val\u00f3s\u00edtjuk meg, csak az alapelveire koncentr\u00e1lunk (b\u00e1r hogy k\u00f6nnyebben meg\u00e9rthet\u0151 legyen, a mint\u00e1t az alkalmaz\u00e1sunkra vet\u00edtve mutatjuk be). K\u00f6vetkezzen egy \u00e1bra, majd a hozz\u00e1 kapcsol\u00f3d\u00f3 gondolatok.

                                • Bevezet\u00fcnk egy Command \u0151soszt\u00e1lyt vagy interf\u00e9szt, melynek van egy Execute \u00e9s egy UnExecute absztrakt m\u0171velete (vagy nevezhetj\u00fck Do \u00e9s Undo-nak is \u0151ket, ha \u00fagy tartja kedv\u00fcnk).
                                • Az egyes felhaszn\u00e1l\u00f3i parancsokhoz bevezet\u00fcnk egy Command lesz\u00e1rmazott oszt\u00e1lyt.
                                  • Els\u0151 k\u00f6rben a New Rect \u00e9s New Ellipse parancsokra vonatkoz\u00f3an k\u00edv\u00e1nunk Undo t\u00e1mogat\u00e1st bevezetni, \u00edgy ezekhez vezet\u00fcnk majd r\u00f6videsen be egy-egy \u00faj oszt\u00e1lyt, pl. NewRectCommand \u00e9s NewEllipseCommand n\u00e9ven.
                                  • Ezen oszt\u00e1lyokban a parancsspecifikusan meg\u00edrjuk az Execute m\u0171veletet (pl. a NewRectCommand.Execute-ban felvesz\u00fcnk a dokumentumunkban egy \u00faj t\u00e9glalapot), az UnExecute-ban pedig visszacsin\u00e1ljuk a m\u0171velet hat\u00e1s\u00e1t.
                                  • A Command lesz\u00e1rmazott oszt\u00e1lyok sokszor nem maguk val\u00f3s\u00edtj\u00e1k meg funkci\u00f3jukat, hanem deleg\u00e1lj\u00e1k azt egy vagy t\u00f6bb m\u00e1sik oszt\u00e1lynak. Ezt az oszt\u00e1lyt az UML diagramon Receiver n\u00e9ven t\u00fcntett\u00fck fel. A gyakorlatban nem \u00edgy szoktuk h\u00edvni. Alkalmaz\u00e1sunkban a Command-ok tipikusan az App oszt\u00e1lyba h\u00edvnak tov\u00e1bb, vagyis eset\u00fcnkben az App felel meg legt\u00f6bb esetben az \u00e1br\u00e1n szerepl\u0151 Receiver oszt\u00e1lynak.
                                • Bevezet\u00fcnk egy k\u00f6zponti CommandProcessor oszt\u00e1lyt k\u00e9t m\u0171velettel:
                                  • ExecuteCommand: v\u00e9grehajtja a param\u00e9ter\u00fcl kapott parancsot (megh\u00edvja az Execute m\u0171velet\u00e9t), majd elt\u00e1rolja egy bels\u0151 stack gy\u0171jtem\u00e9nyben.
                                  • UnExecuteLastCommand: kiveszi az utolj\u00e1ra v\u00e9grehajtott parancsot a command stack-b\u0151l, \u00e9s megh\u00edvja annak UnExecute m\u0171velet\u00e9t. Ezzel gyakorlatilag a parancs visszavon\u00e1s funkci\u00f3j\u00e1t (Undo) val\u00f3s\u00edtja meg.

                                L\u00e9nyeges, hogy a Command Binding mint\u00e1val ellent\u00e9tben itt a parancsok minden egyes futtat\u00e1s\u00e1hoz \u00faj Command objektumot hozunk l\u00e9tre, vagyis ha pl. h\u00e1romszor \u201efuttatjuk\" a NewRectCommand parancsot, akkor h\u00e1rom NewRectCommand objektumot hozunk ehhez l\u00e9tre. Ennek oka az, hogy a CommandProcessor command stack-j\u00e9ben h\u00e1rom parancsobjektumot kell elt\u00e1rolni (hiszen ezeket egym\u00e1st\u00f3l f\u00fcggetlen\u00fcl akarjuk visszavonni Undo eset\u00e9n).

                                "},{"location":"labor/old-7-tervezesi-mintak/#a-command-processor-minta-megvalositasa-alkalmazasunkban","title":"A Command Processor minta megval\u00f3s\u00edt\u00e1sa alkalmaz\u00e1sunkban","text":"

                                K\u00f6vess\u00fck az al\u00e1bbi l\u00e9p\u00e9seket:

                                1. Az AppFx projekt Command mapp\u00e1j\u00e1ban m\u00e1r l\u00e9tezik egy absztrakt Command oszt\u00e1ly, \u00edgy ezzel nincs teend\u0151nk.
                                2. A Command mapp\u00e1ban vegy\u00fck fel a parancsok menedzsel\u00e9s\u00e9\u00e9rt felel\u0151s az \u00e1ltal\u00e1nos CommandProcessor oszt\u00e1lyt:
                                  public class CommandProcessor\n{\n    Stack<Command> commands = new Stack<Command>();\n\n    public void ExecuteCommand(Command cmd)\n    {\n        cmd.Execute();\n        commands.Push(cmd);\n    }\n\n    public void UnExecuteLastCommand()\n    {\n        // Ha \u00fcres, nem csin\u00e1lunk semmit\n        if (!commands.Any())\n            return;\n\n        Command lastCommand = commands.Pop();\n        lastCommand.UnExecute();\n    }\n\n    public void Clear()\n    {\n      commands.Clear();\n    }\n\n    public bool HasAny { get { return commands.Any(); } }\n}\n
                                  A megval\u00f3s\u00edt\u00e1s sor\u00e1n a .NET be\u00e9p\u00edtett Stack<T> oszt\u00e1ly\u00e1t haszn\u00e1ljuk a command stack megval\u00f3s\u00edt\u00e1s\u00e1ra. A met\u00f3dusok implement\u00e1ci\u00f3ja egyszer\u0171, a kor\u00e1bban ismertetett logik\u00e1t k\u00f6veti.
                                3. Integr\u00e1ljuk be a CommandProcessor oszt\u00e1lyt az alkalmaz\u00e1sunkba. Vegy\u00fcnk fel egy tagv\u00e1ltoz\u00f3t az App oszt\u00e1lyba:
                                  readonly CommandProcessor commandProcessor = new CommandProcessor();\n
                                  Annak \u00e9rdek\u00e9ben, hogy ez forduljon, a forr\u00e1sf\u00e1jlban az AppFx.Command n\u00e9vteret \u201eusing-olni\u201d kell.
                                4. Az App.CommandHandlers.cs-be a CloseDocument v\u00e9g\u00e9re vegy\u00fck fel ezt a sort:
                                  commandProcessor.Clear();\n
                                  Ennek az a szerepe, hogy amikor bez\u00e1rjuk a dokumentumot, kipucoljuk az undo sort, hiszen a benne lev\u0151 elemek egy m\u00e1r bez\u00e1rt, nem l\u00e9tez\u0151 dokumentumra vonatkoznak.
                                5. Amikor egy parancsot futtatunk vagy visszavonunk, az Undo men\u00fct \u00e9s toolbar gombot is megfelel\u0151en tiltani vagy enged\u00e9lyezni kell: ha van legal\u00e1bb egy command a command stack-en, akkor enged\u00e9lyezz\u00fck, egy\u00e9bk\u00e9nt tiltjuk. Vezess\u00fcnk be egy-egy seg\u00e9df\u00fcggv\u00e9nyt az App oszt\u00e1lyba a parancsok futtat\u00e1s\u00e1hoz \u00e9s visszavon\u00e1s\u00e1hoz, melyek a kor\u00e1bban ismertetett CommandBindingManager oszt\u00e1lyunk seg\u00edts\u00e9g\u00e9vel gondoskodnak az Undo tilt\u00e1s\u00e1r\u00f3l/enged\u00e9lyez\u00e9s\u00e9r\u0151l is:
                                  void executeCommand(Command cmd)\n{\n    commandProcessor.ExecuteCommand(cmd);\n    CommandBindingManager.Instance.EnableCommandBinding(\n                    CommandName.Undo, commandProcessor.HasAny);\n}\n
                                  void unexecuteLastCommand()\n{\n    commandProcessor.UnExecuteLastCommand();\n    CommandBindingManager.Instance.EnableCommandBinding(\n                    CommandName.Undo, commandProcessor.HasAny);\n}\n
                                  Az unexecuteLastCommand m\u0171veletet akkor kell megh\u00edvni, amikor a felhaszn\u00e1l\u00f3 az Undo funkci\u00f3t aktiv\u00e1lja. Az App.CommandHandlers.cs f\u00e1jlban lev\u0151 UndoLast met\u00f3dus egy CommandBinding seg\u00edts\u00e9g\u00e9vel m\u00e1r hozz\u00e1 van k\u00f6tve a fel\u00fcletelemekhez (Undo men\u00fc \u00e9s toolbar gomb), \u00edgy aktiv\u00e1l\u00e1sukkor meg is h\u00edv\u00f3dik. M\u00e1r csak az a dolgunk, hogy \u00e1t\u00edrjuk az UndoLast t\u00f6rzs\u00e9t:
                                  public void UndoLast()\n{\n    unexecuteLastCommand();\n}\n
                                6. A k\u00f6vetkez\u0151 l\u00e9p\u00e9sekben Command lesz\u00e1rmazott oszt\u00e1lyokat hozunk l\u00e9tre az egyes alkalmaz\u00e1sspecifikus parancsokhoz. A DesignPatternApp projektben vegy\u00fcnk fel egy Commands nev\u0171 mapp\u00e1t (jobb katt a projekten, Add/New Folder men\u00fc), ebbe fogjuk az ide tartoz\u00f3 oszt\u00e1lyokat \u00f6sszegy\u0171jteni.
                                7. Ebbe a Commands mapp\u00e1ba vegy\u00fcnk fel egy NewRectCommand oszt\u00e1lyt a \u201eNew Rect\u201d funkci\u00f3 megval\u00f3s\u00edt\u00e1s\u00e1hoz, a k\u00f6vetkez\u0151 k\u00f3ddal:
                                  using AppFx.Command;\n\u2026\n\nclass NewRectCommand : Command\n{\n    private int shapeId;\n\n    public override void Execute()\n    {\n        shapeId = App.Instance.CreateRandomRect().Id;\n    }\n\n    public override void UnExecute()\n    {\n        App.Instance.RemoveShape(shapeId);\n    }\n}\n
                                  Az Execute m\u0171velet megh\u00edvja az App singleton CreateRandomRect m\u0171velet\u00e9t, amely felvesz egy \u00faj Rectangle objektumot a dokumentumban, v\u00e9letlenszer\u0171en gener\u00e1lt befoglal\u00f3 t\u00e9glalapban, \u00e9s visszat\u00e9r vele. Az \u00fajonnan l\u00e9trehozott Rect objektumra a NewRectCommand elt\u00e1rolja az alakzat azonos\u00edt\u00f3j\u00e1t a shapeId tagv\u00e1ltoz\u00f3ban. (Jelen pillanatban egy referencia t\u00e1rol\u00e1sa is el\u00e9g lenne, de mikor k\u00e9s\u0151bb a Memento megval\u00f3s\u00edt\u00e1sa sor\u00e1n m\u00e1solatot k\u00e9sz\u00edt\u00fcnk az alakzat objektumokr\u00f3l, a referencia haszn\u00e1lata m\u00e1r nem jelentene megold\u00e1st.) Az UnExecute m\u0171veletben az App singleton RemoveShape m\u0171velet\u00e9nek seg\u00edts\u00e9g\u00e9vel elt\u00e1vol\u00edtjuk a parancs \u00e1ltal l\u00e9trehozott alakzatot, \u00edgy visszavonjuk annak hat\u00e1s\u00e1t (n\u00e9zz\u00fck meg a k\u00f3dban, hogyan van megval\u00f3s\u00edtva).
                                8. Vegy\u00fcnk fel a Commands mapp\u00e1ba egy NewEllipseCommand oszt\u00e1lyt, \u00e9s implement\u00e1ljuk a NewRectCommand-hoz hasonl\u00f3 elveknek megfelel\u0151en:
                                  using AppFx.Command;\n\nclass NewEllipseCommand : Command\n{\n    private int shapeId;\n\n    public override void Execute()\n    {\n        shapeId = App.Instance.CreateRandomEllipse().Id;\n    }\n\n    public override void UnExecute()\n    {\n        App.Instance.RemoveShape(shapeId);\n    }\n}\n
                                9. A NewRectCommand \u00e9s NewEllipseCommand oszt\u00e1lyainkat m\u00e9g nem haszn\u00e1ljuk sehol, most ezek bevet\u00e9se k\u00f6vetkezik. Amikor a felhaszn\u00e1l\u00f3 ak\u00e1r men\u00fcb\u0151l, ak\u00e1r toolbarr\u00f3l aktiv\u00e1lja a New Rect funkci\u00f3t, l\u00e9tre kell hozzunk egy NewRectCommand objektumot, \u00e9s futtatni kell seg\u00e9dm\u0171veleteink felhaszn\u00e1l\u00e1s\u00e1val. Keress\u00fck meg az App.CommandHandlers.cs f\u00e1jlban a NewRect met\u00f3dust. Ez egy CommandBinding seg\u00edts\u00e9g\u00e9vel m\u00e1r r\u00e1 van k\u00f6tve a megfelel\u0151 men\u00fcre/toolbar gombra, csak a t\u00f6rzs\u00e9ben lev\u0151 showNotImplemented() h\u00edv\u00e1st kell lecser\u00e9lni:
                                  using DesignPatternApp.Commands;\n\u2026\npublic void NewRect()\n{\n    executeCommand( new NewRectCommand() );\n}\n
                                  Ehhez hasonl\u00f3an alak\u00edtsuk \u00e1t a NewRect mellett tal\u00e1lhat\u00f3 NewEllipse m\u0171veletet is:
                                  public void NewEllipse()\n{\n    executeCommand( new NewEllipseCommand() );\n}\n
                                10. Mostant\u00f3l tudunk \u00faj alakzatokat l\u00e9trehozni, \u00edgy \u00faj dokumentum l\u00e9trehoz\u00e1sakor tesztadatok automatikus felv\u00e9tel\u00e9re nincs sz\u00fcks\u00e9g: az App.NewDocument m\u0171veletben kommentezz\u00fck ki az addTestData h\u00edv\u00e1s\u00e1t.

                                Elk\u00e9sz\u00fclt\u00fcnk, tesztelj\u00fck a megold\u00e1sunkat:

                                1. Futtassuk az alkalmaz\u00e1st, \u00e9s hozzunk l\u00e9tre egy dokumentumot.
                                2. Figyelj\u00fck meg, hogy az Undo parancs (toolbar \u00e9s men\u00fc is) tiltva van.
                                3. A New Rect paranccsal hozzunk l\u00e9tre egy \u00faj t\u00e9glalapot. A t\u00e9glalap megjelenik, \u00e9s az Undo parancs enged\u00e9lyezett lesz.
                                4. Hozzunk l\u00e9tre n\u00e9h\u00e1ny tov\u00e1bbi alakzatot, t\u00e9glalapot \u00e9s ellipszist vegyesen.
                                5. Az Undo funkci\u00f3 haszn\u00e1lat\u00e1val vonjuk vissza a m\u0171veleteket mindaddig, am\u00edg nem marad alakzat: ekkor az Undo parancs letilt\u00e1sra ker\u00fcl.

                                Amennyiben a gyakorlat sor\u00e1n j\u00f3l \u00e1llunk id\u0151vel, a k\u00f3dot l\u00e9p\u00e9senk\u00e9nt futtatva is n\u00e9zz\u00fck vissza megold\u00e1sunk m\u0171k\u00f6d\u00e9s\u00e9t:

                                1. Tegy\u00fcnk egy t\u00f6r\u00e9spontot az App.CommandHandlers.cs-ben tal\u00e1lhat\u00f3 NewRect \u00e9s UndoLast m\u0171veletek t\u00f6rzs\u00e9be (mindk\u00e9t m\u0171velet egysoros).
                                2. Ind\u00edtsuk el debug m\u00f3dban az alkalmaz\u00e1st (F5).
                                3. Hozzunk l\u00e9tre egy dokumentumot, majd egy t\u00e9glalapot. A NewRect k\u00f3dj\u00e1b\u00f3l kiindulva az F11 billenty\u0171vel az executeCommand \u00e9s a CommandProcessor m\u0171veleteibe belel\u00e9pve \u201e\u00e9rtelmezz\u00fck\u201d megold\u00e1sunkat.
                                4. Ezt k\u00f6vet\u0151en vonjuk vissza az utols\u00f3 m\u0171veletet. Ekkor az UndoLast m\u0171veletb\u0151l kiindulva l\u00e9pkedj\u00fcnk v\u00e9gig a k\u00f3dunkon.
                                "},{"location":"labor/old-7-tervezesi-mintak/#3-feladat-memento-minta","title":"3. Feladat \u2013 Memento minta","text":"

                                A feladatban a Memento minta megval\u00f3s\u00edt\u00e1s\u00e1t gyakoroljuk. A minta teljes elm\u00e9leti h\u00e1ttere \u2013 UML diagramokkal illusztr\u00e1lva - el\u0151ad\u00e1son ker\u00fcl ismertet\u00e9sre, itt a minta legfontosabb elemeire koncentr\u00e1lunk.

                                "},{"location":"labor/old-7-tervezesi-mintak/#a-memento-minta-koncepcioja","title":"A Memento minta koncepci\u00f3ja","text":"

                                El\u0151z\u0151 feladatunkban a New Rect \u00e9s New Ellipse parancsok visszavon\u00e1s\u00e1t k\u00f6nnyen meg tudtuk val\u00f3s\u00edtani: mind\u00f6ssze el kellett t\u00e1vol\u00edtani a parancs \u00e1ltal l\u00e9trehozott alakzatot a dokumentum alakzatlist\u00e1j\u00e1b\u00f3l. A command objektumainkban ehhez el\u00e9g volt egy azonos\u00edt\u00f3t elt\u00e1rolni az \u00fajonnan l\u00e9trehozott alakzatra.

                                Az alkalmaz\u00e1sok t\u00f6bbs\u00e9g\u00e9n\u00e9l azonban sz\u00e1mos olyan parancs felbukkanhat, mely a dokumentum \u00e1llapot\u00e1t jelent\u0151s m\u00e9rt\u00e9kben befoly\u00e1solja. Ilyenkor a parancsnak a v\u00e9grehajt\u00e1s el\u0151tt a dokumentum \u00e1llapot\u00e1nak jelent\u0151s r\u00e9sz\u00e9hez, vagy ak\u00e1r a teljes \u00e1llapot\u00e1hoz is hozz\u00e1 kell f\u00e9rnie, hogy eltudja azt menteni az UnExecute megval\u00f3s\u00edt\u00e1s\u00e1hoz. Ez \u00fagy lehets\u00e9ges, ha a dokumentum teljes \u00e1llapot\u00e1t publikuss\u00e1 tessz\u00fck. Ez viszont nem szerencs\u00e9s, mert ellentmond az egys\u00e9gbez\u00e1r\u00e1s elv\u00e9nek. Nem szeretn\u00e9nk a teljes \u00e1llapotot \u2013 r\u00e1ad\u00e1sul m\u00f3dos\u00edt\u00e1sra vonatkoz\u00f3an is \u2013 hozz\u00e1f\u00e9rhet\u0151v\u00e9 tenni a k\u00fclvil\u00e1g sz\u00e1m\u00e1ra, csak a visszavon\u00e1s kedv\u00e9\u00e9rt. Erre a probl\u00e9m\u00e1ra ny\u00fajt megold\u00e1st a Memento tervez\u00e9si minta.

                                Alapelve egy mondatban: dokumentumunk \u00e1llapot\u00e1t egy \u00fan. Memento objektumba csomagoljuk be, hogy az k\u00e9s\u0151bb a visszavon\u00e1s sor\u00e1n vissza\u00e1ll\u00edthat\u00f3 legyen.

                                K\u00f6vetkezzen egy \u00e1bra, majd a hozz\u00e1 kapcsol\u00f3d\u00f3 gondolatok.

                                Alapelve r\u00e9szletesebben: - Az Originator azon oszt\u00e1ly, melynek az \u00e1llapot\u00e1hoz hozz\u00e1 szeretn\u00e9nk f\u00e9rni. Eset\u00fcnkben ez a DrawingDocument oszt\u00e1ly t\u00f6lti be az Originator szerep\u00e9t. Az \u00e1llapotot \u00f6sszefog\u00f3an az \u00e1bra a state:State taggal jel\u00f6li. Eset\u00fcnkben ez a shapes lista, valamint a selectedShape tag lesz. A k\u00f6vetkez\u0151 l\u00e9p\u00e9sekt\u0151l a mint\u00e1t az alkalmaz\u00e1sunkra vet\u00edtj\u00fck. - A dokumentumunk \u00e1llapot\u00e1t (eset\u00fcnkben ez a shapes lista, valamit a selectedShape tag) NEM tessz\u00fck publikuss\u00e1. - A dokumentumunkban bevezet\u00fcnk egy CreateMemento m\u0171veletet, mely egy \u00fan. Memento objektumot hoz l\u00e9tre. A Memento tagv\u00e1ltoz\u00f3iban a dokumentum \u00e1llapot\u00e1nak pillanatnyi k\u00e9p\u00e9t tartalmazza (vagyis tulajdonk\u00e9ppen egy csomagol\u00f3 objektum a dokumentum aktu\u00e1lis \u00e1llapot\u00e1hoz). - A dokumentum \u00e1llapot\u00e1nak vissza\u00e1ll\u00edt\u00e1s\u00e1ra bevezet\u00fcnk a dokumentumban egy RestoreFromMemento m\u0171veletet, mely param\u00e9terk\u00e9nt egy Memento objektumot kap. A dokumentum ebben a m\u0171veletben vissza\u00e1ll\u00edtja saj\u00e1t \u00e1llapot\u00e1t a param\u00e9terk\u00e9nt kapott Memento objektum alapj\u00e1n.

                                "},{"location":"labor/old-7-tervezesi-mintak/#a-memento-minta-megvalositasa-alkalmazasunkban","title":"A Memento minta megval\u00f3s\u00edt\u00e1sa alkalmaz\u00e1sunkban","text":"

                                Alkalmaz\u00e1sunkban a Clear funkci\u00f3t val\u00f3s\u00edtjuk meg a Memento mint\u00e1ra \u00e9p\u00edtve. A Clear parancs t\u00f6rli a dokumentumb\u00f3l az \u00f6sszes alakzatot. Annak \u00e9rdek\u00e9ken, hogy ez visszavonhat\u00f3 legyen, a dokumentumunk teljes \u00e1llapot\u00e1t el kell menteni a parancs v\u00e9grehajt\u00e1sa el\u0151tt. Ehhez a DrawingDocument oszt\u00e1lyunk \u00e1llapot\u00e1t jelent\u0151 shapes tagot NEM fogjuk publikuss\u00e1 tenni. M\u00e9g k\u00f6zvetve, property-n/m\u0171veleten kereszt\u00fcl sem tessz\u00fck m\u00f3dos\u00edthat\u00f3v\u00e1!

                                Warning

                                Amennyiben kev\u00e9s id\u0151 maradt gyakorlaton, nyissuk meg a k\u00e9sz megold\u00e1st, \u00e9s abban mutassuk be a megval\u00f3s\u00edt\u00e1s r\u00e9szleteit!

                                • A dokumentum \u00e1llapot\u00e1t t\u00e1rol\u00f3 Memento oszt\u00e1lyt egy a DrawingDocument-be be\u00e1gyazott oszt\u00e1lyk\u00e9nt val\u00f3s\u00edtjuk meg, ezzel is hangs\u00falyozva, hogy Memento oszt\u00e1lyunk nagyon szorosan kapcsol\u00f3dik a dokumentumhoz. Forr\u00e1sk\u00f3d szintj\u00e9n viszont igyeksz\u00fcnk lev\u00e1lasztani, \u00edgy a DrawingDocument oszt\u00e1lyt partial class-ra alak\u00edtva k\u00fcl\u00f6n f\u00e1jlban dolgozunk.

                                  • Alak\u00edtsuk a DrawingDocument-et partial class-\u00e1:
                                    public partial class DrawingDocument\n
                                  • Vegy\u00fcnk fel egy DrawingDocument.Memento.cs f\u00e1jlt a DesignPatternApp projektbe (jobb katt a projekten, Add/New Item, \u00e9s a megjelen\u0151 ablakban a Code File-t v\u00e1lasszuk ki).
                                  • Illessz\u00fck be az al\u00e1bbi k\u00f3dr\u00e9szletet a f\u00e1jlba:

                                    DrawingDocument.Memento.cs
                                    using System.Collections.Generic;\n\nnamespace DesignPatternApp\n{\n    public partial class DrawingDocument\n    {\n        public class Memento\n        {\n            private List<Shape> shapes = new List<Shape>();\n            private Shape selectedShape;\n\n            public Memento(List<Shape> shapes, Shape selectedShape)\n            {\n                // Deep copyra van sz\u00fcks\u00e9g\u00fcnk!\n                foreach (Shape shape in shapes)\n                    this.shapes.Add(shape.CreateCopy());\n\n                // Be kell \u00e1ll\u00edtsuk selectedShape-nek. Az \u00faj Shape list\u00e1ban kell a megfelel\u00f5\n                // elemre hivatkoznia, nem az eredetiben. Be kell \u00e1ll\u00edtsuk.\n                this.selectedShape = null;\n                for (int i = 0; i < shapes.Count; ++i)\n                    if (shapes[i] == selectedShape)\n                    {\n                        this.selectedShape = this.shapes[i];\n                        break;\n                    }\n            }\n\n            public void GetState(out List<Shape> shapes, out Shape selectedShape)\n            {\n                shapes = this.shapes;\n                selectedShape = this.selectedShape;\n            }\n        }\n\n    }\n}\n
                                • A Memento oszt\u00e1lyunk legfontosabb aspektusai:

                                  • Pontosan olyan tagv\u00e1ltoz\u00f3i vannak, mint a dokumentum oszt\u00e1lyunknak: \u00edgy tudja annak teljes \u00e1llapot\u00e1t elt\u00e1rolni.
                                  • Konstruktor\u00e1ban a dokumentum \u00e1llapotv\u00e1ltoz\u00f3it v\u00e1rja (shapes \u00e9s selectedShape). L\u00e9nyeges, hogy a shapes list\u00e1r\u00f3l deep-copy m\u00e1solatot k\u00e9sz\u00edt: ha csak referenci\u00e1kat t\u00e1rolna a dokumentumban lev\u0151 objektumokra, akkor a dokumentum v\u00e1ltoz\u00e1s\u00e1val a memento objektumunk \u00e1llapota is v\u00e1ltozna. Nek\u00fcnk viszont az aktu\u00e1lis \u00e1llapot meg\u0151rz\u00e9se a c\u00e9lunk.
                                  • A GetState-ben k\u00e9t out param\u00e9terben visszaadja az elmentett \u00e1llapotot. Az Undo m\u0171velet sor\u00e1n fogjuk ezt haszn\u00e1lni.
                                • Emelj\u00fck be az al\u00e1bbi k\u00f3dr\u00e9szletet a DrawingDocument.cs f\u00e1jlba a DrawingDocument oszt\u00e1lyba:

                                  DrawingDocument.cs f\u00e1jlba
                                  public Memento CreateMemento()\n{\n    return new Memento(shapes, selectedShape);\n}\n\npublic void RestoreFromMemento(Memento m)\n{\n    m.GetState(out shapes, out selectedShape);\n    fireShapesChanged();\n    fireSelectionChanged();\n}\n

                                  A CreateMemento m\u0171velet a mint\u00e1nak megfelel\u0151en legy\u00e1rt egy Memento objektumot a dokumentum \u00e1llapot\u00e1r\u00f3l. A RestoreFromMemento pedig a param\u00e9ter\u00fcl kapott Memento objektum alapj\u00e1n vissza\u00e1ll\u00edtja a dokumentum \u00e1llapot\u00e1t.

                                Ezzel a Memento t\u00e1mogat\u00e1s be\u00e9p\u00edt\u00e9s\u00e9vel v\u00e9gezt\u00fcnk. Ugyanakkor jelen pillanatban egyetlen parancsunk sem haszn\u00e1lja ezt a szolg\u00e1ltat\u00e1st. Mint kor\u00e1bban eml\u00edtett\u00fck, a Clear funkci\u00f3t val\u00f3s\u00edtjuk meg a Memento mint\u00e1ra \u00e9p\u00edtve.

                                • Vegy\u00fcnk fel egy ClearCommand oszt\u00e1lyt a DesignPatternApp projekt Commands mapp\u00e1j\u00e1ban.
                                • Emelj\u00fck be az al\u00e1bbi k\u00f3dr\u00e9szletet az \u00faj ClearCommand.cs f\u00e1jlba:

                                  ClearCommand.cs
                                  class ClearCommand: Command\n{\n    DrawingDocument.Memento memento = null;\n\n    public override void Execute()\n    {\n        if (App.Instance.Document == null)\n            return;\n\n        memento = App.Instance.Document.CreateMemento();\n        App.Instance.Document.Clear();\n    }\n\n    public override void UnExecute()\n    {\n        if (App.Instance.Document == null)\n            return;\n\n        App.Instance.Document.RestoreFromMemento(memento);\n    }\n}\n
                                • Vegy\u00fck fel a f\u00e1jl elej\u00e9re ez al\u00e1bbi sort:

                                  using AppFx.Command;\n

                                • Az App.CommandHandlers.cs f\u00e1jlban a ClearDocument m\u0171veletet \u00edrjuk \u00e1t, hogy most m\u00e1r az \u00fajonnan l\u00e9trehozott ClearCommand parancsunkat \u201efuttassa\u201d:
                                  public void ClearDocument()\n{\n    executeCommand(new ClearCommand());\n}\n

                                Tesztelj\u00fck megold\u00e1sunkat:

                                • Futtassuk az alkalmaz\u00e1st,
                                • Hozzunk l\u00e9tre p\u00e1r alakzatot,
                                • A File/Clear men\u00fcb\u0151l futtassuk a Clear parancsot: az alakzataink elt\u0171nnek.
                                • Az Undo paranccsal vonjuk vissza a parancsot: az alakzatok \u00fajra megjelennek.

                                L\u00e9p\u00e9senk\u00e9nt futtatva is tesztelj\u00fck a megold\u00e1st:

                                • Tegy\u00fcnk egy t\u00f6r\u00e9spontot a ClearCommand.Execute m\u0171velet els\u0151 sor\u00e1ra.
                                • Ind\u00edtsuk ez az alkalmaz\u00e1st, hozzunk l\u00e9tre p\u00e1r alakzatot, majd a File/Clear men\u00fcb\u0151l futtassuk a Clear parancsot.
                                • Mikor a k\u00f3dunk meg\u00e1ll a t\u00f6r\u00e9spontn\u00e1l, l\u00e9pkedj\u00fck el a CreateMemento h\u00edv\u00e1s\u00e1ig, \u00e9s l\u00e9pj\u00fcnk is \u00e1t rajta. A CreateMemento \u00e1ltal visszaadott memento objektum bels\u0151 \u00e1llapot\u00e1t n\u00e9zz\u00fck meg vagy a Watch ablakban, vagy tooltipben r\u00e1\u00e1llva. Azt l\u00e1tjuk, hogy val\u00f3ban \u201etartalmazza\u201d a dokumentum pillanatnyi \u00e1llapot\u00e1t a shapes \u00e9s selectedShape tagv\u00e1ltoz\u00f3j\u00e1ban. A ClearCommand ezt el is t\u00e1rolja a tagv\u00e1ltoz\u00f3j\u00e1ban, amit az UnExecute m\u0171veletben haszn\u00e1l fel a dokumentum \u00e1llapot\u00e1nak vissza\u00e1ll\u00edt\u00e1s\u00e1ra.

                                P\u00e9ld\u00e1nkban a Memento minta arra \u00e9p\u00edt, hogy a dokumentum teljes \u00e1llapot\u00e1r\u00f3l m\u00e1solatot k\u00e9sz\u00edt\u00fcnk. Sok alkalmaz\u00e1s, illetve nagym\u00e9ret\u0171 dokumentum eset\u00e9ben ennek nagyon nagy lehet a mem\u00f3riaig\u00e9nye. Milyen megold\u00e1sokban gondolkozhatunk a probl\u00e9ma elker\u00fcl\u00e9s\u00e9re?

                                • A kisebb v\u00e1ltoz\u00e1sok hat\u00e1s\u00e1t ink\u00e1bb \u201einverz\u201d m\u0171velettel pr\u00f3b\u00e1ljuk visszacsin\u00e1lni. Ezt alkalmaztuk pl. a New Rect parancs eset\u00e9ben.
                                • A Memento-ba nem mentj\u00fck bele a teljes \u00e1llapotot, hanem csak m\u00f3dosult \u00e1llapotot. Sajnos ez nem mindig tehet\u0151 meg, valamint nehezebben karbantarthat\u00f3 megold\u00e1st eredm\u00e9nyez.
                                • Korl\u00e1tozzuk a visszavonhat\u00f3 l\u00e9p\u00e9sek sz\u00e1m\u00e1t.
                                "}]} \ No newline at end of file diff --git a/2024/sitemap.xml b/2024/sitemap.xml index 7749e5f..d2647b6 100644 --- a/2024/sitemap.xml +++ b/2024/sitemap.xml @@ -2,252 +2,252 @@ https://bmeviauab00.github.io/szoftvertechnikak/2024/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/egyeb/interfesz-es-absztrakt-os/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/egyeb/interfesz-es-absztrakt-os/index_eng/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/egyeb/interfesz-es-absztrakt-os/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/egyeb/uml-kod-kapcsolata/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/egyeb/uml-kod-kapcsolata/index_eng/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/egyeb/uml-kod-kapcsolata/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/VisualStudio/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/meghirdetes-elott/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/meghirdetes-elott_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/1-model-es-kod-kapcsolata/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/1-model-es-kod-kapcsolata/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/2-nyelvi-eszkozok/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/2-nyelvi-eszkozok/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/3-felhasznaloi-felulet-kialakitasa/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/3-felhasznaloi-felulet-kialakitasa/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/4-tobbszalu-alkalmazasok-fejlesztese/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/4-tobbszalu-alkalmazasok-fejlesztese/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/5-mvvm/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/5-mvvm/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/5b-mvvm-advanced/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/5b-mvvm-advanced/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/6-tervezesi-mintak/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/beadas-ellenorzes/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/beadas-ellenorzes/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/eloellenorzes-ertekeles/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/fejlesztokornyezet/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/fejlesztokornyezet/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/git-github-github-classroom/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/hf-folyamat/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/imsc-liftsystem/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/hazi/old-5-document-view-architectura/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/1-model-es-kod-kapcsolata/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/1-model-es-kod-kapcsolata/index_eng/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/1-model-es-kod-kapcsolata/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/2-nyelvi-eszkozok/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/2-nyelvi-eszkozok/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/3-felhasznaloi-felulet/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/3-felhasznaloi-felulet/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/4-tobbszalu/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/4-tobbszalu/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/5-mvvm/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/5-mvvm/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/6-tervezesi-mintak/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/7-adatkezeles/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/7-adatkezeles/index_ger/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/old-6-doc-view/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/labor/old-7-tervezesi-mintak/ - 2024-05-10 + 2024-05-11 daily https://bmeviauab00.github.io/szoftvertechnikak/2024/laborvezetoknek/ - 2024-05-10 + 2024-05-11 daily \ No newline at end of file diff --git a/2024/sitemap.xml.gz b/2024/sitemap.xml.gz index 75f891f..3ebc799 100644 Binary files a/2024/sitemap.xml.gz and b/2024/sitemap.xml.gz differ