LoginSignup
2
1

More than 3 years have passed since last update.

C++/WinRT(非ストアアプリ)でデータバインディング

Last updated at Posted at 2021-03-21

一口にC++/WinRTと言っても、ストアアプリか単体exeデスクトップアプリかでだいぶ変わってくる。
ストア用にビルドするといろいろ自動的に生成してくれるが、非ストアだとそれが無い。
(中間生成ファイルをキープしておいて、非ストアで無理矢理ビルドするのは試してみたが、実行すると例外で落ちる。)

その中でも、非ストアの場合はAppやMainPageとXAMLを結び付けてくれないのがかなり痛い。つまり、コンパイルの時点でXAML側からコードで作成したオブジェクトとかが使えないのだ。

上記から、データバインディングも絶望的かと思われたが、条件を整えまくったら何とか出来たのでご紹介。

非ストアなC++/WinRTでデータバインディングするための条件

  1. バインドするオブジェクトをランタイムクラスにする
    ランタイムクラスでないと、XAML UIから参照しようとした途端に例外で落ちる。
    ランタイムクラスは「*.idl」というファイルを作って一旦コンパイルし、さらに生成された「*.h」「*.cpp」を編集してプロジェクトに加え、再度コンパイルすることによって作れる。
     
  2. INotifyPropertyChangedを継承する
    これは確かC#/WPFでもそうだったと思う。
    なお{x:Bind}を使う場合はC++でもこの時点でバインディング可能になるが、おそらくコンパイル時にXAMLが(バインド対象の)クラスと結び付いてないといけないので、非ストアだと無理。
     
  3. {x:Bind}ではなく{Binding}を使う場合は[bindable]属性を付ける
    付けてみたところ、ストアアプリだと確かにバインディング出来るようになったが、ビルド時に何かいろいろ生成してくれてるおかげのようで、非ストアだと無意味だった。
     
  4. ICustomPropertyProviderおよびICustomPropertyを使う
    条件1と条件2に加え、これでやっとこさ非ストアでもバインディング出来るようになる。
    ただ、ネットで調べてもこれらのインターフェースを使ってる例がほぼ皆無だったので苦労した。いろいろ試行錯誤して、ついにデータとTextBlockが連動した瞬間は声が出た。

今回のサンプルでやること

binding.png

Personクラスを作り、NameプロパティとHeightプロパティを装備する。

NameプロパティはTextBoxTextBlock両方のText属性にバインディングする。これによって、TextBoxに入力した内容がTextBlockに連動して表示される。

HeightプロパティはSliderValueTextBlockTextにそれぞれバインディングする。つまみを動かすと連動してつまみの位置に応じた数値が表示される。

※なお、単純に同じXAML内のコントロール同士を連動させるだけなら、わざわざランタイムクラス作らずともXAML内で{Binding ElementName=~}とかすれば可能。今回の記事はどちらかというと次回のコレクションのバインドへの布石。

サンプルコード

とりあえず以前の記事をベースにVSCodeでやっていくが、今回は主に追加点のみの説明にとどめる。
VSCodeとBuildToolsの記事で基本的な環境を作りつつ、C++/WinRTの記事でWinRTならではの環境構築をしていただき、下記のソース一式を用意してビルドすれば動くはず。
プロジェクト名は「cppbind」とする。

まずはプロジェクトファイルから。

