================================================================================ КАК ВЫУЧИТЬ ЗА ~2 ЧАСА (ПОРЯДОК ПАМЯТИ) ================================================================================ Час 1 — «скелет» 1) SQL: две таблицы users + products, поля как в schema.sql. 2) Модели User и Product (имена полей = как в SELECT). 3) DbService: строка подключения, Login (одна строка из users), GetProducts (цикл while Read). Час 2 — «окна» 4) App: старт с LoginWindow как MainWindow. 5) LoginWindow: два TextBox, кнопки, вызов db.Login, OpenByRole — if по Role, ОБЯЗАТЕЛЬНО desktop.MainWindow = новое окно, иначе после Close() приложение умрёт. 6) BaseProductsWindow: UserName + Products, DataContext = this, ItemsControl ItemsSource. 7) Картинка: строка ImagePath из БД → конвертер → Bitmap (avares://ИМЯ_СБОРКИ/...). 8) Admin / Manager / Client — три класса, только Title и : base(user). Запомнить наизусть: порядок колонок в reader (0,1,2,3), SQL с @параметрами, и три роли: admin, manager, client. ================================================================================ БИБЛИОТЕКИ (NuGet) — КОРОТКО ================================================================================ Avalonia — UI Avalonia.Desktop — настольный запуск Avalonia.Themes.Fluent — тема оформления Npgsql — PostgreSQL Все Avalonia.* — одна версия. Подробнее: файл Packages.txt ================================================================================ ВЕРСИЯ AVALONIA ================================================================================ Код рассчитан на Avalonia 11.x. На экзамене поменяйте только номера пакетов в .csproj на те, что в шаблоне (главное — одинаковые между собой). В XAML: ItemsSource у списка (не Items). Имя сборки в конвертере должно совпадать с именем проекта (здесь: AvaloniaApplication1). ================================================================================ ФАЙЛ: AvaloniaApplication1.csproj ================================================================================ (см. реальный файл в проекте — там PackageReference и AvaloniaResource для Assets) ================================================================================ ФАЙЛ: Program.cs ================================================================================ using System; using Avalonia; namespace AvaloniaApplication1; internal static class Program { [STAThread] public static void Main(string[] args) => BuildAvaloniaApp() .StartWithClassicDesktopLifetime(args); public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() .LogToTrace(); } ================================================================================ ФАЙЛ: App.axaml ================================================================================ ================================================================================ ФАЙЛ: App.axaml.cs ================================================================================ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using AvaloniaApplication1.Views; namespace AvaloniaApplication1; public partial class App : Application { public override void Initialize() { AvaloniaXamlLoader.Load(this); } public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) desktop.MainWindow = new LoginWindow(); base.OnFrameworkInitializationCompleted(); } } ================================================================================ ФАЙЛ: Models/User.cs ================================================================================ namespace AvaloniaApplication1.Models; public sealed class User { public int Id { get; set; } public string Login { get; set; } = ""; public string Password { get; set; } = ""; public string Role { get; set; } = ""; public string FullName { get; set; } = ""; } ================================================================================ ФАЙЛ: Models/Product.cs ================================================================================ namespace AvaloniaApplication1.Models; public sealed class Product { public string Name { get; set; } = ""; public decimal Price { get; set; } public int Discount { get; set; } public string ImagePath { get; set; } = ""; public string FinalPrice => (Price * (100 - Discount) / 100m).ToString("F2"); } ================================================================================ ФАЙЛ: Services/DbService.cs (папка может называться services — без разницы) ================================================================================ using System.Collections.Generic; using AvaloniaApplication1.Models; using Npgsql; namespace AvaloniaApplication1.Services; public sealed class DbService { private readonly string connString = "Host=localhost;Port=5432;Username=postgres;Password=ВАШ_ПАРОЛЬ;Database=ВАША_БД"; public User? Login(string login, string password) { using var conn = new NpgsqlConnection(connString); conn.Open(); using var cmd = new NpgsqlCommand( "SELECT id, login, password, role, fullname FROM users WHERE login=@l AND password=@p", conn); cmd.Parameters.AddWithValue("l", login); cmd.Parameters.AddWithValue("p", password); using var reader = cmd.ExecuteReader(); if (reader.Read()) { return new User { Id = reader.GetInt32(0), Login = reader.GetString(1), Role = reader.GetString(3), FullName = reader.GetString(4) }; } return null; } public List GetProducts() { var list = new List(); using var conn = new NpgsqlConnection(connString); conn.Open(); using var cmd = new NpgsqlCommand( "SELECT name, price, discount, image FROM products", conn); using var reader = cmd.ExecuteReader(); while (reader.Read()) { var img = reader.IsDBNull(3) ? "" : reader.GetString(3); if (string.IsNullOrWhiteSpace(img)) img = "Assets/no_image.png"; else img = "Assets/" + img; list.Add(new Product { Name = reader.GetString(0), Price = reader.GetDecimal(1), Discount = reader.GetInt32(2), ImagePath = img }); } return list; } } ================================================================================ ФАЙЛ: Converters/AssetPathToBitmapConverter.cs ================================================================================ using System; using System.Globalization; using Avalonia.Data.Converters; using Avalonia.Media.Imaging; using Avalonia.Platform; namespace AvaloniaApplication1.Converters; public sealed class AssetPathToBitmapConverter : IValueConverter { public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { var relative = value as string; if (string.IsNullOrWhiteSpace(relative)) relative = "Assets/no_image.png"; relative = relative.Replace('\\', '/'); var uri = new Uri("avares://AvaloniaApplication1/" + relative); try { using var stream = AssetLoader.Open(uri); return new Bitmap(stream); } catch { var fallback = new Uri("avares://AvaloniaApplication1/Assets/no_image.png"); using var stream = AssetLoader.Open(fallback); return new Bitmap(stream); } } public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => throw new NotSupportedException(); } ================================================================================ ФАЙЛ: Views/LoginWindow.axaml ================================================================================