Description
April Update for WPF on .NET Core 3.0
Last December, Scott Guthrie and Kevin Gallo announced that Windows Presentation Foundation (WPF), Windows Forms and WinUI would be open sourced. .NET Core 3.0 Preview 1 added support for building client apps using WPF, Windows Forms, and XAML Islands. We apologize that the WPF repository has been a little quiet, but the WPF team here at Microsoft has been hard at work at the follow items:
- We onboarded to the Arcade SDK based Build infrastructure
- The WPF project template has been made available
- We recently open sourced PresentationBuildTasks along with Microsoft.NET.Sdk.WindowsDesktop!
- We’re defining the overall test approach for WPF in a GitHub world
This work was foundational and will help us as we continue with open sourcing remaining WPF components. If you’re interested in the nitty gritty details of what we’ve been doing, take a look below.
Going Forward
As we move towards the .NET Core 3.0 General Availability (GA) we will post more regular updates to this repository. For the next few months, our plan is to:
- Continue porting the WPF component list to this repo
- Port regression tests so that validation against new Pull Requests can be automated
Community Contributions
We know that the community is eager to contribute and we thank you for your contributions and engagement to date!
Our primary focus for the initial .NET Core 3.0 release for WPF is to achieve parity with .NET Framework. Priority will be given to contributions that align with that goal. The most help that we need right now is on bug fixes that specifically target parity between .NET Core and .NET Framework.
You can learn more from our contributing guidelines. If you have questions, suggestions, or concerns, please let us know.
Details about onboarding the Arcade SDK
The Arcade SDK offers several benefits for products that build as part of NET Core. When the WPF team adopted this SDK for our builds, we realized some work needed to be done.
WPF was one of the first teams to onboard and build native C++ vcxproj based projects (as against CMake etc. projects) within the Arcade SDK ecosystem. This resulted in some focused work to ensure that C++ related properties and targets are appropriately tuned to ensure MSBuild just worked to build WPF with the same compiler, linker etc. settings that it has been historically built with.
We also realized that the default infrastructure in place for NuGet packaging – which is quite adequate for most projects - was insufficient for the needs of WPF given the fact that we produce native assemblies in the mix.
In addition to this, we also produce transport packages out of the WPF repository that contain the WPF .NET Core assemblies (like System.Xaml.dll, WindowsBase.dll), the native-RID specific assemblies (like wpfgfx_cor3.dll, PresentationNative_cor3.dll), reference assemblies (ref\WindowsBase.dll, ref\System.Xaml.dll), and satellite (resource) assemblies (like cs\System.Xaml.resources.dll, de\System.Xaml.resources.dll).
This fact is in itself not unique to WPF – all .NET Core repositories generate transport packages – but WPF’s unique composition involving RID-specific native assemblies necessitated us to customize the packaging process to suit our needs.
Currently, we publish the following transport packages to the https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json feed. These can be viewed using NuGet Package Explorer (which, BTW, has been ported to WPF on .NET Core 3.0!):
- Microsoft.DotNet.Wpf.GitHub
o runtime.win-x64.Microsoft.DotNet.Wpf.GitHub
o runtime.win-x86.Microsoft.DotNet.Wpf.GitHub - Microsoft.DotNet.Wpf.DncEng
o runtime.win-x64.Microsoft.DotNet.Wpf.DncEng
o runtime.win-x86.Microsoft.DotNet.Wpf.DncEng - Microsoft.DotNet.Wpf.ProjectTemplates
- Microsoft.NET.Sdk.WindowsDesktop
And since WPF has two repositories (one on GitHub with the open-sourced projects, and another one internal within Microsoft), we realized that coherence in the way we build things was important. To achieve this, we have centralized all our build props/targets and published it as an MSBuild SDK onto the same NuGet feed - Microsoft.DotNet.Arcade.Wpf.Sdk.
Note: All of these transport packages are intended for internal use by the .NET Core build systems and repos only and should not be consumed directly for any other purpose.
In addition to onboarding native C++ projects and building transport packages right, we also undertook some work to build C++/CLI assemblies. WPF has two notable C++/CLI based assemblies – PresentationCore.dll and System.Printing.dll.
Note: In terms of build dependencies, PresentationBuildTasks and System.Xaml can be built independently, WindowsBase requires only System.Xaml as a dependency, PresentationCore requires WindowsBase & System.Xaml, and so on. Nearly all of the remaining WPF assemblies require System.Xaml, WindowsBase, and PresentationCore. In short, PresentationCore is far in front of our build dependency chain, and we had to ensure that a build system that we open-source can handle builds for PresentationCore.dll, as well as for other C++/CLI .NET Core DLL effectively.
Many of you are probably aware that there is currently no support for building C++/CLI in .NET Core. In Dev16.0 (aka Visual Studio 2019), the C++ team has added limited capability (note: I didn’t write “support”) for compiling C++/CLI assemblies targeting .NET Core. For more details on how this works in the WPF codebase, please dig into Wpf.Cpp.props/Wpf.Cpp.targets and search for /clr:netcore. (If you try to use this today, it will probably not work for you due to a bug – please wait until Visual Studio 2019 Preview 2 comes out).
This limited support for C++/CLI has no SDK support yet. In other words, we couldn’t just take a C++/CLI vcxproj project that targeted .NET Framework and retarget it to .NET Core – the underlying NuGet support for discovering .NET Core references and a myriad of other build targets just didn’t exist. We worked with several colleagues in the .NET and C++ teams to build our own limited support for discovering the right NuGet references during build (see CppCliHelper in Wpf.Cpp.targets)
Over the years, WPF codebase had accumulated a number of cycles between some of the assemblies. The team never really noticed them because our builds depended on reference-assemblies from the previous builds (ref-assemblies that were checked in with sources in binary form).
In our .NET Core codebase, we do not check-in any binaries and require everything to be built from sources. In order to do this right, we spent some effort analyzing the dependencies between various assemblies and ensured that our build used project-references exclusively. Wherever we couldn’t break a cycle, we created small (and scoped) hand-crafted synthetic ref-assembly projects that could stand-in for the real one.
These “cycle-breakers” (there are 8 of these at last count) haven’t moved over to GitHub yet, but here is one illustrative example that is used to build PresentationUI.csproj. We found that PresentationFramework required PresentationUI to build, and PresentationUI required PresentationFramework (thus a cycle). We isolated just the small subset of types from PresentationFramework that were needed to build PresentationUI and put it into a special-purpose project that acts as a build-time cycle-breaker helper. This type of refactoring also helps prevents additional types from being added to these cycles, and will allow us to decide upon how to remove these cycles in the future.
dotnet-wpf-int/src/Microsoft.DotNet.Wpf/cycle-breakers/PresentationUI/PresentationUI.internals.cs
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// ------------------------------------------------------------------------------
// Changes to this file must follow the http://aka.ms/api-review process.
// ------------------------------------------------------------------------------
using System.Runtime.CompilerServices;
using MS.Internal.PresentationCore;
[assembly:InternalsVisibleTo(BuildInfo.PresentationFramework)]
// This is the minimum set of surface area required to enable PresentationFramework to build.
namespace MS.Internal.Documents.Application
{
internal sealed partial class DocumentStream
{
internal static readonly string XpsFileExtension;
}
internal partial struct DocumentApplicationState
{
private int _dummyPrimitive;
public DocumentApplicationState(double zoom, double horizontalOffset, double verticalOffset, int maxPagesAcross) { throw null; }
public double HorizontalOffset { get { throw null; } }
public int MaxPagesAcross { get { throw null; } }
public double VerticalOffset { get { throw null; } }
public double Zoom { get { throw null; } }
}
}
namespace MS.Internal.Documents
{
internal sealed partial class DocumentApplicationDocumentViewer : System.Windows.Controls.DocumentViewer
{
public static System.Windows.Input.RoutedUICommand RequestSigners { get { throw null; } }
public static System.Windows.Input.RoutedUICommand ShowRMCredentialManager { get { throw null; } }
public static System.Windows.Input.RoutedUICommand ShowRMPermissions { get { throw null; } }
public static System.Windows.Input.RoutedUICommand ShowRMPublishingUI { get { throw null; } }
public static System.Windows.Input.RoutedUICommand ShowSignatureSummary { get { throw null; } }
public static System.Windows.Input.RoutedUICommand Sign { get { throw null; } }
public MS.Internal.Documents.Application.DocumentApplicationState StoredDocumentApplicationState { get { throw null; } set { } }
public void SetUIToStoredState() { }
}
internal partial class FindToolBar : System.Windows.Controls.ToolBar //, System.Windows.Markup.IComponentConnector
{
public FindToolBar() { }
public bool DocumentLoaded { set { } }
public bool FindEnabled { get { throw null; } }
public bool MatchAlefHamza { get { throw null; } }
public bool MatchCase { get { throw null; } }
public bool MatchDiacritic { get { throw null; } }
public bool MatchKashida { get { throw null; } }
public bool MatchWholeWord { get { throw null; } }
public string SearchText { get { throw null; } }
public bool SearchUp { get { throw null; } set { } }
public event System.EventHandler FindClicked { add { } remove { } }
public void GoToTextBox() { }
}
}