cppbind.vcxproj
<Project DefaultTargets="Build" ToolsVersion="16.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Import Project="packages\Microsoft.Windows.CppWinRT.2.0.210304.5\build\native\Microsoft.Windows.CppWinRT.props"/>
    <ItemGroup>
        <ProjectConfiguration Include="Debug|Win32">
            <Configuration>Debug</Configuration>
            <Platform>Win32</Platform>
        </ProjectConfiguration>
        <ProjectConfiguration Include="Release|Win32">
            <Configuration>Release</Configuration>
            <Platform>Win32</Platform>
        </ProjectConfiguration>
        <ProjectConfiguration Include="Debug|x64">
            <Configuration>Debug</Configuration>
            <Platform>x64</Platform>
        </ProjectConfiguration>
        <ProjectConfiguration Include="Release|x64">
            <Configuration>Release</Configuration>
            <Platform>x64</Platform>
        </ProjectConfiguration>
    </ItemGroup>
    <Import Project="$(VCTargetsPath)\Microsoft.Cpp.default.props"/>
    <PropertyGroup>
        <CppWinRTOptimized>true</CppWinRTOptimized>
        <CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
        <CppWinRTGenerateWindowsMetadata>true</CppWinRTGenerateWindowsMetadata>
        <ConfigurationType>Application</ConfigurationType>
        <PlatformToolset>v142</PlatformToolset>
        <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
        <CharacterSet>Unicode</CharacterSet>
        <IntermediateOutputPath>obj\$(Configuration)\$(Platform)\</IntermediateOutputPath>
        <OutDir>bin\$(Configuration)\$(Platform)\</OutDir>
    </PropertyGroup>
    <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props"/>
    <ItemDefinitionGroup>
        <ClCompile>
            <PrecompiledHeader>Use</PrecompiledHeader>
            <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
            <AdditionalIncludeDirectories>$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
        </ClCompile>
        <Manifest>
            <AdditionalManifestFiles>app.manifest;%(AdditionalManifestFiles)</AdditionalManifestFiles>
        </Manifest>
    </ItemDefinitionGroup>
    <ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
        <ClCompile>
            <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
        </ClCompile>
        <Link>
            <GenerateDebugInformation>false</GenerateDebugInformation>
        </Link>
    </ItemDefinitionGroup>
    <ItemGroup>
        <ClCompile Include="*.cpp" Exclude="pch.cpp"/>
        <ClCompile Include="pch.cpp">
            <PrecompiledHeader>Create</PrecompiledHeader>
        </ClCompile>
    </ItemGroup>
    <ItemGroup>
        <ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
    </ItemGroup>
    <ItemGroup>
        <ClInclude Include="*.h"/>
    </ItemGroup>
    <ItemGroup>
        <ResourceCompile Include="*.rc"/>
    </ItemGroup>
    <ItemGroup>
        <Midl Include="*.idl"/>
    </ItemGroup>
    <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Targets"/>
    <Import Project="packages\Microsoft.Windows.CppWinRT.2.0.210304.5\build\native\Microsoft.Windows.CppWinRT.targets"/>
</Project>

まずは<CppWinRT~>三姉妹。これはおそらくランタイムクラスを自作するにあたって重要なオプションと思われる。詳しくは分からない。

次に、WinRTとは直接関係無いのだが、今回プリコンパイル済みヘッダーを使うことにした。
ここでは詳しく説明しないが、プリコンパイルの対象としてpch.hUseし、pch.cppを利用してプリコンパイル済みヘッダーをCreateする、みたいな設定になってると思う。

idlファイルがコンパイルされると中間ファイルフォルダにいろいろ自動生成されるが、その中の一つ「module.g.cpp」がコンパイル対象になり、こいつがpch.hを欲するので、<AdditionalIncludeDirectories>タグでプロジェクトフォルダをインクルード対象に追加してやる。

そしてidlファイルをコンパイルするゆえ、<Midl Include="*.idl"/>タグを追加している。

app.manifest
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
            <!-- Windows 10 -->
            <maxversiontested Id="10.0.18362.0"/>
            <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
        </application>
    </compatibility>
</assembly>

おまじない的マニフェスト。

Person.idl
namespace CppBind {
    runtimeclass Person : Windows.UI.Xaml.Data.ICustomPropertyProvider, Windows.UI.Xaml.Data.INotifyPropertyChanged {
        Person();
        String Name;
        Double Height;
        void FirePropertyChanged(String name);
    }
}

ランタイムクラスを作るためのidl。C#の書式に似てる。
PersonクラスとICustomPropertyProvider(を継承する)クラスは別々でも構わないが、兼ねることにした。
Person();はコンストラクタ。
String Name;とかDouble Height;はフィールド宣言っぽいけど{ get; set; }が省略されてるイメージで、これだけでプロパティ扱いになる。
インターフェースから継承するメソッドはここで宣言してはいけない。クラス内部で完結するメソッドやフィールドも、ここで書く必要は無い。

PersonPropaty.idl
namespace CppBind {
    [default_interface]
    runtimeclass PersonProperty : Windows.UI.Xaml.Data.ICustomProperty {
        PersonProperty(Boolean canRead, Boolean canWrite, String name, Windows.UI.Xaml.Interop.TypeName type);
    }
}

実質プロパティの読み書きを行うクラス。
コンストラクタ以外にメソッドを書かないと怒られるので、「これはうっかりじゃなくて意図的ですよ」というのを示すために、クラス名の前に[default_interface]を宣言しておく。

