Release v1.0.21: consignees from web, remove test pharmacies.
This commit is contained in:
parent
166a431bda
commit
2659de14b9
5
.gitignore
vendored
5
.gitignore
vendored
@ -20,3 +20,8 @@ Desktop.ini
|
|||||||
efClient.db
|
efClient.db
|
||||||
*.db-journal
|
*.db-journal
|
||||||
**/user.config
|
**/user.config
|
||||||
|
|
||||||
|
packages/
|
||||||
|
installer/output/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
|||||||
110
installer/ElfisaPharmacy.iss
Normal file
110
installer/ElfisaPharmacy.iss
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
; ЭльФиСА — установщик «Электронная Фармация»
|
||||||
|
; Кодировка: UTF-8 with BOM
|
||||||
|
|
||||||
|
#define MyAppName "Электронная Фармация"
|
||||||
|
#define MyAppBrand "ЭльФиСА"
|
||||||
|
#define MyAppVersion "1.0.20"
|
||||||
|
#define MyAppPublisher "PharmData"
|
||||||
|
#define MyAppURL "https://cdn.24pharmdata.ru"
|
||||||
|
#define MyAppSupportURL "https://24pharmdata.ru"
|
||||||
|
#define MyAppExeName "Электронная Фармация.exe"
|
||||||
|
#define MyAppId "{{A7C4E2B1-9F3D-4E6A-8B2C-1D5F7A9E0C42}"
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
AppId={#MyAppId}
|
||||||
|
AppName={#MyAppName}
|
||||||
|
AppVersion={#MyAppVersion}
|
||||||
|
AppVerName={#MyAppBrand} — {#MyAppName} {#MyAppVersion}
|
||||||
|
AppPublisher={#MyAppPublisher}
|
||||||
|
AppPublisherURL={#MyAppSupportURL}
|
||||||
|
AppSupportURL={#MyAppSupportURL}
|
||||||
|
AppUpdatesURL={#MyAppURL}
|
||||||
|
; Программа — в LocalAppData (без прав админа). База/логи — тоже в профиле пользователя.
|
||||||
|
DefaultDirName={localappdata}\{#MyAppBrand}\{#MyAppName}
|
||||||
|
DefaultGroupName={#MyAppBrand}
|
||||||
|
DisableProgramGroupPage=no
|
||||||
|
LicenseFile=license.txt
|
||||||
|
InfoBeforeFile=welcome.txt
|
||||||
|
OutputDir=output
|
||||||
|
OutputBaseFilename=ElfisaPharmacy-Setup-{#MyAppVersion}
|
||||||
|
SetupIconFile=assets\elfisa.ico
|
||||||
|
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||||
|
UninstallDisplayName={#MyAppBrand} — {#MyAppName}
|
||||||
|
Compression=lzma2/ultra64
|
||||||
|
SolidCompression=yes
|
||||||
|
WizardStyle=modern
|
||||||
|
WizardSizePercent=120
|
||||||
|
WizardImageFile=assets\wizard-side.bmp
|
||||||
|
WizardSmallImageFile=assets\wizard-small.bmp
|
||||||
|
WizardImageStretch=yes
|
||||||
|
AllowNoIcons=yes
|
||||||
|
PrivilegesRequired=lowest
|
||||||
|
PrivilegesRequiredOverridesAllowed=dialog
|
||||||
|
ArchitecturesInstallIn64BitMode=x64compatible
|
||||||
|
VersionInfoVersion={#MyAppVersion}.0
|
||||||
|
VersionInfoCompany={#MyAppPublisher}
|
||||||
|
VersionInfoDescription=Установщик {#MyAppBrand} — {#MyAppName}
|
||||||
|
VersionInfoProductName={#MyAppName}
|
||||||
|
VersionInfoProductVersion={#MyAppVersion}
|
||||||
|
VersionInfoCopyright=Copyright (C) {#MyAppPublisher}
|
||||||
|
ShowLanguageDialog=no
|
||||||
|
DisableWelcomePage=no
|
||||||
|
DisableDirPage=no
|
||||||
|
DisableFinishedPage=no
|
||||||
|
CloseApplications=yes
|
||||||
|
RestartApplications=no
|
||||||
|
SetupLogging=yes
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"
|
||||||
|
|
||||||
|
[Messages]
|
||||||
|
russian.BeveledLabel={#MyAppBrand} · PharmData
|
||||||
|
russian.WelcomeLabel1=Добро пожаловать в установку [name]
|
||||||
|
russian.WelcomeLabel2=Мастер установит [name/ver] на ваш компьютер.%n%nРекомендуется закрыть все другие приложения перед продолжением.%n%nНажмите «Далее», чтобы продолжить.
|
||||||
|
russian.FinishedLabel=Установка [name] успешно завершена.%n%nПриложение готово к работе. При первом запуске выполните вход в систему.
|
||||||
|
russian.ClickFinish=Нажмите «Готово», чтобы выйти из мастера установки.
|
||||||
|
russian.SelectTasksDesc=Выберите дополнительные задачи, затем нажмите «Далее».
|
||||||
|
russian.SelectTasksLabel2=Какие дополнительные задачи нужно выполнить?
|
||||||
|
russian.WizardSelectTasks=Дополнительные задачи
|
||||||
|
russian.WizardReady=Всё готово к установке
|
||||||
|
russian.ReadyLabel1=Мастер готов установить [name] на ваш компьютер.
|
||||||
|
russian.ReadyLabel2a=Нажмите «Установить», чтобы продолжить, или «Назад», если хотите просмотреть либо изменить параметры.
|
||||||
|
russian.InstallingLabel=Подождите, пока [name] устанавливается на ваш компьютер…
|
||||||
|
russian.StatusExtractFiles=Распаковка файлов…
|
||||||
|
russian.StatusCreateDirs=Создание папок…
|
||||||
|
russian.StatusRegisterFiles=Регистрация компонентов…
|
||||||
|
russian.ConfirmUninstall=Вы действительно хотите удалить %1 и все его компоненты?
|
||||||
|
russian.UninstalledAll=%1 успешно удалена.
|
||||||
|
russian.ExitSetupMessage=Установка не завершена. Если вы выйдете сейчас, программа не будет установлена.%n%nВы можете запустить установщик позже.%n%nВыйти из установки?
|
||||||
|
|
||||||
|
[CustomMessages]
|
||||||
|
russian.CreateDesktopIcon=Создать ярлык на рабочем столе
|
||||||
|
russian.LaunchAfterInstall=Запустить Электронную Фармацию
|
||||||
|
russian.AppWorkingDir=Рабочая папка программы
|
||||||
|
russian.AdditionalIcons=Дополнительные ярлыки:
|
||||||
|
russian.BrandLine=ЭльФиСА — удобный заказ лекарств для аптек
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
; Основной выпуск Release (без отладочных PDB и XML-документации)
|
||||||
|
Source: "..\src\ElectronicPharmacy\bin\Release\*.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\src\ElectronicPharmacy\bin\Release\*.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\src\ElectronicPharmacy\bin\Release\*.config"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\src\ElectronicPharmacy\bin\Release\x86\*"; DestDir: "{app}\x86"; Flags: ignoreversion recursesubdirs createallsubdirs skipifsourcedoesntexist
|
||||||
|
Source: "..\src\ElectronicPharmacy\bin\Release\x64\*"; DestDir: "{app}\x64"; Flags: ignoreversion recursesubdirs createallsubdirs skipifsourcedoesntexist
|
||||||
|
Source: "assets\elfisa.ico"; DestDir: "{app}"; DestName: "elfisa.ico"; Flags: ignoreversion
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\elfisa.ico"; Comment: "{cm:BrandLine}"
|
||||||
|
Name: "{group}\Удалить {#MyAppName}"; Filename: "{uninstallexe}"; IconFilename: "{app}\elfisa.ico"
|
||||||
|
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\elfisa.ico"; Comment: "{cm:BrandLine}"; Tasks: desktopicon
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchAfterInstall}"; Flags: nowait postinstall skipifsilent
|
||||||
|
|
||||||
|
[UninstallDelete]
|
||||||
|
Type: filesandordirs; Name: "{app}\Logs"
|
||||||
|
Type: files; Name: "{app}\*.pdb"
|
||||||
24
installer/README.md
Normal file
24
installer/README.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Установщик ЭльФиСА
|
||||||
|
|
||||||
|
## Публичная ссылка (CDN)
|
||||||
|
|
||||||
|
https://cdn.24pharmdata.ru
|
||||||
|
|
||||||
|
При открытии корня CDN сразу скачивается установщик.
|
||||||
|
|
||||||
|
Стабильное имя файла:
|
||||||
|
|
||||||
|
https://cdn.24pharmdata.ru/ElfisaPharmacy-Setup.exe
|
||||||
|
|
||||||
|
## Локальная сборка
|
||||||
|
|
||||||
|
- `installer/output/ElfisaPharmacy-Setup-1.0.20.exe`
|
||||||
|
- `installer/output/ElfisaPharmacy-Setup.exe` (копия для CDN)
|
||||||
|
|
||||||
|
## Пересборка
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\scripts\build-installer.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
После сборки залейте `ElfisaPharmacy-Setup.exe` на CDN (или сделайте его index/default document).
|
||||||
BIN
installer/assets/elfisa-logo.png
Normal file
BIN
installer/assets/elfisa-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
BIN
installer/assets/elfisa.ico
Normal file
BIN
installer/assets/elfisa.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
BIN
installer/assets/wizard-side.bmp
Normal file
BIN
installer/assets/wizard-side.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 201 KiB |
BIN
installer/assets/wizard-small.bmp
Normal file
BIN
installer/assets/wizard-small.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
18
installer/license.txt
Normal file
18
installer/license.txt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
ЛИЦЕНЗИОННОЕ СОГЛАШЕНИЕ
|
||||||
|
Электронная Фармация (ЭльФиСА)
|
||||||
|
|
||||||
|
1. Программа предоставляется для использования в рамках договора
|
||||||
|
с поставщиком услуг PharmData / 24pharmdata.
|
||||||
|
|
||||||
|
2. Запрещается передавать третьим лицам учётные данные доступа,
|
||||||
|
а также модифицировать клиент способом, нарушающим безопасность
|
||||||
|
обмена данными.
|
||||||
|
|
||||||
|
3. Пользователь несёт ответственность за корректность оформляемых
|
||||||
|
заказов и за сохранность локальных данных на рабочем месте.
|
||||||
|
|
||||||
|
4. Устанавливая программу, вы подтверждаете согласие с условиями
|
||||||
|
использования сервиса и политикой обработки данных, действующими
|
||||||
|
у оператора сервиса.
|
||||||
|
|
||||||
|
При возникновении вопросов обратитесь в службу поддержки.
|
||||||
20
installer/welcome.txt
Normal file
20
installer/welcome.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
ЭльФиСА — Электронная Фармация
|
||||||
|
================================
|
||||||
|
|
||||||
|
Добро пожаловать в установку настольного клиента «Электронная Фармация».
|
||||||
|
|
||||||
|
Программа предназначена для работы аптек с прайс-листами поставщиков,
|
||||||
|
заказами, накладными и обменом данными с сервером PharmData.
|
||||||
|
|
||||||
|
Скачать установщик всегда можно по адресу:
|
||||||
|
https://cdn.24pharmdata.ru
|
||||||
|
|
||||||
|
Установщик:
|
||||||
|
• скопирует программу в выбранную папку;
|
||||||
|
• предложит ярлык в меню «Пуск»;
|
||||||
|
• по желанию создаст ярлык на рабочем столе.
|
||||||
|
|
||||||
|
Рекомендуется закрыть запущенную «Электронную Фармацию» перед установкой.
|
||||||
|
После установки при первом запуске выполните вход в систему.
|
||||||
|
|
||||||
|
© PharmData / ЭльФиСА
|
||||||
48
scripts/build-installer.ps1
Normal file
48
scripts/build-installer.ps1
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#Requires -Version 5.1
|
||||||
|
param([switch]$SkipBuild)
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$root = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||||
|
Set-Location $root
|
||||||
|
Write-Host "Root: $root" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
$iscc = @(
|
||||||
|
"$env:LocalAppData\Programs\Inno Setup 6\ISCC.exe",
|
||||||
|
"${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe",
|
||||||
|
"${env:ProgramFiles}\Inno Setup 6\ISCC.exe"
|
||||||
|
) | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||||
|
|
||||||
|
if (-not $iscc) {
|
||||||
|
throw "Inno Setup 6 (ISCC.exe) не найден. Установите: winget install JRSoftware.InnoSetup"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $SkipBuild) {
|
||||||
|
Write-Host "Сборка Release..." -ForegroundColor Cyan
|
||||||
|
dotnet build "src\ElectronicPharmacy\ElectronicPharmacy.csproj" -c Release
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Сборка приложения завершилась с ошибкой" }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Сборка установщика..." -ForegroundColor Cyan
|
||||||
|
& $iscc "installer\ElfisaPharmacy.iss"
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Inno Setup завершился с ошибкой" }
|
||||||
|
|
||||||
|
$out = Get-ChildItem "installer\output\ElfisaPharmacy-Setup-*.exe" |
|
||||||
|
Where-Object { $_.Name -notmatch '\.bak\.' -and $_.Name -ne 'ElfisaPharmacy-Setup.exe' } |
|
||||||
|
Sort-Object LastWriteTime -Descending |
|
||||||
|
Select-Object -First 1
|
||||||
|
|
||||||
|
if (-not $out) {
|
||||||
|
throw "Не найден собранный Setup exe в installer\output"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Стабильное имя для CDN: https://cdn.24pharmdata.ru/ElfisaPharmacy-Setup.exe
|
||||||
|
$stable = Join-Path $out.DirectoryName "ElfisaPharmacy-Setup.exe"
|
||||||
|
Copy-Item $out.FullName $stable -Force
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Готово: $($out.FullName)" -ForegroundColor Green
|
||||||
|
Write-Host "CDN-файл: $stable" -ForegroundColor Green
|
||||||
|
Write-Host ("Размер: {0:N1} МБ" -f ($out.Length / 1MB))
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Залейте на CDN:" -ForegroundColor Cyan
|
||||||
|
Write-Host " https://cdn.24pharmdata.ru/ <- корень отдаёт этот Setup"
|
||||||
|
Write-Host " или файл: ElfisaPharmacy-Setup.exe"
|
||||||
@ -374,6 +374,30 @@ namespace Электронная_Фармация.Classes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<BuyerLocationDto>> GetBuyerLocationsAsync()
|
||||||
|
{
|
||||||
|
EnsureAuthenticated();
|
||||||
|
var responseBody = await SendAuthorizedGetAsync("/api/buyer/locations", "загрузку грузополучателей");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var doc = JsonDocument.Parse(responseBody))
|
||||||
|
{
|
||||||
|
if (doc.RootElement.ValueKind == JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<List<BuyerLocationDto>>(responseBody, JsonOptions)
|
||||||
|
?? new List<BuyerLocationDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new List<BuyerLocationDto>();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppDebugLog.Error("ApiClient", "Не удалось разобрать грузополучателей", ex);
|
||||||
|
throw new HttpRequestException($"Некорректный ответ сервера при загрузке грузополучателей: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<BuyerOrderDetailDto> GetBuyerOrderByIdAsync(string orderId)
|
public async Task<BuyerOrderDetailDto> GetBuyerOrderByIdAsync(string orderId)
|
||||||
{
|
{
|
||||||
EnsureAuthenticated();
|
EnsureAuthenticated();
|
||||||
@ -734,6 +758,27 @@ namespace Электронная_Фармация.Classes
|
|||||||
public List<BuyerOrderListItemDto> Orders { get; set; }
|
public List<BuyerOrderListItemDto> Orders { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class BuyerLocationDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("buyer_location_id")]
|
||||||
|
public string BuyerLocationID { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("buyer_id")]
|
||||||
|
public string BuyerID { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("address")]
|
||||||
|
public string Address { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("region_id")]
|
||||||
|
public string RegionID { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_default")]
|
||||||
|
public bool IsDefault { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("region_name")]
|
||||||
|
public string RegionName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class BuyerOrderListItemDto
|
public sealed class BuyerOrderListItemDto
|
||||||
{
|
{
|
||||||
[JsonPropertyName("order_id")]
|
[JsonPropertyName("order_id")]
|
||||||
|
|||||||
@ -12,11 +12,42 @@ namespace Электронная_Фармация.Classes
|
|||||||
public const string DefaultApiBaseUrl = "https://24pharmdata.ru";
|
public const string DefaultApiBaseUrl = "https://24pharmdata.ru";
|
||||||
public const string DefaultUpdateCheckBaseUrl = "https://git.24pharmdata.ru";
|
public const string DefaultUpdateCheckBaseUrl = "https://git.24pharmdata.ru";
|
||||||
public const string DefaultUpdateRepo = "pharmdata/elfisa-pharmacy";
|
public const string DefaultUpdateRepo = "pharmdata/elfisa-pharmacy";
|
||||||
|
/// <summary>Публичная раздача установщика (корень CDN отдаёт Setup.exe).</summary>
|
||||||
|
public const string DefaultInstallerCdnUrl = "https://cdn.24pharmdata.ru";
|
||||||
|
public const string DefaultInstallerDownloadUrl = "https://cdn.24pharmdata.ru/ElfisaPharmacy-Setup.exe";
|
||||||
private const string LegacyApiBaseUrl = "http://195.34.241.84:9988";
|
private const string LegacyApiBaseUrl = "http://195.34.241.84:9988";
|
||||||
private const string ApiBaseUrlEnvVar = "ELF_API_BASE_URL";
|
private const string ApiBaseUrlEnvVar = "ELF_API_BASE_URL";
|
||||||
private static bool _sqliteInteropPrepared;
|
private static bool _sqliteInteropPrepared;
|
||||||
private static readonly object _sqliteInteropLock = new object();
|
private static readonly object _sqliteInteropLock = new object();
|
||||||
|
|
||||||
|
public static string InstallerDownloadUrl
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var fromEnv = Environment.GetEnvironmentVariable("ELF_INSTALLER_URL");
|
||||||
|
if (!string.IsNullOrWhiteSpace(fromEnv))
|
||||||
|
{
|
||||||
|
return fromEnv.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultInstallerDownloadUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string InstallerCdnUrl
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var fromEnv = Environment.GetEnvironmentVariable("ELF_CDN_URL");
|
||||||
|
if (!string.IsNullOrWhiteSpace(fromEnv))
|
||||||
|
{
|
||||||
|
return fromEnv.Trim().TrimEnd('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultInstallerCdnUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Replaces known dead API URLs in saved user settings.
|
/// Replaces known dead API URLs in saved user settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -97,24 +128,77 @@ namespace Электронная_Фармация.Classes
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writable per-user data (DB, logs). Program Files is read-only for normal users.
|
||||||
|
/// </summary>
|
||||||
|
public static string UserDataDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var dir = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
|
"ЭльФиСА",
|
||||||
|
"Электронная Фармация");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Caller will surface a clearer error when opening the DB.
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string LogsDirectory => Path.Combine(UserDataDirectory, "Logs");
|
||||||
|
|
||||||
public static string SqliteDbPath
|
public static string SqliteDbPath
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
MigrateLegacySqliteDatabaseIfNeeded();
|
||||||
|
return Path.Combine(UserDataDirectory, "efClient.db");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// One-time copy from install folder (old portable layout) into LocalAppData.
|
||||||
|
/// </summary>
|
||||||
|
private static void MigrateLegacySqliteDatabaseIfNeeded()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var target = Path.Combine(UserDataDirectory, "efClient.db");
|
||||||
|
if (File.Exists(target))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
var root = Path.Combine(baseDir, "efClient.db");
|
var candidates = new[]
|
||||||
var data = Path.Combine(baseDir, "Data", "efClient.db");
|
|
||||||
if (File.Exists(root))
|
|
||||||
{
|
{
|
||||||
return root;
|
Path.Combine(baseDir, "efClient.db"),
|
||||||
}
|
Path.Combine(baseDir, "Data", "efClient.db")
|
||||||
|
};
|
||||||
|
|
||||||
if (File.Exists(data))
|
foreach (var source in candidates)
|
||||||
{
|
{
|
||||||
return data;
|
if (!File.Exists(source))
|
||||||
}
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
return root;
|
File.Copy(source, target, overwrite: false);
|
||||||
|
AppDebugLog.Info("AppConfig", $"SQLite DB migrated: {source} -> {target}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppDebugLog.Info("AppConfig", $"SQLite DB migrate skipped: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ namespace Электронная_Фармация.Classes
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// File logger for API/auth/network debugging.
|
/// File logger for API/auth/network debugging.
|
||||||
/// Logs are written to Logs/api-debug-YYYY-MM-DD.log next to the executable.
|
/// Logs are written to %LocalAppData%\ЭльФиСА\...\Logs\api-debug-YYYY-MM-DD.log.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class AppDebugLog
|
public static class AppDebugLog
|
||||||
{
|
{
|
||||||
@ -33,8 +33,7 @@ namespace Электронная_Фармация.Classes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string LogDirectory =>
|
public static string LogDirectory => AppConfig.LogsDirectory;
|
||||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
|
|
||||||
|
|
||||||
public static string CurrentLogFilePath =>
|
public static string CurrentLogFilePath =>
|
||||||
Path.Combine(LogDirectory, $"api-debug-{DateTime.Now:yyyy-MM-dd}.log");
|
Path.Combine(LogDirectory, $"api-debug-{DateTime.Now:yyyy-MM-dd}.log");
|
||||||
|
|||||||
@ -322,6 +322,131 @@ namespace Электронная_Фармация.Classes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Подтягивает грузополучателей с сервера (BuyerLocation) в локальную таблицу Consignees.
|
||||||
|
/// Источник истины — веб-кабинет; локально только кэш + выбор активной аптеки.
|
||||||
|
/// </summary>
|
||||||
|
public static int SyncFromServer(IList<BuyerLocationDto> locations)
|
||||||
|
{
|
||||||
|
if (locations == null || locations.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var upserted = 0;
|
||||||
|
using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
using (var tx = connection.BeginTransaction())
|
||||||
|
{
|
||||||
|
// Сбрасываем default — выставим по данным сервера.
|
||||||
|
using (var clearDefault = new SQLiteCommand(
|
||||||
|
"UPDATE Consignees SET ConsigneesUseAsDefault = 0",
|
||||||
|
connection,
|
||||||
|
tx))
|
||||||
|
{
|
||||||
|
clearDefault.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
int nextCode;
|
||||||
|
using (var nextCodeCmd = new SQLiteCommand(
|
||||||
|
"SELECT ifnull(MAX(codeConsignees), 0) + 1 FROM Consignees",
|
||||||
|
connection,
|
||||||
|
tx))
|
||||||
|
{
|
||||||
|
nextCode = Convert.ToInt32(nextCodeCmd.ExecuteScalar());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var loc in locations)
|
||||||
|
{
|
||||||
|
var locationId = (loc.BuyerLocationID ?? string.Empty).Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(locationId))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = (loc.Address ?? string.Empty).Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(address))
|
||||||
|
{
|
||||||
|
address = "Грузополучатель";
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = address;
|
||||||
|
if (name.Length > 120)
|
||||||
|
{
|
||||||
|
name = name.Substring(0, 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
long? existingId = null;
|
||||||
|
using (var find = new SQLiteCommand(
|
||||||
|
@"SELECT idConsignees FROM Consignees
|
||||||
|
WHERE lower(trim(LocationId)) = lower(@loc)
|
||||||
|
LIMIT 1",
|
||||||
|
connection,
|
||||||
|
tx))
|
||||||
|
{
|
||||||
|
find.Parameters.AddWithValue("@loc", locationId);
|
||||||
|
var found = find.ExecuteScalar();
|
||||||
|
if (found != null && found != DBNull.Value)
|
||||||
|
{
|
||||||
|
existingId = Convert.ToInt64(found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingId.HasValue)
|
||||||
|
{
|
||||||
|
using (var upd = new SQLiteCommand(
|
||||||
|
@"UPDATE Consignees
|
||||||
|
SET ConsigneesName = @name,
|
||||||
|
ConsigneesAddress = @address,
|
||||||
|
ConsigneesUseAsDefault = @isDefault,
|
||||||
|
LocationId = @loc
|
||||||
|
WHERE idConsignees = @id",
|
||||||
|
connection,
|
||||||
|
tx))
|
||||||
|
{
|
||||||
|
upd.Parameters.AddWithValue("@name", name);
|
||||||
|
upd.Parameters.AddWithValue("@address", address);
|
||||||
|
upd.Parameters.AddWithValue("@isDefault", loc.IsDefault ? 1 : 0);
|
||||||
|
upd.Parameters.AddWithValue("@loc", locationId);
|
||||||
|
upd.Parameters.AddWithValue("@id", existingId.Value);
|
||||||
|
upd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (var ins = new SQLiteCommand(
|
||||||
|
@"INSERT INTO Consignees
|
||||||
|
(codeConsignees, ConsigneesName, ConsigneesAddress, ConsigneesUseAsDefault, LocationId)
|
||||||
|
VALUES (@code, @name, @address, @isDefault, @loc)",
|
||||||
|
connection,
|
||||||
|
tx))
|
||||||
|
{
|
||||||
|
ins.Parameters.AddWithValue("@code", nextCode++);
|
||||||
|
ins.Parameters.AddWithValue("@name", name);
|
||||||
|
ins.Parameters.AddWithValue("@address", address);
|
||||||
|
ins.Parameters.AddWithValue("@isDefault", loc.IsDefault ? 1 : 0);
|
||||||
|
ins.Parameters.AddWithValue("@loc", locationId);
|
||||||
|
ins.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
upserted++;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var active = GetDefaultConsigneeName();
|
||||||
|
if (!string.IsNullOrWhiteSpace(active))
|
||||||
|
{
|
||||||
|
SetActiveConsignee(active);
|
||||||
|
}
|
||||||
|
|
||||||
|
return upserted;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Синхронизирует поля заказа в `PriceList` (Zakaz/SummaZakaza) с корзиной выбранной аптеки.
|
/// Синхронизирует поля заказа в `PriceList` (Zakaz/SummaZakaza) с корзиной выбранной аптеки.
|
||||||
/// Данные корзины хранятся в TempOrderItems с фильтром по ConsigneeName, а PriceList обновляется
|
/// Данные корзины хранятся в TempOrderItems с фильтром по ConsigneeName, а PriceList обновляется
|
||||||
|
|||||||
@ -125,15 +125,36 @@ namespace Электронная_Фармация.Classes
|
|||||||
}
|
}
|
||||||
else if (control is Panel || control is UserControl || control is TableLayoutPanel)
|
else if (control is Panel || control is UserControl || control is TableLayoutPanel)
|
||||||
{
|
{
|
||||||
if (control.Tag as string == "keep-bg")
|
var tag = control.Tag as string;
|
||||||
|
if (tag == "keep-bg")
|
||||||
{
|
{
|
||||||
// preserve custom background
|
// preserve custom background
|
||||||
}
|
}
|
||||||
else if (control.Tag as string == "chrome-child")
|
else if (tag == "chrome-child")
|
||||||
{
|
{
|
||||||
control.BackColor = theme.BackgroundPrimary;
|
control.BackColor = theme.BackgroundPrimary;
|
||||||
control.ForeColor = theme.TextPrimary;
|
control.ForeColor = theme.TextPrimary;
|
||||||
}
|
}
|
||||||
|
else if (tag == "toolbar")
|
||||||
|
{
|
||||||
|
control.BackColor = theme.Surface;
|
||||||
|
control.ForeColor = theme.TextPrimary;
|
||||||
|
}
|
||||||
|
else if (tag == "status-footer")
|
||||||
|
{
|
||||||
|
control.BackColor = theme.CartHeaderBackground;
|
||||||
|
control.ForeColor = theme.TextPrimary;
|
||||||
|
}
|
||||||
|
else if (tag == "surface-bar")
|
||||||
|
{
|
||||||
|
control.BackColor = theme.SectionHeaderBackground;
|
||||||
|
control.ForeColor = theme.TextPrimary;
|
||||||
|
}
|
||||||
|
else if (tag == "markup-bar")
|
||||||
|
{
|
||||||
|
control.BackColor = theme.AccentPrimarySoft;
|
||||||
|
control.ForeColor = theme.TextPrimary;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
control.BackColor = theme.BackgroundSecondary;
|
control.BackColor = theme.BackgroundSecondary;
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
<GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources>
|
<GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources>
|
||||||
<NuGetPackageImportStamp>
|
<NuGetPackageImportStamp>
|
||||||
</NuGetPackageImportStamp>
|
</NuGetPackageImportStamp>
|
||||||
|
<ApplicationIcon>Resources\elfisa.ico</ApplicationIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
@ -353,4 +354,7 @@
|
|||||||
<Import Project="packages\EntityFramework.6.4.4\build\EntityFramework.targets" Condition="Exists('packages\EntityFramework.6.4.4\build\EntityFramework.targets')" />
|
<Import Project="packages\EntityFramework.6.4.4\build\EntityFramework.targets" Condition="Exists('packages\EntityFramework.6.4.4\build\EntityFramework.targets')" />
|
||||||
<Import Project="packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets" Condition="Exists('packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" />
|
<Import Project="packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets" Condition="Exists('packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" />
|
||||||
<Import Project="packages\System.Resources.Extensions.8.0.0\buildTransitive\net462\System.Resources.Extensions.targets" Condition="Exists('packages\System.Resources.Extensions.8.0.0\buildTransitive\net462\System.Resources.Extensions.targets')" />
|
<Import Project="packages\System.Resources.Extensions.8.0.0\buildTransitive\net462\System.Resources.Extensions.targets" Condition="Exists('packages\System.Resources.Extensions.8.0.0\buildTransitive\net462\System.Resources.Extensions.targets')" />
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="Resources\elfisa.ico" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
7
src/ElectronicPharmacy/Form1.Designer.cs
generated
7
src/ElectronicPharmacy/Form1.Designer.cs
generated
@ -1,4 +1,4 @@
|
|||||||
namespace Электронная_Фармация
|
namespace Электронная_Фармация
|
||||||
{
|
{
|
||||||
partial class ElectroPharmacy
|
partial class ElectroPharmacy
|
||||||
{
|
{
|
||||||
@ -381,8 +381,11 @@
|
|||||||
this.ClientSize = new System.Drawing.Size(1089, 554);
|
this.ClientSize = new System.Drawing.Size(1089, 554);
|
||||||
this.Controls.Add(this.bindControl);
|
this.Controls.Add(this.bindControl);
|
||||||
this.Controls.Add(this.tabContent);
|
this.Controls.Add(this.tabContent);
|
||||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable;
|
||||||
|
this.MaximizeBox = true;
|
||||||
|
this.MinimizeBox = true;
|
||||||
this.Margin = new System.Windows.Forms.Padding(4);
|
this.Margin = new System.Windows.Forms.Padding(4);
|
||||||
|
this.MinimumSize = new System.Drawing.Size(900, 620);
|
||||||
this.Name = "ElectroPharmacy";
|
this.Name = "ElectroPharmacy";
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||||
this.Text = "Электронная Фармация";
|
this.Text = "Электронная Фармация";
|
||||||
|
|||||||
@ -23,6 +23,7 @@ namespace Электронная_Фармация
|
|||||||
private ToastService _toastService;
|
private ToastService _toastService;
|
||||||
private LoadingOverlay _loadingOverlay;
|
private LoadingOverlay _loadingOverlay;
|
||||||
private ModernAppHeader _appHeader;
|
private ModernAppHeader _appHeader;
|
||||||
|
private ModernTitleBar _titleBar;
|
||||||
private ModernDocumentTabStrip _documentTabStrip;
|
private ModernDocumentTabStrip _documentTabStrip;
|
||||||
private Panel _documentHost;
|
private Panel _documentHost;
|
||||||
private Panel _statusFooter;
|
private Panel _statusFooter;
|
||||||
@ -38,6 +39,9 @@ namespace Электронная_Фармация
|
|||||||
Font = new Font("Segoe UI", 12f);
|
Font = new Font("Segoe UI", 12f);
|
||||||
AutoScaleMode = AutoScaleMode.Dpi;
|
AutoScaleMode = AutoScaleMode.Dpi;
|
||||||
KeyPreview = true;
|
KeyPreview = true;
|
||||||
|
FormBorderStyle = FormBorderStyle.None;
|
||||||
|
MaximizeBox = true;
|
||||||
|
MinimizeBox = true;
|
||||||
MinimumSize = new Size(900, 620);
|
MinimumSize = new Size(900, 620);
|
||||||
|
|
||||||
bindControl.Visible = false;
|
bindControl.Visible = false;
|
||||||
@ -47,17 +51,21 @@ namespace Электронная_Фармация
|
|||||||
AppServices.Initialize(_toastService);
|
AppServices.Initialize(_toastService);
|
||||||
_loadingOverlay = new LoadingOverlay { Name = "loadingOverlay", Dock = DockStyle.Fill };
|
_loadingOverlay = new LoadingOverlay { Name = "loadingOverlay", Dock = DockStyle.Fill };
|
||||||
|
|
||||||
|
_titleBar = new ModernTitleBar { Name = "titleBar" };
|
||||||
|
_titleBar.Attach(this);
|
||||||
|
_titleBar.ApplyTheme(ThemeManager.Colors);
|
||||||
|
|
||||||
_appHeader = new ModernAppHeader { Name = "appHeader", Dock = DockStyle.Top };
|
_appHeader = new ModernAppHeader { Name = "appHeader", Dock = DockStyle.Top };
|
||||||
_appHeader.SetItems(new[]
|
_appHeader.SetItems(new[]
|
||||||
{
|
{
|
||||||
new SidebarItem { Key = "price", Text = "ПРАЙС-ЛИСТ", IconGlyph = "\uE8FD" },
|
new SidebarItem { Key = "price", Text = "Прайс-лист", IconGlyph = "\uE14C" },
|
||||||
new SidebarItem { Key = "orders", Text = "ЗАКАЗЫ", IconGlyph = "\uE719" },
|
new SidebarItem { Key = "orders", Text = "Заказы", IconGlyph = "\uE7BF" },
|
||||||
new SidebarItem { Key = "invoices", Text = "НАКЛАДНЫЕ", IconGlyph = "\uE8A5" },
|
new SidebarItem { Key = "invoices", Text = "Накладные", IconGlyph = "\uE8A5" },
|
||||||
new SidebarItem { Key = "upload", Text = "ЗАГРУЗИТЬ", IconGlyph = "\uE896" },
|
new SidebarItem { Key = "upload", Text = "Загрузить", IconGlyph = "\uE896" },
|
||||||
new SidebarItem { Key = "send", Text = "ОТПРАВИТЬ", IconGlyph = "\uE725" },
|
new SidebarItem { Key = "send", Text = "Отправить", IconGlyph = "\uE725" },
|
||||||
new SidebarItem { Key = "refusals", Text = "ОТКАЗЫ", IconGlyph = "\uE711" },
|
new SidebarItem { Key = "refusals", Text = "Отказы", IconGlyph = "\uE7BA" },
|
||||||
new SidebarItem { Key = "directory", Text = "СПРАВОЧНИК", IconGlyph = "\uE8FD" },
|
new SidebarItem { Key = "directory", Text = "Справочник", IconGlyph = "\uE8F1" },
|
||||||
new SidebarItem { Key = "auth", Text = "АВТОРИЗАЦИЯ", IconGlyph = "\uE192" }
|
new SidebarItem { Key = "auth", Text = "Вход", IconGlyph = "\uE192" }
|
||||||
});
|
});
|
||||||
|
|
||||||
_documentTabStrip = new ModernDocumentTabStrip { Name = "documentTabStrip", Dock = DockStyle.Top };
|
_documentTabStrip = new ModernDocumentTabStrip { Name = "documentTabStrip", Dock = DockStyle.Top };
|
||||||
@ -67,16 +75,22 @@ namespace Электронная_Фармация
|
|||||||
{
|
{
|
||||||
Name = "statusFooter",
|
Name = "statusFooter",
|
||||||
Dock = DockStyle.Bottom,
|
Dock = DockStyle.Bottom,
|
||||||
Height = 28,
|
Height = 32,
|
||||||
Tag = "status-footer",
|
Tag = "status-footer",
|
||||||
Padding = new Padding(10, 0, 4, 0)
|
Padding = new Padding(12, 0, 8, 0)
|
||||||
};
|
};
|
||||||
_statusFooter.Paint += (_, e) =>
|
_statusFooter.Paint += (_, e) =>
|
||||||
{
|
{
|
||||||
var border = ThemeManager.Colors.Border;
|
var theme = ThemeManager.Colors;
|
||||||
using (var pen = new Pen(border))
|
var g = e.Graphics;
|
||||||
|
using (var fill = new SolidBrush(theme.CartHeaderBackground))
|
||||||
{
|
{
|
||||||
e.Graphics.DrawLine(pen, 0, 0, _statusFooter.Width, 0);
|
g.FillRectangle(fill, _statusFooter.ClientRectangle);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var pen = new Pen(theme.AccentPrimaryBorder))
|
||||||
|
{
|
||||||
|
g.DrawLine(pen, 0, 0, _statusFooter.Width, 0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -118,6 +132,7 @@ namespace Электронная_Фармация
|
|||||||
Controls.Add(_statusFooter);
|
Controls.Add(_statusFooter);
|
||||||
Controls.Add(_documentTabStrip);
|
Controls.Add(_documentTabStrip);
|
||||||
Controls.Add(_appHeader);
|
Controls.Add(_appHeader);
|
||||||
|
Controls.Add(_titleBar);
|
||||||
|
|
||||||
_directoryMenu = new ContextMenuStrip();
|
_directoryMenu = new ContextMenuStrip();
|
||||||
var contractorsItem = new ToolStripMenuItem("Поставщики");
|
var contractorsItem = new ToolStripMenuItem("Поставщики");
|
||||||
@ -167,12 +182,24 @@ namespace Электронная_Фармация
|
|||||||
{
|
{
|
||||||
UiThemeHelper.ApplyToControlTree(this);
|
UiThemeHelper.ApplyToControlTree(this);
|
||||||
UpdateThemeToggleState();
|
UpdateThemeToggleState();
|
||||||
|
_titleBar?.ApplyTheme(ThemeManager.Colors);
|
||||||
WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
|
WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
|
||||||
};
|
};
|
||||||
|
|
||||||
HandleCreated += (_, __) => WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
|
HandleCreated += (_, __) => WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void WndProc(ref Message m)
|
||||||
|
{
|
||||||
|
if (FormBorderStyle == FormBorderStyle.None
|
||||||
|
&& WindowChromeHelper.TryHandleNcHitTest(this, ref m))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.WndProc(ref m);
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateThemeToggleState()
|
private void UpdateThemeToggleState()
|
||||||
{
|
{
|
||||||
if (_documentTabStrip != null)
|
if (_documentTabStrip != null)
|
||||||
@ -193,14 +220,49 @@ namespace Электронная_Фармация
|
|||||||
{
|
{
|
||||||
ThemeManager.CurrentVariant = Settings.Default.UseDarkTheme
|
ThemeManager.CurrentVariant = Settings.Default.UseDarkTheme
|
||||||
? ThemeVariant.DarkProductivity
|
? ThemeVariant.DarkProductivity
|
||||||
: ThemeVariant.PharmaTrust;
|
: ThemeVariant.ClinicalPro;
|
||||||
_themeService.Initialize(this);
|
_themeService.Initialize(this);
|
||||||
UpdateThemeToggleState();
|
UpdateThemeToggleState();
|
||||||
UiThemeHelper.ApplyToControlTree(this);
|
UiThemeHelper.ApplyToControlTree(this);
|
||||||
|
_titleBar?.ApplyTheme(ThemeManager.Colors);
|
||||||
WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
|
WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Icon = CreateAppIcon();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// keep default
|
||||||
|
}
|
||||||
RefreshCurrentUserStatus();
|
RefreshCurrentUserStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Icon CreateAppIcon()
|
||||||
|
{
|
||||||
|
using (var bmp = new Bitmap(32, 32))
|
||||||
|
using (var g = Graphics.FromImage(bmp))
|
||||||
|
{
|
||||||
|
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
|
||||||
|
g.Clear(Color.Transparent);
|
||||||
|
using (var brush = new SolidBrush(Color.FromArgb(15, 118, 110)))
|
||||||
|
{
|
||||||
|
g.FillEllipse(brush, 1, 1, 30, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var pen = new Pen(Color.White, 3f)
|
||||||
|
{
|
||||||
|
StartCap = System.Drawing.Drawing2D.LineCap.Round,
|
||||||
|
EndCap = System.Drawing.Drawing2D.LineCap.Round
|
||||||
|
})
|
||||||
|
{
|
||||||
|
g.DrawLine(pen, 16, 8, 16, 24);
|
||||||
|
g.DrawLine(pen, 8, 16, 24, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Icon.FromHandle(bmp.GetHicon());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void RefreshCurrentUserStatus()
|
private void RefreshCurrentUserStatus()
|
||||||
{
|
{
|
||||||
if (_lblCurrentUser == null)
|
if (_lblCurrentUser == null)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
@ -509,76 +509,17 @@ FROM [TempOrderItems];";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Перед seed-вставкой грузополучателей нужно гарантировать наличие LocationId,
|
// LocationId нужен для связи с BuyerLocation на сервере.
|
||||||
// иначе INSERT в Consignees (LocationId) упадёт на старых версиях БД.
|
|
||||||
TryAddColumn(conForCheckTables, "Consignees", "LocationId", "nvarchar(64)");
|
TryAddColumn(conForCheckTables, "Consignees", "LocationId", "nvarchar(64)");
|
||||||
|
|
||||||
// Seed-тестовые грузополучатели (аптеки) для удобства ручного тестирования.
|
// Удаляем старые тестовые грузополучатели — точки создаются в веб-кабинете.
|
||||||
// Вставляем только если таблица пустая.
|
using (var delTest = new SQLiteCommand(
|
||||||
using (var countConsigneesCmd = new SQLiteCommand(
|
@"DELETE FROM Consignees
|
||||||
@"SELECT COUNT(*) FROM Consignees;",
|
WHERE ConsigneesName LIKE 'Тестовая аптека%'
|
||||||
|
OR ConsigneesAddress LIKE 'Тестовый адрес%';",
|
||||||
conForCheckTables))
|
conForCheckTables))
|
||||||
{
|
{
|
||||||
var countObj = countConsigneesCmd.ExecuteScalar();
|
delTest.ExecuteNonQuery();
|
||||||
var count = 0;
|
|
||||||
try { count = Convert.ToInt32(countObj); } catch { count = 0; }
|
|
||||||
|
|
||||||
if (count == 0)
|
|
||||||
{
|
|
||||||
var testLocationIds = new[]
|
|
||||||
{
|
|
||||||
"87368f6d-488b-41a9-b170-edc629522844",
|
|
||||||
"c4cd845f-820d-45ac-a473-3df1a0b96feb",
|
|
||||||
"d6187013-00f5-4c1b-b258-c670cf71d115"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Тестовые имена/адреса — можно менять под вашу тестовую среду.
|
|
||||||
var testNames = new[]
|
|
||||||
{
|
|
||||||
"Тестовая аптека 1",
|
|
||||||
"Тестовая аптека 2",
|
|
||||||
"Тестовая аптека 3"
|
|
||||||
};
|
|
||||||
|
|
||||||
var testAddresses = new[]
|
|
||||||
{
|
|
||||||
"Тестовый адрес 1",
|
|
||||||
"Тестовый адрес 2",
|
|
||||||
"Тестовый адрес 3"
|
|
||||||
};
|
|
||||||
|
|
||||||
using (var tx = conForCheckTables.BeginTransaction())
|
|
||||||
{
|
|
||||||
int nextCode;
|
|
||||||
using (var nextCodeCmd = new SQLiteCommand(
|
|
||||||
@"SELECT ifnull(MAX(codeConsignees), 0) + 1 FROM Consignees;",
|
|
||||||
conForCheckTables,
|
|
||||||
tx))
|
|
||||||
{
|
|
||||||
nextCode = Convert.ToInt32(nextCodeCmd.ExecuteScalar());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < 3; i++)
|
|
||||||
{
|
|
||||||
using (var insertCmd = new SQLiteCommand(
|
|
||||||
@"INSERT INTO Consignees
|
|
||||||
(codeConsignees, ConsigneesName, ConsigneesAddress, ConsigneesUseAsDefault, LocationId)
|
|
||||||
VALUES (@code, @name, @address, @isDefault, @loc);",
|
|
||||||
conForCheckTables,
|
|
||||||
tx))
|
|
||||||
{
|
|
||||||
insertCmd.Parameters.AddWithValue("@code", nextCode + i);
|
|
||||||
insertCmd.Parameters.AddWithValue("@name", testNames[i]);
|
|
||||||
insertCmd.Parameters.AddWithValue("@address", testAddresses[i]);
|
|
||||||
insertCmd.Parameters.AddWithValue("@isDefault", i == 0 ? 1 : 0);
|
|
||||||
insertCmd.Parameters.AddWithValue("@loc", testLocationIds[i]);
|
|
||||||
insertCmd.ExecuteNonQuery();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.Commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TryAddColumn(conForCheckTables, "Orders", "ConsigneeName", "nvarchar(128)");
|
TryAddColumn(conForCheckTables, "Orders", "ConsigneeName", "nvarchar(128)");
|
||||||
@ -600,7 +541,7 @@ FROM [TempOrderItems];";
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var logDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
|
var logDir = AppConfig.LogsDirectory;
|
||||||
Directory.CreateDirectory(logDir);
|
Directory.CreateDirectory(logDir);
|
||||||
var logPath = Path.Combine(
|
var logPath = Path.Combine(
|
||||||
logDir,
|
logDir,
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using Электронная_Фармация.Classes;
|
using Электронная_Фармация.Classes;
|
||||||
|
using Электронная_Фармация.Properties;
|
||||||
|
|
||||||
namespace Электронная_Фармация.Forms
|
namespace Электронная_Фармация.Forms
|
||||||
{
|
{
|
||||||
@ -15,10 +17,12 @@ namespace Электронная_Фармация.Forms
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FConsignees_Load(object sender, EventArgs e)
|
private async void FConsignees_Load(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
UiThemeHelper.ApplyToControlTree(this);
|
UiThemeHelper.ApplyToControlTree(this);
|
||||||
|
btnAdd.Text = "С сервера";
|
||||||
showMeContentFromConsigneesTable();
|
showMeContentFromConsigneesTable();
|
||||||
|
await SyncFromServerAsync(silent: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showMeContentFromConsigneesTable()
|
void showMeContentFromConsigneesTable()
|
||||||
@ -49,7 +53,8 @@ order by [ConsigneesUseAsDefault] desc, [ConsigneesName]";
|
|||||||
|
|
||||||
foreach (DataGridViewColumn column in dgvConsignees.Columns)
|
foreach (DataGridViewColumn column in dgvConsignees.Columns)
|
||||||
{
|
{
|
||||||
column.ReadOnly = column.Name != "Location ID" && column.Name != "Адрес";
|
// Редактируем только адрес локально; Location ID и имя приходят с сервера.
|
||||||
|
column.ReadOnly = column.Name != "Адрес";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -88,8 +93,6 @@ order by [ConsigneesUseAsDefault] desc, [ConsigneesName]";
|
|||||||
|
|
||||||
dgvConsignees.EndEdit();
|
dgvConsignees.EndEdit();
|
||||||
|
|
||||||
// Кто должен стать активной аптекой после сохранения.
|
|
||||||
// По вашему ТЗ: "аптека выбранная в этом окне" => текущая выделенная строка.
|
|
||||||
var selectedName = dgvConsignees.CurrentRow?.Cells["Наименование"]?.Value?.ToString()?.Trim()
|
var selectedName = dgvConsignees.CurrentRow?.Cells["Наименование"]?.Value?.ToString()?.Trim()
|
||||||
?? string.Empty;
|
?? string.Empty;
|
||||||
SelectedConsigneeNameAfterSave = selectedName;
|
SelectedConsigneeNameAfterSave = selectedName;
|
||||||
@ -108,14 +111,12 @@ order by [ConsigneesUseAsDefault] desc, [ConsigneesName]";
|
|||||||
|
|
||||||
using (var cmd = new SQLiteCommand(
|
using (var cmd = new SQLiteCommand(
|
||||||
@"UPDATE Consignees
|
@"UPDATE Consignees
|
||||||
SET ConsigneesAddress = @address,
|
SET ConsigneesAddress = @address
|
||||||
LocationId = @locationId
|
|
||||||
WHERE idConsignees = @id",
|
WHERE idConsignees = @id",
|
||||||
connection,
|
connection,
|
||||||
tx))
|
tx))
|
||||||
{
|
{
|
||||||
cmd.Parameters.AddWithValue("@address", row["Адрес"]?.ToString() ?? string.Empty);
|
cmd.Parameters.AddWithValue("@address", row["Адрес"]?.ToString() ?? string.Empty);
|
||||||
cmd.Parameters.AddWithValue("@locationId", row["Location ID"]?.ToString()?.Trim() ?? string.Empty);
|
|
||||||
cmd.Parameters.AddWithValue("@id", Convert.ToInt64(row["Id"]));
|
cmd.Parameters.AddWithValue("@id", Convert.ToInt64(row["Id"]));
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
@ -137,107 +138,58 @@ order by [ConsigneesUseAsDefault] desc, [ConsigneesName]";
|
|||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnAdd_Click(object sender, EventArgs e)
|
private async void btnAdd_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
using (var dialog = new Form())
|
await SyncFromServerAsync(silent: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SyncFromServerAsync(bool silent)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
dialog.Text = "Новая аптека";
|
btnAdd.Enabled = false;
|
||||||
dialog.FormBorderStyle = FormBorderStyle.FixedDialog;
|
var client = new ApiClient();
|
||||||
dialog.StartPosition = FormStartPosition.CenterParent;
|
if (!client.IsAuthenticated)
|
||||||
// Немного увеличиваем высоту/ширину на случай DPI/шрифтов.
|
|
||||||
dialog.ClientSize = new System.Drawing.Size(480, 310);
|
|
||||||
dialog.MaximizeBox = false;
|
|
||||||
dialog.MinimizeBox = false;
|
|
||||||
dialog.ShowInTaskbar = false;
|
|
||||||
|
|
||||||
var lblName = new Label { Text = "Наименование", Left = 16, Top = 16, AutoSize = true };
|
|
||||||
var txtName = new TextBox { Left = 16, Top = 42, Width = 440, Height = 28 };
|
|
||||||
var lblAddress = new Label { Text = "Адрес", Left = 16, Top = 82, AutoSize = true };
|
|
||||||
var txtAddress = new TextBox { Left = 16, Top = 108, Width = 440, Height = 28 };
|
|
||||||
var lblLocation = new Label { Text = "Location ID", Left = 16, Top = 148, AutoSize = true };
|
|
||||||
var txtLocation = new TextBox { Left = 16, Top = 174, Width = 440, Height = 28 };
|
|
||||||
|
|
||||||
var btnOk = new Elfisa.UI.Controls.ModernButton
|
|
||||||
{
|
{
|
||||||
Text = "Добавить",
|
var token = Settings.Default.stringToken;
|
||||||
DialogResult = DialogResult.OK
|
if (string.IsNullOrWhiteSpace(token))
|
||||||
};
|
|
||||||
var btnCancel = new Elfisa.UI.Controls.ModernButton
|
|
||||||
{
|
|
||||||
Text = "Отмена",
|
|
||||||
DialogResult = DialogResult.Cancel
|
|
||||||
};
|
|
||||||
|
|
||||||
dialog.Controls.AddRange(new Control[]
|
|
||||||
{
|
|
||||||
lblName, txtName, lblAddress, txtAddress, lblLocation, txtLocation, btnOk, btnCancel
|
|
||||||
});
|
|
||||||
dialog.AcceptButton = btnOk;
|
|
||||||
dialog.CancelButton = btnCancel;
|
|
||||||
UiThemeHelper.ApplyToControlTree(dialog);
|
|
||||||
|
|
||||||
btnOk.Padding = new Padding(12, 4, 12, 4);
|
|
||||||
btnCancel.Padding = new Padding(12, 4, 12, 4);
|
|
||||||
Elfisa.UI.Controls.ModernButtonStyles.FitToText(btnOk, 36);
|
|
||||||
Elfisa.UI.Controls.ModernButtonStyles.FitToText(btnCancel, 36);
|
|
||||||
btnOk.Height = 36;
|
|
||||||
btnCancel.Height = 36;
|
|
||||||
|
|
||||||
const int padding = 16;
|
|
||||||
const int gap = 10;
|
|
||||||
|
|
||||||
// Минимально гарантируем ширину, чтобы обе кнопки поместились.
|
|
||||||
var requiredWidth = btnOk.Width + btnCancel.Width + gap + padding * 2;
|
|
||||||
if (dialog.ClientSize.Width < requiredWidth)
|
|
||||||
{
|
|
||||||
dialog.ClientSize = new System.Drawing.Size(requiredWidth, dialog.ClientSize.Height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Всегда зажимаем в границы формы после вычисления ширины ModernButton'ов.
|
|
||||||
btnCancel.Left = Math.Max(padding, dialog.ClientSize.Width - btnCancel.Width - padding);
|
|
||||||
btnCancel.Top = dialog.ClientSize.Height - btnCancel.Height - padding;
|
|
||||||
|
|
||||||
btnOk.Left = Math.Max(padding, btnCancel.Left - btnOk.Width - gap);
|
|
||||||
btnOk.Top = btnCancel.Top;
|
|
||||||
|
|
||||||
if (dialog.ShowDialog(this) != DialogResult.OK)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var name = (txtName.Text ?? string.Empty).Trim();
|
|
||||||
var address = (txtAddress.Text ?? string.Empty).Trim();
|
|
||||||
var locationId = (txtLocation.Text ?? string.Empty).Trim();
|
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
|
||||||
{
|
|
||||||
UiDialogs.ShowError("Укажите наименование аптеки.", "Грузополучатели", this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var connection = new SQLiteConnection(AppConfig.SqliteConnectionString))
|
|
||||||
{
|
|
||||||
connection.Open();
|
|
||||||
int nextCode = 1;
|
|
||||||
using (var codeCmd = new SQLiteCommand("SELECT ifnull(MAX(codeConsignees), 0) + 1 FROM Consignees", connection))
|
|
||||||
{
|
{
|
||||||
nextCode = Convert.ToInt32(codeCmd.ExecuteScalar());
|
if (!silent)
|
||||||
}
|
{
|
||||||
|
UiDialogs.ShowInfo(
|
||||||
using (var insert = new SQLiteCommand(
|
"Сначала войдите на сервер — грузополучатели подтягиваются из веб-кабинета покупателя.",
|
||||||
@"INSERT INTO Consignees (codeConsignees, ConsigneesName, ConsigneesAddress, ConsigneesUseAsDefault, LocationId)
|
"Грузополучатели",
|
||||||
VALUES (@code, @name, @address, 0, @locationId)",
|
this);
|
||||||
connection))
|
}
|
||||||
{
|
return;
|
||||||
insert.Parameters.AddWithValue("@code", nextCode);
|
|
||||||
insert.Parameters.AddWithValue("@name", name);
|
|
||||||
insert.Parameters.AddWithValue("@address", address);
|
|
||||||
insert.Parameters.AddWithValue("@locationId", locationId);
|
|
||||||
insert.ExecuteNonQuery();
|
|
||||||
}
|
}
|
||||||
|
client.SetToken(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
ToastNotification.ShowSuccess("Аптека добавлена");
|
var locations = await client.GetBuyerLocationsAsync();
|
||||||
|
var count = ConsigneeHelper.SyncFromServer(locations);
|
||||||
showMeContentFromConsigneesTable();
|
showMeContentFromConsigneesTable();
|
||||||
|
if (!silent)
|
||||||
|
{
|
||||||
|
ToastNotification.ShowSuccess(count > 0
|
||||||
|
? $"Обновлено грузополучателей: {count}"
|
||||||
|
: "На сервере пока нет грузополучателей");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (!silent)
|
||||||
|
{
|
||||||
|
UiDialogs.ShowError($"Не удалось загрузить грузополучателей с сервера.\n{ex.Message}", "Грузополучатели", this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AppDebugLog.Error("FConsignees", "Тихая синхронизация грузополучателей не удалась", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
btnAdd.Enabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,15 +201,15 @@ order by [ConsigneesUseAsDefault] desc, [ConsigneesName]";
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var name = dgvConsignees.CurrentRow.Cells["Наименование"]?.Value?.ToString();
|
var name = dgvConsignees.CurrentRow.Cells["Наименование"]?.Value?.ToString()?.Trim();
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsigneeHelper.SetActiveConsignee(name);
|
ConsigneeHelper.SetActiveConsignee(name);
|
||||||
ToastNotification.ShowSuccess($"Аптека по умолчанию: {name}");
|
|
||||||
showMeContentFromConsigneesTable();
|
showMeContentFromConsigneesTable();
|
||||||
|
ToastNotification.ShowSuccess($"Аптека по умолчанию: {name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,6 +54,21 @@ namespace Электронная_Фармация.HelpForms
|
|||||||
SetOverallProgress(1, "Авторизация...");
|
SetOverallProgress(1, "Авторизация...");
|
||||||
await EnsureAuthenticatedAsync(client);
|
await EnsureAuthenticatedAsync(client);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AppendStatus("Загружаю грузополучателей с сервера...");
|
||||||
|
var locations = await client.GetBuyerLocationsAsync();
|
||||||
|
var synced = ConsigneeHelper.SyncFromServer(locations);
|
||||||
|
AppendStatus(synced > 0
|
||||||
|
? $"Грузополучателей обновлено: {synced}"
|
||||||
|
: "Грузополучателей на сервере пока нет");
|
||||||
|
}
|
||||||
|
catch (Exception locEx)
|
||||||
|
{
|
||||||
|
AppendStatus($"Грузополучатели не обновлены: {locEx.Message}");
|
||||||
|
AppDebugLog.Error("Download", "Не удалось синхронизировать грузополучателей", locEx);
|
||||||
|
}
|
||||||
|
|
||||||
SetOverallProgress(3, "Загрузка прайса с сервера...");
|
SetOverallProgress(3, "Загрузка прайса с сервера...");
|
||||||
AppendStatus("Загружаю сводный прайс...");
|
AppendStatus("Загружаю сводный прайс...");
|
||||||
var allSuppliersSummary = await GetFullPriceSummaryPagedAsync(client);
|
var allSuppliersSummary = await GetFullPriceSummaryPagedAsync(client);
|
||||||
|
|||||||
@ -1,36 +1,37 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
// пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,
|
// пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ. пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ,
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
|
// пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ.
|
||||||
[assembly: AssemblyTitle("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>")]
|
[assembly: AssemblyTitle("пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ")]
|
||||||
[assembly: AssemblyDescription("")]
|
[assembly: AssemblyDescription("")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany("")]
|
[assembly: AssemblyCompany("")]
|
||||||
[assembly: AssemblyProduct("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>")]
|
[assembly: AssemblyProduct("пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ")]
|
||||||
[assembly: AssemblyCopyright("Copyright <EFBFBD> 2023")]
|
[assembly: AssemblyCopyright("Copyright пїЅ 2023")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> False <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ComVisible <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
// пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ False пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ ComVisible пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ
|
||||||
// <EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> COM. <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>
|
// пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ COM. пїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅ
|
||||||
// COM, <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ComVisible <20> TRUE <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>.
|
// COM, пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ ComVisible пїЅ TRUE пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅ.
|
||||||
[assembly: ComVisible(false)]
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> GUID <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> COM
|
// пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ GUID пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅ, пїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ COM
|
||||||
[assembly: Guid("145d8060-4187-4871-b4fb-8df795727b66")]
|
[assembly: Guid("145d8060-4187-4871-b4fb-8df795727b66")]
|
||||||
|
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>:
|
// пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ:
|
||||||
//
|
//
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
// пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
// пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
// пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
// пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ
|
||||||
//
|
//
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
// пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> "*", <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>:
|
// пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ "*", пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅ:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.0.20.0")]
|
[assembly: AssemblyVersion("1.0.21.0")]
|
||||||
[assembly: AssemblyFileVersion("1.0.20.0")]
|
[assembly: AssemblyFileVersion("1.0.21.0")]
|
||||||
|
|
||||||
|
|||||||
@ -131,6 +131,18 @@ namespace Электронная_Фармация.Properties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("0")]
|
||||||
|
public int PriceListSplitterDistance {
|
||||||
|
get {
|
||||||
|
return ((int)(this["PriceListSplitterDistance"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["PriceListSplitterDistance"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||||
|
|||||||
@ -29,6 +29,9 @@
|
|||||||
<Setting Name="UseDarkTheme" Type="System.Boolean" Scope="User">
|
<Setting Name="UseDarkTheme" Type="System.Boolean" Scope="User">
|
||||||
<Value Profile="(Default)">False</Value>
|
<Value Profile="(Default)">False</Value>
|
||||||
</Setting>
|
</Setting>
|
||||||
|
<Setting Name="PriceListSplitterDistance" Type="System.Int32" Scope="User">
|
||||||
|
<Value Profile="(Default)">0</Value>
|
||||||
|
</Setting>
|
||||||
<Setting Name="InvoiceExportPath" Type="System.String" Scope="User">
|
<Setting Name="InvoiceExportPath" Type="System.String" Scope="User">
|
||||||
<Value Profile="(Default)" />
|
<Value Profile="(Default)" />
|
||||||
</Setting>
|
</Setting>
|
||||||
|
|||||||
BIN
src/ElectronicPharmacy/Resources/elfisa.ico
Normal file
BIN
src/ElectronicPharmacy/Resources/elfisa.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
@ -1,4 +1,4 @@
|
|||||||
namespace Электронная_Фармация.UserControls
|
namespace Электронная_Фармация.UserControls
|
||||||
{
|
{
|
||||||
partial class UCPriceList
|
partial class UCPriceList
|
||||||
{
|
{
|
||||||
@ -99,7 +99,7 @@
|
|||||||
this.SplitPriceList.Panel2.Controls.Add(this.POrderInfo);
|
this.SplitPriceList.Panel2.Controls.Add(this.POrderInfo);
|
||||||
this.SplitPriceList.Size = new System.Drawing.Size(1053, 532);
|
this.SplitPriceList.Size = new System.Drawing.Size(1053, 532);
|
||||||
this.SplitPriceList.SplitterDistance = 290;
|
this.SplitPriceList.SplitterDistance = 290;
|
||||||
this.SplitPriceList.SplitterWidth = 2;
|
this.SplitPriceList.SplitterWidth = 6;
|
||||||
this.SplitPriceList.TabIndex = 0;
|
this.SplitPriceList.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// TLPPriceList
|
// TLPPriceList
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
@ -78,13 +78,14 @@ namespace Электронная_Фармация.UserControls
|
|||||||
private void UCPriceList_Load(object sender, EventArgs e)
|
private void UCPriceList_Load(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
EnsureBrowseSplit();
|
EnsureBrowseSplit();
|
||||||
|
EnsureChromeLayout();
|
||||||
UiThemeHelper.ApplyToControlTree(this);
|
UiThemeHelper.ApplyToControlTree(this);
|
||||||
Tag = "chrome-child";
|
Tag = "chrome-child";
|
||||||
|
|
||||||
AlignSearchRow();
|
AlignSearchRow();
|
||||||
|
LayoutContentBelowSearch();
|
||||||
|
|
||||||
panel1.SizeChanged += (_, __) => LayoutMarkupRow();
|
SizeChanged += (_, __) => LayoutContentBelowSearch();
|
||||||
LayoutMarkupRow();
|
|
||||||
|
|
||||||
txtConsignee.Enabled = false;
|
txtConsignee.Enabled = false;
|
||||||
txtConsignee.Text = _WorkWithConsignee;
|
txtConsignee.Text = _WorkWithConsignee;
|
||||||
@ -97,6 +98,120 @@ namespace Электронная_Фармация.UserControls
|
|||||||
txtSearchGoodNameInPriceList.Focus();
|
txtSearchGoodNameInPriceList.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Поиск остаётся сверху как в оригинале; контент явно ниже — без Dock-наложения.
|
||||||
|
/// </summary>
|
||||||
|
private void EnsureChromeLayout()
|
||||||
|
{
|
||||||
|
// Убираем старую dock-панель, если осталась от прошлых сборок в той же сессии.
|
||||||
|
var oldToolbar = Controls["priceToolbar"];
|
||||||
|
if (oldToolbar != null)
|
||||||
|
{
|
||||||
|
var toRestore = oldToolbar.Controls.Cast<Control>().ToArray();
|
||||||
|
foreach (var control in toRestore)
|
||||||
|
{
|
||||||
|
oldToolbar.Controls.Remove(control);
|
||||||
|
Controls.Add(control);
|
||||||
|
control.BringToFront();
|
||||||
|
}
|
||||||
|
|
||||||
|
Controls.Remove(oldToolbar);
|
||||||
|
oldToolbar.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Строка наценки скрыта — проценты берутся из настроек/дефолта без UI-прослойки.
|
||||||
|
if (TLPPriceList.RowStyles.Count >= 2)
|
||||||
|
{
|
||||||
|
TLPPriceList.RowStyles[1].SizeType = SizeType.Absolute;
|
||||||
|
TLPPriceList.RowStyles[1].Height = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
panel1.Visible = false;
|
||||||
|
panel1.Height = 0;
|
||||||
|
|
||||||
|
SplitPriceList.Panel1MinSize = 120;
|
||||||
|
SplitPriceList.Panel2MinSize = 80;
|
||||||
|
SplitPriceList.IsSplitterFixed = false;
|
||||||
|
SplitPriceList.FixedPanel = FixedPanel.None;
|
||||||
|
SplitPriceList.SplitterWidth = 6;
|
||||||
|
|
||||||
|
// Восстановить положение сплиттера корзины, если пользователь уже двигал.
|
||||||
|
RestoreCartSplitterDistance();
|
||||||
|
SplitPriceList.SplitterMoved += (_, __) => PersistCartSplitterDistance();
|
||||||
|
|
||||||
|
dgvPriceList.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None;
|
||||||
|
dgvPriceList.ColumnHeadersVisible = true;
|
||||||
|
dgvPriceList.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
|
||||||
|
if (dgvPriceList.ColumnHeadersHeight < 34)
|
||||||
|
{
|
||||||
|
dgvPriceList.ColumnHeadersHeight = 36;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!POrderInfo.Controls.ContainsKey("cartHeader"))
|
||||||
|
{
|
||||||
|
var cartHeader = new ModernSectionHeader
|
||||||
|
{
|
||||||
|
Name = "cartHeader",
|
||||||
|
Height = 32,
|
||||||
|
HeaderText = "Корзина"
|
||||||
|
};
|
||||||
|
cartHeader.ApplyTheme(Elfisa.UI.Theming.ThemeManager.Colors, cartStyle: true);
|
||||||
|
cartHeader.Dock = DockStyle.Top;
|
||||||
|
POrderInfo.Controls.Add(cartHeader);
|
||||||
|
tabOrderItems.SendToBack();
|
||||||
|
cartHeader.BringToFront();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LayoutContentBelowSearch()
|
||||||
|
{
|
||||||
|
const int searchBand = 52;
|
||||||
|
PContentPriceList.Dock = DockStyle.None;
|
||||||
|
PContentPriceList.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||||
|
PContentPriceList.SetBounds(
|
||||||
|
0,
|
||||||
|
searchBand,
|
||||||
|
Math.Max(100, ClientSize.Width),
|
||||||
|
Math.Max(120, ClientSize.Height - searchBand));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestoreCartSplitterDistance()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var saved = Properties.Settings.Default.PriceListSplitterDistance;
|
||||||
|
if (saved <= 0 || SplitPriceList.Height < 200)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var max = Math.Max(
|
||||||
|
SplitPriceList.Panel1MinSize,
|
||||||
|
SplitPriceList.Height - SplitPriceList.Panel2MinSize - SplitPriceList.SplitterWidth);
|
||||||
|
SplitPriceList.SplitterDistance = Math.Max(SplitPriceList.Panel1MinSize, Math.Min(saved, max));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore until layout ready
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PersistCartSplitterDistance()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (SplitPriceList.SplitterDistance > 80)
|
||||||
|
{
|
||||||
|
Properties.Settings.Default.PriceListSplitterDistance = SplitPriceList.SplitterDistance;
|
||||||
|
Properties.Settings.Default.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void EnsureBrowseSplit()
|
private void EnsureBrowseSplit()
|
||||||
{
|
{
|
||||||
if (splitNameMgBrowse != null)
|
if (splitNameMgBrowse != null)
|
||||||
@ -112,13 +227,35 @@ namespace Электронная_Фармация.UserControls
|
|||||||
Name = "splitNameMgBrowse",
|
Name = "splitNameMgBrowse",
|
||||||
Dock = DockStyle.Fill,
|
Dock = DockStyle.Fill,
|
||||||
Orientation = Orientation.Vertical,
|
Orientation = Orientation.Vertical,
|
||||||
SplitterWidth = 4,
|
SplitterWidth = 6,
|
||||||
Visible = false
|
Visible = false,
|
||||||
|
Panel1MinSize = 100,
|
||||||
|
Panel2MinSize = 80,
|
||||||
|
IsSplitterFixed = false,
|
||||||
|
FixedPanel = FixedPanel.None
|
||||||
};
|
};
|
||||||
splitNameMgBrowse.Panel1.Controls.Add(dgvBrowseNames);
|
splitNameMgBrowse.Panel1.Controls.Add(dgvBrowseNames);
|
||||||
splitNameMgBrowse.Panel2.Controls.Add(dgvBrowseMg);
|
splitNameMgBrowse.Panel2.Controls.Add(dgvBrowseMg);
|
||||||
|
|
||||||
TLPPriceList.Controls.Add(splitNameMgBrowse, 0, 0);
|
var gridHost = TLPPriceList.Controls["priceGridHost"] as Panel;
|
||||||
|
if (gridHost == null)
|
||||||
|
{
|
||||||
|
gridHost = new Panel
|
||||||
|
{
|
||||||
|
Name = "priceGridHost",
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
Margin = new Padding(0),
|
||||||
|
Padding = new Padding(0),
|
||||||
|
Tag = "chrome-child"
|
||||||
|
};
|
||||||
|
|
||||||
|
TLPPriceList.Controls.Remove(dgvPriceList);
|
||||||
|
dgvPriceList.Dock = DockStyle.Fill;
|
||||||
|
gridHost.Controls.Add(dgvPriceList);
|
||||||
|
TLPPriceList.Controls.Add(gridHost, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
gridHost.Controls.Add(splitNameMgBrowse);
|
||||||
splitNameMgBrowse.BringToFront();
|
splitNameMgBrowse.BringToFront();
|
||||||
|
|
||||||
dgvBrowseNames.SelectionChanged += dgvBrowseNames_SelectionChanged;
|
dgvBrowseNames.SelectionChanged += dgvBrowseNames_SelectionChanged;
|
||||||
@ -157,11 +294,13 @@ namespace Электронная_Фармация.UserControls
|
|||||||
|
|
||||||
private void AlignSearchRow()
|
private void AlignSearchRow()
|
||||||
{
|
{
|
||||||
const int searchY = 9;
|
const int searchY = 10;
|
||||||
const int searchHeight = 32;
|
const int searchHeight = 32;
|
||||||
const int clearGap = 2;
|
const int clearGap = 4;
|
||||||
|
const int leftPad = 10;
|
||||||
|
|
||||||
txtSearchGoodNameInPriceList.SetBounds(78, searchY, 313, searchHeight);
|
label2.Location = new Point(leftPad, searchY + 4);
|
||||||
|
txtSearchGoodNameInPriceList.SetBounds(label2.Right + 6, searchY, 300, searchHeight);
|
||||||
btnClearSearchInPriceList.SetBounds(
|
btnClearSearchInPriceList.SetBounds(
|
||||||
txtSearchGoodNameInPriceList.Right + clearGap,
|
txtSearchGoodNameInPriceList.Right + clearGap,
|
||||||
searchY,
|
searchY,
|
||||||
@ -169,38 +308,23 @@ namespace Электронная_Фармация.UserControls
|
|||||||
searchHeight);
|
searchHeight);
|
||||||
ModernButtonStyles.ApplyClear(btnClearSearchInPriceList);
|
ModernButtonStyles.ApplyClear(btnClearSearchInPriceList);
|
||||||
|
|
||||||
|
lblSupplierFilter.Location = new Point(btnClearSearchInPriceList.Right + 16, searchY + 4);
|
||||||
|
comboSupplierFilter.SetBounds(lblSupplierFilter.Right + 6, searchY, 240, searchHeight);
|
||||||
|
btnClearSupplierFilter.SetBounds(comboSupplierFilter.Right + clearGap, searchY, 32, searchHeight);
|
||||||
btnClearSupplierFilter.Height = searchHeight;
|
btnClearSupplierFilter.Height = searchHeight;
|
||||||
|
|
||||||
|
// Поиск всегда поверх контента, но контент начинается ниже searchBand.
|
||||||
|
label2.BringToFront();
|
||||||
|
txtSearchGoodNameInPriceList.BringToFront();
|
||||||
|
btnClearSearchInPriceList.BringToFront();
|
||||||
|
lblSupplierFilter.BringToFront();
|
||||||
|
comboSupplierFilter.BringToFront();
|
||||||
|
btnClearSupplierFilter.BringToFront();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LayoutMarkupRow()
|
private void LayoutMarkupRow()
|
||||||
{
|
{
|
||||||
// Keep markup row readable at different DPI/window sizes.
|
// UI наценки скрыт; метод оставлен на случай повторного включения.
|
||||||
try
|
|
||||||
{
|
|
||||||
var paddingLeft = 8;
|
|
||||||
var y = 30;
|
|
||||||
var gap = 8;
|
|
||||||
|
|
||||||
label4.Location = new Point(paddingLeft, y + 2);
|
|
||||||
|
|
||||||
numericPercent.Location = new Point(label4.Right + 6, y);
|
|
||||||
numericPercent.Width = 56;
|
|
||||||
|
|
||||||
checkUsePercentageAsDefault.AutoSize = true;
|
|
||||||
checkUsePercentageAsDefault.Location = new Point(
|
|
||||||
Math.Max(numericPercent.Right + gap, panel1.ClientSize.Width - checkUsePercentageAsDefault.PreferredSize.Width - 10),
|
|
||||||
y + 2);
|
|
||||||
|
|
||||||
lblPriceForGood.AutoSize = false;
|
|
||||||
lblPriceForGood.AutoEllipsis = true;
|
|
||||||
lblPriceForGood.Location = new Point(numericPercent.Right + gap, y + 2);
|
|
||||||
lblPriceForGood.Height = 22;
|
|
||||||
lblPriceForGood.Width = Math.Max(90, checkUsePercentageAsDefault.Left - lblPriceForGood.Left - gap);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// best-effort layout; ignore
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeSupplierFilter()
|
private void InitializeSupplierFilter()
|
||||||
@ -725,10 +849,21 @@ namespace Электронная_Фармация.UserControls
|
|||||||
{
|
{
|
||||||
EnsureBrowseSplit();
|
EnsureBrowseSplit();
|
||||||
|
|
||||||
|
var gridHost = TLPPriceList.Controls["priceGridHost"] as Panel;
|
||||||
|
if (gridHost != null)
|
||||||
|
{
|
||||||
|
gridHost.Dock = DockStyle.Fill;
|
||||||
|
if (gridHost.Padding.Top < 8)
|
||||||
|
{
|
||||||
|
gridHost.Padding = new Padding(0, 8, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (browseVisible)
|
if (browseVisible)
|
||||||
{
|
{
|
||||||
dgvPriceList.Visible = false;
|
dgvPriceList.Visible = false;
|
||||||
splitNameMgBrowse.Visible = true;
|
splitNameMgBrowse.Visible = true;
|
||||||
|
splitNameMgBrowse.Dock = DockStyle.Fill;
|
||||||
splitNameMgBrowse.BringToFront();
|
splitNameMgBrowse.BringToFront();
|
||||||
|
|
||||||
var width = Math.Max(200, splitNameMgBrowse.Width);
|
var width = Math.Max(200, splitNameMgBrowse.Width);
|
||||||
@ -754,6 +889,9 @@ namespace Электронная_Фармация.UserControls
|
|||||||
|
|
||||||
splitNameMgBrowse.Visible = false;
|
splitNameMgBrowse.Visible = false;
|
||||||
dgvPriceList.Visible = true;
|
dgvPriceList.Visible = true;
|
||||||
|
dgvPriceList.Dock = DockStyle.Fill;
|
||||||
|
dgvPriceList.ColumnHeadersVisible = true;
|
||||||
|
dgvPriceList.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None;
|
||||||
dgvPriceList.BringToFront();
|
dgvPriceList.BringToFront();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ using Elfisa.UI.Theming;
|
|||||||
namespace Elfisa.UI.Controls
|
namespace Elfisa.UI.Controls
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Верхняя синяя шапка как в оригинале: 3 равные кнопки, owner-draw + плавный hover (Fluent).
|
/// Верхняя навигация: бренд + пункты (иконка сверху, подпись снизу).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ModernAppHeader : Panel
|
public class ModernAppHeader : Panel
|
||||||
{
|
{
|
||||||
@ -21,11 +21,13 @@ namespace Elfisa.UI.Controls
|
|||||||
private string _hoverKey;
|
private string _hoverKey;
|
||||||
private float _hoverProgress;
|
private float _hoverProgress;
|
||||||
|
|
||||||
|
private const int BrandWidth = 118;
|
||||||
|
|
||||||
public event EventHandler<string> ItemSelected;
|
public event EventHandler<string> ItemSelected;
|
||||||
|
|
||||||
public ModernAppHeader()
|
public ModernAppHeader()
|
||||||
{
|
{
|
||||||
Height = 78;
|
Height = 64;
|
||||||
Dock = DockStyle.Top;
|
Dock = DockStyle.Top;
|
||||||
Cursor = Cursors.Hand;
|
Cursor = Cursors.Hand;
|
||||||
SetStyle(ControlStyles.AllPaintingInWmPaint |
|
SetStyle(ControlStyles.AllPaintingInWmPaint |
|
||||||
@ -64,21 +66,16 @@ namespace Elfisa.UI.Controls
|
|||||||
var g = e.Graphics;
|
var g = e.Graphics;
|
||||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||||
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
|
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
|
||||||
|
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||||
|
|
||||||
var rect = ClientRectangle;
|
var rect = ClientRectangle;
|
||||||
using (var brush = new LinearGradientBrush(rect, theme.TitleBarTop, theme.TitleBarBottom, 90f))
|
// Сплошной основной teal — без градиента, в тон AccentPrimary.
|
||||||
|
using (var fill = new SolidBrush(theme.AccentPrimary))
|
||||||
{
|
{
|
||||||
g.FillRectangle(brush, rect);
|
g.FillRectangle(fill, rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var sheen = new LinearGradientBrush(
|
DrawBrand(g, theme);
|
||||||
new Rectangle(0, 0, Width, Height / 2),
|
|
||||||
Color.FromArgb(28, 255, 255, 255),
|
|
||||||
Color.FromArgb(0, 255, 255, 255),
|
|
||||||
LinearGradientMode.Vertical))
|
|
||||||
{
|
|
||||||
g.FillRectangle(sheen, 0, 0, Width, Height / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_items.Count == 0)
|
if (_items.Count == 0)
|
||||||
{
|
{
|
||||||
@ -86,72 +83,125 @@ namespace Elfisa.UI.Controls
|
|||||||
}
|
}
|
||||||
|
|
||||||
_hitAreas.Clear();
|
_hitAreas.Clear();
|
||||||
var gap = 8;
|
const int gap = 6;
|
||||||
var btnWidth = (Width - gap * (_items.Count + 1)) / _items.Count;
|
const int padRight = 8;
|
||||||
var btnHeight = Height - 12;
|
var btnHeight = Height - 10;
|
||||||
var top = 6;
|
var top = 5;
|
||||||
|
var x = BrandWidth + 6;
|
||||||
|
|
||||||
using (var iconFont = new Font("Segoe MDL2 Assets", 18f))
|
using (var iconFont = FluentFonts.CreateIconFont(18f))
|
||||||
using (var textFont = new Font("Segoe UI", 10f, FontStyle.Bold))
|
using (var textFont = new Font("Segoe UI Semibold", 8.5f, FontStyle.Bold))
|
||||||
{
|
{
|
||||||
|
var iconFormat = new StringFormat
|
||||||
|
{
|
||||||
|
Alignment = StringAlignment.Center,
|
||||||
|
LineAlignment = StringAlignment.Center,
|
||||||
|
FormatFlags = StringFormatFlags.NoWrap
|
||||||
|
};
|
||||||
|
|
||||||
|
// Равная ширина слотов — подписи целиком помещаются на широком окне.
|
||||||
|
var available = Math.Max(100, Width - BrandWidth - padRight - 6);
|
||||||
|
var btnWidth = Math.Max(72, (available - gap * (_items.Count - 1)) / _items.Count);
|
||||||
|
|
||||||
for (var i = 0; i < _items.Count; i++)
|
for (var i = 0; i < _items.Count; i++)
|
||||||
{
|
{
|
||||||
var item = _items[i];
|
var item = _items[i];
|
||||||
var btnRect = new Rectangle(gap + i * (btnWidth + gap), top, btnWidth, btnHeight);
|
var btnRect = new Rectangle(x, top, btnWidth, btnHeight);
|
||||||
_hitAreas[item.Key] = btnRect;
|
_hitAreas[item.Key] = btnRect;
|
||||||
|
x += btnWidth + gap;
|
||||||
|
|
||||||
var isActive = item.Key == _activeKey;
|
var isActive = item.Key == _activeKey;
|
||||||
var isHover = item.Key == _hoverKey;
|
var isHover = item.Key == _hoverKey;
|
||||||
var hoverBoost = isHover ? _hoverProgress * 0.22f : 0f;
|
var hoverBoost = isHover ? _hoverProgress * 0.18f : 0f;
|
||||||
var baseAlpha = isActive ? 0.28f : 0.08f;
|
var baseAlpha = isActive ? 0.34f : 0.07f;
|
||||||
var alpha = (int)(255 * Math.Min(0.55f, baseAlpha + hoverBoost));
|
var alpha = (int)(255 * Math.Min(0.52f, baseAlpha + hoverBoost));
|
||||||
var fill = Color.FromArgb(alpha, 255, 255, 255);
|
|
||||||
|
|
||||||
using (var fillBrush = new SolidBrush(fill))
|
using (var fillBrush = new SolidBrush(Color.FromArgb(alpha, 255, 255, 255)))
|
||||||
{
|
{
|
||||||
GraphicsExtensions.FillRoundedRectangle(g, fillBrush, btnRect, theme.CornerRadiusSmall);
|
GraphicsExtensions.FillRoundedRectangle(g, fillBrush, btnRect, theme.CornerRadiusSmall);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isActive)
|
if (isActive)
|
||||||
{
|
{
|
||||||
using (var pen = new Pen(Color.FromArgb(180, 255, 255, 255), 1f))
|
using (var pen = new Pen(Color.FromArgb(210, 255, 255, 255), 1.1f))
|
||||||
{
|
{
|
||||||
GraphicsExtensions.DrawRoundedRectangle(g, pen, btnRect, theme.CornerRadiusSmall);
|
GraphicsExtensions.DrawRoundedRectangle(g, pen, btnRect, theme.CornerRadiusSmall);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using (var accent = new SolidBrush(Color.FromArgb(235, 255, 255, 255)))
|
||||||
|
{
|
||||||
|
g.FillRectangle(accent, btnRect.X + 10, btnRect.Bottom - 3, btnRect.Width - 20, 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (isHover && _hoverProgress > 0.05f)
|
else if (isHover && _hoverProgress > 0.05f)
|
||||||
{
|
{
|
||||||
using (var pen = new Pen(Color.FromArgb((int)(120 * _hoverProgress), 255, 255, 255), 1f))
|
using (var pen = new Pen(Color.FromArgb((int)(110 * _hoverProgress), 255, 255, 255), 1f))
|
||||||
{
|
{
|
||||||
GraphicsExtensions.DrawRoundedRectangle(g, pen, btnRect, theme.CornerRadiusSmall);
|
GraphicsExtensions.DrawRoundedRectangle(g, pen, btnRect, theme.CornerRadiusSmall);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var iconRect = new Rectangle(btnRect.X, btnRect.Y + 8, btnRect.Width, 26);
|
|
||||||
var textRect = new Rectangle(btnRect.X, btnRect.Y + 34, btnRect.Width, btnRect.Height - 34);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(item.IconGlyph))
|
if (!string.IsNullOrEmpty(item.IconGlyph))
|
||||||
{
|
{
|
||||||
TextRenderer.DrawText(
|
var iconRect = new RectangleF(btnRect.X, btnRect.Y + 4, btnRect.Width, 26);
|
||||||
g,
|
using (var brush = new SolidBrush(Color.White))
|
||||||
item.IconGlyph,
|
{
|
||||||
iconFont,
|
g.DrawString(item.IconGlyph, iconFont, brush, iconRect, iconFormat);
|
||||||
iconRect,
|
}
|
||||||
Color.White,
|
|
||||||
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var textRect = new Rectangle(btnRect.X + 2, btnRect.Y + 30, btnRect.Width - 4, btnRect.Height - 34);
|
||||||
TextRenderer.DrawText(
|
TextRenderer.DrawText(
|
||||||
g,
|
g,
|
||||||
item.Text,
|
item.Text,
|
||||||
textFont,
|
textFont,
|
||||||
textRect,
|
textRect,
|
||||||
Color.White,
|
Color.White,
|
||||||
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine);
|
TextFormatFlags.HorizontalCenter | TextFormatFlags.Top | TextFormatFlags.EndEllipsis | TextFormatFlags.SingleLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawBrand(Graphics g, ThemeColors theme)
|
||||||
|
{
|
||||||
|
var brandRect = new Rectangle(0, 0, BrandWidth, Height);
|
||||||
|
using (var dim = new SolidBrush(Color.FromArgb(28, 0, 0, 0)))
|
||||||
|
{
|
||||||
|
g.FillRectangle(dim, brandRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var pen = new Pen(Color.FromArgb(50, 255, 255, 255), 1f))
|
||||||
|
{
|
||||||
|
g.DrawLine(pen, BrandWidth - 1, 8, BrandWidth - 1, Height - 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
var iconSize = 22;
|
||||||
|
var iconRect = new Rectangle(12, (Height - iconSize) / 2, iconSize, iconSize);
|
||||||
|
using (var iconBrush = new SolidBrush(Color.FromArgb(40, 255, 255, 255)))
|
||||||
|
{
|
||||||
|
GraphicsExtensions.FillRoundedRectangle(g, iconBrush, iconRect, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var crossPen = new Pen(Color.White, 2f) { StartCap = LineCap.Round, EndCap = LineCap.Round })
|
||||||
|
{
|
||||||
|
var cx = iconRect.X + iconRect.Width / 2f;
|
||||||
|
var cy = iconRect.Y + iconRect.Height / 2f;
|
||||||
|
g.DrawLine(crossPen, cx - 5, cy, cx + 5, cy);
|
||||||
|
g.DrawLine(crossPen, cx, cy - 5, cx, cy + 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var brandFont = new Font("Segoe UI Semibold", 11f, FontStyle.Bold))
|
||||||
|
{
|
||||||
|
TextRenderer.DrawText(
|
||||||
|
g,
|
||||||
|
"ЭльФиСА",
|
||||||
|
brandFont,
|
||||||
|
new Rectangle(40, 0, BrandWidth - 46, Height),
|
||||||
|
Color.White,
|
||||||
|
TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.SingleLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnPaintBackground(PaintEventArgs e)
|
protected override void OnPaintBackground(PaintEventArgs e)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@ -445,13 +445,15 @@ namespace Elfisa.UI.Controls
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var borderColor = _buttonStyle == ModernButtonStyle.DangerSoft
|
var borderColor = _buttonStyle == ModernButtonStyle.DangerSoft
|
||||||
? GraphicsExtensions.Blend(theme.Danger, Color.White, 0.55f)
|
? GraphicsExtensions.Blend(theme.Danger, Color.White, 0.45f)
|
||||||
: _buttonStyle == ModernButtonStyle.Subtle
|
: _buttonStyle == ModernButtonStyle.Subtle
|
||||||
? theme.Border
|
? theme.Border
|
||||||
: theme.InputBorder;
|
: theme.InputBorder;
|
||||||
if (_hover && Enabled)
|
if (_hover && Enabled)
|
||||||
{
|
{
|
||||||
borderColor = theme.InputBorderFocus;
|
borderColor = _buttonStyle == ModernButtonStyle.DangerSoft
|
||||||
|
? theme.Danger
|
||||||
|
: theme.InputBorderFocus;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var pen = new Pen(borderColor, 1f))
|
using (var pen = new Pen(borderColor, 1f))
|
||||||
|
|||||||
@ -30,7 +30,8 @@ namespace Elfisa.UI.Controls
|
|||||||
|
|
||||||
if (IsDangerAction(text))
|
if (IsDangerAction(text))
|
||||||
{
|
{
|
||||||
ApplyAction(button, ModernButtonStyle.Danger);
|
// Мягкий danger в тон Clinical Pro — без кислотно-красного «крика».
|
||||||
|
ApplyAction(button, ModernButtonStyle.DangerSoft);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +74,7 @@ namespace Elfisa.UI.Controls
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.ButtonStyle = ModernButtonStyle.Danger;
|
button.ButtonStyle = ModernButtonStyle.DangerSoft;
|
||||||
button.CompactIconMode = true;
|
button.CompactIconMode = true;
|
||||||
button.Image = null;
|
button.Image = null;
|
||||||
button.BackgroundImage = null;
|
button.BackgroundImage = null;
|
||||||
|
|||||||
@ -177,11 +177,18 @@ namespace Elfisa.UI.Controls
|
|||||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||||
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
|
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
|
||||||
|
|
||||||
using (var bgBrush = new SolidBrush(theme.BackgroundSecondary))
|
// Та же «кожа», что у шапки/футера: лёгкий teal-тинт, не голый slate.
|
||||||
|
var stripBg = GraphicsExtensions.Blend(theme.Surface, theme.AccentPrimarySoft, 0.55f);
|
||||||
|
using (var bgBrush = new SolidBrush(stripBg))
|
||||||
{
|
{
|
||||||
g.FillRectangle(bgBrush, ClientRectangle);
|
g.FillRectangle(bgBrush, ClientRectangle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using (var edge = new Pen(theme.AccentPrimaryBorder, 1f))
|
||||||
|
{
|
||||||
|
g.DrawLine(edge, 0, Height - 1, Width, Height - 1);
|
||||||
|
}
|
||||||
|
|
||||||
if (_tabs.Count == 0)
|
if (_tabs.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@ -198,8 +205,8 @@ namespace Elfisa.UI.Controls
|
|||||||
const int toggleReserve = 44;
|
const int toggleReserve = 44;
|
||||||
var maxTabRight = Math.Max(10, Width - toggleReserve);
|
var maxTabRight = Math.Max(10, Width - toggleReserve);
|
||||||
|
|
||||||
|
using (var closeFont = FluentFonts.CreateIconFont(10f))
|
||||||
using (var textFont = theme.FontSemibold14 ?? new Font("Segoe UI", 10f, FontStyle.Bold))
|
using (var textFont = theme.FontSemibold14 ?? new Font("Segoe UI", 10f, FontStyle.Bold))
|
||||||
using (var closeFont = new Font("Segoe MDL2 Assets", 10f, FontStyle.Regular))
|
|
||||||
{
|
{
|
||||||
foreach (var tab in _tabs)
|
foreach (var tab in _tabs)
|
||||||
{
|
{
|
||||||
@ -218,25 +225,33 @@ namespace Elfisa.UI.Controls
|
|||||||
|
|
||||||
var backColor = isActive
|
var backColor = isActive
|
||||||
? theme.SurfaceElevated
|
? theme.SurfaceElevated
|
||||||
: theme.Surface;
|
: GraphicsExtensions.Blend(theme.Surface, theme.AccentPrimarySoft, 0.35f);
|
||||||
if (!isActive && isHover)
|
if (!isActive && isHover)
|
||||||
{
|
{
|
||||||
backColor = GraphicsExtensions.Blend(backColor, theme.AccentPrimarySoft, 0.35f);
|
backColor = GraphicsExtensions.Blend(backColor, theme.AccentPrimarySoft, 0.65f);
|
||||||
}
|
}
|
||||||
|
|
||||||
var foreColor = isActive ? theme.TextPrimary : theme.TextSecondary;
|
var foreColor = isActive ? theme.AccentPrimary : theme.TextMuted;
|
||||||
var borderColor = isActive ? theme.AccentPrimaryBorder : theme.BorderSubtle;
|
var borderColor = isActive ? theme.AccentPrimary : theme.AccentPrimaryBorder;
|
||||||
|
|
||||||
using (var brush = new SolidBrush(backColor))
|
using (var brush = new SolidBrush(backColor))
|
||||||
{
|
{
|
||||||
GraphicsExtensions.FillRoundedRectangle(g, brush, tabRect, theme.CornerRadiusSmall);
|
GraphicsExtensions.FillRoundedRectangle(g, brush, tabRect, theme.CornerRadiusSmall);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var pen = new Pen(borderColor, isActive ? 1.2f : 1f))
|
using (var pen = new Pen(borderColor, isActive ? 1.4f : 1f))
|
||||||
{
|
{
|
||||||
GraphicsExtensions.DrawRoundedRectangle(g, pen, tabRect, theme.CornerRadiusSmall);
|
GraphicsExtensions.DrawRoundedRectangle(g, pen, tabRect, theme.CornerRadiusSmall);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isActive)
|
||||||
|
{
|
||||||
|
using (var accent = new SolidBrush(theme.AccentPrimary))
|
||||||
|
{
|
||||||
|
g.FillRectangle(accent, tabRect.X + 10, tabRect.Bottom - 3, tabRect.Width - 20, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var closeRect = new Rectangle(tabRect.Right - closeSize - 8, tabRect.Y + (tabRect.Height - closeSize) / 2, closeSize, closeSize);
|
var closeRect = new Rectangle(tabRect.Right - closeSize - 8, tabRect.Y + (tabRect.Height - closeSize) / 2, closeSize, closeSize);
|
||||||
_closeHitAreas[tab.Id] = closeRect;
|
_closeHitAreas[tab.Id] = closeRect;
|
||||||
|
|
||||||
@ -291,7 +306,7 @@ namespace Elfisa.UI.Controls
|
|||||||
}
|
}
|
||||||
|
|
||||||
var glyph = _isDarkTheme ? "\uE706" : "\uE708";
|
var glyph = _isDarkTheme ? "\uE706" : "\uE708";
|
||||||
using (var iconFont = new Font("Segoe MDL2 Assets", 11f))
|
using (var iconFont = FluentFonts.CreateIconFont(12f))
|
||||||
{
|
{
|
||||||
TextRenderer.DrawText(
|
TextRenderer.DrawText(
|
||||||
g,
|
g,
|
||||||
|
|||||||
@ -10,7 +10,9 @@ namespace Elfisa.UI.Controls
|
|||||||
{
|
{
|
||||||
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
|
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
|
||||||
BorderStyle = BorderStyle.None;
|
BorderStyle = BorderStyle.None;
|
||||||
SplitterWidth = 2;
|
SplitterWidth = 6;
|
||||||
|
IsSplitterFixed = false;
|
||||||
|
FixedPanel = FixedPanel.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyTheme(ThemeColors theme)
|
public void ApplyTheme(ThemeColors theme)
|
||||||
|
|||||||
346
src/Elfisa.UI/Controls/ModernTitleBar.cs
Normal file
346
src/Elfisa.UI/Controls/ModernTitleBar.cs
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Drawing2D;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Elfisa.UI.Theming;
|
||||||
|
|
||||||
|
namespace Elfisa.UI.Controls
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Системная шапка: заголовок + свернуть/развернуть/закрыть.
|
||||||
|
/// Иконки рисуются геометрией (не глифы) — как в Win11, без «ломаных» кнопок.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ModernTitleBar : Panel
|
||||||
|
{
|
||||||
|
private const int ButtonWidth = 40;
|
||||||
|
private enum ChromeKind { None, Minimize, Maximize, Close }
|
||||||
|
|
||||||
|
private Form _owner;
|
||||||
|
private ThemeColors _theme;
|
||||||
|
private ChromeKind _hover = ChromeKind.None;
|
||||||
|
private ChromeKind _pressed = ChromeKind.None;
|
||||||
|
private bool _isMaximized;
|
||||||
|
private readonly ToolTip _tip = new ToolTip { ShowAlways = false, AutoPopDelay = 2000 };
|
||||||
|
|
||||||
|
public ModernTitleBar()
|
||||||
|
{
|
||||||
|
Height = 30;
|
||||||
|
Dock = DockStyle.Top;
|
||||||
|
Cursor = Cursors.Default;
|
||||||
|
DoubleBuffered = true;
|
||||||
|
SetStyle(ControlStyles.AllPaintingInWmPaint |
|
||||||
|
ControlStyles.UserPaint |
|
||||||
|
ControlStyles.OptimizedDoubleBuffer |
|
||||||
|
ControlStyles.ResizeRedraw |
|
||||||
|
ControlStyles.Selectable, true);
|
||||||
|
TabStop = false;
|
||||||
|
ApplyTheme(ThemeManager.Colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Attach(Form owner)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
if (_owner == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_owner.TextChanged += (_, __) => Invalidate();
|
||||||
|
_owner.Resize += (_, __) =>
|
||||||
|
{
|
||||||
|
_isMaximized = _owner.WindowState == FormWindowState.Maximized;
|
||||||
|
Invalidate();
|
||||||
|
};
|
||||||
|
_isMaximized = _owner.WindowState == FormWindowState.Maximized;
|
||||||
|
Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyTheme(ThemeColors theme)
|
||||||
|
{
|
||||||
|
_theme = theme ?? ThemeManager.Colors;
|
||||||
|
BackColor = _theme.AccentPrimary;
|
||||||
|
Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPaintBackground(PaintEventArgs e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPaint(PaintEventArgs e)
|
||||||
|
{
|
||||||
|
var theme = _theme ?? ThemeManager.Colors;
|
||||||
|
var g = e.Graphics;
|
||||||
|
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||||
|
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||||
|
|
||||||
|
using (var fill = new SolidBrush(theme.AccentPrimary))
|
||||||
|
{
|
||||||
|
g.FillRectangle(fill, ClientRectangle);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawTitle(g, theme);
|
||||||
|
DrawChromeButton(g, ChromeKind.Minimize, theme);
|
||||||
|
DrawChromeButton(g, ChromeKind.Maximize, theme);
|
||||||
|
DrawChromeButton(g, ChromeKind.Close, theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawTitle(Graphics g, ThemeColors theme)
|
||||||
|
{
|
||||||
|
var title = _owner == null || string.IsNullOrWhiteSpace(_owner.Text)
|
||||||
|
? "Электронная Фармация"
|
||||||
|
: _owner.Text;
|
||||||
|
var textRect = new Rectangle(12, 0, Math.Max(40, Width - ButtonWidth * 3 - 16), Height);
|
||||||
|
using (var font = new Font("Segoe UI Semibold", 9f, FontStyle.Bold))
|
||||||
|
{
|
||||||
|
TextRenderer.DrawText(
|
||||||
|
g,
|
||||||
|
title,
|
||||||
|
font,
|
||||||
|
textRect,
|
||||||
|
theme.TextOnAccent,
|
||||||
|
TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis | TextFormatFlags.SingleLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rectangle GetButtonRect(ChromeKind kind)
|
||||||
|
{
|
||||||
|
var index = kind == ChromeKind.Close ? 0 : kind == ChromeKind.Maximize ? 1 : 2;
|
||||||
|
return new Rectangle(Width - ButtonWidth * (index + 1), 0, ButtonWidth, Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawChromeButton(Graphics g, ChromeKind kind, ThemeColors theme)
|
||||||
|
{
|
||||||
|
var rect = GetButtonRect(kind);
|
||||||
|
var isHover = _hover == kind;
|
||||||
|
var isPressed = _pressed == kind;
|
||||||
|
|
||||||
|
if (isHover || isPressed)
|
||||||
|
{
|
||||||
|
Color hoverFill;
|
||||||
|
if (kind == ChromeKind.Close)
|
||||||
|
{
|
||||||
|
hoverFill = isPressed
|
||||||
|
? Color.FromArgb(180, 32, 28)
|
||||||
|
: Color.FromArgb(232, 17, 35);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var alpha = isPressed ? 55 : 38;
|
||||||
|
hoverFill = Color.FromArgb(alpha, 255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var brush = new SolidBrush(hoverFill))
|
||||||
|
{
|
||||||
|
g.FillRectangle(brush, rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var iconColor = theme.TextOnAccent;
|
||||||
|
var cx = rect.X + rect.Width / 2f;
|
||||||
|
var cy = rect.Y + rect.Height / 2f;
|
||||||
|
|
||||||
|
using (var pen = new Pen(iconColor, 1.15f))
|
||||||
|
{
|
||||||
|
pen.StartCap = LineCap.Round;
|
||||||
|
pen.EndCap = LineCap.Round;
|
||||||
|
pen.LineJoin = LineJoin.Round;
|
||||||
|
|
||||||
|
switch (kind)
|
||||||
|
{
|
||||||
|
case ChromeKind.Minimize:
|
||||||
|
g.DrawLine(pen, cx - 5f, cy, cx + 5f, cy);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ChromeKind.Maximize:
|
||||||
|
if (_isMaximized)
|
||||||
|
{
|
||||||
|
// restore: два смещённых квадрата
|
||||||
|
var back = new RectangleF(cx - 3.2f, cy - 5.2f, 8.5f, 8.5f);
|
||||||
|
var front = new RectangleF(cx - 5.2f, cy - 3.2f, 8.5f, 8.5f);
|
||||||
|
g.DrawRectangle(pen, back.X, back.Y, back.Width, back.Height);
|
||||||
|
|
||||||
|
// залить передний квадрат цветом шапки (с учётом hover), чтобы «обрезать» задний
|
||||||
|
var coverColor = theme.AccentPrimary;
|
||||||
|
if (isHover || isPressed)
|
||||||
|
{
|
||||||
|
coverColor = Color.FromArgb(
|
||||||
|
255,
|
||||||
|
BlendChannel(theme.AccentPrimary.R, 255, isPressed ? 0.22f : 0.15f),
|
||||||
|
BlendChannel(theme.AccentPrimary.G, 255, isPressed ? 0.22f : 0.15f),
|
||||||
|
BlendChannel(theme.AccentPrimary.B, 255, isPressed ? 0.22f : 0.15f));
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var cover = new SolidBrush(coverColor))
|
||||||
|
{
|
||||||
|
g.FillRectangle(cover, Rectangle.Round(RectangleF.Inflate(front, 1f, 1f)));
|
||||||
|
}
|
||||||
|
|
||||||
|
g.DrawRectangle(pen, front.X, front.Y, front.Width, front.Height);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var box = new RectangleF(cx - 4.5f, cy - 4.5f, 9f, 9f);
|
||||||
|
g.DrawRectangle(pen, box.X, box.Y, box.Width, box.Height);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ChromeKind.Close:
|
||||||
|
g.DrawLine(pen, cx - 4.5f, cy - 4.5f, cx + 4.5f, cy + 4.5f);
|
||||||
|
g.DrawLine(pen, cx + 4.5f, cy - 4.5f, cx - 4.5f, cy + 4.5f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChromeKind HitTest(Point p)
|
||||||
|
{
|
||||||
|
if (GetButtonRect(ChromeKind.Close).Contains(p)) return ChromeKind.Close;
|
||||||
|
if (GetButtonRect(ChromeKind.Maximize).Contains(p)) return ChromeKind.Maximize;
|
||||||
|
if (GetButtonRect(ChromeKind.Minimize).Contains(p)) return ChromeKind.Minimize;
|
||||||
|
return ChromeKind.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseMove(MouseEventArgs e)
|
||||||
|
{
|
||||||
|
var hit = HitTest(e.Location);
|
||||||
|
if (hit != _hover)
|
||||||
|
{
|
||||||
|
_hover = hit;
|
||||||
|
Cursor = hit == ChromeKind.None ? Cursors.Default : Cursors.Hand;
|
||||||
|
UpdateTip(hit);
|
||||||
|
Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnMouseMove(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseLeave(EventArgs e)
|
||||||
|
{
|
||||||
|
if (_hover != ChromeKind.None || _pressed != ChromeKind.None)
|
||||||
|
{
|
||||||
|
_hover = ChromeKind.None;
|
||||||
|
_pressed = ChromeKind.None;
|
||||||
|
Cursor = Cursors.Default;
|
||||||
|
_tip.Hide(this);
|
||||||
|
Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnMouseLeave(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseDown(MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Button != MouseButtons.Left)
|
||||||
|
{
|
||||||
|
base.OnMouseDown(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hit = HitTest(e.Location);
|
||||||
|
if (hit != ChromeKind.None)
|
||||||
|
{
|
||||||
|
_pressed = hit;
|
||||||
|
Invalidate();
|
||||||
|
base.OnMouseDown(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.Clicks >= 2)
|
||||||
|
{
|
||||||
|
ToggleMaximize();
|
||||||
|
}
|
||||||
|
else if (_owner != null)
|
||||||
|
{
|
||||||
|
ReleaseCapture();
|
||||||
|
SendMessage(_owner.Handle, 0xA1, (IntPtr)2, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnMouseDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Button == MouseButtons.Left && _pressed != ChromeKind.None)
|
||||||
|
{
|
||||||
|
var hit = HitTest(e.Location);
|
||||||
|
var action = _pressed;
|
||||||
|
_pressed = ChromeKind.None;
|
||||||
|
Invalidate();
|
||||||
|
|
||||||
|
if (hit == action)
|
||||||
|
{
|
||||||
|
Execute(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Execute(ChromeKind kind)
|
||||||
|
{
|
||||||
|
if (_owner == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (kind)
|
||||||
|
{
|
||||||
|
case ChromeKind.Minimize:
|
||||||
|
_owner.WindowState = FormWindowState.Minimized;
|
||||||
|
break;
|
||||||
|
case ChromeKind.Maximize:
|
||||||
|
ToggleMaximize();
|
||||||
|
break;
|
||||||
|
case ChromeKind.Close:
|
||||||
|
_owner.Close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleMaximize()
|
||||||
|
{
|
||||||
|
if (_owner == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_owner.WindowState = _owner.WindowState == FormWindowState.Maximized
|
||||||
|
? FormWindowState.Normal
|
||||||
|
: FormWindowState.Maximized;
|
||||||
|
_isMaximized = _owner.WindowState == FormWindowState.Maximized;
|
||||||
|
Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTip(ChromeKind kind)
|
||||||
|
{
|
||||||
|
string text = null;
|
||||||
|
switch (kind)
|
||||||
|
{
|
||||||
|
case ChromeKind.Minimize: text = "Свернуть"; break;
|
||||||
|
case ChromeKind.Maximize: text = _isMaximized ? "Свернуть в окно" : "Развернуть"; break;
|
||||||
|
case ChromeKind.Close: text = "Закрыть"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text == null)
|
||||||
|
{
|
||||||
|
_tip.Hide(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rect = GetButtonRect(kind);
|
||||||
|
_tip.Show(text, this, rect.X + rect.Width / 2, Height + 2, 1500);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int BlendChannel(int from, int to, float amount)
|
||||||
|
{
|
||||||
|
return (int)(from + (to - from) * amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool ReleaseCapture();
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,15 +8,15 @@ using Elfisa.UI.Theming;
|
|||||||
namespace Elfisa.UI.Controls
|
namespace Elfisa.UI.Controls
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Экран «Загрузить» — карточка с пунктирной рамкой и кнопками (Pharma Trust).
|
/// Экран «Загрузить» — компактная карточка по центру с действиями синхронизации.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ModernUploadPanel : Panel
|
public class ModernUploadPanel : Panel
|
||||||
{
|
{
|
||||||
private readonly ModernButton _uploadBtn;
|
private readonly ModernButton _uploadBtn;
|
||||||
private readonly ModernButton _downloadBtn;
|
private readonly ModernButton _downloadBtn;
|
||||||
private ThemeColors _theme;
|
private ThemeColors _theme;
|
||||||
private string _title = "Загрузка прайс-листа и синхронизация";
|
private string _title = "Синхронизация прайс-листа";
|
||||||
private string _subtitle = "Импорт PriceList.xml или обмен данными с сервером поставщика.";
|
private string _subtitle = "Обмен данными с сервером: скачайте актуальный прайс или отправьте локальные изменения.";
|
||||||
|
|
||||||
public ModernButton UploadButton => _uploadBtn;
|
public ModernButton UploadButton => _uploadBtn;
|
||||||
public ModernButton DownloadButton => _downloadBtn;
|
public ModernButton DownloadButton => _downloadBtn;
|
||||||
@ -47,16 +47,18 @@ namespace Elfisa.UI.Controls
|
|||||||
Name = "uploadBtn",
|
Name = "uploadBtn",
|
||||||
Text = "Загрузить на сервер",
|
Text = "Загрузить на сервер",
|
||||||
ButtonStyle = ModernButtonStyle.Primary,
|
ButtonStyle = ModernButtonStyle.Primary,
|
||||||
Width = 220,
|
Width = 200,
|
||||||
Height = 40
|
Height = 40,
|
||||||
|
AllowTextEllipsis = false
|
||||||
};
|
};
|
||||||
_downloadBtn = new ModernButton
|
_downloadBtn = new ModernButton
|
||||||
{
|
{
|
||||||
Name = "downloadBtn",
|
Name = "downloadBtn",
|
||||||
Text = "Скачать с сервера",
|
Text = "Скачать с сервера",
|
||||||
ButtonStyle = ModernButtonStyle.Secondary,
|
ButtonStyle = ModernButtonStyle.Secondary,
|
||||||
Width = 220,
|
Width = 200,
|
||||||
Height = 40
|
Height = 40,
|
||||||
|
AllowTextEllipsis = false
|
||||||
};
|
};
|
||||||
|
|
||||||
Controls.Add(_downloadBtn);
|
Controls.Add(_downloadBtn);
|
||||||
@ -78,7 +80,7 @@ namespace Elfisa.UI.Controls
|
|||||||
var card = GetCardRect();
|
var card = GetCardRect();
|
||||||
var totalWidth = _uploadBtn.Width + 12 + _downloadBtn.Width;
|
var totalWidth = _uploadBtn.Width + 12 + _downloadBtn.Width;
|
||||||
var left = card.X + Math.Max(24, (card.Width - totalWidth) / 2);
|
var left = card.X + Math.Max(24, (card.Width - totalWidth) / 2);
|
||||||
var top = card.Bottom - 64;
|
var top = card.Y + 168;
|
||||||
|
|
||||||
_uploadBtn.Location = new Point(left, top);
|
_uploadBtn.Location = new Point(left, top);
|
||||||
_downloadBtn.Location = new Point(left + _uploadBtn.Width + 12, top);
|
_downloadBtn.Location = new Point(left + _uploadBtn.Width + 12, top);
|
||||||
@ -86,12 +88,15 @@ namespace Elfisa.UI.Controls
|
|||||||
|
|
||||||
private Rectangle GetCardRect()
|
private Rectangle GetCardRect()
|
||||||
{
|
{
|
||||||
var margin = 20;
|
const int cardW = 560;
|
||||||
|
const int cardH = 240;
|
||||||
|
var w = Math.Min(cardW, Math.Max(320, Width - 48));
|
||||||
|
var h = Math.Min(cardH, Math.Max(200, Height - 48));
|
||||||
return new Rectangle(
|
return new Rectangle(
|
||||||
margin,
|
Math.Max(24, (Width - w) / 2),
|
||||||
margin,
|
Math.Max(24, (Height - h) / 2),
|
||||||
Math.Max(200, Width - margin * 2),
|
w,
|
||||||
Math.Max(200, Height - margin * 2));
|
h);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnPaint(PaintEventArgs e)
|
protected override void OnPaint(PaintEventArgs e)
|
||||||
@ -102,44 +107,52 @@ namespace Elfisa.UI.Controls
|
|||||||
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
|
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
|
||||||
|
|
||||||
var card = GetCardRect();
|
var card = GetCardRect();
|
||||||
using (var fill = new SolidBrush(theme.AccentPrimarySoft))
|
using (var shadow = new SolidBrush(Color.FromArgb(18, theme.TextPrimary)))
|
||||||
|
{
|
||||||
|
GraphicsExtensions.FillRoundedRectangle(
|
||||||
|
g,
|
||||||
|
shadow,
|
||||||
|
new Rectangle(card.X + 3, card.Y + 4, card.Width, card.Height),
|
||||||
|
theme.CornerRadiusMedium);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var fill = new SolidBrush(theme.Surface))
|
||||||
{
|
{
|
||||||
GraphicsExtensions.FillRoundedRectangle(g, fill, card, theme.CornerRadiusMedium);
|
GraphicsExtensions.FillRoundedRectangle(g, fill, card, theme.CornerRadiusMedium);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var pen = new Pen(theme.AccentPrimaryBorder, 2f))
|
using (var pen = new Pen(theme.Border, 1f))
|
||||||
{
|
{
|
||||||
pen.DashStyle = DashStyle.Dash;
|
|
||||||
GraphicsExtensions.DrawRoundedRectangle(g, pen, card, theme.CornerRadiusMedium);
|
GraphicsExtensions.DrawRoundedRectangle(g, pen, card, theme.CornerRadiusMedium);
|
||||||
}
|
}
|
||||||
|
|
||||||
var iconRect = new Rectangle(card.X + (card.Width - 40) / 2, card.Y + 28, 40, 40);
|
var iconRect = new Rectangle(card.X + (card.Width - 44) / 2, card.Y + 28, 44, 44);
|
||||||
using (var iconBrush = new SolidBrush(Color.FromArgb(40, theme.Success)))
|
using (var iconBrush = new SolidBrush(theme.AccentPrimarySoft))
|
||||||
{
|
{
|
||||||
GraphicsExtensions.FillRoundedRectangle(g, iconBrush, iconRect, 10);
|
GraphicsExtensions.FillRoundedRectangle(g, iconBrush, iconRect, 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var iconFont = new Font("Segoe MDL2 Assets", 20f))
|
using (var iconFont = new Font("Segoe MDL2 Assets", 18f))
|
||||||
{
|
{
|
||||||
TextRenderer.DrawText(
|
TextRenderer.DrawText(
|
||||||
g,
|
g,
|
||||||
"\uE896",
|
"\uE896",
|
||||||
iconFont,
|
iconFont,
|
||||||
iconRect,
|
iconRect,
|
||||||
theme.Success,
|
theme.AccentPrimary,
|
||||||
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine);
|
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
var titleRect = new Rectangle(card.X + 24, card.Y + 78, card.Width - 48, 32);
|
var titleRect = new Rectangle(card.X + 28, card.Y + 84, card.Width - 56, 28);
|
||||||
TextRenderer.DrawText(
|
TextRenderer.DrawText(
|
||||||
g,
|
g,
|
||||||
_title,
|
_title,
|
||||||
theme.FontSemibold16,
|
theme.FontSemibold16,
|
||||||
titleRect,
|
titleRect,
|
||||||
theme.Success,
|
theme.TextPrimary,
|
||||||
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis);
|
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis);
|
||||||
|
|
||||||
var subRect = new Rectangle(card.X + 32, card.Y + 112, card.Width - 64, 48);
|
var subRect = new Rectangle(card.X + 36, card.Y + 116, card.Width - 72, 44);
|
||||||
TextRenderer.DrawText(
|
TextRenderer.DrawText(
|
||||||
g,
|
g,
|
||||||
_subtitle,
|
_subtitle,
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Drawing.Drawing2D;
|
using System.Drawing.Drawing2D;
|
||||||
using Elfisa.UI.Controls;
|
using Elfisa.UI.Controls;
|
||||||
|
using Elfisa.UI.Theming;
|
||||||
|
|
||||||
namespace Elfisa.UI.Helpers
|
namespace Elfisa.UI.Helpers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Градиент из skill winforms-modern-buttons для ModernButton (без тяжёлой тени — она давала артефакты на toolbar).
|
/// Градиент для Primary/Danger в тон активной палитры (Clinical Pro teal, не «Google blue»).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class SkillGradientButtonPaint
|
internal static class SkillGradientButtonPaint
|
||||||
{
|
{
|
||||||
@ -26,41 +27,45 @@ namespace Elfisa.UI.Helpers
|
|||||||
out Color top,
|
out Color top,
|
||||||
out Color bottom)
|
out Color bottom)
|
||||||
{
|
{
|
||||||
|
var theme = ThemeManager.Colors;
|
||||||
|
|
||||||
if (style == ModernButtonStyle.Danger)
|
if (style == ModernButtonStyle.Danger)
|
||||||
{
|
{
|
||||||
|
var danger = theme.Danger;
|
||||||
if (pressed)
|
if (pressed)
|
||||||
{
|
{
|
||||||
top = Color.FromArgb(175, 30, 30);
|
top = GraphicsExtensions.Blend(danger, Color.Black, 0.22f);
|
||||||
bottom = Color.FromArgb(140, 20, 20);
|
bottom = GraphicsExtensions.Blend(danger, Color.Black, 0.32f);
|
||||||
}
|
}
|
||||||
else if (hover)
|
else if (hover)
|
||||||
{
|
{
|
||||||
top = Color.FromArgb(245, 110, 110);
|
top = GraphicsExtensions.Blend(danger, Color.White, 0.18f);
|
||||||
bottom = Color.FromArgb(215, 55, 55);
|
bottom = danger;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
top = Color.FromArgb(235, 87, 87);
|
top = GraphicsExtensions.Blend(danger, Color.White, 0.08f);
|
||||||
bottom = Color.FromArgb(200, 40, 40);
|
bottom = GraphicsExtensions.Blend(danger, Color.Black, 0.12f);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Primary — мягкий teal шапки, без синего «крика».
|
||||||
if (pressed)
|
if (pressed)
|
||||||
{
|
{
|
||||||
top = Color.FromArgb(25, 70, 160);
|
top = theme.AccentPrimaryPressed;
|
||||||
bottom = Color.FromArgb(15, 55, 130);
|
bottom = GraphicsExtensions.Blend(theme.AccentPrimaryPressed, Color.Black, 0.12f);
|
||||||
}
|
}
|
||||||
else if (hover)
|
else if (hover)
|
||||||
{
|
{
|
||||||
top = Color.FromArgb(90, 150, 255);
|
top = theme.AccentPrimaryHover;
|
||||||
bottom = Color.FromArgb(45, 105, 220);
|
bottom = theme.AccentPrimary;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
top = Color.FromArgb(66, 133, 244);
|
top = theme.TitleBarBottom;
|
||||||
bottom = Color.FromArgb(30, 87, 194);
|
bottom = theme.TitleBarTop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +78,7 @@ namespace Elfisa.UI.Helpers
|
|||||||
bool pressed,
|
bool pressed,
|
||||||
bool enabled)
|
bool enabled)
|
||||||
{
|
{
|
||||||
|
var theme = ThemeManager.Colors;
|
||||||
var radius = GetCornerRadius(style, compactIconMode);
|
var radius = GetCornerRadius(style, compactIconMode);
|
||||||
var buttonRect = new Rectangle(
|
var buttonRect = new Rectangle(
|
||||||
clientRect.X,
|
clientRect.X,
|
||||||
@ -86,7 +92,7 @@ namespace Elfisa.UI.Helpers
|
|||||||
|
|
||||||
if (!enabled)
|
if (!enabled)
|
||||||
{
|
{
|
||||||
using (var brush = new SolidBrush(Color.FromArgb(220, 230, 235)))
|
using (var brush = new SolidBrush(GraphicsExtensions.Blend(theme.Border, theme.Surface, 0.35f)))
|
||||||
{
|
{
|
||||||
GraphicsExtensions.FillRoundedRectangle(g, brush, buttonRect, radius);
|
GraphicsExtensions.FillRoundedRectangle(g, brush, buttonRect, radius);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,7 +46,7 @@ namespace Elfisa.UI.Helpers
|
|||||||
TrySetInt(form.Handle, DwmwaUseImmersiveDarkMode, useDarkMode);
|
TrySetInt(form.Handle, DwmwaUseImmersiveDarkMode, useDarkMode);
|
||||||
TrySetInt(form.Handle, DwmwaUseImmersiveDarkModeLegacy, useDarkMode);
|
TrySetInt(form.Handle, DwmwaUseImmersiveDarkModeLegacy, useDarkMode);
|
||||||
|
|
||||||
var caption = theme.TitleBarTop;
|
var caption = theme.AccentPrimary;
|
||||||
var text = IsLightCaption(caption) ? Color.Black : Color.White;
|
var text = IsLightCaption(caption) ? Color.Black : Color.White;
|
||||||
|
|
||||||
TrySetUInt(form.Handle, DwmwaCaptionColor, ColorToDwm(caption));
|
TrySetUInt(form.Handle, DwmwaCaptionColor, ColorToDwm(caption));
|
||||||
@ -55,6 +55,41 @@ namespace Elfisa.UI.Helpers
|
|||||||
RefreshNonClientArea(form);
|
RefreshNonClientArea(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Обработка ресайза для FormBorderStyle.None (края окна).
|
||||||
|
/// </summary>
|
||||||
|
public static bool TryHandleNcHitTest(Form form, ref Message m, int border = 6)
|
||||||
|
{
|
||||||
|
const int wmNcHitTest = 0x84;
|
||||||
|
if (m.Msg != wmNcHitTest || form == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var screen = new Point(m.LParam.ToInt32() & 0xFFFF, (m.LParam.ToInt32() >> 16) & 0xFFFF);
|
||||||
|
// signed short for multi-monitor negative coords
|
||||||
|
screen = new Point((short)(m.LParam.ToInt32() & 0xFFFF), (short)((m.LParam.ToInt32() >> 16) & 0xFFFF));
|
||||||
|
var client = form.PointToClient(screen);
|
||||||
|
var w = form.ClientSize.Width;
|
||||||
|
var h = form.ClientSize.Height;
|
||||||
|
|
||||||
|
var left = client.X <= border;
|
||||||
|
var right = client.X >= w - border;
|
||||||
|
var top = client.Y <= border;
|
||||||
|
var bottom = client.Y >= h - border;
|
||||||
|
|
||||||
|
if (top && left) { m.Result = (IntPtr)13; return true; } // HTTOPLEFT
|
||||||
|
if (top && right) { m.Result = (IntPtr)14; return true; } // HTTOPRIGHT
|
||||||
|
if (bottom && left) { m.Result = (IntPtr)16; return true; } // HTBOTTOMLEFT
|
||||||
|
if (bottom && right) { m.Result = (IntPtr)17; return true; } // HTBOTTOMRIGHT
|
||||||
|
if (left) { m.Result = (IntPtr)10; return true; } // HTLEFT
|
||||||
|
if (right) { m.Result = (IntPtr)11; return true; } // HTRIGHT
|
||||||
|
if (top) { m.Result = (IntPtr)12; return true; } // HTTOP
|
||||||
|
if (bottom) { m.Result = (IntPtr)15; return true; } // HTBOTTOM
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public static void ApplyThemedDialogHeader(Panel headerPanel, Label headerLabel, ThemeColors theme)
|
public static void ApplyThemedDialogHeader(Panel headerPanel, Label headerLabel, ThemeColors theme)
|
||||||
{
|
{
|
||||||
if (theme == null)
|
if (theme == null)
|
||||||
|
|||||||
@ -26,57 +26,57 @@ namespace Elfisa.UI.Theming
|
|||||||
AccentPrimary = Color.FromArgb(15, 118, 110),
|
AccentPrimary = Color.FromArgb(15, 118, 110),
|
||||||
AccentPrimaryHover = Color.FromArgb(13, 148, 136),
|
AccentPrimaryHover = Color.FromArgb(13, 148, 136),
|
||||||
AccentPrimaryPressed = Color.FromArgb(17, 94, 89),
|
AccentPrimaryPressed = Color.FromArgb(17, 94, 89),
|
||||||
AccentPrimarySoft = Color.FromArgb(236, 253, 245),
|
AccentPrimarySoft = Color.FromArgb(240, 253, 250),
|
||||||
AccentPrimaryBorder = Color.FromArgb(153, 246, 228),
|
AccentPrimaryBorder = Color.FromArgb(153, 246, 228),
|
||||||
AccentSecondary = Color.White,
|
AccentSecondary = Color.White,
|
||||||
Success = Color.FromArgb(5, 150, 105),
|
Success = Color.FromArgb(5, 150, 105),
|
||||||
SuccessSoft = Color.FromArgb(209, 250, 229),
|
SuccessSoft = Color.FromArgb(209, 250, 229),
|
||||||
Warning = Color.FromArgb(217, 119, 6),
|
Warning = Color.FromArgb(217, 119, 6),
|
||||||
WarningSoft = Color.FromArgb(254, 243, 199),
|
WarningSoft = Color.FromArgb(254, 243, 199),
|
||||||
Danger = Color.FromArgb(225, 29, 72),
|
Danger = Color.FromArgb(190, 58, 82),
|
||||||
DangerSoft = Color.FromArgb(255, 241, 242),
|
DangerSoft = Color.FromArgb(255, 245, 247),
|
||||||
Info = Color.FromArgb(8, 145, 178),
|
Info = Color.FromArgb(8, 145, 178),
|
||||||
InfoSoft = Color.FromArgb(224, 242, 254),
|
InfoSoft = Color.FromArgb(224, 242, 254),
|
||||||
TitleBarTop = Color.FromArgb(17, 94, 89),
|
TitleBarTop = Color.FromArgb(15, 118, 110),
|
||||||
TitleBarBottom = Color.FromArgb(15, 118, 110),
|
TitleBarBottom = Color.FromArgb(13, 148, 136),
|
||||||
NavBackground = Color.White,
|
NavBackground = Color.White,
|
||||||
CartHeaderBackground = Color.FromArgb(236, 253, 245),
|
CartHeaderBackground = Color.FromArgb(240, 253, 250),
|
||||||
ToolbarBackground = Color.FromArgb(252, 253, 253),
|
ToolbarBackground = Color.FromArgb(248, 250, 252),
|
||||||
SectionHeaderBackground = Color.FromArgb(241, 245, 249),
|
SectionHeaderBackground = Color.FromArgb(241, 245, 249),
|
||||||
SidebarBackground = Color.White,
|
SidebarBackground = Color.White,
|
||||||
SidebarItem = Color.FromArgb(100, 116, 139),
|
SidebarItem = Color.FromArgb(100, 116, 139),
|
||||||
SidebarItemHover = Color.FromArgb(248, 250, 252),
|
SidebarItemHover = Color.FromArgb(248, 250, 252),
|
||||||
SidebarItemActive = Color.FromArgb(236, 253, 245),
|
SidebarItemActive = Color.FromArgb(204, 251, 241),
|
||||||
SidebarItemActiveText = Color.FromArgb(15, 118, 110),
|
SidebarItemActiveText = Color.FromArgb(15, 118, 110),
|
||||||
GridHeaderBackground = Color.FromArgb(241, 245, 249),
|
GridHeaderBackground = Color.FromArgb(241, 245, 249),
|
||||||
GridHeaderForeground = Color.FromArgb(100, 116, 139),
|
GridHeaderForeground = Color.FromArgb(71, 85, 105),
|
||||||
GridRowBackground = Color.White,
|
GridRowBackground = Color.White,
|
||||||
GridRowAlternate = Color.FromArgb(248, 250, 252),
|
GridRowAlternate = Color.FromArgb(248, 250, 252),
|
||||||
GridRowHover = Color.FromArgb(248, 250, 252),
|
GridRowHover = Color.FromArgb(240, 253, 250),
|
||||||
GridRowSelected = Color.FromArgb(15, 118, 110),
|
GridRowSelected = Color.FromArgb(15, 118, 110),
|
||||||
GridRowSelectedText = Color.White,
|
GridRowSelectedText = Color.White,
|
||||||
GridBorder = Color.FromArgb(241, 245, 249),
|
GridBorder = Color.FromArgb(226, 232, 240),
|
||||||
InputBackground = Color.White,
|
InputBackground = Color.White,
|
||||||
InputBorder = Color.FromArgb(226, 232, 240),
|
InputBorder = Color.FromArgb(203, 213, 225),
|
||||||
InputBorderFocus = Color.FromArgb(20, 184, 166),
|
InputBorderFocus = Color.FromArgb(20, 184, 166),
|
||||||
OverlayScrim = Color.FromArgb(160, 15, 23, 42),
|
OverlayScrim = Color.FromArgb(160, 15, 23, 42),
|
||||||
Shadow = Color.FromArgb(28, 15, 23, 42),
|
Shadow = Color.FromArgb(28, 15, 23, 42),
|
||||||
ScrollbarThumb = Color.FromArgb(203, 213, 225),
|
ScrollbarThumb = Color.FromArgb(148, 163, 184),
|
||||||
ScrollbarTrack = Color.FromArgb(248, 250, 252),
|
ScrollbarTrack = Color.FromArgb(248, 250, 252),
|
||||||
CornerRadiusSmall = 8,
|
CornerRadiusSmall = 8,
|
||||||
CornerRadiusMedium = 10,
|
CornerRadiusMedium = 12,
|
||||||
GridRowHeight = 38,
|
GridRowHeight = 36,
|
||||||
GridHeaderHeight = 40
|
GridHeaderHeight = 38
|
||||||
};
|
};
|
||||||
|
|
||||||
public ClinicalProPalette()
|
public ClinicalProPalette()
|
||||||
{
|
{
|
||||||
Colors.FontRegular12 = new Font("Segoe UI", 12f);
|
Colors.FontRegular12 = new Font("Segoe UI", 11f);
|
||||||
Colors.FontRegular13 = new Font("Segoe UI", 13f);
|
Colors.FontRegular13 = new Font("Segoe UI", 12f);
|
||||||
Colors.FontRegular14 = new Font("Segoe UI", 14f);
|
Colors.FontRegular14 = new Font("Segoe UI", 13f);
|
||||||
Colors.FontSemibold14 = new Font("Segoe UI Semibold", 14f);
|
Colors.FontSemibold14 = new Font("Segoe UI Semibold", 13f);
|
||||||
Colors.FontSemibold16 = new Font("Segoe UI Semibold", 16f);
|
Colors.FontSemibold16 = new Font("Segoe UI Semibold", 15f);
|
||||||
Colors.FontBold18 = new Font("Segoe UI", 18f, FontStyle.Bold);
|
Colors.FontBold18 = new Font("Segoe UI Semibold", 17f, FontStyle.Bold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ namespace Elfisa.UI.Theming
|
|||||||
{ ThemeVariant.DarkProductivity, new DarkProductivityPalette() }
|
{ ThemeVariant.DarkProductivity, new DarkProductivityPalette() }
|
||||||
};
|
};
|
||||||
|
|
||||||
private static ThemeVariant _currentVariant = ThemeVariant.FluentWin11;
|
private static ThemeVariant _currentVariant = ThemeVariant.ClinicalPro;
|
||||||
|
|
||||||
public static event EventHandler ThemeChanged;
|
public static event EventHandler ThemeChanged;
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ namespace Elfisa.UI.Theming
|
|||||||
public static void ToggleProductivityTheme()
|
public static void ToggleProductivityTheme()
|
||||||
{
|
{
|
||||||
CurrentVariant = IsDarkProductivityActive
|
CurrentVariant = IsDarkProductivityActive
|
||||||
? ThemeVariant.PharmaTrust
|
? ThemeVariant.ClinicalPro
|
||||||
: ThemeVariant.DarkProductivity;
|
: ThemeVariant.DarkProductivity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user