feat(legacy): add shell-independent module page compatibility
This commit is contained in:
@@ -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 主程序保持为空并继续使用
|
||||
/// PageControl;WPF 主程序注入自己的页面宿主实现。
|
||||
/// </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>
|
||||
|
||||
@@ -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>
|
||||
@@ -111,4 +112,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user