さて、ここで一旦ビルドして(いろいろ足りないのでコンパイルエラーが出るが気にしない。雛型ファイルをゲットするのが目的。)idlファイルをコンパイルすると、中間ファイルフォルダの中の「Generated Files/sources」フォルダの中に、それぞれのヘッダー「*.h」と実装部「*.cpp」の雛型が自動生成される。
それらをプロジェクトフォルダにコピーするなりして、追加フィールドやメソッド、実装の中身などを追記して完成させる。

Person.h
#pragma once
#include "Person.g.h"

// Note: Remove this static_assert after copying these generated source files to your project.
// This assertion exists to avoid compiling these generated source files directly.
//static_assert(false, "Do not compile generated C++/WinRT source files directly");

using namespace winrt;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Interop;

namespace winrt::CppBind::implementation {
    struct Person : PersonT<Person> {
        Person() = default;

        hstring Name();
        void Name(hstring const& value);
        double Height();
        void Height(double value);
        void FirePropertyChanged(hstring const& name);
        ICustomProperty GetCustomProperty(hstring const& name);
        ICustomProperty GetIndexedProperty(hstring const& name, TypeName const& type);
        hstring GetStringRepresentation();
        TypeName Type();
        event_token PropertyChanged(PropertyChangedEventHandler const& handler);
        void PropertyChanged(event_token const& token) noexcept;

        private:
        ICustomProperty m_nameproperty = nullptr;
        ICustomProperty m_heightproperty = nullptr;
        event<PropertyChangedEventHandler> m_propertyChanged;
        std::unordered_map<hstring, std::tuple<bool, bool, TypeName, ICustomProperty> > m_propmap = {
            {L"Name", {true, true, xaml_typename<hstring>(), m_nameproperty}},
            {L"Height", {true, true, xaml_typename<double>(), m_heightproperty}},
        };
        template<typename T> T GetValue(hstring const& name);
        void SetValue(hstring const& name, IInspectable const& value);
    };
}

namespace winrt::CppBind::factory_implementation {
    struct Person : PersonT<Person, implementation::Person> {
    };
}

Person.hの雛型にいろいろ追加して完成させたもの。
なお自動生成された雛型の冒頭コメントの直下にはstatic_assertが書いてあって、そのままだとコンパイルエラーになってしまうので、コメントアウトするなり消すなりしておく。

雛型の時点でidlの時に宣言していなかったGetCustomPropertyなど、インターフェースから継承されたメソッドが自動的に追加されている。
private:の下はここで初めて追加しているが、クラスで保持しておくICustomPropertyや、のちのちプロパティが増えてきたときに冗長にならないためのmapやメソッド等である。
余談だが、unordered_maptupleって便利やねー。

Person.cpp
#include "pch.h"
#include "Person.h"
#include "Person.g.cpp"

// Note: Remove this static_assert after copying these generated source files to your project.
// This assertion exists to avoid compiling these generated source files directly.
//static_assert(false, "Do not compile generated C++/WinRT source files directly");

using namespace winrt;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Interop;

namespace winrt::CppBind::implementation
{
    hstring Person::Name() {
        return GetValue<hstring>(L"Name");
    }

    void Person::Name(hstring const& value) {
        SetValue(L"Name", box_value(value));
    }

    double Person::Height() {
        return GetValue<double>(L"Height");
    }

    void Person::Height(double value) {
        SetValue(L"Height", box_value(value));
    }

    void Person::FirePropertyChanged(hstring const& name) {
        m_propertyChanged(*this, PropertyChangedEventArgs(name));
    }

    ICustomProperty Person::GetCustomProperty(hstring const& name) {
        if (m_propmap.find(name) == m_propmap.end()) return nullptr;
        if (std::get<3>(m_propmap.at(name)) == nullptr) {
            std::get<3>(m_propmap.at(name)) = CppBind::PersonProperty(std::get<0>(m_propmap.at(name)), std::get<1>(m_propmap.at(name)), name, std::get<2>(m_propmap.at(name)));
        }
        return std::get<3>(m_propmap.at(name));
    }

    ICustomProperty Person::GetIndexedProperty(hstring const& name, TypeName const& type) {
        return nullptr;
    }

    hstring Person::GetStringRepresentation() {
        return L"Person";
    }

    TypeName Person::Type() {
        return xaml_typename<CppBind::Person>();
    }

