From 2e1b13fde693b5ea605b76a66a0ddddbfb3f3fa0 Mon Sep 17 00:00:00 2001 From: Magomed Date: Tue, 14 Jul 2026 19:04:39 +0300 Subject: [PATCH] Add tab context menu and polish orders/price-list clear controls. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Right-click closes active or targeted document tabs, fix order items header overlap, and align the price-list search clear button with centered × glyph. Co-authored-by: Cursor --- src/ElectronicPharmacy/Form1.UiShell.cs | 52 +++++++++++++++++++ .../UserControls/FOrders.Designer.cs | 13 ++--- .../UserControls/FOrders.cs | 1 + .../UserControls/UCPriceList.Designer.cs | 4 +- .../UserControls/UCPriceList.cs | 22 ++++++-- src/Elfisa.UI/Controls/ModernButton.cs | 8 ++- .../Controls/ModernDocumentTabStrip.cs | 40 ++++++++++++++ 7 files changed, 128 insertions(+), 12 deletions(-) diff --git a/src/ElectronicPharmacy/Form1.UiShell.cs b/src/ElectronicPharmacy/Form1.UiShell.cs index 39e80b4..7048abb 100644 --- a/src/ElectronicPharmacy/Form1.UiShell.cs +++ b/src/ElectronicPharmacy/Form1.UiShell.cs @@ -27,6 +27,9 @@ namespace Электронная_Фармация private Panel _statusFooter; private Label _lblCurrentUser; private ContextMenuStrip _directoryMenu; + private ContextMenuStrip _documentTabMenu; + private ToolStripMenuItem _documentCloseTabMenuItem; + private ToolStripMenuItem _documentCloseAllTabsMenuItem; partial void InitializeModernShell() { @@ -111,6 +114,25 @@ namespace Электронная_Фармация _appHeader.ItemSelected += OnAppHeaderItemSelected; _documentTabStrip.TabSelected += OnDocumentTabSelected; _documentTabStrip.TabClosed += OnDocumentTabClosed; + _documentTabStrip.TabCloseAllRequested += OnDocumentTabCloseAll; + + _documentCloseTabMenuItem = new ToolStripMenuItem("Закрыть вкладку"); + _documentCloseTabMenuItem.Click += (_, __) => + { + var tabId = _documentTabStrip.ActiveTabId; + if (!string.IsNullOrEmpty(tabId)) + { + CloseDocumentTab(tabId); + } + }; + + _documentCloseAllTabsMenuItem = new ToolStripMenuItem("Закрыть все вкладки"); + _documentCloseAllTabsMenuItem.Click += (_, __) => ClearDocumentTabs(); + + _documentTabMenu = new ContextMenuStrip(); + _documentTabMenu.Items.Add(_documentCloseTabMenuItem); + _documentTabMenu.Items.Add(_documentCloseAllTabsMenuItem); + ThemeManager.ThemeChanged += (_, __) => _themeService.Apply(this); } @@ -207,6 +229,7 @@ namespace Электронная_Фармация _documentContents[tabId] = content; _documentHost.Controls.Add(content); UiThemeHelper.ApplyToControlTree(content); + WireDocumentContextMenu(content); _documentTabStrip.SelectTab(tabId); ShowDocumentTab(tabId); _appHeader.SetActive(navKey); @@ -226,6 +249,35 @@ namespace Электронная_Фармация CloseDocumentTab(tabId); } + private void OnDocumentTabCloseAll(object sender, EventArgs e) + { + ClearDocumentTabs(); + } + + private void WireDocumentContextMenu(Control control) + { + control.MouseUp -= OnDocumentContentMouseUp; + control.MouseUp += OnDocumentContentMouseUp; + foreach (Control child in control.Controls) + { + WireDocumentContextMenu(child); + } + } + + private void OnDocumentContentMouseUp(object sender, MouseEventArgs e) + { + if (e.Button != MouseButtons.Right || _documentTabStrip.Tabs.Count == 0) + { + return; + } + + _documentCloseTabMenuItem.Enabled = !string.IsNullOrEmpty(_documentTabStrip.ActiveTabId); + _documentCloseAllTabsMenuItem.Enabled = _documentTabStrip.Tabs.Count > 0; + + var screenPoint = (sender as Control)?.PointToScreen(e.Location) ?? Cursor.Position; + _documentTabMenu.Show(screenPoint); + } + private void CloseDocumentTab(string tabId) { if (_documentContents.TryGetValue(tabId, out var control)) diff --git a/src/ElectronicPharmacy/UserControls/FOrders.Designer.cs b/src/ElectronicPharmacy/UserControls/FOrders.Designer.cs index 23fd588..5c64afc 100644 --- a/src/ElectronicPharmacy/UserControls/FOrders.Designer.cs +++ b/src/ElectronicPharmacy/UserControls/FOrders.Designer.cs @@ -113,13 +113,14 @@ // panelOrderItems // this.panelOrderItems.BackColor = System.Drawing.SystemColors.Control; - this.panelOrderItems.Controls.Add(this.label3); this.panelOrderItems.Controls.Add(this.dataGridView1); this.panelOrderItems.Controls.Add(this.dgvOrderItems); + this.panelOrderItems.Controls.Add(this.label3); this.panelOrderItems.Dock = System.Windows.Forms.DockStyle.Fill; this.panelOrderItems.Location = new System.Drawing.Point(0, 0); this.panelOrderItems.Margin = new System.Windows.Forms.Padding(2); this.panelOrderItems.Name = "panelOrderItems"; + this.panelOrderItems.Padding = new System.Windows.Forms.Padding(8, 4, 8, 0); this.panelOrderItems.Size = new System.Drawing.Size(1178, 194); this.panelOrderItems.TabIndex = 0; // @@ -134,7 +135,7 @@ this.dataGridView1.BorderStyle = System.Windows.Forms.BorderStyle.None; this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.dataGridView1.Dock = System.Windows.Forms.DockStyle.Fill; - this.dataGridView1.Margin = new System.Windows.Forms.Padding(4); + this.dataGridView1.Margin = new System.Windows.Forms.Padding(0); this.dataGridView1.Name = "dataGridView1"; this.dataGridView1.ReadOnly = true; this.dataGridView1.RowHeadersVisible = false; @@ -165,12 +166,12 @@ // label3 // this.label3.Dock = System.Windows.Forms.DockStyle.Top; - this.label3.Font = new System.Drawing.Font("Segoe UI", 10.2F); - this.label3.Location = new System.Drawing.Point(2, 4); + this.label3.Font = new System.Drawing.Font("Segoe UI Semibold", 10.2F, System.Drawing.FontStyle.Bold); + this.label3.Location = new System.Drawing.Point(8, 4); this.label3.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.label3.Name = "label3"; - this.label3.Padding = new System.Windows.Forms.Padding(6, 4, 0, 4); - this.label3.Size = new System.Drawing.Size(1178, 31); + this.label3.Padding = new System.Windows.Forms.Padding(0, 2, 0, 8); + this.label3.Size = new System.Drawing.Size(1162, 34); this.label3.TabIndex = 0; this.label3.Text = "Позиции заказа"; // diff --git a/src/ElectronicPharmacy/UserControls/FOrders.cs b/src/ElectronicPharmacy/UserControls/FOrders.cs index 928ac65..2ffea97 100644 --- a/src/ElectronicPharmacy/UserControls/FOrders.cs +++ b/src/ElectronicPharmacy/UserControls/FOrders.cs @@ -46,6 +46,7 @@ namespace Электронная_Фармация.UserControls ApplyToolbarLayout(); ModernButtonStyles.ApplyClear(btnClearGoodSearch); ModernButtonStyles.ApplyClear(btnClearOrderNumberFilter); + label3.BringToFront(); panelToolbar.Resize += (_, __) => ApplyToolbarLayout(); } diff --git a/src/ElectronicPharmacy/UserControls/UCPriceList.Designer.cs b/src/ElectronicPharmacy/UserControls/UCPriceList.Designer.cs index 50322c0..1d696dc 100644 --- a/src/ElectronicPharmacy/UserControls/UCPriceList.Designer.cs +++ b/src/ElectronicPharmacy/UserControls/UCPriceList.Designer.cs @@ -367,9 +367,9 @@ // this.btnClearSearchInPriceList.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; this.btnClearSearchInPriceList.Font = new System.Drawing.Font("Segoe UI", 9.75F); - this.btnClearSearchInPriceList.Location = new System.Drawing.Point(393, 7); + this.btnClearSearchInPriceList.Location = new System.Drawing.Point(393, 9); this.btnClearSearchInPriceList.Name = "btnClearSearchInPriceList"; - this.btnClearSearchInPriceList.Size = new System.Drawing.Size(32, 30); + this.btnClearSearchInPriceList.Size = new System.Drawing.Size(32, 32); this.btnClearSearchInPriceList.TabIndex = 9; this.btnClearSearchInPriceList.Text = "×"; this.btnClearSearchInPriceList.Click += new System.EventHandler(this.btnClearSearchInPriceList_Click); diff --git a/src/ElectronicPharmacy/UserControls/UCPriceList.cs b/src/ElectronicPharmacy/UserControls/UCPriceList.cs index fe2f19b..85d9e86 100644 --- a/src/ElectronicPharmacy/UserControls/UCPriceList.cs +++ b/src/ElectronicPharmacy/UserControls/UCPriceList.cs @@ -10,6 +10,7 @@ using System.Windows.Forms; using System.Data.SQLite; using System.Data.OleDb; using System.Data.Common; +using Elfisa.UI.Controls; using Электронная_Фармация.Classes; using Электронная_Фармация.HelpForms; @@ -52,9 +53,7 @@ namespace Электронная_Фармация.UserControls Tag = "chrome-child"; btnDeleteGood.Height = 34; - btnClearSearchInPriceList.Height = 32; - txtSearchGoodNameInPriceList.Height = 32; - btnClearSupplierFilter.Height = 32; + AlignSearchRow(); panel1.SizeChanged += (_, __) => LayoutMarkupRow(); LayoutMarkupRow(); @@ -70,6 +69,23 @@ namespace Электронная_Фармация.UserControls txtSearchGoodNameInPriceList.Focus(); } + private void AlignSearchRow() + { + const int searchY = 9; + const int searchHeight = 32; + const int clearGap = 2; + + txtSearchGoodNameInPriceList.SetBounds(78, searchY, 313, searchHeight); + btnClearSearchInPriceList.SetBounds( + txtSearchGoodNameInPriceList.Right + clearGap, + searchY, + 32, + searchHeight); + ModernButtonStyles.ApplyClear(btnClearSearchInPriceList); + + btnClearSupplierFilter.Height = searchHeight; + } + private void LayoutMarkupRow() { // Keep markup row readable at different DPI/window sizes. diff --git a/src/Elfisa.UI/Controls/ModernButton.cs b/src/Elfisa.UI/Controls/ModernButton.cs index dd312a4..a28415f 100644 --- a/src/Elfisa.UI/Controls/ModernButton.cs +++ b/src/Elfisa.UI/Controls/ModernButton.cs @@ -426,11 +426,17 @@ namespace Elfisa.UI.Controls { if (_compactIconMode) { + var textBounds = bounds; + if (Text == "×") + { + textBounds.Offset(1, -2); + } + TextRenderer.DrawText( g, Text, Font, - bounds, + textBounds, textColor, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine); return; diff --git a/src/Elfisa.UI/Controls/ModernDocumentTabStrip.cs b/src/Elfisa.UI/Controls/ModernDocumentTabStrip.cs index b3da24e..132565d 100644 --- a/src/Elfisa.UI/Controls/ModernDocumentTabStrip.cs +++ b/src/Elfisa.UI/Controls/ModernDocumentTabStrip.cs @@ -30,6 +30,12 @@ namespace Elfisa.UI.Controls public event EventHandler TabSelected; public event EventHandler TabClosed; + public event EventHandler TabCloseAllRequested; + + private readonly ContextMenuStrip _contextMenu; + private readonly ToolStripMenuItem _closeTabMenuItem; + private readonly ToolStripMenuItem _closeAllTabsMenuItem; + private string _contextMenuTabId; public ModernDocumentTabStrip() { @@ -40,6 +46,22 @@ namespace Elfisa.UI.Controls ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true); + + _closeTabMenuItem = new ToolStripMenuItem("Закрыть вкладку"); + _closeTabMenuItem.Click += (_, __) => + { + if (!string.IsNullOrEmpty(_contextMenuTabId)) + { + TabClosed?.Invoke(this, _contextMenuTabId); + } + }; + + _closeAllTabsMenuItem = new ToolStripMenuItem("Закрыть все вкладки"); + _closeAllTabsMenuItem.Click += (_, __) => TabCloseAllRequested?.Invoke(this, EventArgs.Empty); + + _contextMenu = new ContextMenuStrip(); + _contextMenu.Items.Add(_closeTabMenuItem); + _contextMenu.Items.Add(_closeAllTabsMenuItem); } public IReadOnlyList Tabs => _tabs; @@ -218,6 +240,11 @@ namespace Elfisa.UI.Controls protected override void OnMouseClick(MouseEventArgs e) { + if (e.Button != MouseButtons.Left) + { + return; + } + var tabId = HitTestClose(e.Location); if (!string.IsNullOrEmpty(tabId)) { @@ -253,6 +280,19 @@ namespace Elfisa.UI.Controls base.OnMouseLeave(e); } + protected override void OnMouseUp(MouseEventArgs e) + { + if (e.Button == MouseButtons.Right && _tabs.Count > 0) + { + _contextMenuTabId = HitTest(e.Location) ?? _activeTabId; + _closeTabMenuItem.Enabled = !string.IsNullOrEmpty(_contextMenuTabId); + _closeAllTabsMenuItem.Enabled = _tabs.Count > 0; + _contextMenu.Show(this, e.Location); + } + + base.OnMouseUp(e); + } + private string HitTest(Point location) { foreach (var pair in _hitAreas)