Aleksaandrooov.DemoExam.WinForms.Template
1.0.2
dotnet new install Aleksaandrooov.DemoExam.WinForms.Template@1.0.2
Aleksaandrooov.DemoExam.WinForms.Template
NuGet-пакет шаблона для создания минимального проекта Windows Forms App на .NET 8 с SQL Server.
После установки пакет добавляет шаблон с коротким именем:
demoexam-winforms
Установка из NuGet
dotnet new install Aleksaandrooov.DemoExam.WinForms.Template@1.0.2
Локальная установка готового пакета
dotnet new install .\Aleksaandrooov.DemoExam.WinForms.Template.1.0.2.nupkg
Создание проекта
dotnet new demoexam-winforms -n ShoeShop `
--dbServer "192.168.0.106,1433" `
--dbName "DemoExamShoes" `
--dbUser "sa" `
--dbPassword "YOUR_PASSWORD"
После создания откройте ShoeShop.slnx.
Не публикуйте пакет с настоящим паролем в исходных файлах. В шаблоне используются заменяемые параметры подключения.
Целевая платформа проекта: net8.0-windows.
Резервная копия проекта: код по файлам
Этот раздел нужен на случай, если на экзамене не получится скачать или установить шаблон. Создайте обычный проект Windows Forms App, затем создайте папки и файлы по указанным путям и скопируйте содержимое каждого блока.
Бинарные файлы Assets/logo.png и Assets/app.ico в текстовом README не дублируются. Их можно заменить выданными на экзамене ресурсами. Если иконки нет, удалите строку ApplicationIcon из .csproj.
Структура
.template.config/template.json
Database/CreateDatabase.sql
DemoExamTemplate.sln
DemoExamTemplate.slnx
src/DemoExamTemplate/Data/AppConfig.cs
src/DemoExamTemplate/Data/Db.cs
src/DemoExamTemplate/DemoExamTemplate.csproj
src/DemoExamTemplate/Forms/LoginForm.Designer.cs
src/DemoExamTemplate/Forms/LoginForm.cs
src/DemoExamTemplate/Forms/LoginForm.resx
src/DemoExamTemplate/Forms/MainForm.Designer.cs
src/DemoExamTemplate/Forms/MainForm.cs
src/DemoExamTemplate/Forms/MainForm.resx
src/DemoExamTemplate/Forms/OrderEditForm.Designer.cs
src/DemoExamTemplate/Forms/OrderEditForm.cs
src/DemoExamTemplate/Forms/OrderEditForm.resx
src/DemoExamTemplate/Forms/OrdersForm.Designer.cs
src/DemoExamTemplate/Forms/OrdersForm.cs
src/DemoExamTemplate/Forms/OrdersForm.resx
src/DemoExamTemplate/Forms/ProductEditForm.Designer.cs
src/DemoExamTemplate/Forms/ProductEditForm.cs
src/DemoExamTemplate/Forms/ProductEditForm.resx
src/DemoExamTemplate/Models/Category.cs
src/DemoExamTemplate/Models/OrderModels.cs
src/DemoExamTemplate/Models/Product.cs
src/DemoExamTemplate/Models/UserSession.cs
src/DemoExamTemplate/Program.cs
src/DemoExamTemplate/Services/AuthService.cs
src/DemoExamTemplate/Services/OrderService.cs
src/DemoExamTemplate/Services/ProductService.cs
src/DemoExamTemplate/UI/UiHelper.cs
src/DemoExamTemplate/appsettings.json
Assets/logo.png
Assets/app.ico
1. .template.config/template.json
{
"$schema": "http://json.schemastore.org/template",
"author": "Aleksaandrooov",
"classifications": [
"Windows Forms",
"Desktop",
"Demo Exam",
"SQL Server"
],
"identity": "Aleksaandrooov.DemoExam.WinForms.Template",
"name": "Demo Exam Windows Forms SQL Server Template",
"shortName": "demoexam-winforms",
"sourceName": "DemoExamTemplate",
"defaultName": "DemoExamProject",
"preferNameDirectory": true,
"tags": {
"language": "C#",
"type": "project"
},
"guids": [
"8D32B0B9-1F0D-4D5C-8A2D-73A1B5A1B234"
],
"symbols": {
"dbServer": {
"type": "parameter",
"datatype": "text",
"defaultValue": "localhost,1433",
"description": "SQL Server address, for example localhost,1433",
"replaces": "__DB_SERVER__"
},
"dbName": {
"type": "parameter",
"datatype": "text",
"defaultValue": "DemoExamShoes",
"description": "Database name",
"replaces": "__DB_NAME__"
},
"dbUser": {
"type": "parameter",
"datatype": "text",
"defaultValue": "sa",
"description": "SQL Server login",
"replaces": "__DB_USER__"
},
"dbPassword": {
"type": "parameter",
"datatype": "text",
"defaultValue": "CHANGE_ME",
"description": "SQL Server password",
"replaces": "__DB_PASSWORD__"
}
},
"sources": [
{
"modifiers": [
{
"exclude": [
".vs/**",
"**/bin/**",
"**/obj/**",
"**/*.user",
"**/*.suo"
]
}
]
}
],
"primaryOutputs": [
{
"path": "DemoExamTemplate.slnx"
},
{
"path": "src/DemoExamTemplate/DemoExamTemplate.csproj"
}
]
}
2. Database/CreateDatabase.sql
USE master;
GO
IF DB_ID(N'__DB_NAME__') IS NULL
CREATE DATABASE [__DB_NAME__];
GO
USE [__DB_NAME__];
GO
CREATE TABLE dbo.Roles
(
Id INT IDENTITY(1,1) PRIMARY KEY,
Code NVARCHAR(20) NOT NULL UNIQUE,
Name NVARCHAR(50) NOT NULL
);
GO
CREATE TABLE dbo.Users
(
Id INT IDENTITY(1,1) PRIMARY KEY,
Login NVARCHAR(50) NOT NULL UNIQUE,
Password NVARCHAR(100) NOT NULL,
FullName NVARCHAR(150) NOT NULL,
RoleId INT NOT NULL,
CONSTRAINT FK_Users_Roles FOREIGN KEY (RoleId) REFERENCES dbo.Roles(Id)
);
GO
CREATE TABLE dbo.Categories
(
Id INT IDENTITY(1,1) PRIMARY KEY,
Name NVARCHAR(100) NOT NULL UNIQUE
);
GO
CREATE TABLE dbo.Products
(
Id INT IDENTITY(1,1) PRIMARY KEY,
Article NVARCHAR(30) NOT NULL UNIQUE,
Name NVARCHAR(150) NOT NULL,
CategoryId INT NOT NULL,
Price DECIMAL(18,2) NOT NULL CHECK (Price > 0),
Discount INT NOT NULL DEFAULT 0 CHECK (Discount BETWEEN 0 AND 100),
Quantity INT NOT NULL DEFAULT 0 CHECK (Quantity >= 0),
CONSTRAINT FK_Products_Categories FOREIGN KEY (CategoryId) REFERENCES dbo.Categories(Id)
);
GO
CREATE TABLE dbo.Orders
(
Id INT IDENTITY(1,1) PRIMARY KEY,
Number NVARCHAR(30) NOT NULL UNIQUE,
OrderDate DATE NOT NULL,
Customer NVARCHAR(150) NOT NULL,
Status NVARCHAR(50) NOT NULL
);
GO
CREATE TABLE dbo.OrderItems
(
Id INT IDENTITY(1,1) PRIMARY KEY,
OrderId INT NOT NULL,
ProductId INT NOT NULL,
Quantity INT NOT NULL CHECK (Quantity > 0),
Price DECIMAL(18,2) NOT NULL CHECK (Price > 0),
Discount INT NOT NULL DEFAULT 0 CHECK (Discount BETWEEN 0 AND 100),
CONSTRAINT FK_OrderItems_Orders FOREIGN KEY (OrderId) REFERENCES dbo.Orders(Id) ON DELETE CASCADE,
CONSTRAINT FK_OrderItems_Products FOREIGN KEY (ProductId) REFERENCES dbo.Products(Id)
);
GO
INSERT INTO dbo.Roles(Code, Name) VALUES
(N'CLIENT', N'Клиент'),
(N'MANAGER', N'Менеджер'),
(N'ADMIN', N'Администратор');
GO
INSERT INTO dbo.Users(Login, Password, FullName, RoleId)
SELECT N'client', N'client', N'Клиент', Id FROM dbo.Roles WHERE Code=N'CLIENT';
INSERT INTO dbo.Users(Login, Password, FullName, RoleId)
SELECT N'manager', N'manager', N'Менеджер', Id FROM dbo.Roles WHERE Code=N'MANAGER';
INSERT INTO dbo.Users(Login, Password, FullName, RoleId)
SELECT N'admin', N'admin', N'Администратор', Id FROM dbo.Roles WHERE Code=N'ADMIN';
GO
INSERT INTO dbo.Categories(Name) VALUES
(N'Кроссовки'), (N'Ботинки'), (N'Туфли');
GO
INSERT INTO dbo.Products(Article, Name, CategoryId, Price, Discount, Quantity)
SELECT N'SH-001', N'Кроссовки', Id, 4990, 10, 20 FROM dbo.Categories WHERE Name=N'Кроссовки';
INSERT INTO dbo.Products(Article, Name, CategoryId, Price, Discount, Quantity)
SELECT N'SH-002', N'Зимние ботинки', Id, 7990, 20, 10 FROM dbo.Categories WHERE Name=N'Ботинки';
INSERT INTO dbo.Products(Article, Name, CategoryId, Price, Discount, Quantity)
SELECT N'SH-003', N'Классические туфли', Id, 5990, 0, 15 FROM dbo.Categories WHERE Name=N'Туфли';
GO
3. DemoExamTemplate.sln
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.0.0.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoExamTemplate", "src\DemoExamTemplate\DemoExamTemplate.csproj", "{8D32B0B9-1F0D-4D5C-8A2D-73A1B5A1B234}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8D32B0B9-1F0D-4D5C-8A2D-73A1B5A1B234}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D32B0B9-1F0D-4D5C-8A2D-73A1B5A1B234}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D32B0B9-1F0D-4D5C-8A2D-73A1B5A1B234}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D32B0B9-1F0D-4D5C-8A2D-73A1B5A1B234}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
4. DemoExamTemplate.slnx
<Solution>
<Project Path="src/DemoExamTemplate/DemoExamTemplate.csproj" />
</Solution>
5. src/DemoExamTemplate/Data/AppConfig.cs
using System.Text.Json;
namespace DemoExamTemplate.Data;
public static class AppConfig
{
public static string ConnectionString { get; }
static AppConfig()
{
string path = Path.Combine(AppContext.BaseDirectory, "appsettings.json");
string json = File.ReadAllText(path);
using JsonDocument document = JsonDocument.Parse(json);
ConnectionString = document.RootElement.GetProperty("ConnectionString").GetString()
?? throw new InvalidOperationException("В appsettings.json не задана строка подключения.");
}
}
6. src/DemoExamTemplate/Data/Db.cs
using Microsoft.Data.SqlClient;
namespace DemoExamTemplate.Data;
public static class Db
{
public static SqlConnection CreateConnection()
{
return new SqlConnection(AppConfig.ConnectionString);
}
}
7. src/DemoExamTemplate/DemoExamTemplate.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<ForceDesignerDPIUnaware>true</ForceDesignerDPIUnaware>
<ApplicationIcon Condition="Exists('Assets\app.ico')">Assets\app.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Assets\logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Compile Update="Forms\LoginForm.cs"><SubType>Form</SubType></Compile>
<Compile Update="Forms\LoginForm.Designer.cs"><DependentUpon>LoginForm.cs</DependentUpon></Compile>
<EmbeddedResource Update="Forms\LoginForm.resx"><DependentUpon>LoginForm.cs</DependentUpon></EmbeddedResource>
<Compile Update="Forms\MainForm.cs"><SubType>Form</SubType></Compile>
<Compile Update="Forms\MainForm.Designer.cs"><DependentUpon>MainForm.cs</DependentUpon></Compile>
<EmbeddedResource Update="Forms\MainForm.resx"><DependentUpon>MainForm.cs</DependentUpon></EmbeddedResource>
<Compile Update="Forms\ProductEditForm.cs"><SubType>Form</SubType></Compile>
<Compile Update="Forms\ProductEditForm.Designer.cs"><DependentUpon>ProductEditForm.cs</DependentUpon></Compile>
<EmbeddedResource Update="Forms\ProductEditForm.resx"><DependentUpon>ProductEditForm.cs</DependentUpon></EmbeddedResource>
<Compile Update="Forms\OrdersForm.cs"><SubType>Form</SubType></Compile>
<Compile Update="Forms\OrdersForm.Designer.cs"><DependentUpon>OrdersForm.cs</DependentUpon></Compile>
<EmbeddedResource Update="Forms\OrdersForm.resx"><DependentUpon>OrdersForm.cs</DependentUpon></EmbeddedResource>
<Compile Update="Forms\OrderEditForm.cs"><SubType>Form</SubType></Compile>
<Compile Update="Forms\OrderEditForm.Designer.cs"><DependentUpon>OrderEditForm.cs</DependentUpon></Compile>
<EmbeddedResource Update="Forms\OrderEditForm.resx"><DependentUpon>OrderEditForm.cs</DependentUpon></EmbeddedResource>
</ItemGroup>
</Project>
8. src/DemoExamTemplate/Forms/LoginForm.Designer.cs
namespace DemoExamTemplate.Forms;
partial class LoginForm
{
private System.ComponentModel.IContainer? components = null;
protected override void Dispose(bool disposing)
{
if (disposing && components != null) components.Dispose();
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
lblTitle = new Label();
lblLogin = new Label();
txtLogin = new TextBox();
lblPassword = new Label();
txtPassword = new TextBox();
btnLogin = new Button();
btnGuest = new Button();
SuspendLayout();
// lblTitle
lblTitle.AutoSize = true;
lblTitle.Font = new Font("Times New Roman", 20F, FontStyle.Bold);
lblTitle.Location = new Point(92, 38);
lblTitle.Name = "lblTitle";
lblTitle.Size = new Size(246, 31);
lblTitle.Text = "Вход в систему";
// lblLogin
lblLogin.AutoSize = true;
lblLogin.Location = new Point(55, 112);
lblLogin.Name = "lblLogin";
lblLogin.Size = new Size(52, 17);
lblLogin.Text = "Логин";
// txtLogin
txtLogin.Location = new Point(55, 135);
txtLogin.Name = "txtLogin";
txtLogin.Size = new Size(320, 24);
// lblPassword
lblPassword.AutoSize = true;
lblPassword.Location = new Point(55, 178);
lblPassword.Name = "lblPassword";
lblPassword.Size = new Size(58, 17);
lblPassword.Text = "Пароль";
// txtPassword
txtPassword.Location = new Point(55, 201);
txtPassword.Name = "txtPassword";
txtPassword.PasswordChar = '*';
txtPassword.Size = new Size(320, 24);
// btnLogin
btnLogin.BackColor = Color.MediumSpringGreen;
btnLogin.FlatStyle = FlatStyle.Flat;
btnLogin.Location = new Point(55, 255);
btnLogin.Name = "btnLogin";
btnLogin.Size = new Size(150, 42);
btnLogin.Text = "Войти";
btnLogin.UseVisualStyleBackColor = false;
btnLogin.Click += btnLogin_Click;
// btnGuest
btnGuest.BackColor = Color.Chartreuse;
btnGuest.FlatStyle = FlatStyle.Flat;
btnGuest.Location = new Point(225, 255);
btnGuest.Name = "btnGuest";
btnGuest.Size = new Size(150, 42);
btnGuest.Text = "Войти как гость";
btnGuest.UseVisualStyleBackColor = false;
btnGuest.Click += btnGuest_Click;
// LoginForm
AcceptButton = btnLogin;
AutoScaleDimensions = new SizeF(8F, 17F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.White;
ClientSize = new Size(430, 350);
Controls.Add(btnGuest);
Controls.Add(btnLogin);
Controls.Add(txtPassword);
Controls.Add(lblPassword);
Controls.Add(txtLogin);
Controls.Add(lblLogin);
Controls.Add(lblTitle);
Font = new Font("Times New Roman", 11F);
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;
Name = "LoginForm";
StartPosition = FormStartPosition.CenterScreen;
Text = "Авторизация";
ResumeLayout(false);
PerformLayout();
}
#endregion
private Label lblTitle;
private Label lblLogin;
private TextBox txtLogin;
private Label lblPassword;
private TextBox txtPassword;
private Button btnLogin;
private Button btnGuest;
}
9. src/DemoExamTemplate/Forms/LoginForm.cs
using DemoExamTemplate.Models;
using DemoExamTemplate.Services;
using DemoExamTemplate.UI;
namespace DemoExamTemplate.Forms;
public partial class LoginForm : Form
{
private readonly AuthService _authService = new AuthService();
public LoginForm()
{
InitializeComponent();
}
private async void btnLogin_Click(object? sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtLogin.Text) || string.IsNullOrWhiteSpace(txtPassword.Text))
{
MessageBox.Show("Введите логин и пароль.", "Авторизация", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
try
{
UserSession? session = await _authService.LoginAsync(txtLogin.Text, txtPassword.Text);
if (session is null)
{
MessageBox.Show("Неверный логин или пароль.", "Авторизация", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
OpenMain(session);
}
catch (Exception ex)
{
UiHelper.ShowError(ex);
}
}
private void btnGuest_Click(object? sender, EventArgs e)
{
OpenMain(UserSession.Guest());
}
private void OpenMain(UserSession session)
{
Hide();
using MainForm form = new MainForm(session);
form.ShowDialog(this);
Show();
txtPassword.Clear();
}
}
10. src/DemoExamTemplate/Forms/LoginForm.resx
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" use="required" type="xsd:string" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element>
<xsd:element name="assembly"><xsd:complexType><xsd:attribute name="alias" type="xsd:string" /><xsd:attribute name="name" type="xsd:string" /></xsd:complexType></xsd:element>
<xsd:element name="data"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /><xsd:element name="comment" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element>
<xsd:element name="resheader"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /></xsd:complexType></xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
<resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
<resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
</root>
11. src/DemoExamTemplate/Forms/MainForm.Designer.cs
namespace DemoExamTemplate.Forms;
partial class MainForm
{
private System.ComponentModel.IContainer? components = null;
protected override void Dispose(bool disposing)
{
if (disposing && components != null) components.Dispose();
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
DataGridViewCellStyle headerStyle = new DataGridViewCellStyle();
pnlHeader = new Panel();
lblUser = new Label();
lblTitle = new Label();
picLogo = new PictureBox();
pnlTools = new FlowLayoutPanel();
lblSearch = new Label();
txtSearch = new TextBox();
lblCategory = new Label();
cmbCategory = new ComboBox();
lblSort = new Label();
cmbSort = new ComboBox();
btnAdd = new Button();
btnEdit = new Button();
btnDelete = new Button();
btnOrders = new Button();
dgvProducts = new DataGridView();
colArticle = new DataGridViewTextBoxColumn();
colName = new DataGridViewTextBoxColumn();
colCategory = new DataGridViewTextBoxColumn();
colPrice = new DataGridViewTextBoxColumn();
colDiscount = new DataGridViewTextBoxColumn();
colQuantity = new DataGridViewTextBoxColumn();
pnlHeader.SuspendLayout();
((System.ComponentModel.ISupportInitialize)picLogo).BeginInit();
pnlTools.SuspendLayout();
((System.ComponentModel.ISupportInitialize)dgvProducts).BeginInit();
SuspendLayout();
// pnlHeader
pnlHeader.BackColor = Color.Chartreuse;
pnlHeader.Controls.Add(lblUser);
pnlHeader.Controls.Add(lblTitle);
pnlHeader.Controls.Add(picLogo);
pnlHeader.Dock = DockStyle.Top;
pnlHeader.Location = new Point(0, 0);
pnlHeader.Name = "pnlHeader";
pnlHeader.Size = new Size(1180, 95);
// picLogo
picLogo.BackColor = Color.White;
picLogo.Location = new Point(15, 13);
picLogo.Name = "picLogo";
picLogo.Size = new Size(70, 70);
picLogo.SizeMode = PictureBoxSizeMode.Zoom;
picLogo.TabStop = false;
// lblTitle
lblTitle.AutoSize = true;
lblTitle.Font = new Font("Times New Roman", 20F, FontStyle.Bold);
lblTitle.Location = new Point(105, 20);
lblTitle.Name = "lblTitle";
lblTitle.Size = new Size(208, 31);
lblTitle.Text = "Каталог товаров";
// lblUser
lblUser.AutoSize = true;
lblUser.Location = new Point(108, 59);
lblUser.Name = "lblUser";
lblUser.Size = new Size(94, 17);
lblUser.Text = "Пользователь";
// pnlTools
pnlTools.AutoSize = true;
pnlTools.Controls.Add(lblSearch);
pnlTools.Controls.Add(txtSearch);
pnlTools.Controls.Add(lblCategory);
pnlTools.Controls.Add(cmbCategory);
pnlTools.Controls.Add(lblSort);
pnlTools.Controls.Add(cmbSort);
pnlTools.Dock = DockStyle.Top;
pnlTools.Location = new Point(0, 95);
pnlTools.Name = "pnlTools";
pnlTools.Padding = new Padding(10);
pnlTools.Size = new Size(1180, 50);
pnlTools.WrapContents = false;
// lblSearch
lblSearch.AutoSize = true;
lblSearch.Margin = new Padding(3, 7, 3, 0);
lblSearch.Text = "Поиск:";
// txtSearch
txtSearch.Margin = new Padding(3, 3, 15, 3);
txtSearch.Size = new Size(250, 24);
txtSearch.TextChanged += txtSearch_TextChanged;
// lblCategory
lblCategory.AutoSize = true;
lblCategory.Margin = new Padding(3, 7, 3, 0);
lblCategory.Text = "Категория:";
// cmbCategory
cmbCategory.DropDownStyle = ComboBoxStyle.DropDownList;
cmbCategory.Margin = new Padding(3, 3, 15, 3);
cmbCategory.Size = new Size(190, 25);
cmbCategory.SelectedIndexChanged += cmbTools_SelectedIndexChanged;
// lblSort
lblSort.AutoSize = true;
lblSort.Margin = new Padding(3, 7, 3, 0);
lblSort.Text = "Сортировка:";
// cmbSort
cmbSort.DropDownStyle = ComboBoxStyle.DropDownList;
cmbSort.Size = new Size(220, 25);
cmbSort.SelectedIndexChanged += cmbTools_SelectedIndexChanged;
// buttons
btnAdd.Anchor = AnchorStyles.Top | AnchorStyles.Right;
btnAdd.BackColor = Color.MediumSpringGreen;
btnAdd.FlatStyle = FlatStyle.Flat;
btnAdd.Location = new Point(604, 153);
btnAdd.Name = "btnAdd";
btnAdd.Size = new Size(130, 38);
btnAdd.Text = "Добавить";
btnAdd.UseVisualStyleBackColor = false;
btnAdd.Click += btnAdd_Click;
btnEdit.Anchor = AnchorStyles.Top | AnchorStyles.Right;
btnEdit.BackColor = Color.Chartreuse;
btnEdit.FlatStyle = FlatStyle.Flat;
btnEdit.Location = new Point(744, 153);
btnEdit.Name = "btnEdit";
btnEdit.Size = new Size(130, 38);
btnEdit.Text = "Редактировать";
btnEdit.UseVisualStyleBackColor = false;
btnEdit.Click += btnEdit_Click;
btnDelete.Anchor = AnchorStyles.Top | AnchorStyles.Right;
btnDelete.BackColor = Color.Chartreuse;
btnDelete.FlatStyle = FlatStyle.Flat;
btnDelete.Location = new Point(884, 153);
btnDelete.Name = "btnDelete";
btnDelete.Size = new Size(130, 38);
btnDelete.Text = "Удалить";
btnDelete.UseVisualStyleBackColor = false;
btnDelete.Click += btnDelete_Click;
btnOrders.Anchor = AnchorStyles.Top | AnchorStyles.Right;
btnOrders.BackColor = Color.MediumSpringGreen;
btnOrders.FlatStyle = FlatStyle.Flat;
btnOrders.Location = new Point(1024, 153);
btnOrders.Name = "btnOrders";
btnOrders.Size = new Size(130, 38);
btnOrders.Text = "Заказы";
btnOrders.UseVisualStyleBackColor = false;
btnOrders.Click += btnOrders_Click;
// dgvProducts
dgvProducts.AllowUserToAddRows = false;
dgvProducts.AllowUserToDeleteRows = false;
dgvProducts.AllowUserToResizeRows = false;
dgvProducts.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
dgvProducts.AutoGenerateColumns = false;
dgvProducts.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
dgvProducts.BackgroundColor = Color.White;
headerStyle.BackColor = Color.Chartreuse;
headerStyle.Font = new Font("Times New Roman", 11F, FontStyle.Bold);
headerStyle.ForeColor = Color.Black;
dgvProducts.ColumnHeadersDefaultCellStyle = headerStyle;
dgvProducts.ColumnHeadersHeight = 42;
dgvProducts.Columns.AddRange(new DataGridViewColumn[] { colArticle, colName, colCategory, colPrice, colDiscount, colQuantity });
dgvProducts.EnableHeadersVisualStyles = false;
dgvProducts.Location = new Point(15, 202);
dgvProducts.MultiSelect = false;
dgvProducts.Name = "dgvProducts";
dgvProducts.ReadOnly = true;
dgvProducts.RowHeadersVisible = false;
dgvProducts.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgvProducts.Size = new Size(1140, 505);
dgvProducts.RowPrePaint += dgvProducts_RowPrePaint;
colArticle.DataPropertyName = "Article"; colArticle.HeaderText = "Артикул"; colArticle.Name = "colArticle";
colName.DataPropertyName = "Name"; colName.HeaderText = "Наименование"; colName.Name = "colName";
colCategory.DataPropertyName = "CategoryName"; colCategory.HeaderText = "Категория"; colCategory.Name = "colCategory";
colPrice.DataPropertyName = "Price"; colPrice.HeaderText = "Цена"; colPrice.Name = "colPrice"; colPrice.DefaultCellStyle.Format = "N2";
colDiscount.DataPropertyName = "Discount"; colDiscount.HeaderText = "Скидка, %"; colDiscount.Name = "colDiscount";
colQuantity.DataPropertyName = "Quantity"; colQuantity.HeaderText = "Количество"; colQuantity.Name = "colQuantity";
// MainForm
AutoScaleDimensions = new SizeF(8F, 17F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.White;
ClientSize = new Size(1180, 730);
Controls.Add(dgvProducts);
Controls.Add(btnOrders);
Controls.Add(btnDelete);
Controls.Add(btnEdit);
Controls.Add(btnAdd);
Controls.Add(pnlTools);
Controls.Add(pnlHeader);
Font = new Font("Times New Roman", 11F);
MinimumSize = new Size(1000, 650);
Name = "MainForm";
StartPosition = FormStartPosition.CenterScreen;
Text = "Каталог товаров";
Shown += MainForm_Shown;
pnlHeader.ResumeLayout(false);
pnlHeader.PerformLayout();
((System.ComponentModel.ISupportInitialize)picLogo).EndInit();
pnlTools.ResumeLayout(false);
pnlTools.PerformLayout();
((System.ComponentModel.ISupportInitialize)dgvProducts).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private Panel pnlHeader; private PictureBox picLogo; private Label lblTitle; private Label lblUser;
private FlowLayoutPanel pnlTools; private Label lblSearch; private TextBox txtSearch; private Label lblCategory; private ComboBox cmbCategory; private Label lblSort; private ComboBox cmbSort;
private Button btnAdd; private Button btnEdit; private Button btnDelete; private Button btnOrders;
private DataGridView dgvProducts; private DataGridViewTextBoxColumn colArticle; private DataGridViewTextBoxColumn colName; private DataGridViewTextBoxColumn colCategory; private DataGridViewTextBoxColumn colPrice; private DataGridViewTextBoxColumn colDiscount; private DataGridViewTextBoxColumn colQuantity;
}
12. src/DemoExamTemplate/Forms/MainForm.cs
using DemoExamTemplate.Models;
using DemoExamTemplate.Services;
using DemoExamTemplate.UI;
namespace DemoExamTemplate.Forms;
public partial class MainForm : Form
{
private sealed class SortOption
{
public string Name { get; }
public string Code { get; }
public SortOption(string name, string code) { Name = name; Code = code; }
public override string ToString() => Name;
}
private readonly UserSession _session;
private readonly ProductService _productService = new ProductService();
public MainForm(UserSession session)
{
_session = session;
InitializeComponent();
UiHelper.LoadLogo(picLogo);
lblUser.Text = _session.FullName + " — " + _session.RoleName;
pnlTools.Visible = _session.CanUseProductTools;
btnAdd.Visible = _session.CanEditProducts;
btnEdit.Visible = _session.CanEditProducts;
btnDelete.Visible = _session.CanEditProducts;
btnOrders.Visible = _session.CanViewOrders;
if (!_session.CanUseProductTools)
{
dgvProducts.Top = 110;
dgvProducts.Height += 92;
}
}
private async void MainForm_Shown(object? sender, EventArgs e)
{
try
{
if (_session.CanUseProductTools)
{
List<Category> categories = await _productService.GetCategoriesAsync();
categories.Insert(0, new Category { Id = 0, Name = "Все категории" });
cmbCategory.DataSource = categories;
cmbSort.DataSource = new List<SortOption>
{
new SortOption("Название: А–Я", "NAME_ASC"),
new SortOption("Название: Я–А", "NAME_DESC"),
new SortOption("Цена: по возрастанию", "PRICE_ASC"),
new SortOption("Цена: по убыванию", "PRICE_DESC")
};
}
await LoadProductsAsync();
}
catch (Exception ex) { UiHelper.ShowError(ex); }
}
private async Task LoadProductsAsync()
{
string? search = _session.CanUseProductTools ? txtSearch.Text : null;
int? categoryId = _session.CanUseProductTools && cmbCategory.SelectedItem is Category category && category.Id > 0 ? category.Id : null;
string sort = _session.CanUseProductTools && cmbSort.SelectedItem is SortOption option ? option.Code : "NAME_ASC";
dgvProducts.DataSource = await _productService.GetProductsAsync(search, categoryId, sort);
}
private async void txtSearch_TextChanged(object? sender, EventArgs e) => await LoadProductsSafeAsync();
private async void cmbTools_SelectedIndexChanged(object? sender, EventArgs e) { if (CanReload) await LoadProductsSafeAsync(); }
private bool CanReload => IsHandleCreated && Visible;
private async Task LoadProductsSafeAsync()
{
try { await LoadProductsAsync(); }
catch (Exception ex) { UiHelper.ShowError(ex); }
}
private Product? SelectedProduct => dgvProducts.CurrentRow?.DataBoundItem as Product;
private async void btnAdd_Click(object? sender, EventArgs e)
{
using ProductEditForm form = new ProductEditForm();
if (form.ShowDialog(this) == DialogResult.OK) await LoadProductsSafeAsync();
}
private async void btnEdit_Click(object? sender, EventArgs e)
{
Product? product = SelectedProduct;
if (product is null) { MessageBox.Show("Выберите товар."); return; }
using ProductEditForm form = new ProductEditForm(product.Id);
if (form.ShowDialog(this) == DialogResult.OK) await LoadProductsSafeAsync();
}
private async void btnDelete_Click(object? sender, EventArgs e)
{
Product? product = SelectedProduct;
if (product is null) { MessageBox.Show("Выберите товар."); return; }
if (MessageBox.Show("Удалить выбранный товар?", "Удаление", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) return;
try { await _productService.DeleteAsync(product.Id); await LoadProductsAsync(); }
catch (Exception ex) { UiHelper.ShowError(ex); }
}
private void btnOrders_Click(object? sender, EventArgs e)
{
using OrdersForm form = new OrdersForm(_session);
form.ShowDialog(this);
}
private void dgvProducts_RowPrePaint(object? sender, DataGridViewRowPrePaintEventArgs e)
{
DataGridViewRow row = dgvProducts.Rows[e.RowIndex];
row.DefaultCellStyle.BackColor = Color.White;
row.DefaultCellStyle.ForeColor = Color.Black;
if (row.DataBoundItem is Product product && product.Discount > 15)
{
row.DefaultCellStyle.BackColor = Color.SeaGreen;
row.DefaultCellStyle.ForeColor = Color.White;
}
}
}
13. src/DemoExamTemplate/Forms/MainForm.resx
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" use="required" type="xsd:string" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element>
<xsd:element name="assembly"><xsd:complexType><xsd:attribute name="alias" type="xsd:string" /><xsd:attribute name="name" type="xsd:string" /></xsd:complexType></xsd:element>
<xsd:element name="data"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /><xsd:element name="comment" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element>
<xsd:element name="resheader"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /></xsd:complexType></xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
<resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
<resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
</root>
14. src/DemoExamTemplate/Forms/OrderEditForm.Designer.cs
namespace DemoExamTemplate.Forms;
partial class OrderEditForm
{
private System.ComponentModel.IContainer? components = null;
protected override void Dispose(bool disposing) { if (disposing && components != null) components.Dispose(); base.Dispose(disposing); }
#region Windows Form Designer generated code
private void InitializeComponent()
{
DataGridViewCellStyle headerStyle = new DataGridViewCellStyle();
lblTitle = new Label(); tableRoot = new TableLayoutPanel(); tableFields = new TableLayoutPanel();
lblNumber = new Label(); txtNumber = new TextBox(); lblDate = new Label(); dtpDate = new DateTimePicker(); lblCustomer = new Label(); txtCustomer = new TextBox(); lblStatus = new Label(); cmbStatus = new ComboBox();
pnlAddItem = new FlowLayoutPanel(); lblProduct = new Label(); cmbProduct = new ComboBox(); lblQuantity = new Label(); nudQuantity = new NumericUpDown(); btnAddItem = new Button(); btnRemoveItem = new Button();
dgvItems = new DataGridView(); colArticle = new DataGridViewTextBoxColumn(); colProduct = new DataGridViewTextBoxColumn(); colQuantity = new DataGridViewTextBoxColumn(); colPrice = new DataGridViewTextBoxColumn(); colDiscount = new DataGridViewTextBoxColumn(); colTotal = new DataGridViewTextBoxColumn();
pnlButtons = new FlowLayoutPanel(); lblTotal = new Label(); btnSave = new Button(); btnCancel = new Button();
tableRoot.SuspendLayout(); tableFields.SuspendLayout(); pnlAddItem.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)nudQuantity).BeginInit(); ((System.ComponentModel.ISupportInitialize)dgvItems).BeginInit(); pnlButtons.SuspendLayout(); SuspendLayout();
lblTitle.Name = "lblTitle"; lblTitle.AutoSize = true; lblTitle.Font = new Font("Times New Roman", 20F, FontStyle.Bold); lblTitle.Location = new Point(20, 20); lblTitle.Text = "Добавление заказа";
tableRoot.Name = "tableRoot"; tableRoot.ColumnCount = 1; tableRoot.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); tableRoot.Controls.Add(tableFields, 0, 0); tableRoot.Controls.Add(pnlAddItem, 0, 1); tableRoot.Controls.Add(dgvItems, 0, 2); tableRoot.Controls.Add(pnlButtons, 0, 3); tableRoot.Location = new Point(20, 70); tableRoot.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; tableRoot.Padding = new Padding(0); tableRoot.RowCount = 4; tableRoot.RowStyles.Add(new RowStyle(SizeType.Absolute, 100F)); tableRoot.RowStyles.Add(new RowStyle(SizeType.Absolute, 65F)); tableRoot.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); tableRoot.RowStyles.Add(new RowStyle(SizeType.Absolute, 55F)); tableRoot.Size = new Size(1040, 590);
tableFields.Name = "tableFields"; tableFields.ColumnCount = 4; tableFields.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 120F)); tableFields.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); tableFields.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 120F)); tableFields.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); tableFields.Dock = DockStyle.Fill; tableFields.RowCount = 2; tableFields.RowStyles.Add(new RowStyle(SizeType.Percent, 50F)); tableFields.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
tableFields.Controls.Add(lblNumber, 0, 0); tableFields.Controls.Add(txtNumber, 1, 0); tableFields.Controls.Add(lblDate, 2, 0); tableFields.Controls.Add(dtpDate, 3, 0); tableFields.Controls.Add(lblCustomer, 0, 1); tableFields.Controls.Add(txtCustomer, 1, 1); tableFields.Controls.Add(lblStatus, 2, 1); tableFields.Controls.Add(cmbStatus, 3, 1);
lblNumber.Name = "lblNumber"; lblNumber.Anchor = AnchorStyles.Left; lblNumber.AutoSize = true; lblNumber.Text = "Номер*"; txtNumber.Name = "txtNumber"; txtNumber.Anchor = AnchorStyles.Left | AnchorStyles.Right; txtNumber.Size = new Size(390, 24);
lblDate.Name = "lblDate"; lblDate.Anchor = AnchorStyles.Left; lblDate.AutoSize = true; lblDate.Text = "Дата*"; dtpDate.Name = "dtpDate"; dtpDate.Anchor = AnchorStyles.Left | AnchorStyles.Right; dtpDate.Format = DateTimePickerFormat.Short;
lblCustomer.Name = "lblCustomer"; lblCustomer.Anchor = AnchorStyles.Left; lblCustomer.AutoSize = true; lblCustomer.Text = "Клиент*"; txtCustomer.Name = "txtCustomer"; txtCustomer.Anchor = AnchorStyles.Left | AnchorStyles.Right;
lblStatus.Name = "lblStatus"; lblStatus.Anchor = AnchorStyles.Left; lblStatus.AutoSize = true; lblStatus.Text = "Статус*"; cmbStatus.Name = "cmbStatus"; cmbStatus.Anchor = AnchorStyles.Left | AnchorStyles.Right; cmbStatus.DropDownStyle = ComboBoxStyle.DropDownList; cmbStatus.Items.AddRange(new object[] { "Новый", "В обработке", "Завершён", "Отменён" });
pnlAddItem.Name = "pnlAddItem"; pnlAddItem.Dock = DockStyle.Fill; pnlAddItem.Padding = new Padding(0, 10, 0, 5); pnlAddItem.WrapContents = false; pnlAddItem.Controls.AddRange(new Control[] { lblProduct, cmbProduct, lblQuantity, nudQuantity, btnAddItem, btnRemoveItem });
lblProduct.Name = "lblProduct"; lblProduct.AutoSize = true; lblProduct.Margin = new Padding(3, 8, 3, 3); lblProduct.Text = "Товар:"; cmbProduct.Name = "cmbProduct"; cmbProduct.DropDownStyle = ComboBoxStyle.DropDownList; cmbProduct.Margin = new Padding(3, 4, 15, 3); cmbProduct.Size = new Size(330, 25);
lblQuantity.Name = "lblQuantity"; lblQuantity.AutoSize = true; lblQuantity.Margin = new Padding(3, 8, 3, 3); lblQuantity.Text = "Количество:"; nudQuantity.Name = "nudQuantity"; nudQuantity.Margin = new Padding(3, 4, 15, 3); nudQuantity.Minimum = new decimal(new int[] { 1, 0, 0, 0 }); nudQuantity.Maximum = new decimal(new int[] { 10000, 0, 0, 0 }); nudQuantity.Value = new decimal(new int[] { 1, 0, 0, 0 }); nudQuantity.Size = new Size(90, 24);
btnAddItem.Name = "btnAddItem"; btnAddItem.BackColor = Color.MediumSpringGreen; btnAddItem.FlatStyle = FlatStyle.Flat; btnAddItem.Margin = new Padding(3, 0, 10, 0); btnAddItem.Size = new Size(130, 38); btnAddItem.Text = "Добавить"; btnAddItem.UseVisualStyleBackColor = false; btnAddItem.Click += btnAddItem_Click;
btnRemoveItem.Name = "btnRemoveItem"; btnRemoveItem.BackColor = Color.Chartreuse; btnRemoveItem.FlatStyle = FlatStyle.Flat; btnRemoveItem.Margin = new Padding(3, 0, 0, 0); btnRemoveItem.Size = new Size(130, 38); btnRemoveItem.Text = "Удалить"; btnRemoveItem.UseVisualStyleBackColor = false; btnRemoveItem.Click += btnRemoveItem_Click;
dgvItems.Name = "dgvItems"; dgvItems.AllowUserToAddRows = false; dgvItems.AllowUserToDeleteRows = false; dgvItems.AutoGenerateColumns = false; dgvItems.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; dgvItems.BackgroundColor = Color.White; headerStyle.BackColor = Color.Chartreuse; headerStyle.Font = new Font("Times New Roman", 11F, FontStyle.Bold); dgvItems.ColumnHeadersDefaultCellStyle = headerStyle; dgvItems.ColumnHeadersHeight = 40; dgvItems.EnableHeadersVisualStyles = false; dgvItems.Dock = DockStyle.Fill; dgvItems.ReadOnly = true; dgvItems.RowHeadersVisible = false; dgvItems.SelectionMode = DataGridViewSelectionMode.FullRowSelect; dgvItems.Columns.AddRange(new DataGridViewColumn[] { colArticle, colProduct, colQuantity, colPrice, colDiscount, colTotal });
colArticle.Name = "colArticle"; colArticle.DataPropertyName = "Article"; colArticle.HeaderText = "Артикул"; colProduct.Name = "colProduct"; colProduct.DataPropertyName = "ProductName"; colProduct.HeaderText = "Товар"; colQuantity.Name = "colQuantity"; colQuantity.DataPropertyName = "Quantity"; colQuantity.HeaderText = "Количество"; colPrice.Name = "colPrice"; colPrice.DataPropertyName = "Price"; colPrice.HeaderText = "Цена"; colPrice.DefaultCellStyle.Format = "N2"; colDiscount.Name = "colDiscount"; colDiscount.DataPropertyName = "Discount"; colDiscount.HeaderText = "Скидка, %"; colTotal.Name = "colTotal"; colTotal.DataPropertyName = "LineTotal"; colTotal.HeaderText = "Сумма"; colTotal.DefaultCellStyle.Format = "N2";
pnlButtons.Name = "pnlButtons"; pnlButtons.Dock = DockStyle.Fill; pnlButtons.FlowDirection = FlowDirection.RightToLeft; pnlButtons.Padding = new Padding(0, 7, 0, 0); pnlButtons.WrapContents = false; pnlButtons.Controls.AddRange(new Control[] { btnCancel, btnSave, lblTotal });
btnCancel.Name = "btnCancel"; btnCancel.BackColor = Color.Chartreuse; btnCancel.FlatStyle = FlatStyle.Flat; btnCancel.Margin = new Padding(5, 0, 0, 0); btnCancel.Size = new Size(130, 38); btnCancel.Text = "Отмена"; btnCancel.UseVisualStyleBackColor = false; btnCancel.Click += btnCancel_Click;
btnSave.Name = "btnSave"; btnSave.BackColor = Color.MediumSpringGreen; btnSave.FlatStyle = FlatStyle.Flat; btnSave.Margin = new Padding(5, 0, 5, 0); btnSave.Size = new Size(130, 38); btnSave.Text = "Сохранить"; btnSave.UseVisualStyleBackColor = false; btnSave.Click += btnSave_Click;
lblTotal.Name = "lblTotal"; lblTotal.AutoSize = true; lblTotal.Font = new Font("Times New Roman", 13F, FontStyle.Bold); lblTotal.Margin = new Padding(10, 10, 20, 0); lblTotal.Text = "Итого: 0,00";
AutoScaleDimensions = new SizeF(8F, 17F); AutoScaleMode = AutoScaleMode.Font; BackColor = Color.White; ClientSize = new Size(1080, 680); Controls.Add(tableRoot); Controls.Add(lblTitle); Font = new Font("Times New Roman", 11F); MinimumSize = new Size(950, 650); Name = "OrderEditForm"; StartPosition = FormStartPosition.CenterParent; Text = "Добавление заказа"; Shown += OrderEditForm_Shown;
tableRoot.ResumeLayout(false); tableFields.ResumeLayout(false); tableFields.PerformLayout(); pnlAddItem.ResumeLayout(false); pnlAddItem.PerformLayout(); ((System.ComponentModel.ISupportInitialize)nudQuantity).EndInit(); ((System.ComponentModel.ISupportInitialize)dgvItems).EndInit(); pnlButtons.ResumeLayout(false); pnlButtons.PerformLayout(); ResumeLayout(false); PerformLayout();
}
#endregion
private Label lblTitle; private TableLayoutPanel tableRoot; private TableLayoutPanel tableFields; private Label lblNumber; private TextBox txtNumber; private Label lblDate; private DateTimePicker dtpDate; private Label lblCustomer; private TextBox txtCustomer; private Label lblStatus; private ComboBox cmbStatus; private FlowLayoutPanel pnlAddItem; private Label lblProduct; private ComboBox cmbProduct; private Label lblQuantity; private NumericUpDown nudQuantity; private Button btnAddItem; private Button btnRemoveItem; private DataGridView dgvItems; private DataGridViewTextBoxColumn colArticle; private DataGridViewTextBoxColumn colProduct; private DataGridViewTextBoxColumn colQuantity; private DataGridViewTextBoxColumn colPrice; private DataGridViewTextBoxColumn colDiscount; private DataGridViewTextBoxColumn colTotal; private FlowLayoutPanel pnlButtons; private Label lblTotal; private Button btnSave; private Button btnCancel;
}
15. src/DemoExamTemplate/Forms/OrderEditForm.cs
using DemoExamTemplate.Models;
using DemoExamTemplate.Services;
using DemoExamTemplate.UI;
namespace DemoExamTemplate.Forms;
public partial class OrderEditForm : Form
{
private readonly int _orderId;
private readonly ProductService _productService = new ProductService();
private readonly OrderService _orderService = new OrderService();
private readonly BindingSource _itemsSource = new BindingSource();
private readonly List<OrderItemData> _items = new List<OrderItemData>();
public OrderEditForm(int orderId = 0)
{
_orderId = orderId;
InitializeComponent();
Text = orderId == 0 ? "Добавление заказа" : "Редактирование заказа";
lblTitle.Text = Text;
_itemsSource.DataSource = _items;
dgvItems.DataSource = _itemsSource;
}
private async void OrderEditForm_Shown(object? sender, EventArgs e)
{
try
{
cmbProduct.DataSource = await _productService.GetProductsAsync(null, null, "NAME_ASC");
cmbProduct.DisplayMember = nameof(Product.Name);
cmbStatus.SelectedIndex = 0;
if (_orderId == 0) return;
OrderData? order = await _orderService.GetByIdAsync(_orderId);
if (order is null) return;
txtNumber.Text = order.Number;
dtpDate.Value = order.OrderDate;
txtCustomer.Text = order.Customer;
cmbStatus.SelectedItem = order.Status;
_items.AddRange(order.Items);
RefreshItems();
}
catch (Exception ex) { UiHelper.ShowError(ex); }
}
private void btnAddItem_Click(object? sender, EventArgs e)
{
if (cmbProduct.SelectedItem is not Product product) return;
OrderItemData? existing = _items.FirstOrDefault(x => x.ProductId == product.Id);
if (existing is null)
{
_items.Add(new OrderItemData
{
ProductId = product.Id, Article = product.Article, ProductName = product.Name,
Quantity = (int)nudQuantity.Value, Price = product.Price, Discount = product.Discount
});
}
else existing.Quantity += (int)nudQuantity.Value;
RefreshItems();
}
private void btnRemoveItem_Click(object? sender, EventArgs e)
{
if (dgvItems.CurrentRow?.DataBoundItem is OrderItemData item)
{
_items.Remove(item);
RefreshItems();
}
}
private void RefreshItems()
{
_itemsSource.ResetBindings(false);
lblTotal.Text = "Итого: " + _items.Sum(x => x.LineTotal).ToString("N2");
}
private async void btnSave_Click(object? sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtNumber.Text) || string.IsNullOrWhiteSpace(txtCustomer.Text) || cmbStatus.SelectedItem is null || _items.Count == 0)
{
MessageBox.Show("Заполните поля и добавьте хотя бы один товар.");
return;
}
try
{
await _orderService.SaveAsync(new OrderData
{
Id = _orderId, Number = txtNumber.Text, OrderDate = dtpDate.Value,
Customer = txtCustomer.Text, Status = cmbStatus.SelectedItem.ToString()!, Items = _items
});
DialogResult = DialogResult.OK;
Close();
}
catch (Exception ex) { UiHelper.ShowError(ex); }
}
private void btnCancel_Click(object? sender, EventArgs e) => Close();
}
16. src/DemoExamTemplate/Forms/OrderEditForm.resx
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" use="required" type="xsd:string" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element>
<xsd:element name="assembly"><xsd:complexType><xsd:attribute name="alias" type="xsd:string" /><xsd:attribute name="name" type="xsd:string" /></xsd:complexType></xsd:element>
<xsd:element name="data"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /><xsd:element name="comment" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element>
<xsd:element name="resheader"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /></xsd:complexType></xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
<resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
<resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
</root>
17. src/DemoExamTemplate/Forms/OrdersForm.Designer.cs
namespace DemoExamTemplate.Forms;
partial class OrdersForm
{
private System.ComponentModel.IContainer? components = null;
protected override void Dispose(bool disposing) { if (disposing && components != null) components.Dispose(); base.Dispose(disposing); }
#region Windows Form Designer generated code
private void InitializeComponent()
{
DataGridViewCellStyle headerStyle = new DataGridViewCellStyle();
lblTitle = new Label(); btnAdd = new Button(); btnEdit = new Button(); btnDelete = new Button(); dgvOrders = new DataGridView();
colNumber = new DataGridViewTextBoxColumn(); colDate = new DataGridViewTextBoxColumn(); colCustomer = new DataGridViewTextBoxColumn(); colStatus = new DataGridViewTextBoxColumn(); colTotal = new DataGridViewTextBoxColumn();
((System.ComponentModel.ISupportInitialize)dgvOrders).BeginInit(); SuspendLayout();
lblTitle.Name = "lblTitle"; lblTitle.AutoSize = true; lblTitle.Font = new Font("Times New Roman", 20F, FontStyle.Bold); lblTitle.Location = new Point(20, 22); lblTitle.Text = "Заказы";
btnAdd.Name = "btnAdd"; btnAdd.Anchor = AnchorStyles.Top | AnchorStyles.Right; btnAdd.BackColor = Color.MediumSpringGreen; btnAdd.FlatStyle = FlatStyle.Flat; btnAdd.Location = new Point(565, 20); btnAdd.Size = new Size(130, 38); btnAdd.Text = "Добавить"; btnAdd.UseVisualStyleBackColor = false; btnAdd.Click += btnAdd_Click;
btnEdit.Name = "btnEdit"; btnEdit.Anchor = AnchorStyles.Top | AnchorStyles.Right; btnEdit.BackColor = Color.Chartreuse; btnEdit.FlatStyle = FlatStyle.Flat; btnEdit.Location = new Point(705, 20); btnEdit.Size = new Size(130, 38); btnEdit.Text = "Редактировать"; btnEdit.UseVisualStyleBackColor = false; btnEdit.Click += btnEdit_Click;
btnDelete.Name = "btnDelete"; btnDelete.Anchor = AnchorStyles.Top | AnchorStyles.Right; btnDelete.BackColor = Color.Chartreuse; btnDelete.FlatStyle = FlatStyle.Flat; btnDelete.Location = new Point(845, 20); btnDelete.Size = new Size(130, 38); btnDelete.Text = "Удалить"; btnDelete.UseVisualStyleBackColor = false; btnDelete.Click += btnDelete_Click;
dgvOrders.Name = "dgvOrders"; dgvOrders.AllowUserToAddRows = false; dgvOrders.AllowUserToDeleteRows = false; dgvOrders.AutoGenerateColumns = false; dgvOrders.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; dgvOrders.BackgroundColor = Color.White;
headerStyle.BackColor = Color.Chartreuse; headerStyle.Font = new Font("Times New Roman", 11F, FontStyle.Bold); dgvOrders.ColumnHeadersDefaultCellStyle = headerStyle; dgvOrders.ColumnHeadersHeight = 42; dgvOrders.EnableHeadersVisualStyles = false;
dgvOrders.Columns.AddRange(new DataGridViewColumn[] { colNumber, colDate, colCustomer, colStatus, colTotal }); dgvOrders.Location = new Point(20, 78); dgvOrders.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; dgvOrders.MultiSelect = false; dgvOrders.ReadOnly = true; dgvOrders.RowHeadersVisible = false; dgvOrders.SelectionMode = DataGridViewSelectionMode.FullRowSelect; dgvOrders.Size = new Size(955, 500);
colNumber.Name = "colNumber"; colNumber.DataPropertyName = "Number"; colNumber.HeaderText = "Номер"; colDate.Name = "colDate"; colDate.DataPropertyName = "OrderDate"; colDate.HeaderText = "Дата"; colDate.DefaultCellStyle.Format = "d"; colCustomer.Name = "colCustomer"; colCustomer.DataPropertyName = "Customer"; colCustomer.HeaderText = "Клиент"; colStatus.Name = "colStatus"; colStatus.DataPropertyName = "Status"; colStatus.HeaderText = "Статус"; colTotal.Name = "colTotal"; colTotal.DataPropertyName = "Total"; colTotal.HeaderText = "Сумма"; colTotal.DefaultCellStyle.Format = "N2";
AutoScaleDimensions = new SizeF(8F, 17F); AutoScaleMode = AutoScaleMode.Font; BackColor = Color.White; ClientSize = new Size(995, 600); Controls.Add(dgvOrders); Controls.Add(btnDelete); Controls.Add(btnEdit); Controls.Add(btnAdd); Controls.Add(lblTitle); Font = new Font("Times New Roman", 11F); MinimumSize = new Size(850, 550); Name = "OrdersForm"; StartPosition = FormStartPosition.CenterParent; Text = "Заказы"; Shown += OrdersForm_Shown;
((System.ComponentModel.ISupportInitialize)dgvOrders).EndInit(); ResumeLayout(false); PerformLayout();
}
#endregion
private Label lblTitle; private Button btnAdd; private Button btnEdit; private Button btnDelete; private DataGridView dgvOrders; private DataGridViewTextBoxColumn colNumber; private DataGridViewTextBoxColumn colDate; private DataGridViewTextBoxColumn colCustomer; private DataGridViewTextBoxColumn colStatus; private DataGridViewTextBoxColumn colTotal;
}
18. src/DemoExamTemplate/Forms/OrdersForm.cs
using DemoExamTemplate.Models;
using DemoExamTemplate.Services;
using DemoExamTemplate.UI;
namespace DemoExamTemplate.Forms;
public partial class OrdersForm : Form
{
private readonly UserSession _session;
private readonly OrderService _service = new OrderService();
public OrdersForm(UserSession session)
{
_session = session;
InitializeComponent();
btnAdd.Visible = session.CanEditOrders;
btnEdit.Visible = session.CanEditOrders;
btnDelete.Visible = session.CanEditOrders;
}
private async void OrdersForm_Shown(object? sender, EventArgs e) => await LoadSafeAsync();
private async Task LoadSafeAsync()
{
try { dgvOrders.DataSource = await _service.GetOrdersAsync(); }
catch (Exception ex) { UiHelper.ShowError(ex); }
}
private OrderListItem? SelectedOrder => dgvOrders.CurrentRow?.DataBoundItem as OrderListItem;
private async void btnAdd_Click(object? sender, EventArgs e)
{
using OrderEditForm form = new OrderEditForm();
if (form.ShowDialog(this) == DialogResult.OK) await LoadSafeAsync();
}
private async void btnEdit_Click(object? sender, EventArgs e)
{
OrderListItem? order = SelectedOrder;
if (order is null) { MessageBox.Show("Выберите заказ."); return; }
using OrderEditForm form = new OrderEditForm(order.Id);
if (form.ShowDialog(this) == DialogResult.OK) await LoadSafeAsync();
}
private async void btnDelete_Click(object? sender, EventArgs e)
{
OrderListItem? order = SelectedOrder;
if (order is null) { MessageBox.Show("Выберите заказ."); return; }
if (MessageBox.Show("Удалить выбранный заказ?", "Удаление", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) return;
try { await _service.DeleteAsync(order.Id); await LoadSafeAsync(); }
catch (Exception ex) { UiHelper.ShowError(ex); }
}
}
19. src/DemoExamTemplate/Forms/OrdersForm.resx
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" use="required" type="xsd:string" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element>
<xsd:element name="assembly"><xsd:complexType><xsd:attribute name="alias" type="xsd:string" /><xsd:attribute name="name" type="xsd:string" /></xsd:complexType></xsd:element>
<xsd:element name="data"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /><xsd:element name="comment" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element>
<xsd:element name="resheader"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /></xsd:complexType></xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
<resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
<resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
</root>
20. src/DemoExamTemplate/Forms/ProductEditForm.Designer.cs
namespace DemoExamTemplate.Forms;
partial class ProductEditForm
{
private System.ComponentModel.IContainer? components = null;
protected override void Dispose(bool disposing) { if (disposing && components != null) components.Dispose(); base.Dispose(disposing); }
#region Windows Form Designer generated code
private void InitializeComponent()
{
lblTitle = new Label(); lblArticle = new Label(); txtArticle = new TextBox(); lblName = new Label(); txtName = new TextBox();
lblCategory = new Label(); cmbCategory = new ComboBox(); lblPrice = new Label(); nudPrice = new NumericUpDown();
lblDiscount = new Label(); nudDiscount = new NumericUpDown(); lblQuantity = new Label(); nudQuantity = new NumericUpDown();
btnSave = new Button(); btnCancel = new Button();
((System.ComponentModel.ISupportInitialize)nudPrice).BeginInit(); ((System.ComponentModel.ISupportInitialize)nudDiscount).BeginInit(); ((System.ComponentModel.ISupportInitialize)nudQuantity).BeginInit(); SuspendLayout();
lblTitle.AutoSize = true; lblTitle.Font = new Font("Times New Roman", 18F, FontStyle.Bold); lblTitle.Location = new Point(30, 25); lblTitle.Name = "lblTitle"; lblTitle.Text = "Добавление товара";
lblArticle.AutoSize = true; lblArticle.Name = "lblArticle"; lblArticle.Location = new Point(30, 82); lblArticle.Text = "Артикул*";
txtArticle.Name = "txtArticle"; txtArticle.Location = new Point(180, 78); txtArticle.Size = new Size(300, 24);
lblName.AutoSize = true; lblName.Name = "lblName"; lblName.Location = new Point(30, 125); lblName.Text = "Наименование*";
txtName.Name = "txtName"; txtName.Location = new Point(180, 121); txtName.Size = new Size(300, 24);
lblCategory.AutoSize = true; lblCategory.Name = "lblCategory"; lblCategory.Location = new Point(30, 168); lblCategory.Text = "Категория*";
cmbCategory.Name = "cmbCategory"; cmbCategory.DropDownStyle = ComboBoxStyle.DropDownList; cmbCategory.DisplayMember = "Name"; cmbCategory.ValueMember = "Id"; cmbCategory.Location = new Point(180, 164); cmbCategory.Size = new Size(300, 25);
lblPrice.AutoSize = true; lblPrice.Name = "lblPrice"; lblPrice.Location = new Point(30, 211); lblPrice.Text = "Цена*";
nudPrice.Name = "nudPrice"; nudPrice.DecimalPlaces = 2; nudPrice.Maximum = new decimal(new int[] { 10000000, 0, 0, 0 }); nudPrice.Minimum = new decimal(new int[] { 1, 0, 0, 0 }); nudPrice.Location = new Point(180, 207); nudPrice.Size = new Size(300, 24); nudPrice.Value = new decimal(new int[] { 1, 0, 0, 0 });
lblDiscount.AutoSize = true; lblDiscount.Name = "lblDiscount"; lblDiscount.Location = new Point(30, 254); lblDiscount.Text = "Скидка, %";
nudDiscount.Name = "nudDiscount"; nudDiscount.Maximum = new decimal(new int[] { 100, 0, 0, 0 }); nudDiscount.Location = new Point(180, 250); nudDiscount.Size = new Size(300, 24);
lblQuantity.AutoSize = true; lblQuantity.Name = "lblQuantity"; lblQuantity.Location = new Point(30, 297); lblQuantity.Text = "Количество";
nudQuantity.Name = "nudQuantity"; nudQuantity.Maximum = new decimal(new int[] { 100000, 0, 0, 0 }); nudQuantity.Location = new Point(180, 293); nudQuantity.Size = new Size(300, 24);
btnSave.Name = "btnSave"; btnSave.BackColor = Color.MediumSpringGreen; btnSave.FlatStyle = FlatStyle.Flat; btnSave.Location = new Point(180, 350); btnSave.Size = new Size(140, 40); btnSave.Text = "Сохранить"; btnSave.UseVisualStyleBackColor = false; btnSave.Click += btnSave_Click;
btnCancel.Name = "btnCancel"; btnCancel.BackColor = Color.Chartreuse; btnCancel.FlatStyle = FlatStyle.Flat; btnCancel.Location = new Point(340, 350); btnCancel.Size = new Size(140, 40); btnCancel.Text = "Отмена"; btnCancel.UseVisualStyleBackColor = false; btnCancel.Click += btnCancel_Click;
AutoScaleDimensions = new SizeF(8F, 17F); AutoScaleMode = AutoScaleMode.Font; BackColor = Color.White; ClientSize = new Size(530, 430);
Controls.AddRange(new Control[] { lblTitle, lblArticle, txtArticle, lblName, txtName, lblCategory, cmbCategory, lblPrice, nudPrice, lblDiscount, nudDiscount, lblQuantity, nudQuantity, btnSave, btnCancel });
Font = new Font("Times New Roman", 11F); FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; MinimizeBox = false; Name = "ProductEditForm"; StartPosition = FormStartPosition.CenterParent; Text = "Добавление товара"; Shown += ProductEditForm_Shown;
((System.ComponentModel.ISupportInitialize)nudPrice).EndInit(); ((System.ComponentModel.ISupportInitialize)nudDiscount).EndInit(); ((System.ComponentModel.ISupportInitialize)nudQuantity).EndInit(); ResumeLayout(false); PerformLayout();
}
#endregion
private Label lblTitle; private Label lblArticle; private TextBox txtArticle; private Label lblName; private TextBox txtName; private Label lblCategory; private ComboBox cmbCategory; private Label lblPrice; private NumericUpDown nudPrice; private Label lblDiscount; private NumericUpDown nudDiscount; private Label lblQuantity; private NumericUpDown nudQuantity; private Button btnSave; private Button btnCancel;
}
21. src/DemoExamTemplate/Forms/ProductEditForm.cs
using DemoExamTemplate.Models;
using DemoExamTemplate.Services;
using DemoExamTemplate.UI;
namespace DemoExamTemplate.Forms;
public partial class ProductEditForm : Form
{
private readonly int _productId;
private readonly ProductService _service = new ProductService();
public ProductEditForm(int productId = 0)
{
_productId = productId;
InitializeComponent();
Text = productId == 0 ? "Добавление товара" : "Редактирование товара";
lblTitle.Text = Text;
}
private async void ProductEditForm_Shown(object? sender, EventArgs e)
{
try
{
cmbCategory.DataSource = await _service.GetCategoriesAsync();
if (_productId == 0) return;
Product? product = await _service.GetByIdAsync(_productId);
if (product is null) return;
txtArticle.Text = product.Article;
txtName.Text = product.Name;
cmbCategory.SelectedValue = product.CategoryId;
nudPrice.Value = product.Price;
nudDiscount.Value = product.Discount;
nudQuantity.Value = product.Quantity;
}
catch (Exception ex) { UiHelper.ShowError(ex); }
}
private async void btnSave_Click(object? sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtArticle.Text) || string.IsNullOrWhiteSpace(txtName.Text) || cmbCategory.SelectedItem is not Category category)
{
MessageBox.Show("Заполните обязательные поля.");
return;
}
try
{
await _service.SaveAsync(new Product
{
Id = _productId,
Article = txtArticle.Text,
Name = txtName.Text,
CategoryId = category.Id,
Price = nudPrice.Value,
Discount = (int)nudDiscount.Value,
Quantity = (int)nudQuantity.Value
});
DialogResult = DialogResult.OK;
Close();
}
catch (Exception ex) { UiHelper.ShowError(ex); }
}
private void btnCancel_Click(object? sender, EventArgs e) => Close();
}
22. src/DemoExamTemplate/Forms/ProductEditForm.resx
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" use="required" type="xsd:string" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element>
<xsd:element name="assembly"><xsd:complexType><xsd:attribute name="alias" type="xsd:string" /><xsd:attribute name="name" type="xsd:string" /></xsd:complexType></xsd:element>
<xsd:element name="data"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /><xsd:element name="comment" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element>
<xsd:element name="resheader"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /></xsd:complexType></xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader>
<resheader name="version"><value>2.0</value></resheader>
<resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
<resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader>
</root>
23. src/DemoExamTemplate/Models/Category.cs
namespace DemoExamTemplate.Models;
public sealed class Category
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public override string ToString() => Name;
}
24. src/DemoExamTemplate/Models/OrderModels.cs
namespace DemoExamTemplate.Models;
public sealed class OrderListItem
{
public int Id { get; set; }
public string Number { get; set; } = string.Empty;
public DateTime OrderDate { get; set; }
public string Customer { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public decimal Total { get; set; }
}
public sealed class OrderData
{
public int Id { get; set; }
public string Number { get; set; } = string.Empty;
public DateTime OrderDate { get; set; } = DateTime.Today;
public string Customer { get; set; } = string.Empty;
public string Status { get; set; } = "Новый";
public List<OrderItemData> Items { get; set; } = new List<OrderItemData>();
}
public sealed class OrderItemData
{
public int ProductId { get; set; }
public string Article { get; set; } = string.Empty;
public string ProductName { get; set; } = string.Empty;
public int Quantity { get; set; }
public decimal Price { get; set; }
public int Discount { get; set; }
public decimal LineTotal => Quantity * Price * (100 - Discount) / 100m;
}
25. src/DemoExamTemplate/Models/Product.cs
namespace DemoExamTemplate.Models;
public sealed class Product
{
public int Id { get; set; }
public string Article { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public int CategoryId { get; set; }
public string CategoryName { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Discount { get; set; }
public int Quantity { get; set; }
}
26. src/DemoExamTemplate/Models/UserSession.cs
namespace DemoExamTemplate.Models;
public sealed class UserSession
{
public string FullName { get; init; } = "Гость";
public string RoleCode { get; init; } = "GUEST";
public string RoleName { get; init; } = "Гость";
public bool CanUseProductTools => RoleCode is "MANAGER" or "ADMIN";
public bool CanEditProducts => RoleCode == "ADMIN";
public bool CanViewOrders => RoleCode is "MANAGER" or "ADMIN";
public bool CanEditOrders => RoleCode == "ADMIN";
public static UserSession Guest()
{
return new UserSession();
}
}
27. src/DemoExamTemplate/Program.cs
using DemoExamTemplate.Forms;
namespace DemoExamTemplate;
internal static class Program
{
[STAThread]
private static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new LoginForm());
}
}
28. src/DemoExamTemplate/Services/AuthService.cs
using DemoExamTemplate.Data;
using DemoExamTemplate.Models;
using Microsoft.Data.SqlClient;
namespace DemoExamTemplate.Services;
public sealed class AuthService
{
public async Task<UserSession?> LoginAsync(string login, string password)
{
const string sql = """
SELECT u.FullName, r.Code, r.Name
FROM dbo.Users u
INNER JOIN dbo.Roles r ON r.Id = u.RoleId
WHERE u.Login = @Login AND u.Password = @Password;
""";
await using SqlConnection connection = Db.CreateConnection();
await connection.OpenAsync();
await using SqlCommand command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("@Login", login.Trim());
command.Parameters.AddWithValue("@Password", password);
await using SqlDataReader reader = await command.ExecuteReaderAsync();
if (!await reader.ReadAsync()) return null;
return new UserSession
{
FullName = reader.GetString(0),
RoleCode = reader.GetString(1),
RoleName = reader.GetString(2)
};
}
}
29. src/DemoExamTemplate/Services/OrderService.cs
using DemoExamTemplate.Data;
using DemoExamTemplate.Models;
using Microsoft.Data.SqlClient;
namespace DemoExamTemplate.Services;
public sealed class OrderService
{
public async Task<List<OrderListItem>> GetOrdersAsync()
{
const string sql = """
SELECT o.Id, o.Number, o.OrderDate, o.Customer, o.Status,
COALESCE(SUM(oi.Quantity * oi.Price * (100 - oi.Discount) / 100.0), 0) AS Total
FROM dbo.Orders o
LEFT JOIN dbo.OrderItems oi ON oi.OrderId = o.Id
GROUP BY o.Id, o.Number, o.OrderDate, o.Customer, o.Status
ORDER BY o.OrderDate DESC, o.Id DESC;
""";
List<OrderListItem> orders = new List<OrderListItem>();
await using SqlConnection connection = Db.CreateConnection();
await connection.OpenAsync();
await using SqlCommand command = new SqlCommand(sql, connection);
await using SqlDataReader reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
orders.Add(new OrderListItem
{
Id = reader.GetInt32(0),
Number = reader.GetString(1),
OrderDate = reader.GetDateTime(2),
Customer = reader.GetString(3),
Status = reader.GetString(4),
Total = reader.GetDecimal(5)
});
}
return orders;
}
public async Task<OrderData?> GetByIdAsync(int id)
{
const string orderSql = "SELECT Id, Number, OrderDate, Customer, Status FROM dbo.Orders WHERE Id=@Id;";
const string itemsSql = """
SELECT oi.ProductId, p.Article, p.Name, oi.Quantity, oi.Price, oi.Discount
FROM dbo.OrderItems oi
INNER JOIN dbo.Products p ON p.Id = oi.ProductId
WHERE oi.OrderId=@Id;
""";
await using SqlConnection connection = Db.CreateConnection();
await connection.OpenAsync();
OrderData? order;
await using (SqlCommand command = new SqlCommand(orderSql, connection))
{
command.Parameters.AddWithValue("@Id", id);
await using SqlDataReader reader = await command.ExecuteReaderAsync();
if (!await reader.ReadAsync()) return null;
order = new OrderData
{
Id = reader.GetInt32(0), Number = reader.GetString(1), OrderDate = reader.GetDateTime(2),
Customer = reader.GetString(3), Status = reader.GetString(4)
};
}
await using (SqlCommand command = new SqlCommand(itemsSql, connection))
{
command.Parameters.AddWithValue("@Id", id);
await using SqlDataReader reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
order.Items.Add(new OrderItemData
{
ProductId = reader.GetInt32(0), Article = reader.GetString(1), ProductName = reader.GetString(2),
Quantity = reader.GetInt32(3), Price = reader.GetDecimal(4), Discount = reader.GetInt32(5)
});
}
}
return order;
}
public async Task SaveAsync(OrderData order)
{
await using SqlConnection connection = Db.CreateConnection();
await connection.OpenAsync();
await using SqlTransaction transaction = (SqlTransaction)await connection.BeginTransactionAsync();
try
{
int orderId = order.Id;
if (orderId == 0)
{
const string insertOrder = """
INSERT INTO dbo.Orders(Number, OrderDate, Customer, Status)
OUTPUT INSERTED.Id VALUES(@Number, @OrderDate, @Customer, @Status);
""";
await using SqlCommand command = new SqlCommand(insertOrder, connection, transaction);
AddOrderParameters(command, order);
orderId = Convert.ToInt32(await command.ExecuteScalarAsync());
}
else
{
const string updateOrder = """
UPDATE dbo.Orders SET Number=@Number, OrderDate=@OrderDate,
Customer=@Customer, Status=@Status WHERE Id=@Id;
""";
await using SqlCommand command = new SqlCommand(updateOrder, connection, transaction);
AddOrderParameters(command, order);
command.Parameters.AddWithValue("@Id", orderId);
await command.ExecuteNonQueryAsync();
await using SqlCommand deleteItems = new SqlCommand("DELETE FROM dbo.OrderItems WHERE OrderId=@Id;", connection, transaction);
deleteItems.Parameters.AddWithValue("@Id", orderId);
await deleteItems.ExecuteNonQueryAsync();
}
const string insertItem = """
INSERT INTO dbo.OrderItems(OrderId, ProductId, Quantity, Price, Discount)
VALUES(@OrderId, @ProductId, @Quantity, @Price, @Discount);
""";
foreach (OrderItemData item in order.Items)
{
await using SqlCommand command = new SqlCommand(insertItem, connection, transaction);
command.Parameters.AddWithValue("@OrderId", orderId);
command.Parameters.AddWithValue("@ProductId", item.ProductId);
command.Parameters.AddWithValue("@Quantity", item.Quantity);
command.Parameters.AddWithValue("@Price", item.Price);
command.Parameters.AddWithValue("@Discount", item.Discount);
await command.ExecuteNonQueryAsync();
}
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
public async Task DeleteAsync(int id)
{
await using SqlConnection connection = Db.CreateConnection();
await connection.OpenAsync();
await using SqlCommand command = new SqlCommand("DELETE FROM dbo.Orders WHERE Id=@Id;", connection);
command.Parameters.AddWithValue("@Id", id);
await command.ExecuteNonQueryAsync();
}
private static void AddOrderParameters(SqlCommand command, OrderData order)
{
command.Parameters.AddWithValue("@Number", order.Number.Trim());
command.Parameters.AddWithValue("@OrderDate", order.OrderDate.Date);
command.Parameters.AddWithValue("@Customer", order.Customer.Trim());
command.Parameters.AddWithValue("@Status", order.Status);
}
}
30. src/DemoExamTemplate/Services/ProductService.cs
using System.Text;
using DemoExamTemplate.Data;
using DemoExamTemplate.Models;
using Microsoft.Data.SqlClient;
namespace DemoExamTemplate.Services;
public sealed class ProductService
{
public async Task<List<Product>> GetProductsAsync(string? search, int? categoryId, string sortCode)
{
StringBuilder sql = new StringBuilder("""
SELECT p.Id, p.Article, p.Name, p.CategoryId, c.Name AS CategoryName,
p.Price, p.Discount, p.Quantity
FROM dbo.Products p
INNER JOIN dbo.Categories c ON c.Id = p.CategoryId
WHERE 1 = 1
""");
List<SqlParameter> parameters = new List<SqlParameter>();
if (!string.IsNullOrWhiteSpace(search))
{
sql.AppendLine("AND (p.Article LIKE @Search OR p.Name LIKE @Search)");
parameters.Add(new SqlParameter("@Search", "%" + search.Trim() + "%"));
}
if (categoryId.HasValue)
{
sql.AppendLine("AND p.CategoryId = @CategoryId");
parameters.Add(new SqlParameter("@CategoryId", categoryId.Value));
}
string orderBy = sortCode switch
{
"NAME_DESC" => "p.Name DESC",
"PRICE_ASC" => "p.Price ASC",
"PRICE_DESC" => "p.Price DESC",
_ => "p.Name ASC"
};
sql.AppendLine("ORDER BY " + orderBy + ";");
List<Product> products = new List<Product>();
await using SqlConnection connection = Db.CreateConnection();
await connection.OpenAsync();
await using SqlCommand command = new SqlCommand(sql.ToString(), connection);
command.Parameters.AddRange(parameters.ToArray());
await using SqlDataReader reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync()) products.Add(Map(reader));
return products;
}
public async Task<Product?> GetByIdAsync(int id)
{
const string sql = """
SELECT p.Id, p.Article, p.Name, p.CategoryId, c.Name AS CategoryName,
p.Price, p.Discount, p.Quantity
FROM dbo.Products p
INNER JOIN dbo.Categories c ON c.Id = p.CategoryId
WHERE p.Id = @Id;
""";
await using SqlConnection connection = Db.CreateConnection();
await connection.OpenAsync();
await using SqlCommand command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("@Id", id);
await using SqlDataReader reader = await command.ExecuteReaderAsync();
return await reader.ReadAsync() ? Map(reader) : null;
}
public async Task<List<Category>> GetCategoriesAsync()
{
const string sql = "SELECT Id, Name FROM dbo.Categories ORDER BY Name;";
List<Category> categories = new List<Category>();
await using SqlConnection connection = Db.CreateConnection();
await connection.OpenAsync();
await using SqlCommand command = new SqlCommand(sql, connection);
await using SqlDataReader reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
categories.Add(new Category { Id = reader.GetInt32(0), Name = reader.GetString(1) });
}
return categories;
}
public async Task SaveAsync(Product product)
{
const string insertSql = """
INSERT INTO dbo.Products(Article, Name, CategoryId, Price, Discount, Quantity)
VALUES(@Article, @Name, @CategoryId, @Price, @Discount, @Quantity);
""";
const string updateSql = """
UPDATE dbo.Products SET Article=@Article, Name=@Name, CategoryId=@CategoryId,
Price=@Price, Discount=@Discount, Quantity=@Quantity
WHERE Id=@Id;
""";
await using SqlConnection connection = Db.CreateConnection();
await connection.OpenAsync();
await using SqlCommand command = new SqlCommand(product.Id == 0 ? insertSql : updateSql, connection);
command.Parameters.AddWithValue("@Article", product.Article.Trim());
command.Parameters.AddWithValue("@Name", product.Name.Trim());
command.Parameters.AddWithValue("@CategoryId", product.CategoryId);
command.Parameters.AddWithValue("@Price", product.Price);
command.Parameters.AddWithValue("@Discount", product.Discount);
command.Parameters.AddWithValue("@Quantity", product.Quantity);
if (product.Id != 0) command.Parameters.AddWithValue("@Id", product.Id);
await command.ExecuteNonQueryAsync();
}
public async Task DeleteAsync(int id)
{
await using SqlConnection connection = Db.CreateConnection();
await connection.OpenAsync();
await using SqlCommand command = new SqlCommand("DELETE FROM dbo.Products WHERE Id=@Id;", connection);
command.Parameters.AddWithValue("@Id", id);
await command.ExecuteNonQueryAsync();
}
private static Product Map(SqlDataReader reader)
{
return new Product
{
Id = reader.GetInt32(reader.GetOrdinal("Id")),
Article = reader.GetString(reader.GetOrdinal("Article")),
Name = reader.GetString(reader.GetOrdinal("Name")),
CategoryId = reader.GetInt32(reader.GetOrdinal("CategoryId")),
CategoryName = reader.GetString(reader.GetOrdinal("CategoryName")),
Price = reader.GetDecimal(reader.GetOrdinal("Price")),
Discount = reader.GetInt32(reader.GetOrdinal("Discount")),
Quantity = reader.GetInt32(reader.GetOrdinal("Quantity"))
};
}
}
31. src/DemoExamTemplate/UI/UiHelper.cs
namespace DemoExamTemplate.UI;
public static class UiHelper
{
public static void LoadLogo(PictureBox pictureBox)
{
string path = Path.Combine(AppContext.BaseDirectory, "Assets", "logo.png");
if (File.Exists(path))
{
using Image image = Image.FromFile(path);
pictureBox.Image = new Bitmap(image);
}
}
public static void ShowError(Exception exception)
{
MessageBox.Show(exception.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
32. src/DemoExamTemplate/appsettings.json
{
"ConnectionString": "Server=__DB_SERVER__;Database=__DB_NAME__;User Id=__DB_USER__;Password=__DB_PASSWORD__;Encrypt=True;TrustServerCertificate=True;MultipleActiveResultSets=True"
}
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.