    winrt::event_token Person::PropertyChanged(PropertyChangedEventHandler const& handler) {
        return m_propertyChanged.add(handler);
    }

    void Person::PropertyChanged(winrt::event_token const& token) noexcept {
        m_propertyChanged.remove(token);
    }

    template<typename T> T Person::GetValue(hstring const& name) {
        if (GetCustomProperty(name) == nullptr) return T();
        return unbox_value<T>(GetCustomProperty(name).GetValue(*this));
    }

    void Person::SetValue(hstring const& name, IInspectable const& value) {
        if (GetCustomProperty(name) == nullptr) return;
        GetCustomProperty(name).SetValue(*this, box_value(value));
    }
}

Personクラスの実装部分。
ゴチャゴチャ自前メソッドを追加してしまっているが、のちのちプロパティが増えてもm_propmapの中身とプロパティ読み書き部分だけ追加すれば済むように作ってある。

PersonProperty.h
#pragma once
#include "PersonProperty.g.h"

// Note: Remove this static_assert after copying these generated source files to your project.
// This assertion exists to avoid compiling these generated source files directly.
//static_assert(false, "Do not compile generated C++/WinRT source files directly");

using namespace winrt;
using namespace Windows::UI::Xaml::Interop;
using namespace Windows::Foundation;

namespace winrt::CppBind::implementation
{
    struct PersonProperty : PersonPropertyT<PersonProperty> {
        PersonProperty() = delete;

        PersonProperty(bool canRead, bool canWrite, hstring const& name, TypeName const& type);
        TypeName Type();
        hstring Name();
        IInspectable GetValue(IInspectable const& target);
        void SetValue(IInspectable const& target, IInspectable const& value);
        IInspectable GetIndexedValue(IInspectable const& target, IInspectable const& index);
        void SetIndexedValue(IInspectable const& target, IInspectable const& value, IInspectable const& index);
        bool CanWrite();
        bool CanRead();

        private:
        hstring m_namevalue;
        double m_heightvalue;
        bool m_CanRead;
        bool m_CanWrite;
        hstring m_Name;
        TypeName m_Type;
        std::unordered_map<hstring, int> m_propnumber = {
            {L"Name", 1},
            {L"Height", 2}
        };
    };
}

namespace winrt::CppBind::factory_implementation {
    struct PersonProperty : PersonPropertyT<PersonProperty, implementation::PersonProperty> {
    };
}

ICustomPropertyを継承したクラスの方のヘッダー。
hstring m_namevaluedouble m_heightvalueが、実際にPersonのプロパティの中身が保持される場所。これはPersonクラスだろうが、はたまた別のクラスだろうがどこにあっても良いのだが、ここに置くのが一番素直かなと思ったのでそうした。
プロパティが増えた場合、m_propnumberの中に追加する仕組みにしている。

そうそう、このクラスのNameプロパティはICustomPropertyから継承されたもので、PersonクラスのNameプロパティとは別物である。名前が一緒だからややこしい…。

PersonProperty.cpp
#include "pch.h"
#include "PersonProperty.h"
#include "PersonProperty.g.cpp"

// Note: Remove this static_assert after copying these generated source files to your project.
// This assertion exists to avoid compiling these generated source files directly.
//static_assert(false, "Do not compile generated C++/WinRT source files directly");

using namespace winrt;
using namespace Windows::UI::Xaml::Interop;
using namespace Windows::Foundation;

namespace winrt::CppBind::implementation {
    PersonProperty::PersonProperty(bool canRead, bool canWrite, hstring const& name, TypeName const& type) :
        m_CanRead(canRead),
        m_CanWrite(canWrite),
        m_Name(name),
        m_Type(type)
    {}

    TypeName PersonProperty::Type() {
        return m_Type;
    }

    hstring PersonProperty::Name() {
        return m_Name;
    }

    IInspectable PersonProperty::GetValue(IInspectable const& target) {
        switch (m_propnumber.at(m_Name)) {
            case 1: //Name
            return box_value(m_namevalue);
            break;

            case 2: //Height
            return box_value(m_heightvalue);
            break;
        }
        return nullptr;
    }

    void PersonProperty::SetValue(IInspectable const& target, IInspectable const& value) {
        auto person = target.as<CppBind::Person>();
        switch (m_propnumber.at(m_Name)) {
            case 1: //Name
            m_namevalue = unbox_value<hstring>(value);
            break;

            case 2: //Height
            m_heightvalue = unbox_value<double>(value);
            break;

            default:
            return;
        }
        person.FirePropertyChanged(m_Name);
    }

