我可以 运行 WPF Windows 在没有 Forms Interop Toolkit 的情况下由 VB6 调用吗?
Can I Run WPF Windows called by VB6 Without Forms Interop Toolkit?
我们有一个要迁移到 .NET 的 VB6 应用程序。我从打开 WPF window 的 VB6 调用 .NET 函数。我们经常需要这些 windows 在主线程上 运行ning 以便它的行为就像另一个 VB6 窗体一样。我们还需要 运行 来自这些新 windows 的异步操作,因此需要 Dispatcher 和 SynchronizationContext。当我们第一次创建调用我们的 WPF windows.
的服务时,我们只需启动一个新的 Dispatcher 和 DispatcherSynchronizationContext
有一次,我打开了一个 WPF window,但它没有绘制边框。 使用上述方法是否存在我不知道的线程问题?这看起来是解决此问题的正确方法吗?我担心这种方法存在竞争条件或隐藏的问题,因为我们自己管理调度程序,这与典型的 .net 应用程序不同。
我知道有 Interop Forms Toolkit,但我们不需要 vb6 代码来直接与表单交互,所以这看起来有点过分和麻烦。我们才刚刚开始编写这些 windows 和这个接口,所以最好尽快知道我的方法是否错误。
您必须为 WPF windows 公开 .tlb 和 .idl 文件提供 COM 接口(ActiveX 用户控件)。
为此,我通常创建一个单独的项目,它只关心这个接口并引用您的 WPF 项目。在那里添加一个class或几个可以被VB6程序实例化的,根据需要提供所有的GUID和接口描述。
对于这个项目,要么在项目构建属性中使用 'Register for COM Interop' 选项,要么(我的偏好)在项目的构建事件脚本中使用 regasm 来创建 . tlb 和 .idl.
您部署:您的 WPF DLL、.tlb 和 .idl。
然后从您的 VB6 项目中引用此类型库,根据需要创建和使用对象。
我建议您在 WPF 和 COM 接口代码中管理所有同步,因为带有 COM 的 VB6 往往是 STA。
希望这对您有所帮助:)
另一种选择是将 WPF windows 构建为单独的进程,然后从 VB6 中调用它们。
这是关于如何执行此操作的线程:
What is the VB 6 equivalent of Process.Start?
同样,特别是如果您不需要来自 VB6 的任何交互,您应该在 WPF exe 中管理所有线程。
到目前为止,一切对我来说都很好。 COM 可见方法和事件的基本示例:
// COM visible interfaces
[ComVisible(true)]
[Guid("..."), InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IWpfCtl
{
void Show();
void Close();
void ComMethod(string key);
}
[ComVisible(true)]
[Guid("..."), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IWpfCtlEvents
{
void OnTestButton(string message);
}
// Implementation
public delegate void TestButtonDelegate(string message);
[ComVisible(true)]
[Guid("..."), ComSourceInterfaces(typeof(IWpfCtlEvents)), ClassInterface(ClassInterfaceType.None)]
public class WpfCtl : IWpfCtl, IDisposable
{
Thread _windowThread;
WpfWindow _wpfWindow;
public event TestButtonDelegate OnTestButton;
// Default parameterless constructor
public WpfCtl()
{
}
// Instantiate and show a WPF Window
public void Show()
{
// create a thread
_windowThread = new Thread(new ThreadStart(() =>
{
// create synchronization context
SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
// instantiate the window
_wpfWindow = new WpfWindow();
// shut down the dispatcher on window close
_wpfWindow.Closed += (s, e) =>
Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
// bind to window events
_wpfWindow.testButton.Click += (sender, args) => OnTestButtonClick("Test button was clicked");
// show the window
_wpfWindow.Show();
// start the dispatcher processing
System.Windows.Threading.Dispatcher.Run();
}));
// set the apartment state
_windowThread.SetApartmentState(ApartmentState.STA);
// make the thread a background thread
_windowThread.IsBackground = true;
// start the thread
_windowThread.Start();
}
public void Dispose()
{
Close();
}
// Close window
public void Close()
{
if (_windowThread != null && _windowThread.IsAlive && _wpfWindow != null)
{
if (_wpfWindow.Dispatcher.CheckAccess())
_wpfWindow.Close();
else
{
_wpfWindow.Dispatcher.Invoke(() => { _wpfWindow.Close(); });
}
}
}
// COM visible method
public void ComMethod(string key)
{
if (_wpfWindow != null)
_wpfWindow.Dispatcher.Invoke(() => { _wpfWindow.Method(key); });
}
// COM event
private void OnTestButtonClick(string message)
{
if (OnTestButton != null)
{
OnTestButton.Invoke(message);
}
}
}
我们有一个要迁移到 .NET 的 VB6 应用程序。我从打开 WPF window 的 VB6 调用 .NET 函数。我们经常需要这些 windows 在主线程上 运行ning 以便它的行为就像另一个 VB6 窗体一样。我们还需要 运行 来自这些新 windows 的异步操作,因此需要 Dispatcher 和 SynchronizationContext。当我们第一次创建调用我们的 WPF windows.
的服务时,我们只需启动一个新的 Dispatcher 和 DispatcherSynchronizationContext有一次,我打开了一个 WPF window,但它没有绘制边框。 使用上述方法是否存在我不知道的线程问题?这看起来是解决此问题的正确方法吗?我担心这种方法存在竞争条件或隐藏的问题,因为我们自己管理调度程序,这与典型的 .net 应用程序不同。
我知道有 Interop Forms Toolkit,但我们不需要 vb6 代码来直接与表单交互,所以这看起来有点过分和麻烦。我们才刚刚开始编写这些 windows 和这个接口,所以最好尽快知道我的方法是否错误。
您必须为 WPF windows 公开 .tlb 和 .idl 文件提供 COM 接口(ActiveX 用户控件)。
为此,我通常创建一个单独的项目,它只关心这个接口并引用您的 WPF 项目。在那里添加一个class或几个可以被VB6程序实例化的,根据需要提供所有的GUID和接口描述。
对于这个项目,要么在项目构建属性中使用 'Register for COM Interop' 选项,要么(我的偏好)在项目的构建事件脚本中使用 regasm 来创建 . tlb 和 .idl.
您部署:您的 WPF DLL、.tlb 和 .idl。 然后从您的 VB6 项目中引用此类型库,根据需要创建和使用对象。
我建议您在 WPF 和 COM 接口代码中管理所有同步,因为带有 COM 的 VB6 往往是 STA。
希望这对您有所帮助:)
另一种选择是将 WPF windows 构建为单独的进程,然后从 VB6 中调用它们。 这是关于如何执行此操作的线程: What is the VB 6 equivalent of Process.Start?
同样,特别是如果您不需要来自 VB6 的任何交互,您应该在 WPF exe 中管理所有线程。
到目前为止,一切对我来说都很好。 COM 可见方法和事件的基本示例:
// COM visible interfaces
[ComVisible(true)]
[Guid("..."), InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IWpfCtl
{
void Show();
void Close();
void ComMethod(string key);
}
[ComVisible(true)]
[Guid("..."), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IWpfCtlEvents
{
void OnTestButton(string message);
}
// Implementation
public delegate void TestButtonDelegate(string message);
[ComVisible(true)]
[Guid("..."), ComSourceInterfaces(typeof(IWpfCtlEvents)), ClassInterface(ClassInterfaceType.None)]
public class WpfCtl : IWpfCtl, IDisposable
{
Thread _windowThread;
WpfWindow _wpfWindow;
public event TestButtonDelegate OnTestButton;
// Default parameterless constructor
public WpfCtl()
{
}
// Instantiate and show a WPF Window
public void Show()
{
// create a thread
_windowThread = new Thread(new ThreadStart(() =>
{
// create synchronization context
SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
// instantiate the window
_wpfWindow = new WpfWindow();
// shut down the dispatcher on window close
_wpfWindow.Closed += (s, e) =>
Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
// bind to window events
_wpfWindow.testButton.Click += (sender, args) => OnTestButtonClick("Test button was clicked");
// show the window
_wpfWindow.Show();
// start the dispatcher processing
System.Windows.Threading.Dispatcher.Run();
}));
// set the apartment state
_windowThread.SetApartmentState(ApartmentState.STA);
// make the thread a background thread
_windowThread.IsBackground = true;
// start the thread
_windowThread.Start();
}
public void Dispose()
{
Close();
}
// Close window
public void Close()
{
if (_windowThread != null && _windowThread.IsAlive && _wpfWindow != null)
{
if (_wpfWindow.Dispatcher.CheckAccess())
_wpfWindow.Close();
else
{
_wpfWindow.Dispatcher.Invoke(() => { _wpfWindow.Close(); });
}
}
}
// COM visible method
public void ComMethod(string key)
{
if (_wpfWindow != null)
_wpfWindow.Dispatcher.Invoke(() => { _wpfWindow.Method(key); });
}
// COM event
private void OnTestButtonClick(string message)
{
if (OnTestButton != null)
{
OnTestButton.Invoke(message);
}
}
}