1. ================================================================================
  2. КАК ВЫУЧИТЬ ЗА ~2 ЧАСА (ПОРЯДОК ПАМЯТИ)
  3. ================================================================================
  4.  
  5. Час 1 — «скелет»
  6. 1) SQL: две таблицы users + products, поля как в schema.sql.
  7. 2) Модели User и Product (имена полей = как в SELECT).
  8. 3) DbService: строка подключения, Login (одна строка из users), GetProducts (цикл while Read).
  9.  
  10. Час 2 — «окна»
  11. 4) App: старт с LoginWindow как MainWindow.
  12. 5) LoginWindow: два TextBox, кнопки, вызов db.Login, OpenByRole — if по Role,
  13. ОБЯЗАТЕЛЬНО desktop.MainWindow = новое окно, иначе после Close() приложение умрёт.
  14. 6) BaseProductsWindow: UserName + Products, DataContext = this, ItemsControl ItemsSource.
  15. 7) Картинка: строка ImagePath из БД → конвертер → Bitmap (avares://ИМЯ_СБОРКИ/...).
  16. 8) Admin / Manager / Client — три класса, только Title и : base(user).
  17.  
  18. Запомнить наизусть: порядок колонок в reader (0,1,2,3), SQL с @параметрами,
  19. и три роли: admin, manager, client.
  20.  
  21. ================================================================================
  22. БИБЛИОТЕКИ (NuGet) — КОРОТКО
  23. ================================================================================
  24.  
  25. Avalonia — UI
  26. Avalonia.Desktop — настольный запуск
  27. Avalonia.Themes.Fluent — тема оформления
  28. Npgsql — PostgreSQL
  29.  
  30. Все Avalonia.* — одна версия. Подробнее: файл Packages.txt
  31.  
  32. ================================================================================
  33. ВЕРСИЯ AVALONIA
  34. ================================================================================
  35.  
  36. Код рассчитан на Avalonia 11.x. На экзамене поменяйте только номера пакетов
  37. в .csproj на те, что в шаблоне (главное — одинаковые между собой).
  38.  
  39. В XAML: ItemsSource у списка (не Items). Имя сборки в конвертере должно совпадать
  40. с именем проекта (здесь: AvaloniaApplication1).
  41.  
  42. ================================================================================
  43. ФАЙЛ: AvaloniaApplication1.csproj
  44. ================================================================================
  45.  
  46. (см. реальный файл в проекте — там PackageReference и AvaloniaResource для Assets)
  47.  
  48. ================================================================================
  49. ФАЙЛ: Program.cs
  50. ================================================================================
  51.  
  52. using System;
  53. using Avalonia;
  54.  
  55. namespace AvaloniaApplication1;
  56.  
  57. internal static class Program
  58. {
  59. [STAThread]
  60. public static void Main(string[] args) => BuildAvaloniaApp()
  61. .StartWithClassicDesktopLifetime(args);
  62.  
  63. public static AppBuilder BuildAvaloniaApp()
  64. => AppBuilder.Configure<App>()
  65. .UsePlatformDetect()
  66. .LogToTrace();
  67. }
  68.  
  69. ================================================================================
  70. ФАЙЛ: App.axaml
  71. ================================================================================
  72.  
  73. <Application xmlns="https://github.com/avaloniaui";
  74. x:Class="AvaloniaApplication1.App"
  75. RequestedThemeVariant="Default">
  76. <Application.Styles>
  77. <FluentTheme />
  78. </Application.Styles>
  79. </Application>
  80.  
  81. ================================================================================
  82. ФАЙЛ: App.axaml.cs
  83. ================================================================================
  84.  
  85. using Avalonia;
  86. using Avalonia.Controls.ApplicationLifetimes;
  87. using Avalonia.Markup.Xaml;
  88. using AvaloniaApplication1.Views;
  89.  
  90. namespace AvaloniaApplication1;
  91.  
  92. public partial class App : Application
  93. {
  94. public override void Initialize()
  95. {
  96. AvaloniaXamlLoader.Load(this);
  97. }
  98.  
  99. public override void OnFrameworkInitializationCompleted()
  100. {
  101. if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  102. desktop.MainWindow = new LoginWindow();
  103.  
  104. base.OnFrameworkInitializationCompleted();
  105. }
  106. }
  107.  
  108. ================================================================================
  109. ФАЙЛ: Models/User.cs
  110. ================================================================================
  111.  
  112. namespace AvaloniaApplication1.Models;
  113.  
  114. public sealed class User
  115. {
  116. public int Id { get; set; }
  117. public string Login { get; set; } = "";
  118. public string Password { get; set; } = "";
  119. public string Role { get; set; } = "";
  120. public string FullName { get; set; } = "";
  121. }
  122.  
  123. ================================================================================
  124. ФАЙЛ: Models/Product.cs
  125. ================================================================================
  126.  
  127. namespace AvaloniaApplication1.Models;
  128.  
  129. public sealed class Product
  130. {
  131. public string Name { get; set; } = "";
  132. public decimal Price { get; set; }
  133. public int Discount { get; set; }
  134. public string ImagePath { get; set; } = "";
  135.  
  136. public string FinalPrice =>
  137. (Price * (100 - Discount) / 100m).ToString("F2");
  138. }
  139.  
  140. ================================================================================
  141. ФАЙЛ: Services/DbService.cs (папка может называться services — без разницы)
  142. ================================================================================
  143.  
  144. using System.Collections.Generic;
  145. using AvaloniaApplication1.Models;
  146. using Npgsql;
  147.  
  148. namespace AvaloniaApplication1.Services;
  149.  
  150. public sealed class DbService
  151. {
  152. private readonly string connString =
  153. "Host=localhost;Port=5432;Username=postgres;Password=ВАШ_ПАРОЛЬ;Database=ВАША_БД";
  154.  
  155. public User? Login(string login, string password)
  156. {
  157. using var conn = new NpgsqlConnection(connString);
  158. conn.Open();
  159.  
  160. using var cmd = new NpgsqlCommand(
  161. "SELECT id, login, password, role, fullname FROM users WHERE login=@l AND password=@p",
  162. conn);
  163.  
  164. cmd.Parameters.AddWithValue("l", login);
  165. cmd.Parameters.AddWithValue("p", password);
  166.  
  167. using var reader = cmd.ExecuteReader();
  168.  
  169. if (reader.Read())
  170. {
  171. return new User
  172. {
  173. Id = reader.GetInt32(0),
  174. Login = reader.GetString(1),
  175. Role = reader.GetString(3),
  176. FullName = reader.GetString(4)
  177. };
  178. }
  179.  
  180. return null;
  181. }
  182.  
  183. public List<Product> GetProducts()
  184. {
  185. var list = new List<Product>();
  186.  
  187. using var conn = new NpgsqlConnection(connString);
  188. conn.Open();
  189.  
  190. using var cmd = new NpgsqlCommand(
  191. "SELECT name, price, discount, image FROM products", conn);
  192. using var reader = cmd.ExecuteReader();
  193.  
  194. while (reader.Read())
  195. {
  196. var img = reader.IsDBNull(3) ? "" : reader.GetString(3);
  197.  
  198. if (string.IsNullOrWhiteSpace(img))
  199. img = "Assets/no_image.png";
  200. else
  201. img = "Assets/" + img;
  202.  
  203. list.Add(new Product
  204. {
  205. Name = reader.GetString(0),
  206. Price = reader.GetDecimal(1),
  207. Discount = reader.GetInt32(2),
  208. ImagePath = img
  209. });
  210. }
  211.  
  212. return list;
  213. }
  214. }
  215.  
  216. ================================================================================
  217. ФАЙЛ: Converters/AssetPathToBitmapConverter.cs
  218. ================================================================================
  219.  
  220. using System;
  221. using System.Globalization;
  222. using Avalonia.Data.Converters;
  223. using Avalonia.Media.Imaging;
  224. using Avalonia.Platform;
  225.  
  226. namespace AvaloniaApplication1.Converters;
  227.  
  228. public sealed class AssetPathToBitmapConverter : IValueConverter
  229. {
  230. public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
  231. {
  232. var relative = value as string;
  233. if (string.IsNullOrWhiteSpace(relative))
  234. relative = "Assets/no_image.png";
  235.  
  236. relative = relative.Replace('\\', '/');
  237. var uri = new Uri("avares://AvaloniaApplication1/" + relative);
  238.  
  239. try
  240. {
  241. using var stream = AssetLoader.Open(uri);
  242. return new Bitmap(stream);
  243. }
  244. catch
  245. {
  246. var fallback = new Uri("avares://AvaloniaApplication1/Assets/no_image.png");
  247. using var stream = AssetLoader.Open(fallback);
  248. return new Bitmap(stream);
  249. }
  250. }
  251.  
  252. public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
  253. => throw new NotSupportedException();
  254. }
  255.  
  256. ================================================================================
  257. ФАЙЛ: Views/LoginWindow.axaml
  258. ================================================================================
  259.  
  260. x:Class="AvaloniaApplication1.Views.LoginWindow"
  261. Width="400" Height="280"
  262. Title="Вход">
  263. <StackPanel Margin="20" Spacing="10">
  264. <TextBox x:Name="loginBox" Watermark="Логин"/>
  265. <TextBox x:Name="passwordBox" PasswordChar="*"/>
  266. <TextBlock x:Name="errorText" Foreground="Red" TextWrapping="Wrap"/>
  267. <Button Content="Войти" Click="Login_Click"/>
  268. <Button Content="Гость" Click="Guest_Click"/>
  269. </StackPanel>
  270. </Window>
  271.  
  272. ================================================================================
  273. ФАЙЛ: Views/LoginWindow.axaml.cs
  274. ================================================================================
  275.  
  276. using System;
  277. using Avalonia.Controls;
  278. using Avalonia.Controls.ApplicationLifetimes;
  279. using Avalonia.Interactivity;
  280. using AvaloniaApplication1.Models;
  281. using AvaloniaApplication1.Services;
  282.  
  283. namespace AvaloniaApplication1.Views;
  284.  
  285. public partial class LoginWindow : Window
  286. {
  287. private readonly DbService db = new DbService();
  288.  
  289. public LoginWindow()
  290. {
  291. InitializeComponent();
  292. }
  293.  
  294. private void Login_Click(object? sender, RoutedEventArgs e)
  295. {
  296. errorText.Text = "";
  297. var login = loginBox.Text?.Trim() ?? "";
  298. var password = passwordBox.Text ?? "";
  299.  
  300. try
  301. {
  302. var user = db.Login(login, password);
  303. if (user == null)
  304. {
  305. errorText.Text = "Неверный логин или пароль.";
  306. return;
  307. }
  308.  
  309. OpenByRole(user);
  310. }
  311. catch (Exception ex)
  312. {
  313. errorText.Text = "Ошибка БД: " + ex.Message;
  314. }
  315. }
  316.  
  317. private void Guest_Click(object? sender, RoutedEventArgs e)
  318. {
  319. errorText.Text = "";
  320. OpenByRole(new User { Role = "client", FullName = "Гость" });
  321. }
  322.  
  323. private void OpenByRole(User user)
  324. {
  325. Window w;
  326. if (user.Role == "admin")
  327. w = new AdminWindow(user);
  328. else if (user.Role == "manager")
  329. w = new ManagerWindow(user);
  330. else
  331. w = new ClientWindow(user);
  332.  
  333. if (Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  334. desktop.MainWindow = w;
  335.  
  336. w.Show();
  337. Close();
  338. }
  339. }
  340.  
  341. ================================================================================
  342. ФАЙЛ: Views/BaseProductsWindow.axaml
  343. ================================================================================
  344.  
  345. xmlns:conv="clr-namespace:AvaloniaApplication1.Converters"
  346. x:Class="AvaloniaApplication1.Views.BaseProductsWindow"
  347. Width="900" Height="600">
  348.  
  349. <Window.Resources>
  350. <conv:AssetPathToBitmapConverter x:Key="ImgConv"/>
  351. </Window.Resources>
  352.  
  353. <DockPanel>
  354. <Border DockPanel.Dock="Top" Padding="12" Background="#E8E8E8">
  355. <Grid ColumnDefinitions="*,Auto">
  356. <TextBlock VerticalAlignment="Center" Text="{Binding UserName, StringFormat='Пользователь: {0}'}"/>
  357. <Button Grid.Column="1" Content="Выйти" Click="Logout_Click"/>
  358. </Grid>
  359. </Border>
  360.  
  361. <ScrollViewer>
  362. <ItemsControl Margin="10" ItemsSource="{Binding Products}">
  363. <ItemsControl.ItemsPanel>
  364. <ItemsPanelTemplate>
  365. <WrapPanel/>
  366. </ItemsPanelTemplate>
  367. </ItemsControl.ItemsPanel>
  368. <ItemsControl.ItemTemplate>
  369. <DataTemplate>
  370. <Border Margin="10" Padding="10" BorderBrush="Gray" BorderThickness="1" Width="200">
  371. <StackPanel Spacing="6">
  372. <Image Width="120" Height="120" HorizontalAlignment="Center"
  373. Source="{Binding ImagePath, Converter={StaticResource ImgConv}}"/>
  374. <TextBlock Text="{Binding Name}" FontWeight="Bold" TextWrapping="Wrap"/>
  375. <TextBlock Text="{Binding Price, StringFormat='Цена: {0}'}"/>
  376. <TextBlock Text="{Binding FinalPrice, StringFormat='Со скидкой: {0}'}"/>
  377. </StackPanel>
  378. </Border>
  379. </DataTemplate>
  380. </ItemsControl.ItemTemplate>
  381. </ItemsControl>
  382. </ScrollViewer>
  383. </DockPanel>
  384. </Window>
  385.  
  386. ================================================================================
  387. ФАЙЛ: Views/BaseProductsWindow.axaml.cs
  388. ================================================================================
  389.  
  390. using System.Collections.Generic;
  391. using Avalonia;
  392. using Avalonia.Controls;
  393. using Avalonia.Controls.ApplicationLifetimes;
  394. using Avalonia.Interactivity;
  395. using AvaloniaApplication1.Models;
  396. using AvaloniaApplication1.Services;
  397.  
  398. namespace AvaloniaApplication1.Views;
  399.  
  400. public partial class BaseProductsWindow : Window
  401. {
  402. private readonly DbService db = new DbService();
  403.  
  404. public string UserName { get; set; } = "";
  405. public List<Product> Products { get; set; } = new List<Product>();
  406.  
  407. public BaseProductsWindow()
  408. {
  409. InitializeComponent();
  410. }
  411.  
  412. public BaseProductsWindow(User user) : this()
  413. {
  414. UserName = user.FullName;
  415. Products = db.GetProducts();
  416. DataContext = this;
  417. }
  418.  
  419. public void Logout()
  420. {
  421. var login = new LoginWindow();
  422. if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  423. desktop.MainWindow = login;
  424.  
  425. login.Show();
  426. Close();
  427. }
  428.  
  429. private void Logout_Click(object? sender, RoutedEventArgs e)
  430. {
  431. Logout();
  432. }
  433. }
  434.  
  435. ================================================================================
  436. ФАЙЛ: Views/AdminWindow.cs
  437. ================================================================================
  438.  
  439. using AvaloniaApplication1.Models;
  440.  
  441. namespace AvaloniaApplication1.Views;
  442.  
  443. public sealed class AdminWindow : BaseProductsWindow
  444. {
  445. public AdminWindow(User user) : base(user)
  446. {
  447. Title = "Администратор";
  448. }
  449. }
  450.  
  451. ================================================================================
  452. ФАЙЛ: Views/ManagerWindow.cs
  453. ================================================================================
  454.  
  455. using AvaloniaApplication1.Models;
  456.  
  457. namespace AvaloniaApplication1.Views;
  458.  
  459. public sealed class ManagerWindow : BaseProductsWindow
  460. {
  461. public ManagerWindow(User user) : base(user)
  462. {
  463. Title = "Менеджер";
  464. }
  465. }
  466.  
  467. ================================================================================
  468. ФАЙЛ: Views/ClientWindow.cs
  469. ================================================================================
  470.  
  471. using AvaloniaApplication1.Models;
  472.  
  473. namespace AvaloniaApplication1.Views;
  474.  
  475. public sealed class ClientWindow : BaseProductsWindow
  476. {
  477. public ClientWindow(User user) : base(user)
  478. {
  479. Title = "Клиент";
  480. }
  481. }
  482.  
  483. ================================================================================
  484. ФАЙЛ: Database/schema.sql
  485. ================================================================================
  486.  
  487. CREATE TABLE users (
  488. id SERIAL PRIMARY KEY,
  489. login TEXT,
  490. password TEXT,
  491. role TEXT,
  492. fullname TEXT
  493. );
  494.  
  495. INSERT INTO users (login, password, role, fullname) VALUES
  496. ('admin','123','admin','Администратор'),
  497. ('manager','123','manager','Менеджер'),
  498. ('user','123','client','Пользователь');
  499.  
  500. CREATE TABLE products (
  501. id SERIAL PRIMARY KEY,
  502. name TEXT,
  503. price NUMERIC,
  504. discount INT,
  505. image TEXT
  506. );
  507.  
  508. INSERT INTO products (name, price, discount, image) VALUES
  509. ('Телефон', 50000, 10, 'phone.png'),
  510. ('Ноутбук', 90000, 20, 'laptop.png'),
  511. ('Мышка', 1500, 0, ''),
  512. ('Клавиатура', 3000, 5, NULL);
  513.  
  514. ================================================================================
  515. КОНЕЦ СБОРНИКА (дублирует рабочий проект; при правках в коде обновите этот файл)
  516. ================================================================================