    IInspectable PersonProperty::GetIndexedValue(IInspectable const& target, IInspectable const& index) {
        return nullptr;
    }

    void PersonProperty::SetIndexedValue(IInspectable const& target, IInspectable const& value, IInspectable const& index) {
    }

    bool PersonProperty::CanWrite() {
        return m_CanWrite;
    }

    bool PersonProperty::CanRead() {
        return m_CanRead;
    }
}

ICustomPropertyを継承したクラスの実装部分。ほぼGetValueSetValueの2つのメソッドだけが本体。
プロパティ名(文字列)をたよりにswitchで分岐出来るように作った。
なおバインドしてるXAMLのコントロールが書き変わると、ここのSetValueに直接飛んでくるようなので、ここでPersonクラスのFirePropertyChangedを呼ぶようにしている。

Resource.h
//
#define IDR_XAML_MAIN 101

今回もXAMLスクリプトはリソースに保持する。

cppbind.rc
#include "resource.h"

IDR_XAML_MAIN XAMLFILE "main.xaml"

リソーススクリプト。XAMLだけ。

pch.h
#include <windows.h>
#include <windows.ui.xaml.hosting.desktopwindowxamlsource.h>    //DesktopWindowXamlSource

#pragma push_macro("GetCurrentTime")
#pragma push_macro("TRY")
#undef GetCurrentTime
#undef TRY

#include <winrt/windows.ui.xaml.hosting.h>  //WindowsXamlManager
#include <winrt/windows.ui.xaml.markup.h>   //XamlReader
#include <winrt/windows.ui.xaml.controls.h>
#include <winrt/windows.ui.xaml.data.h>    //Binding
#include <winrt/windows.ui.xaml.interop.h>  //xaml_typename
#include <winrt/windows.foundation.h>   //box_value
#include <winrt/windows.security.cryptography.h>    //CryptographicBuffer

#pragma pop_macro("TRY")
#pragma pop_macro("GetCurrentTime")

プリコンパイル済みヘッダー。
あとで気付いたが、pch.hの内容を変更した場合は通常ビルドじゃなくてリビルドした方が良いっぽい。(常識?)
何故かコンパイルが途中で止まってしまい進まなくなる現象はそのパターンな気がする。
そういう意味でも、pch.hは基本編集しない方が良いようだ。(頻繁に編集する必要が無いような作り方をする。)

pch.cpp
#include "pch.h"

プリコンパイル済みヘッダーを生成するためのcpp。

main.xaml
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <TextBox Name="textbox1" Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    <TextBlock Name="textblock1" Text="{Binding Path=Name, Mode=OneWay}"/>
    <Slider Name="slider1" Minimum="0" Maximum="300" Value="{Binding Path=Height, Mode=TwoWay}"/>
    <TextBlock Name="textblock2" Text="{Binding Path=Height, Mode=OneWay}"/>
</StackPanel>

XAMLファイル。

main.cpp
#include "pch.h"
#include "Person.h"

#include "resource.h"

using namespace winrt;
using namespace Windows::UI;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Hosting;
using namespace Windows::UI::Xaml::Markup;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::Foundation;
using namespace Windows::Security::Cryptography;
using namespace CppBind;

//名前からコントロールを特定するテンプレート(今回は未使用)
template<typename T> T Element(const wchar_t *name) {
    return _xamlroot.Content().as<Panel>().FindName(name).as<T>();
};

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);

HWND _hWnd;
HWND _hWndXamlIsland = nullptr;
XamlRoot _xamlroot = { nullptr };

