简体   繁体   English

NET Core 3.1 WinForms Designer 与 WPF 用户控件不兼容

[英]NET Core 3.1 WinForms Designer incompatibility with WPF User Control

Over a year ago I began writing a .NET Framework 4.6.1 app using Windows Forms.一年多以前,我开始使用 Windows Forms 编写 .NET Framework 4.6.1 应用程序。 At the time I knew about WPF, yet was familiar with Windows Forms and it had most of the controls I needed.当时我知道 WPF,但熟悉 Windows Forms,它拥有我需要的大部分控件。 For the missing controls, I wrote two in Windows Forms and one in WPF.对于缺少的控件,我在 Windows Forms 中写了两个,在 WPF 中写了一个。 All of these coexisted fine, with the WPF control containerized within an element host.所有这些都很好地共存,将 WPF 控件容器化在一个元素主机中。

This week I began the migration process to .NET Core 3.1.本周我开始迁移到 .NET Core 3.1。 My tests with a copy of the project were positive, as well as initial results with the actual migration.我对项目副本的测试是积极的,实际迁移的初步结果也是如此。 After minor refactoring, the solution built and ran without issue.经过小的重构后,解决方案构建并运行没有问题。 Then the gremlin appeared after opening the main UI form in the WinForms Designer.然后在 WinForms Designer 中打开主 UI 表单后出现 gremlin。 Back in .NET Framework, all of my custom controls appeared inside the Designer's Toolbox, providing easy drag-and-drop onto the form.回到 .NET 框架中,我的所有自定义控件都出现在 Designer 的工具箱中,可以轻松拖放到表单上。 In .NET Core, only my WinForms controls appeared in the Toolbox, not my WPF control.在 .NET Core 中,只有我的 WinForms 控件出现在工具箱中,而不是我的 WPF 控件。 Because the Designer could not see that control, it stripped it from the Form's designer code, leaving an empty element host behind.因为设计器看不到该控件,所以它将它从窗体的设计器代码中剥离,留下一个空的元素宿主。

Here's the kicker.这是踢球者。 After reverting the Designer's changes, any direct manual edits to the Form's designer code is accepted, and building the project succeeds and runs fine.还原设计器的更改后,接受对表单设计器代码的任何直接手动编辑,并且构建项目成功并运行良好。 So for some reason the Designer does not like WPF controls in WinForms.所以出于某种原因,设计师不喜欢 WinForms 中的 WPF 控件。

Things I've tried:我尝试过的事情:

  • During my testing I discovered that the main WinForms UI needed both "UseWindowsForms" and "UseWPF" set to "true" for the project to compile.在我的测试过程中,我发现主 WinForms UI 需要将“UseWindowsForms”和“UseWPF”都设置为“true”才能编译项目。 I then added the "UseWindowsForms" parameter to the WPF user control library.然后我将“UseWindowsForms”参数添加到 WPF 用户控件库中。 This caused the control to appear in the Designer's Toolbox, yet attempting to add the control resulted in this error: "Failed to create component... Microsoft.DotNet.DesignTools.Client.DesignToolsServerException... Make sure the type implements IComponent and provides an appropriate public constructor. Appropriate constructors either take no parameters or take a single IContainer parameter."这导致控件出现在设计器的工具箱中,但尝试添加控件会导致此错误: “无法创建组件... Microsoft.DotNet.DesignTools.Client.DesignToolsServerException...确保类型实现 IComponent 并提供一个合适的公共构造函数。合适的构造函数要么不带参数,要么只带一个 IContainer 参数。 And the existing WPF control in the code was still removed.并且代码中已有的 WPF 控件仍然被移除。
  • I copied the WPF control from the library to the main UI project, edited the namespace, and removed the library project reference.我将 WPF 控件从库复制到主 UI 项目,编辑命名空间,并删除库项目引用。 Same result as above.结果与上述相同。
  • Created a new Windows Forms User Control library, added "UseWPF" to the project, and copied the WPF control to this library.新建 Windows Forms 用户控件库,将“UseWPF”添加到项目中,并将 WPF 控件复制到此库中。 Same result as above.结果与上述相同。
  • Back to the test copy of my project, I followed Microsoft's guides for "try-convert" and "upgrade-assistant".回到我项目的测试副本,我按照 Microsoft 的“try-convert”和“upgrade-assistant”指南进行操作。 The latter seemed promising at first, as it replaced, modified, or removed outdated referenced and packages.后者起初看起来很有希望,因为它替换、修改或删除了过时的引用和包。 But, no success.但是,没有成功。
  • Tried the above migration steps with both .NET Core 3.1 and .NET 5. Same results.使用 .NET Core 3.1 和 .NET 5 尝试了上述迁移步骤。结果相同。

The point I'm at now is to keep manually editing the Form's designer code.我现在的重点是继续手动编辑表单的设计器代码。 Not ideal for large changes, and also not sustainable if/when this project is passed to another developer.不适合大的变化,如果/当这个项目被传递给另一个开发人员时也不可持续。 Any thoughts?有什么想法吗? Should I attempt porting the Windows Forms UI to WPF?我应该尝试将 Windows Forms UI 移植到 WPF 吗? Or is this simply a maturity issue with the still relatively new .NET Core Windows Forms Designer?或者这仅仅是相对较新的 .NET Core Windows Forms Designer 的成熟度问题?

Visual Studio version: Community 2019 16.9.3 Visual Studio 版本:社区 2019 16.9.3

Screenshot of IComponent error: enter image description here IComponent 错误的屏幕截图:在此处输入图像描述

