NET Core 3.1 WinForms Designer 与 WPF 用户控件不兼容
Posted
技术标签:
【中文标题】NET Core 3.1 WinForms Designer 与 WPF 用户控件不兼容【英文标题】:NET Core 3.1 WinForms Designer incompatibility with WPF User Control 【发布时间】:2021-07-09 22:17:18 【问题描述】:一年多前,我开始使用 Windows 窗体编写 .NET Framework 4.6.1 应用程序。当时我知道 WPF,但熟悉 Windows 窗体,它具有我需要的大部分控件。对于缺少的控件,我在 Windows 窗体中编写了两个,在 WPF 中编写了一个。所有这些都可以很好地共存,并将 WPF 控件容器化在一个元素宿主中。
本周我开始迁移到 .NET Core 3.1。我对项目副本的测试是积极的,实际迁移的初步结果也是如此。经过小的重构后,解决方案构建并运行没有问题。然后在 WinForms Designer 中打开主 UI 表单后出现 gremlin。回到 .NET Framework,我的所有自定义控件都出现在 Designer 的工具箱中,可以轻松拖放到表单上。在 .NET Core 中,只有我的 WinForms 控件出现在工具箱中,而不是我的 WPF 控件。由于设计器看不到该控件,因此将其从表单的设计器代码中剥离,留下一个空的元素宿主。
这是踢球者。还原设计器的更改后,接受对表单设计器代码的任何直接手动编辑,并且构建项目成功并运行良好。所以出于某种原因,Designer 不喜欢 WinForms 中的 WPF 控件。
我尝试过的事情:
在测试期间,我发现主 WinForms UI 需要将“UseWindowsForms”和“UseWPF”都设置为“true”才能编译项目。然后,我将“UseWindowsForms”参数添加到 WPF 用户控件库中。这导致控件出现在设计器的工具箱中,但尝试添加控件会导致此错误:“无法创建组件... Microsoft.DotNet.DesignTools.Client.DesignToolsServerException ...确保类型实现IComponent 并提供适当的公共构造函数。适当的构造函数要么不带参数,要么只带一个 IContainer 参数。” 并且代码中现有的 WPF 控件仍然被删除。 我将 WPF 控件从库复制到主 UI 项目,编辑了命名空间,并删除了库项目引用。结果与上述相同。 创建了一个新的 Windows 窗体用户控件库,将“UseWPF”添加到项目中,并将 WPF 控件复制到此库中。结果与上述相同。 回到我的项目的测试副本,我按照 Microsoft 的“try-convert”和“upgrade-assistant”指南进行操作。后者起初看起来很有希望,因为它替换、修改或删除了过时的引用和包。但是,没有成功。 使用 .NET Core 3.1 和 .NET 5 尝试了上述迁移步骤。结果相同。我现在的重点是继续手动编辑表单的设计器代码。不适合大的变化,如果/当这个项目被传递给另一个开发人员时也不可持续。有什么想法吗?我应该尝试将 Windows 窗体 UI 移植到 WPF 吗?或者这仅仅是相对较新的 .NET Core Windows 窗体设计器的成熟度问题?
Visual Studio 版本:Community 2019 16.9.3
IComponent 错误截图: enter image description here
【问题讨论】:
我目前唯一的建议:要依赖新的 WF Designer,至少使用 .NET 5 作为目标,因为 3.1 版本的新 WF Designer 不完整/有问题。尝试迁移单个控件并通过将代码插入到已经针对 .NET 5 的干净新项目中手动使其工作。使其工作,然后比较 Designer 的代码,您会发现输出代码将与 Framework 的代码部分不兼容.这并不能解决整个问题,但有助于了解如何以最小的时间损失实现它。 好主意,@aepot。在这方面,作为一个实验,我创建了一个包含四个新项目的解决方案:.NET Core 3.1 WinForms、.NET 5 WinForms 和两个 WPF Control 项目来匹配。这一次,将“UseWPF”添加到 WinForms 项目并没有显示工具箱中的控件,但是将“UseWindowsForms”添加到控件中可以。但是,我仍然收到与以前相同的错误,附加错误“无法访问已处置的对象。对象名称:'AdornerWindow'。” 【参考方案1】:我终于想出了一个解决方法;这个想法是由 ElementHost 控件的 Microsoft Docs 页面引发的: https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.integration.elementhost?view=netcore-3.1
实质上,将 WPF 控件托管从主 UI 移至 WinForms 控件库。因此,此 WinForms 控件库中的 User 控件将成为 WPF 控件的包装器。以下是我在测试 VS 解决方案中采取的步骤:
-
从我在测试期间添加的主 UI 项目文件中删除“true”条目。
在新的 WinForms 控件库中,将“true”添加到此项目文件的“true”条目下方。这使该库成为两个 UI 框架之间的桥梁。
如果要托管的 WPF 控件位于专用控件库中(如我的),则将此项目作为依赖项添加到 WinForms 控件库中。
在 WinForms 库中创建一个新的用户控件(如果还没有的话)。
在设计器中,使用“填充”停靠向控件添加面板容器。我将面板命名为“panelWpf”。
这里是 Microsoft Doc 的用武之地。在 WinForms 控件的代码隐藏文件中,我首先添加了一个 ElementHost 控件和 WPF 控件作为私有全局变量。然后,在WinForms的“Load”事件中,我设置了ElementHost的停靠样式,将WPF作为子添加,最后将ElementHost作为控件添加到“panelWpf”容器中。以下是此文件中的代码。 “WpfControlLibrary31”是我的 WPF 控件库项目,“TestControl31”是 WPF 控件本身。最后,“WpfTest”是包装 WinForms 用户控件的名称。
构建 WinForms 控件库后,它出现在主 UI 项目的工具箱中,我能够像任何其他 Windows 窗体控件一样将其添加到窗体中。接下来的步骤是将事件处理程序、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);
想法:
-
有些人可能想知道我为什么使用面板容器。在这个简单的实验中,它是矫枉过正的。我可以简单地将 ElementHost 停靠到控件本身。但是,如果 WinForms 用户控件具有更复杂的设计,则面板将是一个占位符,同时仍允许使用设计器。此外,如果 WPF 控件周围需要边框或类似设计,那么面板应该可以实现。
将 ElementHost 和 WPF 控件对象设为全局允许从所有控件的方法进行访问,显然,就像在设计器本身中添加的任何控件一样。
托管的 WPF 控件不需要位于专用的 WPF 控件库项目中。如果它是预先存在的 WPF 控件(例如 MediaElement),则将其用于全局 WPF 对象。
这个 WinForms 控件库是我整合和提高自定义控件效率所需要的。所以 .NET Core WinForms Designer 的这个问题原来是因祸得福。
你的想法是什么?感谢您的头脑风暴帮助!
【讨论】:
以上是关于NET Core 3.1 WinForms Designer 与 WPF 用户控件不兼容的主要内容,如果未能解决你的问题,请参考以下文章
NET Core 3.1 WinForms Designer 与 WPF 用户控件不兼容
使用 JavaScriptService 在.NET Core 里实现DES加密算法