Person _person;

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) {
    //ウインドウクラス登録
    const wchar_t szWindowClass[] = L"Win32DesktopApp";
    WNDCLASSEX windowClass = {};
    windowClass.cbSize = sizeof(WNDCLASSEX);
    windowClass.lpfnWndProc = WindowProc;
    windowClass.hInstance = hInstance;
    windowClass.lpszClassName = szWindowClass;
    windowClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    windowClass.hIconSm = ::LoadIcon(nullptr, IDI_APPLICATION);
    if (::RegisterClassEx(&windowClass) == 0) {
        ::MessageBox(nullptr, L"Windows registration failed!", L"Error", MB_OK);
        return 0;
    }

    //ウインドウ作成
    _hWnd = ::CreateWindow(
        szWindowClass,
        L"Windows c++ Win32 Desktop App",
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT, 320, 640,
        nullptr,
        nullptr,
        hInstance,
        nullptr
    );
    if (_hWnd == nullptr) {
        ::MessageBox(nullptr, L"Call to CreateWindow failed!", L"Error", MB_OK);
        return 0;
    }

    //C++/WinRTのおまじない
    init_apartment(apartment_type::single_threaded);
    WindowsXamlManager winxamlmanager = WindowsXamlManager::InitializeForCurrentThread();

    //XAMLホスト準備
    DesktopWindowXamlSource desktopSource;
    auto interop = desktopSource.as<IDesktopWindowXamlSourceNative>();
    check_hresult(interop->AttachToWindow(_hWnd));
    interop->get_WindowHandle(&_hWndXamlIsland);

    //XAMLホストのサイズをメインウインドウに合わせる
    RECT rcClient;
    ::GetClientRect(_hWnd, &rcClient);
    ::SetWindowPos(_hWndXamlIsland, nullptr, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, SWP_SHOWWINDOW);

    //リソースからXAMLコードを取り出してXAMLホストにセット
    auto hResInfo = ::FindResource(hInstance, MAKEINTRESOURCE(IDR_XAML_MAIN), L"XAMLFILE");
    auto res_size = ::SizeofResource(hInstance, hResInfo);
    std::vector<uint8_t> xaml_array(res_size, 0);
    auto hGlobal = ::LoadResource(hInstance, hResInfo);
    char *xaml_bin = (char *)::LockResource(hGlobal);
    ::memcpy_s(xaml_array.data(), res_size, xaml_bin, res_size);
    auto xaml_buf = CryptographicBuffer::CreateFromByteArray(xaml_array);
    auto xamlstr = CryptographicBuffer::ConvertBinaryToString(BinaryStringEncoding::Utf8, xaml_buf);
    auto xamlContainer = XamlReader::Load(xamlstr).as<Panel>();
    desktopSource.Content(xamlContainer);
    _xamlroot = xamlContainer.XamlRoot();

    xamlContainer.DataContext(_person);

    _person.Name(L"Cube Cube");
    _person.Height(160);

    //メインウインドウ表示
    ::ShowWindow(_hWnd, nShowCmd);
    ::UpdateWindow(_hWnd);

    //メッセージループ
    MSG msg = {};
    while (::GetMessage(&msg, nullptr, 0, 0)) {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }

    return 0;
}

//ウインドウプロシージャ
LRESULT CALLBACK WindowProc(HWND hWnd, UINT messageCode, WPARAM wParam, LPARAM lParam) {
    RECT rcClient;

    switch (messageCode) {
        case WM_CREATE:
            //return 0;
            break;

        case WM_DESTROY:
            ::PostQuitMessage(0);
            break;

        case WM_SIZE:   //メインウインドウのサイズが変わったらXAMLホストも追従
            ::GetClientRect(hWnd, &rcClient);
            ::SetWindowPos(_hWndXamlIsland, nullptr, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, SWP_SHOWWINDOW);

            return 0;
            break;

        default:
            return ::DefWindowProc(hWnd, messageCode, wParam, lParam);
            break;
    }

    return 0;
}

アプリのメイン。
データバインディングに関わる部分はほんのちょっとしかない。
あっけない。

なお、バインディングの設定をXAML側でやらずにコード側で記述することもできる。
その場合は、下記のようなコードになる。

auto proppath = PropertyPath(L"Name");

auto binding1 = Binding();
binding1.Source(_person);
binding1.Path(proppath);
binding1.Mode(BindingMode::TwoWay);
binding1.UpdateSourceTrigger(UpdateSourceTrigger::PropertyChanged);
Element<TextBox>(L"textbox1").SetBinding(TextBox::TextProperty(), binding1);

auto binding2 = Binding();
binding2.Source(_person);
binding2.Path(proppath);
binding2.Mode(BindingMode::OneWay);
Element<TextBlock>(L"textblock1").SetBinding(TextBlock::TextProperty(), binding2);

Nameプロパティだけでもこんだけ。
うーん、これならXAMLの方に書いた方が良いな…。

感想

とりあえず、非ストアアプリなC++/WinRTでもデータバインディングが可能なことは分かった。

あとはコレクションのバインディングやなー。データバインディングはコレクションでやってこそ本領発揮すると思うし…。
と思ってたら割とすんなり実現した。次の記事で紹介する。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1