feat(legacy): add shell-independent module page compatibility

This commit is contained in:
2026-08-28 11:56:48 +08:00
parent 8472004711
commit 1c59fda8b8
8 changed files with 387 additions and 21 deletions
@@ -260,6 +260,16 @@ namespace Lskj.Main.Control
{
try
{
// Bind the real WinForms page surface before any menu or
// startup callback can open a module. The WPF host never
// constructs this panel; the explicit shell check keeps its
// ModulePageHost untouched if a shared AppDomain is used.
if (Manager.ExternalMainShell == null)
{
ERPInfo.Instance.ModulePageHost = null;
ERPInfo.Instance.PageControl = this.tabMain;
Manager.TabMain = this.tabMain;
}
WaitForm.ShowForm();
ContextMenuStrip headMenuStrip = new ContextMenuStrip();
@@ -366,7 +376,6 @@ namespace Lskj.Main.Control
new Thread(MainImpl.DnsHostEntry).Start();
this.mark_timer.Start();
ERPInfo.Instance.PageControl = this.tabMain;
ERPInfo.Instance.ModuleForms = Manager.ModuleForms;
if ((!this._allMenus.Columns.Contains("menuposition") || (!SystemInfo.Instance.IsOpenWork && !SystemInfo.Instance.ShowGallerysFromMenu)) && (!SystemInfo.Instance.HideGallerys || SystemInfo.Instance.DesktopFormatShowGallerys))
{
@@ -3850,12 +3859,12 @@ namespace Lskj.Main.Control
XtraTabPage beforePage = m.BeforePage;
if (beforePage != null)
{
foreach (XtraTabPage tablepage in ERPInfo.Instance.PageControl.TabPages)
foreach (XtraTabPage tablepage in ERPInfo.Instance.ModulePages)
{
if (tablepage == beforePage)
{
ERPInfo.Instance.PageControl.SelectedTabPage = beforePage;
ERPInfo.Instance.CurrentModulePage = beforePage;
if (isclose) tabPage.Dispose();
}
}
@@ -4945,6 +4954,7 @@ namespace Lskj.Main.Control
try
{
XtraTabPage tabPage = this.tabMain.SelectedTabPage;
ERPInfo.Instance.NotifyModulePageSelected(tabPage);
string guid = tabPage.Tag + "";
if (!string.IsNullOrWhiteSpace(guid) && Manager.ModuleForms.ContainsKey(guid))
{
@@ -61,6 +61,17 @@ namespace Lskj.Main.Hosting
Manager.ExternalMainShell = externalMainShell;
try
{
// A standalone Ls_ERP process must always start in the
// original WinForms page mode. Clear any stale host
// references before FrmMain is created so Manager.OpenModule
// cannot accidentally route pages to a previous WPF shell
// when the legacy runtime is reused in-process.
if (externalMainShell == null)
{
ERPInfo.Instance.ModulePageHost = null;
ERPInfo.Instance.PageControl = null;
Manager.TabMain = null;
}
Program.InitializeMainProcess();
_mainProcessInitialized = true;
}
+7 -10
View File
@@ -356,12 +356,12 @@ namespace Lskj.Main.Model
dllName = MainImpl.GetDefaultValue(dllName);
if (SystemInfo.Instance.SoftOpenMode || SingleOpenMode)
{
foreach (XtraTabPage tablepage in TabMain.TabPages)
foreach (XtraTabPage tablepage in ERPInfo.Instance.ModulePages)
{
if (tablepage.Text == menuName)
{
MessageUtil.Show("已设置不能打开多个相同模块!");
TabMain.SelectedTabPage = tablepage;
ERPInfo.Instance.SelectModulePage(tablepage);
return;
}
}
@@ -414,8 +414,8 @@ namespace Lskj.Main.Model
tp.Dock = DockStyle.Fill;
module.Tag = guid;
module.ModuleInPtr = exeMainHandle;
TabMain.TabPages.Add(tp);
TabMain.SelectedTabPage = tp;
ERPInfo.Instance.AddModulePage(tp);
ERPInfo.Instance.SelectModulePage(tp);
ModuleForms.Add(guid, module);
}
else//直接打开exe模块
@@ -688,8 +688,8 @@ namespace Lskj.Main.Model
//}
}
TabMain.TabPages.Add(tp);
TabMain.SelectedTabPage = tp;
ERPInfo.Instance.AddModulePage(tp);
ERPInfo.Instance.SelectModulePage(tp);
ModuleForms.Add(guid, module);
//LastModuleForm.Add(module);
@@ -720,10 +720,7 @@ namespace Lskj.Main.Model
if (tabPage != null)
{
StaticControl.DogVerifyModuleForms.Remove(tabPage);
if (TabMain != null && TabMain.TabPages.Contains(tabPage))
{
TabMain.TabPages.Remove(tabPage);
}
ERPInfo.Instance.RemoveModulePage(tabPage);
}
if (form != null && !form.IsDisposed)
@@ -194,6 +194,7 @@ namespace Lskj.Main3.Control
// 解析dns
new Thread(MainImpl.DnsHostEntry).Start();
this.mark_timer.Start();
if (ERPInfo.Instance.ModulePageHost == null)
ERPInfo.Instance.PageControl = this.tabMain;
ERPInfo.Instance.ModuleForms = Manager.ModuleForms;
}
@@ -1976,12 +1977,12 @@ namespace Lskj.Main3.Control
XtraTabPage beforePage = m.BeforePage;
if (beforePage != null)
{
foreach (XtraTabPage tablepage in ERPInfo.Instance.PageControl.TabPages)
foreach (XtraTabPage tablepage in ERPInfo.Instance.ModulePages)
{
if (tablepage == beforePage)
{
ERPInfo.Instance.PageControl.SelectedTabPage = beforePage;
ERPInfo.Instance.CurrentModulePage = beforePage;
}
}
}
@@ -2584,6 +2585,7 @@ namespace Lskj.Main3.Control
if (!SystemInfo.Instance.RefreshNotify)
{
XtraTabPage tabPage = this.tabMain.SelectedTabPage;
ERPInfo.Instance.NotifyModulePageSelected(tabPage);
if (tabPage.TabIndex == 0)
{
Thread thread = new Thread(SetNotifys);
@@ -1,4 +1,4 @@
/***********************
/***********************
* 说明:主窗体界面的构建
* 创建人:龚宇超
* 创建日期:
@@ -238,6 +238,7 @@ namespace Lskj.Main4.Control
// 解析dns
new Thread(MainImpl.DnsHostEntry).Start();
this.mark_timer.Start();
if (ERPInfo.Instance.ModulePageHost == null)
ERPInfo.Instance.PageControl = this.tabMain;
ERPInfo.Instance.ModuleForms = Manager.ModuleForms;
@@ -2099,12 +2100,12 @@ namespace Lskj.Main4.Control
XtraTabPage beforePage = m.BeforePage;
if (beforePage != null)
{
foreach (XtraTabPage tablepage in ERPInfo.Instance.PageControl.TabPages)
foreach (XtraTabPage tablepage in ERPInfo.Instance.ModulePages)
{
if (tablepage == beforePage)
{
ERPInfo.Instance.PageControl.SelectedTabPage = beforePage;
ERPInfo.Instance.CurrentModulePage = beforePage;
}
}
}
@@ -2872,6 +2873,7 @@ namespace Lskj.Main4.Control
if (!SystemInfo.Instance.RefreshNotify)
{
XtraTabPage tabPage = this.tabMain.SelectedTabPage;
ERPInfo.Instance.NotifyModulePageSelected(tabPage);
if (tabPage.TabIndex == 0)
{
Thread thread = new Thread(SetNotifys);
+305
View File
@@ -88,6 +88,311 @@ namespace Lskj.Model
/// 主界面page界面
/// </summary>
public XtraTabControl PageControl;
private readonly object _modulePageCallbackSync = new object();
private readonly List<Action<string>> _modulePageSelectedCallbacks =
new List<Action<string>>();
/// <summary>
/// 顶层页面生命周期服务。旧 WinForms 主程序保持为空并继续使用
/// PageControlWPF 主程序注入自己的页面宿主实现。
/// </summary>
public IModulePageHost ModulePageHost;
public XtraTabPage CurrentModulePage
{
get
{
if (ModulePageHost != null)
return ModulePageHost.SelectedPage;
return PageControl == null ? null : PageControl.SelectedTabPage;
}
set
{
// Keep the original assignment-based API working for legacy
// modules. In the classic WinForms shell this forwards to
// the real PageControl; when WPF injects ModulePageHost it
// selects the page through that host instead of creating a
// second XtraTabControl.
SelectModulePage(value);
}
}
public IEnumerable<XtraTabPage> ModulePages
{
get
{
if (ModulePageHost != null)
return ModulePageHost.Pages;
if (PageControl == null)
return new XtraTabPage[0];
return PageControl.TabPages.Cast<XtraTabPage>();
}
}
/// <summary>
/// Indicates whether a top-level module page surface has been
/// initialized. The classic shell exposes its XtraTabControl while
/// WPF exposes IModulePageHost; callers can use this without reaching
/// into either visual implementation.
/// </summary>
public bool HasModulePageSurface
{
get { return ModulePageHost != null || PageControl != null; }
}
public void AddModulePage(XtraTabPage page)
{
if (page == null)
return;
if (ModulePageHost != null)
{
ModulePageHost.AddPage(page);
return;
}
if (PageControl != null && !PageControl.TabPages.Contains(page))
PageControl.TabPages.Add(page);
}
public bool RemoveModulePage(XtraTabPage page)
{
if (page == null)
return false;
if (ModulePageHost != null)
return ModulePageHost.RemovePage(page);
if (PageControl == null || !PageControl.TabPages.Contains(page))
return false;
PageControl.TabPages.Remove(page);
return true;
}
public bool SelectModulePage(XtraTabPage page)
{
if (page == null)
return false;
if (ModulePageHost != null)
return ModulePageHost.SelectPage(page);
if (PageControl == null || !PageControl.TabPages.Contains(page))
return false;
PageControl.SelectedTabPage = page;
return true;
}
public bool ContainsModulePage(XtraTabPage page)
{
if (page == null)
return false;
if (ModulePageHost != null)
return ModulePageHost.ContainsPage(page);
return PageControl != null && PageControl.TabPages.Contains(page);
}
/// <summary>
/// Returns the stable logical identifier used to bind a legacy page to
/// its WPF tab. The existing Tag value is retained whenever the old
/// module already assigned one; a generated value is written only for
/// an otherwise unidentified page.
/// </summary>
public string GetModulePageId(XtraTabPage page)
{
if (page == null)
return string.Empty;
string pageId = Convert.ToString(page.Tag);
if (string.IsNullOrWhiteSpace(pageId))
{
pageId = "legacy-module-" + Guid.NewGuid().ToString("N");
page.Tag = pageId;
}
return pageId;
}
/// <summary>
/// Gets the logical identifier of the selected top-level page without
/// exposing the shell's visual tab-control implementation.
/// </summary>
public string CurrentModulePageId
{
get { return GetModulePageId(CurrentModulePage); }
}
/// <summary>
/// Enumerates top-level page identifiers for modules that only need to
/// compare or select pages and should not reach PageControl.TabPages.
/// </summary>
public IEnumerable<string> GetModulePageIds()
{
return ModulePages.Select(GetModulePageId).ToArray();
}
/// <summary>
/// Selects a top-level page by its logical identifier. Both the WPF
/// host and the classic WinForms PageControl use the same fallback
/// path as the original XtraTabPage-based API.
/// </summary>
public bool SelectModulePage(string pageId)
{
XtraTabPage page = FindModulePage(pageId);
return page != null && SelectModulePage(page);
}
/// <summary>
/// Removes a top-level page by its logical identifier.
/// </summary>
public bool RemoveModulePage(string pageId)
{
XtraTabPage page = FindModulePage(pageId);
return page != null && RemoveModulePage(page);
}
/// <summary>
/// Indicates whether the supplied logical identifier belongs to a
/// top-level module page. This replaces checks based on
/// page.Parent as XtraTabControl in migrated module code.
/// </summary>
public bool IsTopLevelModulePage(string pageId)
{
return FindModulePage(pageId) != null;
}
/// <summary>
/// Registers a logical page-selection callback. This is intentionally
/// a page-service callback rather than a subscription to a concrete
/// WinForms or WPF tab-control event. Dispose the returned token when
/// the module closes so the callback cannot retain the module.
/// </summary>
public IDisposable RegisterModulePageSelected(Action<string> callback)
{
if (callback == null)
throw new ArgumentNullException("callback");
lock (_modulePageCallbackSync)
{
_modulePageSelectedCallbacks.Add(callback);
}
return new ModulePageCallbackRegistration(this, callback);
}
/// <summary>
/// Registers a callback for one logical module page. This overload is
/// the source-compatible replacement for code that used to subscribe
/// to the top-level PageControl.SelectedPageChanged event and then
/// filter by the selected page. The callback receives no WinForms
/// event arguments, so the same module code works when the WPF shell
/// owns the visible tab while the direct WinForms shell still uses its
/// original XtraTabControl.
/// </summary>
public IDisposable RegisterModulePageSelected(
string modulePageId,
Action callback)
{
if (callback == null)
throw new ArgumentNullException("callback");
string expectedPageId = (modulePageId ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(expectedPageId))
throw new ArgumentException(
"模块页面标识不能为空。",
"modulePageId");
return RegisterModulePageSelected(
selectedPageId =>
{
if (string.Equals(
selectedPageId,
expectedPageId,
StringComparison.Ordinal))
{
callback();
}
});
}
/// <summary>
/// Notifies logical page-selection subscribers. The WPF page host
/// calls this after its own selection state is updated; the classic
/// MainPanelControlEx calls it from its existing SelectedPageChanged
/// handler. No visual control event is introduced in the WPF path.
/// </summary>
public void NotifyModulePageSelected(XtraTabPage page)
{
if (page == null)
return;
string pageId = GetModulePageId(page);
Action<string>[] callbacks;
lock (_modulePageCallbackSync)
{
callbacks = _modulePageSelectedCallbacks.ToArray();
}
foreach (Action<string> callback in callbacks)
{
try
{
callback(pageId);
}
catch (Exception exception)
{
System.Diagnostics.Trace.WriteLine(
"旧模块页面回调执行失败:" + exception);
}
}
}
private XtraTabPage FindModulePage(string pageId)
{
if (string.IsNullOrWhiteSpace(pageId))
return null;
foreach (XtraTabPage page in ModulePages)
{
if (string.Equals(
GetModulePageId(page),
pageId,
StringComparison.Ordinal))
{
return page;
}
}
return null;
}
private void UnregisterModulePageSelected(Action<string> callback)
{
if (callback == null)
return;
lock (_modulePageCallbackSync)
{
_modulePageSelectedCallbacks.Remove(callback);
}
}
private sealed class ModulePageCallbackRegistration : IDisposable
{
private ERPInfo _owner;
private readonly Action<string> _callback;
public ModulePageCallbackRegistration(
ERPInfo owner,
Action<string> callback)
{
_owner = owner;
_callback = callback;
}
public void Dispose()
{
ERPInfo owner = _owner;
if (owner == null)
return;
_owner = null;
owner.UnregisterModulePageSelected(_callback);
}
}
/// <summary>
/// 主界面page界面字典
/// </summary>
+1
View File
@@ -35,6 +35,7 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<ItemGroup>
<Compile Include="ModulePageHost.cs" />
<Reference Include="DevExpress.Utils.v15.2, Version=15.2.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\Debug\DevExpress.Utils.v15.2.dll</HintPath>
+38
View File
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using DevExpress.XtraTab;
namespace Lskj.Model
{
/// <summary>
/// Provides the top-level module page lifecycle without coupling module
/// code to a particular visual tab control. The WPF shell supplies an
/// implementation backed by DXTabItem instances; the classic WinForms
/// shell leaves this interface unset and ERPInfo falls back to its real
/// XtraTabControl/PageControl.
/// </summary>
public interface IModulePageHost
{
event EventHandler<ModulePageEventArgs> PageAdded;
event EventHandler<ModulePageEventArgs> PageRemoved;
event EventHandler<ModulePageEventArgs> PageSelected;
XtraTabPage SelectedPage { get; }
IEnumerable<XtraTabPage> Pages { get; }
void AddPage(XtraTabPage page);
bool RemovePage(XtraTabPage page);
bool SelectPage(XtraTabPage page);
bool ContainsPage(XtraTabPage page);
}
public sealed class ModulePageEventArgs : EventArgs
{
public ModulePageEventArgs(XtraTabPage page)
{
Page = page;
}
public XtraTabPage Page { get; private set; }
}
}