diff --git a/插件库/Lskj.Business/Impl/SystemInfo.cs b/插件库/Lskj.Business/Impl/SystemInfo.cs index 856e57b..b8296d6 100644 --- a/插件库/Lskj.Business/Impl/SystemInfo.cs +++ b/插件库/Lskj.Business/Impl/SystemInfo.cs @@ -1194,5 +1194,22 @@ namespace Lskj.Business /// 新版界面(读取 P_SystemTab.NewMain;true 时加载 MainPanelControlEx2,默认使用旧界面) /// public bool NewMain; + /// + /// 新版界面顶部背景色 + /// + public string MainTopColor; + /// + /// 新版界面左侧背景色 + /// + public string MainLeftColor; + /// + /// 新版界面顶部字体大小 + /// + public string MainTopFontSize; + /// + /// 新版界面左侧字体大小 + /// + public string MainLeftFontSize; + } } diff --git a/插件库/Lskj.Control/GridControlEx.cs b/插件库/Lskj.Control/GridControlEx.cs index 9113d30..83bd89a 100644 --- a/插件库/Lskj.Control/GridControlEx.cs +++ b/插件库/Lskj.Control/GridControlEx.cs @@ -6530,6 +6530,8 @@ namespace Lskj.Control this.gridView.RowCount > 0 && this.gridView.OptionsBehavior.Editable) { + GridColumnModel model = column.Tag as GridColumnModel; + // 允许操作 int focusedRowHandle = this.gridView.FocusedRowHandle; int totalRowCount = this.gridView.RowCount; @@ -6544,7 +6546,58 @@ namespace Lskj.Control int valueNum = 0; object obj = this.gridView.GetFocusedRowCellValue(column); - if (!string.IsNullOrEmpty(obj + "")) + + if (model != null && (ControlType.IsDate(model.FieldType) || model.FieldType == ControlType.LabShortDate)) + { + DateTime dateValue; + if (!DateTime.TryParse(obj + "", out dateValue)) + { + MessageUtil.Show("当前单元格不是有效日期,无法按日期生成。"); + e.Handled = true; + return; + } + + string dateFormat = DateFormat.Format(model.FieldType); + bool onlyYear = !string.IsNullOrEmpty(dateFormat) + && dateFormat.IndexOf("y", StringComparison.OrdinalIgnoreCase) >= 0 + && dateFormat.IndexOf("M", StringComparison.Ordinal) < 0 + && dateFormat.IndexOf("d", StringComparison.OrdinalIgnoreCase) < 0; + bool onlyMonth = !string.IsNullOrEmpty(dateFormat) + && dateFormat.IndexOf("M", StringComparison.Ordinal) >= 0 + && dateFormat.IndexOf("d", StringComparison.OrdinalIgnoreCase) < 0; + + this.gridView.BeginUpdate(); + this.gridView.BeginDataUpdate(); + try + { + for (int i = focusedRowHandle; i < totalRowCount; i++) + { + gridView.SetRowCellValue(i, column, dateValue); + if (onlyYear) + { + dateValue = dateValue.AddYears(1); + } + else if (onlyMonth) + { + dateValue = dateValue.AddMonths(1); + } + else + { + dateValue = dateValue.AddDays(1); + } + } + } + finally + { + this.gridView.EndDataUpdate(); + this.gridView.EndUpdate(); + } + + gridView.FocusedRowHandle = totalRowCount - 1; + e.Handled = true; + return; + } + else if (!string.IsNullOrEmpty(obj + "")) { //isExec = Int64.TryParse(obj + "", out value); Regex regex = new Regex(@"\D", RegexOptions.RightToLeft); diff --git a/插件库/Lskj.Control/Model/MyControl.cs b/插件库/Lskj.Control/Model/MyControl.cs index bb34219..e9d98b2 100644 --- a/插件库/Lskj.Control/Model/MyControl.cs +++ b/插件库/Lskj.Control/Model/MyControl.cs @@ -435,27 +435,23 @@ namespace Lskj.Control.Model { BaseUserControl baseControl = control as BaseUserControl; baseControl.Location = model.Location; - System.Threading.SynchronizationContext.Current.Post(SetProperty, null); - void SetProperty(object state) + // 控件创建、属性设置和挂载必须保持在同一条 UI 线程上,避免 WPF 宿主下 Timer 先执行。 + baseControl.Model = model; + baseControl.FontSize = model.FontSize; + baseControl.Name = "txt_" + model.FieldName; + baseControl.LabelText = model.LabelText; + baseControl.NullText = model.NullText; + //baseControl.EditText = model.DefaultValue; + //baseControl.Location = model.Location; + baseControl.Size = model.Size; + if (!model.ReadOnly && !string.IsNullOrWhiteSpace(model.ControlTitleColor)) { - //放到外面避免线程问题 - baseControl.Model = model; - baseControl.FontSize = model.FontSize; - baseControl.Name = "txt_" + model.FieldName; - baseControl.LabelText = model.LabelText; - baseControl.NullText = model.NullText; - //baseControl.EditText = model.DefaultValue; - //baseControl.Location = model.Location; - baseControl.Size = model.Size; - if (!model.ReadOnly && !string.IsNullOrWhiteSpace(model.ControlTitleColor)) - { - baseControl.ModifyDefaultColors(ColorTranslator.FromHtml(model.ControlTitleColor)); - } - baseControl.ReadOnly = model.ReadOnly; - baseControl.Required = model.IsEmpty; - baseControl.Tag = model; - baseControl.TabIndex = model.TabOrder; + baseControl.ModifyDefaultColors(ColorTranslator.FromHtml(model.ControlTitleColor)); } + baseControl.ReadOnly = model.ReadOnly; + baseControl.Required = model.IsEmpty; + baseControl.Tag = model; + baseControl.TabIndex = model.TabOrder; for (int i = 0; i < startLocktion.Count; i++) { diff --git a/插件库/Lskj.Control/TabControlEx.cs b/插件库/Lskj.Control/TabControlEx.cs index b1ed64d..8a8cdb4 100644 --- a/插件库/Lskj.Control/TabControlEx.cs +++ b/插件库/Lskj.Control/TabControlEx.cs @@ -255,9 +255,10 @@ namespace Lskj.Control /// /// 设置当前页签内 ModuleGridEx 底部操作栏是否可交互。 - /// 只处理左右结构右侧页签里直接承载的 ModuleGridEx,不影响普通页签和左侧表。 + /// 左右结构中,右侧明细是否允许操作由左侧主表是否有数据决定, + /// 不能使用右侧明细查询结果判断,否则右侧为空时无法新增第一行。 /// - private void SetModuleGridBottomPanelEnabled(XtraTabPage tabPage, bool enabled) + private void SetModuleGridBottomPanelEnabled(XtraTabPage tabPage, GridControlEx leftGrid) { if (tabPage == null) return; if (!_rightSideTabPages.Contains(tabPage)) return; @@ -265,7 +266,7 @@ namespace Lskj.Control ModuleGridEx moduleGrid = tabPage.Controls.OfType().FirstOrDefault(); if (moduleGrid != null && moduleGrid.ObjPanel != null) { - moduleGrid.ObjPanel.Enabled = enabled; + moduleGrid.ObjPanel.Enabled = leftGrid != null && leftGrid.DataRowCount() > 0; } } #endregion @@ -286,7 +287,7 @@ namespace Lskj.Control if (model == null) { BindTableToPage(tabPage, new DataTable()); - SetModuleGridBottomPanelEnabled(tabPage, false); + SetModuleGridBottomPanelEnabled(tabPage, leftGrid); return; } @@ -311,12 +312,12 @@ namespace Lskj.Control sqlValue = ReplaceHelper.ReplaceWhereCond(sqlValue); DataTable dt = MainImpl.GetDataTableResult(sqlValue); BindTableToPage(tabPage, dt); - SetModuleGridBottomPanelEnabled(tabPage, dt != null && dt.Rows.Count > 0); + SetModuleGridBottomPanelEnabled(tabPage, leftGrid); } else { BindTableToPage(tabPage, new DataTable()); - SetModuleGridBottomPanelEnabled(tabPage, false); + SetModuleGridBottomPanelEnabled(tabPage, leftGrid); } return; } @@ -335,7 +336,7 @@ namespace Lskj.Control if (!visible) { BindTableToPage(tabPage, new DataTable()); - SetModuleGridBottomPanelEnabled(tabPage, false); + SetModuleGridBottomPanelEnabled(tabPage, leftGrid); return; } } @@ -418,7 +419,7 @@ namespace Lskj.Control DataTable table = MainImpl.GetDataTableResult(finalSql); BindTableToPage(tabPage, table, finalSql); - SetModuleGridBottomPanelEnabled(tabPage, table != null && table.Rows.Count > 0); + SetModuleGridBottomPanelEnabled(tabPage, leftGrid); tabPage.Text = $"{model.DetailName}({table.Rows.Count}条)"; tabPage.Appearance.Header.ForeColor = table.Rows.Count > 0 ? Color.Red : Color.FromArgb(0, 0, 0); // 首次加载自动查询 diff --git a/插件库/Lskj.Main/Control/MainPanelControlEx2.Designer.cs b/插件库/Lskj.Main/Control/MainPanelControlEx2.Designer.cs index c451c01..4d5ca08 100644 --- a/插件库/Lskj.Main/Control/MainPanelControlEx2.Designer.cs +++ b/插件库/Lskj.Main/Control/MainPanelControlEx2.Designer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections; using System.ComponentModel; using System.Data; using System.Drawing; @@ -139,9 +140,12 @@ namespace Lskj.Main.Control // 旧版 DevExpress 皮肤会把相邻页签绘成深浅不一的灰块, // 在新版蓝色顶部栏下显得突兀;统一为 WPF 主界面的浅灰页签带, // 当前页签保持白色,边界与左侧导航自然衔接。 - Color tabStrip = Color.FromArgb(245, 247, 251); - Color tabHover = Color.FromArgb(232, 239, 249); - Color tabBorder = Color.FromArgb(214, 222, 233); + // 与 WPF WorkspaceTabStrip 保持一致:页签栏 #F3F5F8, + // 普通页签 #F8FAFC,边框/底部分隔线 #C7D3E4。 + Color tabStrip = Color.FromArgb(243, 245, 248); + Color tabNormal = Color.FromArgb(248, 250, 252); + Color tabHover = Color.White; + Color tabBorder = Color.FromArgb(199, 211, 228); tabs.PaintStyleName = "Flat"; tabs.HeaderLocation = DevExpress.XtraTab.TabHeaderLocation.Top; tabs.HeaderAutoFill = DevExpress.Utils.DefaultBoolean.False; @@ -149,7 +153,7 @@ namespace Lskj.Main.Control tabs.BackColor = tabStrip; tabs.Appearance.BackColor = tabStrip; tabs.Appearance.Options.UseBackColor = true; - ConfigureTabAppearance(tabs.AppearancePage.Header, Color.White, Color.FromArgb(74, 86, 105), tabBorder); + ConfigureTabAppearance(tabs.AppearancePage.Header, tabNormal, Color.FromArgb(74, 86, 105), Color.FromArgb(213, 222, 234)); ConfigureTabAppearance(tabs.AppearancePage.HeaderActive, Color.White, Color.FromArgb(31, 119, 201), tabBorder); ConfigureTabAppearance(tabs.AppearancePage.HeaderHotTracked, tabHover, Color.FromArgb(31, 119, 201), tabBorder); ConfigureTabAppearance(tabs.AppearancePage.HeaderDisabled, tabStrip, Color.FromArgb(160, 169, 182), tabBorder); @@ -157,6 +161,8 @@ namespace Lskj.Main.Control tabs.AppearancePage.HeaderActive.Font = tabs.AppearancePage.Header.Font; tabs.AppearancePage.HeaderHotTracked.Font = tabs.AppearancePage.Header.Font; tabs.AppearancePage.HeaderDisabled.Font = tabs.AppearancePage.Header.Font; + tabs.Paint -= MainTabs_Paint; + tabs.Paint += MainTabs_Paint; // 功能导航页签要与左侧 176px 目录栏严格对齐。DevExpress 旧版 // 页签宽度按文本自适应,因此用不可见的全角空格补足首个页签宽度, @@ -165,6 +171,59 @@ namespace Lskj.Main.Control if (first != null) first.Text = "功能导航" + new string('\u3000', 10); } + private void MainTabs_Paint(object sender, PaintEventArgs e) + { + XtraTabControl tabs = sender as XtraTabControl; + DevExpress.XtraTab.IXtraTab tabInterface = tabs as DevExpress.XtraTab.IXtraTab; + DevExpress.XtraTab.ViewInfo.BaseTabControlViewInfo viewInfo = + tabInterface == null ? null : tabInterface.ViewInfo; + if (tabs == null || viewInfo == null || viewInfo.HeaderInfo == null) return; + + Rectangle header = viewInfo.HeaderInfo.Bounds; + if (header.Width <= 0 || header.Height <= 0) return; + Color stripColor = Color.FromArgb(243, 245, 248); + Color borderColor = Color.FromArgb(199, 211, 228); + int lastRight = header.Left; + + foreach (DevExpress.XtraTab.ViewInfo.BaseTabPageViewInfo page in viewInfo.HeaderInfo.VisiblePages) + { + Rectangle bounds = page.Bounds; + if (bounds.Width <= 0 || bounds.Height <= 0) continue; + lastRight = Math.Max(lastRight, bounds.Right); + using (Pen pen = new Pen(borderColor)) + { + // 明确绘制页签左右分隔及顶部边线,避免 DevExpress 不同皮肤 + // 忽略 Appearance.BorderColor 后多个白色页签连成一片。 + e.Graphics.DrawLine(pen, bounds.Left, bounds.Top, bounds.Left, bounds.Bottom - 1); + e.Graphics.DrawLine(pen, bounds.Right - 1, bounds.Top, bounds.Right - 1, bounds.Bottom - 1); + e.Graphics.DrawLine(pen, bounds.Left, bounds.Top, bounds.Right - 1, bounds.Top); + } + } + + // WPF 页签后方是浅灰色页签带,而不是与内容区相同的纯白色。 + Rectangle buttons = viewInfo.HeaderInfo.ButtonsBounds; + int emptyRight = buttons.Width > 0 ? buttons.Left : header.Right; + if (lastRight < emptyRight) + { + using (Brush brush = new SolidBrush(stripColor)) + e.Graphics.FillRectangle(brush, lastRight, header.Top, emptyRight - lastRight, header.Height); + } + // WPF 在整个 WorkspaceTabStrip 底部单独覆盖一条 1px 的 #C7D3E4 + // 分隔线。不能沿用 HeaderInfo.Bounds.Bottom:旧版 DevExpress 中该位置 + // 仍可能属于页签头内部,线条会与页签背景混在一起。这里按内容区顶部定位, + // 横跨控件全宽,并增加一条极浅下沿,使 WinForms 在不同 DPI 下仍有边界感。 + Rectangle pageClient = tabs.PageClientBounds; + int dividerY = pageClient.Top > 0 ? pageClient.Top - 1 : header.Bottom - 1; + dividerY = Math.Max(0, Math.Min(tabs.ClientSize.Height - 1, dividerY)); + using (Pen pen = new Pen(borderColor)) + e.Graphics.DrawLine(pen, 0, dividerY, tabs.ClientSize.Width - 1, dividerY); + if (dividerY + 1 < tabs.ClientSize.Height) + { + using (Pen pen = new Pen(Color.FromArgb(237, 242, 247))) + e.Graphics.DrawLine(pen, 0, dividerY + 1, tabs.ClientSize.Width - 1, dividerY + 1); + } + } + private static void ConfigureTabAppearance(DevExpress.Utils.AppearanceObject appearance, Color backColor, Color foreColor, Color borderColor) { appearance.BackColor = backColor; @@ -407,7 +466,26 @@ namespace Lskj.Main.Control { Panel center = FindControl("pl_center_main"); if (center != null) center.BackColor = WorkspaceColor; Panel middle = FindControl("pl_center_main_middle"); - if (middle != null) { middle.BackColor = Color.White; middle.Padding = new Padding(20, 18, 20, 22); } + if (middle != null) + { + // 工作区外围使用 WPF 的 #F7F9FC,内部模块保持白色。 + middle.BackColor = WorkspaceColor; + // 与“我的桌面”页签一致:页签底线下面立即进入内容区, + // 不再保留额外的顶部浅灰留白,只留下 1px 分隔边界。 + middle.Padding = new Padding(20, 0, 20, 22); + AddWorkspaceContentDivider(middle); + } + Panel collect = FindControl("collectPanel"); + Panel all = FindControl("allPanel"); + Panel allMenu = FindControl("allMenuPanel"); + if (collect != null) collect.BackColor = Color.White; + if (all != null) all.BackColor = Color.White; + if (allMenu != null) allMenu.BackColor = Color.White; + if (SystemInfo.Instance.HideGallerys) + { + DisableShortcutChannel(); + return; + } Panel right = FindControl("pl_center_main_right"); Panel rightContainer = FindControl("pl_center_main_right_container"); EnableDoubleBuffer(right); @@ -428,6 +506,113 @@ namespace Lskj.Main.Control ResizeShortcutSections(); } + private static void AddWorkspaceContentDivider(Panel middle) + { + Panel divider = middle.Controls.Find("wpfWorkspaceContentDivider", false) + .OfType().FirstOrDefault(); + if (divider == null) + { + divider = new Panel + { + Name = "wpfWorkspaceContentDivider", + Height = 1, + BackColor = Color.FromArgb(199, 211, 228), + Enabled = false, + TabStop = false, + Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right + }; + middle.Controls.Add(divider); + } + + // 内容区紧贴页签栏时,分隔线固定在内容区最顶部;独立控件 + // 不会被 DevExpress 页签或模块内容的重绘覆盖。 + divider.Location = new Point(0, Math.Max(0, middle.Padding.Top - divider.Height)); + divider.Width = Math.Max(0, middle.ClientSize.Width); + divider.BringToFront(); + } + + private void DisableShortcutChannel() + { + SplitContainer split = FindControl("splitMainControl"); + if (split != null) + { + split.Panel2.VisibleChanged -= ShortcutPanel_VisibleChanged; + split.Panel2.VisibleChanged += ShortcutPanel_VisibleChanged; + split.SizeChanged -= ShortcutHost_SizeChanged; + split.SizeChanged += ShortcutHost_SizeChanged; + } + + EnsureShortcutChannelHidden(); + + // 释放旧控件 OnLoad 中已创建的快捷通道控件;后续因 HideGallerys=true + // 不会再从目录切换逻辑中生成这部分界面。 + DisposeChildControls(FindControl("pl_center_main_bottom_right_main")); + DisposeChildControls(FindControl("pl_center_main_bottom_left_main")); + DisposeChildControls(FindControl("workTopPanel")); + DisposeChildControls(FindControl("workBottomPanel")); + ClearLegacyCollection("topLayoutPanelDic"); + ClearLegacyCollection("botLayoutPanelDic"); + ClearLegacyCollection("_quickPageHeaders"); + } + + private void ShortcutPanel_VisibleChanged(object sender, EventArgs e) + { + if (!_shortcutChannelDisabled || _enforcingShortcutHidden || !IsHandleCreated) return; + BeginInvoke((MethodInvoker)EnsureShortcutChannelHidden); + } + + private void ShortcutHost_SizeChanged(object sender, EventArgs e) + { + if (_shortcutChannelDisabled) EnsureShortcutChannelHidden(); + } + + private void EnsureShortcutChannelHidden() + { + if (!_shortcutChannelDisabled || _enforcingShortcutHidden) return; + _enforcingShortcutHidden = true; + try + { + SplitContainer split = FindControl("splitMainControl"); + Panel panel = FindControl("panel5"); + Panel container = FindControl("pl_center_main_right_container"); + Panel right = FindControl("pl_center_main_right"); + + // 同时隐藏内部旧面板并折叠 SplitContainer,防止旧版页签/布局事件 + // 将 Panel2Collapsed 恢复后再次露出“快捷操作通道”。 + if (right != null) right.Visible = false; + if (container != null) container.Visible = false; + if (panel != null) panel.Visible = false; + if (split != null && !split.Panel2Collapsed) split.Panel2Collapsed = true; + } + finally + { + _enforcingShortcutHidden = false; + } + } + + private static void DisposeChildControls(WinFormsControl parent) + { + if (parent == null || parent.Controls.Count == 0) return; + WinFormsControl[] children = parent.Controls.Cast().ToArray(); + parent.Controls.Clear(); + foreach (WinFormsControl child in children) child.Dispose(); + } + + private void ClearLegacyCollection(string fieldName) + { + try + { + FieldInfo field = typeof(MainPanelControlEx).GetField( + fieldName, BindingFlags.Instance | BindingFlags.NonPublic); + IDictionary dictionary = field == null ? null : field.GetValue(_legacyPanel) as IDictionary; + if (dictionary != null) dictionary.Clear(); + } + catch (Exception) + { + // 清理缓存失败不影响右侧面板隐藏。 + } + } + private static void EnableDoubleBuffer(WinFormsControl control) { if (control == null) return; @@ -561,17 +746,18 @@ namespace Lskj.Main.Control if (!current) current = !string.IsNullOrWhiteSpace(currentName) && string.Equals((item.Text ?? string.Empty).Trim(), currentName, StringComparison.OrdinalIgnoreCase); - bool hover = item.Selected || item.Pressed; - bool drawBackground = current || hover; - // 当前目录使用黄色文字;鼠标悬浮只显示背景,不改变文字颜色和下划线。 + bool hover = (item.Selected || item.Pressed) && !current; + // 当前目录只使用黄色文字和下划线,不再绘制浅色选中背景; + // 其它目录鼠标悬浮时仍保留背景反馈。 item.ForeColor = current ? Color.FromArgb(255, 214, 102) : Color.FromArgb(242, 246, 255); - if (!drawBackground) return; Rectangle r = new Rectangle(Point.Empty, item.Size); - using (Brush brush = new SolidBrush(current - ? Color.FromArgb(49, 90, 166) - : Color.FromArgb(42, 88, 164))) e.Graphics.FillRectangle(brush, r); + if (hover) + { + using (Brush brush = new SolidBrush(Color.FromArgb(42, 88, 164))) + e.Graphics.FillRectangle(brush, r); + } if (current) { using (Brush brush = new SolidBrush(Color.FromArgb(255, 214, 102))) diff --git a/插件库/Lskj.Main/Control/MainPanelControlEx2.cs b/插件库/Lskj.Main/Control/MainPanelControlEx2.cs index c953f06..fc2665b 100644 --- a/插件库/Lskj.Main/Control/MainPanelControlEx2.cs +++ b/插件库/Lskj.Main/Control/MainPanelControlEx2.cs @@ -13,6 +13,8 @@ namespace Lskj.Main.Control private ToolStripMenuItem _selectedTopMenu; private bool _styleUpdatePending; private bool _shortcutRedrawSuppressed; + private bool _shortcutChannelDisabled; + private bool _enforcingShortcutHidden; private string _styledSubSysId; private string _styledSubSysName; @@ -45,7 +47,12 @@ namespace Lskj.Main.Control /// 保持旧 FrmMain 的 OnLoad 调用契约,在菜单绑定完成后应用新版样式。 public void OnLoad() { + // 新版主界面不显示旧版右侧快捷通道。先启用旧控件已有的隐藏开关, + // 使后续目录切换不再继续创建按菜单位置生成的快捷项。 + _shortcutChannelDisabled = true; + SystemInfo.Instance.HideGallerys = true; _legacyPanel.OnLoad(); + DisableShortcutChannel(); ApplyWpfShellStyle(); }