我可以在没有Forms Interop Toolkit的情况下运行VB6调用的WPF Windows吗?

时间:2016-11-09 20:30:59

标签: .net wpf vb6 com-interop vb6-migration

我们有一个VB6应用程序,我们正在迁移到.NET。我正在调用VB6中的.NET函数打开一个WPF窗口。我们经常需要在主线程上运行这些窗口,因此它的行为就像另一个VB6 Form。我们还需要从这些新窗口运行异步操作,因此需要Dispatcher和SynchronizationContext。当我们第一次创建调用WPF窗口的服务时,我们只需启动一个新的Dispatcher和DispatcherSynchronizationContext。

有一次,我打开了一个WPF窗口,它没有绘制边框。 是否存在使用上述方法我不知道线程问题的可能性?这似乎是解决这个问题的正确方法吗?我很害怕这种方法存在竞争条件或隐藏的问题,因为我们自己管理调度程序不像典型的.net应用程序。

我知道有Interop Forms Toolkit,但我们不需要vb6代码直接与表单进行交互,因此看起来有点过分和繁琐。我们刚刚开始编写这些窗口和这个界面,所以很高兴知道我的方法是否早晚出错。

3 个答案:

答案 0 :(得分:1)

您必须为WPF窗口提供COM接口(ActiveX用户控件),以显示.tlb和.idl文件。

为此,我通常会创建一个单独的项目,仅关注此接口并引用您的WPF项目。添加一个或多个可以由VB6程序实例化的类,根据需要提供所有GUID和接口描述。

使用此项目,使用项目构建属性中的“注册COM Interop”选项,或者(我的偏好)在项目的构建事件脚本中使用 regasm 来创建.tlb和.idl

部署:您的WPF DLL,.tlb和.idl。 然后从VB6项目中引用此类型库,根据需要创建和使用对象。

我建议你管理WPF和COM接口代码中的所有同步,因为带有COM的VB6往往是STA。

希望这会有所帮助:)

答案 1 :(得分:0)

另一种选择是将WPF窗口构建为单独的进程,并从VB6中调用它们。 这是一个关于如何执行此操作的线程: What is the VB 6 equivalent of Process.Start?

同样,特别是如果您不需要VB6的任何交互,您应该管理WPF exe中的所有线程。

答案 2 :(得分:0)

到目前为止,对我来说一切都很好。 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);
        }
    }
}