I finally figured out a workaround;我终于想出了一个解决方法; the idea was sparked by the Microsoft Docs page for the ElementHost control: https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.integration.elementhost?view=netcore-3.1这个想法是由 ElementHost 控件的 Microsoft Docs 页面引发的: https://docs.microsoft.com/en-us/dotnet/api/system.windows.ZAC68B62ABFD6A9FE26E38AC4236C8CE0=netcoreC.integration

In essence, move the WPF control hosting from the main UI into a WinForms control library.实质上,将 WPF 控件托管从主 UI 移动到 WinForms 控件库中。 Hence, the User control in this WinForms control library will be a wrapper for the WPF control.因此,此 WinForms 控件库中的用户控件将成为 WPF 控件的包装器。 Here are the steps I took in my test VS solution:以下是我在测试 VS 解决方案中采取的步骤:

  1. Remove the "true" entry from the main UI project file I had added during testing.从我在测试期间添加的主 UI 项目文件中删除“true”条目。
  2. In the new WinForms control library, add "true" to this project file underneath the "true" entry.在新的 WinForms 控件库中,将“true”添加到该项目文件的“true”条目下方。 This enables the library to be a bridge between the two UI frameworks.这使该库成为两个 UI 框架之间的桥梁。
  3. If the WPF control to host is in a dedicated control library (like mine), then add this project as a dependency in the WinForms control library.如果要托管的 WPF 控件位于专用控件库(如我的)中,则将此项目作为依赖项添加到 WinForms 控件库中。
  4. Create a new User control in the WinForms library, if there isn't one already.在 WinForms 库中创建一个新的用户控件(如果还没有的话)。
  5. In the Designer, add a panel container to the control with "Fill" docking.在设计器中,使用“填充”停靠向控件添加面板容器。 I named my panel as "panelWpf".我将面板命名为“panelWpf”。
  6. Here is where the Microsoft Doc comes in. In the code behind file for the WinForms control, I first added an ElementHost control and the WPF control as private global variables.这是 Microsoft Doc 的用武之地。在 WinForms 控件的代码隐藏文件中,我首先添加了一个 ElementHost 控件和 WPF 控件作为私有全局变量。 Then, in the WinForms "Load" event, I set the ElementHost docking style, added the WPF as a child, and finally added ElementHost as a control to the "panelWpf" container.然后,在WinForms的“Load”事件中,我设置了ElementHost的对接样式,添加了WPF作为child,最后将ElementHost作为控件添加到了“panelWpf”容器中。 Below is the code from this file.以下是此文件中的代码。 "WpfControlLibrary31" is my WPF control library project, and "TestControl31" is the WPF control itself. “WpfControlLibrary31”是我的 WPF 控件库项目,“TestControl31”是 WPF 控件本身。 Lastly, "WpfTest" is the name of the wrapper WinForms User control.最后,“WpfTest”是包装 WinForms 用户控件的名称。
  7. After building the WinForms control library, it appeared in the main UI project's Toolbox, and I was able to add it to the form like any other Windows Forms control.构建 WinForms 控件库后,它出现在主 UI 项目的工具箱中,我能够像任何其他 Windows Forms 控件一样将其添加到表单中。 The next steps will be to add event handlers, getters, setters, etc. to the control for the needed interaction.接下来的步骤是将事件处理程序、getter、setter 等添加到控件以进行所需的交互。
using System;
using System.Windows.Forms;
using System.Windows.Forms.Integration;


namespace WinFormsLibrary
{
    public partial class WpfTest : UserControl
    {
        // ElementHost for the WPF control
        private ElementHost host = new ElementHost();
        // WPF control to be hosted
        private WpfControlLibrary31.TestControl31 uc = new WpfControlLibrary31.TestControl31();

        public WpfTest()
        {
            InitializeComponent();
        }

        private void WpfTest_Load(object sender, EventArgs e)
        {
            // set the docking style for the ElementHost
            host.Dock = DockStyle.Fill;

            // add the WPF control as a child of ElementHost
            host.Child = uc;

            // add the ElementHost as a control of the panel container
            panelWpf.Controls.Add(host);
        }
    }
}

Thoughts :想法

  1. Some may wonder why I used the panel container.有些人可能想知道我为什么使用面板容器。 And in this simple experiment it was overkill;在这个简单的实验中,它是矫枉过正的。 I could have simply docked the ElementHost to the control itself.我可以简单地将 ElementHost 停靠到控件本身。 However, if the WinForms User control has a more complex design, then the panel will be a placeholder while still allowing use of the Designer.但是,如果 WinForms 用户控件具有更复杂的设计,则面板将是一个占位符,同时仍允许使用设计器。 Also, if a border or similar design is needed around the WPF control, then this should be possible with the panel.此外,如果 WPF 控件周围需要边框或类似设计,那么面板应该可以实现。
  2. Having the ElementHost and WPF control object as global allows access from all the control's methods, obviously, just like any controls added in the Designer itself.显然,将 ElementHost 和 WPF 控件 object 作为全局控件允许从所有控件的方法进行访问,就像在设计器本身中添加的任何控件一样。
  3. The WPF control to host does not need to be in a dedicated WPF control library project. WPF 控件到主机不需要位于专用的 WPF 控件库项目中。 If it is a pre-existing WPF control (eg MediaElement), then use it for the global WPF object.如果它是预先存在的 WPF 控件(例如 MediaElement),则将其用于全局 WPF object。
  4. This WinForms control library is what I have been needing to consolidate and improve efficiency of my custom controls.这个 WinForms 控件库是我整合和提高自定义控件效率所需要的。 So this issue with the .NET Core WinForms Designer turned out to be a blessing in disguise.因此,.NET Core WinForms Designer 的这个问题原来是因祸得福。

What are your thoughts?你觉得呢?你有没有什么想法? Thanks for the brainstorming help!感谢头脑风暴的帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM