[{"data":1,"prerenderedAt":16746},["ShallowReactive",2],{"navigation_docs":3,"-csharp-desktop-ui-viewmodel-implementation":2949,"-csharp-desktop-ui-viewmodel-implementation-surround":16741},[4,1640,1765,2219,2352,2559,2641,2691,2748,2782,2908,2945],{"title":5,"icon":6,"path":7,"stem":8,"children":9},"C#","i-devicon-csharp","/csharp","01.csharp",[10,13,60,90,120,202,219,253,379,404,457,650,1346,1636],{"title":11,"path":7,"stem":12},"C# Roadmap","01.csharp/index",{"title":14,"icon":15,"path":16,"stem":17,"children":18,"page":59},"Fundamentals","i-lucide-book-open","/csharp/fundamentals","01.csharp/01.fundamentals",[19,23,27,31,35,39,43,47,51,55],{"title":20,"path":21,"stem":22},"Вступ до екосистеми .NET","/csharp/fundamentals/introduction-to-ecosystem","01.csharp/01.fundamentals/01.introduction-to-ecosystem",{"title":24,"path":25,"stem":26},"Структура програми на C#","/csharp/fundamentals/program-structure","01.csharp/01.fundamentals/02.program-structure",{"title":28,"path":29,"stem":30},"Змінні та Типи Даних","/csharp/fundamentals/variables-data-types","01.csharp/01.fundamentals/03.variables-data-types",{"title":32,"path":33,"stem":34},"Масиви","/csharp/fundamentals/arrays","01.csharp/01.fundamentals/04.arrays",{"title":36,"path":37,"stem":38},"Strings & Text Handling","/csharp/fundamentals/strings-text-handling","01.csharp/01.fundamentals/05.strings-text-handling",{"title":40,"path":41,"stem":42},"Дати і Час","/csharp/fundamentals/dates-time-handling","01.csharp/01.fundamentals/06.dates-time-handling",{"title":44,"path":45,"stem":46},"Потік Керування","/csharp/fundamentals/control-flow","01.csharp/01.fundamentals/07.control-flow",{"title":48,"path":49,"stem":50},"Методи","/csharp/fundamentals/methods","01.csharp/01.fundamentals/08.methods",{"title":52,"path":53,"stem":54},"Основи Відлагодження","/csharp/fundamentals/debugging-basics","01.csharp/01.fundamentals/09.debugging-basics",{"title":56,"path":57,"stem":58},"Інтерактивна Консоль (Classic)","/csharp/fundamentals/interactive-console","01.csharp/01.fundamentals/10.interactive-console",false,{"title":61,"icon":62,"path":63,"stem":64,"children":65,"page":59},"OOP","i-lucide-box","/csharp/oop","01.csharp/02.oop",[66,70,74,78,82,86],{"title":67,"path":68,"stem":69},"Package Management (Управління Пакетами)","/csharp/oop/package-management","01.csharp/02.oop/01.package-management",{"title":71,"path":72,"stem":73},"Класи та Об'єкти","/csharp/oop/classes-objects","01.csharp/02.oop/02.classes-objects",{"title":75,"path":76,"stem":77},"Властивості та Поля","/csharp/oop/properties-fields","01.csharp/02.oop/03.properties-fields",{"title":79,"path":80,"stem":81},"Стовпи ООП","/csharp/oop/oop-pillars","01.csharp/02.oop/04.oop-pillars",{"title":83,"path":84,"stem":85},"Advanced Types","/csharp/oop/advanced-types","01.csharp/02.oop/05.advanced-types",{"title":87,"path":88,"stem":89},"Namespaces (Простори Імен)","/csharp/oop/namespaces","01.csharp/02.oop/06.namespaces",{"title":91,"icon":92,"path":93,"stem":94,"children":95,"page":59},"Advanced Core","i-lucide-zap","/csharp/advanced-core","01.csharp/03.advanced-core",[96,100,104,108,112,116],{"title":97,"path":98,"stem":99},"Generics (Узагальнення)","/csharp/advanced-core/generics","01.csharp/03.advanced-core/01.generics",{"title":101,"path":102,"stem":103},"Делегати, Події та Лямбда-вирази","/csharp/advanced-core/delegates-events-lambdas","01.csharp/03.advanced-core/02.delegates-events-lambdas",{"title":105,"path":106,"stem":107},"Interfaces Deep Dive (Інтерфейси: Поглиблений Розгляд)","/csharp/advanced-core/interfaces-deep-dive","01.csharp/03.advanced-core/03.interfaces-deep-dive",{"title":109,"path":110,"stem":111},"Обробка Винятків","/csharp/advanced-core/exception-handling","01.csharp/03.advanced-core/04.exception-handling",{"title":113,"path":114,"stem":115},"Pattern Matching","/csharp/advanced-core/pattern-matching","01.csharp/03.advanced-core/05.pattern-matching",{"title":117,"path":118,"stem":119},"Додаткові Можливості C#","/csharp/advanced-core/additional-features","01.csharp/03.advanced-core/06.additional-features",{"title":121,"icon":122,"path":123,"stem":124,"children":125,"page":59},"Architecture Best Practices","i-lucide-building-2","/csharp/architecture-best-practices","01.csharp/04.architecture-best-practices",[126,130,149,153,157,161,165,169],{"title":127,"path":128,"stem":129},"Software Design Principles (Частина 1)","/csharp/architecture-best-practices/software-design-principles","01.csharp/04.architecture-best-practices/01.software-design-principles",{"title":131,"icon":132,"path":133,"stem":134,"children":135,"page":59},"Design Patterns","i-lucide-folder","/csharp/architecture-best-practices/design-patterns","01.csharp/04.architecture-best-practices/02.design-patterns",[136],{"title":137,"icon":132,"path":138,"stem":139,"children":140,"page":59},"Creational","/csharp/architecture-best-practices/design-patterns/creational","01.csharp/04.architecture-best-practices/02.design-patterns/creational",[141,145],{"title":142,"path":143,"stem":144},"Singleton (Одинак)","/csharp/architecture-best-practices/design-patterns/creational/singleton","01.csharp/04.architecture-best-practices/02.design-patterns/creational/01.singleton",{"title":146,"path":147,"stem":148},"Builder (Будівельник)","/csharp/architecture-best-practices/design-patterns/creational/builder","01.csharp/04.architecture-best-practices/02.design-patterns/creational/02.builder",{"title":150,"path":151,"stem":152},"Building Professional CLIs","/csharp/architecture-best-practices/building-professional-clis","01.csharp/04.architecture-best-practices/03.building-professional-clis",{"title":154,"path":155,"stem":156},"Validation & Flow Control","/csharp/architecture-best-practices/validation-flow-control","01.csharp/04.architecture-best-practices/04.validation-flow-control",{"title":158,"path":159,"stem":160},"The Modern .NET Host (Microsoft.Extensions)","/csharp/architecture-best-practices/modern-dotnet-host","01.csharp/04.architecture-best-practices/05.modern-dotnet-host",{"title":162,"path":163,"stem":164},"Data Mapper: Repository та DAO патерни (Частина 1)","/csharp/architecture-best-practices/data-mapper-part1","01.csharp/04.architecture-best-practices/06.data-mapper-part1",{"title":166,"path":167,"stem":168},"Data Mapper: Repository та DAO патерни (Частина 2)","/csharp/architecture-best-practices/data-mapper-part2","01.csharp/04.architecture-best-practices/07.data-mapper-part2",{"title":170,"icon":132,"path":171,"stem":172,"children":173,"page":59},"Di Ioc","/csharp/architecture-best-practices/di-ioc","01.csharp/04.architecture-best-practices/08.di-ioc",[174,178,182,186,190,194,198],{"title":175,"path":176,"stem":177},"Проблема залежностей та Інверсія Контролю","/csharp/architecture-best-practices/di-ioc/the-dependency-problem","01.csharp/04.architecture-best-practices/08.di-ioc/01.the-dependency-problem",{"title":179,"path":180,"stem":181},"Будуємо власний Service Container","/csharp/architecture-best-practices/di-ioc/build-your-own-container","01.csharp/04.architecture-best-practices/08.di-ioc/02.build-your-own-container",{"title":183,"path":184,"stem":185},"Service Locator: Паттерн та Анти-паттерн","/csharp/architecture-best-practices/di-ioc/service-locator-pattern","01.csharp/04.architecture-best-practices/08.di-ioc/03.service-locator-pattern",{"title":187,"path":188,"stem":189},"Паттерни Dependency Injection","/csharp/architecture-best-practices/di-ioc/dependency-injection-patterns","01.csharp/04.architecture-best-practices/08.di-ioc/04.dependency-injection-patterns",{"title":191,"path":192,"stem":193},"Microsoft DI: IServiceCollection та IServiceProvider","/csharp/architecture-best-practices/di-ioc/microsoft-di-deep-dive","01.csharp/04.architecture-best-practices/08.di-ioc/05.microsoft-di-deep-dive",{"title":195,"path":196,"stem":197},"Service Lifetimes та Scopes","/csharp/architecture-best-practices/di-ioc/service-lifetimes-and-scopes","01.csharp/04.architecture-best-practices/08.di-ioc/06.service-lifetimes-and-scopes",{"title":199,"path":200,"stem":201},"DI Анти-паттерни та Найкращі Практики","/csharp/architecture-best-practices/di-ioc/di-anti-patterns-and-best-practices","01.csharp/04.architecture-best-practices/08.di-ioc/07.di-anti-patterns-and-best-practices",{"title":203,"icon":132,"path":204,"stem":205,"children":206,"page":59},"Standard Library","/csharp/standard-library","01.csharp/05.standard-library",[207,211,215],{"title":208,"path":209,"stem":210},"Collections (Колекції)","/csharp/standard-library/collections","01.csharp/05.standard-library/01.collections",{"title":212,"path":213,"stem":214},"High Performance Types (Високопродуктивні Типи)","/csharp/standard-library/high-performance-types","01.csharp/05.standard-library/02.high-performance-types",{"title":216,"path":217,"stem":218},"LINQ (Language Integrated Query)","/csharp/standard-library/linq","01.csharp/05.standard-library/03.linq",{"title":220,"icon":221,"path":222,"stem":223,"children":224,"page":59},"System Internals Concurrency","i-lucide-server","/csharp/system-internals-concurrency","01.csharp/06.system-internals-concurrency",[225,229,233,237,241,245,249],{"title":226,"path":227,"stem":228},"Memory Management","/csharp/system-internals-concurrency/memory-management","01.csharp/06.system-internals-concurrency/01.memory-management",{"title":230,"path":231,"stem":232},"Reflection API: System.Type та Метадані","/csharp/system-internals-concurrency/reflection-fundamentals","01.csharp/06.system-internals-concurrency/02.reflection-fundamentals",{"title":234,"path":235,"stem":236},"Attributes та Dynamic Language Runtime","/csharp/system-internals-concurrency/attributes-dynamic","01.csharp/06.system-internals-concurrency/03.attributes-dynamic",{"title":238,"path":239,"stem":240},"Expression Trees: Швидка Альтернатива Рефлексії","/csharp/system-internals-concurrency/expression-trees-compiled","01.csharp/06.system-internals-concurrency/04.expression-trees-compiled",{"title":242,"path":243,"stem":244},"Source Generators: Compile-Time Code Generation","/csharp/system-internals-concurrency/source-generators","01.csharp/06.system-internals-concurrency/05.source-generators",{"title":246,"path":247,"stem":248},"Multithreading Fundamentals","/csharp/system-internals-concurrency/multithreading-fundamentals","01.csharp/06.system-internals-concurrency/06.multithreading-fundamentals",{"title":250,"path":251,"stem":252},"Synchronization Primitives","/csharp/system-internals-concurrency/synchronization-primitives","01.csharp/06.system-internals-concurrency/07.synchronization-primitives",{"title":254,"icon":255,"path":256,"stem":257,"children":258,"page":59},"System Programming Windows","i-lucide-cpu","/csharp/system-programming-windows","01.csharp/07.system-programming-windows",[259,263,267,271,275,279,283,287,291,295,299,303,307,311,315,319,323,327,331,335,339,343,347,351,355,359,363,367,371,375],{"title":260,"path":261,"stem":262},"Як Працює Операційна Система","/csharp/system-programming-windows/how-os-works","01.csharp/07.system-programming-windows/01.how-os-works",{"title":264,"path":265,"stem":266},"Процеси в .NET — API та Запуск","/csharp/system-programming-windows/processes-in-dotnet","01.csharp/07.system-programming-windows/02.processes-in-dotnet",{"title":268,"path":269,"stem":270},"Процеси в .NET — IPC та Міжпроцесна Комунікація","/csharp/system-programming-windows/02a.processes-ipc","01.csharp/07.system-programming-windows/02a.processes-ipc",{"title":272,"path":273,"stem":274},"Application Domains та Збірки — AppDomain і AssemblyLoadContext","/csharp/system-programming-windows/appdomains-assemblies","01.csharp/07.system-programming-windows/03.appdomains-assemblies",{"title":276,"path":277,"stem":278},"Application Domains та Збірки — Plug-in Система з Hot-Reload","/csharp/system-programming-windows/03a.appdomains-plugin-system","01.csharp/07.system-programming-windows/03a.appdomains-plugin-system",{"title":280,"path":281,"stem":282},"Потоки — Основи та API Thread","/csharp/system-programming-windows/thread-fundamentals","01.csharp/07.system-programming-windows/04.thread-fundamentals",{"title":284,"path":285,"stem":286},"Потоки — Lifecycle, Пріоритети та Безпечне Завершення","/csharp/system-programming-windows/04a.thread-lifecycle-priorities","01.csharp/07.system-programming-windows/04a.thread-lifecycle-priorities",{"title":288,"path":289,"stem":290},"Проблеми Спільного Стану — Race Condition та Data Race","/csharp/system-programming-windows/shared-state-problems","01.csharp/07.system-programming-windows/05.shared-state-problems",{"title":292,"path":293,"stem":294},"Проблеми Спільного Стану — Memory Model та volatile","/csharp/system-programming-windows/05a.shared-state-memory-model","01.csharp/07.system-programming-windows/05a.shared-state-memory-model",{"title":296,"path":297,"stem":298},"Синхронізація — Monitor, lock та еволюція примітивів","/csharp/system-programming-windows/synchronization-fundamentals","01.csharp/07.system-programming-windows/06.synchronization-fundamentals",{"title":300,"path":301,"stem":302},"Синхронізація — Наскрізний Приклад та Deadlock Detection","/csharp/system-programming-windows/06a.synchronization-walkthrough","01.csharp/07.system-programming-windows/06a.synchronization-walkthrough",{"title":304,"path":305,"stem":306},"Синхронізація — Mutex, Semaphore та Event-Based Primitives","/csharp/system-programming-windows/synchronization-advanced","01.csharp/07.system-programming-windows/07.synchronization-advanced",{"title":308,"path":309,"stem":310},"Синхронізація — Interlocked, Volatile та Lock-Free Структури","/csharp/system-programming-windows/07a.synchronization-advanced-walkthrough","01.csharp/07.system-programming-windows/07a.synchronization-advanced-walkthrough",{"title":312,"path":313,"stem":314},"Interlocked, CAS та Lock-Free Структури","/csharp/system-programming-windows/interlocked-cas-lockfree","01.csharp/07.system-programming-windows/08.interlocked-cas-lockfree",{"title":316,"path":317,"stem":318},"Volatile, Memory Model та Spinning","/csharp/system-programming-windows/08a.volatile-memory-model","01.csharp/07.system-programming-windows/08a.volatile-memory-model",{"title":320,"path":321,"stem":322},"ThreadPool — Пул Потоків для Ефективного Виконання","/csharp/system-programming-windows/thread-pool","01.csharp/07.system-programming-windows/09.thread-pool",{"title":324,"path":325,"stem":326},"ThreadPool — Просунуті Сценарії та Внутрішня Будова","/csharp/system-programming-windows/09a.thread-pool-advanced","01.csharp/07.system-programming-windows/09a.thread-pool-advanced",{"title":328,"path":329,"stem":330},"Concurrent та Immutable Collections","/csharp/system-programming-windows/concurrent-collections","01.csharp/07.system-programming-windows/10.concurrent-collections",{"title":332,"path":333,"stem":334},"TPL, Task та Композиція — Від Thread до Task","/csharp/system-programming-windows/tpl-parallel-plinq","01.csharp/07.system-programming-windows/11.tpl-parallel-plinq",{"title":336,"path":337,"stem":338},"Parallel Class та PLINQ — Data Parallelism","/csharp/system-programming-windows/11a.tpl-parallel-plinq-advanced","01.csharp/07.system-programming-windows/11a.tpl-parallel-plinq-advanced",{"title":340,"path":341,"stem":342},"Async/Await — Фундамент Асинхронного Програмування","/csharp/system-programming-windows/async-fundamentals","01.csharp/07.system-programming-windows/12.async-fundamentals",{"title":344,"path":345,"stem":346},"SynchronizationContext та ConfigureAwait — Контекст Виконання","/csharp/system-programming-windows/async-context-configureawait","01.csharp/07.system-programming-windows/13.async-context-configureawait",{"title":348,"path":349,"stem":350},"Async — Просунуті Паттерни","/csharp/system-programming-windows/async-advanced","01.csharp/07.system-programming-windows/14.async-advanced",{"title":352,"path":353,"stem":354},"System.Threading.Channels — Async Producer-Consumer","/csharp/system-programming-windows/channels","01.csharp/07.system-programming-windows/15.channels",{"title":356,"path":357,"stem":358},"Асинхронна Синхронізація","/csharp/system-programming-windows/async-synchronization","01.csharp/07.system-programming-windows/16.async-synchronization",{"title":360,"path":361,"stem":362},"Unsafe Code та Вказівники","/csharp/system-programming-windows/unsafe-code","01.csharp/07.system-programming-windows/17.unsafe-code",{"title":364,"path":365,"stem":366},"P/Invoke та Windows API — Міст між .NET та Native Code","/csharp/system-programming-windows/pinvoke-winapi","01.csharp/07.system-programming-windows/18.pinvoke-winapi",{"title":368,"path":369,"stem":370},"Реєстр Windows — Центральна База Конфігурації Системи","/csharp/system-programming-windows/windows-registry","01.csharp/07.system-programming-windows/19.windows-registry",{"title":372,"path":373,"stem":374},"Windows Hooks, Hotkeys та Services — Глибока Інтеграція з ОС","/csharp/system-programming-windows/windows-hooks-services","01.csharp/07.system-programming-windows/20.windows-hooks-services",{"title":376,"path":377,"stem":378},"Системне Програмування C# (Windows) — 07.system-programming-windows","/csharp/system-programming-windows/implementation_plan","01.csharp/07.system-programming-windows/implementation_plan",{"title":380,"icon":132,"path":381,"stem":382,"children":383,"page":59},"Io","/csharp/io","01.csharp/08.io",[384,388,392,396,400],{"title":385,"path":386,"stem":387},"8.1.1. Основи роботи з файловою системою","/csharp/io/file-system-basics","01.csharp/08.io/01.file-system-basics",{"title":389,"path":390,"stem":391},"8.1.2. Потоки (Streams) та Серіалізація Даних","/csharp/io/streams-serialization","01.csharp/08.io/02.streams-serialization",{"title":393,"path":394,"stem":395},"8.2.1. JSON Serialization з System.Text.Json","/csharp/io/json-serialization","01.csharp/08.io/03.json-serialization",{"title":397,"path":398,"stem":399},"8.2.2. XML Serialization та LINQ to XML","/csharp/io/xml-serialization","01.csharp/08.io/04.xml-serialization",{"title":401,"path":402,"stem":403},"8.2.3. Binary Serialization: MessagePack та Protocol Buffers","/csharp/io/binary-serialization","01.csharp/08.io/05.binary-serialization",{"title":405,"icon":132,"path":406,"stem":407,"children":408,"page":59},"Ado Net","/csharp/ado-net","01.csharp/09.ado-net",[409,413,417,421,425,429,433,437,441,445,449,453],{"title":410,"path":411,"stem":412},"9.1. Введення в ADO.NET","/csharp/ado-net/introduction-to-adonet","01.csharp/09.ado-net/01.introduction-to-adonet",{"title":414,"path":415,"stem":416},"9.2. Клас DbConnection — з'єднання з базою даних","/csharp/ado-net/connection","01.csharp/09.ado-net/02.connection",{"title":418,"path":419,"stem":420},"9.3. Клас DbCommand — виконання SQL-запитів","/csharp/ado-net/command-and-queries","01.csharp/09.ado-net/03.command-and-queries",{"title":422,"path":423,"stem":424},"9.4. Клас DbDataReader — ефективне читання даних","/csharp/ado-net/datareader","01.csharp/09.ado-net/04.datareader",{"title":426,"path":427,"stem":428},"9.5. Параметризовані запити та захист від SQL Injection","/csharp/ado-net/parameters-and-sql-injection","01.csharp/09.ado-net/05.parameters-and-sql-injection",{"title":430,"path":431,"stem":432},"9.6. Транзакції в ADO.NET","/csharp/ado-net/transactions","01.csharp/09.ado-net/06.transactions",{"title":434,"path":435,"stem":436},"9.7. DbProviderFactory — провайдер-незалежний код","/csharp/ado-net/provider-factory","01.csharp/09.ado-net/07.provider-factory",{"title":438,"path":439,"stem":440},"9.8. Асинхронний доступ до даних","/csharp/ado-net/async-data-access","01.csharp/09.ado-net/08.async-data-access",{"title":442,"path":443,"stem":444},"9.9. Від'єднаний режим: DataSet, DataTable, DataRow","/csharp/ado-net/disconnected-mode-dataset","01.csharp/09.ado-net/09.disconnected-mode-dataset",{"title":446,"path":447,"stem":448},"9.10. DataAdapter — міст між DataSet та базою даних","/csharp/ado-net/data-adapter","01.csharp/09.ado-net/10.data-adapter",{"title":450,"path":451,"stem":452},"9.11. Data Mapper та Repository: Архітектура доступу до даних","/csharp/ado-net/data-mapper-repository","01.csharp/09.ado-net/11.data-mapper-repository",{"title":454,"path":455,"stem":456},"9.12. Identity Map, Unit of Work та Specification Pattern","/csharp/ado-net/advanced-patterns","01.csharp/09.ado-net/12.advanced-patterns",{"title":458,"icon":255,"path":459,"stem":460,"children":461,"page":59},"Ef Core","/csharp/ef-core","01.csharp/10.ef-core",[462,466,470,474,478,482,486,490,494,498,502,506,510,514,518,522,526,532,538,542,546,550,554,558,562,566,570,574,578,582,586,590,594,598,602,606,610,614,618,622,626,630,634,638,642,646],{"title":463,"path":464,"stem":465},"Що таке ORM? Від SQL до об'єктів","/csharp/ef-core/what-is-orm","01.csharp/10.ef-core/01.what-is-orm",{"title":467,"path":468,"stem":469},"Перший проєкт — від нуля до CRUD","/csharp/ef-core/first-project","01.csharp/10.ef-core/02.first-project",{"title":471,"path":472,"stem":473},"DbContext — Серце EF Core","/csharp/ef-core/dbcontext-deep-dive","01.csharp/10.ef-core/03.dbcontext-deep-dive",{"title":475,"path":476,"stem":477},"Провайдери баз даних — Архітектура та Вибір СУБД","/csharp/ef-core/database-providers","01.csharp/10.ef-core/04.database-providers",{"title":479,"path":480,"stem":481},"Конвенції EF Core — Магія без конфігурації","/csharp/ef-core/conventions","01.csharp/10.ef-core/05.conventions",{"title":483,"path":484,"stem":485},"Fluent API та Data Annotations — Явна конфігурація моделі","/csharp/ef-core/fluent-api-vs-annotations","01.csharp/10.ef-core/06.fluent-api-vs-annotations",{"title":487,"path":488,"stem":489},"Зв'язки — One-to-One та One-to-Many","/csharp/ef-core/relationships-basics","01.csharp/10.ef-core/07.relationships-basics",{"title":491,"path":492,"stem":493},"Зв'язки Advanced — Many-to-Many та Складні Сценарії","/csharp/ef-core/relationships-advanced","01.csharp/10.ef-core/08.relationships-advanced",{"title":495,"path":496,"stem":497},"Властивості — Типи, Конвертери, Компаратори (Частина 1)","/csharp/ef-core/property-configuration-part1","01.csharp/10.ef-core/09.property-configuration-part1",{"title":499,"path":500,"stem":501},"Властивості — Value Comparers, Generators, Shadow Properties (Частина 2)","/csharp/ef-core/property-configuration-part2","01.csharp/10.ef-core/09.property-configuration-part2",{"title":503,"path":504,"stem":505},"Складні типи — Owned Types та Complex Types (Частина 1)","/csharp/ef-core/complex-types-owned-part1","01.csharp/10.ef-core/10.complex-types-owned-part1",{"title":507,"path":508,"stem":509},"Складні типи — Complex Types, Keyless Entities, Порівняння (Частина 2)","/csharp/ef-core/complex-types-owned-part2","01.csharp/10.ef-core/10.complex-types-owned-part2",{"title":511,"path":512,"stem":513},"JSON Columns — Складні дані у JSON (Частина 1)","/csharp/ef-core/json-columns-part1","01.csharp/10.ef-core/11.json-columns-part1",{"title":515,"path":516,"stem":517},"JSON Columns — Value Comparers, Індекси, Провайдери (Частина 2)","/csharp/ef-core/json-columns-part2","01.csharp/10.ef-core/11.json-columns-part2",{"title":519,"path":520,"stem":521},"Успадкування — Абстрактні класи та TPH (Частина 1)","/csharp/ef-core/inheritance-part1","01.csharp/10.ef-core/12.inheritance-part1",{"title":523,"path":524,"stem":525},"Успадкування — TPT, TPC та Порівняння Стратегій (Частина 2)","/csharp/ef-core/inheritance-part2","01.csharp/10.ef-core/12.inheritance-part2",{"title":527,"path":528,"stem":529,"children":530},"Індекси, Обмеження та Схема (Частина 1)","/csharp/ef-core/indexes-constraints-part1","01.csharp/10.ef-core/13.indexes-constraints-part1",[531],{"title":527,"path":528,"stem":529},{"title":533,"path":534,"stem":535,"children":536},"Індекси, Обмеження та Схема (Частина 2)","/csharp/ef-core/indexes-constraints-part2","01.csharp/10.ef-core/13.indexes-constraints-part2",[537],{"title":533,"path":534,"stem":535},{"title":539,"path":540,"stem":541},"Seed Data — Початкові Дані (Частина 1)","/csharp/ef-core/seeding-part1","01.csharp/10.ef-core/14.seeding-part1",{"title":543,"path":544,"stem":545},"Seed Data — SQL-скрипти, Bogus та Стратегії (Частина 2)","/csharp/ef-core/seeding-part2","01.csharp/10.ef-core/14.seeding-part2",{"title":547,"path":548,"stem":549},"Global Query Filters — Глобальні Фільтри (Частина 1)","/csharp/ef-core/global-query-filters-part1","01.csharp/10.ef-core/15.global-query-filters-part1",{"title":551,"path":552,"stem":553},"Global Query Filters — Підводні камені та Інтеграція (Частина 2)","/csharp/ef-core/global-query-filters-part2","01.csharp/10.ef-core/15.global-query-filters-part2",{"title":555,"path":556,"stem":557},"LINQ-запити в EF Core (Частина 1)","/csharp/ef-core/linq-queries-part1","01.csharp/10.ef-core/16.linq-queries-part1",{"title":559,"path":560,"stem":561},"LINQ-запити в EF Core (Частина 2)","/csharp/ef-core/linq-queries-part2","01.csharp/10.ef-core/16.linq-queries-part2",{"title":563,"path":564,"stem":565},"Завантаження Пов'язаних Даних (Частина 1)","/csharp/ef-core/loading-related-data-part1","01.csharp/10.ef-core/17.loading-related-data-part1",{"title":567,"path":568,"stem":569},"Завантаження Пов'язаних Даних (Частина 2)","/csharp/ef-core/loading-related-data-part2","01.csharp/10.ef-core/17.loading-related-data-part2",{"title":571,"path":572,"stem":573},"Raw SQL, Views та Stored Procedures (Частина 1)","/csharp/ef-core/raw-sql-part1","01.csharp/10.ef-core/18.raw-sql-part1",{"title":575,"path":576,"stem":577},"Raw SQL — Stored Procedures, DbFunction та Bulk Operations (Частина 2)","/csharp/ef-core/raw-sql-part2","01.csharp/10.ef-core/18.raw-sql-part2",{"title":579,"path":580,"stem":581},"Продвинуті Запити — Compiled Queries, Bulk та Оптимізація (Частина 1)","/csharp/ef-core/advanced-queries-part1","01.csharp/10.ef-core/19.advanced-queries-part1",{"title":583,"path":584,"stem":585},"Продвинуті Запити — Query Tags, Bulk та Interceptors (Частина 2)","/csharp/ef-core/advanced-queries-part2","01.csharp/10.ef-core/19.advanced-queries-part2",{"title":587,"path":588,"stem":589},"Change Tracker — Відстеження Змін (Частина 1)","/csharp/ef-core/change-tracking-part1","01.csharp/10.ef-core/20.change-tracking-part1",{"title":591,"path":592,"stem":593},"Change Tracker — Графи Об'єктів та Disconnected (Частина 2)","/csharp/ef-core/change-tracking-part2","01.csharp/10.ef-core/20.change-tracking-part2",{"title":595,"path":596,"stem":597},"Збереження Даних та Транзакції (Частина 1)","/csharp/ef-core/saving-data-part1","01.csharp/10.ef-core/21.saving-data-part1",{"title":599,"path":600,"stem":601},"Збереження Даних — Concurrency та Outbox (Частина 2)","/csharp/ef-core/saving-data-part2","01.csharp/10.ef-core/21.saving-data-part2",{"title":603,"path":604,"stem":605},"Конкурентність та Блокування (Частина 1)","/csharp/ef-core/concurrency-part1","01.csharp/10.ef-core/22.concurrency-part1",{"title":607,"path":608,"stem":609},"Конкурентність — Дедлоки та Queue Processing (Частина 2)","/csharp/ef-core/concurrency-part2","01.csharp/10.ef-core/22.concurrency-part2",{"title":611,"path":612,"stem":613},"Міграції в EF Core — Основи (Частина 1)","/csharp/ef-core/migrations-basics-part1","01.csharp/10.ef-core/23.migrations-basics-part1",{"title":615,"path":616,"stem":617},"Міграції в EF Core — Основи (Частина 2)","/csharp/ef-core/migrations-basics-part2","01.csharp/10.ef-core/23.migrations-basics-part2",{"title":619,"path":620,"stem":621},"Міграції — Просунуті Сценарії (Частина 1)","/csharp/ef-core/migrations-advanced-part1","01.csharp/10.ef-core/24.migrations-advanced-part1",{"title":623,"path":624,"stem":625},"Міграції — Просунуті Сценарії (Частина 2)","/csharp/ef-core/migrations-advanced-part2","01.csharp/10.ef-core/24.migrations-advanced-part2",{"title":627,"path":628,"stem":629},"Управління Схемою та Database-First (Частина 1)","/csharp/ef-core/schema-management-part1","01.csharp/10.ef-core/25.schema-management-part1",{"title":631,"path":632,"stem":633},"Управління Схемою та Database-First (Частина 2)","/csharp/ef-core/schema-management-part2","01.csharp/10.ef-core/25.schema-management-part2",{"title":635,"path":636,"stem":637},"Продуктивність EF Core — Основи (Частина 1)","/csharp/ef-core/performance-fundamentals-part1","01.csharp/10.ef-core/26.performance-fundamentals-part1",{"title":639,"path":640,"stem":641},"Interceptors в EF Core (Частина 1)","/csharp/ef-core/interceptors-part1","01.csharp/10.ef-core/29.interceptors-part1",{"title":643,"path":644,"stem":645},"Interceptors в EF Core — Connection, Transaction та Materialization (Частина 2)","/csharp/ef-core/interceptors-part2","01.csharp/10.ef-core/29.interceptors-part2",{"title":647,"path":648,"stem":649},"План вивчення Entity Framework Core — Повний курс","/csharp/ef-core/implementation_plan","01.csharp/10.ef-core/implementation_plan",{"title":651,"icon":652,"path":653,"stem":654,"children":655,"page":59},"ASP.NET","i-devicon-dotnetcore","/csharp/aspnet","01.csharp/11.aspnet",[656,730,791,869,927,941,967,1057,1111,1182,1212,1289],{"title":657,"icon":658,"path":659,"stem":660,"children":661,"page":59},"Minimal API","i-lucide-network","/csharp/aspnet/minimal-api","01.csharp/11.aspnet/01.minimal-api",[662,666,670,674,678,682,686,690,694,698,702,706,710,714,718,722,726],{"title":663,"path":664,"stem":665},"Вступ до ASP.NET та еволюція фреймворку","/csharp/aspnet/minimal-api/introduction","01.csharp/11.aspnet/01.minimal-api/01.introduction",{"title":667,"path":668,"stem":669},"Перший додаток на ASP.NET Core","/csharp/aspnet/minimal-api/first-application","01.csharp/11.aspnet/01.minimal-api/02.first-application",{"title":671,"path":672,"stem":673},"WebApplication, Builder та Dependency Injection","/csharp/aspnet/minimal-api/webapplication-builder","01.csharp/11.aspnet/01.minimal-api/03.webapplication-builder",{"title":675,"path":676,"stem":677},"Конвеєр запитів та Middleware","/csharp/aspnet/minimal-api/request-pipeline-middleware","01.csharp/11.aspnet/01.minimal-api/04.request-pipeline-middleware",{"title":679,"path":680,"stem":681},"Маршрутизація в ASP.NET Core: Основи","/csharp/aspnet/minimal-api/routing-basics","01.csharp/11.aspnet/01.minimal-api/05.routing-basics",{"title":683,"path":684,"stem":685},"Маршрутизація в ASP.NET Core: Розширені можливості","/csharp/aspnet/minimal-api/routing-advanced","01.csharp/11.aspnet/01.minimal-api/06.routing-advanced",{"title":687,"path":688,"stem":689},"Статичні файли в ASP.NET Core","/csharp/aspnet/minimal-api/static-files","01.csharp/11.aspnet/01.minimal-api/07.static-files",{"title":691,"path":692,"stem":693},"Статичні Активи: MapStaticAssets (ASP.NET Core 9.0)","/csharp/aspnet/minimal-api/static-assets","01.csharp/11.aspnet/01.minimal-api/08.static-assets",{"title":695,"path":696,"stem":697},"Конфігурація в ASP.NET Core: Основи","/csharp/aspnet/minimal-api/configuration-fundamentals","01.csharp/11.aspnet/01.minimal-api/09.configuration-fundamentals",{"title":699,"path":700,"stem":701},"Конфігурація: Паттерн Options","/csharp/aspnet/minimal-api/configuration-options","01.csharp/11.aspnet/01.minimal-api/10.configuration-options",{"title":703,"path":704,"stem":705},"Логування в ASP.NET Core: Основи","/csharp/aspnet/minimal-api/logging-basics","01.csharp/11.aspnet/01.minimal-api/11.logging-basics",{"title":707,"path":708,"stem":709},"Логування: Serilog та Middleware","/csharp/aspnet/minimal-api/logging-advanced","01.csharp/11.aspnet/01.minimal-api/12.logging-advanced",{"title":711,"path":712,"stem":713},"Управління станом: HttpContext.Items та Cookies","/csharp/aspnet/minimal-api/state-management","01.csharp/11.aspnet/01.minimal-api/13.state-management",{"title":715,"path":716,"stem":717},"Стан сесії: Sessions","/csharp/aspnet/minimal-api/session-state","01.csharp/11.aspnet/01.minimal-api/14.session-state",{"title":719,"path":720,"stem":721},"Структура проєкту: від хаосу до архітектури","/csharp/aspnet/minimal-api/project-structure","01.csharp/11.aspnet/01.minimal-api/15.project-structure",{"title":723,"path":724,"stem":725},"Scalar у Minimal API: повний проєкт і Fluent OpenAPI","/csharp/aspnet/minimal-api/scalar-openapi-fluent","01.csharp/11.aspnet/01.minimal-api/16.scalar-openapi-fluent",{"title":727,"path":728,"stem":729},"Swagger / Swashbuckle у Minimal API: окремий класичний шлях","/csharp/aspnet/minimal-api/swagger-swashbuckle","01.csharp/11.aspnet/01.minimal-api/17.swagger-swashbuckle",{"title":731,"icon":658,"path":732,"stem":733,"children":734,"page":59},"API","/csharp/aspnet/api","01.csharp/11.aspnet/02.api",[735,739,743,747,751,755,759,763,767,771,775,779,783,787],{"title":736,"path":737,"stem":738},"Що таке API. Клієнт-серверна архітектура","/csharp/aspnet/api/what-is-api","01.csharp/11.aspnet/02.api/01.what-is-api",{"title":740,"path":741,"stem":742},"Формати даних: JSON, XML, TOML та бінарні формати","/csharp/aspnet/api/data-formats","01.csharp/11.aspnet/02.api/02.data-formats",{"title":744,"path":745,"stem":746},"Парадигми API та концепція REST","/csharp/aspnet/api/api-paradigms-rest","01.csharp/11.aspnet/02.api/03.api-paradigms-rest",{"title":748,"path":749,"stem":750},"HTTP-методи, статус-коди та заголовки","/csharp/aspnet/api/http-methods-status-codes","01.csharp/11.aspnet/02.api/04.http-methods-status-codes",{"title":752,"path":753,"stem":754},"Організація HTTP API за принципами REST","/csharp/aspnet/api/rest-organizing","01.csharp/11.aspnet/02.api/05.rest-organizing",{"title":756,"path":757,"stem":758},"Номенклатура URL та CRUD-операції","/csharp/aspnet/api/url-nomenclature-crud","01.csharp/11.aspnet/02.api/06.url-nomenclature-crud",{"title":760,"path":761,"stem":762},"Правила дизайну: іменування та стандарти","/csharp/aspnet/api/api-design-naming","01.csharp/11.aspnet/02.api/07.api-design-naming",{"title":764,"path":765,"stem":766},"Валідація, ліміти та обробка помилок","/csharp/aspnet/api/api-design-validation","01.csharp/11.aspnet/02.api/08.api-design-validation",{"title":768,"path":769,"stem":770},"Обробка помилок у Minimal API","/csharp/aspnet/api/error-handling-http","01.csharp/11.aspnet/02.api/09.error-handling-http",{"title":772,"path":773,"stem":774},"Ідемпотентність та синхронізація стану","/csharp/aspnet/api/idempotency-sync","01.csharp/11.aspnet/02.api/10.idempotency-sync",{"title":776,"path":777,"stem":778},"Пагінація та організація списків","/csharp/aspnet/api/pagination-lists","01.csharp/11.aspnet/02.api/11.pagination-lists",{"title":780,"path":781,"stem":782},"Безпека API, кешування та інтернаціоналізація","/csharp/aspnet/api/security-auth","01.csharp/11.aspnet/02.api/12.security-auth",{"title":784,"path":785,"stem":786},"Процес проєктування API та документування","/csharp/aspnet/api/api-design-process","01.csharp/11.aspnet/02.api/13.api-design-process",{"title":788,"path":789,"stem":790},"OpenAPI: контракт, специфікація та документація API","/csharp/aspnet/api/openapi","01.csharp/11.aspnet/02.api/14.openapi",{"title":792,"icon":793,"path":794,"stem":795,"children":796,"page":59},"Auth","i-lucide-shield-check","/csharp/aspnet/auth","01.csharp/11.aspnet/03.auth",[797,801,805,809,813,817,821,825,829,833,837,841,845,849,853,857,861,865],{"title":798,"path":799,"stem":800},"Основи аутентифікації та авторизації","/csharp/aspnet/auth/auth-fundamentals","01.csharp/11.aspnet/03.auth/01.auth-fundamentals",{"title":802,"path":803,"stem":804},"JWT-аутентифікація","/csharp/aspnet/auth/jwt-authentication","01.csharp/11.aspnet/03.auth/02.jwt-authentication",{"title":806,"path":807,"stem":808},"Авторизація: ролі, політики та resource-based доступ","/csharp/aspnet/auth/authorization-policies","01.csharp/11.aspnet/03.auth/03.authorization-policies",{"title":810,"path":811,"stem":812},"Cookie-аутентифікація та ASP.NET Core Identity","/csharp/aspnet/auth/cookie-auth-identity","01.csharp/11.aspnet/03.auth/04.cookie-auth-identity",{"title":814,"path":815,"stem":816},"JWT + Refresh Tokens (HttpOnly Cookie)","/csharp/aspnet/auth/04b.identity-auth-jwt","01.csharp/11.aspnet/03.auth/04b.identity-auth-jwt",{"title":818,"path":819,"stem":820},"Identity: Підтвердження Email та Скидання Пароля","/csharp/aspnet/auth/identity-email-confirmation","01.csharp/11.aspnet/03.auth/05.identity-email-confirmation",{"title":822,"path":823,"stem":824},"Identity: Двофакторна Аутентифікація (2FA)","/csharp/aspnet/auth/identity-two-factor","01.csharp/11.aspnet/03.auth/06.identity-two-factor",{"title":826,"path":827,"stem":828},"Identity: Внутрішня Архітектура та Кастомізація","/csharp/aspnet/auth/identity-internals","01.csharp/11.aspnet/03.auth/07.identity-internals",{"title":830,"path":831,"stem":832},"OAuth 2.0 та зовнішні провайдери","/csharp/aspnet/auth/oauth-external-providers","01.csharp/11.aspnet/03.auth/08.oauth-external-providers",{"title":834,"path":835,"stem":836},"Безпека на практиці: CORS, HTTPS та захист від атак","/csharp/aspnet/auth/security-hardening","01.csharp/11.aspnet/03.auth/09.security-hardening",{"title":838,"path":839,"stem":840},"Теорія OAuth 2.0: Поняття, Аналогії та Флоу","/csharp/aspnet/auth/oauth-theory","01.csharp/11.aspnet/03.auth/10.oauth-theory",{"title":842,"path":843,"stem":844},"OIDC, OAuth 2.0 та Keycloak в ASP.NET Core","/csharp/aspnet/auth/oidc-keycloak","01.csharp/11.aspnet/03.auth/10.oidc-keycloak",{"title":846,"path":847,"stem":848},"API Keys аутентифікація в ASP.NET Core","/csharp/aspnet/auth/api-keys","01.csharp/11.aspnet/03.auth/11.api-keys",{"title":850,"path":851,"stem":852},"Rate Limiting та Throttling в ASP.NET Core","/csharp/aspnet/auth/rate-limiting","01.csharp/11.aspnet/03.auth/12.rate-limiting",{"title":854,"path":855,"stem":856},"Refresh Token Rotation в ASP.NET Core","/csharp/aspnet/auth/refresh-token-rotation","01.csharp/11.aspnet/03.auth/13.refresh-token-rotation",{"title":858,"path":859,"stem":860},"Certificate Authentication та mTLS в ASP.NET Core","/csharp/aspnet/auth/certificate-auth","01.csharp/11.aspnet/03.auth/14.certificate-auth",{"title":862,"path":863,"stem":864},"RBAC, ABAC та ReBAC в ASP.NET Core","/csharp/aspnet/auth/rbac-abac-rebac","01.csharp/11.aspnet/03.auth/15.rbac-abac-rebac",{"title":866,"path":867,"stem":868},"Multi-tenancy та ізоляція даних в ASP.NET Core","/csharp/aspnet/auth/multi-tenancy","01.csharp/11.aspnet/03.auth/16.multi-tenancy",{"title":870,"icon":871,"path":872,"stem":873,"children":874,"page":59},"Нотифікації","i-lucide-bell","/csharp/aspnet/notifications","01.csharp/11.aspnet/04.notifications",[875,879,883,887,891,895,899,903,907,911,915,919,923],{"title":876,"path":877,"stem":878},"In-App нотифікації через базу даних","/csharp/aspnet/notifications/in-app-database-notifications","01.csharp/11.aspnet/04.notifications/01.in-app-database-notifications",{"title":880,"path":881,"stem":882},"Polling: Регулярний запит оновлень","/csharp/aspnet/notifications/polling","01.csharp/11.aspnet/04.notifications/02.polling",{"title":884,"path":885,"stem":886},"Server-Sent Events: Однострімовий push від сервера","/csharp/aspnet/notifications/server-sent-events","01.csharp/11.aspnet/04.notifications/03.server-sent-events",{"title":888,"path":889,"stem":890},"WebSockets: Двостороннє з'єднання в реальному часі","/csharp/aspnet/notifications/websockets","01.csharp/11.aspnet/04.notifications/04.websockets",{"title":892,"path":893,"stem":894},"SignalR: Абстракція над транспортами реального часу","/csharp/aspnet/notifications/signalr","01.csharp/11.aspnet/04.notifications/05.signalr",{"title":896,"path":897,"stem":898},"Background Services: Фонові задачі в ASP.NET Core","/csharp/aspnet/notifications/background-services","01.csharp/11.aspnet/04.notifications/06.background-services",{"title":900,"path":901,"stem":902},"Web Push нотифікації","/csharp/aspnet/notifications/web-push","01.csharp/11.aspnet/04.notifications/07.web-push",{"title":904,"path":905,"stem":906},"Email нотифікації","/csharp/aspnet/notifications/email-notifications","01.csharp/11.aspnet/04.notifications/08.email-notifications",{"title":908,"path":909,"stem":910},"Порівняння підходів: Як вибрати правильну технологію нотифікацій","/csharp/aspnet/notifications/choosing-the-right-approach","01.csharp/11.aspnet/04.notifications/09.choosing-the-right-approach",{"title":912,"path":913,"stem":914},"Hangfire: Надійне планування фонових задач","/csharp/aspnet/notifications/hangfire","01.csharp/11.aspnet/04.notifications/10.hangfire",{"title":916,"path":917,"stem":918},"Практика: Конвертація зображень у WebP через Hangfire","/csharp/aspnet/notifications/hangfire-image-webp","01.csharp/11.aspnet/04.notifications/11.hangfire-image-webp",{"title":920,"path":921,"stem":922},"Практика: Підготовка відео до HLS-стрімінгу через Hangfire","/csharp/aspnet/notifications/hangfire-video-hls","01.csharp/11.aspnet/04.notifications/12.hangfire-video-hls",{"title":924,"path":925,"stem":926},"Telegram-нотифікації: від одного повідомлення до масових розсилок і мульти-канального підходу","/csharp/aspnet/notifications/telegram-notifications","01.csharp/11.aspnet/04.notifications/13.telegram-notifications",{"title":928,"icon":929,"path":930,"stem":931,"children":932,"page":59},"Інтернаціоналізація","i-lucide-languages","/csharp/aspnet/i18n","01.csharp/11.aspnet/05.i18n",[933,937],{"title":934,"path":935,"stem":936},"Інтернаціоналізація (i18n) у Minimal API: від A до Я","/csharp/aspnet/i18n/internationalization","01.csharp/11.aspnet/05.i18n/01.internationalization",{"title":938,"path":939,"stem":940},"Humanizer: людиномовні рядки у .NET","/csharp/aspnet/i18n/humanizer","01.csharp/11.aspnet/05.i18n/02.humanizer",{"title":942,"icon":943,"path":944,"stem":945,"children":946,"page":59},"Кешування","i-lucide-layers","/csharp/aspnet/caching","01.csharp/11.aspnet/06.caching",[947,951,955,959,963],{"title":948,"path":949,"stem":950},"Огляд кешування: чотири рівні і коли що обирати","/csharp/aspnet/caching/caching","01.csharp/11.aspnet/06.caching/01.caching",{"title":952,"path":953,"stem":954},"IMemoryCache: кеш в оперативній пам'яті","/csharp/aspnet/caching/memory-cache","01.csharp/11.aspnet/06.caching/02.memory-cache",{"title":956,"path":957,"stem":958},"IDistributedCache і Redis: розподілений кеш","/csharp/aspnet/caching/distributed-cache","01.csharp/11.aspnet/06.caching/03.distributed-cache",{"title":960,"path":961,"stem":962},"Response Cache: HTTP-кешування через Cache-Control","/csharp/aspnet/caching/response-cache","01.csharp/11.aspnet/06.caching/04.response-cache",{"title":964,"path":965,"stem":966},"Output Cache: серверний кеш HTTP-відповідей (.NET 7+)","/csharp/aspnet/caching/output-cache","01.csharp/11.aspnet/06.caching/05.output-cache",{"title":968,"icon":969,"path":970,"stem":971,"children":972,"page":59},"Тестування","i-lucide-test-tube","/csharp/aspnet/testing","01.csharp/11.aspnet/07.testing",[973,977,981,985,989,993,997,1001,1005,1009,1013,1017,1021,1025,1029,1033,1037,1041,1045,1049,1053],{"title":974,"path":975,"stem":976},"Що таке тестування? Від інтуїції до науки","/csharp/aspnet/testing/what-is-testing","01.csharp/11.aspnet/07.testing/01.what-is-testing",{"title":978,"path":979,"stem":980},"Піраміда тестування — Стратегія, а не Догма","/csharp/aspnet/testing/testing-pyramid","01.csharp/11.aspnet/07.testing/02.testing-pyramid",{"title":982,"path":983,"stem":984},"Дві Школи Тестування — Лондон проти Детройту","/csharp/aspnet/testing/testing-schools","01.csharp/11.aspnet/07.testing/03.testing-schools",{"title":986,"path":987,"stem":988},"TDD та BDD — Тести як Дизайн-інструмент","/csharp/aspnet/testing/tdd-and-bdd","01.csharp/11.aspnet/07.testing/04.tdd-and-bdd",{"title":990,"path":991,"stem":992},"Що саме тестувати — Техніки аналізу та Циклomatична складність","/csharp/aspnet/testing/what-to-test","01.csharp/11.aspnet/07.testing/05.what-to-test",{"title":994,"path":995,"stem":996},"Тестові Фреймворки — Навіщо вони і що всередині","/csharp/aspnet/testing/test-frameworks","01.csharp/11.aspnet/07.testing/06.test-frameworks",{"title":998,"path":999,"stem":1000},"xUnit — Факти, Теорії та Lifecycle тестів","/csharp/aspnet/testing/xunit-basics","01.csharp/11.aspnet/07.testing/07.xunit-basics",{"title":1002,"path":1003,"stem":1004},"xUnit Advanced — Fixtures, Кастомізація та Розширення","/csharp/aspnet/testing/xunit-advanced","01.csharp/11.aspnet/07.testing/08.xunit-advanced",{"title":1006,"path":1007,"stem":1008},"Moq — Глибоке занурення в мокування","/csharp/aspnet/testing/mocking-with-moq","01.csharp/11.aspnet/07.testing/09.mocking-with-moq",{"title":1010,"path":1011,"stem":1012},"Тестування Баз Даних — EF Core, SQLite та Testcontainers","/csharp/aspnet/testing/database-testing","01.csharp/11.aspnet/07.testing/10.database-testing",{"title":1014,"path":1015,"stem":1016},"Integration Testing — Частина 1 [Теорія та WebApplicationFactory]","/csharp/aspnet/testing/integration-testing","01.csharp/11.aspnet/07.testing/11.integration-testing",{"title":1018,"path":1019,"stem":1020},"Інтеграційне тестування — Практика","/csharp/aspnet/testing/11a.integration-testing-practice","01.csharp/11.aspnet/07.testing/11a.integration-testing-practice",{"title":1022,"path":1023,"stem":1024},"Integration Testing — Частина 2 [Просунуті Сценарії та Testcontainers]","/csharp/aspnet/testing/integration-testing-advanced","01.csharp/11.aspnet/07.testing/12.integration-testing-advanced",{"title":1026,"path":1027,"stem":1028},"Професійний Postman: Колекції, Змінні та GitHub Інтеграція","/csharp/aspnet/testing/postman-professional","01.csharp/11.aspnet/07.testing/13.postman-professional",{"title":1030,"path":1031,"stem":1032},"HttpClient у Тестах Частина 1: Архітектура та MockHttpMessageHandler","/csharp/aspnet/testing/httpclient-testing","01.csharp/11.aspnet/07.testing/14.httpclient-testing",{"title":1034,"path":1035,"stem":1036},"HttpClient у Тестах Частина 2: WireMock.Net та Resilience","/csharp/aspnet/testing/wiremock-net","01.csharp/11.aspnet/07.testing/15.wiremock-net",{"title":1038,"path":1039,"stem":1040},"Патерни та Анти-патерни Тестування: Test Smells","/csharp/aspnet/testing/testing-patterns","01.csharp/11.aspnet/07.testing/16.testing-patterns",{"title":1042,"path":1043,"stem":1044},"Просунуті інструменти: Time, Snapshots та Властивості","/csharp/aspnet/testing/advanced-testing-tools","01.csharp/11.aspnet/07.testing/17.advanced-testing-tools",{"title":1046,"path":1047,"stem":1048},"Тестування Архітектури з NetArchTest","/csharp/aspnet/testing/architecture-testing","01.csharp/11.aspnet/07.testing/18.architecture-testing",{"title":1050,"path":1051,"stem":1052},"Тестування Продуктивності: BenchmarkDotNet, NBomber та k6","/csharp/aspnet/testing/performance-testing","01.csharp/11.aspnet/07.testing/19.performance-testing",{"title":1054,"path":1055,"stem":1056},"Залишок плану для курсу \"Тестування ASP.NET Minimal API\"","/csharp/aspnet/testing/remaining_plan","01.csharp/11.aspnet/07.testing/remaining_plan",{"title":1058,"icon":1059,"path":1060,"stem":1061,"children":1062,"page":59},"Платежі","i-lucide-credit-card","/csharp/aspnet/payments","01.csharp/11.aspnet/08.payments",[1063,1067,1071,1075,1079,1083,1087,1091,1095,1099,1103,1107],{"title":1064,"path":1065,"stem":1066},"Основи платіжної інфраструктури","/csharp/aspnet/payments/payment-fundamentals","01.csharp/11.aspnet/08.payments/01.payment-fundamentals",{"title":1068,"path":1069,"stem":1070},"Методи оплати в Україні","/csharp/aspnet/payments/payment-methods-ukraine","01.csharp/11.aspnet/08.payments/02.payment-methods-ukraine",{"title":1072,"path":1073,"stem":1074},"PCI DSS та безпека платежів","/csharp/aspnet/payments/pci-dss-security","01.csharp/11.aspnet/08.payments/03.pci-dss-security",{"title":1076,"path":1077,"stem":1078},"Архітектура платіжної підсистеми","/csharp/aspnet/payments/payment-architecture","01.csharp/11.aspnet/08.payments/04.payment-architecture",{"title":1080,"path":1081,"stem":1082},"Інтеграція LiqPay (ПриватБанк)","/csharp/aspnet/payments/liqpay-integration","01.csharp/11.aspnet/08.payments/05.liqpay-integration",{"title":1084,"path":1085,"stem":1086},"Інтеграція Monobank Acquiring API","/csharp/aspnet/payments/monobank-acquiring","01.csharp/11.aspnet/08.payments/06.monobank-acquiring",{"title":1088,"path":1089,"stem":1090},"Інтеграція Stripe","/csharp/aspnet/payments/stripe-integration","01.csharp/11.aspnet/08.payments/07.stripe-integration",{"title":1092,"path":1093,"stem":1094},"Webhooks — глибоке занурення","/csharp/aspnet/payments/webhooks-deep-dive","01.csharp/11.aspnet/08.payments/08.webhooks-deep-dive",{"title":1096,"path":1097,"stem":1098},"Підписки та рекурентні платежі","/csharp/aspnet/payments/subscriptions-recurring","01.csharp/11.aspnet/08.payments/09.subscriptions-recurring",{"title":1100,"path":1101,"stem":1102},"Повернення коштів та диспути","/csharp/aspnet/payments/refunds-disputes","01.csharp/11.aspnet/08.payments/10.refunds-disputes",{"title":1104,"path":1105,"stem":1106},"Тестування платіжних інтеграцій","/csharp/aspnet/payments/testing-payments","01.csharp/11.aspnet/08.payments/11.testing-payments",{"title":1108,"path":1109,"stem":1110},"Чекліст виходу в Production","/csharp/aspnet/payments/production-checklist","01.csharp/11.aspnet/08.payments/12.production-checklist",{"title":1112,"icon":1113,"items":1114,"path":1127,"stem":1128,"children":1129,"page":59},"Популярні бібліотеки","lucide:box",[1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126],"01.fluent-validation","02.mapster","03.erroror-result-pattern","04.serilog","05.mediatr","06.polly","07.health-checks","08.feature-management","09.fluent-email","10.quest-pdf","11.bogus","12.humanizer-guard","/csharp/aspnet/libraries","01.csharp/11.aspnet/09.libraries",[1130,1134,1138,1142,1146,1150,1154,1158,1162,1166,1170,1174,1178],{"title":1131,"path":1132,"stem":1133},"Валідація з FluentValidation в ASP.NET Core","/csharp/aspnet/libraries/fluent-validation","01.csharp/11.aspnet/09.libraries/01.fluent-validation",{"title":1135,"path":1136,"stem":1137},"Маппінг об","/csharp/aspnet/libraries/mapster","01.csharp/11.aspnet/09.libraries/02.mapster",{"title":1139,"path":1140,"stem":1141},"Обробка помилок з ErrorOr та Result Pattern в ASP.NET Core","/csharp/aspnet/libraries/erroror-result-pattern","01.csharp/11.aspnet/09.libraries/03.erroror-result-pattern",{"title":1143,"path":1144,"stem":1145},"Структуроване логування з Serilog в ASP.NET Core","/csharp/aspnet/libraries/serilog","01.csharp/11.aspnet/09.libraries/04.serilog",{"title":1147,"path":1148,"stem":1149},"CQRS та Mediator з MediatR в ASP.NET Core","/csharp/aspnet/libraries/mediatr","01.csharp/11.aspnet/09.libraries/05.mediatr",{"title":1151,"path":1152,"stem":1153},"Відмовостійкість з Polly в ASP.NET Core","/csharp/aspnet/libraries/polly","01.csharp/11.aspnet/09.libraries/06.polly",{"title":1155,"path":1156,"stem":1157},"Health Checks в ASP.NET Core","/csharp/aspnet/libraries/health-checks","01.csharp/11.aspnet/09.libraries/07.health-checks",{"title":1159,"path":1160,"stem":1161},"Feature Management та Feature Flags в ASP.NET Core","/csharp/aspnet/libraries/feature-management","01.csharp/11.aspnet/09.libraries/08.feature-management",{"title":1163,"path":1164,"stem":1165},"Відправка Email з FluentEmail в ASP.NET Core","/csharp/aspnet/libraries/fluent-email","01.csharp/11.aspnet/09.libraries/09.fluent-email",{"title":1167,"path":1168,"stem":1169},"Генерація PDF з QuestPDF в ASP.NET Core","/csharp/aspnet/libraries/quest-pdf","01.csharp/11.aspnet/09.libraries/10.quest-pdf",{"title":1171,"path":1172,"stem":1173},"Генерація тестових даних з Bogus в ASP.NET Core","/csharp/aspnet/libraries/bogus","01.csharp/11.aspnet/09.libraries/11.bogus",{"title":1175,"path":1176,"stem":1177},"Humanizer та Guard Clauses в ASP.NET Core","/csharp/aspnet/libraries/humanizer-guard","01.csharp/11.aspnet/09.libraries/12.humanizer-guard",{"title":1179,"path":1180,"stem":1181},"План модуля 10.libraries — Популярні бібліотеки ASP.NET","/csharp/aspnet/libraries/plan","01.csharp/11.aspnet/09.libraries/plan",{"title":1183,"icon":1184,"path":1185,"stem":1186,"children":1187,"page":59},"Razor Pages","i-lucide-layout-template","/csharp/aspnet/razor-pages","01.csharp/11.aspnet/10.razor-pages",[1188,1192,1196,1200,1204,1208],{"title":1189,"path":1190,"stem":1191},"Від Minimal API до Razor Pages: концептуальний перехід","/csharp/aspnet/razor-pages/from-minimal-api","01.csharp/11.aspnet/10.razor-pages/01.from-minimal-api",{"title":1193,"path":1194,"stem":1195},"PageModel: логіка сторінки Razor Pages","/csharp/aspnet/razor-pages/page-model","01.csharp/11.aspnet/10.razor-pages/02.page-model",{"title":1197,"path":1198,"stem":1199},"Razor синтаксис: шаблонізатор у .cshtml","/csharp/aspnet/razor-pages/razor-syntax","01.csharp/11.aspnet/10.razor-pages/03.razor-syntax",{"title":1201,"path":1202,"stem":1203},"Tag Helpers: типізований HTML","/csharp/aspnet/razor-pages/tag-helpers","01.csharp/11.aspnet/10.razor-pages/04.tag-helpers",{"title":1205,"path":1206,"stem":1207},"Форми і валідація: повний цикл обробки даних","/csharp/aspnet/razor-pages/forms-validation","01.csharp/11.aspnet/10.razor-pages/05.forms-validation",{"title":1209,"path":1210,"stem":1211},"Практичний проєкт: TaskManager на Razor Pages","/csharp/aspnet/razor-pages/project-task-manager","01.csharp/11.aspnet/10.razor-pages/06.project-task-manager",{"title":1213,"path":1214,"stem":1215,"children":1216,"page":59},"ASP.NET Core MVC","/csharp/aspnet/mvc","01.csharp/11.aspnet/11.mvc",[1217,1221,1225,1229,1233,1237,1241,1245,1249,1253,1257,1261,1265,1269,1273,1277,1281,1285],{"title":1218,"path":1219,"stem":1220},"Патерн MVC: архітектура, що змінила веб","/csharp/aspnet/mvc/mvc-pattern","01.csharp/11.aspnet/11.mvc/01.mvc-pattern",{"title":1222,"path":1223,"stem":1224},"Від Razor Pages до MVC: концептуальний перехід","/csharp/aspnet/mvc/from-razor-pages","01.csharp/11.aspnet/11.mvc/02.from-razor-pages",{"title":1226,"path":1227,"stem":1228},"Controllers та Actions: серце MVC","/csharp/aspnet/mvc/controllers-actions","01.csharp/11.aspnet/11.mvc/03.controllers-actions",{"title":1230,"path":1231,"stem":1232},"Маршрутизація в MVC: Convention vs Attribute Routing","/csharp/aspnet/mvc/routing-mvc","01.csharp/11.aspnet/11.mvc/04.routing-mvc",{"title":1234,"path":1235,"stem":1236},"Model Binding: від HTTP до C#","/csharp/aspnet/mvc/model-binding","01.csharp/11.aspnet/11.mvc/05.model-binding",{"title":1238,"path":1239,"stem":1240},"Views, ViewData, ViewBag, TempData і ViewModel","/csharp/aspnet/mvc/views-viewdata-tempdata","01.csharp/11.aspnet/11.mvc/06.views-viewdata-tempdata",{"title":1242,"path":1243,"stem":1244},"Filters: аспектно-орієнтоване програмування в MVC","/csharp/aspnet/mvc/filters","01.csharp/11.aspnet/11.mvc/07.filters",{"title":1246,"path":1247,"stem":1248},"Areas: структурування великих застосунків","/csharp/aspnet/mvc/areas","01.csharp/11.aspnet/11.mvc/08.areas",{"title":1250,"path":1251,"stem":1252},"View Components: повторювані незалежні блоки UI","/csharp/aspnet/mvc/view-components","01.csharp/11.aspnet/11.mvc/09.view-components",{"title":1254,"path":1255,"stem":1256},"Display та Editor Templates","/csharp/aspnet/mvc/display-editor-templates","01.csharp/11.aspnet/11.mvc/10.display-editor-templates",{"title":1258,"path":1259,"stem":1260},"Валідація: IValidatableObject та FluentValidation","/csharp/aspnet/mvc/validation-advanced","01.csharp/11.aspnet/11.mvc/11.validation-advanced",{"title":1262,"path":1263,"stem":1264},"HTMX: інтерактивність через HTML-атрибути","/csharp/aspnet/mvc/htmx","01.csharp/11.aspnet/11.mvc/12.htmx",{"title":1266,"path":1267,"stem":1268},"HTMX у ASP.NET Core MVC: серверна інтеграція","/csharp/aspnet/mvc/ajax-htmx-mvc","01.csharp/11.aspnet/11.mvc/13.ajax-htmx-mvc",{"title":1270,"path":1271,"stem":1272},"Практичний проєкт: Каталог товарів з HTMX","/csharp/aspnet/mvc/htmx-project","01.csharp/11.aspnet/11.mvc/14.htmx-project",{"title":1274,"path":1275,"stem":1276},"Завантаження та обробка файлів","/csharp/aspnet/mvc/file-upload","01.csharp/11.aspnet/11.mvc/15.file-upload",{"title":1278,"path":1279,"stem":1280},"Глобалізація та Локалізація MVC","/csharp/aspnet/mvc/globalization-localization","01.csharp/11.aspnet/11.mvc/16.globalization-localization",{"title":1282,"path":1283,"stem":1284},"Підсумковий проєкт: Блог-платформа","/csharp/aspnet/mvc/mvc-project","01.csharp/11.aspnet/11.mvc/17.mvc-project",{"title":1286,"path":1287,"stem":1288},"План курсу: ASP.NET Core MVC","/csharp/aspnet/mvc/plan","01.csharp/11.aspnet/11.mvc/plan",{"title":1290,"path":1291,"stem":1292,"children":1293,"page":59},"Web Api","/csharp/aspnet/web-api","01.csharp/11.aspnet/12.web-api",[1294,1298,1302,1306,1310,1314,1318,1322,1326,1330,1334,1338,1342],{"title":1295,"path":1296,"stem":1297},"Від Minimal API до Controller-based API","/csharp/aspnet/web-api/from-minimal-api-to-controllers","01.csharp/11.aspnet/12.web-api/01.from-minimal-api-to-controllers",{"title":1299,"path":1300,"stem":1301},"ControllerBase, ActionResult\u003CT> та Response Types","/csharp/aspnet/web-api/controller-base-actionresult","01.csharp/11.aspnet/12.web-api/02.controller-base-actionresult",{"title":1303,"path":1304,"stem":1305},"Content Negotiation - JSON, XML та власні форматери","/csharp/aspnet/web-api/content-negotiation","01.csharp/11.aspnet/12.web-api/03.content-negotiation",{"title":1307,"path":1308,"stem":1309},"Версіонування API","/csharp/aspnet/web-api/api-versioning","01.csharp/11.aspnet/12.web-api/04.api-versioning",{"title":1311,"path":1312,"stem":1313},"ProblemDetails та структурована обробка помилок","/csharp/aspnet/web-api/problemdetails-error-handling","01.csharp/11.aspnet/12.web-api/05.problemdetails-error-handling",{"title":1315,"path":1316,"stem":1317},"Фільтри у Web API контексті","/csharp/aspnet/web-api/filters-for-api","01.csharp/11.aspnet/12.web-api/06.filters-for-api",{"title":1319,"path":1320,"stem":1321},"Пагінація, фільтрація та сортування","/csharp/aspnet/web-api/pagination-filtering-sorting","01.csharp/11.aspnet/12.web-api/07.pagination-filtering-sorting",{"title":1323,"path":1324,"stem":1325},"HATEOAS та Resource Expansion","/csharp/aspnet/web-api/hateoas-resource-expansion","01.csharp/11.aspnet/12.web-api/08.hateoas-resource-expansion",{"title":1327,"path":1328,"stem":1329},"Гібридна архітектура - Minimal API + Controllers","/csharp/aspnet/web-api/minimal-api-vs-controllers-hybrid","01.csharp/11.aspnet/12.web-api/09.minimal-api-vs-controllers-hybrid",{"title":1331,"path":1332,"stem":1333},"Документація API - Swashbuckle, NSwag та генерація клієнтів","/csharp/aspnet/web-api/api-documentation-generation","01.csharp/11.aspnet/12.web-api/10.api-documentation-generation",{"title":1335,"path":1336,"stem":1337},"Health Checks та моніторинг API","/csharp/aspnet/web-api/health-checks-monitoring","01.csharp/11.aspnet/12.web-api/11.health-checks-monitoring",{"title":1339,"path":1340,"stem":1341},"Підсумковий проєкт - Production-Ready REST API","/csharp/aspnet/web-api/web-api-project","01.csharp/11.aspnet/12.web-api/12.web-api-project",{"title":1343,"path":1344,"stem":1345},"План курсу: ASP.NET Core Web API (Controllers)","/csharp/aspnet/web-api/plan","01.csharp/11.aspnet/12.web-api/plan",{"title":1347,"icon":1348,"path":1349,"stem":1350,"children":1351,"page":59},"Desktop UI","i-lucide-app-window","/csharp/desktop-ui","01.csharp/12.desktop-ui",[1352,1356,1360,1364,1368,1372,1376,1380,1384,1388,1392,1396,1400,1404,1408,1412,1416,1420,1424,1428,1432,1436,1440,1444,1448,1452,1456,1460,1464,1468,1472,1476,1480,1484,1488,1492,1496,1500,1504,1508,1512,1516,1520,1524,1528,1532,1536,1540,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1592,1596,1600,1604,1608,1612,1616,1620,1624,1628,1632],{"title":1353,"path":1354,"stem":1355},"Що таке десктопна розробка?","/csharp/desktop-ui/what-is-desktop-dev","01.csharp/12.desktop-ui/01.what-is-desktop-dev",{"title":1357,"path":1358,"stem":1359},"Архітектура WPF — як влаштований графічний інтерфейс","/csharp/desktop-ui/wpf-architecture","01.csharp/12.desktop-ui/02.wpf-architecture",{"title":1361,"path":1362,"stem":1363},"Перший WPF-проєкт — від нуля до вікна","/csharp/desktop-ui/first-wpf-app","01.csharp/12.desktop-ui/03.first-wpf-app",{"title":1365,"path":1366,"stem":1367},"Перший Avalonia-проєкт: WPF для всіх платформ","/csharp/desktop-ui/03a.first-avalonia-app","01.csharp/12.desktop-ui/03a.first-avalonia-app",{"title":1369,"path":1370,"stem":1371},"XAML: декларативний інтерфейс","/csharp/desktop-ui/xaml-basics","01.csharp/12.desktop-ui/04.xaml-basics",{"title":1373,"path":1374,"stem":1375},"Fluent UI у WPF — сучасний дизайн Windows 11","/csharp/desktop-ui/04a.wpf-fluent-ui","01.csharp/12.desktop-ui/04a.wpf-fluent-ui",{"title":1377,"path":1378,"stem":1379},"WPF UI — сучасна бібліотека Fluent контролів","/csharp/desktop-ui/04b.wpf-ui-library","01.csharp/12.desktop-ui/04b.wpf-ui-library",{"title":1381,"path":1382,"stem":1383},"HandyControl — велика бібліотека UI контролів для WPF","/csharp/desktop-ui/04c.handycontrol-library","01.csharp/12.desktop-ui/04c.handycontrol-library",{"title":1385,"path":1386,"stem":1387},"Простори імен та ресурси XAML","/csharp/desktop-ui/xaml-namespaces-resources","01.csharp/12.desktop-ui/05.xaml-namespaces-resources",{"title":1389,"path":1390,"stem":1391},"XAML в Avalonia: ключові відмінності від WPF","/csharp/desktop-ui/05a.avalonia-xaml-differences","01.csharp/12.desktop-ui/05a.avalonia-xaml-differences",{"title":1393,"path":1394,"stem":1395},"Розширення розмітки XAML (Markup Extensions)","/csharp/desktop-ui/xaml-markup-extensions","01.csharp/12.desktop-ui/06.xaml-markup-extensions",{"title":1397,"path":1398,"stem":1399},"Панелі Layout: StackPanel, WrapPanel, DockPanel","/csharp/desktop-ui/layout-panels-part1","01.csharp/12.desktop-ui/07.layout-panels-part1",{"title":1401,"path":1402,"stem":1403},"Grid, Canvas, UniformGrid","/csharp/desktop-ui/layout-panels-part2","01.csharp/12.desktop-ui/07.layout-panels-part2",{"title":1405,"path":1406,"stem":1407},"Просунуті техніки Layout","/csharp/desktop-ui/layout-advanced","01.csharp/12.desktop-ui/08.layout-advanced",{"title":1409,"path":1410,"stem":1411},"Адаптивний Layout та найкращі практики","/csharp/desktop-ui/layout-responsive","01.csharp/12.desktop-ui/09.layout-responsive",{"title":1413,"path":1414,"stem":1415},"Layout в Avalonia: відмінності та нові можливості","/csharp/desktop-ui/09a.layout-avalonia","01.csharp/12.desktop-ui/09a.layout-avalonia",{"title":1417,"path":1418,"stem":1419},"Button, Image, ProgressBar та інші базові контроли","/csharp/desktop-ui/basic-controls","01.csharp/12.desktop-ui/10.basic-controls",{"title":1421,"path":1422,"stem":1423},"Контроли в Avalonia: відмінності від WPF","/csharp/desktop-ui/10a.controls-avalonia","01.csharp/12.desktop-ui/10a.controls-avalonia",{"title":1425,"path":1426,"stem":1427},"Текстові контроли — TextBlock, TextBox, RichTextBox","/csharp/desktop-ui/text-controls","01.csharp/12.desktop-ui/11.text-controls",{"title":1429,"path":1430,"stem":1431},"Контроли вибору — CheckBox, RadioButton, ComboBox, ListBox, DatePicker","/csharp/desktop-ui/selection-controls","01.csharp/12.desktop-ui/12.selection-controls",{"title":1433,"path":1434,"stem":1435},"Content Model — GroupBox, Expander, TabControl, StatusBar","/csharp/desktop-ui/content-controls","01.csharp/12.desktop-ui/13.content-controls",{"title":1437,"path":1438,"stem":1439},"UI/UX принципи десктопних застосунків","/csharp/desktop-ui/13a.ui-ux-principles","01.csharp/12.desktop-ui/13a.ui-ux-principles",{"title":1441,"path":1442,"stem":1443},"Dependency Properties — Концепція та Value Resolution","/csharp/desktop-ui/dependency-properties-part1","01.csharp/12.desktop-ui/14.dependency-properties-part1",{"title":1445,"path":1446,"stem":1447},"Avalonia Property System — StyledProperty та DirectProperty","/csharp/desktop-ui/14a.avalonia-property-system","01.csharp/12.desktop-ui/14a.avalonia-property-system",{"title":1449,"path":1450,"stem":1451},"Attached Properties — Властивості без меж","/csharp/desktop-ui/attached-properties","01.csharp/12.desktop-ui/15.attached-properties",{"title":1453,"path":1454,"stem":1455},"Routed Events — Маршрутизація подій у WPF","/csharp/desktop-ui/routed-events","01.csharp/12.desktop-ui/16.routed-events",{"title":1457,"path":1458,"stem":1459},"Data Binding — Від Code-Behind до Декларативності","/csharp/desktop-ui/data-binding-basics-part1","01.csharp/12.desktop-ui/17.data-binding-basics-part1",{"title":1461,"path":1462,"stem":1463},"INotifyPropertyChanged — Живе оновлення UI","/csharp/desktop-ui/data-binding-basics-part2","01.csharp/12.desktop-ui/17.data-binding-basics-part2",{"title":1465,"path":1466,"stem":1467},"Compiled Bindings в Avalonia — Безпека на етапі компіляції","/csharp/desktop-ui/17a.avalonia-compiled-bindings","01.csharp/12.desktop-ui/17a.avalonia-compiled-bindings",{"title":1469,"path":1470,"stem":1471},"Просунутий Data Binding — ElementName, RelativeSource, MultiBinding","/csharp/desktop-ui/data-binding-advanced","01.csharp/12.desktop-ui/18.data-binding-advanced",{"title":1473,"path":1474,"stem":1475},"Value Converters — Перетворення типів даних у Data Binding","/csharp/desktop-ui/value-converters","01.csharp/12.desktop-ui/19.value-converters",{"title":1477,"path":1478,"stem":1479},"Data Templates — Візуалізація об'єктів у WPF","/csharp/desktop-ui/data-templates","01.csharp/12.desktop-ui/20.data-templates",{"title":1481,"path":1482,"stem":1483},"Collections Binding Part 1 — ObservableCollection та ItemsControl","/csharp/desktop-ui/collections-binding-part1","01.csharp/12.desktop-ui/21.collections-binding-part1",{"title":1485,"path":1486,"stem":1487},"Collections Binding Part 2 — ICollectionView, Filtering, Sorting та Virtualization","/csharp/desktop-ui/collections-binding-part2","01.csharp/12.desktop-ui/21.collections-binding-part2",{"title":1489,"path":1490,"stem":1491},"MVVM Pattern — Від Spaghetti Code до архітектури","/csharp/desktop-ui/mvvm-pattern","01.csharp/12.desktop-ui/22.mvvm-pattern",{"title":1493,"path":1494,"stem":1495},"ViewModel Implementation — Від BaseViewModel до валідації","/csharp/desktop-ui/viewmodel-implementation","01.csharp/12.desktop-ui/23.viewmodel-implementation",{"title":1497,"path":1498,"stem":1499},"Commands — Від event handlers до декларативних команд","/csharp/desktop-ui/commands","01.csharp/12.desktop-ui/24.commands",{"title":1501,"path":1502,"stem":1503},"MVVM Toolkit — MVVM без boilerplate через Source Generators","/csharp/desktop-ui/mvvm-toolkit","01.csharp/12.desktop-ui/25.mvvm-toolkit",{"title":1505,"path":1506,"stem":1507},"Messenger Pattern — Комунікація між ViewModel без прямих посилань","/csharp/desktop-ui/messenger-pattern","01.csharp/12.desktop-ui/26.messenger-pattern",{"title":1509,"path":1510,"stem":1511},"Стилі WPF — CSS для десктопу","/csharp/desktop-ui/styles-basics","01.csharp/12.desktop-ui/27.styles-basics",{"title":1513,"path":1514,"stem":1515},"CSS-like стилі Avalonia","/csharp/desktop-ui/27a.avalonia-css-styling","01.csharp/12.desktop-ui/27a.avalonia-css-styling",{"title":1517,"path":1518,"stem":1519},"Control Templates — Частина 1. Концепція та TemplateBinding","/csharp/desktop-ui/control-templates-part1","01.csharp/12.desktop-ui/28.control-templates-part1",{"title":1521,"path":1522,"stem":1523},"Control Templates — Частина 2. Named Parts та ContentPresenter","/csharp/desktop-ui/control-templates-part2","01.csharp/12.desktop-ui/28.control-templates-part2",{"title":1525,"path":1526,"stem":1527},"Control Themes в Avalonia — нова ера стилізації","/csharp/desktop-ui/28a.avalonia-control-themes","01.csharp/12.desktop-ui/28a.avalonia-control-themes",{"title":1529,"path":1530,"stem":1531},"Triggers та Visual State Manager у WPF","/csharp/desktop-ui/triggers-visual-states","01.csharp/12.desktop-ui/29.triggers-visual-states",{"title":1533,"path":1534,"stem":1535},"Pseudo-classes в Avalonia — замість WPF Triggers","/csharp/desktop-ui/29a.avalonia-pseudo-classes","01.csharp/12.desktop-ui/29a.avalonia-pseudo-classes",{"title":1537,"path":1538,"stem":1539},"Теми та ресурсні словники у WPF","/csharp/desktop-ui/resources-themes","01.csharp/12.desktop-ui/30.resources-themes",{"title":1541,"path":1542,"stem":1543},"Avalonia Themes — Fluent Design та система тематизації","/csharp/desktop-ui/30a.avalonia-themes-fluent","01.csharp/12.desktop-ui/30a.avalonia-themes-fluent",{"title":1545,"path":1546,"stem":1547},"Контроли колекцій — глибоке занурення","/csharp/desktop-ui/collection-controls","01.csharp/12.desktop-ui/31.collection-controls",{"title":1549,"path":1550,"stem":1551},"DataGrid — колонки та базове відображення","/csharp/desktop-ui/datagrid-part1","01.csharp/12.desktop-ui/32.datagrid-part1",{"title":1553,"path":1554,"stem":1555},"DataGrid — сортування, фільтрація, редагування","/csharp/desktop-ui/datagrid-part2","01.csharp/12.desktop-ui/32.datagrid-part2",{"title":1557,"path":1558,"stem":1559},"TreeView та GridView","/csharp/desktop-ui/treeview-listview","01.csharp/12.desktop-ui/33.treeview-listview",{"title":1561,"path":1562,"stem":1563},"Меню, Toolbar, ContextMenu, StatusBar","/csharp/desktop-ui/menus-toolbars","01.csharp/12.desktop-ui/34.menus-toolbars",{"title":1565,"path":1566,"stem":1567},"Навігація та керування вікнами. Частина 1: вікна та сторінки","/csharp/desktop-ui/navigation-windows-part1","01.csharp/12.desktop-ui/35.navigation-windows-part1",{"title":1569,"path":1570,"stem":1571},"Навігація та керування вікнами. Частина 2: MVVM-навігація","/csharp/desktop-ui/navigation-windows-part2","01.csharp/12.desktop-ui/35.navigation-windows-part2",{"title":1573,"path":1574,"stem":1575},"Avalonia — Навігація та діалоги","/csharp/desktop-ui/35a.avalonia-navigation-dialogs","01.csharp/12.desktop-ui/35a.avalonia-navigation-dialogs",{"title":1577,"path":1578,"stem":1579},"Діалоги та File Pickers у WPF","/csharp/desktop-ui/dialogs-file-pickers","01.csharp/12.desktop-ui/36.dialogs-file-pickers",{"title":1581,"path":1582,"stem":1583},"UserControl: компонентний підхід у WPF","/csharp/desktop-ui/user-controls","01.csharp/12.desktop-ui/37.user-controls",{"title":1585,"path":1586,"stem":1587},"Custom Controls: Lookless Controls у WPF","/csharp/desktop-ui/custom-controls","01.csharp/12.desktop-ui/38.custom-controls",{"title":1589,"path":1590,"stem":1591},"Avalonia TemplatedControl — Lookless Controls","/csharp/desktop-ui/38a.avalonia-templated-controls","01.csharp/12.desktop-ui/38a.avalonia-templated-controls",{"title":1593,"path":1594,"stem":1595},"Анімації у WPF: Storyboard та Easing Functions","/csharp/desktop-ui/animations-transitions","01.csharp/12.desktop-ui/39.animations-transitions",{"title":1597,"path":1598,"stem":1599},"Анімації в Avalonia","/csharp/desktop-ui/39a.avalonia-animations","01.csharp/12.desktop-ui/39a.avalonia-animations",{"title":1601,"path":1602,"stem":1603},"2D Графіка та Мультимедіа у WPF","/csharp/desktop-ui/media-graphics","01.csharp/12.desktop-ui/40.media-graphics",{"title":1605,"path":1606,"stem":1607},"Dependency Injection у WPF та Avalonia","/csharp/desktop-ui/di-integration","01.csharp/12.desktop-ui/41.di-integration",{"title":1609,"path":1610,"stem":1611},"SQLite та EF Core у десктопних додатках","/csharp/desktop-ui/data-persistence-part1","01.csharp/12.desktop-ui/42.data-persistence-part1",{"title":1613,"path":1614,"stem":1615},"Repository Pattern та Unit of Work","/csharp/desktop-ui/data-persistence-part2","01.csharp/12.desktop-ui/43.data-persistence-part2",{"title":1617,"path":1618,"stem":1619},"Тестування ViewModels","/csharp/desktop-ui/viewmodel-testing","01.csharp/12.desktop-ui/44.viewmodel-testing",{"title":1621,"path":1622,"stem":1623},"Avalonia Headless Testing — тестування UI без вікон","/csharp/desktop-ui/44a.avalonia-headless-testing","01.csharp/12.desktop-ui/44a.avalonia-headless-testing",{"title":1625,"path":1626,"stem":1627},"Кросплатформна розробка з Avalonia","/csharp/desktop-ui/avalonia-cross-platform","01.csharp/12.desktop-ui/45.avalonia-cross-platform",{"title":1629,"path":1630,"stem":1631},"Пакування та розгортання Avalonia додатків","/csharp/desktop-ui/avalonia-packaging-deployment","01.csharp/12.desktop-ui/46.avalonia-packaging-deployment",{"title":1633,"path":1634,"stem":1635},"Розгортання WPF застосунків","/csharp/desktop-ui/wpf-packaging-deployment","01.csharp/12.desktop-ui/47.wpf-packaging-deployment",{"title":1637,"path":1638,"stem":1639},"C# & .NET: The Ultimate Roadmap","/csharp/roadmap","01.csharp/roadmap",{"title":1641,"icon":1642,"path":1643,"stem":1644,"children":1645,"page":59},"C++","i-devicon-cplusplus","/cpp","02.cpp",[1646,1650,1654,1658,1662,1666,1670,1674,1678,1681,1685,1689,1693,1697,1701,1705,1709,1713,1717,1721,1725,1729,1733,1737,1741,1745,1749,1753,1757,1761],{"title":1647,"path":1648,"stem":1649},"Вступ у програмування та алгоритми","/cpp/intro-algorithms","02.cpp/01.intro-algorithms",{"title":1651,"path":1652,"stem":1653},"Code Style: угоди про оформлення коду","/cpp/code-style","02.cpp/02.code-style",{"title":1655,"path":1656,"stem":1657},"Середовище розробки та перший проєкт","/cpp/ide-setup","02.cpp/03.ide-setup",{"title":1659,"path":1660,"stem":1661},"Вивід даних на екран","/cpp/data-output","02.cpp/04.data-output",{"title":1663,"path":1664,"stem":1665},"Типи даних, змінні та константи","/cpp/data-types-variables","02.cpp/05.data-types-variables",{"title":1667,"path":1668,"stem":1669},"Ввід даних з клавіатури","/cpp/data-input","02.cpp/06.data-input",{"title":1671,"path":1672,"stem":1673},"Оператори, перетворення типів та логічні операції","/cpp/operators-type-conversion","02.cpp/07.operators-type-conversion",{"title":1675,"path":1676,"stem":1677},"Цикли","/cpp/loops","02.cpp/08.loops",{"title":32,"path":1679,"stem":1680},"/cpp/arrays","02.cpp/09.arrays",{"title":1682,"path":1683,"stem":1684},"Алгоритми сортування та аналіз складності","/cpp/sorting","02.cpp/10.sorting",{"title":1686,"path":1687,"stem":1688},"Алгоритми пошуку","/cpp/searching","02.cpp/11.searching",{"title":1690,"path":1691,"stem":1692},"Функції: основи","/cpp/functions-basics","02.cpp/12.functions-basics",{"title":1694,"path":1695,"stem":1696},"Функції: прототипи, область видимості та додаткові можливості","/cpp/functions-scope","02.cpp/13.functions-scope",{"title":1698,"path":1699,"stem":1700},"Функції: перевантаження та шаблони","/cpp/functions-overloading-templates","02.cpp/14.functions-overloading-templates",{"title":1702,"path":1703,"stem":1704},"Вказівники: основи","/cpp/pointers-basics","02.cpp/15.pointers-basics",{"title":1706,"path":1707,"stem":1708},"Посилання (References)","/cpp/references","02.cpp/16.references",{"title":1710,"path":1711,"stem":1712},"Вказівники, const і масиви","/cpp/pointers-const-arrays","02.cpp/17.pointers-const-arrays",{"title":1714,"path":1715,"stem":1716},"Адресна арифметика","/cpp/pointer-arithmetic","02.cpp/18.pointer-arithmetic",{"title":1718,"path":1719,"stem":1720},"Динамічна пам'ять","/cpp/dynamic-memory","02.cpp/19.dynamic-memory",{"title":1722,"path":1723,"stem":1724},"Вказівники типу void","/cpp/void-pointers","02.cpp/20.void-pointers",{"title":1726,"path":1727,"stem":1728},"Вказівники на вказівники","/cpp/pointers-to-pointers","02.cpp/21.pointers-to-pointers",{"title":1730,"path":1731,"stem":1732},"Оператор доступу до членів через вказівник (->)","/cpp/member-access-operator","02.cpp/22.member-access-operator",{"title":1734,"path":1735,"stem":1736},"Цикл for-each (Range-based for)","/cpp/foreach-loop","02.cpp/23.foreach-loop",{"title":1738,"path":1739,"stem":1740},"Вказівники на функції","/cpp/function-pointers","02.cpp/24.function-pointers",{"title":1742,"path":1743,"stem":1744},"Лямбда-вирази","/cpp/lambdas","02.cpp/25.lambdas",{"title":1746,"path":1747,"stem":1748},"Лямбда-захоплення","/cpp/lambda-captures","02.cpp/26.lambda-captures",{"title":1750,"path":1751,"stem":1752},"Еліпсис","/cpp/ellipsis","02.cpp/27.ellipsis",{"title":1754,"path":1755,"stem":1756},"Аргументи командного рядка","/cpp/command-line-arguments","02.cpp/28.command-line-arguments",{"title":1758,"path":1759,"stem":1760},"Перерахування (enum)","/cpp/enum","02.cpp/29.enum",{"title":1762,"path":1763,"stem":1764},"План навчання: Курс C++ — Продовження (Статті 29–60+)","/cpp/curriculum-plan","02.cpp/curriculum-plan",{"title":1766,"icon":1767,"path":1768,"stem":1769,"children":1770,"page":59},"JavaScript","i-devicon-javascript","/javascript","03.javascript",[1771,1797,1851,1873,2177,2215],{"title":1772,"icon":1773,"path":1774,"stem":1775,"children":1776,"page":59},"Events","i-lucide-mouse-pointer-click","/javascript/events","03.javascript/01.events",[1777,1781,1785,1789,1793],{"title":1778,"path":1779,"stem":1780},"Вступ до подій браузера","/javascript/events/intro","03.javascript/01.events/01.intro",{"title":1782,"path":1783,"stem":1784},"Бульбашковий механізм (Bubbling) та занурення (Capturing)","/javascript/events/bubbling-capturing","03.javascript/01.events/02.bubbling-capturing",{"title":1786,"path":1787,"stem":1788},"Делегування подій (Event Delegation)","/javascript/events/delegate-events","03.javascript/01.events/03.delegate-events",{"title":1790,"path":1791,"stem":1792},"Типові дії браузера та preventDefault()","/javascript/events/prevent-default","03.javascript/01.events/04.prevent-default",{"title":1794,"path":1795,"stem":1796},"Запуск користувацьких подій (Custom Events)","/javascript/events/custom-events","03.javascript/01.events/05.custom-events",{"title":1798,"icon":1799,"path":1800,"stem":1801,"children":1802,"page":59},"Network","i-lucide-globe","/javascript/network","03.javascript/02.network",[1803,1807,1811,1815,1819,1823,1827,1831,1835,1839,1843,1847],{"title":1804,"path":1805,"stem":1806},"Fetch API - Сучасний підхід до HTTP-запитів","/javascript/network/01-fetch-api","03.javascript/02.network/01-fetch-api",{"title":1808,"path":1809,"stem":1810},"FormData - Робота з формами та файлами","/javascript/network/02-formdata","03.javascript/02.network/02-formdata",{"title":1812,"path":1813,"stem":1814},"Відстеження прогресу завантаження","/javascript/network/03-download-progress","03.javascript/02.network/03-download-progress",{"title":1816,"path":1817,"stem":1818},"Переривання fetch-запитів","/javascript/network/04-abort-requests","03.javascript/02.network/04-abort-requests",{"title":1820,"path":1821,"stem":1822},"CORS - Запити між різними джерелами","/javascript/network/05-cors","03.javascript/02.network/05-cors",{"title":1824,"path":1825,"stem":1826},"Fetch API - Повний довідник опцій","/javascript/network/06-fetch-options","03.javascript/02.network/06-fetch-options",{"title":1828,"path":1829,"stem":1830},"URL Objects - Робота з посиланнями","/javascript/network/07-url-objects","03.javascript/02.network/07-url-objects",{"title":1832,"path":1833,"stem":1834},"XMLHttpRequest - AJAX та низькорівневі запити","/javascript/network/08-xmlhttprequest","03.javascript/02.network/08-xmlhttprequest",{"title":1836,"path":1837,"stem":1838},"Відновлюване завантаження файлів","/javascript/network/09-resumable-upload","03.javascript/02.network/09-resumable-upload",{"title":1840,"path":1841,"stem":1842},"Cookies, document.cookie та світ після \"Cookiepocalypse\"","/javascript/network/10-cookies","03.javascript/02.network/10-cookies",{"title":1844,"path":1845,"stem":1846},"js-cookie: Керування Cookies без Болю","/javascript/network/11-js-cookie","03.javascript/02.network/11-js-cookie",{"title":1848,"path":1849,"stem":1850},"Axios: Потужний HTTP-клієнт для JavaScript","/javascript/network/12-axios","03.javascript/02.network/12-axios",{"title":1852,"icon":1853,"path":1854,"stem":1855,"children":1856,"page":59},"Bom","i-lucide-monitor","/javascript/bom","03.javascript/03.bom",[1857,1861,1865,1869],{"title":1858,"path":1859,"stem":1860},"LocalStorage, SessionStorage та patterns збереження даних","/javascript/bom/01-localstorage","03.javascript/03.bom/01-localstorage",{"title":1862,"path":1863,"stem":1864},"Location Object - Керування адресою сторінки","/javascript/bom/02-location-object","03.javascript/03.bom/02-location-object",{"title":1866,"path":1867,"stem":1868},"History API - Керування історією браузера","/javascript/bom/03-history-api","03.javascript/03.bom/03-history-api",{"title":1870,"path":1871,"stem":1872},"Navigator Object - Ідентифікація та Можливості Пристрою","/javascript/bom/04-navigator-object","03.javascript/03.bom/04-navigator-object",{"title":1874,"icon":1875,"path":1876,"stem":1877,"children":1878},"React","i-devicon-react","/javascript/react","03.javascript/04.react/index",[1879,1880,1884,1888,1892,1896,1959,1994,2146],{"title":1874,"path":1876,"stem":1877},{"title":1881,"path":1882,"stem":1883},"Робота з Формами в React","/javascript/react/react-forms","03.javascript/04.react/01.react-forms",{"title":1885,"path":1886,"stem":1887},"React Hook Form: Професійна Робота з Формами","/javascript/react/react-hook-form","03.javascript/04.react/02.react-hook-form",{"title":1889,"path":1890,"stem":1891},"React Hook Form: Глибоке Розуміння Архітектури та Оптимізації","/javascript/react/react-hook-form-new","03.javascript/04.react/02.react-hook-form-new",{"title":1893,"path":1894,"stem":1895},"Axios та React: Професійна Архітектура Запитів","/javascript/react/data-fetching-axios","03.javascript/04.react/03.data-fetching-axios",{"title":1897,"icon":132,"path":1898,"stem":1899,"children":1900},"Tanstack Query","/javascript/react/tanstack-query","03.javascript/04.react/04.tanstack-query/index",[1901,1903,1907,1911,1915,1919,1923,1927,1931,1935,1939,1943,1947,1951,1955],{"title":1902,"path":1898,"stem":1899},"TanStack Query: Майстерність Керування Станом Сервера",{"title":1904,"path":1905,"stem":1906},"Парадигма Server State: Чому useEffect недостатньо","/javascript/react/tanstack-query/server-state-paradigm","03.javascript/04.react/04.tanstack-query/01.server-state-paradigm",{"title":1908,"path":1909,"stem":1910},"Встановлення та Налаштування: Фундамент","/javascript/react/tanstack-query/installation-and-devtools","03.javascript/04.react/04.tanstack-query/02.installation-and-devtools",{"title":1912,"path":1913,"stem":1914},"Основи Запитів та Магія Ключів","/javascript/react/tanstack-query/query-basics-and-keys","03.javascript/04.react/04.tanstack-query/03.query-basics-and-keys",{"title":1916,"path":1917,"stem":1918},"Синхронізація Даних: Життєвий Цикл Запиту","/javascript/react/tanstack-query/data-synchronization","03.javascript/04.react/04.tanstack-query/04.data-synchronization",{"title":1920,"path":1921,"stem":1922},"Мутації та Інвалідація: Зміна Даних","/javascript/react/tanstack-query/mutations-and-invalidation","03.javascript/04.react/04.tanstack-query/05.mutations-and-invalidation",{"title":1924,"path":1925,"stem":1926},"Оптимістичні Оновлення: Швидше за Світло","/javascript/react/tanstack-query/optimistic-updates","03.javascript/04.react/04.tanstack-query/06.optimistic-updates",{"title":1928,"path":1929,"stem":1930},"Пагінація та Infinite Scroll","/javascript/react/tanstack-query/pagination-and-load-more","03.javascript/04.react/04.tanstack-query/07.pagination-and-load-more",{"title":1932,"path":1933,"stem":1934},"Просунуті Патерни та Оптимізація","/javascript/react/tanstack-query/advanced-patterns","03.javascript/04.react/04.tanstack-query/08.advanced-patterns",{"title":1936,"path":1937,"stem":1938},"Архітектура та Best Practices","/javascript/react/tanstack-query/architecture-and-best-practices","03.javascript/04.react/04.tanstack-query/09.architecture-and-best-practices",{"title":1940,"path":1941,"stem":1942},"Server-Side Rendering (SSR) та Гідратація","/javascript/react/tanstack-query/server-side-rendering","03.javascript/04.react/04.tanstack-query/10.server-side-rendering",{"title":1944,"path":1945,"stem":1946},"Стратегії Тестування","/javascript/react/tanstack-query/testing-strategies","03.javascript/04.react/04.tanstack-query/11.testing-strategies",{"title":1948,"path":1949,"stem":1950},"Аутентифікація та Обробка Помилок","/javascript/react/tanstack-query/authentication-and-errors","03.javascript/04.react/04.tanstack-query/12.authentication-and-errors",{"title":1952,"path":1953,"stem":1954},"React Suspense та Майбутнє","/javascript/react/tanstack-query/react-suspense","03.javascript/04.react/04.tanstack-query/13.react-suspense",{"title":1956,"path":1957,"stem":1958},"Глибоке Занурення в Продуктивність","/javascript/react/tanstack-query/performance-deep-dive","03.javascript/04.react/04.tanstack-query/14.performance-deep-dive",{"title":1960,"icon":1875,"path":1961,"stem":1962,"children":1963},"React Router","/javascript/react/react-router","03.javascript/04.react/05.react-router/index",[1964,1966,1970,1974,1978,1982,1986,1990],{"title":1965,"path":1961,"stem":1962},"React Router: Навігаційна система сучасного вебу",{"title":1967,"path":1968,"stem":1969},"Налаштування та Базовий Роутинг","/javascript/react/react-router/setup-and-basic-routing","03.javascript/04.react/05.react-router/01.setup-and-basic-routing",{"title":1971,"path":1972,"stem":1973},"Динамічна Навігація","/javascript/react/react-router/navigation-and-links","03.javascript/04.react/05.react-router/02.navigation-and-links",{"title":1975,"path":1976,"stem":1977},"Вкладені Маршрути та Макети","/javascript/react/react-router/nested-routes-and-layouts","03.javascript/04.react/05.react-router/03.nested-routes-and-layouts",{"title":1979,"path":1980,"stem":1981},"Динамічні Маршрути та Параметри","/javascript/react/react-router/dynamic-routing","03.javascript/04.react/05.react-router/04.dynamic-routing",{"title":1983,"path":1984,"stem":1985},"Data APIs: Loaders та Actions","/javascript/react/react-router/data-loading","03.javascript/04.react/05.react-router/05.data-loading",{"title":1987,"path":1988,"stem":1989},"Просунуті Патерни","/javascript/react/react-router/advanced-patterns","03.javascript/04.react/05.react-router/06.advanced-patterns",{"title":1991,"path":1992,"stem":1993},"Legacy Routing: Компонентний підхід","/javascript/react/react-router/legacy-routing","03.javascript/04.react/05.react-router/07.legacy-routing",{"title":1995,"icon":132,"path":1996,"stem":1997,"children":1998},"Redux","/javascript/react/redux","03.javascript/04.react/06.redux/index",[1999,2001,2017,2046,2055,2076,2092,2121],{"title":2000,"path":1996,"stem":1997},"Redux: Еволюція управління станом",{"title":14,"icon":15,"path":2002,"stem":2003,"children":2004,"page":59},"/javascript/react/redux/fundamentals","03.javascript/04.react/06.redux/01.fundamentals",[2005,2009,2013],{"title":2006,"path":2007,"stem":2008},"Вступ до State Management","/javascript/react/redux/fundamentals/intro-state-management","03.javascript/04.react/06.redux/01.fundamentals/01.intro-state-management",{"title":2010,"path":2011,"stem":2012},"Філософія Redux та Три Принципи","/javascript/react/redux/fundamentals/redux-philosophy","03.javascript/04.react/06.redux/01.fundamentals/02.redux-philosophy",{"title":2014,"path":2015,"stem":2016},"Чисті функції та Іммутабельність","/javascript/react/redux/fundamentals/pure-functions-immutability","03.javascript/04.react/06.redux/01.fundamentals/03.pure-functions-immutability",{"title":2018,"icon":132,"path":2019,"stem":2020,"children":2021,"page":59},"Classic Redux","/javascript/react/redux/classic-redux","03.javascript/04.react/06.redux/02.classic-redux",[2022,2026,2030,2034,2038,2042],{"title":2023,"path":2024,"stem":2025},"Створення Store (Classic Redux)","/javascript/react/redux/classic-redux/store-setup","03.javascript/04.react/06.redux/02.classic-redux/01.store-setup",{"title":2027,"path":2028,"stem":2029},"Actions, Constants та Action Creators","/javascript/react/redux/classic-redux/actions-constants","03.javascript/04.react/06.redux/02.classic-redux/02.actions-constants",{"title":2031,"path":2032,"stem":2033},"Логіка Reducers","/javascript/react/redux/classic-redux/reducers","03.javascript/04.react/06.redux/02.classic-redux/03.reducers",{"title":2035,"path":2036,"stem":2037},"Комбінування Reducers (Root Reducer)","/javascript/react/redux/classic-redux/data-flow","03.javascript/04.react/06.redux/02.classic-redux/04.data-flow",{"title":2039,"path":2040,"stem":2041},"Підключення до React (React-Redux)","/javascript/react/redux/classic-redux/react-redux-connection","03.javascript/04.react/06.redux/02.classic-redux/05.react-redux-connection",{"title":2043,"path":2044,"stem":2045},"Middleware та Асинхронність (Redux Thunk)","/javascript/react/redux/classic-redux/middleware-thunk","03.javascript/04.react/06.redux/02.classic-redux/06.middleware-thunk",{"title":2047,"icon":132,"path":2048,"stem":2049,"children":2050,"page":59},"Transition To Rtk","/javascript/react/redux/transition-to-rtk","03.javascript/04.react/06.redux/03.transition-to-rtk",[2051],{"title":2052,"path":2053,"stem":2054},"Проблеми класичного Redux","/javascript/react/redux/transition-to-rtk/problems-with-classic","03.javascript/04.react/06.redux/03.transition-to-rtk/01.problems-with-classic",{"title":2056,"icon":132,"path":2057,"stem":2058,"children":2059,"page":59},"Redux Toolkit","/javascript/react/redux/redux-toolkit","03.javascript/04.react/06.redux/04.redux-toolkit",[2060,2064,2068,2072],{"title":2061,"path":2062,"stem":2063},"Налаштування Store з configureStore","/javascript/react/redux/redux-toolkit/configure-store","03.javascript/04.react/06.redux/04.redux-toolkit/01.configure-store",{"title":2065,"path":2066,"stem":2067},"createSlice: Революція в Redux","/javascript/react/redux/redux-toolkit/create-slice","03.javascript/04.react/06.redux/04.redux-toolkit/02.create-slice",{"title":2069,"path":2070,"stem":2071},"Асинхронність з createAsyncThunk","/javascript/react/redux/redux-toolkit/async-thunks","03.javascript/04.react/06.redux/04.redux-toolkit/03.async-thunks",{"title":2073,"path":2074,"stem":2075},"04. Entity Adapter: Керування нормалізованим станом","/javascript/react/redux/redux-toolkit/entity-adapter","03.javascript/04.react/06.redux/04.redux-toolkit/04.entity-adapter",{"title":2077,"icon":92,"path":2078,"stem":2079,"children":2080,"page":59},"Advanced","/javascript/react/redux/advanced","03.javascript/04.react/06.redux/05.advanced",[2081,2085,2089],{"title":2082,"path":2083,"stem":2084},"Мемоізація та Селектори: Повний Гайд по Reselect","/javascript/react/redux/advanced/selectors-reselect","03.javascript/04.react/06.redux/05.advanced/01.selectors-reselect",{"title":2086,"path":2087,"stem":2088},"RTK Query: Архітектура Серверного Кешу","/javascript/react/redux/advanced/rtk-query-intro","03.javascript/04.react/06.redux/05.advanced/02.rtk-query-intro",{"title":1936,"path":2090,"stem":2091},"/javascript/react/redux/advanced/architecture-best-practices","03.javascript/04.react/06.redux/05.advanced/03.architecture-best-practices",{"title":2093,"icon":132,"path":2094,"stem":2095,"children":2096,"page":59},"Project Kanban","/javascript/react/redux/project-kanban","03.javascript/04.react/06.redux/06.project-kanban",[2097,2101,2105,2109,2113,2117],{"title":2098,"path":2099,"stem":2100},"Проєкт: Kanban Board (Trello Clone)","/javascript/react/redux/project-kanban/project-overview","03.javascript/04.react/06.redux/06.project-kanban/01.project-overview",{"title":2102,"path":2103,"stem":2104},"Налаштування та Типізація","/javascript/react/redux/project-kanban/setup-and-types","03.javascript/04.react/06.redux/06.project-kanban/02.setup-and-types",{"title":2106,"path":2107,"stem":2108},"Board Slice: Серце Дошки","/javascript/react/redux/project-kanban/board-slice","03.javascript/04.react/06.redux/06.project-kanban/03.board-slice",{"title":2110,"path":2111,"stem":2112},"Логіка Drag & Drop","/javascript/react/redux/project-kanban/drag-and-drop-logic","03.javascript/04.react/06.redux/06.project-kanban/04.drag-and-drop-logic",{"title":2114,"path":2115,"stem":2116},"Інтеграція з RTK Query","/javascript/react/redux/project-kanban/rtk-query-integration","03.javascript/04.react/06.redux/06.project-kanban/05.rtk-query-integration",{"title":2118,"path":2119,"stem":2120},"Optimistic Updates","/javascript/react/redux/project-kanban/optimistic-updates","03.javascript/04.react/06.redux/06.project-kanban/06.optimistic-updates",{"title":2122,"icon":132,"path":2123,"stem":2124,"children":2125,"page":59},"Testing","/javascript/react/redux/testing","03.javascript/04.react/06.redux/07.testing",[2126,2130,2134,2138,2142],{"title":2127,"path":2128,"stem":2129},"Тестування Redux","/javascript/react/redux/testing/intro-testing","03.javascript/04.react/06.redux/07.testing/01.intro-testing",{"title":2131,"path":2132,"stem":2133},"Тестування Reducers","/javascript/react/redux/testing/testing-reducers","03.javascript/04.react/06.redux/07.testing/02.testing-reducers",{"title":2135,"path":2136,"stem":2137},"Тестування Селекторів","/javascript/react/redux/testing/testing-selectors","03.javascript/04.react/06.redux/07.testing/03.testing-selectors",{"title":2139,"path":2140,"stem":2141},"Тестування Компонентів (Integration)","/javascript/react/redux/testing/testing-components","03.javascript/04.react/06.redux/07.testing/04.testing-components",{"title":2143,"path":2144,"stem":2145},"Тестування Async Thunks","/javascript/react/redux/testing/testing-thunks","03.javascript/04.react/06.redux/07.testing/05.testing-thunks",{"title":2147,"icon":132,"path":2148,"stem":2149,"children":2150},"Ui Libraries","/javascript/react/ui-libraries","03.javascript/04.react/07.ui-libraries/index",[2151,2153,2157,2161,2165,2169,2173],{"title":2152,"path":2148,"stem":2149},"UI Бібліотеки в React",{"title":2154,"path":2155,"stem":2156},"Вступ до UI Бібліотек: Навіщо Винаходити Велосипед Двічі?","/javascript/react/ui-libraries/introduction-to-ui-libraries","03.javascript/04.react/07.ui-libraries/01.introduction-to-ui-libraries",{"title":2158,"path":2159,"stem":2160},"Філософія shadcn/ui: \"Not a Component Library\"","/javascript/react/ui-libraries/shadcn-philosophy","03.javascript/04.react/07.ui-libraries/02.shadcn-philosophy",{"title":2162,"path":2163,"stem":2164},"Установка та Налаштування shadcn/ui","/javascript/react/ui-libraries/shadcn-installation","03.javascript/04.react/07.ui-libraries/03.shadcn-installation",{"title":2166,"path":2167,"stem":2168},"Базові Компоненти shadcn/ui: Фундамент Інтерфейсу","/javascript/react/ui-libraries/shadcn-components-basics","03.javascript/04.react/07.ui-libraries/04.shadcn-components-basics",{"title":2170,"path":2171,"stem":2172},"Компоненти Форм: Побудова Інтерактивних Form","/javascript/react/ui-libraries/shadcn-components-forms","03.javascript/04.react/07.ui-libraries/05.shadcn-components-forms",{"title":2174,"path":2175,"stem":2176},"Складні Компоненти: Dialog, Dropdown, Table та Command","/javascript/react/ui-libraries/shadcn-components-advanced","03.javascript/04.react/07.ui-libraries/06.shadcn-components-advanced",{"title":2178,"icon":2179,"path":2180,"stem":2181,"children":2182,"page":59},"TypeScript","i-devicon-typescript","/javascript/typescript","03.javascript/05.typescript",[2183,2187,2191,2195,2199,2203,2207,2211],{"title":2184,"path":2185,"stem":2186},"TypeScript: Броня для вашого коду","/javascript/typescript/intro-and-basic-types","03.javascript/05.typescript/01.intro-and-basic-types",{"title":2188,"path":2189,"stem":2190},"Майстерність Моделювання Даних: Інтерфейси та Просунуті Типи","/javascript/typescript/interfaces-and-advanced-types","03.javascript/05.typescript/02.interfaces-and-advanced-types",{"title":2192,"path":2193,"stem":2194},"Алхімія Типів: Generics та Utility Types","/javascript/typescript/generics-and-utilities","03.javascript/05.typescript/03.generics-and-utilities",{"title":2196,"path":2197,"stem":2198},"Архітектура та Шаблони: Класи в TypeScript","/javascript/typescript/classes-and-oop","03.javascript/05.typescript/04.classes-and-oop",{"title":2200,"path":2201,"stem":2202},"Продакшн та Екосистема: Advanced Config & Workflow","/javascript/typescript/advanced-patterns-and-config","03.javascript/05.typescript/05.advanced-patterns-and-config",{"title":2204,"path":2205,"stem":2206},"TypeScript у світі React","/javascript/typescript/react-basics","03.javascript/05.typescript/06.react-basics",{"title":2208,"path":2209,"stem":2210},"React + TypeScript: Продвинуті патерни","/javascript/typescript/react-advanced","03.javascript/05.typescript/07.react-advanced",{"title":2212,"path":2213,"stem":2214},"React + TypeScript: Екосистема та бібліотеки","/javascript/typescript/react-ecosystem","03.javascript/05.typescript/08.react-ecosystem",{"title":2216,"path":2217,"stem":2218},"Atomic Design","/javascript/atomic-design","03.javascript/2.atomic-design",{"title":2220,"icon":2221,"path":2222,"stem":2223,"children":2224,"page":59},"Java","i-devicon-java","/java","04.java",[2225,2228,2231,2235,2239,2243,2247],{"title":162,"path":2226,"stem":2227},"/java/data-mapper-part1","04.java/01.data-mapper-part1",{"title":166,"path":2229,"stem":2230},"/java/data-mapper-part2","04.java/02.data-mapper-part2",{"title":2232,"path":2233,"stem":2234},"Service Layer: Організація бізнес-логіки","/java/service-layer","04.java/03.service-layer",{"title":2236,"path":2237,"stem":2238},"Rich Domain Model та State Pattern","/java/rich-domain-model","04.java/04.rich-domain-model",{"title":2240,"path":2241,"stem":2242},"Патерни для складної бізнес-логіки","/java/business-logic-patterns","04.java/05.business-logic-patterns",{"title":2244,"path":2245,"stem":2246},"Обробка помилок та валідація","/java/error-handling-validation","04.java/06.error-handling-validation",{"title":2248,"path":2249,"stem":2250,"children":2251,"page":59},"Проектування баз даних","/java/pr2","04.java/pr2",[2252,2256,2260,2264,2268,2272,2276,2280,2284,2288,2292,2296,2300,2304,2308,2312,2316,2320,2324,2328,2332,2336,2340,2344,2348],{"title":2253,"path":2254,"stem":2255},"Концептуальне моделювання: Мистецтво розуміння предметної області","/java/pr2/conceptual-modeling","04.java/pr2/01.conceptual-modeling",{"title":2257,"path":2258,"stem":2259},"Логічне моделювання: Від бізнес-ідей до структур даних","/java/pr2/logical-modeling","04.java/pr2/02.logical-modeling",{"title":2261,"path":2262,"stem":2263},"Нормалізація: Гігієна даних та боротьба з аномаліями","/java/pr2/normalization","04.java/pr2/03.normalization",{"title":2265,"path":2266,"stem":2267},"Фізична схема: Від абстракції до DDL","/java/pr2/physical-schema","04.java/pr2/04.physical-schema",{"title":2269,"path":2270,"stem":2271},"Архітектурна класифікація таблиць","/java/pr2/table-classification","04.java/pr2/05.table-classification",{"title":2273,"path":2274,"stem":2275},"Database Migrations: Версіонування схеми з Flyway","/java/pr2/database-migrations","04.java/pr2/06.database-migrations",{"title":2277,"path":2278,"stem":2279},"А що, якби це була не реляційна БД?","/java/pr2/beyond-relational","04.java/pr2/07.beyond-relational",{"title":2281,"path":2282,"stem":2283},"Object-Relational Impedance Mismatch: Два світи, що не хочуть дружити","/java/pr2/impedance-mismatch","04.java/pr2/09.impedance-mismatch",{"title":2285,"path":2286,"stem":2287},"JDBC: Перший контакт із базою даних","/java/pr2/jdbc-fundamentals","04.java/pr2/10.jdbc-fundamentals",{"title":2289,"path":2290,"stem":2291},"Якість коду: Spotless, SpotBugs та SonarQube","/java/pr2/10a.code-quality","04.java/pr2/10a.code-quality",{"title":2293,"path":2294,"stem":2295},"Connection Pool: Патерн Object Pool для JDBC-з'єднань","/java/pr2/connection-pool","04.java/pr2/11.connection-pool",{"title":2297,"path":2298,"stem":2299},"Row Data Gateway: Об'єкт як обгортка рядка таблиці","/java/pr2/row-data-gateway","04.java/pr2/12.row-data-gateway",{"title":2301,"path":2302,"stem":2303},"Table Data Gateway: Фасад таблиці як архітектурний відступ","/java/pr2/table-data-gateway","04.java/pr2/13.table-data-gateway",{"title":2305,"path":2306,"stem":2307},"Repository + Data Mapper: Правильна шарова архітектура з JDBC","/java/pr2/repository-data-mapper","04.java/pr2/14.repository-data-mapper",{"title":2309,"path":2310,"stem":2311},"Identity Map: Кешування сутностей у рамках сесії","/java/pr2/identity-map","04.java/pr2/15.identity-map",{"title":2313,"path":2314,"stem":2315},"Unit of Work: Відстеження змін і координація JDBC-транзакцій","/java/pr2/unit-of-work","04.java/pr2/16.unit-of-work",{"title":2317,"path":2318,"stem":2319},"Strategy: Замінювані SQL-стратегії для підтримки різних СУБД","/java/pr2/strategy-sql","04.java/pr2/17.strategy-sql",{"title":2321,"path":2322,"stem":2323},"Proxy: Lazy Loading для One-To-Many колекцій","/java/pr2/proxy-lazy-loading","04.java/pr2/18.proxy-lazy-loading",{"title":2325,"path":2326,"stem":2327},"Generic Repository через Java Reflection: анотації та динамічний SQL","/java/pr2/generic-repository-reflection","04.java/pr2/19.generic-repository-reflection",{"title":2329,"path":2330,"stem":2331},"Specification Pattern: Композиція бізнес-правил для складних запитів","/java/pr2/specification-pattern","04.java/pr2/20.specification-pattern",{"title":2333,"path":2334,"stem":2335},"Розширені можливості Specification Pattern: підзапити, агрегації та гібридний підхід","/java/pr2/20a.advanced-specifications","04.java/pr2/20a.advanced-specifications",{"title":2337,"path":2338,"stem":2339},"Асинхронність у JDBC: Від блокуючих викликів до CompletableFuture","/java/pr2/asynchronous-jdbc","04.java/pr2/21.asynchronous-jdbc",{"title":2341,"path":2342,"stem":2343},"Інтеграційне тестування JDBC-репозиторіїв: Embedded H2 та патерн AAA","/java/pr2/integration-testing-h2","04.java/pr2/22.integration-testing-h2",{"title":2345,"path":2346,"stem":2347},"Testcontainers: Тестування з реальною PostgreSQL у Docker-контейнерах","/java/pr2/integration-testing-testcontainers","04.java/pr2/23.integration-testing-testcontainers",{"title":2349,"path":2350,"stem":2351},"Модуль \"Проектування реляційних баз даних\" для 04.java/pr2","/java/pr2/implementation_plan","04.java/pr2/implementation_plan",{"title":2353,"icon":2354,"path":2355,"stem":2356,"children":2357,"page":59},"Бази даних","i-lucide-database","/databases","06.databases",[2358,2388,2411,2448,2477,2495,2529,2541,2550],{"title":2359,"icon":2360,"path":2361,"stem":2362,"children":2363,"page":59},"Intro","i-lucide-play","/databases/intro","06.databases/01.intro",[2364,2368,2372,2376,2380,2384],{"title":2365,"path":2366,"stem":2367},"Введення в теорію баз даних","/databases/intro/introduction-to-databases","06.databases/01.intro/01.introduction-to-databases",{"title":2369,"path":2370,"stem":2371},"Реляційна модель даних","/databases/intro/relational-model-theory","06.databases/01.intro/02.relational-model-theory",{"title":2373,"path":2374,"stem":2375},"ER-моделювання","/databases/intro/er-modeling","06.databases/01.intro/03.er-modeling",{"title":2377,"path":2378,"stem":2379},"Логічне проектування БД","/databases/intro/logical-schema","06.databases/01.intro/04.logical-schema",{"title":2381,"path":2382,"stem":2383},"Класифікація таблиць","/databases/intro/table-classification","06.databases/01.intro/05.table-classification",{"title":2385,"path":2386,"stem":2387},"PlantUML для баз даних","/databases/intro/plantuml-diagrams","06.databases/01.intro/06.plantuml-diagrams",{"title":2389,"icon":2354,"path":2390,"stem":2391,"children":2392,"page":59},"MS SQL Server Start","/databases/ms-sql-server-start","06.databases/02.ms-sql-server-start",[2393,2397,2403,2407],{"title":2394,"path":2395,"stem":2396},"Типи даних у MS SQL Server","/databases/ms-sql-server-start/data-types","06.databases/02.ms-sql-server-start/01.data-types",{"title":2398,"path":2399,"stem":2400,"children":2401},"Індекси у MS SQL Server","/databases/ms-sql-server-start/sql-indexes","06.databases/02.ms-sql-server-start/02.sql-indexes",[2402],{"title":2398,"path":2399,"stem":2400},{"title":2404,"path":2405,"stem":2406},"Системні бази даних MS SQL Server","/databases/ms-sql-server-start/system-databases","06.databases/02.ms-sql-server-start/03.system-databases",{"title":2408,"path":2409,"stem":2410},"Огляд мови SQL та запитів","/databases/ms-sql-server-start/sql-queries-overview","06.databases/02.ms-sql-server-start/04.sql-queries-overview",{"title":2412,"icon":2354,"path":2413,"stem":2414,"children":2415,"page":59},"SQL","/databases/sql","06.databases/03.sql",[2416,2420,2424,2428,2432,2436,2440,2444],{"title":2417,"path":2418,"stem":2419},"Налаштування демонстраційної бази даних","/databases/sql/sample-database-setup","06.databases/03.sql/00.sample-database-setup",{"title":2421,"path":2422,"stem":2423},"DDL - Створення таблиць (CREATE TABLE)","/databases/sql/ddl-create-table","06.databases/03.sql/01.ddl-create-table",{"title":2425,"path":2426,"stem":2427},"DDL - Зміна та видалення таблиць (ALTER, DROP)","/databases/sql/ddl-alter-drop-table","06.databases/03.sql/02.ddl-alter-drop-table",{"title":2429,"path":2430,"stem":2431},"SELECT запити - Основи","/databases/sql/select-queries-fundamentals","06.databases/03.sql/03.select-queries-fundamentals",{"title":2433,"path":2434,"stem":2435},"SELECT запити - Розширені можливості","/databases/sql/select-queries-advanced","06.databases/03.sql/04.select-queries-advanced",{"title":2437,"path":2438,"stem":2439},"INSERT запити - Додавання даних","/databases/sql/insert-queries","06.databases/03.sql/05.insert-queries",{"title":2441,"path":2442,"stem":2443},"UPDATE та DELETE запити","/databases/sql/update-delete-queries","06.databases/03.sql/06.update-delete-queries",{"title":2445,"path":2446,"stem":2447},"Транзакції в SQL","/databases/sql/transactions","06.databases/03.sql/07.transactions",{"title":2449,"icon":2354,"path":2450,"stem":2451,"children":2452,"page":59},"Multi Table Databases","/databases/multi-table-databases","06.databases/04.multi-table-databases",[2453,2457,2461,2465,2469,2473],{"title":2454,"path":2455,"stem":2456},"Зв'язки та нормалізація БД","/databases/multi-table-databases/relationships-and-normalization","06.databases/04.multi-table-databases/00.relationships-and-normalization",{"title":2458,"path":2459,"stem":2460},"INNER JOIN - З'єднання таблиць","/databases/multi-table-databases/inner-join","06.databases/04.multi-table-databases/01.inner-join",{"title":2462,"path":2463,"stem":2464},"OUTER JOINs - LEFT, RIGHT, FULL","/databases/multi-table-databases/outer-joins","06.databases/04.multi-table-databases/02.outer-joins",{"title":2466,"path":2467,"stem":2468},"CROSS та SELF JOINs","/databases/multi-table-databases/cross-self-joins","06.databases/04.multi-table-databases/03.cross-self-joins",{"title":2470,"path":2471,"stem":2472},"Підзапити (Subqueries)","/databases/multi-table-databases/subqueries","06.databases/04.multi-table-databases/04.subqueries",{"title":2474,"path":2475,"stem":2476},"Агрегації з JOIN","/databases/multi-table-databases/aggregations-with-joins","06.databases/04.multi-table-databases/05.aggregations-with-joins",{"title":2478,"icon":2479,"path":2480,"stem":2481,"children":2482,"page":59},"Aggregate Functions","i-lucide-calculator","/databases/aggregate-functions","06.databases/05.aggregate-functions",[2483,2487,2491],{"title":2484,"path":2485,"stem":2486},"Функції агрегування в MS SQL Server","/databases/aggregate-functions/introduction-aggregate-functions","06.databases/05.aggregate-functions/01.introduction-aggregate-functions",{"title":2488,"path":2489,"stem":2490},"Групування даних в MS SQL Server","/databases/aggregate-functions/grouping-data","06.databases/05.aggregate-functions/02.grouping-data",{"title":2492,"path":2493,"stem":2494},"Підзапити з агрегатними функціями","/databases/aggregate-functions/subqueries-aggregates","06.databases/05.aggregate-functions/03.subqueries-aggregates",{"title":2496,"icon":2497,"path":2498,"stem":2499,"children":2500,"page":59},"Тригери та зберігаємі процедури","i-lucide-database-zap","/databases/triggers-stored-procedures","06.databases/07.triggers-stored-procedures",[2501,2505,2509,2513,2517,2521,2525],{"title":2502,"path":2503,"stem":2504},"DML-тригери","/databases/triggers-stored-procedures/dml-triggers","06.databases/07.triggers-stored-procedures/01.dml-triggers",{"title":2506,"path":2507,"stem":2508},"DDL-тригери","/databases/triggers-stored-procedures/ddl-triggers","06.databases/07.triggers-stored-procedures/02.ddl-triggers",{"title":2510,"path":2511,"stem":2512},"Transact-SQL розширення","/databases/triggers-stored-procedures/transact-sql-extensions","06.databases/07.triggers-stored-procedures/03.transact-sql-extensions",{"title":2514,"path":2515,"stem":2516},"Транзакції","/databases/triggers-stored-procedures/transactions","06.databases/07.triggers-stored-procedures/04.transactions",{"title":2518,"path":2519,"stem":2520},"Зберігаємі процедури","/databases/triggers-stored-procedures/stored-procedures","06.databases/07.triggers-stored-procedures/05.stored-procedures",{"title":2522,"path":2523,"stem":2524},"Користувацькі функції","/databases/triggers-stored-procedures/user-defined-functions","06.databases/07.triggers-stored-procedures/06.user-defined-functions",{"title":2526,"path":2527,"stem":2528},"Безпека баз даних","/databases/triggers-stored-procedures/security","06.databases/07.triggers-stored-procedures/08.security",{"title":2526,"icon":793,"path":2530,"stem":2531,"children":2532,"page":59},"/databases/security","06.databases/08.security",[2533,2537],{"title":2534,"path":2535,"stem":2536},"Вступ до безпеки баз даних","/databases/security/introduction","06.databases/08.security/01.introduction",{"title":2538,"path":2539,"stem":2540},"Системні представлення та метадані","/databases/security/system-views","06.databases/08.security/02.system-views",{"title":2542,"icon":2543,"path":2544,"stem":2545,"children":2546,"page":59},"Резервне копіювання та відновлення","i-lucide-database-backup","/databases/backup-recovery","06.databases/09.backup-recovery",[2547],{"title":2542,"path":2548,"stem":2549},"/databases/backup-recovery/backup-restore","06.databases/09.backup-recovery/01.backup-restore",{"title":2551,"icon":2552,"path":2553,"stem":2554,"children":2555,"page":59},"Повнотекстовий пошук","i-lucide-search","/databases/full-text-search","06.databases/10.full-text-search",[2556],{"title":2551,"path":2557,"stem":2558},"/databases/full-text-search/full-text-search","06.databases/10.full-text-search/01.full-text-search",{"title":2560,"icon":2561,"path":2562,"stem":2563,"children":2564,"page":59},"Tools","i-lucide-wrench","/tools","07.tools",[2565],{"title":2566,"icon":2567,"path":2568,"stem":2569,"children":2570},"Docker","i-simple-icons-docker","/tools/docker","07.tools/01.docker/index",[2571,2573,2577,2581,2585,2589,2593,2597,2601,2605,2609,2613,2617,2621,2625,2629,2633,2637],{"title":2572,"path":2568,"stem":2569},"Docker: від нуля до production",{"title":2574,"path":2575,"stem":2576},"Контейнеризація — від проблеми до рішення","/tools/docker/containerization-concept","07.tools/01.docker/01.containerization-concept",{"title":2578,"path":2579,"stem":2580},"Docker — що це і навіщо?","/tools/docker/docker-what-and-why","07.tools/01.docker/02.docker-what-and-why",{"title":2582,"path":2583,"stem":2584},"Архітектура Docker Engine","/tools/docker/docker-architecture","07.tools/01.docker/03.docker-architecture",{"title":2586,"path":2587,"stem":2588},"Встановлення Docker","/tools/docker/installation","07.tools/01.docker/04.installation",{"title":2590,"path":2591,"stem":2592},"Перший контейнер — docker run","/tools/docker/first-container","07.tools/01.docker/05.first-container",{"title":2594,"path":2595,"stem":2596},"Життєвий цикл контейнера","/tools/docker/container-lifecycle","07.tools/01.docker/06.container-lifecycle",{"title":2598,"path":2599,"stem":2600},"Docker Images — фундаментальні концепції","/tools/docker/docker-images-fundamentals","07.tools/01.docker/07.docker-images-fundamentals",{"title":2602,"path":2603,"stem":2604},"Dockerfile — основи","/tools/docker/dockerfile-basics","07.tools/01.docker/08.dockerfile-basics",{"title":2606,"path":2607,"stem":2608},"Dockerfile — просунуті техніки","/tools/docker/dockerfile-advanced","07.tools/01.docker/09.dockerfile-advanced",{"title":2610,"path":2611,"stem":2612},"Build Context та кешування шарів","/tools/docker/build-context-and-cache","07.tools/01.docker/10.build-context-and-cache",{"title":2614,"path":2615,"stem":2616},"Реєстри Docker-образів","/tools/docker/image-registries","07.tools/01.docker/11.image-registries",{"title":2618,"path":2619,"stem":2620},"Контейнеризація .NET додатків","/tools/docker/dotnet-containerization","07.tools/01.docker/12.dotnet-containerization",{"title":2622,"path":2623,"stem":2624},"Томи та збереження даних","/tools/docker/volumes-and-data","07.tools/01.docker/13.volumes-and-data",{"title":2626,"path":2627,"stem":2628},"Основи мережі в Docker","/tools/docker/networking-basics","07.tools/01.docker/14.networking-basics",{"title":2630,"path":2631,"stem":2632},"Змінні оточення та конфігурація","/tools/docker/environment-and-configuration","07.tools/01.docker/15.environment-and-configuration",{"title":2634,"path":2635,"stem":2636},"Docker Compose — оркестрація контейнерів","/tools/docker/docker-compose-basics","07.tools/01.docker/16.docker-compose-basics",{"title":2638,"path":2639,"stem":2640},"Docker Compose — Multi-Service застосунки","/tools/docker/compose-multi-service","07.tools/01.docker/17.compose-multi-service",{"title":2642,"icon":2643,"path":2644,"stem":2645,"children":2646,"page":59},"Software Engineering","i-lucide-code-2","/software-engineering","09.software-engineering",[2647,2651,2655,2659,2663,2667,2671,2675,2679,2683,2687],{"title":2648,"path":2649,"stem":2650},"1. Аналіз предметної області. Експертні знання та складність","/software-engineering/intro.subdomains","09.software-engineering/01.intro.subdomains",{"title":2652,"path":2653,"stem":2654},"2. Обмежені контексти. Інтеграція обмежених контекстів","/software-engineering/integrating-limited-contexts","09.software-engineering/02.integrating-limited-contexts",{"title":2656,"path":2657,"stem":2658},"3. Реалізація простої бізнес-логіки","/software-engineering/simple","09.software-engineering/03.simple",{"title":2660,"path":2661,"stem":2662},"4. Опрацювання складної бізнес-логіки","/software-engineering/complex-business-logic","09.software-engineering/04.complex-business-logic",{"title":2664,"path":2665,"stem":2666},"5. Моделювання фактора часу. Подієво-орієнтована архітектура.","/software-engineering/modelling-the-time-factor","09.software-engineering/05.modelling-the-time-factor",{"title":2668,"path":2669,"stem":2670},"6. Архітектурні патерни","/software-engineering/architectural-patterns","09.software-engineering/06.architectural-patterns",{"title":2672,"path":2673,"stem":2674},"Паттерни взаємодії","/software-engineering/patterns-of-interaction","09.software-engineering/07.patterns-of-interaction",{"title":2676,"path":2677,"stem":2678},"Евристика проєктування","/software-engineering/design-heuristics","09.software-engineering/08.design-heuristics",{"title":2680,"path":2681,"stem":2682},"Еволюція проєктних рішень","/software-engineering/evolution-of-design-solutions","09.software-engineering/09.evolution-of-design-solutions",{"title":2684,"path":2685,"stem":2686},"EventStorming","/software-engineering/eventstorming","09.software-engineering/10.eventstorming",{"title":2688,"path":2689,"stem":2690},"DDD на практиці","/software-engineering/ddd-in-practice","09.software-engineering/11.ddd-in-practice",{"title":2692,"icon":943,"path":2693,"stem":2694,"children":2695,"page":59},"DDD","/ddd","10.ddd",[2696,2700,2704,2708,2712,2716,2720,2724,2728,2732,2736,2740,2744],{"title":2697,"path":2698,"stem":2699},"Аналіз предметної області","/ddd/domain-analysis","10.ddd/01.domain-analysis",{"title":2701,"path":2702,"stem":2703},"Експертні знання про предметну область","/ddd/domain-expert-knowledge","10.ddd/02.domain-expert-knowledge",{"title":2705,"path":2706,"stem":2707},"Як осмислити складність предметної області","/ddd/managing-domain-complexity","10.ddd/03.managing-domain-complexity",{"title":2709,"path":2710,"stem":2711},"Інтеграція обмежених контекстів","/ddd/bounded-context-integration","10.ddd/04.bounded-context-integration",{"title":2713,"path":2714,"stem":2715},"Реалізація простої бізнес-логіки","/ddd/simple-business-logic","10.ddd/05.simple-business-logic",{"title":2717,"path":2718,"stem":2719},"Обробка складної бізнес-логіки","/ddd/complex-business-logic","10.ddd/06.complex-business-logic",{"title":2721,"path":2722,"stem":2723},"Моделювання фактора часу","/ddd/time-modeling","10.ddd/07.time-modeling",{"title":2725,"path":2726,"stem":2727},"Глава 8. Архітектурні Патерни","/ddd/architectural-patterns","10.ddd/08.architectural-patterns",{"title":2729,"path":2730,"stem":2731},"Глава 9. Патерни Взаємодії","/ddd/interaction-patterns","10.ddd/09.interaction-patterns",{"title":2733,"path":2734,"stem":2735},"Глава 10. Проектні Евристики","/ddd/design-heuristics","10.ddd/10.design-heuristics",{"title":2737,"path":2738,"stem":2739},"Глава 11. Еволюція Проектних Рішень","/ddd/evolution-of-design-decisions","10.ddd/11.evolution-of-design-decisions",{"title":2741,"path":2742,"stem":2743},"Глава 12. EventStorming","/ddd/event-storming","10.ddd/12.event-storming",{"title":2745,"path":2746,"stem":2747},"Глава 13. DDD на Практиці","/ddd/ddd-in-practice","10.ddd/13.ddd-in-practice",{"title":2749,"icon":2750,"path":2751,"stem":2752,"children":2753,"page":59},"Media Streaming","i-lucide-video","/media-streaming","11.media-streaming",[2754,2758,2762,2766,2770,2774,2778],{"title":2755,"path":2756,"stem":2757},"01. Магія Стрімінгу: Що відбувається, коли ви натискаєте \"Play\"","/media-streaming/introduction","11.media-streaming/01.introduction",{"title":2759,"path":2760,"stem":2761},"02. Анатомія Медіа: Кодеки, Контейнери та Стиснення","/media-streaming/audio-video-anatomy","11.media-streaming/02.audio-video-anatomy",{"title":2763,"path":2764,"stem":2765},"03. The Gym: FFmpeg Deep Dive","/media-streaming/ffmpeg-gym","11.media-streaming/03.ffmpeg-gym",{"title":2767,"path":2768,"stem":2769},"04. HLS Protocol: HTTP Live Streaming у Деталях","/media-streaming/hls-protocol","11.media-streaming/04.hls-protocol",{"title":2771,"path":2772,"stem":2773},"05. DASH Protocol: Відкритий Стандарт","/media-streaming/dash-protocol","11.media-streaming/05.dash-protocol",{"title":2775,"path":2776,"stem":2777},"06. Масштабування: CDN та Adaptive Bitrate","/media-streaming/cdn-and-adaptive-bitrate","11.media-streaming/06.cdn-and-adaptive-bitrate",{"title":2779,"path":2780,"stem":2781},"07. Війна із Затримкою (Latency)","/media-streaming/realtime-latency","11.media-streaming/07.realtime-latency",{"title":2783,"icon":2784,"path":2785,"stem":2786,"children":2787,"page":59},"HTML & CSS","i-devicon-html5","/html-css","12.html-css",[2788,2792,2796,2800,2804,2808,2812,2816,2820,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864,2868,2872,2876,2880,2884,2888,2892,2896,2900,2904],{"title":2789,"path":2790,"stem":2791},"Вступ до HTML. Структура документа","/html-css/intro-html-structure","12.html-css/01.intro-html-structure",{"title":2793,"path":2794,"stem":2795},"Форматування тексту в HTML","/html-css/html-text-formatting","12.html-css/02.html-text-formatting",{"title":2797,"path":2798,"stem":2799},"Посилання та зображення в HTML","/html-css/html-links-images","12.html-css/03.html-links-images",{"title":2801,"path":2802,"stem":2803},"Списки та таблиці в HTML","/html-css/html-lists-tables","12.html-css/04.html-lists-tables",{"title":2805,"path":2806,"stem":2807},"Форми в HTML","/html-css/html-forms","12.html-css/05.html-forms",{"title":2809,"path":2810,"stem":2811},"Семантичні елементи HTML5","/html-css/html-semantic-elements","12.html-css/06.html-semantic-elements",{"title":2813,"path":2814,"stem":2815},"Мультимедіа та розширені елементи HTML","/html-css/html-multimedia-advanced","12.html-css/07.html-multimedia-advanced",{"title":2817,"path":2818,"stem":2819},"Мікророзмітка та SEO в HTML","/html-css/html-microdata-seo","12.html-css/08.html-microdata-seo",{"title":2821,"path":2822,"stem":2823},"Вступ до CSS. Селектори та специфічність","/html-css/css-intro-selectors","12.html-css/09.css-intro-selectors",{"title":2825,"path":2826,"stem":2827},"Блокова модель CSS. Відступи. Box Sizing","/html-css/css-box-model","12.html-css/10.css-box-model",{"title":2829,"path":2830,"stem":2831},"Розміри у CSS: повний довідник одиниць і ключових слів","/html-css/10a.css-sizing","12.html-css/10a.css-sizing",{"title":2833,"path":2834,"stem":2835},"Типографіка в CSS. Шрифти та текст","/html-css/css-typography","12.html-css/11.css-typography",{"title":2837,"path":2838,"stem":2839},"Кольори та фони в CSS","/html-css/css-colors-backgrounds","12.html-css/12.css-colors-backgrounds",{"title":2841,"path":2842,"stem":2843},"Тіні та фільтри в CSS","/html-css/12b.css-shadows-filters","12.html-css/12b.css-shadows-filters",{"title":2845,"path":2846,"stem":2847},"CSS Flexbox: Фундамент гнучких макетів","/html-css/css-flexbox-fundamentals","12.html-css/13.css-flexbox-fundamentals",{"title":2849,"path":2850,"stem":2851},"CSS Flexbox: Вирівнювання та Позиціонування","/html-css/css-flexbox-alignment-sizing-and-patterns","12.html-css/14.css-flexbox-alignment-sizing-and-patterns",{"title":2853,"path":2854,"stem":2855},"CSS Grid. Двовимірний макет. Частина 1","/html-css/css-layout-grid","12.html-css/15.css-layout-grid",{"title":2857,"path":2858,"stem":2859},"CSS Grid. Двовимірний макет. Частина 2","/html-css/css-layout-grid-advanced","12.html-css/16.css-layout-grid-advanced",{"title":2861,"path":2862,"stem":2863},"Позиціонування в CSS. Z-index. Stacking Context","/html-css/css-positioning","12.html-css/17.css-positioning",{"title":2865,"path":2866,"stem":2867},"CSS Анімації та Переходи","/html-css/css-animations-transitions","12.html-css/18.css-animations-transitions",{"title":2869,"path":2870,"stem":2871},"Адаптивний дизайн. Media Queries. Частина 1","/html-css/css-responsive-media-queries","12.html-css/19.css-responsive-media-queries",{"title":2873,"path":2874,"stem":2875},"Адаптивний дизайн. Частина 2: clamp(), Container Queries, @layer","/html-css/css-responsive-advanced","12.html-css/20.css-responsive-advanced",{"title":2877,"path":2878,"stem":2879},"CSS Custom Properties. Методології. Сучасний CSS","/html-css/css-variables-methodologies","12.html-css/21.css-variables-methodologies",{"title":2881,"path":2882,"stem":2883},"Сучасний CSS 2023–2025: Нові можливості","/html-css/css-modern-features","12.html-css/22.css-modern-features",{"title":2885,"path":2886,"stem":2887},"CSS Nesting, @layer, @scope та @property: нативний препроцесор","/html-css/22a.css-nesting-modern-syntax","12.html-css/22a.css-nesting-modern-syntax",{"title":2889,"path":2890,"stem":2891},"CSS для форм та інтерактивних станів","/html-css/css-forms-interactive-states","12.html-css/23.css-forms-interactive-states",{"title":2893,"path":2894,"stem":2895},"Доступність у CSS (CSS Accessibility)","/html-css/css-accessibility","12.html-css/24.css-accessibility",{"title":2897,"path":2898,"stem":2899},"CSS-функції та сучасні sizing primitives","/html-css/css-functions-sizing","12.html-css/25.css-functions-sizing",{"title":2901,"path":2902,"stem":2903},"Rendering Pipeline і CSS Performance","/html-css/css-rendering-performance","12.html-css/26.css-rendering-performance",{"title":2905,"path":2906,"stem":2907},"CSS Best Practices: типові ситуації та правильні рішення","/html-css/css-best-practices","12.html-css/27.css-best-practices",{"title":2909,"path":2910,"stem":2911,"children":2912,"page":59},"Tailwind","/tailwind","21.tailwind",[2913,2917,2921,2925,2929,2933,2937,2941],{"title":2914,"path":2915,"stem":2916},"Що таке Tailwind CSS і навіщо він потрібен","/tailwind/tailwind-intro-philosophy","21.tailwind/01.tailwind-intro-philosophy",{"title":2918,"path":2919,"stem":2920},"Встановлення та налаштування Tailwind CSS v4","/tailwind/tailwind-installation-setup","21.tailwind/02.tailwind-installation-setup",{"title":2922,"path":2923,"stem":2924},"Utility-класи: основи та система Tailwind","/tailwind/tailwind-utility-classes-core","21.tailwind/03.tailwind-utility-classes-core",{"title":2926,"path":2927,"stem":2928},"Layout: Flexbox та Grid через Tailwind","/tailwind/tailwind-flexbox-grid","21.tailwind/04.tailwind-flexbox-grid",{"title":2930,"path":2931,"stem":2932},"Кастомізація теми через @theme у Tailwind v4","/tailwind/tailwind-theme-customization","21.tailwind/05.tailwind-theme-customization",{"title":2934,"path":2935,"stem":2936},"Варіанти: hover, focus, responsive, dark mode та нові v4","/tailwind/tailwind-variants-states","21.tailwind/06.tailwind-variants-states",{"title":2938,"path":2939,"stem":2940},"Типографіка та система кольорів у Tailwind v4","/tailwind/tailwind-typography-colors","21.tailwind/07.tailwind-typography-colors",{"title":2942,"path":2943,"stem":2944},"Компоненти та повторюваність: @apply, @utility та патерни","/tailwind/tailwind-components-patterns","21.tailwind/08.tailwind-components-patterns",{"title":2946,"path":2947,"stem":2948},"Showcase Компонентів kostyl.dev","/test-new-components","98.test-new-components",{"id":2950,"title":1493,"body":2951,"description":16735,"extension":16736,"links":16737,"meta":16738,"navigation":3426,"path":1494,"seo":16739,"stem":1495,"__hash__":16740},"docs/01.csharp/12.desktop-ui/23.viewmodel-implementation.md",{"type":2952,"value":2953,"toc":16683},"minimark",[2954,2959,2964,2978,2981,3001,3120,3125,3156,3166,3180,3183,3187,3193,3198,3204,3748,3753,3806,3817,3821,3826,3907,3912,3923,3931,3955,3958,3962,3967,4066,4069,4074,4088,4091,4093,4097,4106,4110,4116,4272,4277,4307,4312,4408,4413,4427,4447,4451,4456,4461,4525,4530,4597,4602,4672,4676,4690,4695,4753,4755,4763,4771,4840,4843,4849,4857,5149,5153,5196,5202,5207,5421,5426,5608,5612,5627,5632,5688,5702,5706,5711,5837,5841,5852,5857,5913,5915,5919,5931,5935,5948,6132,6136,6166,6185,6258,6262,6267,6478,6482,6490,6495,6509,6513,6518,6739,6743,6911,6915,6923,6927,6941,7302,7307,7337,7340,7354,7356,7360,7367,7371,7376,7476,7481,7573,7577,7582,8457,8461,8466,9318,9323,9532,9536,9565,9569,9575,9580,9822,9827,9935,9940,10426,10432,10434,10438,10445,10449,10453,10563,10568,10589,10594,10614,10618,10625,10630,10689,10694,10832,10836,10865,10869,10874,11219,11224,11452,11457,11478,11482,11485,11490,11662,11667,11807,11817,11821,11845,11847,11851,11854,11858,11863,11874,12037,12042,12060,12066,12073,12078,12084,12235,12240,12260,12265,12269,12277,12286,12473,12478,12607,12617,12621,12626,12633,12782,12787,12803,12808,12816,12827,12829,12833,12837,12842,12847,12853,12887,12892,12924,12929,13123,13128,13444,13446,13450,13455,13459,13467,13515,13519,13540,13545,13866,13871,14117,14122,14616,14618,14622,14627,14631,14637,14751,14755,14768,14773,15458,15463,15550,15555,15893,15897,16423,16425,16429,16432,16437,16469,16474,16494,16498,16509,16515,16520,16550,16552,16556,16639,16641,16645,16679],[2955,2956,2958],"h1",{"id":2957},"viewmodel-implementation-від-baseviewmodel-до-валідації","ViewModel Implementation: Від BaseViewModel до валідації",[2960,2961,2963],"h2",{"id":2962},"вступ","Вступ",[2965,2966,2967,2968,2973,2974],"p",{},"У попередній статті ми розібрали ",[2969,2970,2972],"a",{"href":2971},"22.mvvm-pattern","MVVM Pattern"," як архітектурний патерн — три компоненти, золоті правила, потік даних. Але залишилося питання: ",[2975,2976,2977],"strong",{},"як саме реалізувати ViewModel?",[2965,2979,2980],{},"Уявіть ситуацію: ви створюєте додаток з 10 екранами. Кожен екран має свій ViewModel. Кожен ViewModel потребує:",[2982,2983,2984,2992,2998],"ul",{},[2985,2986,2987,2988],"li",{},"Реалізації ",[2989,2990,2991],"code",{},"INotifyPropertyChanged",[2985,2993,2994,2995],{},"Методу ",[2989,2996,2997],{},"OnPropertyChanged(string propertyName)",[2985,2999,3000],{},"Десятків властивостей з однаковим boilerplate-кодом:",[3002,3003,3008],"pre",{"className":3004,"code":3005,"language":3006,"meta":3007,"style":3007},"language-csharp shiki shiki-themes light-plus dark-plus dark-plus","private string _firstName;\npublic string FirstName\n{\n    get => _firstName;\n    set\n    {\n        _firstName = value;\n        OnPropertyChanged(nameof(FirstName));\n    }\n}\n","csharp","",[2989,3009,3010,3030,3041,3047,3061,3067,3073,3087,3108,3114],{"__ignoreMap":3007},[3011,3012,3015,3019,3022,3026],"span",{"class":3013,"line":3014},"line",1,[3011,3016,3018],{"class":3017},"su1O8","private",[3011,3020,3021],{"class":3017}," string",[3011,3023,3025],{"class":3024},"siwwj"," _firstName",[3011,3027,3029],{"class":3028},"sHH4Y",";\n",[3011,3031,3033,3036,3038],{"class":3013,"line":3032},2,[3011,3034,3035],{"class":3017},"public",[3011,3037,3021],{"class":3017},[3011,3039,3040],{"class":3024}," FirstName\n",[3011,3042,3044],{"class":3013,"line":3043},3,[3011,3045,3046],{"class":3028},"{\n",[3011,3048,3050,3053,3056,3059],{"class":3013,"line":3049},4,[3011,3051,3052],{"class":3024},"    get",[3011,3054,3055],{"class":3028}," => ",[3011,3057,3058],{"class":3024},"_firstName",[3011,3060,3029],{"class":3028},[3011,3062,3064],{"class":3013,"line":3063},5,[3011,3065,3066],{"class":3024},"    set\n",[3011,3068,3070],{"class":3013,"line":3069},6,[3011,3071,3072],{"class":3028},"    {\n",[3011,3074,3076,3079,3082,3085],{"class":3013,"line":3075},7,[3011,3077,3078],{"class":3024},"        _firstName",[3011,3080,3081],{"class":3028}," = ",[3011,3083,3084],{"class":3024},"value",[3011,3086,3029],{"class":3028},[3011,3088,3090,3094,3097,3100,3102,3105],{"class":3013,"line":3089},8,[3011,3091,3093],{"class":3092},"s8Opu","        OnPropertyChanged",[3011,3095,3096],{"class":3028},"(",[3011,3098,3099],{"class":3017},"nameof",[3011,3101,3096],{"class":3028},[3011,3103,3104],{"class":3024},"FirstName",[3011,3106,3107],{"class":3028},"));\n",[3011,3109,3111],{"class":3013,"line":3110},9,[3011,3112,3113],{"class":3028},"    }\n",[3011,3115,3117],{"class":3013,"line":3116},10,[3011,3118,3119],{"class":3028},"}\n",[2965,3121,3122],{},[2975,3123,3124],{},"Проблеми:",[2982,3126,3127,3134,3144,3150],{},[2985,3128,3129,3130,3133],{},"❌ ",[2975,3131,3132],{},"Дублювання коду"," — той самий патерн для кожної властивості",[2985,3135,3129,3136,3139,3140,3143],{},[2975,3137,3138],{},"Помилки у назвах"," — ",[2989,3141,3142],{},"OnPropertyChanged(\"FirsName\")"," (опечатка) → UI не оновлюється",[2985,3145,3129,3146,3149],{},[2975,3147,3148],{},"Відсутність валідації"," — як показати помилки у View?",[2985,3151,3129,3152,3155],{},[2975,3153,3154],{},"Складність тестування"," — як перевірити, що PropertyChanged викликається?",[2965,3157,3158,3161,3162,3165],{},[2975,3159,3160],{},"Рішення:"," Створити ",[2975,3163,3164],{},"BaseViewModel"," — базовий клас з усією інфраструктурою, який успадковують всі ViewModel.",[3167,3168,3169,3172,3173,3176,3177,3179],"note",{},[2975,3170,3171],{},"Для кого ця стаття?"," Якщо ви вже знайомі з ",[2969,3174,2991],{"href":3175},"17.data-binding-basics-part2"," та ",[2969,3178,2972],{"href":2971},", ця стаття покаже, як створити production-ready ViewModel з валідацією та DesignTime даними.",[3181,3182],"hr",{},[2960,3184,3186],{"id":3185},"проблема-boilerplate-чому-потрібен-baseviewmodel","Проблема boilerplate: Чому потрібен BaseViewModel",[2965,3188,3189,3190,3192],{},"Розберемо детально, чому ручна реалізація ",[2989,3191,2991],{}," для кожного ViewModel — це антипатерн.",[3194,3195,3197],"h3",{"id":3196},"дублювання-коду-у-кожному-viewmodel","Дублювання коду у кожному ViewModel",[2965,3199,3200,3203],{},[2975,3201,3202],{},"Проблема:"," Той самий код у 10 ViewModel.",[3002,3205,3207],{"className":3004,"code":3206,"language":3006,"meta":3007,"style":3007},"// MainViewModel.cs\npublic class MainViewModel : INotifyPropertyChanged\n{\n    public event PropertyChangedEventHandler PropertyChanged;\n    \n    protected void OnPropertyChanged(string propertyName)\n    {\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n    }\n    \n    private string _title;\n    public string Title\n    {\n        get => _title;\n        set\n        {\n            _title = value;\n            OnPropertyChanged(nameof(Title));\n        }\n    }\n}\n\n// SettingsViewModel.cs\npublic class SettingsViewModel : INotifyPropertyChanged\n{\n    // ❌ Копіювання того самого коду\n    public event PropertyChangedEventHandler PropertyChanged;\n    \n    protected void OnPropertyChanged(string propertyName)\n    {\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n    }\n    \n    private string _theme;\n    public string Theme\n    {\n        get => _theme;\n        set\n        {\n            _theme = value;\n            OnPropertyChanged(nameof(Theme));\n        }\n    }\n}\n\n// LoginViewModel.cs\npublic class LoginViewModel : INotifyPropertyChanged\n{\n    // ❌ Знову те саме\n    public event PropertyChangedEventHandler PropertyChanged;\n    \n    protected void OnPropertyChanged(string propertyName)\n    {\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n    }\n    \n    // ... властивості\n}\n",[2989,3208,3209,3215,3232,3236,3252,3257,3279,3283,3315,3319,3323,3336,3346,3351,3364,3370,3376,3388,3405,3411,3416,3421,3428,3434,3448,3453,3459,3472,3477,3494,3499,3524,3529,3534,3546,3556,3561,3573,3578,3583,3595,3611,3616,3621,3626,3631,3637,3651,3656,3662,3675,3680,3697,3702,3727,3732,3737,3743],{"__ignoreMap":3007},[3011,3210,3211],{"class":3013,"line":3014},[3011,3212,3214],{"class":3213},"spJ8K","// MainViewModel.cs\n",[3011,3216,3217,3219,3222,3226,3229],{"class":3013,"line":3032},[3011,3218,3035],{"class":3017},[3011,3220,3221],{"class":3017}," class",[3011,3223,3225],{"class":3224},"sN1BT"," MainViewModel",[3011,3227,3228],{"class":3028}," : ",[3011,3230,3231],{"class":3224},"INotifyPropertyChanged\n",[3011,3233,3234],{"class":3013,"line":3043},[3011,3235,3046],{"class":3028},[3011,3237,3238,3241,3244,3247,3250],{"class":3013,"line":3049},[3011,3239,3240],{"class":3017},"    public",[3011,3242,3243],{"class":3017}," event",[3011,3245,3246],{"class":3224}," PropertyChangedEventHandler",[3011,3248,3249],{"class":3024}," PropertyChanged",[3011,3251,3029],{"class":3028},[3011,3253,3254],{"class":3013,"line":3063},[3011,3255,3256],{"class":3028},"    \n",[3011,3258,3259,3262,3265,3268,3270,3273,3276],{"class":3013,"line":3069},[3011,3260,3261],{"class":3017},"    protected",[3011,3263,3264],{"class":3017}," void",[3011,3266,3267],{"class":3092}," OnPropertyChanged",[3011,3269,3096],{"class":3028},[3011,3271,3272],{"class":3017},"string",[3011,3274,3275],{"class":3024}," propertyName",[3011,3277,3278],{"class":3028},")\n",[3011,3280,3281],{"class":3013,"line":3075},[3011,3282,3072],{"class":3028},[3011,3284,3285,3288,3291,3294,3296,3299,3302,3305,3308,3310,3313],{"class":3013,"line":3089},[3011,3286,3287],{"class":3024},"        PropertyChanged",[3011,3289,3290],{"class":3028},"?.",[3011,3292,3293],{"class":3092},"Invoke",[3011,3295,3096],{"class":3028},[3011,3297,3298],{"class":3017},"this",[3011,3300,3301],{"class":3028},", ",[3011,3303,3304],{"class":3017},"new",[3011,3306,3307],{"class":3224}," PropertyChangedEventArgs",[3011,3309,3096],{"class":3028},[3011,3311,3312],{"class":3024},"propertyName",[3011,3314,3107],{"class":3028},[3011,3316,3317],{"class":3013,"line":3110},[3011,3318,3113],{"class":3028},[3011,3320,3321],{"class":3013,"line":3116},[3011,3322,3256],{"class":3028},[3011,3324,3326,3329,3331,3334],{"class":3013,"line":3325},11,[3011,3327,3328],{"class":3017},"    private",[3011,3330,3021],{"class":3017},[3011,3332,3333],{"class":3024}," _title",[3011,3335,3029],{"class":3028},[3011,3337,3339,3341,3343],{"class":3013,"line":3338},12,[3011,3340,3240],{"class":3017},[3011,3342,3021],{"class":3017},[3011,3344,3345],{"class":3024}," Title\n",[3011,3347,3349],{"class":3013,"line":3348},13,[3011,3350,3072],{"class":3028},[3011,3352,3354,3357,3359,3362],{"class":3013,"line":3353},14,[3011,3355,3356],{"class":3017},"        get",[3011,3358,3055],{"class":3028},[3011,3360,3361],{"class":3024},"_title",[3011,3363,3029],{"class":3028},[3011,3365,3367],{"class":3013,"line":3366},15,[3011,3368,3369],{"class":3017},"        set\n",[3011,3371,3373],{"class":3013,"line":3372},16,[3011,3374,3375],{"class":3028},"        {\n",[3011,3377,3379,3382,3384,3386],{"class":3013,"line":3378},17,[3011,3380,3381],{"class":3024},"            _title",[3011,3383,3081],{"class":3028},[3011,3385,3084],{"class":3024},[3011,3387,3029],{"class":3028},[3011,3389,3391,3394,3396,3398,3400,3403],{"class":3013,"line":3390},18,[3011,3392,3393],{"class":3092},"            OnPropertyChanged",[3011,3395,3096],{"class":3028},[3011,3397,3099],{"class":3017},[3011,3399,3096],{"class":3028},[3011,3401,3402],{"class":3024},"Title",[3011,3404,3107],{"class":3028},[3011,3406,3408],{"class":3013,"line":3407},19,[3011,3409,3410],{"class":3028},"        }\n",[3011,3412,3414],{"class":3013,"line":3413},20,[3011,3415,3113],{"class":3028},[3011,3417,3419],{"class":3013,"line":3418},21,[3011,3420,3119],{"class":3028},[3011,3422,3424],{"class":3013,"line":3423},22,[3011,3425,3427],{"emptyLinePlaceholder":3426},true,"\n",[3011,3429,3431],{"class":3013,"line":3430},23,[3011,3432,3433],{"class":3213},"// SettingsViewModel.cs\n",[3011,3435,3437,3439,3441,3444,3446],{"class":3013,"line":3436},24,[3011,3438,3035],{"class":3017},[3011,3440,3221],{"class":3017},[3011,3442,3443],{"class":3224}," SettingsViewModel",[3011,3445,3228],{"class":3028},[3011,3447,3231],{"class":3224},[3011,3449,3451],{"class":3013,"line":3450},25,[3011,3452,3046],{"class":3028},[3011,3454,3456],{"class":3013,"line":3455},26,[3011,3457,3458],{"class":3213},"    // ❌ Копіювання того самого коду\n",[3011,3460,3462,3464,3466,3468,3470],{"class":3013,"line":3461},27,[3011,3463,3240],{"class":3017},[3011,3465,3243],{"class":3017},[3011,3467,3246],{"class":3224},[3011,3469,3249],{"class":3024},[3011,3471,3029],{"class":3028},[3011,3473,3475],{"class":3013,"line":3474},28,[3011,3476,3256],{"class":3028},[3011,3478,3480,3482,3484,3486,3488,3490,3492],{"class":3013,"line":3479},29,[3011,3481,3261],{"class":3017},[3011,3483,3264],{"class":3017},[3011,3485,3267],{"class":3092},[3011,3487,3096],{"class":3028},[3011,3489,3272],{"class":3017},[3011,3491,3275],{"class":3024},[3011,3493,3278],{"class":3028},[3011,3495,3497],{"class":3013,"line":3496},30,[3011,3498,3072],{"class":3028},[3011,3500,3502,3504,3506,3508,3510,3512,3514,3516,3518,3520,3522],{"class":3013,"line":3501},31,[3011,3503,3287],{"class":3024},[3011,3505,3290],{"class":3028},[3011,3507,3293],{"class":3092},[3011,3509,3096],{"class":3028},[3011,3511,3298],{"class":3017},[3011,3513,3301],{"class":3028},[3011,3515,3304],{"class":3017},[3011,3517,3307],{"class":3224},[3011,3519,3096],{"class":3028},[3011,3521,3312],{"class":3024},[3011,3523,3107],{"class":3028},[3011,3525,3527],{"class":3013,"line":3526},32,[3011,3528,3113],{"class":3028},[3011,3530,3532],{"class":3013,"line":3531},33,[3011,3533,3256],{"class":3028},[3011,3535,3537,3539,3541,3544],{"class":3013,"line":3536},34,[3011,3538,3328],{"class":3017},[3011,3540,3021],{"class":3017},[3011,3542,3543],{"class":3024}," _theme",[3011,3545,3029],{"class":3028},[3011,3547,3549,3551,3553],{"class":3013,"line":3548},35,[3011,3550,3240],{"class":3017},[3011,3552,3021],{"class":3017},[3011,3554,3555],{"class":3024}," Theme\n",[3011,3557,3559],{"class":3013,"line":3558},36,[3011,3560,3072],{"class":3028},[3011,3562,3564,3566,3568,3571],{"class":3013,"line":3563},37,[3011,3565,3356],{"class":3017},[3011,3567,3055],{"class":3028},[3011,3569,3570],{"class":3024},"_theme",[3011,3572,3029],{"class":3028},[3011,3574,3576],{"class":3013,"line":3575},38,[3011,3577,3369],{"class":3017},[3011,3579,3581],{"class":3013,"line":3580},39,[3011,3582,3375],{"class":3028},[3011,3584,3586,3589,3591,3593],{"class":3013,"line":3585},40,[3011,3587,3588],{"class":3024},"            _theme",[3011,3590,3081],{"class":3028},[3011,3592,3084],{"class":3024},[3011,3594,3029],{"class":3028},[3011,3596,3598,3600,3602,3604,3606,3609],{"class":3013,"line":3597},41,[3011,3599,3393],{"class":3092},[3011,3601,3096],{"class":3028},[3011,3603,3099],{"class":3017},[3011,3605,3096],{"class":3028},[3011,3607,3608],{"class":3024},"Theme",[3011,3610,3107],{"class":3028},[3011,3612,3614],{"class":3013,"line":3613},42,[3011,3615,3410],{"class":3028},[3011,3617,3619],{"class":3013,"line":3618},43,[3011,3620,3113],{"class":3028},[3011,3622,3624],{"class":3013,"line":3623},44,[3011,3625,3119],{"class":3028},[3011,3627,3629],{"class":3013,"line":3628},45,[3011,3630,3427],{"emptyLinePlaceholder":3426},[3011,3632,3634],{"class":3013,"line":3633},46,[3011,3635,3636],{"class":3213},"// LoginViewModel.cs\n",[3011,3638,3640,3642,3644,3647,3649],{"class":3013,"line":3639},47,[3011,3641,3035],{"class":3017},[3011,3643,3221],{"class":3017},[3011,3645,3646],{"class":3224}," LoginViewModel",[3011,3648,3228],{"class":3028},[3011,3650,3231],{"class":3224},[3011,3652,3654],{"class":3013,"line":3653},48,[3011,3655,3046],{"class":3028},[3011,3657,3659],{"class":3013,"line":3658},49,[3011,3660,3661],{"class":3213},"    // ❌ Знову те саме\n",[3011,3663,3665,3667,3669,3671,3673],{"class":3013,"line":3664},50,[3011,3666,3240],{"class":3017},[3011,3668,3243],{"class":3017},[3011,3670,3246],{"class":3224},[3011,3672,3249],{"class":3024},[3011,3674,3029],{"class":3028},[3011,3676,3678],{"class":3013,"line":3677},51,[3011,3679,3256],{"class":3028},[3011,3681,3683,3685,3687,3689,3691,3693,3695],{"class":3013,"line":3682},52,[3011,3684,3261],{"class":3017},[3011,3686,3264],{"class":3017},[3011,3688,3267],{"class":3092},[3011,3690,3096],{"class":3028},[3011,3692,3272],{"class":3017},[3011,3694,3275],{"class":3024},[3011,3696,3278],{"class":3028},[3011,3698,3700],{"class":3013,"line":3699},53,[3011,3701,3072],{"class":3028},[3011,3703,3705,3707,3709,3711,3713,3715,3717,3719,3721,3723,3725],{"class":3013,"line":3704},54,[3011,3706,3287],{"class":3024},[3011,3708,3290],{"class":3028},[3011,3710,3293],{"class":3092},[3011,3712,3096],{"class":3028},[3011,3714,3298],{"class":3017},[3011,3716,3301],{"class":3028},[3011,3718,3304],{"class":3017},[3011,3720,3307],{"class":3224},[3011,3722,3096],{"class":3028},[3011,3724,3312],{"class":3024},[3011,3726,3107],{"class":3028},[3011,3728,3730],{"class":3013,"line":3729},55,[3011,3731,3113],{"class":3028},[3011,3733,3735],{"class":3013,"line":3734},56,[3011,3736,3256],{"class":3028},[3011,3738,3740],{"class":3013,"line":3739},57,[3011,3741,3742],{"class":3213},"    // ... властивості\n",[3011,3744,3746],{"class":3013,"line":3745},58,[3011,3747,3119],{"class":3028},[2965,3749,3750],{},[2975,3751,3752],{},"Що не так?",[3754,3755,3756],"mermaid",{},[3002,3757,3760],{"className":3758,"code":3759,"language":3754,"meta":3007,"style":3007},"language-mermaid shiki shiki-themes light-plus dark-plus dark-plus","graph TD\n    A[MainViewModel] --> D[INotifyPropertyChanged\u003Cbr/>Дублювання]\n    B[SettingsViewModel] --> D\n    C[LoginViewModel] --> D\n    \n    style D fill:#ef4444,stroke:#b91c1c,color:#ffffff\n    style A fill:#f59e0b,stroke:#b45309,color:#ffffff\n    style B fill:#f59e0b,stroke:#b45309,color:#ffffff\n    style C fill:#f59e0b,stroke:#b45309,color:#ffffff\n",[2989,3761,3762,3767,3772,3777,3782,3786,3791,3796,3801],{"__ignoreMap":3007},[3011,3763,3764],{"class":3013,"line":3014},[3011,3765,3766],{},"graph TD\n",[3011,3768,3769],{"class":3013,"line":3032},[3011,3770,3771],{},"    A[MainViewModel] --> D[INotifyPropertyChanged\u003Cbr/>Дублювання]\n",[3011,3773,3774],{"class":3013,"line":3043},[3011,3775,3776],{},"    B[SettingsViewModel] --> D\n",[3011,3778,3779],{"class":3013,"line":3049},[3011,3780,3781],{},"    C[LoginViewModel] --> D\n",[3011,3783,3784],{"class":3013,"line":3063},[3011,3785,3256],{},[3011,3787,3788],{"class":3013,"line":3069},[3011,3789,3790],{},"    style D fill:#ef4444,stroke:#b91c1c,color:#ffffff\n",[3011,3792,3793],{"class":3013,"line":3075},[3011,3794,3795],{},"    style A fill:#f59e0b,stroke:#b45309,color:#ffffff\n",[3011,3797,3798],{"class":3013,"line":3089},[3011,3799,3800],{},"    style B fill:#f59e0b,stroke:#b45309,color:#ffffff\n",[3011,3802,3803],{"class":3013,"line":3110},[3011,3804,3805],{},"    style C fill:#f59e0b,stroke:#b45309,color:#ffffff\n",[2982,3807,3808,3811,3814],{},[2985,3809,3810],{},"Той самий код у 10 місцях",[2985,3812,3813],{},"Зміна логіки → потрібно змінити у 10 місцях",[2985,3815,3816],{},"Легко забути оновити один з ViewModel",[3194,3818,3820],{"id":3819},"помилки-у-назвах-властивостей","Помилки у назвах властивостей",[2965,3822,3823,3825],{},[2975,3824,3202],{}," Рядкові літерали — джерело помилок.",[3002,3827,3829],{"className":3004,"code":3828,"language":3006,"meta":3007,"style":3007},"private string _firstName;\npublic string FirstName\n{\n    get => _firstName;\n    set\n    {\n        _firstName = value;\n        // ❌ Опечатка — UI не оновиться\n        OnPropertyChanged(\"FirsName\");\n    }\n}\n",[2989,3830,3831,3841,3849,3853,3863,3867,3871,3881,3886,3899,3903],{"__ignoreMap":3007},[3011,3832,3833,3835,3837,3839],{"class":3013,"line":3014},[3011,3834,3018],{"class":3017},[3011,3836,3021],{"class":3017},[3011,3838,3025],{"class":3024},[3011,3840,3029],{"class":3028},[3011,3842,3843,3845,3847],{"class":3013,"line":3032},[3011,3844,3035],{"class":3017},[3011,3846,3021],{"class":3017},[3011,3848,3040],{"class":3024},[3011,3850,3851],{"class":3013,"line":3043},[3011,3852,3046],{"class":3028},[3011,3854,3855,3857,3859,3861],{"class":3013,"line":3049},[3011,3856,3052],{"class":3024},[3011,3858,3055],{"class":3028},[3011,3860,3058],{"class":3024},[3011,3862,3029],{"class":3028},[3011,3864,3865],{"class":3013,"line":3063},[3011,3866,3066],{"class":3024},[3011,3868,3869],{"class":3013,"line":3069},[3011,3870,3072],{"class":3028},[3011,3872,3873,3875,3877,3879],{"class":3013,"line":3075},[3011,3874,3078],{"class":3024},[3011,3876,3081],{"class":3028},[3011,3878,3084],{"class":3024},[3011,3880,3029],{"class":3028},[3011,3882,3883],{"class":3013,"line":3089},[3011,3884,3885],{"class":3213},"        // ❌ Опечатка — UI не оновиться\n",[3011,3887,3888,3890,3892,3896],{"class":3013,"line":3110},[3011,3889,3093],{"class":3092},[3011,3891,3096],{"class":3028},[3011,3893,3895],{"class":3894},"sbdoH","\"FirsName\"",[3011,3897,3898],{"class":3028},");\n",[3011,3900,3901],{"class":3013,"line":3116},[3011,3902,3113],{"class":3028},[3011,3904,3905],{"class":3013,"line":3325},[3011,3906,3119],{"class":3028},[2965,3908,3909],{},[2975,3910,3911],{},"Наслідки:",[2982,3913,3914,3917,3920],{},[2985,3915,3916],{},"UI не оновлюється при зміні властивості",[2985,3918,3919],{},"Помилка виявляється тільки у runtime",[2985,3921,3922],{},"Складно знайти причину — компілятор не попереджає",[2965,3924,3925],{},[2975,3926,3927,3928,3930],{},"Спроба виправлення через ",[2989,3929,3099],{},":",[3002,3932,3934],{"className":3004,"code":3933,"language":3006,"meta":3007,"style":3007},"OnPropertyChanged(nameof(FirstName));  // ✅ Compile-time перевірка\n",[2989,3935,3936],{"__ignoreMap":3007},[3011,3937,3938,3941,3943,3945,3947,3949,3952],{"class":3013,"line":3014},[3011,3939,3940],{"class":3092},"OnPropertyChanged",[3011,3942,3096],{"class":3028},[3011,3944,3099],{"class":3017},[3011,3946,3096],{"class":3028},[3011,3948,3104],{"class":3024},[3011,3950,3951],{"class":3028},"));  ",[3011,3953,3954],{"class":3213},"// ✅ Compile-time перевірка\n",[2965,3956,3957],{},"Але це все одно багато boilerplate-коду для кожної властивості.",[3194,3959,3961],{"id":3960},"відсутність-централізованої-логіки","Відсутність централізованої логіки",[2965,3963,3964,3966],{},[2975,3965,3202],{}," Як додати логування змін властивостей?",[3002,3968,3970],{"className":3004,"code":3969,"language":3006,"meta":3007,"style":3007},"// Потрібно змінити у кожному ViewModel\nprotected void OnPropertyChanged(string propertyName)\n{\n    // Додаємо логування\n    Console.WriteLine($\"Property {propertyName} changed\");\n    \n    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n}\n",[2989,3971,3972,3977,3994,3998,4003,4033,4037,4062],{"__ignoreMap":3007},[3011,3973,3974],{"class":3013,"line":3014},[3011,3975,3976],{"class":3213},"// Потрібно змінити у кожному ViewModel\n",[3011,3978,3979,3982,3984,3986,3988,3990,3992],{"class":3013,"line":3032},[3011,3980,3981],{"class":3017},"protected",[3011,3983,3264],{"class":3017},[3011,3985,3267],{"class":3092},[3011,3987,3096],{"class":3028},[3011,3989,3272],{"class":3017},[3011,3991,3275],{"class":3024},[3011,3993,3278],{"class":3028},[3011,3995,3996],{"class":3013,"line":3043},[3011,3997,3046],{"class":3028},[3011,3999,4000],{"class":3013,"line":3049},[3011,4001,4002],{"class":3213},"    // Додаємо логування\n",[3011,4004,4005,4008,4011,4014,4016,4019,4023,4025,4028,4031],{"class":3013,"line":3063},[3011,4006,4007],{"class":3024},"    Console",[3011,4009,4010],{"class":3028},".",[3011,4012,4013],{"class":3092},"WriteLine",[3011,4015,3096],{"class":3028},[3011,4017,4018],{"class":3894},"$\"Property ",[3011,4020,4022],{"class":4021},"sD7JJ","{",[3011,4024,3312],{"class":3024},[3011,4026,4027],{"class":4021},"}",[3011,4029,4030],{"class":3894}," changed\"",[3011,4032,3898],{"class":3028},[3011,4034,4035],{"class":3013,"line":3069},[3011,4036,3256],{"class":3028},[3011,4038,4039,4042,4044,4046,4048,4050,4052,4054,4056,4058,4060],{"class":3013,"line":3075},[3011,4040,4041],{"class":3024},"    PropertyChanged",[3011,4043,3290],{"class":3028},[3011,4045,3293],{"class":3092},[3011,4047,3096],{"class":3028},[3011,4049,3298],{"class":3017},[3011,4051,3301],{"class":3028},[3011,4053,3304],{"class":3017},[3011,4055,3307],{"class":3224},[3011,4057,3096],{"class":3028},[3011,4059,3312],{"class":3024},[3011,4061,3107],{"class":3028},[3011,4063,4064],{"class":3013,"line":3089},[3011,4065,3119],{"class":3028},[2965,4067,4068],{},"Якщо у вас 10 ViewModel — потрібно змінити 10 файлів. Якщо забули один — логування працює неповністю.",[2965,4070,4071],{},[2975,4072,4073],{},"Інші приклади централізованої логіки:",[2982,4075,4076,4079,4082,4085],{},[2985,4077,4078],{},"Відстеження змін для Undo/Redo",[2985,4080,4081],{},"Автоматичне збереження при зміні",[2985,4083,4084],{},"Аналітика (tracking змін користувача)",[2985,4086,4087],{},"Debugging (виведення стеку викликів)",[2965,4089,4090],{},"Без базового класу — неможливо додати цю логіку централізовано.",[3181,4092],{},[2960,4094,4096],{"id":4095},"baseviewmodel-базовий-клас-для-всіх-viewmodel","BaseViewModel: Базовий клас для всіх ViewModel",[2965,4098,4099,4100,4102,4103,4105],{},"Рішення всіх проблем — створити ",[2975,4101,3164],{}," — абстрактний базовий клас, який реалізує ",[2989,4104,2991],{}," та надає інфраструктуру для всіх ViewModel.",[3194,4107,4109],{"id":4108},"мінімальна-реалізація-baseviewmodel","Мінімальна реалізація BaseViewModel",[2965,4111,4112,4115],{},[2975,4113,4114],{},"Мета:"," Винести спільний код у один клас.",[3002,4117,4119],{"className":3004,"code":4118,"language":3006,"meta":3007,"style":3007},"using System.ComponentModel;\nusing System.Runtime.CompilerServices;\n\npublic abstract class BaseViewModel : INotifyPropertyChanged\n{\n    // Подія для сповіщення UI про зміни\n    public event PropertyChangedEventHandler PropertyChanged;\n    \n    // Метод для виклику події\n    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)\n    {\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n    }\n}\n",[2989,4120,4121,4137,4155,4159,4175,4179,4184,4196,4200,4205,4236,4240,4264,4268],{"__ignoreMap":3007},[3011,4122,4123,4127,4130,4132,4135],{"class":3013,"line":3014},[3011,4124,4126],{"class":4125},"sCDza","using",[3011,4128,4129],{"class":3224}," System",[3011,4131,4010],{"class":3028},[3011,4133,4134],{"class":3224},"ComponentModel",[3011,4136,3029],{"class":3028},[3011,4138,4139,4141,4143,4145,4148,4150,4153],{"class":3013,"line":3032},[3011,4140,4126],{"class":4125},[3011,4142,4129],{"class":3224},[3011,4144,4010],{"class":3028},[3011,4146,4147],{"class":3224},"Runtime",[3011,4149,4010],{"class":3028},[3011,4151,4152],{"class":3224},"CompilerServices",[3011,4154,3029],{"class":3028},[3011,4156,4157],{"class":3013,"line":3043},[3011,4158,3427],{"emptyLinePlaceholder":3426},[3011,4160,4161,4163,4166,4168,4171,4173],{"class":3013,"line":3049},[3011,4162,3035],{"class":3017},[3011,4164,4165],{"class":3017}," abstract",[3011,4167,3221],{"class":3017},[3011,4169,4170],{"class":3224}," BaseViewModel",[3011,4172,3228],{"class":3028},[3011,4174,3231],{"class":3224},[3011,4176,4177],{"class":3013,"line":3063},[3011,4178,3046],{"class":3028},[3011,4180,4181],{"class":3013,"line":3069},[3011,4182,4183],{"class":3213},"    // Подія для сповіщення UI про зміни\n",[3011,4185,4186,4188,4190,4192,4194],{"class":3013,"line":3075},[3011,4187,3240],{"class":3017},[3011,4189,3243],{"class":3017},[3011,4191,3246],{"class":3224},[3011,4193,3249],{"class":3024},[3011,4195,3029],{"class":3028},[3011,4197,4198],{"class":3013,"line":3089},[3011,4199,3256],{"class":3028},[3011,4201,4202],{"class":3013,"line":3110},[3011,4203,4204],{"class":3213},"    // Метод для виклику події\n",[3011,4206,4207,4209,4212,4214,4216,4219,4222,4225,4227,4229,4231,4234],{"class":3013,"line":3116},[3011,4208,3261],{"class":3017},[3011,4210,4211],{"class":3017}," virtual",[3011,4213,3264],{"class":3017},[3011,4215,3267],{"class":3092},[3011,4217,4218],{"class":3028},"([",[3011,4220,4221],{"class":3224},"CallerMemberName",[3011,4223,4224],{"class":3028},"] ",[3011,4226,3272],{"class":3017},[3011,4228,3275],{"class":3024},[3011,4230,3081],{"class":3028},[3011,4232,4233],{"class":3017},"null",[3011,4235,3278],{"class":3028},[3011,4237,4238],{"class":3013,"line":3325},[3011,4239,3072],{"class":3028},[3011,4241,4242,4244,4246,4248,4250,4252,4254,4256,4258,4260,4262],{"class":3013,"line":3338},[3011,4243,3287],{"class":3024},[3011,4245,3290],{"class":3028},[3011,4247,3293],{"class":3092},[3011,4249,3096],{"class":3028},[3011,4251,3298],{"class":3017},[3011,4253,3301],{"class":3028},[3011,4255,3304],{"class":3017},[3011,4257,3307],{"class":3224},[3011,4259,3096],{"class":3028},[3011,4261,3312],{"class":3024},[3011,4263,3107],{"class":3028},[3011,4265,4266],{"class":3013,"line":3348},[3011,4267,3113],{"class":3028},[3011,4269,4270],{"class":3013,"line":3353},[3011,4271,3119],{"class":3028},[2965,4273,4274],{},[2975,4275,4276],{},"Ключові моменти:",[4278,4279,4280,4291,4299],"ol",{},[2985,4281,4282,4287,4288,4290],{},[2975,4283,4284],{},[2989,4285,4286],{},"abstract class"," — не можна створити екземпляр ",[2989,4289,3164],{},", тільки успадкувати",[2985,4292,4293,4298],{},[2975,4294,4295],{},[2989,4296,4297],{},"[CallerMemberName]"," — компілятор автоматично підставляє ім'я властивості, що викликала метод",[2985,4300,4301,4306],{},[2975,4302,4303],{},[2989,4304,4305],{},"virtual"," — дозволяє перевизначити у нащадках для кастомної логіки",[2965,4308,4309],{},[2975,4310,4311],{},"Використання:",[3002,4313,4315],{"className":3004,"code":4314,"language":3006,"meta":3007,"style":3007},"public class MainViewModel : BaseViewModel\n{\n    private string _title;\n    public string Title\n    {\n        get => _title;\n        set\n        {\n            _title = value;\n            // ✅ Не потрібно передавати ім'я — CallerMemberName підставить автоматично\n            OnPropertyChanged();\n        }\n    }\n}\n",[2989,4316,4317,4330,4334,4344,4352,4356,4366,4370,4374,4384,4389,4396,4400,4404],{"__ignoreMap":3007},[3011,4318,4319,4321,4323,4325,4327],{"class":3013,"line":3014},[3011,4320,3035],{"class":3017},[3011,4322,3221],{"class":3017},[3011,4324,3225],{"class":3224},[3011,4326,3228],{"class":3028},[3011,4328,4329],{"class":3224},"BaseViewModel\n",[3011,4331,4332],{"class":3013,"line":3032},[3011,4333,3046],{"class":3028},[3011,4335,4336,4338,4340,4342],{"class":3013,"line":3043},[3011,4337,3328],{"class":3017},[3011,4339,3021],{"class":3017},[3011,4341,3333],{"class":3024},[3011,4343,3029],{"class":3028},[3011,4345,4346,4348,4350],{"class":3013,"line":3049},[3011,4347,3240],{"class":3017},[3011,4349,3021],{"class":3017},[3011,4351,3345],{"class":3024},[3011,4353,4354],{"class":3013,"line":3063},[3011,4355,3072],{"class":3028},[3011,4357,4358,4360,4362,4364],{"class":3013,"line":3069},[3011,4359,3356],{"class":3017},[3011,4361,3055],{"class":3028},[3011,4363,3361],{"class":3024},[3011,4365,3029],{"class":3028},[3011,4367,4368],{"class":3013,"line":3075},[3011,4369,3369],{"class":3017},[3011,4371,4372],{"class":3013,"line":3089},[3011,4373,3375],{"class":3028},[3011,4375,4376,4378,4380,4382],{"class":3013,"line":3110},[3011,4377,3381],{"class":3024},[3011,4379,3081],{"class":3028},[3011,4381,3084],{"class":3024},[3011,4383,3029],{"class":3028},[3011,4385,4386],{"class":3013,"line":3116},[3011,4387,4388],{"class":3213},"            // ✅ Не потрібно передавати ім'я — CallerMemberName підставить автоматично\n",[3011,4390,4391,4393],{"class":3013,"line":3325},[3011,4392,3393],{"class":3092},[3011,4394,4395],{"class":3028},"();\n",[3011,4397,4398],{"class":3013,"line":3338},[3011,4399,3410],{"class":3028},[3011,4401,4402],{"class":3013,"line":3348},[3011,4403,3113],{"class":3028},[3011,4405,4406],{"class":3013,"line":3353},[3011,4407,3119],{"class":3028},[2965,4409,4410],{},[2975,4411,4412],{},"Переваги:",[2982,4414,4415,4421,4424],{},[2985,4416,4417,4418,4420],{},"✅ Код ",[2989,4419,2991],{}," у одному місці",[2985,4422,4423],{},"✅ Автоматична підстановка імені властивості",[2985,4425,4426],{},"✅ Легко додати централізовану логіку",[4428,4429,4430,4433,4434,4436,4437,4440,4441,4443,4444,4010],"tip",{},[2975,4431,4432],{},"CallerMemberName:"," Атрибут ",[2989,4435,4297],{}," — це compile-time magic. Компілятор підставляє ім'я методу/властивості, що викликала метод. Якщо викликати ",[2989,4438,4439],{},"OnPropertyChanged()"," з властивості ",[2989,4442,3402],{},", компілятор підставить ",[2989,4445,4446],{},"\"Title\"",[3194,4448,4450],{"id":4449},"як-працює-callermembername","Як працює CallerMemberName",[2965,4452,4453,4454,4010],{},"Розберемо детально механізм ",[2989,4455,4297],{},[2965,4457,4458],{},[2975,4459,4460],{},"Код, який ви пишете:",[3002,4462,4464],{"className":3004,"code":4463,"language":3006,"meta":3007,"style":3007},"public string Title\n{\n    get => _title;\n    set\n    {\n        _title = value;\n        OnPropertyChanged();  // Не передаємо параметр\n    }\n}\n",[2989,4465,4466,4474,4478,4488,4492,4496,4507,4517,4521],{"__ignoreMap":3007},[3011,4467,4468,4470,4472],{"class":3013,"line":3014},[3011,4469,3035],{"class":3017},[3011,4471,3021],{"class":3017},[3011,4473,3345],{"class":3024},[3011,4475,4476],{"class":3013,"line":3032},[3011,4477,3046],{"class":3028},[3011,4479,4480,4482,4484,4486],{"class":3013,"line":3043},[3011,4481,3052],{"class":3024},[3011,4483,3055],{"class":3028},[3011,4485,3361],{"class":3024},[3011,4487,3029],{"class":3028},[3011,4489,4490],{"class":3013,"line":3049},[3011,4491,3066],{"class":3024},[3011,4493,4494],{"class":3013,"line":3063},[3011,4495,3072],{"class":3028},[3011,4497,4498,4501,4503,4505],{"class":3013,"line":3069},[3011,4499,4500],{"class":3024},"        _title",[3011,4502,3081],{"class":3028},[3011,4504,3084],{"class":3024},[3011,4506,3029],{"class":3028},[3011,4508,4509,4511,4514],{"class":3013,"line":3075},[3011,4510,3093],{"class":3092},[3011,4512,4513],{"class":3028},"();  ",[3011,4515,4516],{"class":3213},"// Не передаємо параметр\n",[3011,4518,4519],{"class":3013,"line":3089},[3011,4520,3113],{"class":3028},[3011,4522,4523],{"class":3013,"line":3110},[3011,4524,3119],{"class":3028},[2965,4526,4527],{},[2975,4528,4529],{},"Що бачить компілятор:",[3002,4531,4533],{"className":3004,"code":4532,"language":3006,"meta":3007,"style":3007},"public string Title\n{\n    get => _title;\n    set\n    {\n        _title = value;\n        OnPropertyChanged(\"Title\");  // Компілятор підставив автоматично\n    }\n}\n",[2989,4534,4535,4543,4547,4557,4561,4565,4575,4589,4593],{"__ignoreMap":3007},[3011,4536,4537,4539,4541],{"class":3013,"line":3014},[3011,4538,3035],{"class":3017},[3011,4540,3021],{"class":3017},[3011,4542,3345],{"class":3024},[3011,4544,4545],{"class":3013,"line":3032},[3011,4546,3046],{"class":3028},[3011,4548,4549,4551,4553,4555],{"class":3013,"line":3043},[3011,4550,3052],{"class":3024},[3011,4552,3055],{"class":3028},[3011,4554,3361],{"class":3024},[3011,4556,3029],{"class":3028},[3011,4558,4559],{"class":3013,"line":3049},[3011,4560,3066],{"class":3024},[3011,4562,4563],{"class":3013,"line":3063},[3011,4564,3072],{"class":3028},[3011,4566,4567,4569,4571,4573],{"class":3013,"line":3069},[3011,4568,4500],{"class":3024},[3011,4570,3081],{"class":3028},[3011,4572,3084],{"class":3024},[3011,4574,3029],{"class":3028},[3011,4576,4577,4579,4581,4583,4586],{"class":3013,"line":3075},[3011,4578,3093],{"class":3092},[3011,4580,3096],{"class":3028},[3011,4582,4446],{"class":3894},[3011,4584,4585],{"class":3028},");  ",[3011,4587,4588],{"class":3213},"// Компілятор підставив автоматично\n",[3011,4590,4591],{"class":3013,"line":3089},[3011,4592,3113],{"class":3028},[3011,4594,4595],{"class":3013,"line":3110},[3011,4596,3119],{"class":3028},[2965,4598,4599],{},[2975,4600,4601],{},"Діаграма процесу:",[3754,4603,4604],{},[3002,4605,4607],{"className":3758,"code":4606,"language":3754,"meta":3007,"style":3007},"sequenceDiagram\n    participant Code as Ваш код\n    participant Compiler as C# Compiler\n    participant Runtime as Runtime\n    \n    Code->>Compiler: OnPropertyChanged()\n    Note over Compiler: Бачить атрибут [CallerMemberName]\n    Compiler->>Compiler: Визначає ім'я властивості: \"Title\"\n    Compiler->>Runtime: OnPropertyChanged(\"Title\")\n    Runtime->>Runtime: PropertyChanged?.Invoke(...)\n    \n    style Compiler fill:#3b82f6,stroke:#1d4ed8,color:#ffffff\n    style Runtime fill:#10b981,stroke:#059669,color:#ffffff\n",[2989,4608,4609,4614,4619,4624,4629,4633,4638,4643,4648,4653,4658,4662,4667],{"__ignoreMap":3007},[3011,4610,4611],{"class":3013,"line":3014},[3011,4612,4613],{},"sequenceDiagram\n",[3011,4615,4616],{"class":3013,"line":3032},[3011,4617,4618],{},"    participant Code as Ваш код\n",[3011,4620,4621],{"class":3013,"line":3043},[3011,4622,4623],{},"    participant Compiler as C# Compiler\n",[3011,4625,4626],{"class":3013,"line":3049},[3011,4627,4628],{},"    participant Runtime as Runtime\n",[3011,4630,4631],{"class":3013,"line":3063},[3011,4632,3256],{},[3011,4634,4635],{"class":3013,"line":3069},[3011,4636,4637],{},"    Code->>Compiler: OnPropertyChanged()\n",[3011,4639,4640],{"class":3013,"line":3075},[3011,4641,4642],{},"    Note over Compiler: Бачить атрибут [CallerMemberName]\n",[3011,4644,4645],{"class":3013,"line":3089},[3011,4646,4647],{},"    Compiler->>Compiler: Визначає ім'я властивості: \"Title\"\n",[3011,4649,4650],{"class":3013,"line":3110},[3011,4651,4652],{},"    Compiler->>Runtime: OnPropertyChanged(\"Title\")\n",[3011,4654,4655],{"class":3013,"line":3116},[3011,4656,4657],{},"    Runtime->>Runtime: PropertyChanged?.Invoke(...)\n",[3011,4659,4660],{"class":3013,"line":3325},[3011,4661,3256],{},[3011,4663,4664],{"class":3013,"line":3338},[3011,4665,4666],{},"    style Compiler fill:#3b82f6,stroke:#1d4ed8,color:#ffffff\n",[3011,4668,4669],{"class":3013,"line":3348},[3011,4670,4671],{},"    style Runtime fill:#10b981,stroke:#059669,color:#ffffff\n",[2965,4673,4674],{},[2975,4675,4412],{},[2982,4677,4678,4681,4684],{},[2985,4679,4680],{},"✅ Compile-time перевірка — якщо властивість не існує, компілятор видасть помилку",[2985,4682,4683],{},"✅ Refactoring-safe — при перейменуванні властивості через IDE, ім'я оновиться автоматично",[2985,4685,4686,4687],{},"✅ Менше коду — не потрібно писати ",[2989,4688,4689],{},"nameof(Title)",[2965,4691,4692],{},[2975,4693,4694],{},"Альтернативи без CallerMemberName:",[3002,4696,4698],{"className":3004,"code":4697,"language":3006,"meta":3007,"style":3007},"// ❌ Рядковий літерал — помилки у runtime\nOnPropertyChanged(\"Title\");\n\n// ✅ nameof — compile-time перевірка, але більше коду\nOnPropertyChanged(nameof(Title));\n\n// ✅ CallerMemberName — найкраще рішення\nOnPropertyChanged();\n",[2989,4699,4700,4705,4715,4719,4724,4738,4742,4747],{"__ignoreMap":3007},[3011,4701,4702],{"class":3013,"line":3014},[3011,4703,4704],{"class":3213},"// ❌ Рядковий літерал — помилки у runtime\n",[3011,4706,4707,4709,4711,4713],{"class":3013,"line":3032},[3011,4708,3940],{"class":3092},[3011,4710,3096],{"class":3028},[3011,4712,4446],{"class":3894},[3011,4714,3898],{"class":3028},[3011,4716,4717],{"class":3013,"line":3043},[3011,4718,3427],{"emptyLinePlaceholder":3426},[3011,4720,4721],{"class":3013,"line":3049},[3011,4722,4723],{"class":3213},"// ✅ nameof — compile-time перевірка, але більше коду\n",[3011,4725,4726,4728,4730,4732,4734,4736],{"class":3013,"line":3063},[3011,4727,3940],{"class":3092},[3011,4729,3096],{"class":3028},[3011,4731,3099],{"class":3017},[3011,4733,3096],{"class":3028},[3011,4735,3402],{"class":3024},[3011,4737,3107],{"class":3028},[3011,4739,4740],{"class":3013,"line":3069},[3011,4741,3427],{"emptyLinePlaceholder":3426},[3011,4743,4744],{"class":3013,"line":3075},[3011,4745,4746],{"class":3213},"// ✅ CallerMemberName — найкраще рішення\n",[3011,4748,4749,4751],{"class":3013,"line":3089},[3011,4750,3940],{"class":3092},[3011,4752,4395],{"class":3028},[3181,4754],{},[2960,4756,4758,4759],{"id":4757},"setproperty-універсальний-метод-для-властивостей","SetProperty",[4760,4761,4762],"t",{},": Універсальний метод для властивостей",[2965,4764,4765,4766,3176,4768,4770],{},"Навіть з ",[2989,4767,3164],{},[2989,4769,4221],{},", кожна властивість потребує 7 рядків коду:",[3002,4772,4774],{"className":3004,"code":4773,"language":3006,"meta":3007,"style":3007},"private string _title;\npublic string Title\n{\n    get => _title;\n    set\n    {\n        _title = value;\n        OnPropertyChanged();\n    }\n}\n",[2989,4775,4776,4786,4794,4798,4808,4812,4816,4826,4832,4836],{"__ignoreMap":3007},[3011,4777,4778,4780,4782,4784],{"class":3013,"line":3014},[3011,4779,3018],{"class":3017},[3011,4781,3021],{"class":3017},[3011,4783,3333],{"class":3024},[3011,4785,3029],{"class":3028},[3011,4787,4788,4790,4792],{"class":3013,"line":3032},[3011,4789,3035],{"class":3017},[3011,4791,3021],{"class":3017},[3011,4793,3345],{"class":3024},[3011,4795,4796],{"class":3013,"line":3043},[3011,4797,3046],{"class":3028},[3011,4799,4800,4802,4804,4806],{"class":3013,"line":3049},[3011,4801,3052],{"class":3024},[3011,4803,3055],{"class":3028},[3011,4805,3361],{"class":3024},[3011,4807,3029],{"class":3028},[3011,4809,4810],{"class":3013,"line":3063},[3011,4811,3066],{"class":3024},[3011,4813,4814],{"class":3013,"line":3069},[3011,4815,3072],{"class":3028},[3011,4817,4818,4820,4822,4824],{"class":3013,"line":3075},[3011,4819,4500],{"class":3024},[3011,4821,3081],{"class":3028},[3011,4823,3084],{"class":3024},[3011,4825,3029],{"class":3028},[3011,4827,4828,4830],{"class":3013,"line":3089},[3011,4829,3093],{"class":3092},[3011,4831,4395],{"class":3028},[3011,4833,4834],{"class":3013,"line":3110},[3011,4835,3113],{"class":3028},[3011,4837,4838],{"class":3013,"line":3116},[3011,4839,3119],{"class":3028},[2965,4841,4842],{},"Якщо у ViewModel 20 властивостей — це 140 рядків однакового коду. Можна краще?",[3194,4844,4846,4847],{"id":4845},"реалізація-setproperty","Реалізація SetProperty",[4760,4848],{},[2965,4850,4851,4853,4854,4856],{},[2975,4852,4114],{}," Універсальний метод, що встановлює значення та викликає ",[2989,4855,3940],{}," тільки якщо значення змінилося.",[3002,4858,4860],{"className":3004,"code":4859,"language":3006,"meta":3007,"style":3007},"public abstract class BaseViewModel : INotifyPropertyChanged\n{\n    public event PropertyChangedEventHandler PropertyChanged;\n    \n    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)\n    {\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n    }\n    \n    // Універсальний метод для встановлення властивостей\n    protected bool SetProperty\u003CT>(ref T field, T value, [CallerMemberName] string propertyName = null)\n    {\n        // Перевірка: чи змінилося значення?\n        if (EqualityComparer\u003CT>.Default.Equals(field, value))\n        {\n            return false;  // Значення не змінилося — не викликаємо PropertyChanged\n        }\n        \n        // Встановлюємо нове значення\n        field = value;\n        \n        // Викликаємо PropertyChanged\n        OnPropertyChanged(propertyName);\n        \n        return true;  // Значення змінилося\n    }\n}\n",[2989,4861,4862,4876,4880,4892,4896,4922,4926,4950,4954,4958,4963,5015,5019,5024,5062,5066,5080,5084,5089,5094,5105,5109,5114,5124,5128,5141,5145],{"__ignoreMap":3007},[3011,4863,4864,4866,4868,4870,4872,4874],{"class":3013,"line":3014},[3011,4865,3035],{"class":3017},[3011,4867,4165],{"class":3017},[3011,4869,3221],{"class":3017},[3011,4871,4170],{"class":3224},[3011,4873,3228],{"class":3028},[3011,4875,3231],{"class":3224},[3011,4877,4878],{"class":3013,"line":3032},[3011,4879,3046],{"class":3028},[3011,4881,4882,4884,4886,4888,4890],{"class":3013,"line":3043},[3011,4883,3240],{"class":3017},[3011,4885,3243],{"class":3017},[3011,4887,3246],{"class":3224},[3011,4889,3249],{"class":3024},[3011,4891,3029],{"class":3028},[3011,4893,4894],{"class":3013,"line":3049},[3011,4895,3256],{"class":3028},[3011,4897,4898,4900,4902,4904,4906,4908,4910,4912,4914,4916,4918,4920],{"class":3013,"line":3063},[3011,4899,3261],{"class":3017},[3011,4901,4211],{"class":3017},[3011,4903,3264],{"class":3017},[3011,4905,3267],{"class":3092},[3011,4907,4218],{"class":3028},[3011,4909,4221],{"class":3224},[3011,4911,4224],{"class":3028},[3011,4913,3272],{"class":3017},[3011,4915,3275],{"class":3024},[3011,4917,3081],{"class":3028},[3011,4919,4233],{"class":3017},[3011,4921,3278],{"class":3028},[3011,4923,4924],{"class":3013,"line":3069},[3011,4925,3072],{"class":3028},[3011,4927,4928,4930,4932,4934,4936,4938,4940,4942,4944,4946,4948],{"class":3013,"line":3075},[3011,4929,3287],{"class":3024},[3011,4931,3290],{"class":3028},[3011,4933,3293],{"class":3092},[3011,4935,3096],{"class":3028},[3011,4937,3298],{"class":3017},[3011,4939,3301],{"class":3028},[3011,4941,3304],{"class":3017},[3011,4943,3307],{"class":3224},[3011,4945,3096],{"class":3028},[3011,4947,3312],{"class":3024},[3011,4949,3107],{"class":3028},[3011,4951,4952],{"class":3013,"line":3089},[3011,4953,3113],{"class":3028},[3011,4955,4956],{"class":3013,"line":3110},[3011,4957,3256],{"class":3028},[3011,4959,4960],{"class":3013,"line":3116},[3011,4961,4962],{"class":3213},"    // Універсальний метод для встановлення властивостей\n",[3011,4964,4965,4967,4970,4973,4976,4979,4982,4985,4988,4991,4993,4995,4998,5001,5003,5005,5007,5009,5011,5013],{"class":3013,"line":3325},[3011,4966,3261],{"class":3017},[3011,4968,4969],{"class":3017}," bool",[3011,4971,4972],{"class":3092}," SetProperty",[3011,4974,4975],{"class":3028},"\u003C",[3011,4977,4978],{"class":3224},"T",[3011,4980,4981],{"class":3028},">(",[3011,4983,4984],{"class":3017},"ref",[3011,4986,4987],{"class":3224}," T",[3011,4989,4990],{"class":3024}," field",[3011,4992,3301],{"class":3028},[3011,4994,4978],{"class":3224},[3011,4996,4997],{"class":3024}," value",[3011,4999,5000],{"class":3028},", [",[3011,5002,4221],{"class":3224},[3011,5004,4224],{"class":3028},[3011,5006,3272],{"class":3017},[3011,5008,3275],{"class":3024},[3011,5010,3081],{"class":3028},[3011,5012,4233],{"class":3017},[3011,5014,3278],{"class":3028},[3011,5016,5017],{"class":3013,"line":3338},[3011,5018,3072],{"class":3028},[3011,5020,5021],{"class":3013,"line":3348},[3011,5022,5023],{"class":3213},"        // Перевірка: чи змінилося значення?\n",[3011,5025,5026,5029,5032,5035,5037,5039,5042,5045,5047,5050,5052,5055,5057,5059],{"class":3013,"line":3353},[3011,5027,5028],{"class":4125},"        if",[3011,5030,5031],{"class":3028}," (",[3011,5033,5034],{"class":3024},"EqualityComparer",[3011,5036,4975],{"class":3028},[3011,5038,4978],{"class":3224},[3011,5040,5041],{"class":3028},">.",[3011,5043,5044],{"class":3024},"Default",[3011,5046,4010],{"class":3028},[3011,5048,5049],{"class":3092},"Equals",[3011,5051,3096],{"class":3028},[3011,5053,5054],{"class":3024},"field",[3011,5056,3301],{"class":3028},[3011,5058,3084],{"class":3024},[3011,5060,5061],{"class":3028},"))\n",[3011,5063,5064],{"class":3013,"line":3366},[3011,5065,3375],{"class":3028},[3011,5067,5068,5071,5074,5077],{"class":3013,"line":3372},[3011,5069,5070],{"class":4125},"            return",[3011,5072,5073],{"class":3017}," false",[3011,5075,5076],{"class":3028},";  ",[3011,5078,5079],{"class":3213},"// Значення не змінилося — не викликаємо PropertyChanged\n",[3011,5081,5082],{"class":3013,"line":3378},[3011,5083,3410],{"class":3028},[3011,5085,5086],{"class":3013,"line":3390},[3011,5087,5088],{"class":3028},"        \n",[3011,5090,5091],{"class":3013,"line":3407},[3011,5092,5093],{"class":3213},"        // Встановлюємо нове значення\n",[3011,5095,5096,5099,5101,5103],{"class":3013,"line":3413},[3011,5097,5098],{"class":3024},"        field",[3011,5100,3081],{"class":3028},[3011,5102,3084],{"class":3024},[3011,5104,3029],{"class":3028},[3011,5106,5107],{"class":3013,"line":3418},[3011,5108,5088],{"class":3028},[3011,5110,5111],{"class":3013,"line":3423},[3011,5112,5113],{"class":3213},"        // Викликаємо PropertyChanged\n",[3011,5115,5116,5118,5120,5122],{"class":3013,"line":3430},[3011,5117,3093],{"class":3092},[3011,5119,3096],{"class":3028},[3011,5121,3312],{"class":3024},[3011,5123,3898],{"class":3028},[3011,5125,5126],{"class":3013,"line":3436},[3011,5127,5088],{"class":3028},[3011,5129,5130,5133,5136,5138],{"class":3013,"line":3450},[3011,5131,5132],{"class":4125},"        return",[3011,5134,5135],{"class":3017}," true",[3011,5137,5076],{"class":3028},[3011,5139,5140],{"class":3213},"// Значення змінилося\n",[3011,5142,5143],{"class":3013,"line":3455},[3011,5144,3113],{"class":3028},[3011,5146,5147],{"class":3013,"line":3461},[3011,5148,3119],{"class":3028},[2965,5150,5151],{},[2975,5152,4276],{},[4278,5154,5155,5163,5180],{},[2985,5156,5157,5162],{},[2975,5158,5159],{},[2989,5160,5161],{},"ref T field"," — передаємо backing field за посиланням, щоб змінити його значення",[2985,5164,5165,5170,5171,3301,5173,3301,5176,5179],{},[2975,5166,5167],{},[2989,5168,5169],{},"EqualityComparer\u003CT>.Default"," — універсальне порівняння для будь-якого типу (працює для ",[2989,5172,3272],{},[2989,5174,5175],{},"int",[2989,5177,5178],{},"DateTime",", custom класів)",[2985,5181,5182,3139,5188,5191,5192,5195],{},[2975,5183,5184,5185],{},"Повертає ",[2989,5186,5187],{},"bool",[2989,5189,5190],{},"true"," якщо значення змінилося, ",[2989,5193,5194],{},"false"," якщо ні (корисно для додаткової логіки)",[3194,5197,5199,5200],{"id":5198},"використання-setproperty","Використання SetProperty",[4760,5201],{},[2965,5203,5204],{},[2975,5205,5206],{},"До (без SetProperty):",[3002,5208,5210],{"className":3004,"code":5209,"language":3006,"meta":3007,"style":3007},"private string _firstName;\npublic string FirstName\n{\n    get => _firstName;\n    set\n    {\n        _firstName = value;\n        OnPropertyChanged();\n    }\n}\n\nprivate string _lastName;\npublic string LastName\n{\n    get => _lastName;\n    set\n    {\n        _lastName = value;\n        OnPropertyChanged();\n    }\n}\n\nprivate int _age;\npublic int Age\n{\n    get => _age;\n    set\n    {\n        _age = value;\n        OnPropertyChanged();\n    }\n}\n",[2989,5211,5212,5222,5230,5234,5244,5248,5252,5262,5268,5272,5276,5280,5291,5300,5304,5315,5319,5323,5334,5340,5344,5348,5352,5364,5373,5377,5388,5392,5396,5407,5413,5417],{"__ignoreMap":3007},[3011,5213,5214,5216,5218,5220],{"class":3013,"line":3014},[3011,5215,3018],{"class":3017},[3011,5217,3021],{"class":3017},[3011,5219,3025],{"class":3024},[3011,5221,3029],{"class":3028},[3011,5223,5224,5226,5228],{"class":3013,"line":3032},[3011,5225,3035],{"class":3017},[3011,5227,3021],{"class":3017},[3011,5229,3040],{"class":3024},[3011,5231,5232],{"class":3013,"line":3043},[3011,5233,3046],{"class":3028},[3011,5235,5236,5238,5240,5242],{"class":3013,"line":3049},[3011,5237,3052],{"class":3024},[3011,5239,3055],{"class":3028},[3011,5241,3058],{"class":3024},[3011,5243,3029],{"class":3028},[3011,5245,5246],{"class":3013,"line":3063},[3011,5247,3066],{"class":3024},[3011,5249,5250],{"class":3013,"line":3069},[3011,5251,3072],{"class":3028},[3011,5253,5254,5256,5258,5260],{"class":3013,"line":3075},[3011,5255,3078],{"class":3024},[3011,5257,3081],{"class":3028},[3011,5259,3084],{"class":3024},[3011,5261,3029],{"class":3028},[3011,5263,5264,5266],{"class":3013,"line":3089},[3011,5265,3093],{"class":3092},[3011,5267,4395],{"class":3028},[3011,5269,5270],{"class":3013,"line":3110},[3011,5271,3113],{"class":3028},[3011,5273,5274],{"class":3013,"line":3116},[3011,5275,3119],{"class":3028},[3011,5277,5278],{"class":3013,"line":3325},[3011,5279,3427],{"emptyLinePlaceholder":3426},[3011,5281,5282,5284,5286,5289],{"class":3013,"line":3338},[3011,5283,3018],{"class":3017},[3011,5285,3021],{"class":3017},[3011,5287,5288],{"class":3024}," _lastName",[3011,5290,3029],{"class":3028},[3011,5292,5293,5295,5297],{"class":3013,"line":3348},[3011,5294,3035],{"class":3017},[3011,5296,3021],{"class":3017},[3011,5298,5299],{"class":3024}," LastName\n",[3011,5301,5302],{"class":3013,"line":3353},[3011,5303,3046],{"class":3028},[3011,5305,5306,5308,5310,5313],{"class":3013,"line":3366},[3011,5307,3052],{"class":3024},[3011,5309,3055],{"class":3028},[3011,5311,5312],{"class":3024},"_lastName",[3011,5314,3029],{"class":3028},[3011,5316,5317],{"class":3013,"line":3372},[3011,5318,3066],{"class":3024},[3011,5320,5321],{"class":3013,"line":3378},[3011,5322,3072],{"class":3028},[3011,5324,5325,5328,5330,5332],{"class":3013,"line":3390},[3011,5326,5327],{"class":3024},"        _lastName",[3011,5329,3081],{"class":3028},[3011,5331,3084],{"class":3024},[3011,5333,3029],{"class":3028},[3011,5335,5336,5338],{"class":3013,"line":3407},[3011,5337,3093],{"class":3092},[3011,5339,4395],{"class":3028},[3011,5341,5342],{"class":3013,"line":3413},[3011,5343,3113],{"class":3028},[3011,5345,5346],{"class":3013,"line":3418},[3011,5347,3119],{"class":3028},[3011,5349,5350],{"class":3013,"line":3423},[3011,5351,3427],{"emptyLinePlaceholder":3426},[3011,5353,5354,5356,5359,5362],{"class":3013,"line":3430},[3011,5355,3018],{"class":3017},[3011,5357,5358],{"class":3017}," int",[3011,5360,5361],{"class":3024}," _age",[3011,5363,3029],{"class":3028},[3011,5365,5366,5368,5370],{"class":3013,"line":3436},[3011,5367,3035],{"class":3017},[3011,5369,5358],{"class":3017},[3011,5371,5372],{"class":3024}," Age\n",[3011,5374,5375],{"class":3013,"line":3450},[3011,5376,3046],{"class":3028},[3011,5378,5379,5381,5383,5386],{"class":3013,"line":3455},[3011,5380,3052],{"class":3024},[3011,5382,3055],{"class":3028},[3011,5384,5385],{"class":3024},"_age",[3011,5387,3029],{"class":3028},[3011,5389,5390],{"class":3013,"line":3461},[3011,5391,3066],{"class":3024},[3011,5393,5394],{"class":3013,"line":3474},[3011,5395,3072],{"class":3028},[3011,5397,5398,5401,5403,5405],{"class":3013,"line":3479},[3011,5399,5400],{"class":3024},"        _age",[3011,5402,3081],{"class":3028},[3011,5404,3084],{"class":3024},[3011,5406,3029],{"class":3028},[3011,5408,5409,5411],{"class":3013,"line":3496},[3011,5410,3093],{"class":3092},[3011,5412,4395],{"class":3028},[3011,5414,5415],{"class":3013,"line":3501},[3011,5416,3113],{"class":3028},[3011,5418,5419],{"class":3013,"line":3526},[3011,5420,3119],{"class":3028},[2965,5422,5423],{},[2975,5424,5425],{},"Після (з SetProperty):",[3002,5427,5429],{"className":3004,"code":5428,"language":3006,"meta":3007,"style":3007},"private string _firstName;\npublic string FirstName\n{\n    get => _firstName;\n    set => SetProperty(ref _firstName, value);\n}\n\nprivate string _lastName;\npublic string LastName\n{\n    get => _lastName;\n    set => SetProperty(ref _lastName, value);\n}\n\nprivate int _age;\npublic int Age\n{\n    get => _age;\n    set => SetProperty(ref _age, value);\n}\n",[2989,5430,5431,5441,5449,5453,5463,5484,5488,5492,5502,5510,5514,5524,5544,5548,5552,5562,5570,5574,5584,5604],{"__ignoreMap":3007},[3011,5432,5433,5435,5437,5439],{"class":3013,"line":3014},[3011,5434,3018],{"class":3017},[3011,5436,3021],{"class":3017},[3011,5438,3025],{"class":3024},[3011,5440,3029],{"class":3028},[3011,5442,5443,5445,5447],{"class":3013,"line":3032},[3011,5444,3035],{"class":3017},[3011,5446,3021],{"class":3017},[3011,5448,3040],{"class":3024},[3011,5450,5451],{"class":3013,"line":3043},[3011,5452,3046],{"class":3028},[3011,5454,5455,5457,5459,5461],{"class":3013,"line":3049},[3011,5456,3052],{"class":3024},[3011,5458,3055],{"class":3028},[3011,5460,3058],{"class":3024},[3011,5462,3029],{"class":3028},[3011,5464,5465,5468,5470,5472,5474,5476,5478,5480,5482],{"class":3013,"line":3063},[3011,5466,5467],{"class":3024},"    set",[3011,5469,3055],{"class":3028},[3011,5471,4758],{"class":3092},[3011,5473,3096],{"class":3028},[3011,5475,4984],{"class":3017},[3011,5477,3025],{"class":3024},[3011,5479,3301],{"class":3028},[3011,5481,3084],{"class":3024},[3011,5483,3898],{"class":3028},[3011,5485,5486],{"class":3013,"line":3069},[3011,5487,3119],{"class":3028},[3011,5489,5490],{"class":3013,"line":3075},[3011,5491,3427],{"emptyLinePlaceholder":3426},[3011,5493,5494,5496,5498,5500],{"class":3013,"line":3089},[3011,5495,3018],{"class":3017},[3011,5497,3021],{"class":3017},[3011,5499,5288],{"class":3024},[3011,5501,3029],{"class":3028},[3011,5503,5504,5506,5508],{"class":3013,"line":3110},[3011,5505,3035],{"class":3017},[3011,5507,3021],{"class":3017},[3011,5509,5299],{"class":3024},[3011,5511,5512],{"class":3013,"line":3116},[3011,5513,3046],{"class":3028},[3011,5515,5516,5518,5520,5522],{"class":3013,"line":3325},[3011,5517,3052],{"class":3024},[3011,5519,3055],{"class":3028},[3011,5521,5312],{"class":3024},[3011,5523,3029],{"class":3028},[3011,5525,5526,5528,5530,5532,5534,5536,5538,5540,5542],{"class":3013,"line":3338},[3011,5527,5467],{"class":3024},[3011,5529,3055],{"class":3028},[3011,5531,4758],{"class":3092},[3011,5533,3096],{"class":3028},[3011,5535,4984],{"class":3017},[3011,5537,5288],{"class":3024},[3011,5539,3301],{"class":3028},[3011,5541,3084],{"class":3024},[3011,5543,3898],{"class":3028},[3011,5545,5546],{"class":3013,"line":3348},[3011,5547,3119],{"class":3028},[3011,5549,5550],{"class":3013,"line":3353},[3011,5551,3427],{"emptyLinePlaceholder":3426},[3011,5553,5554,5556,5558,5560],{"class":3013,"line":3366},[3011,5555,3018],{"class":3017},[3011,5557,5358],{"class":3017},[3011,5559,5361],{"class":3024},[3011,5561,3029],{"class":3028},[3011,5563,5564,5566,5568],{"class":3013,"line":3372},[3011,5565,3035],{"class":3017},[3011,5567,5358],{"class":3017},[3011,5569,5372],{"class":3024},[3011,5571,5572],{"class":3013,"line":3378},[3011,5573,3046],{"class":3028},[3011,5575,5576,5578,5580,5582],{"class":3013,"line":3390},[3011,5577,3052],{"class":3024},[3011,5579,3055],{"class":3028},[3011,5581,5385],{"class":3024},[3011,5583,3029],{"class":3028},[3011,5585,5586,5588,5590,5592,5594,5596,5598,5600,5602],{"class":3013,"line":3407},[3011,5587,5467],{"class":3024},[3011,5589,3055],{"class":3028},[3011,5591,4758],{"class":3092},[3011,5593,3096],{"class":3028},[3011,5595,4984],{"class":3017},[3011,5597,5361],{"class":3024},[3011,5599,3301],{"class":3028},[3011,5601,3084],{"class":3024},[3011,5603,3898],{"class":3028},[3011,5605,5606],{"class":3013,"line":3413},[3011,5607,3119],{"class":3028},[2965,5609,5610],{},[2975,5611,4412],{},[2982,5613,5614,5617,5624],{},[2985,5615,5616],{},"✅ Менше коду — 3 рядки замість 7",[2985,5618,5619,5620,5623],{},"✅ Автоматична перевірка на зміну — ",[2989,5621,5622],{},"PropertyChanged"," викликається тільки якщо значення змінилося",[2985,5625,5626],{},"✅ Універсальність — працює для будь-якого типу",[2965,5628,5629],{},[2975,5630,5631],{},"Порівняння кількості коду:",[5633,5634,5635,5651],"table",{},[5636,5637,5638],"thead",{},[5639,5640,5641,5645,5648],"tr",{},[5642,5643,5644],"th",{},"Підхід",[5642,5646,5647],{},"Рядків на властивість",[5642,5649,5650],{},"Рядків для 20 властивостей",[5652,5653,5654,5666,5677],"tbody",{},[5639,5655,5656,5660,5663],{},[5657,5658,5659],"td",{},"Без BaseViewModel",[5657,5661,5662],{},"10",[5657,5664,5665],{},"200",[5639,5667,5668,5671,5674],{},[5657,5669,5670],{},"З BaseViewModel",[5657,5672,5673],{},"7",[5657,5675,5676],{},"140",[5639,5678,5679,5682,5685],{},[5657,5680,5681],{},"З SetProperty",[5657,5683,5684],{},"3",[5657,5686,5687],{},"60",[4428,5689,5690,5693,5694,5697,5698,5701],{},[2975,5691,5692],{},"Expression-bodied setter:"," Синтаксис ",[2989,5695,5696],{},"set => SetProperty(...)"," — це скорочена форма для ",[2989,5699,5700],{},"set { SetProperty(...); }",". Працює з C# 7.0+.",[3194,5703,5705],{"id":5704},"чому-перевірка-на-зміну-важлива","Чому перевірка на зміну важлива",[2965,5707,5708],{},[2975,5709,5710],{},"Проблема без перевірки:",[3002,5712,5714],{"className":3004,"code":5713,"language":3006,"meta":3007,"style":3007},"// Без перевірки\npublic string Title\n{\n    get => _title;\n    set\n    {\n        _title = value;\n        OnPropertyChanged();  // Викликається завжди, навіть якщо значення не змінилося\n    }\n}\n\n// Використання\nviewModel.Title = \"Hello\";\nviewModel.Title = \"Hello\";  // ❌ PropertyChanged викликається знову, хоча значення те саме\nviewModel.Title = \"Hello\";  // ❌ І знову\n",[2989,5715,5716,5721,5729,5733,5743,5747,5751,5761,5770,5774,5778,5782,5787,5803,5820],{"__ignoreMap":3007},[3011,5717,5718],{"class":3013,"line":3014},[3011,5719,5720],{"class":3213},"// Без перевірки\n",[3011,5722,5723,5725,5727],{"class":3013,"line":3032},[3011,5724,3035],{"class":3017},[3011,5726,3021],{"class":3017},[3011,5728,3345],{"class":3024},[3011,5730,5731],{"class":3013,"line":3043},[3011,5732,3046],{"class":3028},[3011,5734,5735,5737,5739,5741],{"class":3013,"line":3049},[3011,5736,3052],{"class":3024},[3011,5738,3055],{"class":3028},[3011,5740,3361],{"class":3024},[3011,5742,3029],{"class":3028},[3011,5744,5745],{"class":3013,"line":3063},[3011,5746,3066],{"class":3024},[3011,5748,5749],{"class":3013,"line":3069},[3011,5750,3072],{"class":3028},[3011,5752,5753,5755,5757,5759],{"class":3013,"line":3075},[3011,5754,4500],{"class":3024},[3011,5756,3081],{"class":3028},[3011,5758,3084],{"class":3024},[3011,5760,3029],{"class":3028},[3011,5762,5763,5765,5767],{"class":3013,"line":3089},[3011,5764,3093],{"class":3092},[3011,5766,4513],{"class":3028},[3011,5768,5769],{"class":3213},"// Викликається завжди, навіть якщо значення не змінилося\n",[3011,5771,5772],{"class":3013,"line":3110},[3011,5773,3113],{"class":3028},[3011,5775,5776],{"class":3013,"line":3116},[3011,5777,3119],{"class":3028},[3011,5779,5780],{"class":3013,"line":3325},[3011,5781,3427],{"emptyLinePlaceholder":3426},[3011,5783,5784],{"class":3013,"line":3338},[3011,5785,5786],{"class":3213},"// Використання\n",[3011,5788,5789,5792,5794,5796,5798,5801],{"class":3013,"line":3348},[3011,5790,5791],{"class":3024},"viewModel",[3011,5793,4010],{"class":3028},[3011,5795,3402],{"class":3024},[3011,5797,3081],{"class":3028},[3011,5799,5800],{"class":3894},"\"Hello\"",[3011,5802,3029],{"class":3028},[3011,5804,5805,5807,5809,5811,5813,5815,5817],{"class":3013,"line":3353},[3011,5806,5791],{"class":3024},[3011,5808,4010],{"class":3028},[3011,5810,3402],{"class":3024},[3011,5812,3081],{"class":3028},[3011,5814,5800],{"class":3894},[3011,5816,5076],{"class":3028},[3011,5818,5819],{"class":3213},"// ❌ PropertyChanged викликається знову, хоча значення те саме\n",[3011,5821,5822,5824,5826,5828,5830,5832,5834],{"class":3013,"line":3366},[3011,5823,5791],{"class":3024},[3011,5825,4010],{"class":3028},[3011,5827,3402],{"class":3024},[3011,5829,3081],{"class":3028},[3011,5831,5800],{"class":3894},[3011,5833,5076],{"class":3028},[3011,5835,5836],{"class":3213},"// ❌ І знову\n",[2965,5838,5839],{},[2975,5840,3911],{},[2982,5842,5843,5846,5849],{},[2985,5844,5845],{},"UI перемальовується без потреби (performance overhead)",[2985,5847,5848],{},"Тригерять залежні властивості без причини",[2985,5850,5851],{},"Безкінечні цикли у складних сценаріях",[2965,5853,5854],{},[2975,5855,5856],{},"З перевіркою:",[3002,5858,5860],{"className":3004,"code":5859,"language":3006,"meta":3007,"style":3007},"viewModel.Title = \"Hello\";  // ✅ PropertyChanged викликається\nviewModel.Title = \"Hello\";  // ✅ Значення не змінилося — PropertyChanged НЕ викликається\nviewModel.Title = \"World\";  // ✅ PropertyChanged викликається\n",[2989,5861,5862,5879,5896],{"__ignoreMap":3007},[3011,5863,5864,5866,5868,5870,5872,5874,5876],{"class":3013,"line":3014},[3011,5865,5791],{"class":3024},[3011,5867,4010],{"class":3028},[3011,5869,3402],{"class":3024},[3011,5871,3081],{"class":3028},[3011,5873,5800],{"class":3894},[3011,5875,5076],{"class":3028},[3011,5877,5878],{"class":3213},"// ✅ PropertyChanged викликається\n",[3011,5880,5881,5883,5885,5887,5889,5891,5893],{"class":3013,"line":3032},[3011,5882,5791],{"class":3024},[3011,5884,4010],{"class":3028},[3011,5886,3402],{"class":3024},[3011,5888,3081],{"class":3028},[3011,5890,5800],{"class":3894},[3011,5892,5076],{"class":3028},[3011,5894,5895],{"class":3213},"// ✅ Значення не змінилося — PropertyChanged НЕ викликається\n",[3011,5897,5898,5900,5902,5904,5906,5909,5911],{"class":3013,"line":3043},[3011,5899,5791],{"class":3024},[3011,5901,4010],{"class":3028},[3011,5903,3402],{"class":3024},[3011,5905,3081],{"class":3028},[3011,5907,5908],{"class":3894},"\"World\"",[3011,5910,5076],{"class":3028},[3011,5912,5878],{"class":3213},[3181,5914],{},[2960,5916,5918],{"id":5917},"обчислювані-властивості-та-залежності","Обчислювані властивості та залежності",[2965,5920,5921,5922,5925,5926,3176,5928,4010],{},"У реальних додатках властивості часто залежать одна від одної. Наприклад, ",[2989,5923,5924],{},"FullName"," залежить від ",[2989,5927,3104],{},[2989,5929,5930],{},"LastName",[3194,5932,5934],{"id":5933},"проблема-залежних-властивостей","Проблема залежних властивостей",[2965,5936,5937,5940,5941,5943,5944,3176,5946,4010],{},[2975,5938,5939],{},"Сценарій:"," Властивість ",[2989,5942,5924],{}," обчислюється з ",[2989,5945,3104],{},[2989,5947,5930],{},[3002,5949,5951],{"className":3004,"code":5950,"language":3006,"meta":3007,"style":3007},"public class PersonViewModel : BaseViewModel\n{\n    private string _firstName;\n    public string FirstName\n    {\n        get => _firstName;\n        set => SetProperty(ref _firstName, value);\n    }\n    \n    private string _lastName;\n    public string LastName\n    {\n        get => _lastName;\n        set => SetProperty(ref _lastName, value);\n    }\n    \n    // Обчислювана властивість\n    public string FullName => $\"{FirstName} {LastName}\";\n}\n",[2989,5952,5953,5966,5970,5980,5988,5992,6002,6023,6027,6031,6041,6049,6053,6063,6083,6087,6091,6096,6128],{"__ignoreMap":3007},[3011,5954,5955,5957,5959,5962,5964],{"class":3013,"line":3014},[3011,5956,3035],{"class":3017},[3011,5958,3221],{"class":3017},[3011,5960,5961],{"class":3224}," PersonViewModel",[3011,5963,3228],{"class":3028},[3011,5965,4329],{"class":3224},[3011,5967,5968],{"class":3013,"line":3032},[3011,5969,3046],{"class":3028},[3011,5971,5972,5974,5976,5978],{"class":3013,"line":3043},[3011,5973,3328],{"class":3017},[3011,5975,3021],{"class":3017},[3011,5977,3025],{"class":3024},[3011,5979,3029],{"class":3028},[3011,5981,5982,5984,5986],{"class":3013,"line":3049},[3011,5983,3240],{"class":3017},[3011,5985,3021],{"class":3017},[3011,5987,3040],{"class":3024},[3011,5989,5990],{"class":3013,"line":3063},[3011,5991,3072],{"class":3028},[3011,5993,5994,5996,5998,6000],{"class":3013,"line":3069},[3011,5995,3356],{"class":3017},[3011,5997,3055],{"class":3028},[3011,5999,3058],{"class":3024},[3011,6001,3029],{"class":3028},[3011,6003,6004,6007,6009,6011,6013,6015,6017,6019,6021],{"class":3013,"line":3075},[3011,6005,6006],{"class":3017},"        set",[3011,6008,3055],{"class":3028},[3011,6010,4758],{"class":3092},[3011,6012,3096],{"class":3028},[3011,6014,4984],{"class":3017},[3011,6016,3025],{"class":3024},[3011,6018,3301],{"class":3028},[3011,6020,3084],{"class":3024},[3011,6022,3898],{"class":3028},[3011,6024,6025],{"class":3013,"line":3089},[3011,6026,3113],{"class":3028},[3011,6028,6029],{"class":3013,"line":3110},[3011,6030,3256],{"class":3028},[3011,6032,6033,6035,6037,6039],{"class":3013,"line":3116},[3011,6034,3328],{"class":3017},[3011,6036,3021],{"class":3017},[3011,6038,5288],{"class":3024},[3011,6040,3029],{"class":3028},[3011,6042,6043,6045,6047],{"class":3013,"line":3325},[3011,6044,3240],{"class":3017},[3011,6046,3021],{"class":3017},[3011,6048,5299],{"class":3024},[3011,6050,6051],{"class":3013,"line":3338},[3011,6052,3072],{"class":3028},[3011,6054,6055,6057,6059,6061],{"class":3013,"line":3348},[3011,6056,3356],{"class":3017},[3011,6058,3055],{"class":3028},[3011,6060,5312],{"class":3024},[3011,6062,3029],{"class":3028},[3011,6064,6065,6067,6069,6071,6073,6075,6077,6079,6081],{"class":3013,"line":3353},[3011,6066,6006],{"class":3017},[3011,6068,3055],{"class":3028},[3011,6070,4758],{"class":3092},[3011,6072,3096],{"class":3028},[3011,6074,4984],{"class":3017},[3011,6076,5288],{"class":3024},[3011,6078,3301],{"class":3028},[3011,6080,3084],{"class":3024},[3011,6082,3898],{"class":3028},[3011,6084,6085],{"class":3013,"line":3366},[3011,6086,3113],{"class":3028},[3011,6088,6089],{"class":3013,"line":3372},[3011,6090,3256],{"class":3028},[3011,6092,6093],{"class":3013,"line":3378},[3011,6094,6095],{"class":3213},"    // Обчислювана властивість\n",[3011,6097,6098,6100,6102,6105,6107,6110,6112,6114,6116,6119,6121,6123,6126],{"class":3013,"line":3390},[3011,6099,3240],{"class":3017},[3011,6101,3021],{"class":3017},[3011,6103,6104],{"class":3024}," FullName",[3011,6106,3055],{"class":3028},[3011,6108,6109],{"class":3894},"$\"",[3011,6111,4022],{"class":4021},[3011,6113,3104],{"class":3024},[3011,6115,4027],{"class":4021},[3011,6117,6118],{"class":4021}," {",[3011,6120,5930],{"class":3024},[3011,6122,4027],{"class":4021},[3011,6124,6125],{"class":3894},"\"",[3011,6127,3029],{"class":3028},[3011,6129,6130],{"class":3013,"line":3407},[3011,6131,3119],{"class":3028},[2965,6133,6134],{},[2975,6135,3202],{},[3002,6137,6141],{"className":6138,"code":6139,"language":6140,"meta":3007,"style":3007},"language-xml shiki shiki-themes light-plus dark-plus dark-plus","\u003CTextBlock Text=\"{Binding FullName}\"/>\n","xml",[2989,6142,6143],{"__ignoreMap":3007},[3011,6144,6145,6148,6152,6156,6159,6163],{"class":3013,"line":3014},[3011,6146,4975],{"class":6147},"s0P7L",[3011,6149,6151],{"class":6150},"sKtos","TextBlock",[3011,6153,6155],{"class":6154},"sa4r_"," Text",[3011,6157,6158],{"class":3028},"=",[3011,6160,6162],{"class":6161},"su9tN","\"{Binding FullName}\"",[3011,6164,6165],{"class":6147},"/>\n",[2965,6167,6168,6169,6171,6172,6174,6175,6178,6179,6181,6182,6184],{},"Коли користувач змінює ",[2989,6170,3104],{},", UI для ",[2989,6173,5924],{}," ",[2975,6176,6177],{},"не оновлюється",", бо ",[2989,6180,5622],{}," для ",[2989,6183,5924],{}," не викликається.",[3754,6186,6187],{},[3002,6188,6190],{"className":3758,"code":6189,"language":3754,"meta":3007,"style":3007},"sequenceDiagram\n    participant User as Користувач\n    participant View as View\n    participant VM as ViewModel\n    \n    User->>View: Змінює FirstName на \"Іван\"\n    View->>VM: FirstName = \"Іван\"\n    VM->>VM: SetProperty(ref _firstName, \"Іван\")\n    VM->>View: PropertyChanged(\"FirstName\")\n    View->>View: Оновлює TextBox для FirstName\n    \n    Note over View,VM: ❌ FullName НЕ оновлюється\n    \n    style VM fill:#ef4444,stroke:#b91c1c,color:#ffffff\n",[2989,6191,6192,6196,6201,6206,6211,6215,6220,6225,6230,6235,6240,6244,6249,6253],{"__ignoreMap":3007},[3011,6193,6194],{"class":3013,"line":3014},[3011,6195,4613],{},[3011,6197,6198],{"class":3013,"line":3032},[3011,6199,6200],{},"    participant User as Користувач\n",[3011,6202,6203],{"class":3013,"line":3043},[3011,6204,6205],{},"    participant View as View\n",[3011,6207,6208],{"class":3013,"line":3049},[3011,6209,6210],{},"    participant VM as ViewModel\n",[3011,6212,6213],{"class":3013,"line":3063},[3011,6214,3256],{},[3011,6216,6217],{"class":3013,"line":3069},[3011,6218,6219],{},"    User->>View: Змінює FirstName на \"Іван\"\n",[3011,6221,6222],{"class":3013,"line":3075},[3011,6223,6224],{},"    View->>VM: FirstName = \"Іван\"\n",[3011,6226,6227],{"class":3013,"line":3089},[3011,6228,6229],{},"    VM->>VM: SetProperty(ref _firstName, \"Іван\")\n",[3011,6231,6232],{"class":3013,"line":3110},[3011,6233,6234],{},"    VM->>View: PropertyChanged(\"FirstName\")\n",[3011,6236,6237],{"class":3013,"line":3116},[3011,6238,6239],{},"    View->>View: Оновлює TextBox для FirstName\n",[3011,6241,6242],{"class":3013,"line":3325},[3011,6243,3256],{},[3011,6245,6246],{"class":3013,"line":3338},[3011,6247,6248],{},"    Note over View,VM: ❌ FullName НЕ оновлюється\n",[3011,6250,6251],{"class":3013,"line":3348},[3011,6252,3256],{},[3011,6254,6255],{"class":3013,"line":3353},[3011,6256,6257],{},"    style VM fill:#ef4444,stroke:#b91c1c,color:#ffffff\n",[3194,6259,6261],{"id":6260},"рішення-ручне-сповіщення-про-залежні-властивості","Рішення: Ручне сповіщення про залежні властивості",[2965,6263,6264],{},[2975,6265,6266],{},"Підхід 1: Викликати OnPropertyChanged вручну",[3002,6268,6270],{"className":3004,"code":6269,"language":3006,"meta":3007,"style":3007},"public string FirstName\n{\n    get => _firstName;\n    set\n    {\n        if (SetProperty(ref _firstName, value))\n        {\n            // Якщо FirstName змінився — сповістити про FullName\n            OnPropertyChanged(nameof(FullName));\n        }\n    }\n}\n\npublic string LastName\n{\n    get => _lastName;\n    set\n    {\n        if (SetProperty(ref _lastName, value))\n        {\n            // Якщо LastName змінився — сповістити про FullName\n            OnPropertyChanged(nameof(FullName));\n        }\n    }\n}\n\npublic string FullName => $\"{FirstName} {LastName}\";\n",[2989,6271,6272,6280,6284,6294,6298,6302,6322,6326,6331,6345,6349,6353,6357,6361,6369,6373,6383,6387,6391,6411,6415,6420,6434,6438,6442,6446,6450],{"__ignoreMap":3007},[3011,6273,6274,6276,6278],{"class":3013,"line":3014},[3011,6275,3035],{"class":3017},[3011,6277,3021],{"class":3017},[3011,6279,3040],{"class":3024},[3011,6281,6282],{"class":3013,"line":3032},[3011,6283,3046],{"class":3028},[3011,6285,6286,6288,6290,6292],{"class":3013,"line":3043},[3011,6287,3052],{"class":3024},[3011,6289,3055],{"class":3028},[3011,6291,3058],{"class":3024},[3011,6293,3029],{"class":3028},[3011,6295,6296],{"class":3013,"line":3049},[3011,6297,3066],{"class":3024},[3011,6299,6300],{"class":3013,"line":3063},[3011,6301,3072],{"class":3028},[3011,6303,6304,6306,6308,6310,6312,6314,6316,6318,6320],{"class":3013,"line":3069},[3011,6305,5028],{"class":4125},[3011,6307,5031],{"class":3028},[3011,6309,4758],{"class":3092},[3011,6311,3096],{"class":3028},[3011,6313,4984],{"class":3017},[3011,6315,3025],{"class":3024},[3011,6317,3301],{"class":3028},[3011,6319,3084],{"class":3024},[3011,6321,5061],{"class":3028},[3011,6323,6324],{"class":3013,"line":3075},[3011,6325,3375],{"class":3028},[3011,6327,6328],{"class":3013,"line":3089},[3011,6329,6330],{"class":3213},"            // Якщо FirstName змінився — сповістити про FullName\n",[3011,6332,6333,6335,6337,6339,6341,6343],{"class":3013,"line":3110},[3011,6334,3393],{"class":3092},[3011,6336,3096],{"class":3028},[3011,6338,3099],{"class":3017},[3011,6340,3096],{"class":3028},[3011,6342,5924],{"class":3024},[3011,6344,3107],{"class":3028},[3011,6346,6347],{"class":3013,"line":3116},[3011,6348,3410],{"class":3028},[3011,6350,6351],{"class":3013,"line":3325},[3011,6352,3113],{"class":3028},[3011,6354,6355],{"class":3013,"line":3338},[3011,6356,3119],{"class":3028},[3011,6358,6359],{"class":3013,"line":3348},[3011,6360,3427],{"emptyLinePlaceholder":3426},[3011,6362,6363,6365,6367],{"class":3013,"line":3353},[3011,6364,3035],{"class":3017},[3011,6366,3021],{"class":3017},[3011,6368,5299],{"class":3024},[3011,6370,6371],{"class":3013,"line":3366},[3011,6372,3046],{"class":3028},[3011,6374,6375,6377,6379,6381],{"class":3013,"line":3372},[3011,6376,3052],{"class":3024},[3011,6378,3055],{"class":3028},[3011,6380,5312],{"class":3024},[3011,6382,3029],{"class":3028},[3011,6384,6385],{"class":3013,"line":3378},[3011,6386,3066],{"class":3024},[3011,6388,6389],{"class":3013,"line":3390},[3011,6390,3072],{"class":3028},[3011,6392,6393,6395,6397,6399,6401,6403,6405,6407,6409],{"class":3013,"line":3407},[3011,6394,5028],{"class":4125},[3011,6396,5031],{"class":3028},[3011,6398,4758],{"class":3092},[3011,6400,3096],{"class":3028},[3011,6402,4984],{"class":3017},[3011,6404,5288],{"class":3024},[3011,6406,3301],{"class":3028},[3011,6408,3084],{"class":3024},[3011,6410,5061],{"class":3028},[3011,6412,6413],{"class":3013,"line":3413},[3011,6414,3375],{"class":3028},[3011,6416,6417],{"class":3013,"line":3418},[3011,6418,6419],{"class":3213},"            // Якщо LastName змінився — сповістити про FullName\n",[3011,6421,6422,6424,6426,6428,6430,6432],{"class":3013,"line":3423},[3011,6423,3393],{"class":3092},[3011,6425,3096],{"class":3028},[3011,6427,3099],{"class":3017},[3011,6429,3096],{"class":3028},[3011,6431,5924],{"class":3024},[3011,6433,3107],{"class":3028},[3011,6435,6436],{"class":3013,"line":3430},[3011,6437,3410],{"class":3028},[3011,6439,6440],{"class":3013,"line":3436},[3011,6441,3113],{"class":3028},[3011,6443,6444],{"class":3013,"line":3450},[3011,6445,3119],{"class":3028},[3011,6447,6448],{"class":3013,"line":3455},[3011,6449,3427],{"emptyLinePlaceholder":3426},[3011,6451,6452,6454,6456,6458,6460,6462,6464,6466,6468,6470,6472,6474,6476],{"class":3013,"line":3461},[3011,6453,3035],{"class":3017},[3011,6455,3021],{"class":3017},[3011,6457,6104],{"class":3024},[3011,6459,3055],{"class":3028},[3011,6461,6109],{"class":3894},[3011,6463,4022],{"class":4021},[3011,6465,3104],{"class":3024},[3011,6467,4027],{"class":4021},[3011,6469,6118],{"class":4021},[3011,6471,5930],{"class":3024},[3011,6473,4027],{"class":4021},[3011,6475,6125],{"class":3894},[3011,6477,3029],{"class":3028},[2965,6479,6480],{},[2975,6481,4412],{},[2982,6483,6484,6487],{},[2985,6485,6486],{},"✅ Працює",[2985,6488,6489],{},"✅ Явно видно залежності",[2965,6491,6492],{},[2975,6493,6494],{},"Недоліки:",[2982,6496,6497,6503],{},[2985,6498,6499,6500],{},"⚠️ Легко забути додати ",[2989,6501,6502],{},"OnPropertyChanged(nameof(FullName))",[2985,6504,6505,6506,6508],{},"⚠️ Якщо ",[2989,6507,5924],{}," залежить від 5 властивостей — потрібно додати у 5 місцях",[3194,6510,6512],{"id":6511},"рішення-setproperty-з-callback","Рішення: SetProperty з callback",[2965,6514,6515],{},[2975,6516,6517],{},"Підхід 2: Розширити SetProperty для підтримки callback",[3002,6519,6521],{"className":3004,"code":6520,"language":3006,"meta":3007,"style":3007},"public abstract class BaseViewModel : INotifyPropertyChanged\n{\n    // ... попередній код\n    \n    // Перевантаження з callback\n    protected bool SetProperty\u003CT>(\n        ref T field, \n        T value, \n        Action onChanged = null,\n        [CallerMemberName] string propertyName = null)\n    {\n        if (EqualityComparer\u003CT>.Default.Equals(field, value))\n        {\n            return false;\n        }\n        \n        field = value;\n        OnPropertyChanged(propertyName);\n        \n        // Викликаємо callback після зміни\n        onChanged?.Invoke();\n        \n        return true;\n    }\n}\n",[2989,6522,6523,6537,6541,6546,6550,6555,6570,6582,6591,6606,6625,6629,6659,6663,6671,6675,6679,6689,6699,6703,6708,6719,6723,6731,6735],{"__ignoreMap":3007},[3011,6524,6525,6527,6529,6531,6533,6535],{"class":3013,"line":3014},[3011,6526,3035],{"class":3017},[3011,6528,4165],{"class":3017},[3011,6530,3221],{"class":3017},[3011,6532,4170],{"class":3224},[3011,6534,3228],{"class":3028},[3011,6536,3231],{"class":3224},[3011,6538,6539],{"class":3013,"line":3032},[3011,6540,3046],{"class":3028},[3011,6542,6543],{"class":3013,"line":3043},[3011,6544,6545],{"class":3213},"    // ... попередній код\n",[3011,6547,6548],{"class":3013,"line":3049},[3011,6549,3256],{"class":3028},[3011,6551,6552],{"class":3013,"line":3063},[3011,6553,6554],{"class":3213},"    // Перевантаження з callback\n",[3011,6556,6557,6559,6561,6563,6565,6567],{"class":3013,"line":3069},[3011,6558,3261],{"class":3017},[3011,6560,4969],{"class":3017},[3011,6562,4972],{"class":3092},[3011,6564,4975],{"class":3028},[3011,6566,4978],{"class":3224},[3011,6568,6569],{"class":3028},">(\n",[3011,6571,6572,6575,6577,6579],{"class":3013,"line":3075},[3011,6573,6574],{"class":3017},"        ref",[3011,6576,4987],{"class":3224},[3011,6578,4990],{"class":3024},[3011,6580,6581],{"class":3028},", \n",[3011,6583,6584,6587,6589],{"class":3013,"line":3089},[3011,6585,6586],{"class":3224},"        T",[3011,6588,4997],{"class":3024},[3011,6590,6581],{"class":3028},[3011,6592,6593,6596,6599,6601,6603],{"class":3013,"line":3110},[3011,6594,6595],{"class":3224},"        Action",[3011,6597,6598],{"class":3024}," onChanged",[3011,6600,3081],{"class":3028},[3011,6602,4233],{"class":3017},[3011,6604,6605],{"class":3028},",\n",[3011,6607,6608,6611,6613,6615,6617,6619,6621,6623],{"class":3013,"line":3116},[3011,6609,6610],{"class":3028},"        [",[3011,6612,4221],{"class":3224},[3011,6614,4224],{"class":3028},[3011,6616,3272],{"class":3017},[3011,6618,3275],{"class":3024},[3011,6620,3081],{"class":3028},[3011,6622,4233],{"class":3017},[3011,6624,3278],{"class":3028},[3011,6626,6627],{"class":3013,"line":3325},[3011,6628,3072],{"class":3028},[3011,6630,6631,6633,6635,6637,6639,6641,6643,6645,6647,6649,6651,6653,6655,6657],{"class":3013,"line":3338},[3011,6632,5028],{"class":4125},[3011,6634,5031],{"class":3028},[3011,6636,5034],{"class":3024},[3011,6638,4975],{"class":3028},[3011,6640,4978],{"class":3224},[3011,6642,5041],{"class":3028},[3011,6644,5044],{"class":3024},[3011,6646,4010],{"class":3028},[3011,6648,5049],{"class":3092},[3011,6650,3096],{"class":3028},[3011,6652,5054],{"class":3024},[3011,6654,3301],{"class":3028},[3011,6656,3084],{"class":3024},[3011,6658,5061],{"class":3028},[3011,6660,6661],{"class":3013,"line":3348},[3011,6662,3375],{"class":3028},[3011,6664,6665,6667,6669],{"class":3013,"line":3353},[3011,6666,5070],{"class":4125},[3011,6668,5073],{"class":3017},[3011,6670,3029],{"class":3028},[3011,6672,6673],{"class":3013,"line":3366},[3011,6674,3410],{"class":3028},[3011,6676,6677],{"class":3013,"line":3372},[3011,6678,5088],{"class":3028},[3011,6680,6681,6683,6685,6687],{"class":3013,"line":3378},[3011,6682,5098],{"class":3024},[3011,6684,3081],{"class":3028},[3011,6686,3084],{"class":3024},[3011,6688,3029],{"class":3028},[3011,6690,6691,6693,6695,6697],{"class":3013,"line":3390},[3011,6692,3093],{"class":3092},[3011,6694,3096],{"class":3028},[3011,6696,3312],{"class":3024},[3011,6698,3898],{"class":3028},[3011,6700,6701],{"class":3013,"line":3407},[3011,6702,5088],{"class":3028},[3011,6704,6705],{"class":3013,"line":3413},[3011,6706,6707],{"class":3213},"        // Викликаємо callback після зміни\n",[3011,6709,6710,6713,6715,6717],{"class":3013,"line":3418},[3011,6711,6712],{"class":3024},"        onChanged",[3011,6714,3290],{"class":3028},[3011,6716,3293],{"class":3092},[3011,6718,4395],{"class":3028},[3011,6720,6721],{"class":3013,"line":3423},[3011,6722,5088],{"class":3028},[3011,6724,6725,6727,6729],{"class":3013,"line":3430},[3011,6726,5132],{"class":4125},[3011,6728,5135],{"class":3017},[3011,6730,3029],{"class":3028},[3011,6732,6733],{"class":3013,"line":3436},[3011,6734,3113],{"class":3028},[3011,6736,6737],{"class":3013,"line":3450},[3011,6738,3119],{"class":3028},[2965,6740,6741],{},[2975,6742,4311],{},[3002,6744,6746],{"className":3004,"code":6745,"language":3006,"meta":3007,"style":3007},"public string FirstName\n{\n    get => _firstName;\n    set => SetProperty(ref _firstName, value, onChanged: () => OnPropertyChanged(nameof(FullName)));\n}\n\npublic string LastName\n{\n    get => _lastName;\n    set => SetProperty(ref _lastName, value, onChanged: () => OnPropertyChanged(nameof(FullName)));\n}\n\npublic string FullName => $\"{FirstName} {LastName}\";\n",[2989,6747,6748,6756,6760,6770,6809,6813,6817,6825,6829,6839,6875,6879,6883],{"__ignoreMap":3007},[3011,6749,6750,6752,6754],{"class":3013,"line":3014},[3011,6751,3035],{"class":3017},[3011,6753,3021],{"class":3017},[3011,6755,3040],{"class":3024},[3011,6757,6758],{"class":3013,"line":3032},[3011,6759,3046],{"class":3028},[3011,6761,6762,6764,6766,6768],{"class":3013,"line":3043},[3011,6763,3052],{"class":3024},[3011,6765,3055],{"class":3028},[3011,6767,3058],{"class":3024},[3011,6769,3029],{"class":3028},[3011,6771,6772,6774,6776,6778,6780,6782,6784,6786,6788,6790,6793,6796,6798,6800,6802,6804,6806],{"class":3013,"line":3049},[3011,6773,5467],{"class":3024},[3011,6775,3055],{"class":3028},[3011,6777,4758],{"class":3092},[3011,6779,3096],{"class":3028},[3011,6781,4984],{"class":3017},[3011,6783,3025],{"class":3024},[3011,6785,3301],{"class":3028},[3011,6787,3084],{"class":3024},[3011,6789,3301],{"class":3028},[3011,6791,6792],{"class":3024},"onChanged",[3011,6794,6795],{"class":3028},": () => ",[3011,6797,3940],{"class":3092},[3011,6799,3096],{"class":3028},[3011,6801,3099],{"class":3017},[3011,6803,3096],{"class":3028},[3011,6805,5924],{"class":3024},[3011,6807,6808],{"class":3028},")));\n",[3011,6810,6811],{"class":3013,"line":3063},[3011,6812,3119],{"class":3028},[3011,6814,6815],{"class":3013,"line":3069},[3011,6816,3427],{"emptyLinePlaceholder":3426},[3011,6818,6819,6821,6823],{"class":3013,"line":3075},[3011,6820,3035],{"class":3017},[3011,6822,3021],{"class":3017},[3011,6824,5299],{"class":3024},[3011,6826,6827],{"class":3013,"line":3089},[3011,6828,3046],{"class":3028},[3011,6830,6831,6833,6835,6837],{"class":3013,"line":3110},[3011,6832,3052],{"class":3024},[3011,6834,3055],{"class":3028},[3011,6836,5312],{"class":3024},[3011,6838,3029],{"class":3028},[3011,6840,6841,6843,6845,6847,6849,6851,6853,6855,6857,6859,6861,6863,6865,6867,6869,6871,6873],{"class":3013,"line":3116},[3011,6842,5467],{"class":3024},[3011,6844,3055],{"class":3028},[3011,6846,4758],{"class":3092},[3011,6848,3096],{"class":3028},[3011,6850,4984],{"class":3017},[3011,6852,5288],{"class":3024},[3011,6854,3301],{"class":3028},[3011,6856,3084],{"class":3024},[3011,6858,3301],{"class":3028},[3011,6860,6792],{"class":3024},[3011,6862,6795],{"class":3028},[3011,6864,3940],{"class":3092},[3011,6866,3096],{"class":3028},[3011,6868,3099],{"class":3017},[3011,6870,3096],{"class":3028},[3011,6872,5924],{"class":3024},[3011,6874,6808],{"class":3028},[3011,6876,6877],{"class":3013,"line":3325},[3011,6878,3119],{"class":3028},[3011,6880,6881],{"class":3013,"line":3338},[3011,6882,3427],{"emptyLinePlaceholder":3426},[3011,6884,6885,6887,6889,6891,6893,6895,6897,6899,6901,6903,6905,6907,6909],{"class":3013,"line":3348},[3011,6886,3035],{"class":3017},[3011,6888,3021],{"class":3017},[3011,6890,6104],{"class":3024},[3011,6892,3055],{"class":3028},[3011,6894,6109],{"class":3894},[3011,6896,4022],{"class":4021},[3011,6898,3104],{"class":3024},[3011,6900,4027],{"class":4021},[3011,6902,6118],{"class":4021},[3011,6904,5930],{"class":3024},[3011,6906,4027],{"class":4021},[3011,6908,6125],{"class":3894},[3011,6910,3029],{"class":3028},[2965,6912,6913],{},[2975,6914,4412],{},[2982,6916,6917,6920],{},[2985,6918,6919],{},"✅ Компактніше",[2985,6921,6922],{},"✅ Callback у одному рядку",[3194,6924,6926],{"id":6925},"складні-залежності-множинні-властивості","Складні залежності: Множинні властивості",[2965,6928,6929,5940,6931,5925,6934,3301,6936,3301,6938,4010],{},[2975,6930,5939],{},[2989,6932,6933],{},"IsValid",[2989,6935,3104],{},[2989,6937,5930],{},[2989,6939,6940],{},"Email",[3002,6942,6944],{"className":3004,"code":6943,"language":3006,"meta":3007,"style":3007},"public class PersonViewModel : BaseViewModel\n{\n    private string _firstName;\n    public string FirstName\n    {\n        get => _firstName;\n        set => SetProperty(ref _firstName, value, onChanged: NotifyValidationChanged);\n    }\n    \n    private string _lastName;\n    public string LastName\n    {\n        get => _lastName;\n        set => SetProperty(ref _lastName, value, onChanged: NotifyValidationChanged);\n    }\n    \n    private string _email;\n    public string Email\n    {\n        get => _email;\n        set => SetProperty(ref _email, value, onChanged: NotifyValidationChanged);\n    }\n    \n    // Обчислювана властивість\n    public bool IsValid =>\n        !string.IsNullOrWhiteSpace(FirstName) &&\n        !string.IsNullOrWhiteSpace(LastName) &&\n        !string.IsNullOrWhiteSpace(Email) &&\n        Email.Contains(\"@\");\n    \n    // Централізований метод для сповіщення про валідацію\n    private void NotifyValidationChanged()\n    {\n        OnPropertyChanged(nameof(IsValid));\n    }\n}\n",[2989,6945,6946,6958,6962,6972,6980,6984,6994,7024,7028,7032,7042,7050,7054,7064,7092,7096,7100,7111,7120,7124,7135,7163,7167,7171,7175,7187,7206,7222,7238,7255,7259,7264,7276,7280,7294,7298],{"__ignoreMap":3007},[3011,6947,6948,6950,6952,6954,6956],{"class":3013,"line":3014},[3011,6949,3035],{"class":3017},[3011,6951,3221],{"class":3017},[3011,6953,5961],{"class":3224},[3011,6955,3228],{"class":3028},[3011,6957,4329],{"class":3224},[3011,6959,6960],{"class":3013,"line":3032},[3011,6961,3046],{"class":3028},[3011,6963,6964,6966,6968,6970],{"class":3013,"line":3043},[3011,6965,3328],{"class":3017},[3011,6967,3021],{"class":3017},[3011,6969,3025],{"class":3024},[3011,6971,3029],{"class":3028},[3011,6973,6974,6976,6978],{"class":3013,"line":3049},[3011,6975,3240],{"class":3017},[3011,6977,3021],{"class":3017},[3011,6979,3040],{"class":3024},[3011,6981,6982],{"class":3013,"line":3063},[3011,6983,3072],{"class":3028},[3011,6985,6986,6988,6990,6992],{"class":3013,"line":3069},[3011,6987,3356],{"class":3017},[3011,6989,3055],{"class":3028},[3011,6991,3058],{"class":3024},[3011,6993,3029],{"class":3028},[3011,6995,6996,6998,7000,7002,7004,7006,7008,7010,7012,7014,7016,7019,7022],{"class":3013,"line":3075},[3011,6997,6006],{"class":3017},[3011,6999,3055],{"class":3028},[3011,7001,4758],{"class":3092},[3011,7003,3096],{"class":3028},[3011,7005,4984],{"class":3017},[3011,7007,3025],{"class":3024},[3011,7009,3301],{"class":3028},[3011,7011,3084],{"class":3024},[3011,7013,3301],{"class":3028},[3011,7015,6792],{"class":3024},[3011,7017,7018],{"class":3028},": ",[3011,7020,7021],{"class":3024},"NotifyValidationChanged",[3011,7023,3898],{"class":3028},[3011,7025,7026],{"class":3013,"line":3089},[3011,7027,3113],{"class":3028},[3011,7029,7030],{"class":3013,"line":3110},[3011,7031,3256],{"class":3028},[3011,7033,7034,7036,7038,7040],{"class":3013,"line":3116},[3011,7035,3328],{"class":3017},[3011,7037,3021],{"class":3017},[3011,7039,5288],{"class":3024},[3011,7041,3029],{"class":3028},[3011,7043,7044,7046,7048],{"class":3013,"line":3325},[3011,7045,3240],{"class":3017},[3011,7047,3021],{"class":3017},[3011,7049,5299],{"class":3024},[3011,7051,7052],{"class":3013,"line":3338},[3011,7053,3072],{"class":3028},[3011,7055,7056,7058,7060,7062],{"class":3013,"line":3348},[3011,7057,3356],{"class":3017},[3011,7059,3055],{"class":3028},[3011,7061,5312],{"class":3024},[3011,7063,3029],{"class":3028},[3011,7065,7066,7068,7070,7072,7074,7076,7078,7080,7082,7084,7086,7088,7090],{"class":3013,"line":3353},[3011,7067,6006],{"class":3017},[3011,7069,3055],{"class":3028},[3011,7071,4758],{"class":3092},[3011,7073,3096],{"class":3028},[3011,7075,4984],{"class":3017},[3011,7077,5288],{"class":3024},[3011,7079,3301],{"class":3028},[3011,7081,3084],{"class":3024},[3011,7083,3301],{"class":3028},[3011,7085,6792],{"class":3024},[3011,7087,7018],{"class":3028},[3011,7089,7021],{"class":3024},[3011,7091,3898],{"class":3028},[3011,7093,7094],{"class":3013,"line":3366},[3011,7095,3113],{"class":3028},[3011,7097,7098],{"class":3013,"line":3372},[3011,7099,3256],{"class":3028},[3011,7101,7102,7104,7106,7109],{"class":3013,"line":3378},[3011,7103,3328],{"class":3017},[3011,7105,3021],{"class":3017},[3011,7107,7108],{"class":3024}," _email",[3011,7110,3029],{"class":3028},[3011,7112,7113,7115,7117],{"class":3013,"line":3390},[3011,7114,3240],{"class":3017},[3011,7116,3021],{"class":3017},[3011,7118,7119],{"class":3024}," Email\n",[3011,7121,7122],{"class":3013,"line":3407},[3011,7123,3072],{"class":3028},[3011,7125,7126,7128,7130,7133],{"class":3013,"line":3413},[3011,7127,3356],{"class":3017},[3011,7129,3055],{"class":3028},[3011,7131,7132],{"class":3024},"_email",[3011,7134,3029],{"class":3028},[3011,7136,7137,7139,7141,7143,7145,7147,7149,7151,7153,7155,7157,7159,7161],{"class":3013,"line":3418},[3011,7138,6006],{"class":3017},[3011,7140,3055],{"class":3028},[3011,7142,4758],{"class":3092},[3011,7144,3096],{"class":3028},[3011,7146,4984],{"class":3017},[3011,7148,7108],{"class":3024},[3011,7150,3301],{"class":3028},[3011,7152,3084],{"class":3024},[3011,7154,3301],{"class":3028},[3011,7156,6792],{"class":3024},[3011,7158,7018],{"class":3028},[3011,7160,7021],{"class":3024},[3011,7162,3898],{"class":3028},[3011,7164,7165],{"class":3013,"line":3423},[3011,7166,3113],{"class":3028},[3011,7168,7169],{"class":3013,"line":3430},[3011,7170,3256],{"class":3028},[3011,7172,7173],{"class":3013,"line":3436},[3011,7174,6095],{"class":3213},[3011,7176,7177,7179,7181,7184],{"class":3013,"line":3450},[3011,7178,3240],{"class":3017},[3011,7180,4969],{"class":3017},[3011,7182,7183],{"class":3024}," IsValid",[3011,7185,7186],{"class":3028}," =>\n",[3011,7188,7189,7192,7194,7196,7199,7201,7203],{"class":3013,"line":3455},[3011,7190,7191],{"class":3028},"        !",[3011,7193,3272],{"class":3017},[3011,7195,4010],{"class":3028},[3011,7197,7198],{"class":3092},"IsNullOrWhiteSpace",[3011,7200,3096],{"class":3028},[3011,7202,3104],{"class":3024},[3011,7204,7205],{"class":3028},") &&\n",[3011,7207,7208,7210,7212,7214,7216,7218,7220],{"class":3013,"line":3461},[3011,7209,7191],{"class":3028},[3011,7211,3272],{"class":3017},[3011,7213,4010],{"class":3028},[3011,7215,7198],{"class":3092},[3011,7217,3096],{"class":3028},[3011,7219,5930],{"class":3024},[3011,7221,7205],{"class":3028},[3011,7223,7224,7226,7228,7230,7232,7234,7236],{"class":3013,"line":3474},[3011,7225,7191],{"class":3028},[3011,7227,3272],{"class":3017},[3011,7229,4010],{"class":3028},[3011,7231,7198],{"class":3092},[3011,7233,3096],{"class":3028},[3011,7235,6940],{"class":3024},[3011,7237,7205],{"class":3028},[3011,7239,7240,7243,7245,7248,7250,7253],{"class":3013,"line":3479},[3011,7241,7242],{"class":3024},"        Email",[3011,7244,4010],{"class":3028},[3011,7246,7247],{"class":3092},"Contains",[3011,7249,3096],{"class":3028},[3011,7251,7252],{"class":3894},"\"@\"",[3011,7254,3898],{"class":3028},[3011,7256,7257],{"class":3013,"line":3496},[3011,7258,3256],{"class":3028},[3011,7260,7261],{"class":3013,"line":3501},[3011,7262,7263],{"class":3213},"    // Централізований метод для сповіщення про валідацію\n",[3011,7265,7266,7268,7270,7273],{"class":3013,"line":3526},[3011,7267,3328],{"class":3017},[3011,7269,3264],{"class":3017},[3011,7271,7272],{"class":3092}," NotifyValidationChanged",[3011,7274,7275],{"class":3028},"()\n",[3011,7277,7278],{"class":3013,"line":3531},[3011,7279,3072],{"class":3028},[3011,7281,7282,7284,7286,7288,7290,7292],{"class":3013,"line":3536},[3011,7283,3093],{"class":3092},[3011,7285,3096],{"class":3028},[3011,7287,3099],{"class":3017},[3011,7289,3096],{"class":3028},[3011,7291,6933],{"class":3024},[3011,7293,3107],{"class":3028},[3011,7295,7296],{"class":3013,"line":3548},[3011,7297,3113],{"class":3028},[3011,7299,7300],{"class":3013,"line":3558},[3011,7301,3119],{"class":3028},[2965,7303,7304],{},[2975,7305,7306],{},"XAML:",[3002,7308,7310],{"className":6138,"code":7309,"language":6140,"meta":3007,"style":3007},"\u003CButton Content=\"Зберегти\" IsEnabled=\"{Binding IsValid}\"/>\n",[2989,7311,7312],{"__ignoreMap":3007},[3011,7313,7314,7316,7319,7322,7324,7327,7330,7332,7335],{"class":3013,"line":3014},[3011,7315,4975],{"class":6147},[3011,7317,7318],{"class":6150},"Button",[3011,7320,7321],{"class":6154}," Content",[3011,7323,6158],{"class":3028},[3011,7325,7326],{"class":6161},"\"Зберегти\"",[3011,7328,7329],{"class":6154}," IsEnabled",[3011,7331,6158],{"class":3028},[3011,7333,7334],{"class":6161},"\"{Binding IsValid}\"",[3011,7336,6165],{"class":6147},[2965,7338,7339],{},"Тепер кнопка автоматично активується/деактивується при зміні будь-якої з трьох властивостей.",[4428,7341,7342,7345,7346,7349,7350,7353],{},[2975,7343,7344],{},"Централізація залежностей:"," Якщо кілька властивостей впливають на одну обчислювану властивість, створіть окремий метод (наприклад, ",[2989,7347,7348],{},"NotifyValidationChanged()","), щоб не дублювати ",[2989,7351,7352],{},"OnPropertyChanged(nameof(IsValid))"," у кожному setter.",[3181,7355],{},[2960,7357,7359],{"id":7358},"валідація-inotifydataerrorinfo","Валідація: INotifyDataErrorInfo",[2965,7361,7362,7363,7366],{},"У реальних додатках потрібна валідація вводу користувача з відображенням помилок у UI. WPF надає інтерфейс ",[2989,7364,7365],{},"INotifyDataErrorInfo"," для цього.",[3194,7368,7370],{"id":7369},"що-таке-inotifydataerrorinfo","Що таке INotifyDataErrorInfo",[2965,7372,7373],{},[2975,7374,7375],{},"Інтерфейс:",[3002,7377,7379],{"className":3004,"code":7378,"language":3006,"meta":3007,"style":3007},"public interface INotifyDataErrorInfo\n{\n    // Чи є помилки у об'єкті\n    bool HasErrors { get; }\n    \n    // Отримати помилки для конкретної властивості\n    IEnumerable GetErrors(string propertyName);\n    \n    // Подія при зміні помилок\n    event EventHandler\u003CDataErrorsChangedEventArgs> ErrorsChanged;\n}\n",[2989,7380,7381,7391,7395,7400,7417,7421,7426,7442,7446,7451,7472],{"__ignoreMap":3007},[3011,7382,7383,7385,7388],{"class":3013,"line":3014},[3011,7384,3035],{"class":3017},[3011,7386,7387],{"class":3017}," interface",[3011,7389,7390],{"class":3224}," INotifyDataErrorInfo\n",[3011,7392,7393],{"class":3013,"line":3032},[3011,7394,3046],{"class":3028},[3011,7396,7397],{"class":3013,"line":3043},[3011,7398,7399],{"class":3213},"    // Чи є помилки у об'єкті\n",[3011,7401,7402,7405,7408,7411,7414],{"class":3013,"line":3049},[3011,7403,7404],{"class":3017},"    bool",[3011,7406,7407],{"class":3024}," HasErrors",[3011,7409,7410],{"class":3028}," { ",[3011,7412,7413],{"class":3017},"get",[3011,7415,7416],{"class":3028},"; }\n",[3011,7418,7419],{"class":3013,"line":3063},[3011,7420,3256],{"class":3028},[3011,7422,7423],{"class":3013,"line":3069},[3011,7424,7425],{"class":3213},"    // Отримати помилки для конкретної властивості\n",[3011,7427,7428,7431,7434,7436,7438,7440],{"class":3013,"line":3075},[3011,7429,7430],{"class":3224},"    IEnumerable",[3011,7432,7433],{"class":3092}," GetErrors",[3011,7435,3096],{"class":3028},[3011,7437,3272],{"class":3017},[3011,7439,3275],{"class":3024},[3011,7441,3898],{"class":3028},[3011,7443,7444],{"class":3013,"line":3089},[3011,7445,3256],{"class":3028},[3011,7447,7448],{"class":3013,"line":3110},[3011,7449,7450],{"class":3213},"    // Подія при зміні помилок\n",[3011,7452,7453,7456,7459,7461,7464,7467,7470],{"class":3013,"line":3116},[3011,7454,7455],{"class":3017},"    event",[3011,7457,7458],{"class":3224}," EventHandler",[3011,7460,4975],{"class":3028},[3011,7462,7463],{"class":3224},"DataErrorsChangedEventArgs",[3011,7465,7466],{"class":3028},"> ",[3011,7468,7469],{"class":3024},"ErrorsChanged",[3011,7471,3029],{"class":3028},[3011,7473,7474],{"class":3013,"line":3325},[3011,7475,3119],{"class":3028},[2965,7477,7478],{},[2975,7479,7480],{},"Як це працює:",[3754,7482,7483],{},[3002,7484,7486],{"className":3758,"code":7485,"language":3754,"meta":3007,"style":3007},"sequenceDiagram\n    participant User as Користувач\n    participant View as View (TextBox)\n    participant VM as ViewModel\n    participant Validation as Validation Logic\n    \n    User->>View: Вводить \"abc\" у Email\n    View->>VM: Email = \"abc\"\n    VM->>Validation: ValidateEmail(\"abc\")\n    Validation-->>VM: Помилка: \"Некоректний email\"\n    VM->>VM: Зберігає помилку\n    VM->>View: ErrorsChanged(\"Email\")\n    View->>VM: GetErrors(\"Email\")\n    VM-->>View: [\"Некоректний email\"]\n    View->>View: Показує червону рамку + tooltip\n    \n    style Validation fill:#ef4444,stroke:#b91c1c,color:#ffffff\n    style VM fill:#3b82f6,stroke:#1d4ed8,color:#ffffff\n",[2989,7487,7488,7492,7496,7501,7505,7510,7514,7519,7524,7529,7534,7539,7544,7549,7554,7559,7563,7568],{"__ignoreMap":3007},[3011,7489,7490],{"class":3013,"line":3014},[3011,7491,4613],{},[3011,7493,7494],{"class":3013,"line":3032},[3011,7495,6200],{},[3011,7497,7498],{"class":3013,"line":3043},[3011,7499,7500],{},"    participant View as View (TextBox)\n",[3011,7502,7503],{"class":3013,"line":3049},[3011,7504,6210],{},[3011,7506,7507],{"class":3013,"line":3063},[3011,7508,7509],{},"    participant Validation as Validation Logic\n",[3011,7511,7512],{"class":3013,"line":3069},[3011,7513,3256],{},[3011,7515,7516],{"class":3013,"line":3075},[3011,7517,7518],{},"    User->>View: Вводить \"abc\" у Email\n",[3011,7520,7521],{"class":3013,"line":3089},[3011,7522,7523],{},"    View->>VM: Email = \"abc\"\n",[3011,7525,7526],{"class":3013,"line":3110},[3011,7527,7528],{},"    VM->>Validation: ValidateEmail(\"abc\")\n",[3011,7530,7531],{"class":3013,"line":3116},[3011,7532,7533],{},"    Validation-->>VM: Помилка: \"Некоректний email\"\n",[3011,7535,7536],{"class":3013,"line":3325},[3011,7537,7538],{},"    VM->>VM: Зберігає помилку\n",[3011,7540,7541],{"class":3013,"line":3338},[3011,7542,7543],{},"    VM->>View: ErrorsChanged(\"Email\")\n",[3011,7545,7546],{"class":3013,"line":3348},[3011,7547,7548],{},"    View->>VM: GetErrors(\"Email\")\n",[3011,7550,7551],{"class":3013,"line":3353},[3011,7552,7553],{},"    VM-->>View: [\"Некоректний email\"]\n",[3011,7555,7556],{"class":3013,"line":3366},[3011,7557,7558],{},"    View->>View: Показує червону рамку + tooltip\n",[3011,7560,7561],{"class":3013,"line":3372},[3011,7562,3256],{},[3011,7564,7565],{"class":3013,"line":3378},[3011,7566,7567],{},"    style Validation fill:#ef4444,stroke:#b91c1c,color:#ffffff\n",[3011,7569,7570],{"class":3013,"line":3390},[3011,7571,7572],{},"    style VM fill:#3b82f6,stroke:#1d4ed8,color:#ffffff\n",[3194,7574,7576],{"id":7575},"реалізація-baseviewmodel-з-валідацією","Реалізація BaseViewModel з валідацією",[2965,7578,7579],{},[2975,7580,7581],{},"Розширений BaseViewModel:",[3002,7583,7585],{"className":3004,"code":7584,"language":3006,"meta":3007,"style":3007},"using System.Collections;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Linq;\nusing System.Runtime.CompilerServices;\n\npublic abstract class BaseViewModel : INotifyPropertyChanged, INotifyDataErrorInfo\n{\n    // INotifyPropertyChanged\n    public event PropertyChangedEventHandler PropertyChanged;\n    \n    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)\n    {\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n    }\n    \n    protected bool SetProperty\u003CT>(ref T field, T value, [CallerMemberName] string propertyName = null)\n    {\n        if (EqualityComparer\u003CT>.Default.Equals(field, value))\n            return false;\n        \n        field = value;\n        OnPropertyChanged(propertyName);\n        return true;\n    }\n    \n    // INotifyDataErrorInfo\n    private readonly Dictionary\u003Cstring, List\u003Cstring>> _errors = new Dictionary\u003Cstring, List\u003Cstring>>();\n    \n    public event EventHandler\u003CDataErrorsChangedEventArgs> ErrorsChanged;\n    \n    public bool HasErrors => _errors.Any();\n    \n    public IEnumerable GetErrors(string propertyName)\n    {\n        if (string.IsNullOrEmpty(propertyName))\n        {\n            // Повернути всі помилки\n            return _errors.Values.SelectMany(e => e);\n        }\n        \n        return _errors.ContainsKey(propertyName) ? _errors[propertyName] : null;\n    }\n    \n    // Додати помилку\n    protected void AddError(string propertyName, string error)\n    {\n        if (!_errors.ContainsKey(propertyName))\n        {\n            _errors[propertyName] = new List\u003Cstring>();\n        }\n        \n        if (!_errors[propertyName].Contains(error))\n        {\n            _errors[propertyName].Add(error);\n            OnErrorsChanged(propertyName);\n        }\n    }\n    \n    // Очистити помилки для властивості\n    protected void ClearErrors(string propertyName)\n    {\n        if (_errors.ContainsKey(propertyName))\n        {\n            _errors.Remove(propertyName);\n            OnErrorsChanged(propertyName);\n        }\n    }\n    \n    // Викликати подію ErrorsChanged\n    protected void OnErrorsChanged(string propertyName)\n    {\n        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));\n        OnPropertyChanged(nameof(HasErrors));\n    }\n}\n",[2989,7586,7587,7600,7617,7629,7642,7658,7662,7681,7685,7690,7702,7706,7732,7736,7760,7764,7768,7810,7814,7844,7852,7856,7866,7876,7884,7888,7892,7897,7947,7951,7969,7973,7992,7996,8013,8017,8036,8040,8045,8073,8077,8081,8113,8117,8121,8126,8150,8154,8173,8177,8201,8205,8209,8233,8237,8256,8267,8271,8275,8280,8286,8304,8309,8328,8333,8349,8360,8365,8370,8375,8381,8399,8404,8431,8447,8452],{"__ignoreMap":3007},[3011,7588,7589,7591,7593,7595,7598],{"class":3013,"line":3014},[3011,7590,4126],{"class":4125},[3011,7592,4129],{"class":3224},[3011,7594,4010],{"class":3028},[3011,7596,7597],{"class":3224},"Collections",[3011,7599,3029],{"class":3028},[3011,7601,7602,7604,7606,7608,7610,7612,7615],{"class":3013,"line":3032},[3011,7603,4126],{"class":4125},[3011,7605,4129],{"class":3224},[3011,7607,4010],{"class":3028},[3011,7609,7597],{"class":3224},[3011,7611,4010],{"class":3028},[3011,7613,7614],{"class":3224},"Generic",[3011,7616,3029],{"class":3028},[3011,7618,7619,7621,7623,7625,7627],{"class":3013,"line":3043},[3011,7620,4126],{"class":4125},[3011,7622,4129],{"class":3224},[3011,7624,4010],{"class":3028},[3011,7626,4134],{"class":3224},[3011,7628,3029],{"class":3028},[3011,7630,7631,7633,7635,7637,7640],{"class":3013,"line":3049},[3011,7632,4126],{"class":4125},[3011,7634,4129],{"class":3224},[3011,7636,4010],{"class":3028},[3011,7638,7639],{"class":3224},"Linq",[3011,7641,3029],{"class":3028},[3011,7643,7644,7646,7648,7650,7652,7654,7656],{"class":3013,"line":3063},[3011,7645,4126],{"class":4125},[3011,7647,4129],{"class":3224},[3011,7649,4010],{"class":3028},[3011,7651,4147],{"class":3224},[3011,7653,4010],{"class":3028},[3011,7655,4152],{"class":3224},[3011,7657,3029],{"class":3028},[3011,7659,7660],{"class":3013,"line":3069},[3011,7661,3427],{"emptyLinePlaceholder":3426},[3011,7663,7664,7666,7668,7670,7672,7674,7676,7678],{"class":3013,"line":3075},[3011,7665,3035],{"class":3017},[3011,7667,4165],{"class":3017},[3011,7669,3221],{"class":3017},[3011,7671,4170],{"class":3224},[3011,7673,3228],{"class":3028},[3011,7675,2991],{"class":3224},[3011,7677,3301],{"class":3028},[3011,7679,7680],{"class":3224},"INotifyDataErrorInfo\n",[3011,7682,7683],{"class":3013,"line":3089},[3011,7684,3046],{"class":3028},[3011,7686,7687],{"class":3013,"line":3110},[3011,7688,7689],{"class":3213},"    // INotifyPropertyChanged\n",[3011,7691,7692,7694,7696,7698,7700],{"class":3013,"line":3116},[3011,7693,3240],{"class":3017},[3011,7695,3243],{"class":3017},[3011,7697,3246],{"class":3224},[3011,7699,3249],{"class":3024},[3011,7701,3029],{"class":3028},[3011,7703,7704],{"class":3013,"line":3325},[3011,7705,3256],{"class":3028},[3011,7707,7708,7710,7712,7714,7716,7718,7720,7722,7724,7726,7728,7730],{"class":3013,"line":3338},[3011,7709,3261],{"class":3017},[3011,7711,4211],{"class":3017},[3011,7713,3264],{"class":3017},[3011,7715,3267],{"class":3092},[3011,7717,4218],{"class":3028},[3011,7719,4221],{"class":3224},[3011,7721,4224],{"class":3028},[3011,7723,3272],{"class":3017},[3011,7725,3275],{"class":3024},[3011,7727,3081],{"class":3028},[3011,7729,4233],{"class":3017},[3011,7731,3278],{"class":3028},[3011,7733,7734],{"class":3013,"line":3348},[3011,7735,3072],{"class":3028},[3011,7737,7738,7740,7742,7744,7746,7748,7750,7752,7754,7756,7758],{"class":3013,"line":3353},[3011,7739,3287],{"class":3024},[3011,7741,3290],{"class":3028},[3011,7743,3293],{"class":3092},[3011,7745,3096],{"class":3028},[3011,7747,3298],{"class":3017},[3011,7749,3301],{"class":3028},[3011,7751,3304],{"class":3017},[3011,7753,3307],{"class":3224},[3011,7755,3096],{"class":3028},[3011,7757,3312],{"class":3024},[3011,7759,3107],{"class":3028},[3011,7761,7762],{"class":3013,"line":3366},[3011,7763,3113],{"class":3028},[3011,7765,7766],{"class":3013,"line":3372},[3011,7767,3256],{"class":3028},[3011,7769,7770,7772,7774,7776,7778,7780,7782,7784,7786,7788,7790,7792,7794,7796,7798,7800,7802,7804,7806,7808],{"class":3013,"line":3378},[3011,7771,3261],{"class":3017},[3011,7773,4969],{"class":3017},[3011,7775,4972],{"class":3092},[3011,7777,4975],{"class":3028},[3011,7779,4978],{"class":3224},[3011,7781,4981],{"class":3028},[3011,7783,4984],{"class":3017},[3011,7785,4987],{"class":3224},[3011,7787,4990],{"class":3024},[3011,7789,3301],{"class":3028},[3011,7791,4978],{"class":3224},[3011,7793,4997],{"class":3024},[3011,7795,5000],{"class":3028},[3011,7797,4221],{"class":3224},[3011,7799,4224],{"class":3028},[3011,7801,3272],{"class":3017},[3011,7803,3275],{"class":3024},[3011,7805,3081],{"class":3028},[3011,7807,4233],{"class":3017},[3011,7809,3278],{"class":3028},[3011,7811,7812],{"class":3013,"line":3390},[3011,7813,3072],{"class":3028},[3011,7815,7816,7818,7820,7822,7824,7826,7828,7830,7832,7834,7836,7838,7840,7842],{"class":3013,"line":3407},[3011,7817,5028],{"class":4125},[3011,7819,5031],{"class":3028},[3011,7821,5034],{"class":3024},[3011,7823,4975],{"class":3028},[3011,7825,4978],{"class":3224},[3011,7827,5041],{"class":3028},[3011,7829,5044],{"class":3024},[3011,7831,4010],{"class":3028},[3011,7833,5049],{"class":3092},[3011,7835,3096],{"class":3028},[3011,7837,5054],{"class":3024},[3011,7839,3301],{"class":3028},[3011,7841,3084],{"class":3024},[3011,7843,5061],{"class":3028},[3011,7845,7846,7848,7850],{"class":3013,"line":3413},[3011,7847,5070],{"class":4125},[3011,7849,5073],{"class":3017},[3011,7851,3029],{"class":3028},[3011,7853,7854],{"class":3013,"line":3418},[3011,7855,5088],{"class":3028},[3011,7857,7858,7860,7862,7864],{"class":3013,"line":3423},[3011,7859,5098],{"class":3024},[3011,7861,3081],{"class":3028},[3011,7863,3084],{"class":3024},[3011,7865,3029],{"class":3028},[3011,7867,7868,7870,7872,7874],{"class":3013,"line":3430},[3011,7869,3093],{"class":3092},[3011,7871,3096],{"class":3028},[3011,7873,3312],{"class":3024},[3011,7875,3898],{"class":3028},[3011,7877,7878,7880,7882],{"class":3013,"line":3436},[3011,7879,5132],{"class":4125},[3011,7881,5135],{"class":3017},[3011,7883,3029],{"class":3028},[3011,7885,7886],{"class":3013,"line":3450},[3011,7887,3113],{"class":3028},[3011,7889,7890],{"class":3013,"line":3455},[3011,7891,3256],{"class":3028},[3011,7893,7894],{"class":3013,"line":3461},[3011,7895,7896],{"class":3213},"    // INotifyDataErrorInfo\n",[3011,7898,7899,7901,7904,7907,7909,7911,7913,7916,7918,7920,7923,7926,7928,7930,7932,7934,7936,7938,7940,7942,7944],{"class":3013,"line":3474},[3011,7900,3328],{"class":3017},[3011,7902,7903],{"class":3017}," readonly",[3011,7905,7906],{"class":3224}," Dictionary",[3011,7908,4975],{"class":3028},[3011,7910,3272],{"class":3017},[3011,7912,3301],{"class":3028},[3011,7914,7915],{"class":3224},"List",[3011,7917,4975],{"class":3028},[3011,7919,3272],{"class":3017},[3011,7921,7922],{"class":3028},">> ",[3011,7924,7925],{"class":3024},"_errors",[3011,7927,3081],{"class":3028},[3011,7929,3304],{"class":3017},[3011,7931,7906],{"class":3224},[3011,7933,4975],{"class":3028},[3011,7935,3272],{"class":3017},[3011,7937,3301],{"class":3028},[3011,7939,7915],{"class":3224},[3011,7941,4975],{"class":3028},[3011,7943,3272],{"class":3017},[3011,7945,7946],{"class":3028},">>();\n",[3011,7948,7949],{"class":3013,"line":3479},[3011,7950,3256],{"class":3028},[3011,7952,7953,7955,7957,7959,7961,7963,7965,7967],{"class":3013,"line":3496},[3011,7954,3240],{"class":3017},[3011,7956,3243],{"class":3017},[3011,7958,7458],{"class":3224},[3011,7960,4975],{"class":3028},[3011,7962,7463],{"class":3224},[3011,7964,7466],{"class":3028},[3011,7966,7469],{"class":3024},[3011,7968,3029],{"class":3028},[3011,7970,7971],{"class":3013,"line":3501},[3011,7972,3256],{"class":3028},[3011,7974,7975,7977,7979,7981,7983,7985,7987,7990],{"class":3013,"line":3526},[3011,7976,3240],{"class":3017},[3011,7978,4969],{"class":3017},[3011,7980,7407],{"class":3024},[3011,7982,3055],{"class":3028},[3011,7984,7925],{"class":3024},[3011,7986,4010],{"class":3028},[3011,7988,7989],{"class":3092},"Any",[3011,7991,4395],{"class":3028},[3011,7993,7994],{"class":3013,"line":3531},[3011,7995,3256],{"class":3028},[3011,7997,7998,8000,8003,8005,8007,8009,8011],{"class":3013,"line":3536},[3011,7999,3240],{"class":3017},[3011,8001,8002],{"class":3224}," IEnumerable",[3011,8004,7433],{"class":3092},[3011,8006,3096],{"class":3028},[3011,8008,3272],{"class":3017},[3011,8010,3275],{"class":3024},[3011,8012,3278],{"class":3028},[3011,8014,8015],{"class":3013,"line":3548},[3011,8016,3072],{"class":3028},[3011,8018,8019,8021,8023,8025,8027,8030,8032,8034],{"class":3013,"line":3558},[3011,8020,5028],{"class":4125},[3011,8022,5031],{"class":3028},[3011,8024,3272],{"class":3017},[3011,8026,4010],{"class":3028},[3011,8028,8029],{"class":3092},"IsNullOrEmpty",[3011,8031,3096],{"class":3028},[3011,8033,3312],{"class":3024},[3011,8035,5061],{"class":3028},[3011,8037,8038],{"class":3013,"line":3563},[3011,8039,3375],{"class":3028},[3011,8041,8042],{"class":3013,"line":3575},[3011,8043,8044],{"class":3213},"            // Повернути всі помилки\n",[3011,8046,8047,8049,8052,8054,8057,8059,8062,8064,8067,8069,8071],{"class":3013,"line":3580},[3011,8048,5070],{"class":4125},[3011,8050,8051],{"class":3024}," _errors",[3011,8053,4010],{"class":3028},[3011,8055,8056],{"class":3024},"Values",[3011,8058,4010],{"class":3028},[3011,8060,8061],{"class":3092},"SelectMany",[3011,8063,3096],{"class":3028},[3011,8065,8066],{"class":3024},"e",[3011,8068,3055],{"class":3028},[3011,8070,8066],{"class":3024},[3011,8072,3898],{"class":3028},[3011,8074,8075],{"class":3013,"line":3585},[3011,8076,3410],{"class":3028},[3011,8078,8079],{"class":3013,"line":3597},[3011,8080,5088],{"class":3028},[3011,8082,8083,8085,8087,8089,8092,8094,8096,8099,8101,8104,8106,8109,8111],{"class":3013,"line":3613},[3011,8084,5132],{"class":4125},[3011,8086,8051],{"class":3024},[3011,8088,4010],{"class":3028},[3011,8090,8091],{"class":3092},"ContainsKey",[3011,8093,3096],{"class":3028},[3011,8095,3312],{"class":3024},[3011,8097,8098],{"class":3028},") ? ",[3011,8100,7925],{"class":3024},[3011,8102,8103],{"class":3028},"[",[3011,8105,3312],{"class":3024},[3011,8107,8108],{"class":3028},"] : ",[3011,8110,4233],{"class":3017},[3011,8112,3029],{"class":3028},[3011,8114,8115],{"class":3013,"line":3618},[3011,8116,3113],{"class":3028},[3011,8118,8119],{"class":3013,"line":3623},[3011,8120,3256],{"class":3028},[3011,8122,8123],{"class":3013,"line":3628},[3011,8124,8125],{"class":3213},"    // Додати помилку\n",[3011,8127,8128,8130,8132,8135,8137,8139,8141,8143,8145,8148],{"class":3013,"line":3633},[3011,8129,3261],{"class":3017},[3011,8131,3264],{"class":3017},[3011,8133,8134],{"class":3092}," AddError",[3011,8136,3096],{"class":3028},[3011,8138,3272],{"class":3017},[3011,8140,3275],{"class":3024},[3011,8142,3301],{"class":3028},[3011,8144,3272],{"class":3017},[3011,8146,8147],{"class":3024}," error",[3011,8149,3278],{"class":3028},[3011,8151,8152],{"class":3013,"line":3639},[3011,8153,3072],{"class":3028},[3011,8155,8156,8158,8161,8163,8165,8167,8169,8171],{"class":3013,"line":3653},[3011,8157,5028],{"class":4125},[3011,8159,8160],{"class":3028}," (!",[3011,8162,7925],{"class":3024},[3011,8164,4010],{"class":3028},[3011,8166,8091],{"class":3092},[3011,8168,3096],{"class":3028},[3011,8170,3312],{"class":3024},[3011,8172,5061],{"class":3028},[3011,8174,8175],{"class":3013,"line":3658},[3011,8176,3375],{"class":3028},[3011,8178,8179,8182,8184,8186,8189,8191,8194,8196,8198],{"class":3013,"line":3664},[3011,8180,8181],{"class":3024},"            _errors",[3011,8183,8103],{"class":3028},[3011,8185,3312],{"class":3024},[3011,8187,8188],{"class":3028},"] = ",[3011,8190,3304],{"class":3017},[3011,8192,8193],{"class":3224}," List",[3011,8195,4975],{"class":3028},[3011,8197,3272],{"class":3017},[3011,8199,8200],{"class":3028},">();\n",[3011,8202,8203],{"class":3013,"line":3677},[3011,8204,3410],{"class":3028},[3011,8206,8207],{"class":3013,"line":3682},[3011,8208,5088],{"class":3028},[3011,8210,8211,8213,8215,8217,8219,8221,8224,8226,8228,8231],{"class":3013,"line":3699},[3011,8212,5028],{"class":4125},[3011,8214,8160],{"class":3028},[3011,8216,7925],{"class":3024},[3011,8218,8103],{"class":3028},[3011,8220,3312],{"class":3024},[3011,8222,8223],{"class":3028},"].",[3011,8225,7247],{"class":3092},[3011,8227,3096],{"class":3028},[3011,8229,8230],{"class":3024},"error",[3011,8232,5061],{"class":3028},[3011,8234,8235],{"class":3013,"line":3704},[3011,8236,3375],{"class":3028},[3011,8238,8239,8241,8243,8245,8247,8250,8252,8254],{"class":3013,"line":3729},[3011,8240,8181],{"class":3024},[3011,8242,8103],{"class":3028},[3011,8244,3312],{"class":3024},[3011,8246,8223],{"class":3028},[3011,8248,8249],{"class":3092},"Add",[3011,8251,3096],{"class":3028},[3011,8253,8230],{"class":3024},[3011,8255,3898],{"class":3028},[3011,8257,8258,8261,8263,8265],{"class":3013,"line":3734},[3011,8259,8260],{"class":3092},"            OnErrorsChanged",[3011,8262,3096],{"class":3028},[3011,8264,3312],{"class":3024},[3011,8266,3898],{"class":3028},[3011,8268,8269],{"class":3013,"line":3739},[3011,8270,3410],{"class":3028},[3011,8272,8273],{"class":3013,"line":3745},[3011,8274,3113],{"class":3028},[3011,8276,8278],{"class":3013,"line":8277},59,[3011,8279,3256],{"class":3028},[3011,8281,8283],{"class":3013,"line":8282},60,[3011,8284,8285],{"class":3213},"    // Очистити помилки для властивості\n",[3011,8287,8289,8291,8293,8296,8298,8300,8302],{"class":3013,"line":8288},61,[3011,8290,3261],{"class":3017},[3011,8292,3264],{"class":3017},[3011,8294,8295],{"class":3092}," ClearErrors",[3011,8297,3096],{"class":3028},[3011,8299,3272],{"class":3017},[3011,8301,3275],{"class":3024},[3011,8303,3278],{"class":3028},[3011,8305,8307],{"class":3013,"line":8306},62,[3011,8308,3072],{"class":3028},[3011,8310,8312,8314,8316,8318,8320,8322,8324,8326],{"class":3013,"line":8311},63,[3011,8313,5028],{"class":4125},[3011,8315,5031],{"class":3028},[3011,8317,7925],{"class":3024},[3011,8319,4010],{"class":3028},[3011,8321,8091],{"class":3092},[3011,8323,3096],{"class":3028},[3011,8325,3312],{"class":3024},[3011,8327,5061],{"class":3028},[3011,8329,8331],{"class":3013,"line":8330},64,[3011,8332,3375],{"class":3028},[3011,8334,8336,8338,8340,8343,8345,8347],{"class":3013,"line":8335},65,[3011,8337,8181],{"class":3024},[3011,8339,4010],{"class":3028},[3011,8341,8342],{"class":3092},"Remove",[3011,8344,3096],{"class":3028},[3011,8346,3312],{"class":3024},[3011,8348,3898],{"class":3028},[3011,8350,8352,8354,8356,8358],{"class":3013,"line":8351},66,[3011,8353,8260],{"class":3092},[3011,8355,3096],{"class":3028},[3011,8357,3312],{"class":3024},[3011,8359,3898],{"class":3028},[3011,8361,8363],{"class":3013,"line":8362},67,[3011,8364,3410],{"class":3028},[3011,8366,8368],{"class":3013,"line":8367},68,[3011,8369,3113],{"class":3028},[3011,8371,8373],{"class":3013,"line":8372},69,[3011,8374,3256],{"class":3028},[3011,8376,8378],{"class":3013,"line":8377},70,[3011,8379,8380],{"class":3213},"    // Викликати подію ErrorsChanged\n",[3011,8382,8384,8386,8388,8391,8393,8395,8397],{"class":3013,"line":8383},71,[3011,8385,3261],{"class":3017},[3011,8387,3264],{"class":3017},[3011,8389,8390],{"class":3092}," OnErrorsChanged",[3011,8392,3096],{"class":3028},[3011,8394,3272],{"class":3017},[3011,8396,3275],{"class":3024},[3011,8398,3278],{"class":3028},[3011,8400,8402],{"class":3013,"line":8401},72,[3011,8403,3072],{"class":3028},[3011,8405,8407,8410,8412,8414,8416,8418,8420,8422,8425,8427,8429],{"class":3013,"line":8406},73,[3011,8408,8409],{"class":3024},"        ErrorsChanged",[3011,8411,3290],{"class":3028},[3011,8413,3293],{"class":3092},[3011,8415,3096],{"class":3028},[3011,8417,3298],{"class":3017},[3011,8419,3301],{"class":3028},[3011,8421,3304],{"class":3017},[3011,8423,8424],{"class":3224}," DataErrorsChangedEventArgs",[3011,8426,3096],{"class":3028},[3011,8428,3312],{"class":3024},[3011,8430,3107],{"class":3028},[3011,8432,8434,8436,8438,8440,8442,8445],{"class":3013,"line":8433},74,[3011,8435,3093],{"class":3092},[3011,8437,3096],{"class":3028},[3011,8439,3099],{"class":3017},[3011,8441,3096],{"class":3028},[3011,8443,8444],{"class":3024},"HasErrors",[3011,8446,3107],{"class":3028},[3011,8448,8450],{"class":3013,"line":8449},75,[3011,8451,3113],{"class":3028},[3011,8453,8455],{"class":3013,"line":8454},76,[3011,8456,3119],{"class":3028},[3194,8458,8460],{"id":8459},"використання-валідації-у-viewmodel","Використання валідації у ViewModel",[2965,8462,8463],{},[2975,8464,8465],{},"Приклад: Форма реєстрації",[3002,8467,8469],{"className":3004,"code":8468,"language":3006,"meta":3007,"style":3007},"public class RegistrationViewModel : BaseViewModel\n{\n    private string _email;\n    public string Email\n    {\n        get => _email;\n        set\n        {\n            if (SetProperty(ref _email, value))\n            {\n                ValidateEmail();\n            }\n        }\n    }\n    \n    private string _password;\n    public string Password\n    {\n        get => _password;\n        set\n        {\n            if (SetProperty(ref _password, value))\n            {\n                ValidatePassword();\n            }\n        }\n    }\n    \n    private string _confirmPassword;\n    public string ConfirmPassword\n    {\n        get => _confirmPassword;\n        set\n        {\n            if (SetProperty(ref _confirmPassword, value))\n            {\n                ValidateConfirmPassword();\n            }\n        }\n    }\n    \n    // Валідація Email\n    private void ValidateEmail()\n    {\n        ClearErrors(nameof(Email));\n        \n        if (string.IsNullOrWhiteSpace(Email))\n        {\n            AddError(nameof(Email), \"Email обов'язковий\");\n        }\n        else if (!Email.Contains(\"@\"))\n        {\n            AddError(nameof(Email), \"Некоректний формат email\");\n        }\n        else if (Email.Length \u003C 5)\n        {\n            AddError(nameof(Email), \"Email занадто короткий\");\n        }\n    }\n    \n    // Валідація Password\n    private void ValidatePassword()\n    {\n        ClearErrors(nameof(Password));\n        \n        if (string.IsNullOrWhiteSpace(Password))\n        {\n            AddError(nameof(Password), \"Пароль обов'язковий\");\n        }\n        else if (Password.Length \u003C 8)\n        {\n            AddError(nameof(Password), \"Пароль має бути мінімум 8 символів\");\n        }\n        else if (!Password.Any(char.IsDigit))\n        {\n            AddError(nameof(Password), \"Пароль має містити цифру\");\n        }\n        \n        // Перевалідувати ConfirmPassword при зміні Password\n        ValidateConfirmPassword();\n    }\n    \n    // Валідація ConfirmPassword\n    private void ValidateConfirmPassword()\n    {\n        ClearErrors(nameof(ConfirmPassword));\n        \n        if (string.IsNullOrWhiteSpace(ConfirmPassword))\n        {\n            AddError(nameof(ConfirmPassword), \"Підтвердження паролю обов'язкове\");\n        }\n        else if (Password != ConfirmPassword)\n        {\n            AddError(nameof(ConfirmPassword), \"Паролі не співпадають\");\n        }\n    }\n}\n",[2989,8470,8471,8484,8488,8498,8506,8510,8520,8524,8528,8549,8554,8561,8566,8570,8574,8578,8589,8598,8602,8613,8617,8621,8641,8645,8652,8656,8660,8664,8668,8679,8688,8692,8703,8707,8711,8731,8735,8742,8746,8750,8754,8758,8763,8774,8778,8793,8797,8815,8819,8840,8844,8866,8870,8889,8893,8917,8921,8940,8944,8948,8952,8957,8968,8972,8987,8991,9009,9013,9032,9036,9057,9061,9080,9084,9110,9114,9133,9138,9143,9149,9157,9162,9167,9173,9185,9190,9206,9211,9230,9235,9255,9260,9278,9283,9303,9308,9313],{"__ignoreMap":3007},[3011,8472,8473,8475,8477,8480,8482],{"class":3013,"line":3014},[3011,8474,3035],{"class":3017},[3011,8476,3221],{"class":3017},[3011,8478,8479],{"class":3224}," RegistrationViewModel",[3011,8481,3228],{"class":3028},[3011,8483,4329],{"class":3224},[3011,8485,8486],{"class":3013,"line":3032},[3011,8487,3046],{"class":3028},[3011,8489,8490,8492,8494,8496],{"class":3013,"line":3043},[3011,8491,3328],{"class":3017},[3011,8493,3021],{"class":3017},[3011,8495,7108],{"class":3024},[3011,8497,3029],{"class":3028},[3011,8499,8500,8502,8504],{"class":3013,"line":3049},[3011,8501,3240],{"class":3017},[3011,8503,3021],{"class":3017},[3011,8505,7119],{"class":3024},[3011,8507,8508],{"class":3013,"line":3063},[3011,8509,3072],{"class":3028},[3011,8511,8512,8514,8516,8518],{"class":3013,"line":3069},[3011,8513,3356],{"class":3017},[3011,8515,3055],{"class":3028},[3011,8517,7132],{"class":3024},[3011,8519,3029],{"class":3028},[3011,8521,8522],{"class":3013,"line":3075},[3011,8523,3369],{"class":3017},[3011,8525,8526],{"class":3013,"line":3089},[3011,8527,3375],{"class":3028},[3011,8529,8530,8533,8535,8537,8539,8541,8543,8545,8547],{"class":3013,"line":3110},[3011,8531,8532],{"class":4125},"            if",[3011,8534,5031],{"class":3028},[3011,8536,4758],{"class":3092},[3011,8538,3096],{"class":3028},[3011,8540,4984],{"class":3017},[3011,8542,7108],{"class":3024},[3011,8544,3301],{"class":3028},[3011,8546,3084],{"class":3024},[3011,8548,5061],{"class":3028},[3011,8550,8551],{"class":3013,"line":3116},[3011,8552,8553],{"class":3028},"            {\n",[3011,8555,8556,8559],{"class":3013,"line":3325},[3011,8557,8558],{"class":3092},"                ValidateEmail",[3011,8560,4395],{"class":3028},[3011,8562,8563],{"class":3013,"line":3338},[3011,8564,8565],{"class":3028},"            }\n",[3011,8567,8568],{"class":3013,"line":3348},[3011,8569,3410],{"class":3028},[3011,8571,8572],{"class":3013,"line":3353},[3011,8573,3113],{"class":3028},[3011,8575,8576],{"class":3013,"line":3366},[3011,8577,3256],{"class":3028},[3011,8579,8580,8582,8584,8587],{"class":3013,"line":3372},[3011,8581,3328],{"class":3017},[3011,8583,3021],{"class":3017},[3011,8585,8586],{"class":3024}," _password",[3011,8588,3029],{"class":3028},[3011,8590,8591,8593,8595],{"class":3013,"line":3378},[3011,8592,3240],{"class":3017},[3011,8594,3021],{"class":3017},[3011,8596,8597],{"class":3024}," Password\n",[3011,8599,8600],{"class":3013,"line":3390},[3011,8601,3072],{"class":3028},[3011,8603,8604,8606,8608,8611],{"class":3013,"line":3407},[3011,8605,3356],{"class":3017},[3011,8607,3055],{"class":3028},[3011,8609,8610],{"class":3024},"_password",[3011,8612,3029],{"class":3028},[3011,8614,8615],{"class":3013,"line":3413},[3011,8616,3369],{"class":3017},[3011,8618,8619],{"class":3013,"line":3418},[3011,8620,3375],{"class":3028},[3011,8622,8623,8625,8627,8629,8631,8633,8635,8637,8639],{"class":3013,"line":3423},[3011,8624,8532],{"class":4125},[3011,8626,5031],{"class":3028},[3011,8628,4758],{"class":3092},[3011,8630,3096],{"class":3028},[3011,8632,4984],{"class":3017},[3011,8634,8586],{"class":3024},[3011,8636,3301],{"class":3028},[3011,8638,3084],{"class":3024},[3011,8640,5061],{"class":3028},[3011,8642,8643],{"class":3013,"line":3430},[3011,8644,8553],{"class":3028},[3011,8646,8647,8650],{"class":3013,"line":3436},[3011,8648,8649],{"class":3092},"                ValidatePassword",[3011,8651,4395],{"class":3028},[3011,8653,8654],{"class":3013,"line":3450},[3011,8655,8565],{"class":3028},[3011,8657,8658],{"class":3013,"line":3455},[3011,8659,3410],{"class":3028},[3011,8661,8662],{"class":3013,"line":3461},[3011,8663,3113],{"class":3028},[3011,8665,8666],{"class":3013,"line":3474},[3011,8667,3256],{"class":3028},[3011,8669,8670,8672,8674,8677],{"class":3013,"line":3479},[3011,8671,3328],{"class":3017},[3011,8673,3021],{"class":3017},[3011,8675,8676],{"class":3024}," _confirmPassword",[3011,8678,3029],{"class":3028},[3011,8680,8681,8683,8685],{"class":3013,"line":3496},[3011,8682,3240],{"class":3017},[3011,8684,3021],{"class":3017},[3011,8686,8687],{"class":3024}," ConfirmPassword\n",[3011,8689,8690],{"class":3013,"line":3501},[3011,8691,3072],{"class":3028},[3011,8693,8694,8696,8698,8701],{"class":3013,"line":3526},[3011,8695,3356],{"class":3017},[3011,8697,3055],{"class":3028},[3011,8699,8700],{"class":3024},"_confirmPassword",[3011,8702,3029],{"class":3028},[3011,8704,8705],{"class":3013,"line":3531},[3011,8706,3369],{"class":3017},[3011,8708,8709],{"class":3013,"line":3536},[3011,8710,3375],{"class":3028},[3011,8712,8713,8715,8717,8719,8721,8723,8725,8727,8729],{"class":3013,"line":3548},[3011,8714,8532],{"class":4125},[3011,8716,5031],{"class":3028},[3011,8718,4758],{"class":3092},[3011,8720,3096],{"class":3028},[3011,8722,4984],{"class":3017},[3011,8724,8676],{"class":3024},[3011,8726,3301],{"class":3028},[3011,8728,3084],{"class":3024},[3011,8730,5061],{"class":3028},[3011,8732,8733],{"class":3013,"line":3558},[3011,8734,8553],{"class":3028},[3011,8736,8737,8740],{"class":3013,"line":3563},[3011,8738,8739],{"class":3092},"                ValidateConfirmPassword",[3011,8741,4395],{"class":3028},[3011,8743,8744],{"class":3013,"line":3575},[3011,8745,8565],{"class":3028},[3011,8747,8748],{"class":3013,"line":3580},[3011,8749,3410],{"class":3028},[3011,8751,8752],{"class":3013,"line":3585},[3011,8753,3113],{"class":3028},[3011,8755,8756],{"class":3013,"line":3597},[3011,8757,3256],{"class":3028},[3011,8759,8760],{"class":3013,"line":3613},[3011,8761,8762],{"class":3213},"    // Валідація Email\n",[3011,8764,8765,8767,8769,8772],{"class":3013,"line":3618},[3011,8766,3328],{"class":3017},[3011,8768,3264],{"class":3017},[3011,8770,8771],{"class":3092}," ValidateEmail",[3011,8773,7275],{"class":3028},[3011,8775,8776],{"class":3013,"line":3623},[3011,8777,3072],{"class":3028},[3011,8779,8780,8783,8785,8787,8789,8791],{"class":3013,"line":3628},[3011,8781,8782],{"class":3092},"        ClearErrors",[3011,8784,3096],{"class":3028},[3011,8786,3099],{"class":3017},[3011,8788,3096],{"class":3028},[3011,8790,6940],{"class":3024},[3011,8792,3107],{"class":3028},[3011,8794,8795],{"class":3013,"line":3633},[3011,8796,5088],{"class":3028},[3011,8798,8799,8801,8803,8805,8807,8809,8811,8813],{"class":3013,"line":3639},[3011,8800,5028],{"class":4125},[3011,8802,5031],{"class":3028},[3011,8804,3272],{"class":3017},[3011,8806,4010],{"class":3028},[3011,8808,7198],{"class":3092},[3011,8810,3096],{"class":3028},[3011,8812,6940],{"class":3024},[3011,8814,5061],{"class":3028},[3011,8816,8817],{"class":3013,"line":3653},[3011,8818,3375],{"class":3028},[3011,8820,8821,8824,8826,8828,8830,8832,8835,8838],{"class":3013,"line":3658},[3011,8822,8823],{"class":3092},"            AddError",[3011,8825,3096],{"class":3028},[3011,8827,3099],{"class":3017},[3011,8829,3096],{"class":3028},[3011,8831,6940],{"class":3024},[3011,8833,8834],{"class":3028},"), ",[3011,8836,8837],{"class":3894},"\"Email обов'язковий\"",[3011,8839,3898],{"class":3028},[3011,8841,8842],{"class":3013,"line":3664},[3011,8843,3410],{"class":3028},[3011,8845,8846,8849,8852,8854,8856,8858,8860,8862,8864],{"class":3013,"line":3677},[3011,8847,8848],{"class":4125},"        else",[3011,8850,8851],{"class":4125}," if",[3011,8853,8160],{"class":3028},[3011,8855,6940],{"class":3024},[3011,8857,4010],{"class":3028},[3011,8859,7247],{"class":3092},[3011,8861,3096],{"class":3028},[3011,8863,7252],{"class":3894},[3011,8865,5061],{"class":3028},[3011,8867,8868],{"class":3013,"line":3682},[3011,8869,3375],{"class":3028},[3011,8871,8872,8874,8876,8878,8880,8882,8884,8887],{"class":3013,"line":3699},[3011,8873,8823],{"class":3092},[3011,8875,3096],{"class":3028},[3011,8877,3099],{"class":3017},[3011,8879,3096],{"class":3028},[3011,8881,6940],{"class":3024},[3011,8883,8834],{"class":3028},[3011,8885,8886],{"class":3894},"\"Некоректний формат email\"",[3011,8888,3898],{"class":3028},[3011,8890,8891],{"class":3013,"line":3704},[3011,8892,3410],{"class":3028},[3011,8894,8895,8897,8899,8901,8903,8905,8908,8911,8915],{"class":3013,"line":3729},[3011,8896,8848],{"class":4125},[3011,8898,8851],{"class":4125},[3011,8900,5031],{"class":3028},[3011,8902,6940],{"class":3024},[3011,8904,4010],{"class":3028},[3011,8906,8907],{"class":3024},"Length",[3011,8909,8910],{"class":3028}," \u003C ",[3011,8912,8914],{"class":8913},"sJj4R","5",[3011,8916,3278],{"class":3028},[3011,8918,8919],{"class":3013,"line":3734},[3011,8920,3375],{"class":3028},[3011,8922,8923,8925,8927,8929,8931,8933,8935,8938],{"class":3013,"line":3739},[3011,8924,8823],{"class":3092},[3011,8926,3096],{"class":3028},[3011,8928,3099],{"class":3017},[3011,8930,3096],{"class":3028},[3011,8932,6940],{"class":3024},[3011,8934,8834],{"class":3028},[3011,8936,8937],{"class":3894},"\"Email занадто короткий\"",[3011,8939,3898],{"class":3028},[3011,8941,8942],{"class":3013,"line":3745},[3011,8943,3410],{"class":3028},[3011,8945,8946],{"class":3013,"line":8277},[3011,8947,3113],{"class":3028},[3011,8949,8950],{"class":3013,"line":8282},[3011,8951,3256],{"class":3028},[3011,8953,8954],{"class":3013,"line":8288},[3011,8955,8956],{"class":3213},"    // Валідація Password\n",[3011,8958,8959,8961,8963,8966],{"class":3013,"line":8306},[3011,8960,3328],{"class":3017},[3011,8962,3264],{"class":3017},[3011,8964,8965],{"class":3092}," ValidatePassword",[3011,8967,7275],{"class":3028},[3011,8969,8970],{"class":3013,"line":8311},[3011,8971,3072],{"class":3028},[3011,8973,8974,8976,8978,8980,8982,8985],{"class":3013,"line":8330},[3011,8975,8782],{"class":3092},[3011,8977,3096],{"class":3028},[3011,8979,3099],{"class":3017},[3011,8981,3096],{"class":3028},[3011,8983,8984],{"class":3024},"Password",[3011,8986,3107],{"class":3028},[3011,8988,8989],{"class":3013,"line":8335},[3011,8990,5088],{"class":3028},[3011,8992,8993,8995,8997,8999,9001,9003,9005,9007],{"class":3013,"line":8351},[3011,8994,5028],{"class":4125},[3011,8996,5031],{"class":3028},[3011,8998,3272],{"class":3017},[3011,9000,4010],{"class":3028},[3011,9002,7198],{"class":3092},[3011,9004,3096],{"class":3028},[3011,9006,8984],{"class":3024},[3011,9008,5061],{"class":3028},[3011,9010,9011],{"class":3013,"line":8362},[3011,9012,3375],{"class":3028},[3011,9014,9015,9017,9019,9021,9023,9025,9027,9030],{"class":3013,"line":8367},[3011,9016,8823],{"class":3092},[3011,9018,3096],{"class":3028},[3011,9020,3099],{"class":3017},[3011,9022,3096],{"class":3028},[3011,9024,8984],{"class":3024},[3011,9026,8834],{"class":3028},[3011,9028,9029],{"class":3894},"\"Пароль обов'язковий\"",[3011,9031,3898],{"class":3028},[3011,9033,9034],{"class":3013,"line":8372},[3011,9035,3410],{"class":3028},[3011,9037,9038,9040,9042,9044,9046,9048,9050,9052,9055],{"class":3013,"line":8377},[3011,9039,8848],{"class":4125},[3011,9041,8851],{"class":4125},[3011,9043,5031],{"class":3028},[3011,9045,8984],{"class":3024},[3011,9047,4010],{"class":3028},[3011,9049,8907],{"class":3024},[3011,9051,8910],{"class":3028},[3011,9053,9054],{"class":8913},"8",[3011,9056,3278],{"class":3028},[3011,9058,9059],{"class":3013,"line":8383},[3011,9060,3375],{"class":3028},[3011,9062,9063,9065,9067,9069,9071,9073,9075,9078],{"class":3013,"line":8401},[3011,9064,8823],{"class":3092},[3011,9066,3096],{"class":3028},[3011,9068,3099],{"class":3017},[3011,9070,3096],{"class":3028},[3011,9072,8984],{"class":3024},[3011,9074,8834],{"class":3028},[3011,9076,9077],{"class":3894},"\"Пароль має бути мінімум 8 символів\"",[3011,9079,3898],{"class":3028},[3011,9081,9082],{"class":3013,"line":8406},[3011,9083,3410],{"class":3028},[3011,9085,9086,9088,9090,9092,9094,9096,9098,9100,9103,9105,9108],{"class":3013,"line":8433},[3011,9087,8848],{"class":4125},[3011,9089,8851],{"class":4125},[3011,9091,8160],{"class":3028},[3011,9093,8984],{"class":3024},[3011,9095,4010],{"class":3028},[3011,9097,7989],{"class":3092},[3011,9099,3096],{"class":3028},[3011,9101,9102],{"class":3017},"char",[3011,9104,4010],{"class":3028},[3011,9106,9107],{"class":3024},"IsDigit",[3011,9109,5061],{"class":3028},[3011,9111,9112],{"class":3013,"line":8449},[3011,9113,3375],{"class":3028},[3011,9115,9116,9118,9120,9122,9124,9126,9128,9131],{"class":3013,"line":8454},[3011,9117,8823],{"class":3092},[3011,9119,3096],{"class":3028},[3011,9121,3099],{"class":3017},[3011,9123,3096],{"class":3028},[3011,9125,8984],{"class":3024},[3011,9127,8834],{"class":3028},[3011,9129,9130],{"class":3894},"\"Пароль має містити цифру\"",[3011,9132,3898],{"class":3028},[3011,9134,9136],{"class":3013,"line":9135},77,[3011,9137,3410],{"class":3028},[3011,9139,9141],{"class":3013,"line":9140},78,[3011,9142,5088],{"class":3028},[3011,9144,9146],{"class":3013,"line":9145},79,[3011,9147,9148],{"class":3213},"        // Перевалідувати ConfirmPassword при зміні Password\n",[3011,9150,9152,9155],{"class":3013,"line":9151},80,[3011,9153,9154],{"class":3092},"        ValidateConfirmPassword",[3011,9156,4395],{"class":3028},[3011,9158,9160],{"class":3013,"line":9159},81,[3011,9161,3113],{"class":3028},[3011,9163,9165],{"class":3013,"line":9164},82,[3011,9166,3256],{"class":3028},[3011,9168,9170],{"class":3013,"line":9169},83,[3011,9171,9172],{"class":3213},"    // Валідація ConfirmPassword\n",[3011,9174,9176,9178,9180,9183],{"class":3013,"line":9175},84,[3011,9177,3328],{"class":3017},[3011,9179,3264],{"class":3017},[3011,9181,9182],{"class":3092}," ValidateConfirmPassword",[3011,9184,7275],{"class":3028},[3011,9186,9188],{"class":3013,"line":9187},85,[3011,9189,3072],{"class":3028},[3011,9191,9193,9195,9197,9199,9201,9204],{"class":3013,"line":9192},86,[3011,9194,8782],{"class":3092},[3011,9196,3096],{"class":3028},[3011,9198,3099],{"class":3017},[3011,9200,3096],{"class":3028},[3011,9202,9203],{"class":3024},"ConfirmPassword",[3011,9205,3107],{"class":3028},[3011,9207,9209],{"class":3013,"line":9208},87,[3011,9210,5088],{"class":3028},[3011,9212,9214,9216,9218,9220,9222,9224,9226,9228],{"class":3013,"line":9213},88,[3011,9215,5028],{"class":4125},[3011,9217,5031],{"class":3028},[3011,9219,3272],{"class":3017},[3011,9221,4010],{"class":3028},[3011,9223,7198],{"class":3092},[3011,9225,3096],{"class":3028},[3011,9227,9203],{"class":3024},[3011,9229,5061],{"class":3028},[3011,9231,9233],{"class":3013,"line":9232},89,[3011,9234,3375],{"class":3028},[3011,9236,9238,9240,9242,9244,9246,9248,9250,9253],{"class":3013,"line":9237},90,[3011,9239,8823],{"class":3092},[3011,9241,3096],{"class":3028},[3011,9243,3099],{"class":3017},[3011,9245,3096],{"class":3028},[3011,9247,9203],{"class":3024},[3011,9249,8834],{"class":3028},[3011,9251,9252],{"class":3894},"\"Підтвердження паролю обов'язкове\"",[3011,9254,3898],{"class":3028},[3011,9256,9258],{"class":3013,"line":9257},91,[3011,9259,3410],{"class":3028},[3011,9261,9263,9265,9267,9269,9271,9274,9276],{"class":3013,"line":9262},92,[3011,9264,8848],{"class":4125},[3011,9266,8851],{"class":4125},[3011,9268,5031],{"class":3028},[3011,9270,8984],{"class":3024},[3011,9272,9273],{"class":3028}," != ",[3011,9275,9203],{"class":3024},[3011,9277,3278],{"class":3028},[3011,9279,9281],{"class":3013,"line":9280},93,[3011,9282,3375],{"class":3028},[3011,9284,9286,9288,9290,9292,9294,9296,9298,9301],{"class":3013,"line":9285},94,[3011,9287,8823],{"class":3092},[3011,9289,3096],{"class":3028},[3011,9291,3099],{"class":3017},[3011,9293,3096],{"class":3028},[3011,9295,9203],{"class":3024},[3011,9297,8834],{"class":3028},[3011,9299,9300],{"class":3894},"\"Паролі не співпадають\"",[3011,9302,3898],{"class":3028},[3011,9304,9306],{"class":3013,"line":9305},95,[3011,9307,3410],{"class":3028},[3011,9309,9311],{"class":3013,"line":9310},96,[3011,9312,3113],{"class":3028},[3011,9314,9316],{"class":3013,"line":9315},97,[3011,9317,3119],{"class":3028},[2965,9319,9320],{},[2975,9321,9322],{},"XAML з відображенням помилок:",[3002,9324,9326],{"className":6138,"code":9325,"language":6140,"meta":3007,"style":3007},"\u003CStackPanel Margin=\"20\">\n    \u003C!-- Email -->\n    \u003CTextBlock Text=\"Email:\"/>\n    \u003CTextBox Text=\"{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}\"/>\n    \n    \u003C!-- Password -->\n    \u003CTextBlock Text=\"Пароль:\" Margin=\"0,10,0,0\"/>\n    \u003CPasswordBox x:Name=\"passwordBox\"/>\n    \n    \u003C!-- Confirm Password -->\n    \u003CTextBlock Text=\"Підтвердження паролю:\" Margin=\"0,10,0,0\"/>\n    \u003CTextBox Text=\"{Binding ConfirmPassword, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}\"/>\n    \n    \u003C!-- Кнопка (активна тільки якщо немає помилок) -->\n    \u003CButton Content=\"Зареєструватися\" \n            IsEnabled=\"{Binding HasErrors, Converter={StaticResource InverseBooleanConverter}}\"\n            Margin=\"0,20,0,0\"/>\n\u003C/StackPanel>\n",[2989,9327,9328,9346,9351,9367,9383,9387,9392,9414,9431,9435,9440,9461,9476,9480,9485,9501,9511,9523],{"__ignoreMap":3007},[3011,9329,9330,9332,9335,9338,9340,9343],{"class":3013,"line":3014},[3011,9331,4975],{"class":6147},[3011,9333,9334],{"class":6150},"StackPanel",[3011,9336,9337],{"class":6154}," Margin",[3011,9339,6158],{"class":3028},[3011,9341,9342],{"class":6161},"\"20\"",[3011,9344,9345],{"class":6147},">\n",[3011,9347,9348],{"class":3013,"line":3032},[3011,9349,9350],{"class":3213},"    \u003C!-- Email -->\n",[3011,9352,9353,9356,9358,9360,9362,9365],{"class":3013,"line":3043},[3011,9354,9355],{"class":6147},"    \u003C",[3011,9357,6151],{"class":6150},[3011,9359,6155],{"class":6154},[3011,9361,6158],{"class":3028},[3011,9363,9364],{"class":6161},"\"Email:\"",[3011,9366,6165],{"class":6147},[3011,9368,9369,9371,9374,9376,9378,9381],{"class":3013,"line":3049},[3011,9370,9355],{"class":6147},[3011,9372,9373],{"class":6150},"TextBox",[3011,9375,6155],{"class":6154},[3011,9377,6158],{"class":3028},[3011,9379,9380],{"class":6161},"\"{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}\"",[3011,9382,6165],{"class":6147},[3011,9384,9385],{"class":3013,"line":3063},[3011,9386,3256],{"class":3028},[3011,9388,9389],{"class":3013,"line":3069},[3011,9390,9391],{"class":3213},"    \u003C!-- Password -->\n",[3011,9393,9394,9396,9398,9400,9402,9405,9407,9409,9412],{"class":3013,"line":3075},[3011,9395,9355],{"class":6147},[3011,9397,6151],{"class":6150},[3011,9399,6155],{"class":6154},[3011,9401,6158],{"class":3028},[3011,9403,9404],{"class":6161},"\"Пароль:\"",[3011,9406,9337],{"class":6154},[3011,9408,6158],{"class":3028},[3011,9410,9411],{"class":6161},"\"0,10,0,0\"",[3011,9413,6165],{"class":6147},[3011,9415,9416,9418,9421,9424,9426,9429],{"class":3013,"line":3089},[3011,9417,9355],{"class":6147},[3011,9419,9420],{"class":6150},"PasswordBox",[3011,9422,9423],{"class":6154}," x:Name",[3011,9425,6158],{"class":3028},[3011,9427,9428],{"class":6161},"\"passwordBox\"",[3011,9430,6165],{"class":6147},[3011,9432,9433],{"class":3013,"line":3110},[3011,9434,3256],{"class":3028},[3011,9436,9437],{"class":3013,"line":3116},[3011,9438,9439],{"class":3213},"    \u003C!-- Confirm Password -->\n",[3011,9441,9442,9444,9446,9448,9450,9453,9455,9457,9459],{"class":3013,"line":3325},[3011,9443,9355],{"class":6147},[3011,9445,6151],{"class":6150},[3011,9447,6155],{"class":6154},[3011,9449,6158],{"class":3028},[3011,9451,9452],{"class":6161},"\"Підтвердження паролю:\"",[3011,9454,9337],{"class":6154},[3011,9456,6158],{"class":3028},[3011,9458,9411],{"class":6161},[3011,9460,6165],{"class":6147},[3011,9462,9463,9465,9467,9469,9471,9474],{"class":3013,"line":3338},[3011,9464,9355],{"class":6147},[3011,9466,9373],{"class":6150},[3011,9468,6155],{"class":6154},[3011,9470,6158],{"class":3028},[3011,9472,9473],{"class":6161},"\"{Binding ConfirmPassword, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}\"",[3011,9475,6165],{"class":6147},[3011,9477,9478],{"class":3013,"line":3348},[3011,9479,3256],{"class":3028},[3011,9481,9482],{"class":3013,"line":3353},[3011,9483,9484],{"class":3213},"    \u003C!-- Кнопка (активна тільки якщо немає помилок) -->\n",[3011,9486,9487,9489,9491,9493,9495,9498],{"class":3013,"line":3366},[3011,9488,9355],{"class":6147},[3011,9490,7318],{"class":6150},[3011,9492,7321],{"class":6154},[3011,9494,6158],{"class":3028},[3011,9496,9497],{"class":6161},"\"Зареєструватися\"",[3011,9499,9500],{"class":3028}," \n",[3011,9502,9503,9506,9508],{"class":3013,"line":3372},[3011,9504,9505],{"class":6154},"            IsEnabled",[3011,9507,6158],{"class":3028},[3011,9509,9510],{"class":6161},"\"{Binding HasErrors, Converter={StaticResource InverseBooleanConverter}}\"\n",[3011,9512,9513,9516,9518,9521],{"class":3013,"line":3378},[3011,9514,9515],{"class":6154},"            Margin",[3011,9517,6158],{"class":3028},[3011,9519,9520],{"class":6161},"\"0,20,0,0\"",[3011,9522,6165],{"class":6147},[3011,9524,9525,9528,9530],{"class":3013,"line":3390},[3011,9526,9527],{"class":6147},"\u003C/",[3011,9529,9334],{"class":6150},[3011,9531,9345],{"class":6147},[2965,9533,9534],{},[2975,9535,4276],{},[4278,9537,9538,9549,9557],{},[2985,9539,9540,9545,9546,9548],{},[2975,9541,9542],{},[2989,9543,9544],{},"ValidatesOnNotifyDataErrors=True"," — WPF автоматично підписується на ",[2989,9547,7469],{}," та показує помилки",[2985,9550,9551,9556],{},[2975,9552,9553],{},[2989,9554,9555],{},"UpdateSourceTrigger=PropertyChanged"," — валідація відбувається при кожній зміні, а не тільки при втраті фокусу",[2985,9558,9559,9564],{},[2975,9560,9561],{},[2989,9562,9563],{},"IsEnabled=\"{Binding HasErrors, Converter=...}\""," — кнопка активна тільки якщо немає помилок",[3194,9566,9568],{"id":9567},"стилізація-помилок-валідації-у-view","Стилізація помилок валідації у View",[2965,9570,9571,9572,4010],{},"WPF автоматично показує червону рамку навколо контролу з помилкою, але можна кастомізувати через ",[2989,9573,9574],{},"Validation.ErrorTemplate",[2965,9576,9577],{},[2975,9578,9579],{},"Кастомний шаблон помилки:",[3002,9581,9583],{"className":6138,"code":9582,"language":6140,"meta":3007,"style":3007},"\u003CWindow.Resources>\n    \u003C!-- Шаблон для відображення помилок -->\n    \u003CControlTemplate x:Key=\"ValidationErrorTemplate\">\n        \u003CDockPanel>\n            \u003C!-- Червона рамка навколо контролу -->\n            \u003CBorder BorderBrush=\"Red\" BorderThickness=\"2\" CornerRadius=\"3\">\n                \u003CAdornedElementPlaceholder/>\n            \u003C/Border>\n            \n            \u003C!-- Іконка помилки -->\n            \u003CTextBlock DockPanel.Dock=\"Right\" \n                       Text=\"⚠\" \n                       Foreground=\"Red\" \n                       FontSize=\"16\" \n                       Margin=\"5,0,0,0\"\n                       ToolTip=\"{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}\"/>\n        \u003C/DockPanel>\n    \u003C/ControlTemplate>\n\u003C/Window.Resources>\n\n\u003CTextBox Text=\"{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}\"\n         Validation.ErrorTemplate=\"{StaticResource ValidationErrorTemplate}\"/>\n",[2989,9584,9585,9594,9599,9616,9626,9631,9665,9675,9684,9689,9694,9710,9722,9733,9745,9755,9767,9776,9785,9793,9797,9810],{"__ignoreMap":3007},[3011,9586,9587,9589,9592],{"class":3013,"line":3014},[3011,9588,4975],{"class":6147},[3011,9590,9591],{"class":6150},"Window.Resources",[3011,9593,9345],{"class":6147},[3011,9595,9596],{"class":3013,"line":3032},[3011,9597,9598],{"class":3213},"    \u003C!-- Шаблон для відображення помилок -->\n",[3011,9600,9601,9603,9606,9609,9611,9614],{"class":3013,"line":3043},[3011,9602,9355],{"class":6147},[3011,9604,9605],{"class":6150},"ControlTemplate",[3011,9607,9608],{"class":6154}," x:Key",[3011,9610,6158],{"class":3028},[3011,9612,9613],{"class":6161},"\"ValidationErrorTemplate\"",[3011,9615,9345],{"class":6147},[3011,9617,9618,9621,9624],{"class":3013,"line":3049},[3011,9619,9620],{"class":6147},"        \u003C",[3011,9622,9623],{"class":6150},"DockPanel",[3011,9625,9345],{"class":6147},[3011,9627,9628],{"class":3013,"line":3063},[3011,9629,9630],{"class":3213},"            \u003C!-- Червона рамка навколо контролу -->\n",[3011,9632,9633,9636,9639,9642,9644,9647,9650,9652,9655,9658,9660,9663],{"class":3013,"line":3069},[3011,9634,9635],{"class":6147},"            \u003C",[3011,9637,9638],{"class":6150},"Border",[3011,9640,9641],{"class":6154}," BorderBrush",[3011,9643,6158],{"class":3028},[3011,9645,9646],{"class":6161},"\"Red\"",[3011,9648,9649],{"class":6154}," BorderThickness",[3011,9651,6158],{"class":3028},[3011,9653,9654],{"class":6161},"\"2\"",[3011,9656,9657],{"class":6154}," CornerRadius",[3011,9659,6158],{"class":3028},[3011,9661,9662],{"class":6161},"\"3\"",[3011,9664,9345],{"class":6147},[3011,9666,9667,9670,9673],{"class":3013,"line":3075},[3011,9668,9669],{"class":6147},"                \u003C",[3011,9671,9672],{"class":6150},"AdornedElementPlaceholder",[3011,9674,6165],{"class":6147},[3011,9676,9677,9680,9682],{"class":3013,"line":3089},[3011,9678,9679],{"class":6147},"            \u003C/",[3011,9681,9638],{"class":6150},[3011,9683,9345],{"class":6147},[3011,9685,9686],{"class":3013,"line":3110},[3011,9687,9688],{"class":3028},"            \n",[3011,9690,9691],{"class":3013,"line":3116},[3011,9692,9693],{"class":3213},"            \u003C!-- Іконка помилки -->\n",[3011,9695,9696,9698,9700,9703,9705,9708],{"class":3013,"line":3325},[3011,9697,9635],{"class":6147},[3011,9699,6151],{"class":6150},[3011,9701,9702],{"class":6154}," DockPanel.Dock",[3011,9704,6158],{"class":3028},[3011,9706,9707],{"class":6161},"\"Right\"",[3011,9709,9500],{"class":3028},[3011,9711,9712,9715,9717,9720],{"class":3013,"line":3338},[3011,9713,9714],{"class":6154},"                       Text",[3011,9716,6158],{"class":3028},[3011,9718,9719],{"class":6161},"\"⚠\"",[3011,9721,9500],{"class":3028},[3011,9723,9724,9727,9729,9731],{"class":3013,"line":3348},[3011,9725,9726],{"class":6154},"                       Foreground",[3011,9728,6158],{"class":3028},[3011,9730,9646],{"class":6161},[3011,9732,9500],{"class":3028},[3011,9734,9735,9738,9740,9743],{"class":3013,"line":3353},[3011,9736,9737],{"class":6154},"                       FontSize",[3011,9739,6158],{"class":3028},[3011,9741,9742],{"class":6161},"\"16\"",[3011,9744,9500],{"class":3028},[3011,9746,9747,9750,9752],{"class":3013,"line":3366},[3011,9748,9749],{"class":6154},"                       Margin",[3011,9751,6158],{"class":3028},[3011,9753,9754],{"class":6161},"\"5,0,0,0\"\n",[3011,9756,9757,9760,9762,9765],{"class":3013,"line":3372},[3011,9758,9759],{"class":6154},"                       ToolTip",[3011,9761,6158],{"class":3028},[3011,9763,9764],{"class":6161},"\"{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}\"",[3011,9766,6165],{"class":6147},[3011,9768,9769,9772,9774],{"class":3013,"line":3378},[3011,9770,9771],{"class":6147},"        \u003C/",[3011,9773,9623],{"class":6150},[3011,9775,9345],{"class":6147},[3011,9777,9778,9781,9783],{"class":3013,"line":3390},[3011,9779,9780],{"class":6147},"    \u003C/",[3011,9782,9605],{"class":6150},[3011,9784,9345],{"class":6147},[3011,9786,9787,9789,9791],{"class":3013,"line":3407},[3011,9788,9527],{"class":6147},[3011,9790,9591],{"class":6150},[3011,9792,9345],{"class":6147},[3011,9794,9795],{"class":3013,"line":3413},[3011,9796,3427],{"emptyLinePlaceholder":3426},[3011,9798,9799,9801,9803,9805,9807],{"class":3013,"line":3418},[3011,9800,4975],{"class":6147},[3011,9802,9373],{"class":6150},[3011,9804,6155],{"class":6154},[3011,9806,6158],{"class":3028},[3011,9808,9809],{"class":6161},"\"{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}\"\n",[3011,9811,9812,9815,9817,9820],{"class":3013,"line":3423},[3011,9813,9814],{"class":6154},"         Validation.ErrorTemplate",[3011,9816,6158],{"class":3028},[3011,9818,9819],{"class":6161},"\"{StaticResource ValidationErrorTemplate}\"",[3011,9821,6165],{"class":6147},[2965,9823,9824],{},[2975,9825,9826],{},"Tooltip з текстом помилки:",[3002,9828,9830],{"className":6138,"code":9829,"language":6140,"meta":3007,"style":3007},"\u003CStyle TargetType=\"TextBox\">\n    \u003CStyle.Triggers>\n        \u003CTrigger Property=\"Validation.HasError\" Value=\"True\">\n            \u003CSetter Property=\"ToolTip\" \n                    Value=\"{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}\"/>\n        \u003C/Trigger>\n    \u003C/Style.Triggers>\n\u003C/Style>\n",[2989,9831,9832,9849,9858,9883,9899,9911,9919,9927],{"__ignoreMap":3007},[3011,9833,9834,9836,9839,9842,9844,9847],{"class":3013,"line":3014},[3011,9835,4975],{"class":6147},[3011,9837,9838],{"class":6150},"Style",[3011,9840,9841],{"class":6154}," TargetType",[3011,9843,6158],{"class":3028},[3011,9845,9846],{"class":6161},"\"TextBox\"",[3011,9848,9345],{"class":6147},[3011,9850,9851,9853,9856],{"class":3013,"line":3032},[3011,9852,9355],{"class":6147},[3011,9854,9855],{"class":6150},"Style.Triggers",[3011,9857,9345],{"class":6147},[3011,9859,9860,9862,9865,9868,9870,9873,9876,9878,9881],{"class":3013,"line":3043},[3011,9861,9620],{"class":6147},[3011,9863,9864],{"class":6150},"Trigger",[3011,9866,9867],{"class":6154}," Property",[3011,9869,6158],{"class":3028},[3011,9871,9872],{"class":6161},"\"Validation.HasError\"",[3011,9874,9875],{"class":6154}," Value",[3011,9877,6158],{"class":3028},[3011,9879,9880],{"class":6161},"\"True\"",[3011,9882,9345],{"class":6147},[3011,9884,9885,9887,9890,9892,9894,9897],{"class":3013,"line":3049},[3011,9886,9635],{"class":6147},[3011,9888,9889],{"class":6150},"Setter",[3011,9891,9867],{"class":6154},[3011,9893,6158],{"class":3028},[3011,9895,9896],{"class":6161},"\"ToolTip\"",[3011,9898,9500],{"class":3028},[3011,9900,9901,9904,9906,9909],{"class":3013,"line":3063},[3011,9902,9903],{"class":6154},"                    Value",[3011,9905,6158],{"class":3028},[3011,9907,9908],{"class":6161},"\"{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}\"",[3011,9910,6165],{"class":6147},[3011,9912,9913,9915,9917],{"class":3013,"line":3069},[3011,9914,9771],{"class":6147},[3011,9916,9864],{"class":6150},[3011,9918,9345],{"class":6147},[3011,9920,9921,9923,9925],{"class":3013,"line":3075},[3011,9922,9780],{"class":6147},[3011,9924,9855],{"class":6150},[3011,9926,9345],{"class":6147},[3011,9928,9929,9931,9933],{"class":3013,"line":3089},[3011,9930,9527],{"class":6147},[3011,9932,9838],{"class":6150},[3011,9934,9345],{"class":6147},[2965,9936,9937],{},[2975,9938,9939],{},"Візуалізація:",[9941,9942,9944,10189],"wpf-preview",{"title":9943},"Валідація форми реєстрації",[3002,9945,9947],{"className":6138,"code":9946,"language":6140,"meta":3007,"style":3007},"\u003CStackPanel Margin=\"20\" Width=\"300\">\n    \u003CTextBlock Text=\"Email:\" FontWeight=\"Bold\"/>\n    \u003CTextBox Text=\"{Binding Email, UpdateSourceTrigger=PropertyChanged}\" \n             Margin=\"0,5,0,0\"/>\n    \n    \u003CTextBlock Text=\"Пароль:\" FontWeight=\"Bold\" Margin=\"0,15,0,0\"/>\n    \u003CTextBox Text=\"{Binding Password, UpdateSourceTrigger=PropertyChanged}\" \n             Margin=\"0,5,0,0\"/>\n    \n    \u003CTextBlock Text=\"Підтвердження:\" FontWeight=\"Bold\" Margin=\"0,15,0,0\"/>\n    \u003CTextBox Text=\"{Binding ConfirmPassword, UpdateSourceTrigger=PropertyChanged}\" \n             Margin=\"0,5,0,0\"/>\n    \n    \u003CButton Content=\"Зареєструватися\" \n            Command=\"{Binding RegisterCommand}\"\n            Margin=\"0,20,0,0\"\n            HorizontalAlignment=\"Stretch\"/>\n\u003C/StackPanel>\n",[2989,9948,9949,9971,9993,10008,10020,10024,10051,10066,10076,10080,10107,10122,10132,10136,10150,10160,10169,10181],{"__ignoreMap":3007},[3011,9950,9951,9953,9955,9957,9959,9961,9964,9966,9969],{"class":3013,"line":3014},[3011,9952,4975],{"class":6147},[3011,9954,9334],{"class":6150},[3011,9956,9337],{"class":6154},[3011,9958,6158],{"class":3028},[3011,9960,9342],{"class":6161},[3011,9962,9963],{"class":6154}," Width",[3011,9965,6158],{"class":3028},[3011,9967,9968],{"class":6161},"\"300\"",[3011,9970,9345],{"class":6147},[3011,9972,9973,9975,9977,9979,9981,9983,9986,9988,9991],{"class":3013,"line":3032},[3011,9974,9355],{"class":6147},[3011,9976,6151],{"class":6150},[3011,9978,6155],{"class":6154},[3011,9980,6158],{"class":3028},[3011,9982,9364],{"class":6161},[3011,9984,9985],{"class":6154}," FontWeight",[3011,9987,6158],{"class":3028},[3011,9989,9990],{"class":6161},"\"Bold\"",[3011,9992,6165],{"class":6147},[3011,9994,9995,9997,9999,10001,10003,10006],{"class":3013,"line":3043},[3011,9996,9355],{"class":6147},[3011,9998,9373],{"class":6150},[3011,10000,6155],{"class":6154},[3011,10002,6158],{"class":3028},[3011,10004,10005],{"class":6161},"\"{Binding Email, UpdateSourceTrigger=PropertyChanged}\"",[3011,10007,9500],{"class":3028},[3011,10009,10010,10013,10015,10018],{"class":3013,"line":3049},[3011,10011,10012],{"class":6154},"             Margin",[3011,10014,6158],{"class":3028},[3011,10016,10017],{"class":6161},"\"0,5,0,0\"",[3011,10019,6165],{"class":6147},[3011,10021,10022],{"class":3013,"line":3063},[3011,10023,3256],{"class":3028},[3011,10025,10026,10028,10030,10032,10034,10036,10038,10040,10042,10044,10046,10049],{"class":3013,"line":3069},[3011,10027,9355],{"class":6147},[3011,10029,6151],{"class":6150},[3011,10031,6155],{"class":6154},[3011,10033,6158],{"class":3028},[3011,10035,9404],{"class":6161},[3011,10037,9985],{"class":6154},[3011,10039,6158],{"class":3028},[3011,10041,9990],{"class":6161},[3011,10043,9337],{"class":6154},[3011,10045,6158],{"class":3028},[3011,10047,10048],{"class":6161},"\"0,15,0,0\"",[3011,10050,6165],{"class":6147},[3011,10052,10053,10055,10057,10059,10061,10064],{"class":3013,"line":3075},[3011,10054,9355],{"class":6147},[3011,10056,9373],{"class":6150},[3011,10058,6155],{"class":6154},[3011,10060,6158],{"class":3028},[3011,10062,10063],{"class":6161},"\"{Binding Password, UpdateSourceTrigger=PropertyChanged}\"",[3011,10065,9500],{"class":3028},[3011,10067,10068,10070,10072,10074],{"class":3013,"line":3089},[3011,10069,10012],{"class":6154},[3011,10071,6158],{"class":3028},[3011,10073,10017],{"class":6161},[3011,10075,6165],{"class":6147},[3011,10077,10078],{"class":3013,"line":3110},[3011,10079,3256],{"class":3028},[3011,10081,10082,10084,10086,10088,10090,10093,10095,10097,10099,10101,10103,10105],{"class":3013,"line":3116},[3011,10083,9355],{"class":6147},[3011,10085,6151],{"class":6150},[3011,10087,6155],{"class":6154},[3011,10089,6158],{"class":3028},[3011,10091,10092],{"class":6161},"\"Підтвердження:\"",[3011,10094,9985],{"class":6154},[3011,10096,6158],{"class":3028},[3011,10098,9990],{"class":6161},[3011,10100,9337],{"class":6154},[3011,10102,6158],{"class":3028},[3011,10104,10048],{"class":6161},[3011,10106,6165],{"class":6147},[3011,10108,10109,10111,10113,10115,10117,10120],{"class":3013,"line":3325},[3011,10110,9355],{"class":6147},[3011,10112,9373],{"class":6150},[3011,10114,6155],{"class":6154},[3011,10116,6158],{"class":3028},[3011,10118,10119],{"class":6161},"\"{Binding ConfirmPassword, UpdateSourceTrigger=PropertyChanged}\"",[3011,10121,9500],{"class":3028},[3011,10123,10124,10126,10128,10130],{"class":3013,"line":3338},[3011,10125,10012],{"class":6154},[3011,10127,6158],{"class":3028},[3011,10129,10017],{"class":6161},[3011,10131,6165],{"class":6147},[3011,10133,10134],{"class":3013,"line":3348},[3011,10135,3256],{"class":3028},[3011,10137,10138,10140,10142,10144,10146,10148],{"class":3013,"line":3353},[3011,10139,9355],{"class":6147},[3011,10141,7318],{"class":6150},[3011,10143,7321],{"class":6154},[3011,10145,6158],{"class":3028},[3011,10147,9497],{"class":6161},[3011,10149,9500],{"class":3028},[3011,10151,10152,10155,10157],{"class":3013,"line":3366},[3011,10153,10154],{"class":6154},"            Command",[3011,10156,6158],{"class":3028},[3011,10158,10159],{"class":6161},"\"{Binding RegisterCommand}\"\n",[3011,10161,10162,10164,10166],{"class":3013,"line":3372},[3011,10163,9515],{"class":6154},[3011,10165,6158],{"class":3028},[3011,10167,10168],{"class":6161},"\"0,20,0,0\"\n",[3011,10170,10171,10174,10176,10179],{"class":3013,"line":3378},[3011,10172,10173],{"class":6154},"            HorizontalAlignment",[3011,10175,6158],{"class":3028},[3011,10177,10178],{"class":6161},"\"Stretch\"",[3011,10180,6165],{"class":6147},[3011,10182,10183,10185,10187],{"class":3013,"line":3390},[3011,10184,9527],{"class":6147},[3011,10186,9334],{"class":6150},[3011,10188,9345],{"class":6147},[3002,10190,10192],{"className":3004,"code":10191,"language":3006,"meta":3007,"style":3007},"// Code-behind для демонстрації\npublic partial class MainWindow : Window\n{\n    public MainWindow()\n    {\n        InitializeComponent();\n        DataContext = new RegistrationViewModel();\n    }\n}\n\npublic class RegistrationViewModel\n{\n    public string Email { get; set; }\n    public string Password { get; set; }\n    public string ConfirmPassword { get; set; }\n    \n    public ICommand RegisterCommand => new RelayCommand(\n        () => { /* Логіка реєстрації */ },\n        () => !string.IsNullOrWhiteSpace(Email) && \n              !string.IsNullOrWhiteSpace(Password) && \n              Password == ConfirmPassword\n    );\n}\n",[2989,10193,10194,10199,10216,10220,10228,10232,10239,10252,10256,10260,10264,10273,10277,10298,10317,10336,10340,10360,10371,10389,10406,10417,10422],{"__ignoreMap":3007},[3011,10195,10196],{"class":3013,"line":3014},[3011,10197,10198],{"class":3213},"// Code-behind для демонстрації\n",[3011,10200,10201,10203,10206,10208,10211,10213],{"class":3013,"line":3032},[3011,10202,3035],{"class":3017},[3011,10204,10205],{"class":3017}," partial",[3011,10207,3221],{"class":3017},[3011,10209,10210],{"class":3224}," MainWindow",[3011,10212,3228],{"class":3028},[3011,10214,10215],{"class":3224},"Window\n",[3011,10217,10218],{"class":3013,"line":3043},[3011,10219,3046],{"class":3028},[3011,10221,10222,10224,10226],{"class":3013,"line":3049},[3011,10223,3240],{"class":3017},[3011,10225,10210],{"class":3092},[3011,10227,7275],{"class":3028},[3011,10229,10230],{"class":3013,"line":3063},[3011,10231,3072],{"class":3028},[3011,10233,10234,10237],{"class":3013,"line":3069},[3011,10235,10236],{"class":3092},"        InitializeComponent",[3011,10238,4395],{"class":3028},[3011,10240,10241,10244,10246,10248,10250],{"class":3013,"line":3075},[3011,10242,10243],{"class":3024},"        DataContext",[3011,10245,3081],{"class":3028},[3011,10247,3304],{"class":3017},[3011,10249,8479],{"class":3224},[3011,10251,4395],{"class":3028},[3011,10253,10254],{"class":3013,"line":3089},[3011,10255,3113],{"class":3028},[3011,10257,10258],{"class":3013,"line":3110},[3011,10259,3119],{"class":3028},[3011,10261,10262],{"class":3013,"line":3116},[3011,10263,3427],{"emptyLinePlaceholder":3426},[3011,10265,10266,10268,10270],{"class":3013,"line":3325},[3011,10267,3035],{"class":3017},[3011,10269,3221],{"class":3017},[3011,10271,10272],{"class":3224}," RegistrationViewModel\n",[3011,10274,10275],{"class":3013,"line":3338},[3011,10276,3046],{"class":3028},[3011,10278,10279,10281,10283,10286,10288,10290,10293,10296],{"class":3013,"line":3348},[3011,10280,3240],{"class":3017},[3011,10282,3021],{"class":3017},[3011,10284,10285],{"class":3024}," Email",[3011,10287,7410],{"class":3028},[3011,10289,7413],{"class":3017},[3011,10291,10292],{"class":3028},"; ",[3011,10294,10295],{"class":3017},"set",[3011,10297,7416],{"class":3028},[3011,10299,10300,10302,10304,10307,10309,10311,10313,10315],{"class":3013,"line":3353},[3011,10301,3240],{"class":3017},[3011,10303,3021],{"class":3017},[3011,10305,10306],{"class":3024}," Password",[3011,10308,7410],{"class":3028},[3011,10310,7413],{"class":3017},[3011,10312,10292],{"class":3028},[3011,10314,10295],{"class":3017},[3011,10316,7416],{"class":3028},[3011,10318,10319,10321,10323,10326,10328,10330,10332,10334],{"class":3013,"line":3366},[3011,10320,3240],{"class":3017},[3011,10322,3021],{"class":3017},[3011,10324,10325],{"class":3024}," ConfirmPassword",[3011,10327,7410],{"class":3028},[3011,10329,7413],{"class":3017},[3011,10331,10292],{"class":3028},[3011,10333,10295],{"class":3017},[3011,10335,7416],{"class":3028},[3011,10337,10338],{"class":3013,"line":3372},[3011,10339,3256],{"class":3028},[3011,10341,10342,10344,10347,10350,10352,10354,10357],{"class":3013,"line":3378},[3011,10343,3240],{"class":3017},[3011,10345,10346],{"class":3224}," ICommand",[3011,10348,10349],{"class":3024}," RegisterCommand",[3011,10351,3055],{"class":3028},[3011,10353,3304],{"class":3017},[3011,10355,10356],{"class":3224}," RelayCommand",[3011,10358,10359],{"class":3028},"(\n",[3011,10361,10362,10365,10368],{"class":3013,"line":3390},[3011,10363,10364],{"class":3028},"        () => { ",[3011,10366,10367],{"class":3213},"/* Логіка реєстрації */",[3011,10369,10370],{"class":3028}," },\n",[3011,10372,10373,10376,10378,10380,10382,10384,10386],{"class":3013,"line":3407},[3011,10374,10375],{"class":3028},"        () => !",[3011,10377,3272],{"class":3017},[3011,10379,4010],{"class":3028},[3011,10381,7198],{"class":3092},[3011,10383,3096],{"class":3028},[3011,10385,6940],{"class":3024},[3011,10387,10388],{"class":3028},") && \n",[3011,10390,10391,10394,10396,10398,10400,10402,10404],{"class":3013,"line":3413},[3011,10392,10393],{"class":3028},"              !",[3011,10395,3272],{"class":3017},[3011,10397,4010],{"class":3028},[3011,10399,7198],{"class":3092},[3011,10401,3096],{"class":3028},[3011,10403,8984],{"class":3024},[3011,10405,10388],{"class":3028},[3011,10407,10408,10411,10414],{"class":3013,"line":3418},[3011,10409,10410],{"class":3024},"              Password",[3011,10412,10413],{"class":3028}," == ",[3011,10415,10416],{"class":3024},"ConfirmPassword\n",[3011,10418,10419],{"class":3013,"line":3423},[3011,10420,10421],{"class":3028},"    );\n",[3011,10423,10424],{"class":3013,"line":3430},[3011,10425,3119],{"class":3028},[3167,10427,10428,10429,10431],{},"Превью використовує Avalonia і не підтримує ",[2989,10430,7365],{}," повністю. У реальному WPF-проєкті червона рамка з'явиться автоматично при помилці валідації.",[3181,10433],{},[2960,10435,10437],{"id":10436},"designtime-дані-робота-дизайнера-без-запуску","DesignTime дані: Робота дизайнера без запуску",[2965,10439,10440,10441,10444],{},"Одна з найбільших проблем при розробці UI — неможливість побачити результат без запуску додатку. Дизайнер Visual Studio показує порожні контроли, бо ",[2989,10442,10443],{},"DataContext"," встановлюється у runtime.",[3194,10446,10448],{"id":10447},"проблема-порожній-дизайнер","Проблема: Порожній дизайнер",[2965,10450,10451],{},[2975,10452,7306],{},[3002,10454,10456],{"className":6138,"code":10455,"language":6140,"meta":3007,"style":3007},"\u003CWindow x:Class=\"MyApp.MainWindow\"\n        DataContext=\"{Binding Source={StaticResource MainViewModel}}\">\n    \u003CStackPanel>\n        \u003CTextBlock Text=\"{Binding Title}\" FontSize=\"24\"/>\n        \u003CTextBlock Text=\"{Binding Description}\"/>\n        \u003CListBox ItemsSource=\"{Binding Items}\"/>\n    \u003C/StackPanel>\n\u003C/Window>\n",[2989,10457,10458,10473,10484,10492,10515,10530,10547,10555],{"__ignoreMap":3007},[3011,10459,10460,10462,10465,10468,10470],{"class":3013,"line":3014},[3011,10461,4975],{"class":6147},[3011,10463,10464],{"class":6150},"Window",[3011,10466,10467],{"class":6154}," x:Class",[3011,10469,6158],{"class":3028},[3011,10471,10472],{"class":6161},"\"MyApp.MainWindow\"\n",[3011,10474,10475,10477,10479,10482],{"class":3013,"line":3032},[3011,10476,10243],{"class":6154},[3011,10478,6158],{"class":3028},[3011,10480,10481],{"class":6161},"\"{Binding Source={StaticResource MainViewModel}}\"",[3011,10483,9345],{"class":6147},[3011,10485,10486,10488,10490],{"class":3013,"line":3043},[3011,10487,9355],{"class":6147},[3011,10489,9334],{"class":6150},[3011,10491,9345],{"class":6147},[3011,10493,10494,10496,10498,10500,10502,10505,10508,10510,10513],{"class":3013,"line":3049},[3011,10495,9620],{"class":6147},[3011,10497,6151],{"class":6150},[3011,10499,6155],{"class":6154},[3011,10501,6158],{"class":3028},[3011,10503,10504],{"class":6161},"\"{Binding Title}\"",[3011,10506,10507],{"class":6154}," FontSize",[3011,10509,6158],{"class":3028},[3011,10511,10512],{"class":6161},"\"24\"",[3011,10514,6165],{"class":6147},[3011,10516,10517,10519,10521,10523,10525,10528],{"class":3013,"line":3063},[3011,10518,9620],{"class":6147},[3011,10520,6151],{"class":6150},[3011,10522,6155],{"class":6154},[3011,10524,6158],{"class":3028},[3011,10526,10527],{"class":6161},"\"{Binding Description}\"",[3011,10529,6165],{"class":6147},[3011,10531,10532,10534,10537,10540,10542,10545],{"class":3013,"line":3069},[3011,10533,9620],{"class":6147},[3011,10535,10536],{"class":6150},"ListBox",[3011,10538,10539],{"class":6154}," ItemsSource",[3011,10541,6158],{"class":3028},[3011,10543,10544],{"class":6161},"\"{Binding Items}\"",[3011,10546,6165],{"class":6147},[3011,10548,10549,10551,10553],{"class":3013,"line":3075},[3011,10550,9780],{"class":6147},[3011,10552,9334],{"class":6150},[3011,10554,9345],{"class":6147},[3011,10556,10557,10559,10561],{"class":3013,"line":3089},[3011,10558,9527],{"class":6147},[3011,10560,10464],{"class":6150},[3011,10562,9345],{"class":6147},[2965,10564,10565],{},[2975,10566,10567],{},"Що бачить дизайнер:",[2982,10569,10570,10579,10586],{},[2985,10571,10572,10573,10575,10576,10578],{},"Порожній ",[2989,10574,6151],{}," (бо ",[2989,10577,3402],{}," = null)",[2985,10580,10572,10581,10575,10583,10578],{},[2989,10582,10536],{},[2989,10584,10585],{},"Items",[2985,10587,10588],{},"Неможливо оцінити layout, розміри, вирівнювання",[2965,10590,10591],{},[2975,10592,10593],{},"Що хочемо бачити:",[2982,10595,10596,10601,10607],{},[2985,10597,10598,10600],{},[2989,10599,3402],{}," = \"Мій додаток\"",[2985,10602,10603,10606],{},[2989,10604,10605],{},"Description"," = \"Опис функціональності\"",[2985,10608,10609,3081,10611],{},[2989,10610,10585],{},[3011,10612,10613],{},"\"Елемент 1\", \"Елемент 2\", \"Елемент 3\"",[3194,10615,10617],{"id":10616},"рішення-ddatacontext-для-designtime","Рішення: d:DataContext для DesignTime",[2965,10619,10620,10621,10624],{},"WPF надає спеціальний namespace ",[2989,10622,10623],{},"d:"," (design-time) для встановлення даних, що використовуються тільки у дизайнері.",[2965,10626,10627],{},[2975,10628,10629],{},"Крок 1: Додати namespace:",[3002,10631,10633],{"className":6138,"code":10632,"language":6140,"meta":3007,"style":3007},"\u003CWindow x:Class=\"MyApp.MainWindow\"\n        xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n        xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n        xmlns:vm=\"clr-namespace:MyApp.ViewModels\"\n        mc:Ignorable=\"d\">\n",[2989,10634,10635,10647,10657,10667,10677],{"__ignoreMap":3007},[3011,10636,10637,10639,10641,10643,10645],{"class":3013,"line":3014},[3011,10638,4975],{"class":6147},[3011,10640,10464],{"class":6150},[3011,10642,10467],{"class":6154},[3011,10644,6158],{"class":3028},[3011,10646,10472],{"class":6161},[3011,10648,10649,10652,10654],{"class":3013,"line":3032},[3011,10650,10651],{"class":6154},"        xmlns:d",[3011,10653,6158],{"class":3028},[3011,10655,10656],{"class":6161},"\"http://schemas.microsoft.com/expression/blend/2008\"\n",[3011,10658,10659,10662,10664],{"class":3013,"line":3043},[3011,10660,10661],{"class":6154},"        xmlns:mc",[3011,10663,6158],{"class":3028},[3011,10665,10666],{"class":6161},"\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n",[3011,10668,10669,10672,10674],{"class":3013,"line":3049},[3011,10670,10671],{"class":6154},"        xmlns:vm",[3011,10673,6158],{"class":3028},[3011,10675,10676],{"class":6161},"\"clr-namespace:MyApp.ViewModels\"\n",[3011,10678,10679,10682,10684,10687],{"class":3013,"line":3063},[3011,10680,10681],{"class":6154},"        mc:Ignorable",[3011,10683,6158],{"class":3028},[3011,10685,10686],{"class":6161},"\"d\"",[3011,10688,9345],{"class":6147},[2965,10690,10691],{},[2975,10692,10693],{},"Крок 2: Встановити d:DataContext:",[3002,10695,10697],{"className":6138,"code":10696,"language":6140,"meta":3007,"style":3007},"\u003CWindow x:Class=\"MyApp.MainWindow\"\n        xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n        xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n        xmlns:vm=\"clr-namespace:MyApp.ViewModels\"\n        mc:Ignorable=\"d\"\n        d:DataContext=\"{d:DesignInstance Type=vm:MainViewModel, IsDesignTimeCreatable=True}\">\n    \n    \u003CStackPanel>\n        \u003CTextBlock Text=\"{Binding Title}\" FontSize=\"24\"/>\n        \u003CTextBlock Text=\"{Binding Description}\"/>\n        \u003CListBox ItemsSource=\"{Binding Items}\"/>\n    \u003C/StackPanel>\n\u003C/Window>\n",[2989,10698,10699,10711,10719,10727,10735,10744,10756,10760,10768,10788,10802,10816,10824],{"__ignoreMap":3007},[3011,10700,10701,10703,10705,10707,10709],{"class":3013,"line":3014},[3011,10702,4975],{"class":6147},[3011,10704,10464],{"class":6150},[3011,10706,10467],{"class":6154},[3011,10708,6158],{"class":3028},[3011,10710,10472],{"class":6161},[3011,10712,10713,10715,10717],{"class":3013,"line":3032},[3011,10714,10651],{"class":6154},[3011,10716,6158],{"class":3028},[3011,10718,10656],{"class":6161},[3011,10720,10721,10723,10725],{"class":3013,"line":3043},[3011,10722,10661],{"class":6154},[3011,10724,6158],{"class":3028},[3011,10726,10666],{"class":6161},[3011,10728,10729,10731,10733],{"class":3013,"line":3049},[3011,10730,10671],{"class":6154},[3011,10732,6158],{"class":3028},[3011,10734,10676],{"class":6161},[3011,10736,10737,10739,10741],{"class":3013,"line":3063},[3011,10738,10681],{"class":6154},[3011,10740,6158],{"class":3028},[3011,10742,10743],{"class":6161},"\"d\"\n",[3011,10745,10746,10749,10751,10754],{"class":3013,"line":3069},[3011,10747,10748],{"class":6154},"        d:DataContext",[3011,10750,6158],{"class":3028},[3011,10752,10753],{"class":6161},"\"{d:DesignInstance Type=vm:MainViewModel, IsDesignTimeCreatable=True}\"",[3011,10755,9345],{"class":6147},[3011,10757,10758],{"class":3013,"line":3075},[3011,10759,3256],{"class":3028},[3011,10761,10762,10764,10766],{"class":3013,"line":3089},[3011,10763,9355],{"class":6147},[3011,10765,9334],{"class":6150},[3011,10767,9345],{"class":6147},[3011,10769,10770,10772,10774,10776,10778,10780,10782,10784,10786],{"class":3013,"line":3110},[3011,10771,9620],{"class":6147},[3011,10773,6151],{"class":6150},[3011,10775,6155],{"class":6154},[3011,10777,6158],{"class":3028},[3011,10779,10504],{"class":6161},[3011,10781,10507],{"class":6154},[3011,10783,6158],{"class":3028},[3011,10785,10512],{"class":6161},[3011,10787,6165],{"class":6147},[3011,10789,10790,10792,10794,10796,10798,10800],{"class":3013,"line":3116},[3011,10791,9620],{"class":6147},[3011,10793,6151],{"class":6150},[3011,10795,6155],{"class":6154},[3011,10797,6158],{"class":3028},[3011,10799,10527],{"class":6161},[3011,10801,6165],{"class":6147},[3011,10803,10804,10806,10808,10810,10812,10814],{"class":3013,"line":3325},[3011,10805,9620],{"class":6147},[3011,10807,10536],{"class":6150},[3011,10809,10539],{"class":6154},[3011,10811,6158],{"class":3028},[3011,10813,10544],{"class":6161},[3011,10815,6165],{"class":6147},[3011,10817,10818,10820,10822],{"class":3013,"line":3338},[3011,10819,9780],{"class":6147},[3011,10821,9334],{"class":6150},[3011,10823,9345],{"class":6147},[3011,10825,10826,10828,10830],{"class":3013,"line":3348},[3011,10827,9527],{"class":6147},[3011,10829,10464],{"class":6150},[3011,10831,9345],{"class":6147},[2965,10833,10834],{},[2975,10835,4276],{},[4278,10837,10838,10849,10857],{},[2985,10839,10840,10845,10846,10848],{},[2975,10841,10842],{},[2989,10843,10844],{},"mc:Ignorable=\"d\""," — WPF ігнорує всі ",[2989,10847,10623],{}," атрибути у runtime",[2985,10850,10851,10856],{},[2975,10852,10853],{},[2989,10854,10855],{},"d:DataContext"," — DataContext тільки для дизайнера",[2985,10858,10859,10864],{},[2975,10860,10861],{},[2989,10862,10863],{},"IsDesignTimeCreatable=True"," — дизайнер може створити екземпляр ViewModel через конструктор без параметрів",[3194,10866,10868],{"id":10867},"створення-designtime-viewmodel","Створення DesignTime ViewModel",[2965,10870,10871],{},[2975,10872,10873],{},"Підхід 1: Конструктор з параметром для DesignTime",[3002,10875,10877],{"className":3004,"code":10876,"language":3006,"meta":3007,"style":3007},"public class MainViewModel : BaseViewModel\n{\n    public string Title { get; set; }\n    public string Description { get; set; }\n    public ObservableCollection\u003Cstring> Items { get; set; }\n    \n    // Конструктор без параметрів для DesignTime\n    public MainViewModel() : this(isDesignTime: true)\n    {\n    }\n    \n    // Конструктор з параметром\n    public MainViewModel(bool isDesignTime)\n    {\n        if (isDesignTime)\n        {\n            // DesignTime дані\n            Title = \"Мій додаток (Design)\";\n            Description = \"Це тестові дані для дизайнера\";\n            Items = new ObservableCollection\u003Cstring>\n            {\n                \"Елемент 1\",\n                \"Елемент 2\",\n                \"Елемент 3\"\n            };\n        }\n        else\n        {\n            // Runtime дані\n            Title = \"Мій додаток\";\n            Description = \"Завантаження...\";\n            Items = new ObservableCollection\u003Cstring>();\n            LoadDataAsync();\n        }\n    }\n    \n    private async void LoadDataAsync()\n    {\n        // Завантаження реальних даних\n    }\n}\n",[2989,10878,10879,10891,10895,10914,10933,10958,10962,10967,10989,10993,10997,11001,11006,11021,11025,11035,11039,11044,11056,11068,11085,11089,11096,11103,11108,11113,11117,11122,11126,11131,11142,11153,11169,11176,11180,11184,11188,11202,11206,11211,11215],{"__ignoreMap":3007},[3011,10880,10881,10883,10885,10887,10889],{"class":3013,"line":3014},[3011,10882,3035],{"class":3017},[3011,10884,3221],{"class":3017},[3011,10886,3225],{"class":3224},[3011,10888,3228],{"class":3028},[3011,10890,4329],{"class":3224},[3011,10892,10893],{"class":3013,"line":3032},[3011,10894,3046],{"class":3028},[3011,10896,10897,10899,10901,10904,10906,10908,10910,10912],{"class":3013,"line":3043},[3011,10898,3240],{"class":3017},[3011,10900,3021],{"class":3017},[3011,10902,10903],{"class":3024}," Title",[3011,10905,7410],{"class":3028},[3011,10907,7413],{"class":3017},[3011,10909,10292],{"class":3028},[3011,10911,10295],{"class":3017},[3011,10913,7416],{"class":3028},[3011,10915,10916,10918,10920,10923,10925,10927,10929,10931],{"class":3013,"line":3049},[3011,10917,3240],{"class":3017},[3011,10919,3021],{"class":3017},[3011,10921,10922],{"class":3024}," Description",[3011,10924,7410],{"class":3028},[3011,10926,7413],{"class":3017},[3011,10928,10292],{"class":3028},[3011,10930,10295],{"class":3017},[3011,10932,7416],{"class":3028},[3011,10934,10935,10937,10940,10942,10944,10946,10948,10950,10952,10954,10956],{"class":3013,"line":3063},[3011,10936,3240],{"class":3017},[3011,10938,10939],{"class":3224}," ObservableCollection",[3011,10941,4975],{"class":3028},[3011,10943,3272],{"class":3017},[3011,10945,7466],{"class":3028},[3011,10947,10585],{"class":3024},[3011,10949,7410],{"class":3028},[3011,10951,7413],{"class":3017},[3011,10953,10292],{"class":3028},[3011,10955,10295],{"class":3017},[3011,10957,7416],{"class":3028},[3011,10959,10960],{"class":3013,"line":3069},[3011,10961,3256],{"class":3028},[3011,10963,10964],{"class":3013,"line":3075},[3011,10965,10966],{"class":3213},"    // Конструктор без параметрів для DesignTime\n",[3011,10968,10969,10971,10973,10976,10978,10980,10983,10985,10987],{"class":3013,"line":3089},[3011,10970,3240],{"class":3017},[3011,10972,3225],{"class":3092},[3011,10974,10975],{"class":3028},"() : ",[3011,10977,3298],{"class":3017},[3011,10979,3096],{"class":3028},[3011,10981,10982],{"class":3024},"isDesignTime",[3011,10984,7018],{"class":3028},[3011,10986,5190],{"class":3017},[3011,10988,3278],{"class":3028},[3011,10990,10991],{"class":3013,"line":3110},[3011,10992,3072],{"class":3028},[3011,10994,10995],{"class":3013,"line":3116},[3011,10996,3113],{"class":3028},[3011,10998,10999],{"class":3013,"line":3325},[3011,11000,3256],{"class":3028},[3011,11002,11003],{"class":3013,"line":3338},[3011,11004,11005],{"class":3213},"    // Конструктор з параметром\n",[3011,11007,11008,11010,11012,11014,11016,11019],{"class":3013,"line":3348},[3011,11009,3240],{"class":3017},[3011,11011,3225],{"class":3092},[3011,11013,3096],{"class":3028},[3011,11015,5187],{"class":3017},[3011,11017,11018],{"class":3024}," isDesignTime",[3011,11020,3278],{"class":3028},[3011,11022,11023],{"class":3013,"line":3353},[3011,11024,3072],{"class":3028},[3011,11026,11027,11029,11031,11033],{"class":3013,"line":3366},[3011,11028,5028],{"class":4125},[3011,11030,5031],{"class":3028},[3011,11032,10982],{"class":3024},[3011,11034,3278],{"class":3028},[3011,11036,11037],{"class":3013,"line":3372},[3011,11038,3375],{"class":3028},[3011,11040,11041],{"class":3013,"line":3378},[3011,11042,11043],{"class":3213},"            // DesignTime дані\n",[3011,11045,11046,11049,11051,11054],{"class":3013,"line":3390},[3011,11047,11048],{"class":3024},"            Title",[3011,11050,3081],{"class":3028},[3011,11052,11053],{"class":3894},"\"Мій додаток (Design)\"",[3011,11055,3029],{"class":3028},[3011,11057,11058,11061,11063,11066],{"class":3013,"line":3407},[3011,11059,11060],{"class":3024},"            Description",[3011,11062,3081],{"class":3028},[3011,11064,11065],{"class":3894},"\"Це тестові дані для дизайнера\"",[3011,11067,3029],{"class":3028},[3011,11069,11070,11073,11075,11077,11079,11081,11083],{"class":3013,"line":3413},[3011,11071,11072],{"class":3024},"            Items",[3011,11074,3081],{"class":3028},[3011,11076,3304],{"class":3017},[3011,11078,10939],{"class":3224},[3011,11080,4975],{"class":3028},[3011,11082,3272],{"class":3017},[3011,11084,9345],{"class":3028},[3011,11086,11087],{"class":3013,"line":3418},[3011,11088,8553],{"class":3028},[3011,11090,11091,11094],{"class":3013,"line":3423},[3011,11092,11093],{"class":3894},"                \"Елемент 1\"",[3011,11095,6605],{"class":3028},[3011,11097,11098,11101],{"class":3013,"line":3430},[3011,11099,11100],{"class":3894},"                \"Елемент 2\"",[3011,11102,6605],{"class":3028},[3011,11104,11105],{"class":3013,"line":3436},[3011,11106,11107],{"class":3894},"                \"Елемент 3\"\n",[3011,11109,11110],{"class":3013,"line":3450},[3011,11111,11112],{"class":3028},"            };\n",[3011,11114,11115],{"class":3013,"line":3455},[3011,11116,3410],{"class":3028},[3011,11118,11119],{"class":3013,"line":3461},[3011,11120,11121],{"class":4125},"        else\n",[3011,11123,11124],{"class":3013,"line":3474},[3011,11125,3375],{"class":3028},[3011,11127,11128],{"class":3013,"line":3479},[3011,11129,11130],{"class":3213},"            // Runtime дані\n",[3011,11132,11133,11135,11137,11140],{"class":3013,"line":3496},[3011,11134,11048],{"class":3024},[3011,11136,3081],{"class":3028},[3011,11138,11139],{"class":3894},"\"Мій додаток\"",[3011,11141,3029],{"class":3028},[3011,11143,11144,11146,11148,11151],{"class":3013,"line":3501},[3011,11145,11060],{"class":3024},[3011,11147,3081],{"class":3028},[3011,11149,11150],{"class":3894},"\"Завантаження...\"",[3011,11152,3029],{"class":3028},[3011,11154,11155,11157,11159,11161,11163,11165,11167],{"class":3013,"line":3526},[3011,11156,11072],{"class":3024},[3011,11158,3081],{"class":3028},[3011,11160,3304],{"class":3017},[3011,11162,10939],{"class":3224},[3011,11164,4975],{"class":3028},[3011,11166,3272],{"class":3017},[3011,11168,8200],{"class":3028},[3011,11170,11171,11174],{"class":3013,"line":3531},[3011,11172,11173],{"class":3092},"            LoadDataAsync",[3011,11175,4395],{"class":3028},[3011,11177,11178],{"class":3013,"line":3536},[3011,11179,3410],{"class":3028},[3011,11181,11182],{"class":3013,"line":3548},[3011,11183,3113],{"class":3028},[3011,11185,11186],{"class":3013,"line":3558},[3011,11187,3256],{"class":3028},[3011,11189,11190,11192,11195,11197,11200],{"class":3013,"line":3563},[3011,11191,3328],{"class":3017},[3011,11193,11194],{"class":3017}," async",[3011,11196,3264],{"class":3017},[3011,11198,11199],{"class":3092}," LoadDataAsync",[3011,11201,7275],{"class":3028},[3011,11203,11204],{"class":3013,"line":3575},[3011,11205,3072],{"class":3028},[3011,11207,11208],{"class":3013,"line":3580},[3011,11209,11210],{"class":3213},"        // Завантаження реальних даних\n",[3011,11212,11213],{"class":3013,"line":3585},[3011,11214,3113],{"class":3028},[3011,11216,11217],{"class":3013,"line":3597},[3011,11218,3119],{"class":3028},[2965,11220,11221],{},[2975,11222,11223],{},"Підхід 2: Окремий DesignTime ViewModel",[3002,11225,11227],{"className":3004,"code":11226,"language":3006,"meta":3007,"style":3007},"// Runtime ViewModel\npublic class MainViewModel : BaseViewModel\n{\n    public string Title { get; set; }\n    public ObservableCollection\u003Cstring> Items { get; set; }\n    \n    public MainViewModel()\n    {\n        Title = \"Мій додаток\";\n        Items = new ObservableCollection\u003Cstring>();\n        LoadDataAsync();\n    }\n}\n\n// DesignTime ViewModel\npublic class DesignMainViewModel : MainViewModel\n{\n    public DesignMainViewModel()\n    {\n        Title = \"Мій додаток (Design)\";\n        Items = new ObservableCollection\u003Cstring>\n        {\n            \"Тестовий елемент 1\",\n            \"Тестовий елемент 2\",\n            \"Тестовий елемент 3\"\n        };\n    }\n}\n",[2989,11228,11229,11234,11246,11250,11268,11292,11296,11304,11308,11319,11336,11343,11347,11351,11355,11360,11374,11378,11386,11390,11400,11416,11420,11427,11434,11439,11444,11448],{"__ignoreMap":3007},[3011,11230,11231],{"class":3013,"line":3014},[3011,11232,11233],{"class":3213},"// Runtime ViewModel\n",[3011,11235,11236,11238,11240,11242,11244],{"class":3013,"line":3032},[3011,11237,3035],{"class":3017},[3011,11239,3221],{"class":3017},[3011,11241,3225],{"class":3224},[3011,11243,3228],{"class":3028},[3011,11245,4329],{"class":3224},[3011,11247,11248],{"class":3013,"line":3043},[3011,11249,3046],{"class":3028},[3011,11251,11252,11254,11256,11258,11260,11262,11264,11266],{"class":3013,"line":3049},[3011,11253,3240],{"class":3017},[3011,11255,3021],{"class":3017},[3011,11257,10903],{"class":3024},[3011,11259,7410],{"class":3028},[3011,11261,7413],{"class":3017},[3011,11263,10292],{"class":3028},[3011,11265,10295],{"class":3017},[3011,11267,7416],{"class":3028},[3011,11269,11270,11272,11274,11276,11278,11280,11282,11284,11286,11288,11290],{"class":3013,"line":3063},[3011,11271,3240],{"class":3017},[3011,11273,10939],{"class":3224},[3011,11275,4975],{"class":3028},[3011,11277,3272],{"class":3017},[3011,11279,7466],{"class":3028},[3011,11281,10585],{"class":3024},[3011,11283,7410],{"class":3028},[3011,11285,7413],{"class":3017},[3011,11287,10292],{"class":3028},[3011,11289,10295],{"class":3017},[3011,11291,7416],{"class":3028},[3011,11293,11294],{"class":3013,"line":3069},[3011,11295,3256],{"class":3028},[3011,11297,11298,11300,11302],{"class":3013,"line":3075},[3011,11299,3240],{"class":3017},[3011,11301,3225],{"class":3092},[3011,11303,7275],{"class":3028},[3011,11305,11306],{"class":3013,"line":3089},[3011,11307,3072],{"class":3028},[3011,11309,11310,11313,11315,11317],{"class":3013,"line":3110},[3011,11311,11312],{"class":3024},"        Title",[3011,11314,3081],{"class":3028},[3011,11316,11139],{"class":3894},[3011,11318,3029],{"class":3028},[3011,11320,11321,11324,11326,11328,11330,11332,11334],{"class":3013,"line":3116},[3011,11322,11323],{"class":3024},"        Items",[3011,11325,3081],{"class":3028},[3011,11327,3304],{"class":3017},[3011,11329,10939],{"class":3224},[3011,11331,4975],{"class":3028},[3011,11333,3272],{"class":3017},[3011,11335,8200],{"class":3028},[3011,11337,11338,11341],{"class":3013,"line":3325},[3011,11339,11340],{"class":3092},"        LoadDataAsync",[3011,11342,4395],{"class":3028},[3011,11344,11345],{"class":3013,"line":3338},[3011,11346,3113],{"class":3028},[3011,11348,11349],{"class":3013,"line":3348},[3011,11350,3119],{"class":3028},[3011,11352,11353],{"class":3013,"line":3353},[3011,11354,3427],{"emptyLinePlaceholder":3426},[3011,11356,11357],{"class":3013,"line":3366},[3011,11358,11359],{"class":3213},"// DesignTime ViewModel\n",[3011,11361,11362,11364,11366,11369,11371],{"class":3013,"line":3372},[3011,11363,3035],{"class":3017},[3011,11365,3221],{"class":3017},[3011,11367,11368],{"class":3224}," DesignMainViewModel",[3011,11370,3228],{"class":3028},[3011,11372,11373],{"class":3224},"MainViewModel\n",[3011,11375,11376],{"class":3013,"line":3378},[3011,11377,3046],{"class":3028},[3011,11379,11380,11382,11384],{"class":3013,"line":3390},[3011,11381,3240],{"class":3017},[3011,11383,11368],{"class":3092},[3011,11385,7275],{"class":3028},[3011,11387,11388],{"class":3013,"line":3407},[3011,11389,3072],{"class":3028},[3011,11391,11392,11394,11396,11398],{"class":3013,"line":3413},[3011,11393,11312],{"class":3024},[3011,11395,3081],{"class":3028},[3011,11397,11053],{"class":3894},[3011,11399,3029],{"class":3028},[3011,11401,11402,11404,11406,11408,11410,11412,11414],{"class":3013,"line":3418},[3011,11403,11323],{"class":3024},[3011,11405,3081],{"class":3028},[3011,11407,3304],{"class":3017},[3011,11409,10939],{"class":3224},[3011,11411,4975],{"class":3028},[3011,11413,3272],{"class":3017},[3011,11415,9345],{"class":3028},[3011,11417,11418],{"class":3013,"line":3423},[3011,11419,3375],{"class":3028},[3011,11421,11422,11425],{"class":3013,"line":3430},[3011,11423,11424],{"class":3894},"            \"Тестовий елемент 1\"",[3011,11426,6605],{"class":3028},[3011,11428,11429,11432],{"class":3013,"line":3436},[3011,11430,11431],{"class":3894},"            \"Тестовий елемент 2\"",[3011,11433,6605],{"class":3028},[3011,11435,11436],{"class":3013,"line":3450},[3011,11437,11438],{"class":3894},"            \"Тестовий елемент 3\"\n",[3011,11440,11441],{"class":3013,"line":3455},[3011,11442,11443],{"class":3028},"        };\n",[3011,11445,11446],{"class":3013,"line":3461},[3011,11447,3113],{"class":3028},[3011,11449,11450],{"class":3013,"line":3474},[3011,11451,3119],{"class":3028},[2965,11453,11454],{},[2975,11455,11456],{},"XAML з DesignTime ViewModel:",[3002,11458,11460],{"className":6138,"code":11459,"language":6140,"meta":3007,"style":3007},"\u003CWindow d:DataContext=\"{d:DesignInstance Type=vm:DesignMainViewModel, IsDesignTimeCreatable=True}\">\n",[2989,11461,11462],{"__ignoreMap":3007},[3011,11463,11464,11466,11468,11471,11473,11476],{"class":3013,"line":3014},[3011,11465,4975],{"class":6147},[3011,11467,10464],{"class":6150},[3011,11469,11470],{"class":6154}," d:DataContext",[3011,11472,6158],{"class":3028},[3011,11474,11475],{"class":6161},"\"{d:DesignInstance Type=vm:DesignMainViewModel, IsDesignTimeCreatable=True}\"",[3011,11477,9345],{"class":6147},[3194,11479,11481],{"id":11480},"перевірка-designtime-у-коді","Перевірка DesignTime у коді",[2965,11483,11484],{},"Іноді потрібно перевірити, чи код виконується у дизайнері, щоб не викликати API або БД.",[2965,11486,11487],{},[2975,11488,11489],{},"Метод 1: DesignerProperties.GetIsInDesignMode",[3002,11491,11493],{"className":3004,"code":11492,"language":3006,"meta":3007,"style":3007},"using System.ComponentModel;\nusing System.Windows;\n\npublic class MainViewModel : BaseViewModel\n{\n    public MainViewModel()\n    {\n        // Перевірка: чи виконується у дизайнері?\n        if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))\n        {\n            // DesignTime дані\n            Title = \"Тестовий заголовок\";\n            Items = new ObservableCollection\u003Cstring> { \"Item 1\", \"Item 2\" };\n        }\n        else\n        {\n            // Runtime дані\n            LoadDataAsync();\n        }\n    }\n}\n",[2989,11494,11495,11507,11520,11524,11536,11540,11548,11552,11557,11581,11585,11589,11600,11628,11632,11636,11640,11644,11650,11654,11658],{"__ignoreMap":3007},[3011,11496,11497,11499,11501,11503,11505],{"class":3013,"line":3014},[3011,11498,4126],{"class":4125},[3011,11500,4129],{"class":3224},[3011,11502,4010],{"class":3028},[3011,11504,4134],{"class":3224},[3011,11506,3029],{"class":3028},[3011,11508,11509,11511,11513,11515,11518],{"class":3013,"line":3032},[3011,11510,4126],{"class":4125},[3011,11512,4129],{"class":3224},[3011,11514,4010],{"class":3028},[3011,11516,11517],{"class":3224},"Windows",[3011,11519,3029],{"class":3028},[3011,11521,11522],{"class":3013,"line":3043},[3011,11523,3427],{"emptyLinePlaceholder":3426},[3011,11525,11526,11528,11530,11532,11534],{"class":3013,"line":3049},[3011,11527,3035],{"class":3017},[3011,11529,3221],{"class":3017},[3011,11531,3225],{"class":3224},[3011,11533,3228],{"class":3028},[3011,11535,4329],{"class":3224},[3011,11537,11538],{"class":3013,"line":3063},[3011,11539,3046],{"class":3028},[3011,11541,11542,11544,11546],{"class":3013,"line":3069},[3011,11543,3240],{"class":3017},[3011,11545,3225],{"class":3092},[3011,11547,7275],{"class":3028},[3011,11549,11550],{"class":3013,"line":3075},[3011,11551,3072],{"class":3028},[3011,11553,11554],{"class":3013,"line":3089},[3011,11555,11556],{"class":3213},"        // Перевірка: чи виконується у дизайнері?\n",[3011,11558,11559,11561,11563,11566,11568,11571,11573,11575,11578],{"class":3013,"line":3110},[3011,11560,5028],{"class":4125},[3011,11562,5031],{"class":3028},[3011,11564,11565],{"class":3024},"DesignerProperties",[3011,11567,4010],{"class":3028},[3011,11569,11570],{"class":3092},"GetIsInDesignMode",[3011,11572,3096],{"class":3028},[3011,11574,3304],{"class":3017},[3011,11576,11577],{"class":3224}," DependencyObject",[3011,11579,11580],{"class":3028},"()))\n",[3011,11582,11583],{"class":3013,"line":3116},[3011,11584,3375],{"class":3028},[3011,11586,11587],{"class":3013,"line":3325},[3011,11588,11043],{"class":3213},[3011,11590,11591,11593,11595,11598],{"class":3013,"line":3338},[3011,11592,11048],{"class":3024},[3011,11594,3081],{"class":3028},[3011,11596,11597],{"class":3894},"\"Тестовий заголовок\"",[3011,11599,3029],{"class":3028},[3011,11601,11602,11604,11606,11608,11610,11612,11614,11617,11620,11622,11625],{"class":3013,"line":3348},[3011,11603,11072],{"class":3024},[3011,11605,3081],{"class":3028},[3011,11607,3304],{"class":3017},[3011,11609,10939],{"class":3224},[3011,11611,4975],{"class":3028},[3011,11613,3272],{"class":3017},[3011,11615,11616],{"class":3028},"> { ",[3011,11618,11619],{"class":3894},"\"Item 1\"",[3011,11621,3301],{"class":3028},[3011,11623,11624],{"class":3894},"\"Item 2\"",[3011,11626,11627],{"class":3028}," };\n",[3011,11629,11630],{"class":3013,"line":3353},[3011,11631,3410],{"class":3028},[3011,11633,11634],{"class":3013,"line":3366},[3011,11635,11121],{"class":4125},[3011,11637,11638],{"class":3013,"line":3372},[3011,11639,3375],{"class":3028},[3011,11641,11642],{"class":3013,"line":3378},[3011,11643,11130],{"class":3213},[3011,11645,11646,11648],{"class":3013,"line":3390},[3011,11647,11173],{"class":3092},[3011,11649,4395],{"class":3028},[3011,11651,11652],{"class":3013,"line":3407},[3011,11653,3410],{"class":3028},[3011,11655,11656],{"class":3013,"line":3413},[3011,11657,3113],{"class":3028},[3011,11659,11660],{"class":3013,"line":3418},[3011,11661,3119],{"class":3028},[2965,11663,11664],{},[2975,11665,11666],{},"Метод 2: Через Environment",[3002,11668,11670],{"className":3004,"code":11669,"language":3006,"meta":3007,"style":3007},"public class MainViewModel : BaseViewModel\n{\n    private static bool IsDesignMode =>\n        (bool)DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue;\n    \n    public MainViewModel()\n    {\n        if (IsDesignMode)\n        {\n            // DesignTime\n        }\n        else\n        {\n            // Runtime\n        }\n    }\n}\n",[2989,11671,11672,11684,11688,11702,11742,11746,11754,11758,11769,11773,11778,11782,11786,11790,11795,11799,11803],{"__ignoreMap":3007},[3011,11673,11674,11676,11678,11680,11682],{"class":3013,"line":3014},[3011,11675,3035],{"class":3017},[3011,11677,3221],{"class":3017},[3011,11679,3225],{"class":3224},[3011,11681,3228],{"class":3028},[3011,11683,4329],{"class":3224},[3011,11685,11686],{"class":3013,"line":3032},[3011,11687,3046],{"class":3028},[3011,11689,11690,11692,11695,11697,11700],{"class":3013,"line":3043},[3011,11691,3328],{"class":3017},[3011,11693,11694],{"class":3017}," static",[3011,11696,4969],{"class":3017},[3011,11698,11699],{"class":3024}," IsDesignMode",[3011,11701,7186],{"class":3028},[3011,11703,11704,11707,11709,11712,11714,11716,11719,11721,11724,11726,11729,11731,11734,11737,11740],{"class":3013,"line":3049},[3011,11705,11706],{"class":3028},"        (",[3011,11708,5187],{"class":3017},[3011,11710,11711],{"class":3028},")",[3011,11713,11565],{"class":3024},[3011,11715,4010],{"class":3028},[3011,11717,11718],{"class":3024},"IsInDesignModeProperty",[3011,11720,4010],{"class":3028},[3011,11722,11723],{"class":3092},"GetMetadata",[3011,11725,3096],{"class":3028},[3011,11727,11728],{"class":3017},"typeof",[3011,11730,3096],{"class":3028},[3011,11732,11733],{"class":3224},"DependencyObject",[3011,11735,11736],{"class":3028},")).",[3011,11738,11739],{"class":3024},"DefaultValue",[3011,11741,3029],{"class":3028},[3011,11743,11744],{"class":3013,"line":3063},[3011,11745,3256],{"class":3028},[3011,11747,11748,11750,11752],{"class":3013,"line":3069},[3011,11749,3240],{"class":3017},[3011,11751,3225],{"class":3092},[3011,11753,7275],{"class":3028},[3011,11755,11756],{"class":3013,"line":3075},[3011,11757,3072],{"class":3028},[3011,11759,11760,11762,11764,11767],{"class":3013,"line":3089},[3011,11761,5028],{"class":4125},[3011,11763,5031],{"class":3028},[3011,11765,11766],{"class":3024},"IsDesignMode",[3011,11768,3278],{"class":3028},[3011,11770,11771],{"class":3013,"line":3110},[3011,11772,3375],{"class":3028},[3011,11774,11775],{"class":3013,"line":3116},[3011,11776,11777],{"class":3213},"            // DesignTime\n",[3011,11779,11780],{"class":3013,"line":3325},[3011,11781,3410],{"class":3028},[3011,11783,11784],{"class":3013,"line":3338},[3011,11785,11121],{"class":4125},[3011,11787,11788],{"class":3013,"line":3348},[3011,11789,3375],{"class":3028},[3011,11791,11792],{"class":3013,"line":3353},[3011,11793,11794],{"class":3213},"            // Runtime\n",[3011,11796,11797],{"class":3013,"line":3366},[3011,11798,3410],{"class":3028},[3011,11800,11801],{"class":3013,"line":3372},[3011,11802,3113],{"class":3028},[3011,11804,11805],{"class":3013,"line":3378},[3011,11806,3119],{"class":3028},[4428,11808,11809,11812,11813,11816],{},[2975,11810,11811],{},"Best Practice:"," Використовуйте окремі DesignTime ViewModel класи замість перевірки ",[2989,11814,11815],{},"IsInDesignMode"," у конструкторі. Це чистіше та легше підтримувати.",[3194,11818,11820],{"id":11819},"переваги-designtime-даних","Переваги DesignTime даних",[11822,11823,11824,11830,11835,11840],"card-group",{},[11825,11826,11829],"card",{"icon":11827,"title":11828},"i-lucide-eye","👁️ Візуальний feedback","Дизайнер показує реальний вигляд UI з даними. Можна оцінити layout, розміри, вирівнювання.",[11825,11831,11834],{"icon":11832,"title":11833},"i-lucide-palette","🎨 Робота дизайнера","Дизайнер може працювати з XAML без запуску додатку. Швидший feedback loop.",[11825,11836,11839],{"icon":11837,"title":11838},"i-lucide-bug","🐛 Легше знайти баги","Проблеми з layout видно одразу у дизайнері, а не після запуску.",[11825,11841,11844],{"icon":11842,"title":11843},"i-lucide-ruler","📏 Тестування різних станів","Можна створити кілька DesignTime ViewModel для різних станів (порожній список, довгий текст, помилка).",[3181,11846],{},[2960,11848,11850],{"id":11849},"recap-ооп-концепції-у-viewmodel","🔵 Recap: ООП концепції у ViewModel",[2965,11852,11853],{},"Для студентів зі слабким розумінням ООП — коротке нагадування ключових концепцій, що використовуються у ViewModel.",[3194,11855,11857],{"id":11856},"абстрактні-класи-baseviewmodel-як-шаблон","Абстрактні класи: BaseViewModel як шаблон",[2965,11859,11860],{},[2975,11861,11862],{},"Що таке абстрактний клас?",[2965,11864,11865,11866,11869,11870,11873],{},"Абстрактний клас — це клас, від якого ",[2975,11867,11868],{},"не можна створити екземпляр",". Він слугує ",[2975,11871,11872],{},"шаблоном"," для нащадків.",[3002,11875,11877],{"className":3004,"code":11876,"language":3006,"meta":3007,"style":3007},"// Абстрактний клас — шаблон\npublic abstract class BaseViewModel : INotifyPropertyChanged\n{\n    // Спільна логіка для всіх ViewModel\n    public event PropertyChangedEventHandler PropertyChanged;\n    \n    protected void OnPropertyChanged(string propertyName)\n    {\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n    }\n}\n\n// ❌ Не можна створити екземпляр\nvar vm = new BaseViewModel();  // Помилка компіляції\n\n// ✅ Можна успадкувати\npublic class MainViewModel : BaseViewModel\n{\n    // Використовує OnPropertyChanged з BaseViewModel\n}\n",[2989,11878,11879,11884,11898,11902,11907,11919,11923,11939,11943,11967,11971,11975,11979,11984,12003,12007,12012,12024,12028,12033],{"__ignoreMap":3007},[3011,11880,11881],{"class":3013,"line":3014},[3011,11882,11883],{"class":3213},"// Абстрактний клас — шаблон\n",[3011,11885,11886,11888,11890,11892,11894,11896],{"class":3013,"line":3032},[3011,11887,3035],{"class":3017},[3011,11889,4165],{"class":3017},[3011,11891,3221],{"class":3017},[3011,11893,4170],{"class":3224},[3011,11895,3228],{"class":3028},[3011,11897,3231],{"class":3224},[3011,11899,11900],{"class":3013,"line":3043},[3011,11901,3046],{"class":3028},[3011,11903,11904],{"class":3013,"line":3049},[3011,11905,11906],{"class":3213},"    // Спільна логіка для всіх ViewModel\n",[3011,11908,11909,11911,11913,11915,11917],{"class":3013,"line":3063},[3011,11910,3240],{"class":3017},[3011,11912,3243],{"class":3017},[3011,11914,3246],{"class":3224},[3011,11916,3249],{"class":3024},[3011,11918,3029],{"class":3028},[3011,11920,11921],{"class":3013,"line":3069},[3011,11922,3256],{"class":3028},[3011,11924,11925,11927,11929,11931,11933,11935,11937],{"class":3013,"line":3075},[3011,11926,3261],{"class":3017},[3011,11928,3264],{"class":3017},[3011,11930,3267],{"class":3092},[3011,11932,3096],{"class":3028},[3011,11934,3272],{"class":3017},[3011,11936,3275],{"class":3024},[3011,11938,3278],{"class":3028},[3011,11940,11941],{"class":3013,"line":3089},[3011,11942,3072],{"class":3028},[3011,11944,11945,11947,11949,11951,11953,11955,11957,11959,11961,11963,11965],{"class":3013,"line":3110},[3011,11946,3287],{"class":3024},[3011,11948,3290],{"class":3028},[3011,11950,3293],{"class":3092},[3011,11952,3096],{"class":3028},[3011,11954,3298],{"class":3017},[3011,11956,3301],{"class":3028},[3011,11958,3304],{"class":3017},[3011,11960,3307],{"class":3224},[3011,11962,3096],{"class":3028},[3011,11964,3312],{"class":3024},[3011,11966,3107],{"class":3028},[3011,11968,11969],{"class":3013,"line":3116},[3011,11970,3113],{"class":3028},[3011,11972,11973],{"class":3013,"line":3325},[3011,11974,3119],{"class":3028},[3011,11976,11977],{"class":3013,"line":3338},[3011,11978,3427],{"emptyLinePlaceholder":3426},[3011,11980,11981],{"class":3013,"line":3348},[3011,11982,11983],{"class":3213},"// ❌ Не можна створити екземпляр\n",[3011,11985,11986,11989,11992,11994,11996,11998,12000],{"class":3013,"line":3353},[3011,11987,11988],{"class":3017},"var",[3011,11990,11991],{"class":3024}," vm",[3011,11993,3081],{"class":3028},[3011,11995,3304],{"class":3017},[3011,11997,4170],{"class":3224},[3011,11999,4513],{"class":3028},[3011,12001,12002],{"class":3213},"// Помилка компіляції\n",[3011,12004,12005],{"class":3013,"line":3366},[3011,12006,3427],{"emptyLinePlaceholder":3426},[3011,12008,12009],{"class":3013,"line":3372},[3011,12010,12011],{"class":3213},"// ✅ Можна успадкувати\n",[3011,12013,12014,12016,12018,12020,12022],{"class":3013,"line":3378},[3011,12015,3035],{"class":3017},[3011,12017,3221],{"class":3017},[3011,12019,3225],{"class":3224},[3011,12021,3228],{"class":3028},[3011,12023,4329],{"class":3224},[3011,12025,12026],{"class":3013,"line":3390},[3011,12027,3046],{"class":3028},[3011,12029,12030],{"class":3013,"line":3407},[3011,12031,12032],{"class":3213},"    // Використовує OnPropertyChanged з BaseViewModel\n",[3011,12034,12035],{"class":3013,"line":3413},[3011,12036,3119],{"class":3028},[2965,12038,12039],{},[2975,12040,12041],{},"Чому це важливо для MVVM?",[2982,12043,12044,12049,12057],{},[2985,12045,12046,12048],{},[2989,12047,3164],{}," — це шаблон для всіх ViewModel",[2985,12050,12051,12052,3301,12054,12056],{},"Спільна логіка (",[2989,12053,2991],{},[2989,12055,4758],{},") у одному місці",[2985,12058,12059],{},"Всі ViewModel успадковують цю логіку автоматично",[2965,12061,12062,12065],{},[2975,12063,12064],{},"Аналогія:"," Абстрактний клас — це як креслення будинку. Ви не можете жити у кресленні, але можете побудувати багато будинків за цим кресленням.",[3194,12067,12069,12070],{"id":12068},"generics-setproperty-для-будь-якого-типу","Generics: SetProperty",[4760,12071,12072],{}," для будь-якого типу",[2965,12074,12075],{},[2975,12076,12077],{},"Що таке Generics?",[2965,12079,12080,12081,4010],{},"Generics — це можливість писати код, що працює з ",[2975,12082,12083],{},"будь-яким типом",[3002,12085,12087],{"className":3004,"code":12086,"language":3006,"meta":3007,"style":3007},"// Без Generics — потрібен окремий метод для кожного типу\nprotected bool SetPropertyString(ref string field, string value) { /* ... */ }\nprotected bool SetPropertyInt(ref int field, int value) { /* ... */ }\nprotected bool SetPropertyDateTime(ref DateTime field, DateTime value) { /* ... */ }\n\n// З Generics — один метод для всіх типів\nprotected bool SetProperty\u003CT>(ref T field, T value)\n{\n    // Працює для string, int, DateTime, будь-якого типу\n}\n",[2989,12088,12089,12094,12126,12155,12185,12189,12194,12222,12226,12231],{"__ignoreMap":3007},[3011,12090,12091],{"class":3013,"line":3014},[3011,12092,12093],{"class":3213},"// Без Generics — потрібен окремий метод для кожного типу\n",[3011,12095,12096,12098,12100,12103,12105,12107,12109,12111,12113,12115,12117,12120,12123],{"class":3013,"line":3032},[3011,12097,3981],{"class":3017},[3011,12099,4969],{"class":3017},[3011,12101,12102],{"class":3092}," SetPropertyString",[3011,12104,3096],{"class":3028},[3011,12106,4984],{"class":3017},[3011,12108,3021],{"class":3017},[3011,12110,4990],{"class":3024},[3011,12112,3301],{"class":3028},[3011,12114,3272],{"class":3017},[3011,12116,4997],{"class":3024},[3011,12118,12119],{"class":3028},") { ",[3011,12121,12122],{"class":3213},"/* ... */",[3011,12124,12125],{"class":3028}," }\n",[3011,12127,12128,12130,12132,12135,12137,12139,12141,12143,12145,12147,12149,12151,12153],{"class":3013,"line":3043},[3011,12129,3981],{"class":3017},[3011,12131,4969],{"class":3017},[3011,12133,12134],{"class":3092}," SetPropertyInt",[3011,12136,3096],{"class":3028},[3011,12138,4984],{"class":3017},[3011,12140,5358],{"class":3017},[3011,12142,4990],{"class":3024},[3011,12144,3301],{"class":3028},[3011,12146,5175],{"class":3017},[3011,12148,4997],{"class":3024},[3011,12150,12119],{"class":3028},[3011,12152,12122],{"class":3213},[3011,12154,12125],{"class":3028},[3011,12156,12157,12159,12161,12164,12166,12168,12171,12173,12175,12177,12179,12181,12183],{"class":3013,"line":3049},[3011,12158,3981],{"class":3017},[3011,12160,4969],{"class":3017},[3011,12162,12163],{"class":3092}," SetPropertyDateTime",[3011,12165,3096],{"class":3028},[3011,12167,4984],{"class":3017},[3011,12169,12170],{"class":3224}," DateTime",[3011,12172,4990],{"class":3024},[3011,12174,3301],{"class":3028},[3011,12176,5178],{"class":3224},[3011,12178,4997],{"class":3024},[3011,12180,12119],{"class":3028},[3011,12182,12122],{"class":3213},[3011,12184,12125],{"class":3028},[3011,12186,12187],{"class":3013,"line":3063},[3011,12188,3427],{"emptyLinePlaceholder":3426},[3011,12190,12191],{"class":3013,"line":3069},[3011,12192,12193],{"class":3213},"// З Generics — один метод для всіх типів\n",[3011,12195,12196,12198,12200,12202,12204,12206,12208,12210,12212,12214,12216,12218,12220],{"class":3013,"line":3075},[3011,12197,3981],{"class":3017},[3011,12199,4969],{"class":3017},[3011,12201,4972],{"class":3092},[3011,12203,4975],{"class":3028},[3011,12205,4978],{"class":3224},[3011,12207,4981],{"class":3028},[3011,12209,4984],{"class":3017},[3011,12211,4987],{"class":3224},[3011,12213,4990],{"class":3024},[3011,12215,3301],{"class":3028},[3011,12217,4978],{"class":3224},[3011,12219,4997],{"class":3024},[3011,12221,3278],{"class":3028},[3011,12223,12224],{"class":3013,"line":3089},[3011,12225,3046],{"class":3028},[3011,12227,12228],{"class":3013,"line":3110},[3011,12229,12230],{"class":3213},"    // Працює для string, int, DateTime, будь-якого типу\n",[3011,12232,12233],{"class":3013,"line":3116},[3011,12234,3119],{"class":3028},[2965,12236,12237],{},[2975,12238,12239],{},"Чому це важливо для ViewModel?",[2982,12241,12242,12248,12257],{},[2985,12243,12244,12247],{},[2989,12245,12246],{},"SetProperty\u003CT>"," працює для будь-якого типу властивості",[2985,12249,12250,12251,3301,12253,3301,12255],{},"Не потрібно писати окремі методи для ",[2989,12252,3272],{},[2989,12254,5175],{},[2989,12256,5178],{},[2985,12258,12259],{},"Компілятор перевіряє типи — безпечно",[2965,12261,12262,12264],{},[2975,12263,12064],{}," Generics — це як універсальний адаптер для розеток. Один адаптер працює з будь-якою вилкою.",[3194,12266,12268],{"id":12267},"ref-параметри-зміна-значення-backing-field","Ref параметри: Зміна значення backing field",[2965,12270,12271],{},[2975,12272,12273,12274,12276],{},"Що таке ",[2989,12275,4984],{},"?",[2965,12278,12279,12281,12282,12285],{},[2989,12280,4984],{}," — це передача параметра ",[2975,12283,12284],{},"за посиланням",", а не за значенням. Метод може змінити оригінальну змінну.",[3002,12287,12289],{"className":3004,"code":12288,"language":3006,"meta":3007,"style":3007},"// Без ref — зміна локальної копії\nvoid SetValue(int x)\n{\n    x = 10;  // Змінюється тільки локальна копія\n}\n\nint number = 5;\nSetValue(number);\nConsole.WriteLine(number);  // 5 — не змінилося\n\n// З ref — зміна оригінальної змінної\nvoid SetValue(ref int x)\n{\n    x = 10;  // Змінюється оригінальна змінна\n}\n\nint number = 5;\nSetValue(ref number);\nConsole.WriteLine(number);  // 10 — змінилося\n",[2989,12290,12291,12296,12313,12317,12331,12335,12339,12352,12364,12382,12386,12391,12407,12411,12424,12428,12432,12444,12456],{"__ignoreMap":3007},[3011,12292,12293],{"class":3013,"line":3014},[3011,12294,12295],{"class":3213},"// Без ref — зміна локальної копії\n",[3011,12297,12298,12301,12304,12306,12308,12311],{"class":3013,"line":3032},[3011,12299,12300],{"class":3017},"void",[3011,12302,12303],{"class":3092}," SetValue",[3011,12305,3096],{"class":3028},[3011,12307,5175],{"class":3017},[3011,12309,12310],{"class":3024}," x",[3011,12312,3278],{"class":3028},[3011,12314,12315],{"class":3013,"line":3043},[3011,12316,3046],{"class":3028},[3011,12318,12319,12322,12324,12326,12328],{"class":3013,"line":3049},[3011,12320,12321],{"class":3024},"    x",[3011,12323,3081],{"class":3028},[3011,12325,5662],{"class":8913},[3011,12327,5076],{"class":3028},[3011,12329,12330],{"class":3213},"// Змінюється тільки локальна копія\n",[3011,12332,12333],{"class":3013,"line":3063},[3011,12334,3119],{"class":3028},[3011,12336,12337],{"class":3013,"line":3069},[3011,12338,3427],{"emptyLinePlaceholder":3426},[3011,12340,12341,12343,12346,12348,12350],{"class":3013,"line":3075},[3011,12342,5175],{"class":3017},[3011,12344,12345],{"class":3024}," number",[3011,12347,3081],{"class":3028},[3011,12349,8914],{"class":8913},[3011,12351,3029],{"class":3028},[3011,12353,12354,12357,12359,12362],{"class":3013,"line":3089},[3011,12355,12356],{"class":3092},"SetValue",[3011,12358,3096],{"class":3028},[3011,12360,12361],{"class":3024},"number",[3011,12363,3898],{"class":3028},[3011,12365,12366,12369,12371,12373,12375,12377,12379],{"class":3013,"line":3110},[3011,12367,12368],{"class":3024},"Console",[3011,12370,4010],{"class":3028},[3011,12372,4013],{"class":3092},[3011,12374,3096],{"class":3028},[3011,12376,12361],{"class":3024},[3011,12378,4585],{"class":3028},[3011,12380,12381],{"class":3213},"// 5 — не змінилося\n",[3011,12383,12384],{"class":3013,"line":3116},[3011,12385,3427],{"emptyLinePlaceholder":3426},[3011,12387,12388],{"class":3013,"line":3325},[3011,12389,12390],{"class":3213},"// З ref — зміна оригінальної змінної\n",[3011,12392,12393,12395,12397,12399,12401,12403,12405],{"class":3013,"line":3338},[3011,12394,12300],{"class":3017},[3011,12396,12303],{"class":3092},[3011,12398,3096],{"class":3028},[3011,12400,4984],{"class":3017},[3011,12402,5358],{"class":3017},[3011,12404,12310],{"class":3024},[3011,12406,3278],{"class":3028},[3011,12408,12409],{"class":3013,"line":3348},[3011,12410,3046],{"class":3028},[3011,12412,12413,12415,12417,12419,12421],{"class":3013,"line":3353},[3011,12414,12321],{"class":3024},[3011,12416,3081],{"class":3028},[3011,12418,5662],{"class":8913},[3011,12420,5076],{"class":3028},[3011,12422,12423],{"class":3213},"// Змінюється оригінальна змінна\n",[3011,12425,12426],{"class":3013,"line":3366},[3011,12427,3119],{"class":3028},[3011,12429,12430],{"class":3013,"line":3372},[3011,12431,3427],{"emptyLinePlaceholder":3426},[3011,12433,12434,12436,12438,12440,12442],{"class":3013,"line":3378},[3011,12435,5175],{"class":3017},[3011,12437,12345],{"class":3024},[3011,12439,3081],{"class":3028},[3011,12441,8914],{"class":8913},[3011,12443,3029],{"class":3028},[3011,12445,12446,12448,12450,12452,12454],{"class":3013,"line":3390},[3011,12447,12356],{"class":3092},[3011,12449,3096],{"class":3028},[3011,12451,4984],{"class":3017},[3011,12453,12345],{"class":3024},[3011,12455,3898],{"class":3028},[3011,12457,12458,12460,12462,12464,12466,12468,12470],{"class":3013,"line":3407},[3011,12459,12368],{"class":3024},[3011,12461,4010],{"class":3028},[3011,12463,4013],{"class":3092},[3011,12465,3096],{"class":3028},[3011,12467,12361],{"class":3024},[3011,12469,4585],{"class":3028},[3011,12471,12472],{"class":3213},"// 10 — змінилося\n",[2965,12474,12475],{},[2975,12476,12477],{},"Чому це важливо для SetProperty?",[3002,12479,12481],{"className":3004,"code":12480,"language":3006,"meta":3007,"style":3007},"protected bool SetProperty\u003CT>(ref T field, T value)\n{\n    // field — це backing field (_firstName, _age, тощо)\n    // ref дозволяє змінити оригінальний backing field\n    field = value;\n}\n\n// Використання\nprivate string _firstName;\npublic string FirstName\n{\n    get => _firstName;\n    set => SetProperty(ref _firstName, value);  // ref передає _firstName за посиланням\n}\n",[2989,12482,12483,12511,12515,12520,12525,12536,12540,12544,12548,12558,12566,12570,12580,12603],{"__ignoreMap":3007},[3011,12484,12485,12487,12489,12491,12493,12495,12497,12499,12501,12503,12505,12507,12509],{"class":3013,"line":3014},[3011,12486,3981],{"class":3017},[3011,12488,4969],{"class":3017},[3011,12490,4972],{"class":3092},[3011,12492,4975],{"class":3028},[3011,12494,4978],{"class":3224},[3011,12496,4981],{"class":3028},[3011,12498,4984],{"class":3017},[3011,12500,4987],{"class":3224},[3011,12502,4990],{"class":3024},[3011,12504,3301],{"class":3028},[3011,12506,4978],{"class":3224},[3011,12508,4997],{"class":3024},[3011,12510,3278],{"class":3028},[3011,12512,12513],{"class":3013,"line":3032},[3011,12514,3046],{"class":3028},[3011,12516,12517],{"class":3013,"line":3043},[3011,12518,12519],{"class":3213},"    // field — це backing field (_firstName, _age, тощо)\n",[3011,12521,12522],{"class":3013,"line":3049},[3011,12523,12524],{"class":3213},"    // ref дозволяє змінити оригінальний backing field\n",[3011,12526,12527,12530,12532,12534],{"class":3013,"line":3063},[3011,12528,12529],{"class":3024},"    field",[3011,12531,3081],{"class":3028},[3011,12533,3084],{"class":3024},[3011,12535,3029],{"class":3028},[3011,12537,12538],{"class":3013,"line":3069},[3011,12539,3119],{"class":3028},[3011,12541,12542],{"class":3013,"line":3075},[3011,12543,3427],{"emptyLinePlaceholder":3426},[3011,12545,12546],{"class":3013,"line":3089},[3011,12547,5786],{"class":3213},[3011,12549,12550,12552,12554,12556],{"class":3013,"line":3110},[3011,12551,3018],{"class":3017},[3011,12553,3021],{"class":3017},[3011,12555,3025],{"class":3024},[3011,12557,3029],{"class":3028},[3011,12559,12560,12562,12564],{"class":3013,"line":3116},[3011,12561,3035],{"class":3017},[3011,12563,3021],{"class":3017},[3011,12565,3040],{"class":3024},[3011,12567,12568],{"class":3013,"line":3325},[3011,12569,3046],{"class":3028},[3011,12571,12572,12574,12576,12578],{"class":3013,"line":3338},[3011,12573,3052],{"class":3024},[3011,12575,3055],{"class":3028},[3011,12577,3058],{"class":3024},[3011,12579,3029],{"class":3028},[3011,12581,12582,12584,12586,12588,12590,12592,12594,12596,12598,12600],{"class":3013,"line":3348},[3011,12583,5467],{"class":3024},[3011,12585,3055],{"class":3028},[3011,12587,4758],{"class":3092},[3011,12589,3096],{"class":3028},[3011,12591,4984],{"class":3017},[3011,12593,3025],{"class":3024},[3011,12595,3301],{"class":3028},[3011,12597,3084],{"class":3024},[3011,12599,4585],{"class":3028},[3011,12601,12602],{"class":3213},"// ref передає _firstName за посиланням\n",[3011,12604,12605],{"class":3013,"line":3353},[3011,12606,3119],{"class":3028},[2965,12608,12609,6174,12611,12613,12614,12616],{},[2975,12610,12064],{},[2989,12612,4984],{}," — це як дати комусь ключ від вашого будинку. Він може зайти та змінити щось всередині. Без ",[2989,12615,4984],{}," — це як дати фотографію будинку — він може змінити фото, але не сам будинок.",[3194,12618,12620],{"id":12619},"dictionary-зберігання-помилок-валідації","Dictionary: Зберігання помилок валідації",[2965,12622,12623],{},[2975,12624,12625],{},"Що таке Dictionary?",[2965,12627,12628,12629,12632],{},"Dictionary — це колекція ",[2975,12630,12631],{},"ключ-значення",". Швидкий пошук за ключем.",[3002,12634,12636],{"className":3004,"code":12635,"language":3006,"meta":3007,"style":3007},"// Dictionary\u003CTKey, TValue>\nDictionary\u003Cstring, List\u003Cstring>> _errors = new Dictionary\u003Cstring, List\u003Cstring>>();\n\n// Додати помилку для властивості \"Email\"\n_errors[\"Email\"] = new List\u003Cstring> { \"Некоректний email\" };\n\n// Отримати помилки для властивості \"Email\"\nList\u003Cstring> emailErrors = _errors[\"Email\"];\n\n// Перевірити, чи є помилки для властивості\nbool hasEmailErrors = _errors.ContainsKey(\"Email\");\n",[2989,12637,12638,12643,12684,12688,12693,12719,12723,12728,12752,12756,12761],{"__ignoreMap":3007},[3011,12639,12640],{"class":3013,"line":3014},[3011,12641,12642],{"class":3213},"// Dictionary\u003CTKey, TValue>\n",[3011,12644,12645,12648,12650,12652,12654,12656,12658,12660,12662,12664,12666,12668,12670,12672,12674,12676,12678,12680,12682],{"class":3013,"line":3032},[3011,12646,12647],{"class":3224},"Dictionary",[3011,12649,4975],{"class":3028},[3011,12651,3272],{"class":3017},[3011,12653,3301],{"class":3028},[3011,12655,7915],{"class":3224},[3011,12657,4975],{"class":3028},[3011,12659,3272],{"class":3017},[3011,12661,7922],{"class":3028},[3011,12663,7925],{"class":3024},[3011,12665,3081],{"class":3028},[3011,12667,3304],{"class":3017},[3011,12669,7906],{"class":3224},[3011,12671,4975],{"class":3028},[3011,12673,3272],{"class":3017},[3011,12675,3301],{"class":3028},[3011,12677,7915],{"class":3224},[3011,12679,4975],{"class":3028},[3011,12681,3272],{"class":3017},[3011,12683,7946],{"class":3028},[3011,12685,12686],{"class":3013,"line":3043},[3011,12687,3427],{"emptyLinePlaceholder":3426},[3011,12689,12690],{"class":3013,"line":3049},[3011,12691,12692],{"class":3213},"// Додати помилку для властивості \"Email\"\n",[3011,12694,12695,12697,12699,12702,12704,12706,12708,12710,12712,12714,12717],{"class":3013,"line":3063},[3011,12696,7925],{"class":3024},[3011,12698,8103],{"class":3028},[3011,12700,12701],{"class":3894},"\"Email\"",[3011,12703,8188],{"class":3028},[3011,12705,3304],{"class":3017},[3011,12707,8193],{"class":3224},[3011,12709,4975],{"class":3028},[3011,12711,3272],{"class":3017},[3011,12713,11616],{"class":3028},[3011,12715,12716],{"class":3894},"\"Некоректний email\"",[3011,12718,11627],{"class":3028},[3011,12720,12721],{"class":3013,"line":3069},[3011,12722,3427],{"emptyLinePlaceholder":3426},[3011,12724,12725],{"class":3013,"line":3075},[3011,12726,12727],{"class":3213},"// Отримати помилки для властивості \"Email\"\n",[3011,12729,12730,12732,12734,12736,12738,12741,12743,12745,12747,12749],{"class":3013,"line":3089},[3011,12731,7915],{"class":3224},[3011,12733,4975],{"class":3028},[3011,12735,3272],{"class":3017},[3011,12737,7466],{"class":3028},[3011,12739,12740],{"class":3024},"emailErrors",[3011,12742,3081],{"class":3028},[3011,12744,7925],{"class":3024},[3011,12746,8103],{"class":3028},[3011,12748,12701],{"class":3894},[3011,12750,12751],{"class":3028},"];\n",[3011,12753,12754],{"class":3013,"line":3110},[3011,12755,3427],{"emptyLinePlaceholder":3426},[3011,12757,12758],{"class":3013,"line":3116},[3011,12759,12760],{"class":3213},"// Перевірити, чи є помилки для властивості\n",[3011,12762,12763,12765,12768,12770,12772,12774,12776,12778,12780],{"class":3013,"line":3325},[3011,12764,5187],{"class":3017},[3011,12766,12767],{"class":3024}," hasEmailErrors",[3011,12769,3081],{"class":3028},[3011,12771,7925],{"class":3024},[3011,12773,4010],{"class":3028},[3011,12775,8091],{"class":3092},[3011,12777,3096],{"class":3028},[3011,12779,12701],{"class":3894},[3011,12781,3898],{"class":3028},[2965,12783,12784],{},[2975,12785,12786],{},"Чому це важливо для валідації?",[2982,12788,12789,12797,12800],{},[2985,12790,12791,12792,3301,12794,11711],{},"Ключ — ім'я властивості (",[2989,12793,12701],{},[2989,12795,12796],{},"\"Password\"",[2985,12798,12799],{},"Значення — список помилок для цієї властивості",[2985,12801,12802],{},"Швидкий доступ до помилок конкретної властивості",[2965,12804,12805],{},[2975,12806,12807],{},"Структура:",[3002,12809,12814],{"className":12810,"code":12812,"language":12813},[12811],"language-text","_errors = {\n    \"Email\": [\"Некоректний email\", \"Email занадто короткий\"],\n    \"Password\": [\"Пароль має містити цифру\"],\n    \"ConfirmPassword\": [\"Паролі не співпадають\"]\n}\n","text",[2989,12815,12812],{"__ignoreMap":3007},[4428,12817,12818,12821,12822,12826],{},[2975,12819,12820],{},"Детальніше про ООП:"," Якщо концепції абстрактних класів, generics або ref параметрів незрозумілі, рекомендую повернутися до розділу ",[2969,12823,12825],{"href":12824},"../02.oop/","ООП: Основи"," для глибшого розуміння.",[3181,12828],{},[2960,12830,12832],{"id":12831},"практичні-завдання","Практичні завдання",[3194,12834,12836],{"id":12835},"рівень-1-створити-baseviewmodel-з-setproperty","Рівень 1: Створити BaseViewModel з SetProperty",[2965,12838,12839,12841],{},[2975,12840,4114],{}," Навчитися створювати базовий клас для всіх ViewModel.",[2965,12843,12844],{},[2975,12845,12846],{},"Завдання:",[2965,12848,12849,12850,12852],{},"Створіть ",[2989,12851,3164],{}," з наступною функціональністю:",[4278,12854,12855,12860,12868,12873],{},[2985,12856,12857,12858],{},"Реалізація ",[2989,12859,2991],{},[2985,12861,12862,12863,12865,12866],{},"Метод ",[2989,12864,3940],{}," з ",[2989,12867,4297],{},[2985,12869,12862,12870,12872],{},[2989,12871,12246],{}," з перевіркою на зміну",[2985,12874,12875,12876,12879,12880,3301,12882,3301,12884],{},"Тестовий ",[2989,12877,12878],{},"PersonViewModel"," з властивостями ",[2989,12881,3104],{},[2989,12883,5930],{},[2989,12885,12886],{},"Age",[2965,12888,12889],{},[2975,12890,12891],{},"Критерії успіху:",[2982,12893,12894,12899,12907,12914,12919],{},[2985,12895,12896,12898],{},[2989,12897,3164],{}," є абстрактним класом",[2985,12900,12901,12903,12904,12906],{},[2989,12902,4758],{}," повертає ",[2989,12905,5187],{}," (чи змінилося значення)",[2985,12908,12909,12911,12912],{},[2989,12910,12878],{}," успадковує ",[2989,12913,3164],{},[2985,12915,12916,12917],{},"Всі властивості використовують ",[2989,12918,4758],{},[2985,12920,12921,12922],{},"При зміні властивості викликається ",[2989,12923,5622],{},[2965,12925,12926],{},[2975,12927,12928],{},"Підказка:",[3002,12930,12932],{"className":3004,"code":12931,"language":3006,"meta":3007,"style":3007},"public abstract class BaseViewModel : INotifyPropertyChanged\n{\n    public event PropertyChangedEventHandler PropertyChanged;\n    \n    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)\n    {\n        // TODO: Реалізувати\n    }\n    \n    protected bool SetProperty\u003CT>(ref T field, T value, [CallerMemberName] string propertyName = null)\n    {\n        // TODO: Реалізувати\n        // 1. Перевірити, чи змінилося значення (EqualityComparer\u003CT>.Default.Equals)\n        // 2. Якщо не змінилося — повернути false\n        // 3. Встановити нове значення (field = value)\n        // 4. Викликати OnPropertyChanged\n        // 5. Повернути true\n    }\n}\n\npublic class PersonViewModel : BaseViewModel\n{\n    // TODO: Додати властивості FirstName, LastName, Age з SetProperty\n}\n",[2989,12933,12934,12948,12952,12964,12968,12994,12998,13003,13007,13011,13053,13057,13061,13066,13071,13076,13081,13086,13090,13094,13098,13110,13114,13119],{"__ignoreMap":3007},[3011,12935,12936,12938,12940,12942,12944,12946],{"class":3013,"line":3014},[3011,12937,3035],{"class":3017},[3011,12939,4165],{"class":3017},[3011,12941,3221],{"class":3017},[3011,12943,4170],{"class":3224},[3011,12945,3228],{"class":3028},[3011,12947,3231],{"class":3224},[3011,12949,12950],{"class":3013,"line":3032},[3011,12951,3046],{"class":3028},[3011,12953,12954,12956,12958,12960,12962],{"class":3013,"line":3043},[3011,12955,3240],{"class":3017},[3011,12957,3243],{"class":3017},[3011,12959,3246],{"class":3224},[3011,12961,3249],{"class":3024},[3011,12963,3029],{"class":3028},[3011,12965,12966],{"class":3013,"line":3049},[3011,12967,3256],{"class":3028},[3011,12969,12970,12972,12974,12976,12978,12980,12982,12984,12986,12988,12990,12992],{"class":3013,"line":3063},[3011,12971,3261],{"class":3017},[3011,12973,4211],{"class":3017},[3011,12975,3264],{"class":3017},[3011,12977,3267],{"class":3092},[3011,12979,4218],{"class":3028},[3011,12981,4221],{"class":3224},[3011,12983,4224],{"class":3028},[3011,12985,3272],{"class":3017},[3011,12987,3275],{"class":3024},[3011,12989,3081],{"class":3028},[3011,12991,4233],{"class":3017},[3011,12993,3278],{"class":3028},[3011,12995,12996],{"class":3013,"line":3069},[3011,12997,3072],{"class":3028},[3011,12999,13000],{"class":3013,"line":3075},[3011,13001,13002],{"class":3213},"        // TODO: Реалізувати\n",[3011,13004,13005],{"class":3013,"line":3089},[3011,13006,3113],{"class":3028},[3011,13008,13009],{"class":3013,"line":3110},[3011,13010,3256],{"class":3028},[3011,13012,13013,13015,13017,13019,13021,13023,13025,13027,13029,13031,13033,13035,13037,13039,13041,13043,13045,13047,13049,13051],{"class":3013,"line":3116},[3011,13014,3261],{"class":3017},[3011,13016,4969],{"class":3017},[3011,13018,4972],{"class":3092},[3011,13020,4975],{"class":3028},[3011,13022,4978],{"class":3224},[3011,13024,4981],{"class":3028},[3011,13026,4984],{"class":3017},[3011,13028,4987],{"class":3224},[3011,13030,4990],{"class":3024},[3011,13032,3301],{"class":3028},[3011,13034,4978],{"class":3224},[3011,13036,4997],{"class":3024},[3011,13038,5000],{"class":3028},[3011,13040,4221],{"class":3224},[3011,13042,4224],{"class":3028},[3011,13044,3272],{"class":3017},[3011,13046,3275],{"class":3024},[3011,13048,3081],{"class":3028},[3011,13050,4233],{"class":3017},[3011,13052,3278],{"class":3028},[3011,13054,13055],{"class":3013,"line":3325},[3011,13056,3072],{"class":3028},[3011,13058,13059],{"class":3013,"line":3338},[3011,13060,13002],{"class":3213},[3011,13062,13063],{"class":3013,"line":3348},[3011,13064,13065],{"class":3213},"        // 1. Перевірити, чи змінилося значення (EqualityComparer\u003CT>.Default.Equals)\n",[3011,13067,13068],{"class":3013,"line":3353},[3011,13069,13070],{"class":3213},"        // 2. Якщо не змінилося — повернути false\n",[3011,13072,13073],{"class":3013,"line":3366},[3011,13074,13075],{"class":3213},"        // 3. Встановити нове значення (field = value)\n",[3011,13077,13078],{"class":3013,"line":3372},[3011,13079,13080],{"class":3213},"        // 4. Викликати OnPropertyChanged\n",[3011,13082,13083],{"class":3013,"line":3378},[3011,13084,13085],{"class":3213},"        // 5. Повернути true\n",[3011,13087,13088],{"class":3013,"line":3390},[3011,13089,3113],{"class":3028},[3011,13091,13092],{"class":3013,"line":3407},[3011,13093,3119],{"class":3028},[3011,13095,13096],{"class":3013,"line":3413},[3011,13097,3427],{"emptyLinePlaceholder":3426},[3011,13099,13100,13102,13104,13106,13108],{"class":3013,"line":3418},[3011,13101,3035],{"class":3017},[3011,13103,3221],{"class":3017},[3011,13105,5961],{"class":3224},[3011,13107,3228],{"class":3028},[3011,13109,4329],{"class":3224},[3011,13111,13112],{"class":3013,"line":3423},[3011,13113,3046],{"class":3028},[3011,13115,13116],{"class":3013,"line":3430},[3011,13117,13118],{"class":3213},"    // TODO: Додати властивості FirstName, LastName, Age з SetProperty\n",[3011,13120,13121],{"class":3013,"line":3436},[3011,13122,3119],{"class":3028},[2965,13124,13125],{},[2975,13126,13127],{},"Тест:",[3002,13129,13131],{"className":3004,"code":13130,"language":3006,"meta":3007,"style":3007},"[Test]\npublic void SetProperty_ShouldRaisePropertyChanged()\n{\n    var vm = new PersonViewModel();\n    bool eventRaised = false;\n    \n    vm.PropertyChanged += (s, e) =>\n    {\n        if (e.PropertyName == nameof(PersonViewModel.FirstName))\n            eventRaised = true;\n    };\n    \n    vm.FirstName = \"Іван\";\n    \n    Assert.IsTrue(eventRaised);\n}\n\n[Test]\npublic void SetProperty_ShouldNotRaisePropertyChanged_WhenValueNotChanged()\n{\n    var vm = new PersonViewModel { FirstName = \"Іван\" };\n    int eventCount = 0;\n    \n    vm.PropertyChanged += (s, e) => eventCount++;\n    \n    vm.FirstName = \"Іван\";  // Те саме значення\n    \n    Assert.AreEqual(0, eventCount);\n}\n",[2989,13132,13133,13143,13154,13158,13173,13186,13190,13212,13216,13243,13254,13259,13263,13278,13282,13299,13303,13307,13315,13326,13330,13352,13367,13371,13396,13400,13417,13421,13440],{"__ignoreMap":3007},[3011,13134,13135,13137,13140],{"class":3013,"line":3014},[3011,13136,8103],{"class":3028},[3011,13138,13139],{"class":3224},"Test",[3011,13141,13142],{"class":3028},"]\n",[3011,13144,13145,13147,13149,13152],{"class":3013,"line":3032},[3011,13146,3035],{"class":3017},[3011,13148,3264],{"class":3017},[3011,13150,13151],{"class":3092}," SetProperty_ShouldRaisePropertyChanged",[3011,13153,7275],{"class":3028},[3011,13155,13156],{"class":3013,"line":3043},[3011,13157,3046],{"class":3028},[3011,13159,13160,13163,13165,13167,13169,13171],{"class":3013,"line":3049},[3011,13161,13162],{"class":3017},"    var",[3011,13164,11991],{"class":3024},[3011,13166,3081],{"class":3028},[3011,13168,3304],{"class":3017},[3011,13170,5961],{"class":3224},[3011,13172,4395],{"class":3028},[3011,13174,13175,13177,13180,13182,13184],{"class":3013,"line":3063},[3011,13176,7404],{"class":3017},[3011,13178,13179],{"class":3024}," eventRaised",[3011,13181,3081],{"class":3028},[3011,13183,5194],{"class":3017},[3011,13185,3029],{"class":3028},[3011,13187,13188],{"class":3013,"line":3069},[3011,13189,3256],{"class":3028},[3011,13191,13192,13195,13197,13199,13202,13205,13207,13209],{"class":3013,"line":3075},[3011,13193,13194],{"class":3024},"    vm",[3011,13196,4010],{"class":3028},[3011,13198,5622],{"class":3024},[3011,13200,13201],{"class":3028}," += (",[3011,13203,13204],{"class":3024},"s",[3011,13206,3301],{"class":3028},[3011,13208,8066],{"class":3024},[3011,13210,13211],{"class":3028},") =>\n",[3011,13213,13214],{"class":3013,"line":3089},[3011,13215,3072],{"class":3028},[3011,13217,13218,13220,13222,13224,13226,13229,13231,13233,13235,13237,13239,13241],{"class":3013,"line":3110},[3011,13219,5028],{"class":4125},[3011,13221,5031],{"class":3028},[3011,13223,8066],{"class":3024},[3011,13225,4010],{"class":3028},[3011,13227,13228],{"class":3024},"PropertyName",[3011,13230,10413],{"class":3028},[3011,13232,3099],{"class":3017},[3011,13234,3096],{"class":3028},[3011,13236,12878],{"class":3024},[3011,13238,4010],{"class":3028},[3011,13240,3104],{"class":3024},[3011,13242,5061],{"class":3028},[3011,13244,13245,13248,13250,13252],{"class":3013,"line":3116},[3011,13246,13247],{"class":3024},"            eventRaised",[3011,13249,3081],{"class":3028},[3011,13251,5190],{"class":3017},[3011,13253,3029],{"class":3028},[3011,13255,13256],{"class":3013,"line":3325},[3011,13257,13258],{"class":3028},"    };\n",[3011,13260,13261],{"class":3013,"line":3338},[3011,13262,3256],{"class":3028},[3011,13264,13265,13267,13269,13271,13273,13276],{"class":3013,"line":3348},[3011,13266,13194],{"class":3024},[3011,13268,4010],{"class":3028},[3011,13270,3104],{"class":3024},[3011,13272,3081],{"class":3028},[3011,13274,13275],{"class":3894},"\"Іван\"",[3011,13277,3029],{"class":3028},[3011,13279,13280],{"class":3013,"line":3353},[3011,13281,3256],{"class":3028},[3011,13283,13284,13287,13289,13292,13294,13297],{"class":3013,"line":3366},[3011,13285,13286],{"class":3024},"    Assert",[3011,13288,4010],{"class":3028},[3011,13290,13291],{"class":3092},"IsTrue",[3011,13293,3096],{"class":3028},[3011,13295,13296],{"class":3024},"eventRaised",[3011,13298,3898],{"class":3028},[3011,13300,13301],{"class":3013,"line":3372},[3011,13302,3119],{"class":3028},[3011,13304,13305],{"class":3013,"line":3378},[3011,13306,3427],{"emptyLinePlaceholder":3426},[3011,13308,13309,13311,13313],{"class":3013,"line":3390},[3011,13310,8103],{"class":3028},[3011,13312,13139],{"class":3224},[3011,13314,13142],{"class":3028},[3011,13316,13317,13319,13321,13324],{"class":3013,"line":3407},[3011,13318,3035],{"class":3017},[3011,13320,3264],{"class":3017},[3011,13322,13323],{"class":3092}," SetProperty_ShouldNotRaisePropertyChanged_WhenValueNotChanged",[3011,13325,7275],{"class":3028},[3011,13327,13328],{"class":3013,"line":3413},[3011,13329,3046],{"class":3028},[3011,13331,13332,13334,13336,13338,13340,13342,13344,13346,13348,13350],{"class":3013,"line":3418},[3011,13333,13162],{"class":3017},[3011,13335,11991],{"class":3024},[3011,13337,3081],{"class":3028},[3011,13339,3304],{"class":3017},[3011,13341,5961],{"class":3224},[3011,13343,7410],{"class":3028},[3011,13345,3104],{"class":3024},[3011,13347,3081],{"class":3028},[3011,13349,13275],{"class":3894},[3011,13351,11627],{"class":3028},[3011,13353,13354,13357,13360,13362,13365],{"class":3013,"line":3423},[3011,13355,13356],{"class":3017},"    int",[3011,13358,13359],{"class":3024}," eventCount",[3011,13361,3081],{"class":3028},[3011,13363,13364],{"class":8913},"0",[3011,13366,3029],{"class":3028},[3011,13368,13369],{"class":3013,"line":3430},[3011,13370,3256],{"class":3028},[3011,13372,13373,13375,13377,13379,13381,13383,13385,13387,13390,13393],{"class":3013,"line":3436},[3011,13374,13194],{"class":3024},[3011,13376,4010],{"class":3028},[3011,13378,5622],{"class":3024},[3011,13380,13201],{"class":3028},[3011,13382,13204],{"class":3024},[3011,13384,3301],{"class":3028},[3011,13386,8066],{"class":3024},[3011,13388,13389],{"class":3028},") => ",[3011,13391,13392],{"class":3024},"eventCount",[3011,13394,13395],{"class":3028},"++;\n",[3011,13397,13398],{"class":3013,"line":3450},[3011,13399,3256],{"class":3028},[3011,13401,13402,13404,13406,13408,13410,13412,13414],{"class":3013,"line":3455},[3011,13403,13194],{"class":3024},[3011,13405,4010],{"class":3028},[3011,13407,3104],{"class":3024},[3011,13409,3081],{"class":3028},[3011,13411,13275],{"class":3894},[3011,13413,5076],{"class":3028},[3011,13415,13416],{"class":3213},"// Те саме значення\n",[3011,13418,13419],{"class":3013,"line":3461},[3011,13420,3256],{"class":3028},[3011,13422,13423,13425,13427,13430,13432,13434,13436,13438],{"class":3013,"line":3474},[3011,13424,13286],{"class":3024},[3011,13426,4010],{"class":3028},[3011,13428,13429],{"class":3092},"AreEqual",[3011,13431,3096],{"class":3028},[3011,13433,13364],{"class":8913},[3011,13435,3301],{"class":3028},[3011,13437,13392],{"class":3024},[3011,13439,3898],{"class":3028},[3011,13441,13442],{"class":3013,"line":3479},[3011,13443,3119],{"class":3028},[3181,13445],{},[3194,13447,13449],{"id":13448},"рівень-2-додати-валідацію-через-inotifydataerrorinfo","Рівень 2: Додати валідацію через INotifyDataErrorInfo",[2965,13451,13452,13454],{},[2975,13453,4114],{}," Реалізувати валідацію з відображенням помилок у UI.",[2965,13456,13457],{},[2975,13458,12846],{},[2965,13460,13461,13462,13464,13465,3930],{},"Розширте ",[2989,13463,3164],{}," з Рівня 1, додавши підтримку ",[2989,13466,7365],{},[4278,13468,13469,13474,13483,13512],{},[2985,13470,13471,13472],{},"Реалізувати інтерфейс ",[2989,13473,7365],{},[2985,13475,13476,13477,3301,13480],{},"Додати методи ",[2989,13478,13479],{},"AddError",[2989,13481,13482],{},"ClearErrors",[2985,13484,13485,13486,13489,13490],{},"Створити ",[2989,13487,13488],{},"RegistrationViewModel"," з валідацією:\n",[2982,13491,13492,13500,13505],{},[2985,13493,13494,13496,13497],{},[2989,13495,6940],{}," — обов'язковий, має містити ",[2989,13498,13499],{},"@",[2985,13501,13502,13504],{},[2989,13503,8984],{}," — мінімум 8 символів, має містити цифру",[2985,13506,13507,13509,13510],{},[2989,13508,9203],{}," — має співпадати з ",[2989,13511,8984],{},[2985,13513,13514],{},"Створити XAML-форму з відображенням помилок",[2965,13516,13517],{},[2975,13518,12891],{},[2982,13520,13521,13528,13531,13534,13537],{},[2985,13522,13523,13525,13526],{},[2989,13524,3164],{}," реалізує ",[2989,13527,7365],{},[2985,13529,13530],{},"Валідація викликається при зміні властивості",[2985,13532,13533],{},"UI показує червону рамку при помилці",[2985,13535,13536],{},"Кнопка \"Зареєструватися\" активна тільки якщо немає помилок",[2985,13538,13539],{},"Всі тести проходять",[2965,13541,13542],{},[2975,13543,13544],{},"Підказка для BaseViewModel:",[3002,13546,13548],{"className":3004,"code":13547,"language":3006,"meta":3007,"style":3007},"public abstract class BaseViewModel : INotifyPropertyChanged, INotifyDataErrorInfo\n{\n    // ... попередній код INotifyPropertyChanged\n    \n    private readonly Dictionary\u003Cstring, List\u003Cstring>> _errors = new Dictionary\u003Cstring, List\u003Cstring>>();\n    \n    public event EventHandler\u003CDataErrorsChangedEventArgs> ErrorsChanged;\n    \n    public bool HasErrors => _errors.Any();\n    \n    public IEnumerable GetErrors(string propertyName)\n    {\n        // TODO: Реалізувати\n    }\n    \n    protected void AddError(string propertyName, string error)\n    {\n        // TODO: Реалізувати\n        // 1. Створити список помилок для властивості, якщо не існує\n        // 2. Додати помилку до списку\n        // 3. Викликати OnErrorsChanged\n    }\n    \n    protected void ClearErrors(string propertyName)\n    {\n        // TODO: Реалізувати\n        // 1. Видалити помилки для властивості\n        // 2. Викликати OnErrorsChanged\n    }\n    \n    protected void OnErrorsChanged(string propertyName)\n    {\n        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));\n        OnPropertyChanged(nameof(HasErrors));\n    }\n}\n",[2989,13549,13550,13568,13572,13577,13581,13625,13629,13647,13651,13669,13673,13689,13693,13697,13701,13705,13727,13731,13735,13740,13745,13750,13754,13758,13774,13778,13782,13787,13792,13796,13800,13816,13820,13844,13858,13862],{"__ignoreMap":3007},[3011,13551,13552,13554,13556,13558,13560,13562,13564,13566],{"class":3013,"line":3014},[3011,13553,3035],{"class":3017},[3011,13555,4165],{"class":3017},[3011,13557,3221],{"class":3017},[3011,13559,4170],{"class":3224},[3011,13561,3228],{"class":3028},[3011,13563,2991],{"class":3224},[3011,13565,3301],{"class":3028},[3011,13567,7680],{"class":3224},[3011,13569,13570],{"class":3013,"line":3032},[3011,13571,3046],{"class":3028},[3011,13573,13574],{"class":3013,"line":3043},[3011,13575,13576],{"class":3213},"    // ... попередній код INotifyPropertyChanged\n",[3011,13578,13579],{"class":3013,"line":3049},[3011,13580,3256],{"class":3028},[3011,13582,13583,13585,13587,13589,13591,13593,13595,13597,13599,13601,13603,13605,13607,13609,13611,13613,13615,13617,13619,13621,13623],{"class":3013,"line":3063},[3011,13584,3328],{"class":3017},[3011,13586,7903],{"class":3017},[3011,13588,7906],{"class":3224},[3011,13590,4975],{"class":3028},[3011,13592,3272],{"class":3017},[3011,13594,3301],{"class":3028},[3011,13596,7915],{"class":3224},[3011,13598,4975],{"class":3028},[3011,13600,3272],{"class":3017},[3011,13602,7922],{"class":3028},[3011,13604,7925],{"class":3024},[3011,13606,3081],{"class":3028},[3011,13608,3304],{"class":3017},[3011,13610,7906],{"class":3224},[3011,13612,4975],{"class":3028},[3011,13614,3272],{"class":3017},[3011,13616,3301],{"class":3028},[3011,13618,7915],{"class":3224},[3011,13620,4975],{"class":3028},[3011,13622,3272],{"class":3017},[3011,13624,7946],{"class":3028},[3011,13626,13627],{"class":3013,"line":3069},[3011,13628,3256],{"class":3028},[3011,13630,13631,13633,13635,13637,13639,13641,13643,13645],{"class":3013,"line":3075},[3011,13632,3240],{"class":3017},[3011,13634,3243],{"class":3017},[3011,13636,7458],{"class":3224},[3011,13638,4975],{"class":3028},[3011,13640,7463],{"class":3224},[3011,13642,7466],{"class":3028},[3011,13644,7469],{"class":3024},[3011,13646,3029],{"class":3028},[3011,13648,13649],{"class":3013,"line":3089},[3011,13650,3256],{"class":3028},[3011,13652,13653,13655,13657,13659,13661,13663,13665,13667],{"class":3013,"line":3110},[3011,13654,3240],{"class":3017},[3011,13656,4969],{"class":3017},[3011,13658,7407],{"class":3024},[3011,13660,3055],{"class":3028},[3011,13662,7925],{"class":3024},[3011,13664,4010],{"class":3028},[3011,13666,7989],{"class":3092},[3011,13668,4395],{"class":3028},[3011,13670,13671],{"class":3013,"line":3116},[3011,13672,3256],{"class":3028},[3011,13674,13675,13677,13679,13681,13683,13685,13687],{"class":3013,"line":3325},[3011,13676,3240],{"class":3017},[3011,13678,8002],{"class":3224},[3011,13680,7433],{"class":3092},[3011,13682,3096],{"class":3028},[3011,13684,3272],{"class":3017},[3011,13686,3275],{"class":3024},[3011,13688,3278],{"class":3028},[3011,13690,13691],{"class":3013,"line":3338},[3011,13692,3072],{"class":3028},[3011,13694,13695],{"class":3013,"line":3348},[3011,13696,13002],{"class":3213},[3011,13698,13699],{"class":3013,"line":3353},[3011,13700,3113],{"class":3028},[3011,13702,13703],{"class":3013,"line":3366},[3011,13704,3256],{"class":3028},[3011,13706,13707,13709,13711,13713,13715,13717,13719,13721,13723,13725],{"class":3013,"line":3372},[3011,13708,3261],{"class":3017},[3011,13710,3264],{"class":3017},[3011,13712,8134],{"class":3092},[3011,13714,3096],{"class":3028},[3011,13716,3272],{"class":3017},[3011,13718,3275],{"class":3024},[3011,13720,3301],{"class":3028},[3011,13722,3272],{"class":3017},[3011,13724,8147],{"class":3024},[3011,13726,3278],{"class":3028},[3011,13728,13729],{"class":3013,"line":3378},[3011,13730,3072],{"class":3028},[3011,13732,13733],{"class":3013,"line":3390},[3011,13734,13002],{"class":3213},[3011,13736,13737],{"class":3013,"line":3407},[3011,13738,13739],{"class":3213},"        // 1. Створити список помилок для властивості, якщо не існує\n",[3011,13741,13742],{"class":3013,"line":3413},[3011,13743,13744],{"class":3213},"        // 2. Додати помилку до списку\n",[3011,13746,13747],{"class":3013,"line":3418},[3011,13748,13749],{"class":3213},"        // 3. Викликати OnErrorsChanged\n",[3011,13751,13752],{"class":3013,"line":3423},[3011,13753,3113],{"class":3028},[3011,13755,13756],{"class":3013,"line":3430},[3011,13757,3256],{"class":3028},[3011,13759,13760,13762,13764,13766,13768,13770,13772],{"class":3013,"line":3436},[3011,13761,3261],{"class":3017},[3011,13763,3264],{"class":3017},[3011,13765,8295],{"class":3092},[3011,13767,3096],{"class":3028},[3011,13769,3272],{"class":3017},[3011,13771,3275],{"class":3024},[3011,13773,3278],{"class":3028},[3011,13775,13776],{"class":3013,"line":3450},[3011,13777,3072],{"class":3028},[3011,13779,13780],{"class":3013,"line":3455},[3011,13781,13002],{"class":3213},[3011,13783,13784],{"class":3013,"line":3461},[3011,13785,13786],{"class":3213},"        // 1. Видалити помилки для властивості\n",[3011,13788,13789],{"class":3013,"line":3474},[3011,13790,13791],{"class":3213},"        // 2. Викликати OnErrorsChanged\n",[3011,13793,13794],{"class":3013,"line":3479},[3011,13795,3113],{"class":3028},[3011,13797,13798],{"class":3013,"line":3496},[3011,13799,3256],{"class":3028},[3011,13801,13802,13804,13806,13808,13810,13812,13814],{"class":3013,"line":3501},[3011,13803,3261],{"class":3017},[3011,13805,3264],{"class":3017},[3011,13807,8390],{"class":3092},[3011,13809,3096],{"class":3028},[3011,13811,3272],{"class":3017},[3011,13813,3275],{"class":3024},[3011,13815,3278],{"class":3028},[3011,13817,13818],{"class":3013,"line":3526},[3011,13819,3072],{"class":3028},[3011,13821,13822,13824,13826,13828,13830,13832,13834,13836,13838,13840,13842],{"class":3013,"line":3531},[3011,13823,8409],{"class":3024},[3011,13825,3290],{"class":3028},[3011,13827,3293],{"class":3092},[3011,13829,3096],{"class":3028},[3011,13831,3298],{"class":3017},[3011,13833,3301],{"class":3028},[3011,13835,3304],{"class":3017},[3011,13837,8424],{"class":3224},[3011,13839,3096],{"class":3028},[3011,13841,3312],{"class":3024},[3011,13843,3107],{"class":3028},[3011,13845,13846,13848,13850,13852,13854,13856],{"class":3013,"line":3536},[3011,13847,3093],{"class":3092},[3011,13849,3096],{"class":3028},[3011,13851,3099],{"class":3017},[3011,13853,3096],{"class":3028},[3011,13855,8444],{"class":3024},[3011,13857,3107],{"class":3028},[3011,13859,13860],{"class":3013,"line":3548},[3011,13861,3113],{"class":3028},[3011,13863,13864],{"class":3013,"line":3558},[3011,13865,3119],{"class":3028},[2965,13867,13868],{},[2975,13869,13870],{},"Підказка для RegistrationViewModel:",[3002,13872,13874],{"className":3004,"code":13873,"language":3006,"meta":3007,"style":3007},"public class RegistrationViewModel : BaseViewModel\n{\n    private string _email;\n    public string Email\n    {\n        get => _email;\n        set\n        {\n            if (SetProperty(ref _email, value))\n            {\n                ValidateEmail();\n            }\n        }\n    }\n    \n    private void ValidateEmail()\n    {\n        ClearErrors(nameof(Email));\n        \n        if (string.IsNullOrWhiteSpace(Email))\n        {\n            AddError(nameof(Email), \"Email обов'язковий\");\n        }\n        else if (!Email.Contains(\"@\"))\n        {\n            AddError(nameof(Email), \"Некоректний формат email\");\n        }\n    }\n    \n    // TODO: Додати Password, ConfirmPassword з валідацією\n}\n",[2989,13875,13876,13888,13892,13902,13910,13914,13924,13928,13932,13952,13956,13962,13966,13970,13974,13978,13988,13992,14006,14010,14028,14032,14050,14054,14074,14078,14096,14100,14104,14108,14113],{"__ignoreMap":3007},[3011,13877,13878,13880,13882,13884,13886],{"class":3013,"line":3014},[3011,13879,3035],{"class":3017},[3011,13881,3221],{"class":3017},[3011,13883,8479],{"class":3224},[3011,13885,3228],{"class":3028},[3011,13887,4329],{"class":3224},[3011,13889,13890],{"class":3013,"line":3032},[3011,13891,3046],{"class":3028},[3011,13893,13894,13896,13898,13900],{"class":3013,"line":3043},[3011,13895,3328],{"class":3017},[3011,13897,3021],{"class":3017},[3011,13899,7108],{"class":3024},[3011,13901,3029],{"class":3028},[3011,13903,13904,13906,13908],{"class":3013,"line":3049},[3011,13905,3240],{"class":3017},[3011,13907,3021],{"class":3017},[3011,13909,7119],{"class":3024},[3011,13911,13912],{"class":3013,"line":3063},[3011,13913,3072],{"class":3028},[3011,13915,13916,13918,13920,13922],{"class":3013,"line":3069},[3011,13917,3356],{"class":3017},[3011,13919,3055],{"class":3028},[3011,13921,7132],{"class":3024},[3011,13923,3029],{"class":3028},[3011,13925,13926],{"class":3013,"line":3075},[3011,13927,3369],{"class":3017},[3011,13929,13930],{"class":3013,"line":3089},[3011,13931,3375],{"class":3028},[3011,13933,13934,13936,13938,13940,13942,13944,13946,13948,13950],{"class":3013,"line":3110},[3011,13935,8532],{"class":4125},[3011,13937,5031],{"class":3028},[3011,13939,4758],{"class":3092},[3011,13941,3096],{"class":3028},[3011,13943,4984],{"class":3017},[3011,13945,7108],{"class":3024},[3011,13947,3301],{"class":3028},[3011,13949,3084],{"class":3024},[3011,13951,5061],{"class":3028},[3011,13953,13954],{"class":3013,"line":3116},[3011,13955,8553],{"class":3028},[3011,13957,13958,13960],{"class":3013,"line":3325},[3011,13959,8558],{"class":3092},[3011,13961,4395],{"class":3028},[3011,13963,13964],{"class":3013,"line":3338},[3011,13965,8565],{"class":3028},[3011,13967,13968],{"class":3013,"line":3348},[3011,13969,3410],{"class":3028},[3011,13971,13972],{"class":3013,"line":3353},[3011,13973,3113],{"class":3028},[3011,13975,13976],{"class":3013,"line":3366},[3011,13977,3256],{"class":3028},[3011,13979,13980,13982,13984,13986],{"class":3013,"line":3372},[3011,13981,3328],{"class":3017},[3011,13983,3264],{"class":3017},[3011,13985,8771],{"class":3092},[3011,13987,7275],{"class":3028},[3011,13989,13990],{"class":3013,"line":3378},[3011,13991,3072],{"class":3028},[3011,13993,13994,13996,13998,14000,14002,14004],{"class":3013,"line":3390},[3011,13995,8782],{"class":3092},[3011,13997,3096],{"class":3028},[3011,13999,3099],{"class":3017},[3011,14001,3096],{"class":3028},[3011,14003,6940],{"class":3024},[3011,14005,3107],{"class":3028},[3011,14007,14008],{"class":3013,"line":3407},[3011,14009,5088],{"class":3028},[3011,14011,14012,14014,14016,14018,14020,14022,14024,14026],{"class":3013,"line":3413},[3011,14013,5028],{"class":4125},[3011,14015,5031],{"class":3028},[3011,14017,3272],{"class":3017},[3011,14019,4010],{"class":3028},[3011,14021,7198],{"class":3092},[3011,14023,3096],{"class":3028},[3011,14025,6940],{"class":3024},[3011,14027,5061],{"class":3028},[3011,14029,14030],{"class":3013,"line":3418},[3011,14031,3375],{"class":3028},[3011,14033,14034,14036,14038,14040,14042,14044,14046,14048],{"class":3013,"line":3423},[3011,14035,8823],{"class":3092},[3011,14037,3096],{"class":3028},[3011,14039,3099],{"class":3017},[3011,14041,3096],{"class":3028},[3011,14043,6940],{"class":3024},[3011,14045,8834],{"class":3028},[3011,14047,8837],{"class":3894},[3011,14049,3898],{"class":3028},[3011,14051,14052],{"class":3013,"line":3430},[3011,14053,3410],{"class":3028},[3011,14055,14056,14058,14060,14062,14064,14066,14068,14070,14072],{"class":3013,"line":3436},[3011,14057,8848],{"class":4125},[3011,14059,8851],{"class":4125},[3011,14061,8160],{"class":3028},[3011,14063,6940],{"class":3024},[3011,14065,4010],{"class":3028},[3011,14067,7247],{"class":3092},[3011,14069,3096],{"class":3028},[3011,14071,7252],{"class":3894},[3011,14073,5061],{"class":3028},[3011,14075,14076],{"class":3013,"line":3450},[3011,14077,3375],{"class":3028},[3011,14079,14080,14082,14084,14086,14088,14090,14092,14094],{"class":3013,"line":3455},[3011,14081,8823],{"class":3092},[3011,14083,3096],{"class":3028},[3011,14085,3099],{"class":3017},[3011,14087,3096],{"class":3028},[3011,14089,6940],{"class":3024},[3011,14091,8834],{"class":3028},[3011,14093,8886],{"class":3894},[3011,14095,3898],{"class":3028},[3011,14097,14098],{"class":3013,"line":3461},[3011,14099,3410],{"class":3028},[3011,14101,14102],{"class":3013,"line":3474},[3011,14103,3113],{"class":3028},[3011,14105,14106],{"class":3013,"line":3479},[3011,14107,3256],{"class":3028},[3011,14109,14110],{"class":3013,"line":3496},[3011,14111,14112],{"class":3213},"    // TODO: Додати Password, ConfirmPassword з валідацією\n",[3011,14114,14115],{"class":3013,"line":3501},[3011,14116,3119],{"class":3028},[2965,14118,14119],{},[2975,14120,14121],{},"Тести:",[3002,14123,14125],{"className":3004,"code":14124,"language":3006,"meta":3007,"style":3007},"[Test]\npublic void Email_ShouldHaveError_WhenEmpty()\n{\n    var vm = new RegistrationViewModel();\n    vm.Email = \"\";\n    \n    Assert.IsTrue(vm.HasErrors);\n    var errors = vm.GetErrors(nameof(vm.Email)).Cast\u003Cstring>().ToList();\n    Assert.IsTrue(errors.Any(e => e.Contains(\"обов'язковий\")));\n}\n\n[Test]\npublic void Email_ShouldHaveError_WhenInvalidFormat()\n{\n    var vm = new RegistrationViewModel();\n    vm.Email = \"invalid-email\";\n    \n    Assert.IsTrue(vm.HasErrors);\n    var errors = vm.GetErrors(nameof(vm.Email)).Cast\u003Cstring>().ToList();\n    Assert.IsTrue(errors.Any(e => e.Contains(\"Некоректний формат\")));\n}\n\n[Test]\npublic void ConfirmPassword_ShouldHaveError_WhenNotMatchingPassword()\n{\n    var vm = new RegistrationViewModel();\n    vm.Password = \"Password123\";\n    vm.ConfirmPassword = \"DifferentPassword\";\n    \n    Assert.IsTrue(vm.HasErrors);\n    var errors = vm.GetErrors(nameof(vm.ConfirmPassword)).Cast\u003Cstring>().ToList();\n    Assert.IsTrue(errors.Any(e => e.Contains(\"не співпадають\")));\n}\n",[2989,14126,14127,14135,14146,14150,14164,14179,14183,14202,14247,14283,14287,14291,14299,14310,14314,14328,14343,14347,14365,14405,14440,14444,14448,14456,14467,14471,14485,14500,14515,14519,14537,14577,14612],{"__ignoreMap":3007},[3011,14128,14129,14131,14133],{"class":3013,"line":3014},[3011,14130,8103],{"class":3028},[3011,14132,13139],{"class":3224},[3011,14134,13142],{"class":3028},[3011,14136,14137,14139,14141,14144],{"class":3013,"line":3032},[3011,14138,3035],{"class":3017},[3011,14140,3264],{"class":3017},[3011,14142,14143],{"class":3092}," Email_ShouldHaveError_WhenEmpty",[3011,14145,7275],{"class":3028},[3011,14147,14148],{"class":3013,"line":3043},[3011,14149,3046],{"class":3028},[3011,14151,14152,14154,14156,14158,14160,14162],{"class":3013,"line":3049},[3011,14153,13162],{"class":3017},[3011,14155,11991],{"class":3024},[3011,14157,3081],{"class":3028},[3011,14159,3304],{"class":3017},[3011,14161,8479],{"class":3224},[3011,14163,4395],{"class":3028},[3011,14165,14166,14168,14170,14172,14174,14177],{"class":3013,"line":3063},[3011,14167,13194],{"class":3024},[3011,14169,4010],{"class":3028},[3011,14171,6940],{"class":3024},[3011,14173,3081],{"class":3028},[3011,14175,14176],{"class":3894},"\"\"",[3011,14178,3029],{"class":3028},[3011,14180,14181],{"class":3013,"line":3069},[3011,14182,3256],{"class":3028},[3011,14184,14185,14187,14189,14191,14193,14196,14198,14200],{"class":3013,"line":3075},[3011,14186,13286],{"class":3024},[3011,14188,4010],{"class":3028},[3011,14190,13291],{"class":3092},[3011,14192,3096],{"class":3028},[3011,14194,14195],{"class":3024},"vm",[3011,14197,4010],{"class":3028},[3011,14199,8444],{"class":3024},[3011,14201,3898],{"class":3028},[3011,14203,14204,14206,14209,14211,14213,14215,14218,14220,14222,14224,14226,14228,14230,14232,14235,14237,14239,14242,14245],{"class":3013,"line":3089},[3011,14205,13162],{"class":3017},[3011,14207,14208],{"class":3024}," errors",[3011,14210,3081],{"class":3028},[3011,14212,14195],{"class":3024},[3011,14214,4010],{"class":3028},[3011,14216,14217],{"class":3092},"GetErrors",[3011,14219,3096],{"class":3028},[3011,14221,3099],{"class":3017},[3011,14223,3096],{"class":3028},[3011,14225,14195],{"class":3024},[3011,14227,4010],{"class":3028},[3011,14229,6940],{"class":3024},[3011,14231,11736],{"class":3028},[3011,14233,14234],{"class":3092},"Cast",[3011,14236,4975],{"class":3028},[3011,14238,3272],{"class":3017},[3011,14240,14241],{"class":3028},">().",[3011,14243,14244],{"class":3092},"ToList",[3011,14246,4395],{"class":3028},[3011,14248,14249,14251,14253,14255,14257,14260,14262,14264,14266,14268,14270,14272,14274,14276,14278,14281],{"class":3013,"line":3110},[3011,14250,13286],{"class":3024},[3011,14252,4010],{"class":3028},[3011,14254,13291],{"class":3092},[3011,14256,3096],{"class":3028},[3011,14258,14259],{"class":3024},"errors",[3011,14261,4010],{"class":3028},[3011,14263,7989],{"class":3092},[3011,14265,3096],{"class":3028},[3011,14267,8066],{"class":3024},[3011,14269,3055],{"class":3028},[3011,14271,8066],{"class":3024},[3011,14273,4010],{"class":3028},[3011,14275,7247],{"class":3092},[3011,14277,3096],{"class":3028},[3011,14279,14280],{"class":3894},"\"обов'язковий\"",[3011,14282,6808],{"class":3028},[3011,14284,14285],{"class":3013,"line":3116},[3011,14286,3119],{"class":3028},[3011,14288,14289],{"class":3013,"line":3325},[3011,14290,3427],{"emptyLinePlaceholder":3426},[3011,14292,14293,14295,14297],{"class":3013,"line":3338},[3011,14294,8103],{"class":3028},[3011,14296,13139],{"class":3224},[3011,14298,13142],{"class":3028},[3011,14300,14301,14303,14305,14308],{"class":3013,"line":3348},[3011,14302,3035],{"class":3017},[3011,14304,3264],{"class":3017},[3011,14306,14307],{"class":3092}," Email_ShouldHaveError_WhenInvalidFormat",[3011,14309,7275],{"class":3028},[3011,14311,14312],{"class":3013,"line":3353},[3011,14313,3046],{"class":3028},[3011,14315,14316,14318,14320,14322,14324,14326],{"class":3013,"line":3366},[3011,14317,13162],{"class":3017},[3011,14319,11991],{"class":3024},[3011,14321,3081],{"class":3028},[3011,14323,3304],{"class":3017},[3011,14325,8479],{"class":3224},[3011,14327,4395],{"class":3028},[3011,14329,14330,14332,14334,14336,14338,14341],{"class":3013,"line":3372},[3011,14331,13194],{"class":3024},[3011,14333,4010],{"class":3028},[3011,14335,6940],{"class":3024},[3011,14337,3081],{"class":3028},[3011,14339,14340],{"class":3894},"\"invalid-email\"",[3011,14342,3029],{"class":3028},[3011,14344,14345],{"class":3013,"line":3378},[3011,14346,3256],{"class":3028},[3011,14348,14349,14351,14353,14355,14357,14359,14361,14363],{"class":3013,"line":3390},[3011,14350,13286],{"class":3024},[3011,14352,4010],{"class":3028},[3011,14354,13291],{"class":3092},[3011,14356,3096],{"class":3028},[3011,14358,14195],{"class":3024},[3011,14360,4010],{"class":3028},[3011,14362,8444],{"class":3024},[3011,14364,3898],{"class":3028},[3011,14366,14367,14369,14371,14373,14375,14377,14379,14381,14383,14385,14387,14389,14391,14393,14395,14397,14399,14401,14403],{"class":3013,"line":3407},[3011,14368,13162],{"class":3017},[3011,14370,14208],{"class":3024},[3011,14372,3081],{"class":3028},[3011,14374,14195],{"class":3024},[3011,14376,4010],{"class":3028},[3011,14378,14217],{"class":3092},[3011,14380,3096],{"class":3028},[3011,14382,3099],{"class":3017},[3011,14384,3096],{"class":3028},[3011,14386,14195],{"class":3024},[3011,14388,4010],{"class":3028},[3011,14390,6940],{"class":3024},[3011,14392,11736],{"class":3028},[3011,14394,14234],{"class":3092},[3011,14396,4975],{"class":3028},[3011,14398,3272],{"class":3017},[3011,14400,14241],{"class":3028},[3011,14402,14244],{"class":3092},[3011,14404,4395],{"class":3028},[3011,14406,14407,14409,14411,14413,14415,14417,14419,14421,14423,14425,14427,14429,14431,14433,14435,14438],{"class":3013,"line":3413},[3011,14408,13286],{"class":3024},[3011,14410,4010],{"class":3028},[3011,14412,13291],{"class":3092},[3011,14414,3096],{"class":3028},[3011,14416,14259],{"class":3024},[3011,14418,4010],{"class":3028},[3011,14420,7989],{"class":3092},[3011,14422,3096],{"class":3028},[3011,14424,8066],{"class":3024},[3011,14426,3055],{"class":3028},[3011,14428,8066],{"class":3024},[3011,14430,4010],{"class":3028},[3011,14432,7247],{"class":3092},[3011,14434,3096],{"class":3028},[3011,14436,14437],{"class":3894},"\"Некоректний формат\"",[3011,14439,6808],{"class":3028},[3011,14441,14442],{"class":3013,"line":3418},[3011,14443,3119],{"class":3028},[3011,14445,14446],{"class":3013,"line":3423},[3011,14447,3427],{"emptyLinePlaceholder":3426},[3011,14449,14450,14452,14454],{"class":3013,"line":3430},[3011,14451,8103],{"class":3028},[3011,14453,13139],{"class":3224},[3011,14455,13142],{"class":3028},[3011,14457,14458,14460,14462,14465],{"class":3013,"line":3436},[3011,14459,3035],{"class":3017},[3011,14461,3264],{"class":3017},[3011,14463,14464],{"class":3092}," ConfirmPassword_ShouldHaveError_WhenNotMatchingPassword",[3011,14466,7275],{"class":3028},[3011,14468,14469],{"class":3013,"line":3450},[3011,14470,3046],{"class":3028},[3011,14472,14473,14475,14477,14479,14481,14483],{"class":3013,"line":3455},[3011,14474,13162],{"class":3017},[3011,14476,11991],{"class":3024},[3011,14478,3081],{"class":3028},[3011,14480,3304],{"class":3017},[3011,14482,8479],{"class":3224},[3011,14484,4395],{"class":3028},[3011,14486,14487,14489,14491,14493,14495,14498],{"class":3013,"line":3461},[3011,14488,13194],{"class":3024},[3011,14490,4010],{"class":3028},[3011,14492,8984],{"class":3024},[3011,14494,3081],{"class":3028},[3011,14496,14497],{"class":3894},"\"Password123\"",[3011,14499,3029],{"class":3028},[3011,14501,14502,14504,14506,14508,14510,14513],{"class":3013,"line":3474},[3011,14503,13194],{"class":3024},[3011,14505,4010],{"class":3028},[3011,14507,9203],{"class":3024},[3011,14509,3081],{"class":3028},[3011,14511,14512],{"class":3894},"\"DifferentPassword\"",[3011,14514,3029],{"class":3028},[3011,14516,14517],{"class":3013,"line":3479},[3011,14518,3256],{"class":3028},[3011,14520,14521,14523,14525,14527,14529,14531,14533,14535],{"class":3013,"line":3496},[3011,14522,13286],{"class":3024},[3011,14524,4010],{"class":3028},[3011,14526,13291],{"class":3092},[3011,14528,3096],{"class":3028},[3011,14530,14195],{"class":3024},[3011,14532,4010],{"class":3028},[3011,14534,8444],{"class":3024},[3011,14536,3898],{"class":3028},[3011,14538,14539,14541,14543,14545,14547,14549,14551,14553,14555,14557,14559,14561,14563,14565,14567,14569,14571,14573,14575],{"class":3013,"line":3501},[3011,14540,13162],{"class":3017},[3011,14542,14208],{"class":3024},[3011,14544,3081],{"class":3028},[3011,14546,14195],{"class":3024},[3011,14548,4010],{"class":3028},[3011,14550,14217],{"class":3092},[3011,14552,3096],{"class":3028},[3011,14554,3099],{"class":3017},[3011,14556,3096],{"class":3028},[3011,14558,14195],{"class":3024},[3011,14560,4010],{"class":3028},[3011,14562,9203],{"class":3024},[3011,14564,11736],{"class":3028},[3011,14566,14234],{"class":3092},[3011,14568,4975],{"class":3028},[3011,14570,3272],{"class":3017},[3011,14572,14241],{"class":3028},[3011,14574,14244],{"class":3092},[3011,14576,4395],{"class":3028},[3011,14578,14579,14581,14583,14585,14587,14589,14591,14593,14595,14597,14599,14601,14603,14605,14607,14610],{"class":3013,"line":3526},[3011,14580,13286],{"class":3024},[3011,14582,4010],{"class":3028},[3011,14584,13291],{"class":3092},[3011,14586,3096],{"class":3028},[3011,14588,14259],{"class":3024},[3011,14590,4010],{"class":3028},[3011,14592,7989],{"class":3092},[3011,14594,3096],{"class":3028},[3011,14596,8066],{"class":3024},[3011,14598,3055],{"class":3028},[3011,14600,8066],{"class":3024},[3011,14602,4010],{"class":3028},[3011,14604,7247],{"class":3092},[3011,14606,3096],{"class":3028},[3011,14608,14609],{"class":3894},"\"не співпадають\"",[3011,14611,6808],{"class":3028},[3011,14613,14614],{"class":3013,"line":3531},[3011,14615,3119],{"class":3028},[3181,14617],{},[3194,14619,14621],{"id":14620},"рівень-3-обчислювані-властивості-та-designtime-дані","Рівень 3: Обчислювані властивості та DesignTime дані",[2965,14623,14624,14626],{},[2975,14625,4114],{}," Реалізувати складні залежності між властивостями та додати DesignTime дані для дизайнера.",[2965,14628,14629],{},[2975,14630,12846],{},[2965,14632,12849,14633,14636],{},[2989,14634,14635],{},"ProductViewModel"," для інтернет-магазину:",[4278,14638,14639,14684,14710,14735],{},[2985,14640,14641,14644],{},[2975,14642,14643],{},"Властивості:",[2982,14645,14646,14652,14658,14664,14670,14679],{},[2985,14647,14648,14651],{},[2989,14649,14650],{},"Name"," (string) — назва товару",[2985,14653,14654,14657],{},[2989,14655,14656],{},"Price"," (decimal) — ціна за одиницю",[2985,14659,14660,14663],{},[2989,14661,14662],{},"Quantity"," (int) — кількість",[2985,14665,14666,14669],{},[2989,14667,14668],{},"Discount"," (decimal) — знижка у відсотках (0-100)",[2985,14671,14672,14675,14676],{},[2989,14673,14674],{},"TotalPrice"," (обчислювана) — ",[2989,14677,14678],{},"Price * Quantity * (1 - Discount/100)",[2985,14680,14681,14683],{},[2989,14682,6933],{}," (обчислювана) — чи всі поля заповнені коректно",[2985,14685,14686,14689],{},[2975,14687,14688],{},"Валідація:",[2982,14690,14691,14696,14701,14705],{},[2985,14692,14693,14695],{},[2989,14694,14650],{}," — обов'язковий, мінімум 3 символи",[2985,14697,14698,14700],{},[2989,14699,14656],{}," — більше 0",[2985,14702,14703,14700],{},[2989,14704,14662],{},[2985,14706,14707,14709],{},[2989,14708,14668],{}," — від 0 до 100",[2985,14711,14712,14715],{},[2975,14713,14714],{},"Залежності:",[2982,14716,14717,14730],{},[2985,14718,14719,14720,3301,14722,14724,14725,14727,14728],{},"При зміні ",[2989,14721,14656],{},[2989,14723,14662],{}," або ",[2989,14726,14668],{}," → оновити ",[2989,14729,14674],{},[2985,14731,14732,14733],{},"При зміні будь-якої властивості → оновити ",[2989,14734,6933],{},[2985,14736,14737,14740],{},[2975,14738,14739],{},"DesignTime дані:",[2982,14741,14742,14748],{},[2985,14743,13485,14744,14747],{},[2989,14745,14746],{},"DesignProductViewModel"," з тестовими даними",[2985,14749,14750],{},"XAML має показувати дані у дизайнері",[2965,14752,14753],{},[2975,14754,12891],{},[2982,14756,14757,14760,14763,14766],{},[2985,14758,14759],{},"Всі обчислювані властивості оновлюються автоматично",[2985,14761,14762],{},"Валідація працює для всіх полів",[2985,14764,14765],{},"DesignTime дані видно у дизайнері Visual Studio",[2985,14767,13539],{},[2965,14769,14770],{},[2975,14771,14772],{},"Підказка для ProductViewModel:",[3002,14774,14776],{"className":3004,"code":14775,"language":3006,"meta":3007,"style":3007},"public class ProductViewModel : BaseViewModel\n{\n    private string _name;\n    public string Name\n    {\n        get => _name;\n        set\n        {\n            if (SetProperty(ref _name, value))\n            {\n                ValidateName();\n                OnPropertyChanged(nameof(IsValid));\n            }\n        }\n    }\n    \n    private decimal _price;\n    public decimal Price\n    {\n        get => _price;\n        set\n        {\n            if (SetProperty(ref _price, value))\n            {\n                ValidatePrice();\n                OnPropertyChanged(nameof(TotalPrice));\n                OnPropertyChanged(nameof(IsValid));\n            }\n        }\n    }\n    \n    private int _quantity;\n    public int Quantity\n    {\n        get => _quantity;\n        set\n        {\n            if (SetProperty(ref _quantity, value))\n            {\n                ValidateQuantity();\n                OnPropertyChanged(nameof(TotalPrice));\n                OnPropertyChanged(nameof(IsValid));\n            }\n        }\n    }\n    \n    private decimal _discount;\n    public decimal Discount\n    {\n        get => _discount;\n        set\n        {\n            if (SetProperty(ref _discount, value))\n            {\n                ValidateDiscount();\n                OnPropertyChanged(nameof(TotalPrice));\n                OnPropertyChanged(nameof(IsValid));\n            }\n        }\n    }\n    \n    // Обчислювана властивість\n    public decimal TotalPrice\n    {\n        get\n        {\n            if (Price \u003C= 0 || Quantity \u003C= 0)\n                return 0;\n            \n            return Price * Quantity * (1 - Discount / 100);\n        }\n    }\n    \n    // Обчислювана властивість\n    public bool IsValid =>\n        !HasErrors &&\n        !string.IsNullOrWhiteSpace(Name) &&\n        Price > 0 &&\n        Quantity > 0 &&\n        Discount >= 0 && Discount \u003C= 100;\n    \n    // TODO: Додати методи валідації\n}\n",[2989,14777,14778,14791,14795,14806,14815,14819,14830,14834,14838,14858,14862,14869,14884,14888,14892,14896,14900,14912,14921,14925,14936,14940,14944,14964,14968,14975,14989,15003,15007,15011,15015,15019,15030,15039,15043,15054,15058,15062,15082,15086,15093,15107,15121,15125,15129,15133,15137,15148,15157,15161,15172,15176,15180,15200,15204,15211,15225,15239,15243,15247,15251,15255,15259,15268,15272,15277,15281,15305,15315,15319,15350,15354,15358,15362,15366,15376,15385,15401,15413,15424,15445,15449,15454],{"__ignoreMap":3007},[3011,14779,14780,14782,14784,14787,14789],{"class":3013,"line":3014},[3011,14781,3035],{"class":3017},[3011,14783,3221],{"class":3017},[3011,14785,14786],{"class":3224}," ProductViewModel",[3011,14788,3228],{"class":3028},[3011,14790,4329],{"class":3224},[3011,14792,14793],{"class":3013,"line":3032},[3011,14794,3046],{"class":3028},[3011,14796,14797,14799,14801,14804],{"class":3013,"line":3043},[3011,14798,3328],{"class":3017},[3011,14800,3021],{"class":3017},[3011,14802,14803],{"class":3024}," _name",[3011,14805,3029],{"class":3028},[3011,14807,14808,14810,14812],{"class":3013,"line":3049},[3011,14809,3240],{"class":3017},[3011,14811,3021],{"class":3017},[3011,14813,14814],{"class":3024}," Name\n",[3011,14816,14817],{"class":3013,"line":3063},[3011,14818,3072],{"class":3028},[3011,14820,14821,14823,14825,14828],{"class":3013,"line":3069},[3011,14822,3356],{"class":3017},[3011,14824,3055],{"class":3028},[3011,14826,14827],{"class":3024},"_name",[3011,14829,3029],{"class":3028},[3011,14831,14832],{"class":3013,"line":3075},[3011,14833,3369],{"class":3017},[3011,14835,14836],{"class":3013,"line":3089},[3011,14837,3375],{"class":3028},[3011,14839,14840,14842,14844,14846,14848,14850,14852,14854,14856],{"class":3013,"line":3110},[3011,14841,8532],{"class":4125},[3011,14843,5031],{"class":3028},[3011,14845,4758],{"class":3092},[3011,14847,3096],{"class":3028},[3011,14849,4984],{"class":3017},[3011,14851,14803],{"class":3024},[3011,14853,3301],{"class":3028},[3011,14855,3084],{"class":3024},[3011,14857,5061],{"class":3028},[3011,14859,14860],{"class":3013,"line":3116},[3011,14861,8553],{"class":3028},[3011,14863,14864,14867],{"class":3013,"line":3325},[3011,14865,14866],{"class":3092},"                ValidateName",[3011,14868,4395],{"class":3028},[3011,14870,14871,14874,14876,14878,14880,14882],{"class":3013,"line":3338},[3011,14872,14873],{"class":3092},"                OnPropertyChanged",[3011,14875,3096],{"class":3028},[3011,14877,3099],{"class":3017},[3011,14879,3096],{"class":3028},[3011,14881,6933],{"class":3024},[3011,14883,3107],{"class":3028},[3011,14885,14886],{"class":3013,"line":3348},[3011,14887,8565],{"class":3028},[3011,14889,14890],{"class":3013,"line":3353},[3011,14891,3410],{"class":3028},[3011,14893,14894],{"class":3013,"line":3366},[3011,14895,3113],{"class":3028},[3011,14897,14898],{"class":3013,"line":3372},[3011,14899,3256],{"class":3028},[3011,14901,14902,14904,14907,14910],{"class":3013,"line":3378},[3011,14903,3328],{"class":3017},[3011,14905,14906],{"class":3017}," decimal",[3011,14908,14909],{"class":3024}," _price",[3011,14911,3029],{"class":3028},[3011,14913,14914,14916,14918],{"class":3013,"line":3390},[3011,14915,3240],{"class":3017},[3011,14917,14906],{"class":3017},[3011,14919,14920],{"class":3024}," Price\n",[3011,14922,14923],{"class":3013,"line":3407},[3011,14924,3072],{"class":3028},[3011,14926,14927,14929,14931,14934],{"class":3013,"line":3413},[3011,14928,3356],{"class":3017},[3011,14930,3055],{"class":3028},[3011,14932,14933],{"class":3024},"_price",[3011,14935,3029],{"class":3028},[3011,14937,14938],{"class":3013,"line":3418},[3011,14939,3369],{"class":3017},[3011,14941,14942],{"class":3013,"line":3423},[3011,14943,3375],{"class":3028},[3011,14945,14946,14948,14950,14952,14954,14956,14958,14960,14962],{"class":3013,"line":3430},[3011,14947,8532],{"class":4125},[3011,14949,5031],{"class":3028},[3011,14951,4758],{"class":3092},[3011,14953,3096],{"class":3028},[3011,14955,4984],{"class":3017},[3011,14957,14909],{"class":3024},[3011,14959,3301],{"class":3028},[3011,14961,3084],{"class":3024},[3011,14963,5061],{"class":3028},[3011,14965,14966],{"class":3013,"line":3436},[3011,14967,8553],{"class":3028},[3011,14969,14970,14973],{"class":3013,"line":3450},[3011,14971,14972],{"class":3092},"                ValidatePrice",[3011,14974,4395],{"class":3028},[3011,14976,14977,14979,14981,14983,14985,14987],{"class":3013,"line":3455},[3011,14978,14873],{"class":3092},[3011,14980,3096],{"class":3028},[3011,14982,3099],{"class":3017},[3011,14984,3096],{"class":3028},[3011,14986,14674],{"class":3024},[3011,14988,3107],{"class":3028},[3011,14990,14991,14993,14995,14997,14999,15001],{"class":3013,"line":3461},[3011,14992,14873],{"class":3092},[3011,14994,3096],{"class":3028},[3011,14996,3099],{"class":3017},[3011,14998,3096],{"class":3028},[3011,15000,6933],{"class":3024},[3011,15002,3107],{"class":3028},[3011,15004,15005],{"class":3013,"line":3474},[3011,15006,8565],{"class":3028},[3011,15008,15009],{"class":3013,"line":3479},[3011,15010,3410],{"class":3028},[3011,15012,15013],{"class":3013,"line":3496},[3011,15014,3113],{"class":3028},[3011,15016,15017],{"class":3013,"line":3501},[3011,15018,3256],{"class":3028},[3011,15020,15021,15023,15025,15028],{"class":3013,"line":3526},[3011,15022,3328],{"class":3017},[3011,15024,5358],{"class":3017},[3011,15026,15027],{"class":3024}," _quantity",[3011,15029,3029],{"class":3028},[3011,15031,15032,15034,15036],{"class":3013,"line":3531},[3011,15033,3240],{"class":3017},[3011,15035,5358],{"class":3017},[3011,15037,15038],{"class":3024}," Quantity\n",[3011,15040,15041],{"class":3013,"line":3536},[3011,15042,3072],{"class":3028},[3011,15044,15045,15047,15049,15052],{"class":3013,"line":3548},[3011,15046,3356],{"class":3017},[3011,15048,3055],{"class":3028},[3011,15050,15051],{"class":3024},"_quantity",[3011,15053,3029],{"class":3028},[3011,15055,15056],{"class":3013,"line":3558},[3011,15057,3369],{"class":3017},[3011,15059,15060],{"class":3013,"line":3563},[3011,15061,3375],{"class":3028},[3011,15063,15064,15066,15068,15070,15072,15074,15076,15078,15080],{"class":3013,"line":3575},[3011,15065,8532],{"class":4125},[3011,15067,5031],{"class":3028},[3011,15069,4758],{"class":3092},[3011,15071,3096],{"class":3028},[3011,15073,4984],{"class":3017},[3011,15075,15027],{"class":3024},[3011,15077,3301],{"class":3028},[3011,15079,3084],{"class":3024},[3011,15081,5061],{"class":3028},[3011,15083,15084],{"class":3013,"line":3580},[3011,15085,8553],{"class":3028},[3011,15087,15088,15091],{"class":3013,"line":3585},[3011,15089,15090],{"class":3092},"                ValidateQuantity",[3011,15092,4395],{"class":3028},[3011,15094,15095,15097,15099,15101,15103,15105],{"class":3013,"line":3597},[3011,15096,14873],{"class":3092},[3011,15098,3096],{"class":3028},[3011,15100,3099],{"class":3017},[3011,15102,3096],{"class":3028},[3011,15104,14674],{"class":3024},[3011,15106,3107],{"class":3028},[3011,15108,15109,15111,15113,15115,15117,15119],{"class":3013,"line":3613},[3011,15110,14873],{"class":3092},[3011,15112,3096],{"class":3028},[3011,15114,3099],{"class":3017},[3011,15116,3096],{"class":3028},[3011,15118,6933],{"class":3024},[3011,15120,3107],{"class":3028},[3011,15122,15123],{"class":3013,"line":3618},[3011,15124,8565],{"class":3028},[3011,15126,15127],{"class":3013,"line":3623},[3011,15128,3410],{"class":3028},[3011,15130,15131],{"class":3013,"line":3628},[3011,15132,3113],{"class":3028},[3011,15134,15135],{"class":3013,"line":3633},[3011,15136,3256],{"class":3028},[3011,15138,15139,15141,15143,15146],{"class":3013,"line":3639},[3011,15140,3328],{"class":3017},[3011,15142,14906],{"class":3017},[3011,15144,15145],{"class":3024}," _discount",[3011,15147,3029],{"class":3028},[3011,15149,15150,15152,15154],{"class":3013,"line":3653},[3011,15151,3240],{"class":3017},[3011,15153,14906],{"class":3017},[3011,15155,15156],{"class":3024}," Discount\n",[3011,15158,15159],{"class":3013,"line":3658},[3011,15160,3072],{"class":3028},[3011,15162,15163,15165,15167,15170],{"class":3013,"line":3664},[3011,15164,3356],{"class":3017},[3011,15166,3055],{"class":3028},[3011,15168,15169],{"class":3024},"_discount",[3011,15171,3029],{"class":3028},[3011,15173,15174],{"class":3013,"line":3677},[3011,15175,3369],{"class":3017},[3011,15177,15178],{"class":3013,"line":3682},[3011,15179,3375],{"class":3028},[3011,15181,15182,15184,15186,15188,15190,15192,15194,15196,15198],{"class":3013,"line":3699},[3011,15183,8532],{"class":4125},[3011,15185,5031],{"class":3028},[3011,15187,4758],{"class":3092},[3011,15189,3096],{"class":3028},[3011,15191,4984],{"class":3017},[3011,15193,15145],{"class":3024},[3011,15195,3301],{"class":3028},[3011,15197,3084],{"class":3024},[3011,15199,5061],{"class":3028},[3011,15201,15202],{"class":3013,"line":3704},[3011,15203,8553],{"class":3028},[3011,15205,15206,15209],{"class":3013,"line":3729},[3011,15207,15208],{"class":3092},"                ValidateDiscount",[3011,15210,4395],{"class":3028},[3011,15212,15213,15215,15217,15219,15221,15223],{"class":3013,"line":3734},[3011,15214,14873],{"class":3092},[3011,15216,3096],{"class":3028},[3011,15218,3099],{"class":3017},[3011,15220,3096],{"class":3028},[3011,15222,14674],{"class":3024},[3011,15224,3107],{"class":3028},[3011,15226,15227,15229,15231,15233,15235,15237],{"class":3013,"line":3739},[3011,15228,14873],{"class":3092},[3011,15230,3096],{"class":3028},[3011,15232,3099],{"class":3017},[3011,15234,3096],{"class":3028},[3011,15236,6933],{"class":3024},[3011,15238,3107],{"class":3028},[3011,15240,15241],{"class":3013,"line":3745},[3011,15242,8565],{"class":3028},[3011,15244,15245],{"class":3013,"line":8277},[3011,15246,3410],{"class":3028},[3011,15248,15249],{"class":3013,"line":8282},[3011,15250,3113],{"class":3028},[3011,15252,15253],{"class":3013,"line":8288},[3011,15254,3256],{"class":3028},[3011,15256,15257],{"class":3013,"line":8306},[3011,15258,6095],{"class":3213},[3011,15260,15261,15263,15265],{"class":3013,"line":8311},[3011,15262,3240],{"class":3017},[3011,15264,14906],{"class":3017},[3011,15266,15267],{"class":3024}," TotalPrice\n",[3011,15269,15270],{"class":3013,"line":8330},[3011,15271,3072],{"class":3028},[3011,15273,15274],{"class":3013,"line":8335},[3011,15275,15276],{"class":3017},"        get\n",[3011,15278,15279],{"class":3013,"line":8351},[3011,15280,3375],{"class":3028},[3011,15282,15283,15285,15287,15289,15292,15294,15297,15299,15301,15303],{"class":3013,"line":8362},[3011,15284,8532],{"class":4125},[3011,15286,5031],{"class":3028},[3011,15288,14656],{"class":3024},[3011,15290,15291],{"class":3028}," \u003C= ",[3011,15293,13364],{"class":8913},[3011,15295,15296],{"class":3028}," || ",[3011,15298,14662],{"class":3024},[3011,15300,15291],{"class":3028},[3011,15302,13364],{"class":8913},[3011,15304,3278],{"class":3028},[3011,15306,15307,15310,15313],{"class":3013,"line":8367},[3011,15308,15309],{"class":4125},"                return",[3011,15311,15312],{"class":8913}," 0",[3011,15314,3029],{"class":3028},[3011,15316,15317],{"class":3013,"line":8372},[3011,15318,9688],{"class":3028},[3011,15320,15321,15323,15326,15329,15331,15334,15337,15340,15342,15345,15348],{"class":3013,"line":8377},[3011,15322,5070],{"class":4125},[3011,15324,15325],{"class":3024}," Price",[3011,15327,15328],{"class":3028}," * ",[3011,15330,14662],{"class":3024},[3011,15332,15333],{"class":3028}," * (",[3011,15335,15336],{"class":8913},"1",[3011,15338,15339],{"class":3028}," - ",[3011,15341,14668],{"class":3024},[3011,15343,15344],{"class":3028}," / ",[3011,15346,15347],{"class":8913},"100",[3011,15349,3898],{"class":3028},[3011,15351,15352],{"class":3013,"line":8383},[3011,15353,3410],{"class":3028},[3011,15355,15356],{"class":3013,"line":8401},[3011,15357,3113],{"class":3028},[3011,15359,15360],{"class":3013,"line":8406},[3011,15361,3256],{"class":3028},[3011,15363,15364],{"class":3013,"line":8433},[3011,15365,6095],{"class":3213},[3011,15367,15368,15370,15372,15374],{"class":3013,"line":8449},[3011,15369,3240],{"class":3017},[3011,15371,4969],{"class":3017},[3011,15373,7183],{"class":3024},[3011,15375,7186],{"class":3028},[3011,15377,15378,15380,15382],{"class":3013,"line":8454},[3011,15379,7191],{"class":3028},[3011,15381,8444],{"class":3024},[3011,15383,15384],{"class":3028}," &&\n",[3011,15386,15387,15389,15391,15393,15395,15397,15399],{"class":3013,"line":9135},[3011,15388,7191],{"class":3028},[3011,15390,3272],{"class":3017},[3011,15392,4010],{"class":3028},[3011,15394,7198],{"class":3092},[3011,15396,3096],{"class":3028},[3011,15398,14650],{"class":3024},[3011,15400,7205],{"class":3028},[3011,15402,15403,15406,15409,15411],{"class":3013,"line":9140},[3011,15404,15405],{"class":3024},"        Price",[3011,15407,15408],{"class":3028}," > ",[3011,15410,13364],{"class":8913},[3011,15412,15384],{"class":3028},[3011,15414,15415,15418,15420,15422],{"class":3013,"line":9145},[3011,15416,15417],{"class":3024},"        Quantity",[3011,15419,15408],{"class":3028},[3011,15421,13364],{"class":8913},[3011,15423,15384],{"class":3028},[3011,15425,15426,15429,15432,15434,15437,15439,15441,15443],{"class":3013,"line":9151},[3011,15427,15428],{"class":3024},"        Discount",[3011,15430,15431],{"class":3028}," >= ",[3011,15433,13364],{"class":8913},[3011,15435,15436],{"class":3028}," && ",[3011,15438,14668],{"class":3024},[3011,15440,15291],{"class":3028},[3011,15442,15347],{"class":8913},[3011,15444,3029],{"class":3028},[3011,15446,15447],{"class":3013,"line":9159},[3011,15448,3256],{"class":3028},[3011,15450,15451],{"class":3013,"line":9164},[3011,15452,15453],{"class":3213},"    // TODO: Додати методи валідації\n",[3011,15455,15456],{"class":3013,"line":9169},[3011,15457,3119],{"class":3028},[2965,15459,15460],{},[2975,15461,15462],{},"Підказка для DesignTime:",[3002,15464,15466],{"className":3004,"code":15465,"language":3006,"meta":3007,"style":3007},"public class DesignProductViewModel : ProductViewModel\n{\n    public DesignProductViewModel()\n    {\n        Name = \"Ноутбук Lenovo ThinkPad\";\n        Price = 25000;\n        Quantity = 2;\n        Discount = 10;\n    }\n}\n",[2989,15467,15468,15482,15486,15494,15498,15510,15521,15532,15542,15546],{"__ignoreMap":3007},[3011,15469,15470,15472,15474,15477,15479],{"class":3013,"line":3014},[3011,15471,3035],{"class":3017},[3011,15473,3221],{"class":3017},[3011,15475,15476],{"class":3224}," DesignProductViewModel",[3011,15478,3228],{"class":3028},[3011,15480,15481],{"class":3224},"ProductViewModel\n",[3011,15483,15484],{"class":3013,"line":3032},[3011,15485,3046],{"class":3028},[3011,15487,15488,15490,15492],{"class":3013,"line":3043},[3011,15489,3240],{"class":3017},[3011,15491,15476],{"class":3092},[3011,15493,7275],{"class":3028},[3011,15495,15496],{"class":3013,"line":3049},[3011,15497,3072],{"class":3028},[3011,15499,15500,15503,15505,15508],{"class":3013,"line":3063},[3011,15501,15502],{"class":3024},"        Name",[3011,15504,3081],{"class":3028},[3011,15506,15507],{"class":3894},"\"Ноутбук Lenovo ThinkPad\"",[3011,15509,3029],{"class":3028},[3011,15511,15512,15514,15516,15519],{"class":3013,"line":3069},[3011,15513,15405],{"class":3024},[3011,15515,3081],{"class":3028},[3011,15517,15518],{"class":8913},"25000",[3011,15520,3029],{"class":3028},[3011,15522,15523,15525,15527,15530],{"class":3013,"line":3075},[3011,15524,15417],{"class":3024},[3011,15526,3081],{"class":3028},[3011,15528,15529],{"class":8913},"2",[3011,15531,3029],{"class":3028},[3011,15533,15534,15536,15538,15540],{"class":3013,"line":3089},[3011,15535,15428],{"class":3024},[3011,15537,3081],{"class":3028},[3011,15539,5662],{"class":8913},[3011,15541,3029],{"class":3028},[3011,15543,15544],{"class":3013,"line":3110},[3011,15545,3113],{"class":3028},[3011,15547,15548],{"class":3013,"line":3116},[3011,15549,3119],{"class":3028},[2965,15551,15552],{},[2975,15553,15554],{},"XAML з DesignTime:",[3002,15556,15558],{"className":6138,"code":15557,"language":6140,"meta":3007,"style":3007},"\u003CWindow x:Class=\"MyApp.MainWindow\"\n        xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n        xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n        xmlns:vm=\"clr-namespace:MyApp.ViewModels\"\n        mc:Ignorable=\"d\"\n        d:DataContext=\"{d:DesignInstance Type=vm:DesignProductViewModel, IsDesignTimeCreatable=True}\">\n    \n    \u003CStackPanel Margin=\"20\">\n        \u003CTextBlock Text=\"Назва товару:\"/>\n        \u003CTextBox Text=\"{Binding Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}\"/>\n        \n        \u003CTextBlock Text=\"Ціна:\" Margin=\"0,10,0,0\"/>\n        \u003CTextBox Text=\"{Binding Price, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}\"/>\n        \n        \u003CTextBlock Text=\"Кількість:\" Margin=\"0,10,0,0\"/>\n        \u003CTextBox Text=\"{Binding Quantity, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}\"/>\n        \n        \u003CTextBlock Text=\"Знижка (%):\" Margin=\"0,10,0,0\"/>\n        \u003CTextBox Text=\"{Binding Discount, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}\"/>\n        \n        \u003CTextBlock Text=\"{Binding TotalPrice, StringFormat='Загальна сума: {0:C}'}\" \n                   FontSize=\"18\" \n                   FontWeight=\"Bold\" \n                   Margin=\"0,20,0,0\"/>\n        \n        \u003CButton Content=\"Додати до кошика\" \n                IsEnabled=\"{Binding IsValid}\" \n                Margin=\"0,20,0,0\"/>\n    \u003C/StackPanel>\n\u003C/Window>\n",[2989,15559,15560,15572,15580,15588,15596,15604,15615,15619,15633,15648,15663,15667,15688,15703,15707,15728,15743,15747,15768,15783,15787,15802,15814,15825,15836,15840,15855,15866,15877,15885],{"__ignoreMap":3007},[3011,15561,15562,15564,15566,15568,15570],{"class":3013,"line":3014},[3011,15563,4975],{"class":6147},[3011,15565,10464],{"class":6150},[3011,15567,10467],{"class":6154},[3011,15569,6158],{"class":3028},[3011,15571,10472],{"class":6161},[3011,15573,15574,15576,15578],{"class":3013,"line":3032},[3011,15575,10651],{"class":6154},[3011,15577,6158],{"class":3028},[3011,15579,10656],{"class":6161},[3011,15581,15582,15584,15586],{"class":3013,"line":3043},[3011,15583,10661],{"class":6154},[3011,15585,6158],{"class":3028},[3011,15587,10666],{"class":6161},[3011,15589,15590,15592,15594],{"class":3013,"line":3049},[3011,15591,10671],{"class":6154},[3011,15593,6158],{"class":3028},[3011,15595,10676],{"class":6161},[3011,15597,15598,15600,15602],{"class":3013,"line":3063},[3011,15599,10681],{"class":6154},[3011,15601,6158],{"class":3028},[3011,15603,10743],{"class":6161},[3011,15605,15606,15608,15610,15613],{"class":3013,"line":3069},[3011,15607,10748],{"class":6154},[3011,15609,6158],{"class":3028},[3011,15611,15612],{"class":6161},"\"{d:DesignInstance Type=vm:DesignProductViewModel, IsDesignTimeCreatable=True}\"",[3011,15614,9345],{"class":6147},[3011,15616,15617],{"class":3013,"line":3075},[3011,15618,3256],{"class":3028},[3011,15620,15621,15623,15625,15627,15629,15631],{"class":3013,"line":3089},[3011,15622,9355],{"class":6147},[3011,15624,9334],{"class":6150},[3011,15626,9337],{"class":6154},[3011,15628,6158],{"class":3028},[3011,15630,9342],{"class":6161},[3011,15632,9345],{"class":6147},[3011,15634,15635,15637,15639,15641,15643,15646],{"class":3013,"line":3110},[3011,15636,9620],{"class":6147},[3011,15638,6151],{"class":6150},[3011,15640,6155],{"class":6154},[3011,15642,6158],{"class":3028},[3011,15644,15645],{"class":6161},"\"Назва товару:\"",[3011,15647,6165],{"class":6147},[3011,15649,15650,15652,15654,15656,15658,15661],{"class":3013,"line":3116},[3011,15651,9620],{"class":6147},[3011,15653,9373],{"class":6150},[3011,15655,6155],{"class":6154},[3011,15657,6158],{"class":3028},[3011,15659,15660],{"class":6161},"\"{Binding Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}\"",[3011,15662,6165],{"class":6147},[3011,15664,15665],{"class":3013,"line":3325},[3011,15666,5088],{"class":3028},[3011,15668,15669,15671,15673,15675,15677,15680,15682,15684,15686],{"class":3013,"line":3338},[3011,15670,9620],{"class":6147},[3011,15672,6151],{"class":6150},[3011,15674,6155],{"class":6154},[3011,15676,6158],{"class":3028},[3011,15678,15679],{"class":6161},"\"Ціна:\"",[3011,15681,9337],{"class":6154},[3011,15683,6158],{"class":3028},[3011,15685,9411],{"class":6161},[3011,15687,6165],{"class":6147},[3011,15689,15690,15692,15694,15696,15698,15701],{"class":3013,"line":3348},[3011,15691,9620],{"class":6147},[3011,15693,9373],{"class":6150},[3011,15695,6155],{"class":6154},[3011,15697,6158],{"class":3028},[3011,15699,15700],{"class":6161},"\"{Binding Price, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}\"",[3011,15702,6165],{"class":6147},[3011,15704,15705],{"class":3013,"line":3353},[3011,15706,5088],{"class":3028},[3011,15708,15709,15711,15713,15715,15717,15720,15722,15724,15726],{"class":3013,"line":3366},[3011,15710,9620],{"class":6147},[3011,15712,6151],{"class":6150},[3011,15714,6155],{"class":6154},[3011,15716,6158],{"class":3028},[3011,15718,15719],{"class":6161},"\"Кількість:\"",[3011,15721,9337],{"class":6154},[3011,15723,6158],{"class":3028},[3011,15725,9411],{"class":6161},[3011,15727,6165],{"class":6147},[3011,15729,15730,15732,15734,15736,15738,15741],{"class":3013,"line":3372},[3011,15731,9620],{"class":6147},[3011,15733,9373],{"class":6150},[3011,15735,6155],{"class":6154},[3011,15737,6158],{"class":3028},[3011,15739,15740],{"class":6161},"\"{Binding Quantity, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}\"",[3011,15742,6165],{"class":6147},[3011,15744,15745],{"class":3013,"line":3378},[3011,15746,5088],{"class":3028},[3011,15748,15749,15751,15753,15755,15757,15760,15762,15764,15766],{"class":3013,"line":3390},[3011,15750,9620],{"class":6147},[3011,15752,6151],{"class":6150},[3011,15754,6155],{"class":6154},[3011,15756,6158],{"class":3028},[3011,15758,15759],{"class":6161},"\"Знижка (%):\"",[3011,15761,9337],{"class":6154},[3011,15763,6158],{"class":3028},[3011,15765,9411],{"class":6161},[3011,15767,6165],{"class":6147},[3011,15769,15770,15772,15774,15776,15778,15781],{"class":3013,"line":3407},[3011,15771,9620],{"class":6147},[3011,15773,9373],{"class":6150},[3011,15775,6155],{"class":6154},[3011,15777,6158],{"class":3028},[3011,15779,15780],{"class":6161},"\"{Binding Discount, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}\"",[3011,15782,6165],{"class":6147},[3011,15784,15785],{"class":3013,"line":3413},[3011,15786,5088],{"class":3028},[3011,15788,15789,15791,15793,15795,15797,15800],{"class":3013,"line":3418},[3011,15790,9620],{"class":6147},[3011,15792,6151],{"class":6150},[3011,15794,6155],{"class":6154},[3011,15796,6158],{"class":3028},[3011,15798,15799],{"class":6161},"\"{Binding TotalPrice, StringFormat='Загальна сума: {0:C}'}\"",[3011,15801,9500],{"class":3028},[3011,15803,15804,15807,15809,15812],{"class":3013,"line":3423},[3011,15805,15806],{"class":6154},"                   FontSize",[3011,15808,6158],{"class":3028},[3011,15810,15811],{"class":6161},"\"18\"",[3011,15813,9500],{"class":3028},[3011,15815,15816,15819,15821,15823],{"class":3013,"line":3430},[3011,15817,15818],{"class":6154},"                   FontWeight",[3011,15820,6158],{"class":3028},[3011,15822,9990],{"class":6161},[3011,15824,9500],{"class":3028},[3011,15826,15827,15830,15832,15834],{"class":3013,"line":3436},[3011,15828,15829],{"class":6154},"                   Margin",[3011,15831,6158],{"class":3028},[3011,15833,9520],{"class":6161},[3011,15835,6165],{"class":6147},[3011,15837,15838],{"class":3013,"line":3450},[3011,15839,5088],{"class":3028},[3011,15841,15842,15844,15846,15848,15850,15853],{"class":3013,"line":3455},[3011,15843,9620],{"class":6147},[3011,15845,7318],{"class":6150},[3011,15847,7321],{"class":6154},[3011,15849,6158],{"class":3028},[3011,15851,15852],{"class":6161},"\"Додати до кошика\"",[3011,15854,9500],{"class":3028},[3011,15856,15857,15860,15862,15864],{"class":3013,"line":3461},[3011,15858,15859],{"class":6154},"                IsEnabled",[3011,15861,6158],{"class":3028},[3011,15863,7334],{"class":6161},[3011,15865,9500],{"class":3028},[3011,15867,15868,15871,15873,15875],{"class":3013,"line":3474},[3011,15869,15870],{"class":6154},"                Margin",[3011,15872,6158],{"class":3028},[3011,15874,9520],{"class":6161},[3011,15876,6165],{"class":6147},[3011,15878,15879,15881,15883],{"class":3013,"line":3479},[3011,15880,9780],{"class":6147},[3011,15882,9334],{"class":6150},[3011,15884,9345],{"class":6147},[3011,15886,15887,15889,15891],{"class":3013,"line":3496},[3011,15888,9527],{"class":6147},[3011,15890,10464],{"class":6150},[3011,15892,9345],{"class":6147},[2965,15894,15895],{},[2975,15896,14121],{},[3002,15898,15900],{"className":3004,"code":15899,"language":3006,"meta":3007,"style":3007},"[Test]\npublic void TotalPrice_ShouldUpdate_WhenPriceChanges()\n{\n    var vm = new ProductViewModel { Price = 100, Quantity = 2, Discount = 0 };\n    \n    Assert.AreEqual(200, vm.TotalPrice);\n    \n    vm.Price = 150;\n    \n    Assert.AreEqual(300, vm.TotalPrice);\n}\n\n[Test]\npublic void TotalPrice_ShouldApplyDiscount()\n{\n    var vm = new ProductViewModel { Price = 100, Quantity = 2, Discount = 10 };\n    \n    // 100 * 2 * (1 - 10/100) = 180\n    Assert.AreEqual(180, vm.TotalPrice);\n}\n\n[Test]\npublic void IsValid_ShouldBeFalse_WhenNameIsEmpty()\n{\n    var vm = new ProductViewModel { Name = \"\", Price = 100, Quantity = 1, Discount = 0 };\n    \n    Assert.IsFalse(vm.IsValid);\n}\n\n[Test]\npublic void PropertyChanged_ShouldRaiseForTotalPrice_WhenQuantityChanges()\n{\n    var vm = new ProductViewModel { Price = 100, Quantity = 1, Discount = 0 };\n    bool eventRaised = false;\n    \n    vm.PropertyChanged += (s, e) =>\n    {\n        if (e.PropertyName == nameof(ProductViewModel.TotalPrice))\n            eventRaised = true;\n    };\n    \n    vm.Quantity = 2;\n    \n    Assert.IsTrue(eventRaised);\n}\n",[2989,15901,15902,15910,15921,15925,15963,15967,15989,15993,16008,16012,16035,16039,16043,16051,16062,16066,16104,16108,16113,16136,16140,16144,16152,16163,16167,16213,16217,16236,16240,16244,16252,16263,16267,16305,16317,16321,16339,16343,16369,16379,16383,16387,16401,16405,16419],{"__ignoreMap":3007},[3011,15903,15904,15906,15908],{"class":3013,"line":3014},[3011,15905,8103],{"class":3028},[3011,15907,13139],{"class":3224},[3011,15909,13142],{"class":3028},[3011,15911,15912,15914,15916,15919],{"class":3013,"line":3032},[3011,15913,3035],{"class":3017},[3011,15915,3264],{"class":3017},[3011,15917,15918],{"class":3092}," TotalPrice_ShouldUpdate_WhenPriceChanges",[3011,15920,7275],{"class":3028},[3011,15922,15923],{"class":3013,"line":3043},[3011,15924,3046],{"class":3028},[3011,15926,15927,15929,15931,15933,15935,15937,15939,15941,15943,15945,15947,15949,15951,15953,15955,15957,15959,15961],{"class":3013,"line":3049},[3011,15928,13162],{"class":3017},[3011,15930,11991],{"class":3024},[3011,15932,3081],{"class":3028},[3011,15934,3304],{"class":3017},[3011,15936,14786],{"class":3224},[3011,15938,7410],{"class":3028},[3011,15940,14656],{"class":3024},[3011,15942,3081],{"class":3028},[3011,15944,15347],{"class":8913},[3011,15946,3301],{"class":3028},[3011,15948,14662],{"class":3024},[3011,15950,3081],{"class":3028},[3011,15952,15529],{"class":8913},[3011,15954,3301],{"class":3028},[3011,15956,14668],{"class":3024},[3011,15958,3081],{"class":3028},[3011,15960,13364],{"class":8913},[3011,15962,11627],{"class":3028},[3011,15964,15965],{"class":3013,"line":3063},[3011,15966,3256],{"class":3028},[3011,15968,15969,15971,15973,15975,15977,15979,15981,15983,15985,15987],{"class":3013,"line":3069},[3011,15970,13286],{"class":3024},[3011,15972,4010],{"class":3028},[3011,15974,13429],{"class":3092},[3011,15976,3096],{"class":3028},[3011,15978,5665],{"class":8913},[3011,15980,3301],{"class":3028},[3011,15982,14195],{"class":3024},[3011,15984,4010],{"class":3028},[3011,15986,14674],{"class":3024},[3011,15988,3898],{"class":3028},[3011,15990,15991],{"class":3013,"line":3075},[3011,15992,3256],{"class":3028},[3011,15994,15995,15997,15999,16001,16003,16006],{"class":3013,"line":3089},[3011,15996,13194],{"class":3024},[3011,15998,4010],{"class":3028},[3011,16000,14656],{"class":3024},[3011,16002,3081],{"class":3028},[3011,16004,16005],{"class":8913},"150",[3011,16007,3029],{"class":3028},[3011,16009,16010],{"class":3013,"line":3110},[3011,16011,3256],{"class":3028},[3011,16013,16014,16016,16018,16020,16022,16025,16027,16029,16031,16033],{"class":3013,"line":3116},[3011,16015,13286],{"class":3024},[3011,16017,4010],{"class":3028},[3011,16019,13429],{"class":3092},[3011,16021,3096],{"class":3028},[3011,16023,16024],{"class":8913},"300",[3011,16026,3301],{"class":3028},[3011,16028,14195],{"class":3024},[3011,16030,4010],{"class":3028},[3011,16032,14674],{"class":3024},[3011,16034,3898],{"class":3028},[3011,16036,16037],{"class":3013,"line":3325},[3011,16038,3119],{"class":3028},[3011,16040,16041],{"class":3013,"line":3338},[3011,16042,3427],{"emptyLinePlaceholder":3426},[3011,16044,16045,16047,16049],{"class":3013,"line":3348},[3011,16046,8103],{"class":3028},[3011,16048,13139],{"class":3224},[3011,16050,13142],{"class":3028},[3011,16052,16053,16055,16057,16060],{"class":3013,"line":3353},[3011,16054,3035],{"class":3017},[3011,16056,3264],{"class":3017},[3011,16058,16059],{"class":3092}," TotalPrice_ShouldApplyDiscount",[3011,16061,7275],{"class":3028},[3011,16063,16064],{"class":3013,"line":3366},[3011,16065,3046],{"class":3028},[3011,16067,16068,16070,16072,16074,16076,16078,16080,16082,16084,16086,16088,16090,16092,16094,16096,16098,16100,16102],{"class":3013,"line":3372},[3011,16069,13162],{"class":3017},[3011,16071,11991],{"class":3024},[3011,16073,3081],{"class":3028},[3011,16075,3304],{"class":3017},[3011,16077,14786],{"class":3224},[3011,16079,7410],{"class":3028},[3011,16081,14656],{"class":3024},[3011,16083,3081],{"class":3028},[3011,16085,15347],{"class":8913},[3011,16087,3301],{"class":3028},[3011,16089,14662],{"class":3024},[3011,16091,3081],{"class":3028},[3011,16093,15529],{"class":8913},[3011,16095,3301],{"class":3028},[3011,16097,14668],{"class":3024},[3011,16099,3081],{"class":3028},[3011,16101,5662],{"class":8913},[3011,16103,11627],{"class":3028},[3011,16105,16106],{"class":3013,"line":3378},[3011,16107,3256],{"class":3028},[3011,16109,16110],{"class":3013,"line":3390},[3011,16111,16112],{"class":3213},"    // 100 * 2 * (1 - 10/100) = 180\n",[3011,16114,16115,16117,16119,16121,16123,16126,16128,16130,16132,16134],{"class":3013,"line":3407},[3011,16116,13286],{"class":3024},[3011,16118,4010],{"class":3028},[3011,16120,13429],{"class":3092},[3011,16122,3096],{"class":3028},[3011,16124,16125],{"class":8913},"180",[3011,16127,3301],{"class":3028},[3011,16129,14195],{"class":3024},[3011,16131,4010],{"class":3028},[3011,16133,14674],{"class":3024},[3011,16135,3898],{"class":3028},[3011,16137,16138],{"class":3013,"line":3413},[3011,16139,3119],{"class":3028},[3011,16141,16142],{"class":3013,"line":3418},[3011,16143,3427],{"emptyLinePlaceholder":3426},[3011,16145,16146,16148,16150],{"class":3013,"line":3423},[3011,16147,8103],{"class":3028},[3011,16149,13139],{"class":3224},[3011,16151,13142],{"class":3028},[3011,16153,16154,16156,16158,16161],{"class":3013,"line":3430},[3011,16155,3035],{"class":3017},[3011,16157,3264],{"class":3017},[3011,16159,16160],{"class":3092}," IsValid_ShouldBeFalse_WhenNameIsEmpty",[3011,16162,7275],{"class":3028},[3011,16164,16165],{"class":3013,"line":3436},[3011,16166,3046],{"class":3028},[3011,16168,16169,16171,16173,16175,16177,16179,16181,16183,16185,16187,16189,16191,16193,16195,16197,16199,16201,16203,16205,16207,16209,16211],{"class":3013,"line":3450},[3011,16170,13162],{"class":3017},[3011,16172,11991],{"class":3024},[3011,16174,3081],{"class":3028},[3011,16176,3304],{"class":3017},[3011,16178,14786],{"class":3224},[3011,16180,7410],{"class":3028},[3011,16182,14650],{"class":3024},[3011,16184,3081],{"class":3028},[3011,16186,14176],{"class":3894},[3011,16188,3301],{"class":3028},[3011,16190,14656],{"class":3024},[3011,16192,3081],{"class":3028},[3011,16194,15347],{"class":8913},[3011,16196,3301],{"class":3028},[3011,16198,14662],{"class":3024},[3011,16200,3081],{"class":3028},[3011,16202,15336],{"class":8913},[3011,16204,3301],{"class":3028},[3011,16206,14668],{"class":3024},[3011,16208,3081],{"class":3028},[3011,16210,13364],{"class":8913},[3011,16212,11627],{"class":3028},[3011,16214,16215],{"class":3013,"line":3455},[3011,16216,3256],{"class":3028},[3011,16218,16219,16221,16223,16226,16228,16230,16232,16234],{"class":3013,"line":3461},[3011,16220,13286],{"class":3024},[3011,16222,4010],{"class":3028},[3011,16224,16225],{"class":3092},"IsFalse",[3011,16227,3096],{"class":3028},[3011,16229,14195],{"class":3024},[3011,16231,4010],{"class":3028},[3011,16233,6933],{"class":3024},[3011,16235,3898],{"class":3028},[3011,16237,16238],{"class":3013,"line":3474},[3011,16239,3119],{"class":3028},[3011,16241,16242],{"class":3013,"line":3479},[3011,16243,3427],{"emptyLinePlaceholder":3426},[3011,16245,16246,16248,16250],{"class":3013,"line":3496},[3011,16247,8103],{"class":3028},[3011,16249,13139],{"class":3224},[3011,16251,13142],{"class":3028},[3011,16253,16254,16256,16258,16261],{"class":3013,"line":3501},[3011,16255,3035],{"class":3017},[3011,16257,3264],{"class":3017},[3011,16259,16260],{"class":3092}," PropertyChanged_ShouldRaiseForTotalPrice_WhenQuantityChanges",[3011,16262,7275],{"class":3028},[3011,16264,16265],{"class":3013,"line":3526},[3011,16266,3046],{"class":3028},[3011,16268,16269,16271,16273,16275,16277,16279,16281,16283,16285,16287,16289,16291,16293,16295,16297,16299,16301,16303],{"class":3013,"line":3531},[3011,16270,13162],{"class":3017},[3011,16272,11991],{"class":3024},[3011,16274,3081],{"class":3028},[3011,16276,3304],{"class":3017},[3011,16278,14786],{"class":3224},[3011,16280,7410],{"class":3028},[3011,16282,14656],{"class":3024},[3011,16284,3081],{"class":3028},[3011,16286,15347],{"class":8913},[3011,16288,3301],{"class":3028},[3011,16290,14662],{"class":3024},[3011,16292,3081],{"class":3028},[3011,16294,15336],{"class":8913},[3011,16296,3301],{"class":3028},[3011,16298,14668],{"class":3024},[3011,16300,3081],{"class":3028},[3011,16302,13364],{"class":8913},[3011,16304,11627],{"class":3028},[3011,16306,16307,16309,16311,16313,16315],{"class":3013,"line":3536},[3011,16308,7404],{"class":3017},[3011,16310,13179],{"class":3024},[3011,16312,3081],{"class":3028},[3011,16314,5194],{"class":3017},[3011,16316,3029],{"class":3028},[3011,16318,16319],{"class":3013,"line":3548},[3011,16320,3256],{"class":3028},[3011,16322,16323,16325,16327,16329,16331,16333,16335,16337],{"class":3013,"line":3558},[3011,16324,13194],{"class":3024},[3011,16326,4010],{"class":3028},[3011,16328,5622],{"class":3024},[3011,16330,13201],{"class":3028},[3011,16332,13204],{"class":3024},[3011,16334,3301],{"class":3028},[3011,16336,8066],{"class":3024},[3011,16338,13211],{"class":3028},[3011,16340,16341],{"class":3013,"line":3563},[3011,16342,3072],{"class":3028},[3011,16344,16345,16347,16349,16351,16353,16355,16357,16359,16361,16363,16365,16367],{"class":3013,"line":3575},[3011,16346,5028],{"class":4125},[3011,16348,5031],{"class":3028},[3011,16350,8066],{"class":3024},[3011,16352,4010],{"class":3028},[3011,16354,13228],{"class":3024},[3011,16356,10413],{"class":3028},[3011,16358,3099],{"class":3017},[3011,16360,3096],{"class":3028},[3011,16362,14635],{"class":3024},[3011,16364,4010],{"class":3028},[3011,16366,14674],{"class":3024},[3011,16368,5061],{"class":3028},[3011,16370,16371,16373,16375,16377],{"class":3013,"line":3580},[3011,16372,13247],{"class":3024},[3011,16374,3081],{"class":3028},[3011,16376,5190],{"class":3017},[3011,16378,3029],{"class":3028},[3011,16380,16381],{"class":3013,"line":3585},[3011,16382,13258],{"class":3028},[3011,16384,16385],{"class":3013,"line":3597},[3011,16386,3256],{"class":3028},[3011,16388,16389,16391,16393,16395,16397,16399],{"class":3013,"line":3613},[3011,16390,13194],{"class":3024},[3011,16392,4010],{"class":3028},[3011,16394,14662],{"class":3024},[3011,16396,3081],{"class":3028},[3011,16398,15529],{"class":8913},[3011,16400,3029],{"class":3028},[3011,16402,16403],{"class":3013,"line":3618},[3011,16404,3256],{"class":3028},[3011,16406,16407,16409,16411,16413,16415,16417],{"class":3013,"line":3623},[3011,16408,13286],{"class":3024},[3011,16410,4010],{"class":3028},[3011,16412,13291],{"class":3092},[3011,16414,3096],{"class":3028},[3011,16416,13296],{"class":3024},[3011,16418,3898],{"class":3028},[3011,16420,16421],{"class":3013,"line":3628},[3011,16422,3119],{"class":3028},[3181,16424],{},[2960,16426,16428],{"id":16427},"підсумок","Підсумок",[2965,16430,16431],{},"ViewModel — це не просто клас з властивостями. Це інфраструктура, що забезпечує зв'язок між View та Model, валідацію, обчислювані властивості та DesignTime дані.",[2965,16433,16434],{},[2975,16435,16436],{},"Ключові висновки:",[11822,16438,16439,16446,16450,16455,16460,16464],{},[11825,16440,16442,16443],{"icon":943,"title":16441},"🏗️ BaseViewModel","Базовий клас з INotifyPropertyChanged та SetProperty",[4760,16444,16445],{}," усуває дублювання коду та централізує логіку.",[11825,16447,16449],{"icon":92,"title":16448},"⚡ SetProperty\u003CT>","Універсальний метод для встановлення властивостей з автоматичною перевіркою на зміну та викликом PropertyChanged.",[11825,16451,16454],{"icon":16452,"title":16453},"i-lucide-link","🔗 Обчислювані властивості","Властивості, що залежать від інших, оновлюються автоматично через OnPropertyChanged у setter залежних властивостей.",[11825,16456,16459],{"icon":16457,"title":16458},"i-lucide-check-circle","✅ Валідація","INotifyDataErrorInfo надає механізм валідації з автоматичним відображенням помилок у UI через Validation.ErrorTemplate.",[11825,16461,16463],{"icon":11832,"title":16462},"🎨 DesignTime дані","d:DataContext дозволяє бачити дані у дизайнері без запуску додатку. Окремі DesignTime ViewModel для різних станів.",[11825,16465,16468],{"icon":16466,"title":16467},"i-lucide-flask","🧪 Testability","BaseViewModel легко тестувати — всі методи публічні або protected, PropertyChanged можна перевірити через підписку на подію.",[2965,16470,16471],{},[2975,16472,16473],{},"Переваги правильної реалізації ViewModel:",[2982,16475,16476,16479,16482,16485,16488,16491],{},[2985,16477,16478],{},"✅ Менше boilerplate-коду (SetProperty замість 7 рядків на властивість)",[2985,16480,16481],{},"✅ Compile-time перевірка (CallerMemberName, nameof)",[2985,16483,16484],{},"✅ Централізована логіка (логування, tracking у одному місці)",[2985,16486,16487],{},"✅ Автоматична валідація з відображенням у UI",[2985,16489,16490],{},"✅ Обчислювані властивості з автоматичним оновленням",[2985,16492,16493],{},"✅ DesignTime дані для швидкого feedback",[2965,16495,16496],{},[2975,16497,6494],{},[2982,16499,16500,16503,16506],{},[2985,16501,16502],{},"⚠️ Більше коду на початку (BaseViewModel, валідація)",[2985,16504,16505],{},"⚠️ Потрібно пам'ятати про залежності між властивостями",[2985,16507,16508],{},"⚠️ Складність для простих форм (overkill для 2-3 полів)",[4428,16510,16511,16514],{},[2975,16512,16513],{},"Коли використовувати повну реалізацію:"," Для будь-якого додатку з формами, валідацією, складними залежностями. Для простих діалогів можна обійтися без валідації та DesignTime даних.",[2965,16516,16517],{},[2975,16518,16519],{},"Що далі?",[2982,16521,16522,16532,16544],{},[2985,16523,16524,5031,16527,16531],{},[2975,16525,16526],{},"Commands",[2969,16528,16530],{"href":16529},"24.commands","наступна стаття",") — ICommand, RelayCommand, AsyncRelayCommand для прив'язки дій до кнопок",[2985,16533,16534,16537,16538,3301,16541,11711],{},[2975,16535,16536],{},"MVVM Toolkit"," (стаття 25) — автоматизація boilerplate через Source Generators (",[3011,16539,16540],{},"ObservableProperty",[3011,16542,16543],{},"RelayCommand",[2985,16545,16546,16549],{},[2975,16547,16548],{},"Messenger Pattern"," (стаття 26) — комунікація між ViewModel без прямих посилань",[3181,16551],{},[2960,16553,16555],{"id":16554},"словник-термінів","Словник термінів",[3167,16557,16559,16564,16571,16576,16587,16599,16604,16609,16615,16620,16626,16633],{"title":16558},"📚 Глосарій",[2965,16560,16561,16563],{},[2975,16562,3164],{}," — абстрактний базовий клас, що реалізує INotifyPropertyChanged та надає інфраструктуру для всіх ViewModel.",[2965,16565,16566,16570],{},[2975,16567,4758,16568],{},[4760,16569],{}," — універсальний метод для встановлення значення властивості з автоматичною перевіркою на зміну та викликом PropertyChanged.",[2965,16572,16573,16575],{},[2975,16574,4221],{}," — атрибут, що дозволяє компілятору автоматично підставляти ім'я методу/властивості, що викликала метод.",[2965,16577,16578,16581,16582,6181,16584,16586],{},[2975,16579,16580],{},"Backing field"," — приватне поле, що зберігає значення властивості (наприклад, ",[2989,16583,3058],{},[2989,16585,3104],{},").",[2965,16588,16589,16592,16593,12865,16595,3176,16597,16586],{},[2975,16590,16591],{},"Обчислювана властивість"," — властивість без backing field, що обчислюється з інших властивостей (наприклад, ",[2989,16594,5924],{},[2989,16596,3104],{},[2989,16598,5930],{},[2965,16600,16601,16603],{},[2975,16602,7365],{}," — інтерфейс для валідації з автоматичним відображенням помилок у UI.",[2965,16605,16606,16608],{},[2975,16607,9574],{}," — шаблон для відображення помилок валідації у WPF (червона рамка, tooltip).",[2965,16610,16611,16614],{},[2975,16612,16613],{},"DesignTime дані"," — тестові дані, що використовуються тільки у дизайнері Visual Studio для візуального feedback.",[2965,16616,16617,16619],{},[2975,16618,10855],{}," — атрибут для встановлення DataContext тільки у дизайнері (ігнорується у runtime).",[2965,16621,16622,16625],{},[2975,16623,16624],{},"IsDesignTimeCreatable"," — атрибут, що вказує, чи може дизайнер створити екземпляр ViewModel через конструктор без параметрів.",[2965,16627,16628,16632],{},[2975,16629,5034,16630],{},[4760,16631],{}," — клас для універсального порівняння значень будь-якого типу.",[2965,16634,16635,16638],{},[2975,16636,16637],{},"ref параметр"," — передача параметра за посиланням, що дозволяє методу змінити оригінальну змінну.",[3181,16640],{},[2960,16642,16644],{"id":16643},"додаткові-ресурси","Додаткові ресурси",[11822,16646,16647,16652,16658,16664,16669,16674],{},[11825,16648,16651],{"icon":15,"title":16649,"to":16650},"📖 Microsoft Docs: INotifyPropertyChanged","https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged","Офіційна документація про INotifyPropertyChanged з прикладами.",[11825,16653,16657],{"icon":16654,"title":16655,"to":16656},"i-lucide-alert-circle","📖 Microsoft Docs: INotifyDataErrorInfo","https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifydataerrorinfo","Офіційна документація про валідацію через INotifyDataErrorInfo.",[11825,16659,16663],{"icon":16660,"title":16661,"to":16662},"i-lucide-graduation-cap","🎓 CallerMemberName Attribute","https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callermembernameattribute","Детальний опис атрибута CallerMemberName та його використання.",[11825,16665,16668],{"icon":2561,"title":16666,"to":16667},"🔧 WPF Validation","https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-implement-validation-logic-on-custom-objects","Best practices для валідації у WPF.",[11825,16670,16673],{"icon":16671,"title":16672,"to":2971},"i-lucide-arrow-left","📚 Попередня стаття: MVVM Pattern","Повернутися до MVVM Pattern — архітектурний патерн, три компоненти, золоті правила.",[11825,16675,16678],{"icon":16676,"title":16677,"to":16529},"i-lucide-arrow-right","📚 Наступна стаття: Commands","Дізнатися про ICommand, RelayCommand та AsyncRelayCommand для прив'язки дій до кнопок.",[16680,16681,16682],"style",{},"html pre.shiki code .su1O8, html code.shiki .su1O8{--shiki-light:#0000FF;--shiki-default:#569CD6;--shiki-dark:#569CD6}html pre.shiki code .siwwj, html code.shiki .siwwj{--shiki-light:#001080;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .sHH4Y, html code.shiki .sHH4Y{--shiki-light:#000000;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}html pre.shiki code .s8Opu, html code.shiki .s8Opu{--shiki-light:#795E26;--shiki-default:#DCDCAA;--shiki-dark:#DCDCAA}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spJ8K, html code.shiki .spJ8K{--shiki-light:#008000;--shiki-default:#6A9955;--shiki-dark:#6A9955}html pre.shiki code .sN1BT, html code.shiki .sN1BT{--shiki-light:#267F99;--shiki-default:#4EC9B0;--shiki-dark:#4EC9B0}html pre.shiki code .sbdoH, html code.shiki .sbdoH{--shiki-light:#A31515;--shiki-default:#CE9178;--shiki-dark:#CE9178}html pre.shiki code .sD7JJ, html code.shiki .sD7JJ{--shiki-light:#000000FF;--shiki-default:#D4D4D4;--shiki-dark:#D4D4D4}html pre.shiki code .sCDza, html code.shiki .sCDza{--shiki-light:#AF00DB;--shiki-default:#CE92A4;--shiki-dark:#CE92A4}html pre.shiki code .s0P7L, html code.shiki .s0P7L{--shiki-light:#800000;--shiki-default:#808080;--shiki-dark:#808080}html pre.shiki code .sKtos, html code.shiki .sKtos{--shiki-light:#800000;--shiki-default:#569CD6;--shiki-dark:#569CD6}html pre.shiki code .sa4r_, html code.shiki .sa4r_{--shiki-light:#E50000;--shiki-default:#9CDCFE;--shiki-dark:#9CDCFE}html pre.shiki code .su9tN, html code.shiki .su9tN{--shiki-light:#0000FF;--shiki-default:#CE9178;--shiki-dark:#CE9178}html pre.shiki code .sJj4R, html code.shiki .sJj4R{--shiki-light:#098658;--shiki-default:#B5CEA8;--shiki-dark:#B5CEA8}",{"title":3007,"searchDepth":3032,"depth":3032,"links":16684},[16685,16686,16691,16695,16701,16707,16713,16720,16727,16732,16733,16734],{"id":2962,"depth":3032,"text":2963},{"id":3185,"depth":3032,"text":3186,"children":16687},[16688,16689,16690],{"id":3196,"depth":3043,"text":3197},{"id":3819,"depth":3043,"text":3820},{"id":3960,"depth":3043,"text":3961},{"id":4095,"depth":3032,"text":4096,"children":16692},[16693,16694],{"id":4108,"depth":3043,"text":4109},{"id":4449,"depth":3043,"text":4450},{"id":4757,"depth":3032,"text":16696,"children":16697},"SetProperty: Універсальний метод для властивостей",[16698,16699,16700],{"id":4845,"depth":3043,"text":4846},{"id":5198,"depth":3043,"text":5199},{"id":5704,"depth":3043,"text":5705},{"id":5917,"depth":3032,"text":5918,"children":16702},[16703,16704,16705,16706],{"id":5933,"depth":3043,"text":5934},{"id":6260,"depth":3043,"text":6261},{"id":6511,"depth":3043,"text":6512},{"id":6925,"depth":3043,"text":6926},{"id":7358,"depth":3032,"text":7359,"children":16708},[16709,16710,16711,16712],{"id":7369,"depth":3043,"text":7370},{"id":7575,"depth":3043,"text":7576},{"id":8459,"depth":3043,"text":8460},{"id":9567,"depth":3043,"text":9568},{"id":10436,"depth":3032,"text":10437,"children":16714},[16715,16716,16717,16718,16719],{"id":10447,"depth":3043,"text":10448},{"id":10616,"depth":3043,"text":10617},{"id":10867,"depth":3043,"text":10868},{"id":11480,"depth":3043,"text":11481},{"id":11819,"depth":3043,"text":11820},{"id":11849,"depth":3032,"text":11850,"children":16721},[16722,16723,16725,16726],{"id":11856,"depth":3043,"text":11857},{"id":12068,"depth":3043,"text":16724},"Generics: SetProperty для будь-якого типу",{"id":12267,"depth":3043,"text":12268},{"id":12619,"depth":3043,"text":12620},{"id":12831,"depth":3032,"text":12832,"children":16728},[16729,16730,16731],{"id":12835,"depth":3043,"text":12836},{"id":13448,"depth":3043,"text":13449},{"id":14620,"depth":3043,"text":14621},{"id":16427,"depth":3032,"text":16428},{"id":16554,"depth":3032,"text":16555},{"id":16643,"depth":3032,"text":16644},"Практична реалізація ViewModel — створення BaseViewModel, SetProperty\u003CT>, обчислювані властивості, валідація через INotifyDataErrorInfo та DesignTime дані","md",null,{},{"title":1493,"description":16735},"er3Bx6pfQU4JHit3sek6jv75NeRlsmGDXJh9v75swgg",[16742,16744],{"title":1489,"path":1490,"stem":1491,"description":16743,"children":-1},"Розуміння MVVM як архітектурного патерну — мотивація, структура, три компоненти (Model, View, ViewModel) та золоті правила розділення відповідальності",{"title":1497,"path":1498,"stem":1499,"description":16745,"children":-1},"ICommand інтерфейс, RelayCommand реалізація, CanExecute для автоматичного IsEnabled, CommandParameter, AsyncRelayCommand та KeyBindings",1777911654806]