.NET Core 3と2以下の混在下でWPF/WinFormを有効にする

また何年振りかという更新です。

課題

.NET Core 3.0から、WPFWindows Formsがサポートされました。WPFを使う場合のcsprojファイルの基本形は以下です。 (<UseWPF><UseWindowsForms> と置き換えればWindows Forms向けになります。)

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>Library</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
</Project>

本記事は、ここで何らかの理由で TargetFrameworksnetcoreapp3.1 以外を入れたくなった場合の話です。

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>Library</OutputType>
    <TargetFrameworks>netcoreapp3.1;netcoreapp2.1</TargetFrameworks>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
</Project>

netcoreapp2.1ではWPFは使えません。警告が出るだけなので無視を決め込んでも良いかもしれませんが、何とかしてみましょう。あくまでこういうことができるという話として。

解決法

以下を参考にします。ひょっとするともう少し簡単に書けるのかもしれませんが。 github.com

<Project>
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" Condition="'$(TargetFramework)' != 'netcoreapp3.1'" />
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk.WindowsDesktop" Condition="'$(TargetFramework)' == 'netcoreapp3.1'" />

  <PropertyGroup>
    <TargetFrameworks>netcoreapp3.1;netcoreapp2.1</TargetFrameworks>
    <OutputType>Library</OutputType>
  </PropertyGroup>
  <PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" Condition="'$(TargetFramework)' != 'netcoreapp3.1'" />
  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk.WindowsDesktop" Condition="'$(TargetFramework)' == 'netcoreapp3.1'" />
</Project>

Microsoft.NET.Sdk.WindowsDesktopUseWPF について、netcoreapp3.1 のときだけ有効にします。

マルチプラットフォーム対応

WPFWindows Formsに対応と言っても、現在のところWindows限定なのは変わりません。このcsprojをLinuxに持って行ってビルドすると失敗します。

そこまで考慮したい場合は以下のようにします。OSによる分岐は以下のページが参考になります。

stackoverflow.com

<Project>
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" Condition="'$(OS)' != 'Windows_NT' Or '$(TargetFramework)' != 'netcoreapp3.1'" />
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk.WindowsDesktop" Condition="'$(OS)' == 'Windows_NT' And '$(TargetFramework)' == 'netcoreapp3.1'" />

  <PropertyGroup>
    <TargetFrameworks>netcoreapp3.1;netcoreapp2.1</TargetFrameworks>
    <OutputType>Library</OutputType>
  </PropertyGroup>
  <PropertyGroup Condition="'$(OS)' == 'Windows_NT' And '$(TargetFramework)' == 'netcoreapp3.1'">
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <PropertyGroup Condition="'$(OS)' == 'Windows_NT'">
    <DefineConstants>$(DefineConstants);WINDOWS;</DefineConstants>
  </PropertyGroup>

  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" Condition="'$(OS)' != 'Windows_NT' Or '$(TargetFramework)' != 'netcoreapp3.1'" />
  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk.WindowsDesktop" Condition="'$(OS)' == 'Windows_NT' And '$(TargetFramework)' == 'netcoreapp3.1'" />
</Project>

上記では、WINDOWS というコンパイラ定数を定義しています。csprojさえ分岐して乗り切ればよいわけではなく、実際にはコードでも分岐が必要になるためです。using System.Windows.Media; 等といった箇所でエラーが出てきます。

Hoge.cs(4,22): error CS0234: The type or namespace name 'Media' does not exist in the namespace 'System.Windows' (are you missing an assembly reference?) [/foo/bar/baz.csproj]

コンパイラ定数を使って、WPFの利用箇所をまるごと無効にするような格好になるでしょう。

#if WINDOWS && NETCOREAPP3_1
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
... (以下略) ...

#endif