C++/WinRTでUWPその3　データバインディングその1 xamlでバインドできるView Model

#1.データバインディングとは
・まずはMSのページをよく読んでください。よく読んでも私にはよくわからなかったですorz
[データ バインディングの概要](https://docs.microsoft.com/ja-jp/windows/uwp/data-binding/data-binding-quickstart)
[データ バインディングの詳細](https://docs.microsoft.com/ja-jp/windows/uwp/data-binding/data-binding-in-depth)

・個人的に一番参考になったのはC#ですがこのページでした。
[marikoootaの日記 データ・バインディングを理解する](http://marikooota.hatenablog.com/entry/2017/05/30/002059)

・データバインディングとは、私の中では「コントロールにデータを結びつける仕組み」です。
・「データが変化したらコントロールに通知する」こともできます。そこまで実装したら、QTでいうシグナル/スロット、もしくはデザインパターンで言うオブザーバーパターンのようなものにまですることができます。

・今回はコントロール（TextBlock）にバインドできるデータ（ViewModel）を作成し、xamlでバインドするところまでやります。
・View Modelは「.idl」「.h」「.cpp」の3ファイルからなり、.idlではC#風のコンストラクタとプロパティーの宣言をし、.hと.cppでC++の実装をするというような感じとなります。

・MSのwebページでいうと[XAML コントロール: C++/WinRT プロパティへのバインド](https://docs.microsoft.com/ja-jp/windows/uwp/cpp-and-winrt-apis/binding-property)の一部です。次回はデータが変化したらコントロールに通知する箇所をやります。


#2.プロジェクトの作成
・前回と同じようにBlank App(C++/WinRT)を選び、今回は「bind_1」という名前でプロジェクトを作成します。
・MainPage.xamlからmyButton、MainPage.hとMainPage.cppからイベントハンドラを削除し、まっさらにしてください。


#3.ViewModelの作成
##3-1.StringViewModelの作成（バインドできるViewModelの作成）
・まずはStringViewModelという名前のViewModelを作成し、それをTextBlockへバインドして表示されるか確認します。

・それでは実装に入ります。プロジェクト→モジュールの追加から
![2-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131051/aa8c84c1-dce1-e91f-a0a3-b367e37c62c6.png)
<br>
・View Modelを選び、名前を「StringViewModel」にして追加します。
![2-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131051/b1b6f31f-218e-7871-0080-e0c2a524af55.png)
<br>
・すると、「StringViewModel.idl」「StringViewModel.h」「StringViewModel.cpp」が追加されます。
![2-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131051/903621ee-ec48-ef68-85c9-8d31b2689032.png)
<br>
##3-2.StringViewModel.idlの編集
・[Windows.UI.Xaml.Data.Bindable]に変更します。
・StringViewModel.idlを以下に変更します。MyPropertyとかいらないので削除し、DataStringを追加します。
・このDataStringが（C#でいうプロパティー？）ゲッター/セッターだよという宣言です。
・StringViewModel()はコンストラクタでそのまま残しておきます。

```xml:StringViewModel.idl（変更前）
    [bindable]
    [default_interface]
    runtimeclass StringViewModel 
    {
        StringViewModel();
        Int32 MyProperty;
    }
```

```xml:StringViewModel.idl（変更後）
namespace bind_1
{
    [Windows.UI.Xaml.Data.Bindable]
    runtimeclass StringViewModel 
    {
        StringViewModel();
        String DataString;
    }
}
```

<br>
##3-3.StringViewModel.hの編集
・StringViewModel.hを以下に変更します。いらないMyMyPropertyを削除し、idlで追加したDataStringとデータメンバを追加します。C#なら自動実装プロパティーとかあって楽なのですが、C++ではゲッターとセッターをゴリゴリ書きます。.g.hは気にしないで下さい。

```C++:StringViewModel.h（変更前）
#pragma once

#include "StringViewModel.g.h"

namespace winrt::bind_1::implementation
{
    struct StringViewModel : StringViewModelT<StringViewModel>
    {
        StringViewModel() = default;

        int32_t MyProperty();
        void MyProperty(int32_t value);
    };
}

namespace winrt::bind_1::factory_implementation
{
    struct StringViewModel : StringViewModelT<StringViewModel, implementation::StringViewModel>
    {
    };
}
```

```C++:StringViewModel.h（変更後）
#pragma once

#include "StringViewModel.g.h"

namespace winrt::bind_1::implementation
{
    struct StringViewModel : StringViewModelT<StringViewModel>
    {
        //ここはdefaultを削除
        StringViewModel();
        //ここ追加
        StringViewModel(const winrt::hstring value);

        //ここ追加　セッターですね
        void DataString(winrt::hstring const& value);
        //こちらはゲッター
        winrt::hstring DataString();

    private:
        //ここ追加
        winrt::hstring m_DataString;
    };
}

namespace winrt::bind_1::factory_implementation
{
    struct StringViewModel : StringViewModelT<StringViewModel, implementation::StringViewModel>
    {
    };
}
```

<br>
・StringViewModel.cppも以下に変更します。こちらも.g.cppは気にしないで下さい。勝手に作っているファイルです。でもちょくちょくここが原因でエラーになるんですよねorz

```C++:StringViewModel.cpp（変更前）
#include "pch.h"
#include "StringViewModel.h"
#if __has_include("StringViewModel.g.cpp")
#include "StringViewModel.g.cpp"
#endif

namespace winrt::bind_1::implementation
{
    int32_t StringViewModel::MyProperty()
    {
        throw hresult_not_implemented();
    }

    void StringViewModel::MyProperty(int32_t /*value*/)
    {
        throw hresult_not_implemented();
    }
}
```

```C++:StringViewModel.cpp（変更後）
#include "pch.h"
#include "StringViewModel.h"
#if __has_include("StringViewModel.g.cpp")
#include "StringViewModel.g.cpp"
#endif

namespace winrt::bind_1::implementation
{
    //これ以降追加
    StringViewModel::StringViewModel() : m_DataString(L"First String")
    {
    }
    StringViewModel::StringViewModel(const winrt::hstring value)
    {
        m_DataString = value;
    }

    void StringViewModel::DataString(winrt::hstring const& value)
    {
        m_DataString = value;
    }
    winrt::hstring StringViewModel::DataString()
    {
        return m_DataString;
    }
}
```
<br>
・MainPage.idlを編集して、StringViewModel.idlをインポートします。importは＃無しで合ってます。#importだとエラーが出ます。importの最後にセミコロンも必要です。#importディレクティブと紛らわしいので注意して下さい。何度間違えてエラーを起こしたことか・・・
・StringViewModelのオブジェクトのSViewModelもここで宣言します。

```C++:MainPage.idl（変更前）
namespace bind_1
{
    [default_interface]
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        Int32 MyProperty;
    }
}
```

```C++:MainPage.idl（変更後）
import "StringViewModel.idl";

namespace bind_1
{
    [default_interface]
    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        Int32 MyProperty;

        StringViewModel SViewModel{ get; };
    }
}
```

<br>
・MainPage.hを編集して、StringViewModel.hをインクルードして、SviewModel()とm_SViewModelを追加します。

```C++:MainPage.h（変更前）
#pragma once

#include "MainPage.g.h"

namespace winrt::bind_1::implementation
{
    struct MainPage : MainPageT<MainPage>
    {
        MainPage();

        int32_t MyProperty();
        void MyProperty(int32_t value);
}

namespace winrt::bind_1::factory_implementation
{
    struct MainPage : MainPageT<MainPage, implementation::MainPage>
    {
    };
}
```

```C++:MainPage.h（変更後）
#pragma once

#include "MainPage.g.h"
#include "StringViewModel.h"

namespace winrt::bind_1::implementation
{
    struct MainPage : MainPageT<MainPage>
    {
        MainPage();

        int32_t MyProperty();
        void MyProperty(int32_t value);

        //ここ追加
        bind_1::StringViewModel SViewModel();

    private :
        //ここ追加
        bind_1::StringViewModel m_SViewModel{ nullptr };
    };
}

namespace winrt::bind_1::factory_implementation
{
    struct MainPage : MainPageT<MainPage, implementation::MainPage>
    {
    };
}
```
<br>
・MainPage.cppを編集して、SViewModel()を実装します。SViewModelはm_SViewModelを返すだけです。また、コンストラクタでm_SViewModelを作成します。

```C++:MainPage.cpp（変更前）
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"

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

namespace winrt::bind_1::implementation
{
    MainPage::MainPage()
    {
        InitializeComponent();
    }

    int32_t MainPage::MyProperty()
    {
        throw hresult_not_implemented();
    }

    void MainPage::MyProperty(int32_t /* value */)
    {
        throw hresult_not_implemented();
    }
}
```

```C++:MainPage.cpp（変更後）
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"

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

namespace winrt::bind_1::implementation
{
    MainPage::MainPage()
    {
        //ここ追加
        m_SViewModel = winrt::make<bind_1::implementation::StringViewModel>();
        InitializeComponent();
    }

    int32_t MainPage::MyProperty()
    {
        throw hresult_not_implemented();
    }

    void MainPage::MyProperty(int32_t /* value */)
    {
        throw hresult_not_implemented();
    }
}

//ここ追加
bind_1::StringViewModel winrt::bind_1::implementation::MainPage::SViewModel()
{
    return m_SViewModel;
}
```
<br>
・MainPage.xamlを変更して、ボタン一つとtextBlock一つを作りハンドラーを追加し、textBlockにSviewModelをバインドします。

```XML:MainPage.xaml（変更前）
<Page
    x:Class="bind_1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:bind_1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

</Page>

```

```XML:MainPage.xaml（変更後）
<Page
    x:Class="bind_1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:bind_1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button x:Name="button1" Height="30" Width="300" Content="click" Click="button1_Click"></Button>
        <TextBlock x:Name="text1" Text="{x:Bind Path=SViewModel.DataString, Mode=OneWay}" Height="300" Width="300" Margin="10,10,10,10"></TextBlock>

    </StackPanel>
</Page>

```


・ここまで終わったらビルド→実行してみて下さい。ボタンとtextblockへ「First String」が表示されているはずです。MainPage.cppでm_SViewModelを作成するときにデフォルトコンストラクタを呼んでいるので「First String」が表示されます
![2-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131051/386bea5a-ca3d-6048-e6df-c306c03f2121.png)

・MainPage.cppでm_SViewModel = winrt::make＜bind_1::implementation::StringViewModel＞(L"Rewritten on the main page");と変更すると「Rewritten on the main page」が表示されます。

・xamlでバインドしているのは以下の部分です。
＜TextBlock x:Name="text1" Text="{x:Bind Path=SViewModel.DataString, Mode=OneWay}" Height="300" Width="300" Margin="10,10,10,10"＞＜/TextBlock＞
text1という名前のTextBlockのTextにSViewModel.DataStringをバインドするよという宣言ですね。

・これでコントロールにバインドできるViewModelができました。次は[StringVireModelの変更通知部分](https://qiita.com/lilac0011/items/2c3f1a31fd5a91345647)を作成します。


[もくじへ](https://qiita.com/lilac0011/items/f53f7401ea7da31e3a0b)