LoginSignup
1
1

More than 3 years have passed since last update.

C++/WinRT(非ストアアプリ)でコレクションのデータバインディング

Last updated at Posted at 2021-03-22

前回の最後に、コレクションもバインディングしたいと述べた。
さぞかし難航するんだろうなーと思ってたら、すんなり出来た。

中に入れる要素は前回のようにきちんと作らないといけないが、ガワのコレクションはIObservableVectorをそのまま使えば良かった。
ただしコレクションの初期化はsingle_threaded_observable_vectorでやる必要がある。

非ストアアプリにおいて、コレクションのデータバインディング条件をまとめるとこう。

  1. バインディング可能な要素をIObservableVectorで包んでコレクションを宣言
  2. 専用の関数テンプレートsingle_threaded_observable_vectorでコレクションを初期化

これだけ。

binding_collection.png

サンプルアプリは、右上のボタンをクリックするたびにリストの項目が増えていくというものにした。

サンプルコード

前回の記事と異なるのは「pch.h」「main.xaml」「main.cpp」だけなので、その3つだけ記載。
手抜きで申し訳無い。

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.controls.primitives.h>  //Click
#include <winrt/windows.ui.xaml.data.h>    //Binding
#include <winrt/windows.ui.xaml.interop.h>  //xaml_typename
#include <winrt/windows.foundation.collections.h>   //IObservableVector
#include <winrt/windows.security.cryptography.h>    //CryptographicBuffer

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

IObservableVectorを使うのでwindows.foundation.collections.hのインクルードが増えたが、それにより上位のwindows.foundation.hはここでインクルードする必要が無くなった。

main.xaml
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Button Name="button1" Content="ボタン" HorizontalAlignment="Right"/>
    <ListView Name="list1" Grid.Row="1">
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel VerticalAlignment="Center">
                    <TextBlock Text="{Binding Name}" FontSize="16"/>
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>

今回はルートを<StackPanel>じゃなくて<Grid>にしてみた。
(ListViewをスクロールさせたかったため。)
基本的には最上位は<Grid>の方が良さそう。

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

#include "resource.h"

using namespace winrt;
using namespace Windows::UI::Xaml;  //XamlRoot
using namespace Windows::UI::Xaml::Hosting;  //WindowsXamlManager
using namespace Windows::UI::Xaml::Markup;   //XamlReader
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::Foundation;    //IInspectable
using namespace Windows::Foundation::Collections;   //IObservableVector
using namespace Windows::Security::Cryptography;    //CryptographicBuffer
using namespace CppBind;    //Person

//名前からコントロールを特定するテンプレート
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);
void OnButtonClick(IInspectable const &, RoutedEventArgs const &);

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

//コレクション宣言
IObservableVector<Person> _persons;

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.data()).as<Grid>();
    desktopSource.Content(xamlContainer);
    _xamlroot = xamlContainer.XamlRoot();

    //コレクション初期化
    _persons = single_threaded_observable_vector<Person>();

    Element<ListView>(L"list1").ItemsSource(_persons);

    Person person;
    person.Name(L"Cube Cube");
    _persons.Append(person);

    //ボタンにイベントハンドラ登録
    Element<Button>(L"button1").Click({ &OnButtonClick });

    //メインウインドウ表示
    ::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;
}

//ボタンクリックのイベントハンドラ
void OnButtonClick(IInspectable const &sender, RoutedEventArgs const &e) {
    Person person;
    person.Name(L"Mr. Append");
    _persons.Append(person);
}

気を付けるのは、コレクション(IObservableVector)をsingle_threaded_observable_vector<T>()で初期化するのを忘れないこと。

コレクションの中の任意の要素の任意のプロパティを変更してみたら、ちゃんとListViewの方にも反映されたので一安心。

1
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
1
1