diff --git a/插件库/Lskj.Main/Control/MainPanelControlEx.cs b/插件库/Lskj.Main/Control/MainPanelControlEx.cs index b138671..b9ae326 100644 --- a/插件库/Lskj.Main/Control/MainPanelControlEx.cs +++ b/插件库/Lskj.Main/Control/MainPanelControlEx.cs @@ -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)) { diff --git a/插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs b/插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs index 4282b22..a5031f1 100644 --- a/插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs +++ b/插件库/Lskj.Main/Hosting/LegacyApplicationHost.cs @@ -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; } diff --git a/插件库/Lskj.Main/Model/Manager.cs b/插件库/Lskj.Main/Model/Manager.cs index a31cdbd..9733673 100644 --- a/插件库/Lskj.Main/Model/Manager.cs +++ b/插件库/Lskj.Main/Model/Manager.cs @@ -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) diff --git a/插件库/Lskj.Main3/Control/MainPanelControlEx.cs b/插件库/Lskj.Main3/Control/MainPanelControlEx.cs index 1706542..63dfcaa 100644 --- a/插件库/Lskj.Main3/Control/MainPanelControlEx.cs +++ b/插件库/Lskj.Main3/Control/MainPanelControlEx.cs @@ -194,7 +194,8 @@ namespace Lskj.Main3.Control // 解析dns new Thread(MainImpl.DnsHostEntry).Start(); this.mark_timer.Start(); - ERPInfo.Instance.PageControl = this.tabMain; + if (ERPInfo.Instance.ModulePageHost == null) + ERPInfo.Instance.PageControl = this.tabMain; ERPInfo.Instance.ModuleForms = Manager.ModuleForms; } catch (Exception ex) @@ -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); diff --git a/插件库/Lskj.Main4/Control/MainPanelControlEx.cs b/插件库/Lskj.Main4/Control/MainPanelControlEx.cs index 366d861..c638a31 100644 --- a/插件库/Lskj.Main4/Control/MainPanelControlEx.cs +++ b/插件库/Lskj.Main4/Control/MainPanelControlEx.cs @@ -1,4 +1,4 @@ -/*********************** +/*********************** * 说明:主窗体界面的构建 * 创建人:龚宇超 * 创建日期: @@ -238,7 +238,8 @@ namespace Lskj.Main4.Control // 解析dns new Thread(MainImpl.DnsHostEntry).Start(); this.mark_timer.Start(); - ERPInfo.Instance.PageControl = this.tabMain; + if (ERPInfo.Instance.ModulePageHost == null) + ERPInfo.Instance.PageControl = this.tabMain; ERPInfo.Instance.ModuleForms = Manager.ModuleForms; splitMainControl.SplitterMoved += new SplitterEventHandler(this.OnSplitterPositionChanged); @@ -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); diff --git a/插件库/Lskj.Model/ERPInfo.cs b/插件库/Lskj.Model/ERPInfo.cs index 6e2bd0b..6b3b8e4 100644 --- a/插件库/Lskj.Model/ERPInfo.cs +++ b/插件库/Lskj.Model/ERPInfo.cs @@ -88,6 +88,311 @@ namespace Lskj.Model /// 主界面page界面 /// public XtraTabControl PageControl; + + private readonly object _modulePageCallbackSync = new object(); + private readonly List> _modulePageSelectedCallbacks = + new List>(); + + /// + /// 顶层页面生命周期服务。旧 WinForms 主程序保持为空并继续使用 + /// PageControl;WPF 主程序注入自己的页面宿主实现。 + /// + 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 ModulePages + { + get + { + if (ModulePageHost != null) + return ModulePageHost.Pages; + if (PageControl == null) + return new XtraTabPage[0]; + return PageControl.TabPages.Cast(); + } + } + + /// + /// 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. + /// + 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); + } + + /// + /// 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. + /// + 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; + } + + /// + /// Gets the logical identifier of the selected top-level page without + /// exposing the shell's visual tab-control implementation. + /// + public string CurrentModulePageId + { + get { return GetModulePageId(CurrentModulePage); } + } + + /// + /// Enumerates top-level page identifiers for modules that only need to + /// compare or select pages and should not reach PageControl.TabPages. + /// + public IEnumerable GetModulePageIds() + { + return ModulePages.Select(GetModulePageId).ToArray(); + } + + /// + /// 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. + /// + public bool SelectModulePage(string pageId) + { + XtraTabPage page = FindModulePage(pageId); + return page != null && SelectModulePage(page); + } + + /// + /// Removes a top-level page by its logical identifier. + /// + public bool RemoveModulePage(string pageId) + { + XtraTabPage page = FindModulePage(pageId); + return page != null && RemoveModulePage(page); + } + + /// + /// 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. + /// + public bool IsTopLevelModulePage(string pageId) + { + return FindModulePage(pageId) != null; + } + + /// + /// 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. + /// + public IDisposable RegisterModulePageSelected(Action callback) + { + if (callback == null) + throw new ArgumentNullException("callback"); + + lock (_modulePageCallbackSync) + { + _modulePageSelectedCallbacks.Add(callback); + } + return new ModulePageCallbackRegistration(this, callback); + } + + /// + /// 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. + /// + 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(); + } + }); + } + + /// + /// 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. + /// + public void NotifyModulePageSelected(XtraTabPage page) + { + if (page == null) + return; + + string pageId = GetModulePageId(page); + Action[] callbacks; + lock (_modulePageCallbackSync) + { + callbacks = _modulePageSelectedCallbacks.ToArray(); + } + + foreach (Action 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 callback) + { + if (callback == null) + return; + + lock (_modulePageCallbackSync) + { + _modulePageSelectedCallbacks.Remove(callback); + } + } + + private sealed class ModulePageCallbackRegistration : IDisposable + { + private ERPInfo _owner; + private readonly Action _callback; + + public ModulePageCallbackRegistration( + ERPInfo owner, + Action callback) + { + _owner = owner; + _callback = callback; + } + + public void Dispose() + { + ERPInfo owner = _owner; + if (owner == null) + return; + + _owner = null; + owner.UnregisterModulePageSelected(_callback); + } + } + /// /// 主界面page界面字典 /// diff --git a/插件库/Lskj.Model/Lskj.Model.csproj b/插件库/Lskj.Model/Lskj.Model.csproj index 6413036..e2e9194 100644 --- a/插件库/Lskj.Model/Lskj.Model.csproj +++ b/插件库/Lskj.Model/Lskj.Model.csproj @@ -35,6 +35,7 @@ false + False ..\..\Debug\DevExpress.Utils.v15.2.dll @@ -111,4 +112,4 @@ --> - \ No newline at end of file + diff --git a/插件库/Lskj.Model/ModulePageHost.cs b/插件库/Lskj.Model/ModulePageHost.cs new file mode 100644 index 0000000..e15bf09 --- /dev/null +++ b/插件库/Lskj.Model/ModulePageHost.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using DevExpress.XtraTab; + +namespace Lskj.Model +{ + /// + /// 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. + /// + public interface IModulePageHost + { + event EventHandler PageAdded; + event EventHandler PageRemoved; + event EventHandler PageSelected; + + XtraTabPage SelectedPage { get; } + IEnumerable 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; } + } +}