1. 今回やること
・C++/WinRTでUWPその13 ピクチャフォルダをサムネイル表示しようでやったようにピクチャフォルダをサムネイル表示するアプリを作成します。
・基本となる部分はMSのC++ デスクトップ (Win32) アプリでカスタム WinRT XAML コントロールをホストする、ほぼそのまままです。
・なるべく簡単にやろうと思っていましたが、ちょっと何か高度なことをやろうとするとやっぱり避けては通れません・・・
・と、言うわけで上記二つをよく読めば出来ることを今回やっていきます。
・話はちょっと変わりますが、今年に入ってからMSのHPが機械翻訳のみからおそらく人の改訂が入って凄く読みやすくなりました。C++/WInRT関連や各APIもかなり読みやすくなっていますので是非一度読んでみて下さい。
2. プロジェクトの作成
・最近OSをWin11、Visual Studioを2022へ変更しました。
・使いづらかったり、ヘッダーが足りないと言われたり色々不便なこともありますが・・・
・VC2022のMFCアプリでプロジェクトを開始します。名前は「MFCWinRTThumb」。SDIにして、高度な機能のドッキング…は使用しません。
・プロパティーで言語標準をC++17にして、C++コマンドラインに「/await」を追加します。
・pch.hにGetCurrentTimeとTRYの#undefを追加します。
#ifndef PCH_H
#define PCH_H
// プリコンパイルするヘッダーをここに追加します
#include "framework.h"
#undef GetCurrentTime
#undef TRY
#endif //PCH_H
・次にソリューションを右クリックして、「追加→新しいプロジェクト」から空のアプリ (C++/WinRT) プロジェクトを追加します。
・名前は「ThumbView」、最小ターゲットバージョンは「1903」へ設定します。
・メタデータへの参照を追加します。今回最小のターゲットバージョンを1903(10.0.18362.0)としたので、このフォルダにあるメタデータへの参照を追加します。ソリューションエクスプローラーのプロジェクトの参照から右クリックして参照の追加より追加。
・Nugetから以下の四つのパッケージを追加します。MFCとWinRTの両方のプロジェクトへ追加してください。
①Microsoft.Windows.CppWinRT
②Microsoft.Toolkit.Win32.UI.SDK
③Microsoft.Toolkit.Win32.UI.XamlApplication
④Microsoft.VCRTForwarders.140
3. ThumbViewの変更
・MFCのViewクラスから表示させるThumbViewを作成します。
・いきなりサムネイル部分も作るのでは無く、まずはお試し表示で作成します。
・MSのHPでは「MyUWPApp」という名前で作成していますが、こちらは「ThumbView」という名前で作成しています。つまり、名前の部分は書き換えが必要となります。
・長いけど、一気にいきます。
3-1. プロパティーの変更
・共通プロパティーの「Verbosity」を「normal」へ変更します。
・「構成の種類」を「ダイナミック ライブラリ」へ変更します。
・ThumbViewに「placeholder.exe」という名前のテキストファイルを新しい項目として追加し、「placeholder.exe」プロパティーウインドウより「コンテンツ」を「True」へ変更します。
・「Package.appxmanifest」をXML(テキスト)エディターから開き、<Application>要素のExecutable 属性の値を placeholder.exe に変更します。
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" IgnorableNamespaces="uap mp">
<Identity
Name="04b0e12b-80de-41ab-b0f5-61ae7d68df7f"
Publisher="CN=hen"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="04b0e12b-80de-41ab-b0f5-61ae7d68df7f" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>ThumbView</DisplayName>
<PublisherDisplayName>hen</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="App" Executable="placeholder.exe" EntryPoint="ThumbView.App">
<uap:VisualElements DisplayName="ThumbView" Description="Project for a single page C++/WinRT Universal Windows Platform (UWP) app with no predefined layout"
Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png">
</uap:DefaultTile>
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
</Capabilities>
</Package>
3-2. プロジェクトファイルの変更
・ThumbViewプロジェクトをプロジェクトからアンロードします。すると「ThumbView」のプロジェクトファイルを読み込むので、<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> 要素を見つけて、次の XML に置き換えます。ここはMSのHPまんまですね。
・上書き保存したら、プロジェクトを再読込します。
<PropertyGroup Label="Globals">
<WindowsAppContainer>true</WindowsAppContainer>
<AppxGeneratePriEnabled>true</AppxGeneratePriEnabled>
<ProjectPriIndexName>App</ProjectPriIndexName>
<AppxPackage>true</AppxPackage>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
3-3. 表示部分の作成
・ThumbViewに新しい項目の追加より「Blank User Control (C++/WinRT)」を追加します。名前は「MainControl」
・表示する内容は以下にでもしときます。
<UserControl
x:Class="ThumbView.MainControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ThumbView"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel HorizontalAlignment="Center" Spacing="10" Padding="20" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" TextWrapping="Wrap" Text="まだ作成中だ!💋" FontSize="30" />
<Button HorizontalAlignment="Center" x:Name="Button" Click="ClickHandler">Click Me</Button>
</StackPanel>
</UserControl>
3-5. その他の編集
・ThumbViewの「MainPage.xaml」を削除します。
・「App.xaml」を以下に変更します
<Toolkit:XamlApplication
x:Class="ThumbView.App"
xmlns:Toolkit="using:Microsoft.Toolkit.Win32.UI.XamlHost"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ThumbView">
</Toolkit:XamlApplication>
・「App.idl」を以下に変更します
namespace ThumbView
{
[default_interface]
runtimeclass App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication
{
App();
}
}
・「App.h」を以下に変更します
#pragma once
#include "App.g.h"
#include "App.base.h"
namespace winrt::ThumbView::implementation
{
class App : public AppT2<App>
{
public:
App();
~App();
};
}
namespace winrt::ThumbView::factory_implementation
{
class App : public AppT<App, implementation::App>
{
};
}
・「App.cpp」を以下に変更します
#include "pch.h"
#include "App.h"
#include "App.g.cpp"
using namespace winrt;
using namespace Windows::UI::Xaml;
namespace winrt::ThumbView::implementation
{
App::App()
{
Initialize();
AddRef();
m_inner.as<::IUnknown>()->Release();
}
App::~App()
{
Close();
}
}
#pragma once
namespace winrt::ThumbView::implementation
{
template <typename D, typename... I>
struct App_baseWithProvider : public App_base<D, ::winrt::Windows::UI::Xaml::Markup::IXamlMetadataProvider>
{
using IXamlType = ::winrt::Windows::UI::Xaml::Markup::IXamlType;
IXamlType GetXamlType(::winrt::Windows::UI::Xaml::Interop::TypeName const& type)
{
return _appProvider.GetXamlType(type);
}
IXamlType GetXamlType(::winrt::hstring const& fullName)
{
return _appProvider.GetXamlType(fullName);
}
::winrt::com_array<::winrt::Windows::UI::Xaml::Markup::XmlnsDefinition> GetXmlnsDefinitions()
{
return _appProvider.GetXmlnsDefinitions();
}
private:
bool _contentLoaded{ false };
winrt::ThumbView::XamlMetaDataProvider _appProvider;
};
template <typename D, typename... I>
using AppT2 = App_baseWithProvider<D, I...>;
}
4. ソリューションの変更
・ビルド関連で作成するファイルの作成先の変更や、依存先の変更を行います。
・まずはソリューションにXMLファイルを追加し、名前を「Solution.props」とします。
・「Solution.props」を以下へ書き換えます。
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<IntDir>$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
<OutDir>$(SolutionDir)\bin\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutDir>
<GeneratedFilesDir>$(IntDir)Generated Files\</GeneratedFilesDir>
</PropertyGroup>
</Project>
・プロパティーマネージャーの二つのプロジェクトを右クリックして、既存のプロパティーシートの追加より今作った「Solution.props」を読ませます。二つとも。ここ重要。
・これで、一つのフォルダの中に二つのプロジェクトからビルドされたファイルが生成されます。
・ここで一度ビルドして失敗しなければOK。後はMFCのプロジェクトへWinRTで作成したプロジェクトを表示させる部分を作り込むだけ!
ここまででも十分長いょ・・・
5. ThumbViewを表示させるようにMFCWinRTThumbを改造する。
・MSのページではパッケージ化してますが、面倒なのでパッケージ化はしません。VC++ランタイムは入っているでしょ?
5-1. マニフェストの作成とプロジェクトファイルの変更
・まずは「app.manifest」を作成します。ただし、今までとはちょっと内容が変わります。
<?xml version="1.0" encoding="utf-8"?>
<assembly
xmlns="urn:schemas-microsoft-com:asm.v1"
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"
manifestVersion="1.0">
<asmv3:file name="ThumbView.dll">
<activatableClass
name="ThumbView.App"
threadingModel="both"
xmlns="urn:schemas-microsoft-com:winrt.v1" />
<activatableClass
name="ThumbView.XamlMetadataProvider"
threadingModel="both"
xmlns="urn:schemas-microsoft-com:winrt.v1" />
<activatableClass
name="ThumbView.MainControl"
threadingModel="both"
xmlns="urn:schemas-microsoft-com:winrt.v1" />
</asmv3:file>
</assembly>
・MFCWinRTThumbのプロジェクトファイルを編集します。まずはMFCWinRTThumbをアンロードして、ファイルの末尾にある終了タグ の直前に、次の XML を追加します
<!-- Configure these for your UWP project -->
<PropertyGroup>
<AppProjectName>ThumbView</AppProjectName>
</PropertyGroup>
<PropertyGroup>
<AppIncludeDirectories>$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(AppProjectName)\;$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(AppProjectName)\Generated Files\;</AppIncludeDirectories>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\$(AppProjectName)\$(AppProjectName).vcxproj" />
</ItemGroup>
<!-- End Section-->
・追加して上書き保存したら、プロジェクトの再読込
・MFCWinRTThumbのプロパティーより、「DPI認識」を「モニターごとの高いDPI認識」へ変更します。今までこれを変更せずにエラーが起きたことは無いですが、エラーが発生することがあるみたいですからね。
5-2. MFCのViewでThumbViewを表示する。
・それでは今回はここまで準備したので、XamlReaderを使わずに表示させます。
・まずはMFCWinRTThumbの「pch.h」で必要なヘッダー等をincludeします。
#ifndef PCH_H
#define PCH_H
// プリコンパイルするヘッダーをここに追加します
#include "framework.h"
#undef GetCurrentTime
#undef TRY
#include "resource.h"
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.system.h>
#include <winrt/windows.ui.xaml.hosting.h>
#include <windows.ui.xaml.hosting.desktopwindowxamlsource.h>
#include <winrt/windows.ui.xaml.controls.h>
#include <winrt/Windows.ui.xaml.media.h>
#include <winrt/Windows.UI.Core.h>
#include <winrt/ThumbView.h>
using namespace winrt;
using namespace Windows::UI;
using namespace Windows::UI::Composition;
using namespace Windows::UI::Xaml::Hosting;
using namespace Windows::Foundation::Numerics;
using namespace Windows::UI::Xaml::Controls;
#endif //PCH_H
・ViewクラスにThumbViewとDesktopWindowXamlSourceのメンバを追加します。
class CMFCWinRTThumbView : public CView
{
//ここにWinRT関連のメンバを追加
winrt::ThumbView::App hostApp{ nullptr };
winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource _desktopWindowXamlSource{ nullptr };
winrt::ThumbView::MainControl _mainControl{ nullptr };
winrt::impl::com_ref<IDesktopWindowXamlSourceNative> interop;
protected: // シリアル化からのみ作成します。
CMFCWinRTThumbView() noexcept;
DECLARE_DYNCREATE(CMFCWinRTThumbView)
・CMFCWinRTThumbViewクラスにOnCreateを追加して、WinRTの初期化~ウインドウのアタッチまで行います。
int CMFCWinRTThumbView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: ここに特定な作成コードを追加してください。
//ここでWinRTの初期化~ウインドウのアタッチまで
winrt::init_apartment(winrt::apartment_type::single_threaded);
hostApp = winrt::ThumbView::App{};
_desktopWindowXamlSource = winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource{};
if (_desktopWindowXamlSource != nullptr)
{
interop = _desktopWindowXamlSource.as<IDesktopWindowXamlSourceNative>();
check_hresult(interop->AttachToWindow(this->m_hWnd));
_mainControl = winrt::ThumbView::MainControl();
}
return 0;
}
・WinRTの表示関連はOnDrawに記述します。
void CMFCWinRTThumbView::OnDraw(CDC* /*pDC*/)
{
CMFCWinRTThumbDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: この場所にネイティブ データ用の描画コードを追加します。
HWND hWndXamlIsland = nullptr;
interop->get_WindowHandle(&hWndXamlIsland);
RECT windowRect;
::GetWindowRect(this->m_hWnd, &windowRect);
::SetWindowPos(hWndXamlIsland, NULL, 0, 0, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, SWP_SHOWWINDOW);
_desktopWindowXamlSource.Content(_mainControl);
}
・ふぅ、長かった。これでMFCWinRTThumbをビルド/実行してエラーが出なければ、「MFC()笑」と言われないモダンなアプリの雛型が完成です。
・実行するとこんな感じ
・しかし、たったこれだけしか表示しないのにメモリを74MBも食うなんてなんてUWPは燃費が悪いんだ。MFCだけなら10MBも行かないのに・・・
・色々苦労してMFCからUWPを利用していますが、その最大の利点は一々インストールしなくてもUWPアプリが動くこと。今回のように作成すると、dllなんかも一緒にしてくれるのでbinの中にあるMFCWinRTThumbフォルダをそのままコピーするだけで使えます。「インストーラーが動かない;;」とか「証明書が;;」とか言う苦情を聞かなくても済むんです!インストーラーを無効にするMS許すまじ! もちろんVCランタイムは入っている前提ですよ?
・次回はこれを元にして、ピクチャフォルダをサムネイル表示します。