================================================================================
КАК ВЫУЧИТЬ ЗА ~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<App>()
.UsePlatformDetect()
.LogToTrace();
}
================================================================================
ФАЙЛ: App.axaml
================================================================================
x:Class="AvaloniaApplication1.App"
RequestedThemeVariant="Default">
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>
================================================================================
ФАЙЛ: 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<Product> GetProducts()
{
var list = new List<Product>();
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
================================================================================
x:Class="AvaloniaApplication1.Views.LoginWindow"
Width="400" Height="280"
Title="Вход">
<StackPanel Margin="20" Spacing="10">
<TextBox x:Name="loginBox" Watermark="Логин"/>
<TextBox x:Name="passwordBox" PasswordChar="*"/>
<TextBlock x:Name="errorText" Foreground="Red" TextWrapping="Wrap"/>
<Button Content="Войти" Click="Login_Click"/>
<Button Content="Гость" Click="Guest_Click"/>
</StackPanel>
</Window>
================================================================================
ФАЙЛ: Views/LoginWindow.axaml.cs
================================================================================
using System;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Interactivity;
using AvaloniaApplication1.Models;
using AvaloniaApplication1.Services;
namespace AvaloniaApplication1.Views;
public partial class LoginWindow : Window
{
private readonly DbService db = new DbService();
public LoginWindow()
{
InitializeComponent();
}
private void Login_Click(object? sender, RoutedEventArgs e)
{
errorText.Text = "";
var login = loginBox.Text?.Trim() ?? "";
var password = passwordBox.Text ?? "";
try
{
var user = db.Login(login, password);
if (user == null)
{
errorText.Text = "Неверный логин или пароль.";
return;
}
OpenByRole(user);
}
catch (Exception ex)
{
errorText.Text = "Ошибка БД: " + ex.Message;
}
}
private void Guest_Click(object? sender, RoutedEventArgs e)
{
errorText.Text = "";
OpenByRole(new User { Role = "client", FullName = "Гость" });
}
private void OpenByRole(User user)
{
Window w;
if (user.Role == "admin")
w = new AdminWindow(user);
else if (user.Role == "manager")
w = new ManagerWindow(user);
else
w = new ClientWindow(user);
if (Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
desktop.MainWindow = w;
w.Show();
Close();
}
}
================================================================================
ФАЙЛ: Views/BaseProductsWindow.axaml
================================================================================
xmlns:conv="clr-namespace:AvaloniaApplication1.Converters"
x:Class="AvaloniaApplication1.Views.BaseProductsWindow"
Width="900" Height="600">
<Window.Resources>
<conv:AssetPathToBitmapConverter x:Key="ImgConv"/>
</Window.Resources>
<DockPanel>
<Border DockPanel.Dock="Top" Padding="12" Background="#E8E8E8">
<Grid ColumnDefinitions="*,Auto">
<TextBlock VerticalAlignment="Center" Text="{Binding UserName, StringFormat='Пользователь: {0}'}"/>
<Button Grid.Column="1" Content="Выйти" Click="Logout_Click"/>
</Grid>
</Border>
<ScrollViewer>
<ItemsControl Margin="10" ItemsSource="{Binding Products}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="10" Padding="10" BorderBrush="Gray" BorderThickness="1" Width="200">
<StackPanel Spacing="6">
<Image Width="120" Height="120" HorizontalAlignment="Center"
Source="{Binding ImagePath, Converter={StaticResource ImgConv}}"/>
<TextBlock Text="{Binding Name}" FontWeight="Bold" TextWrapping="Wrap"/>
<TextBlock Text="{Binding Price, StringFormat='Цена: {0}'}"/>
<TextBlock Text="{Binding FinalPrice, StringFormat='Со скидкой: {0}'}"/>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</Window>
================================================================================
ФАЙЛ: Views/BaseProductsWindow.axaml.cs
================================================================================
using System.Collections.Generic;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Interactivity;
using AvaloniaApplication1.Models;
using AvaloniaApplication1.Services;
namespace AvaloniaApplication1.Views;
public partial class BaseProductsWindow : Window
{
private readonly DbService db = new DbService();
public string UserName { get; set; } = "";
public List<Product> Products { get; set; } = new List<Product>();
public BaseProductsWindow()
{
InitializeComponent();
}
public BaseProductsWindow(User user) : this()
{
UserName = user.FullName;
Products = db.GetProducts();
DataContext = this;
}
public void Logout()
{
var login = new LoginWindow();
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
desktop.MainWindow = login;
login.Show();
Close();
}
private void Logout_Click(object? sender, RoutedEventArgs e)
{
Logout();
}
}
================================================================================
ФАЙЛ: Views/AdminWindow.cs
================================================================================
using AvaloniaApplication1.Models;
namespace AvaloniaApplication1.Views;
public sealed class AdminWindow : BaseProductsWindow
{
public AdminWindow(User user) : base(user)
{
Title = "Администратор";
}
}
================================================================================
ФАЙЛ: Views/ManagerWindow.cs
================================================================================
using AvaloniaApplication1.Models;
namespace AvaloniaApplication1.Views;
public sealed class ManagerWindow : BaseProductsWindow
{
public ManagerWindow(User user) : base(user)
{
Title = "Менеджер";
}
}
================================================================================
ФАЙЛ: Views/ClientWindow.cs
================================================================================
using AvaloniaApplication1.Models;
namespace AvaloniaApplication1.Views;
public sealed class ClientWindow : BaseProductsWindow
{
public ClientWindow(User user) : base(user)
{
Title = "Клиент";
}
}
================================================================================
ФАЙЛ: Database/schema.sql
================================================================================
CREATE TABLE users (
id SERIAL PRIMARY KEY,
login TEXT,
password TEXT,
role TEXT,
fullname TEXT
);
INSERT INTO users (login, password, role, fullname) VALUES
('admin','123','admin','Администратор'),
('manager','123','manager','Менеджер'),
('user','123','client','Пользователь');
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
price NUMERIC,
discount INT,
image TEXT
);
INSERT INTO products (name, price, discount, image) VALUES
('Телефон', 50000, 10, 'phone.png'),
('Ноутбук', 90000, 20, 'laptop.png'),
('Мышка', 1500, 0, ''),
('Клавиатура', 3000, 5, NULL);
================================================================================
КОНЕЦ СБОРНИКА (дублирует рабочий проект; при правках в коде обновите этот файл)
================================================================================