Window

ウィンドウにタイトルを付けてみる。

Window Class (System.Windows) | Microsoft Docs

空のプロジェクト
空のプロジェクト

今回もあえて空のプロジェクト。

MainClass.cs
MainClass.cs

MainClass.csを追加。

WindowsBase
WindowsBase

PresentationCore
PresentationCore

PresentationFramework
PresentationFramework

System
System

この辺を追加するのは前回と一緒。

MainClass.csは、

WindowオブジェクトwindowのTitleプロパティにタイトル"Window"をセットする。
あとはapplication.Runにwindowを渡す。

実行すると、

タイトル"Window"
タイトル"Window"

タイトルが"Window"になってる。

Windows アプリケーション
Windows アプリケーション

コンソール アプリケーションになってるからWindows アプリケーションに。

これでOK
これでOK

これでOK。

Sample/wpf/Window/Window/src/Window at master · bg1bgst333/Sample · GitHub

Application

ここからは、WPFについて扱っていく。

WPF とは何ですか? - Visual Studio | Microsoft Docs

WPFは、XAMLでUIを構築していくタイプのC#.NETのプロジェクト形式。

まずは、ApplicationクラスとWindowクラスだけで、簡単なウィンドウを表示するサンプルを作ってみる。

Application Class (System.Windows) | Microsoft Docs

空のプロジェクト
空のプロジェクト

空のプロジェクトで、

作成
作成

作成したら、

追加
追加

メニューから、新しい項目を追加。

コードファイル
コードファイル

コードファイルからMainClass.csを追加。

参照の追加
参照の追加

参照の追加。

WindowsBase
WindowsBase

WindowsBase。

PresentationCore
PresentationCore

PresentationCore。

PresentationFramework
PresentationFramework

PresentationFramework。

System
System

そしてSystem。

MainClass.csは、

Applicationオブジェクトのapplicationを作成し、application.RunにWindowオブジェクトを渡すと、Windowオブジェクトが表示されるアプリケーションが実行される。

コンソール上
コンソール上

そのまま実行すると、コンソール上でウィンドウが表示されるような形になってしまう。

コンソール アプリケーション
コンソール アプリケーション

プロジェクトのプロパティから、

Windows アプリケーション
Windows アプリケーション

Windows アプリケーション」に変更して、実行すると、

ウィンドウのみ表示
ウィンドウのみ表示

ウィンドウのみ表示される。

Sample/wpf/Application/Application/src/Application at master · bg1bgst333/Sample · GitHub

ReactiveX

ReactiveX(リアクティブプログラミング)は、.NETから生まれたパターンで、何らかの処理をした後にイベント通知を発行するObservableと、Observableを監視しイベント通知を受け取って処理するObserver、で構成されている。

ReactiveXを説明できるようになるシリーズその1 - ObserverとObservable - Qiita
Reactive Extensions入門「まとめ」 - かずきのBlog@hatena
Rx初心者がカウントアプリにRxを導入して全てをストリームに感じるまで - Qiita
ObserverパターンとHelloWorldからはじめるRxJava - Qiita
何となくRxJavaを使ってるけど正直よく分かってない人が読むと良さそうな記事・基礎編 - Qiita

超簡易的なReactiveXをC++で作ってみる。

まずは、interface_observable。
subscribeとnotifyだけ。

次に、interface_observer。
completed、error、nextがある。

interface_observableを継承したclass_custom_observableでは、オブザーバーリストobservers_を持つ。

subscribeでobserverを追加、notifyでobservers_の各オブザーバーに順次nextを実行していく。

interface_observerを継承したclass_custom_observerはメンバ関数の実装だけ。

nextだけ渡されたvalueを出力。他は今回実装していない。

main.cppでは、

observer1とobserver2の2つと、observableを1つ用意。
observableにobserver1とobserver2を登録。
notifyに"ABCDE"を渡して実行。

$ g++ -o main main.cpp custom_observable.cpp  custom_observer.cpp -std=c++11
$ ./main
value = ABCDE
value = ABCDE
$

このように通知される。

Sample/designpattern/reactive_x/reactive_x/src/reactive_x at master · bg1bgst333/Sample · GitHub

MVC

MVCは、アーキテクチャパターンの一種で、役割をM(Model)とV(View)とC(Controller)に分ける方法。

MVCモデルの問題点を解決するPMモデルとMVPモデル - GeekなNooblog

C++で作るので、まずは通知機能を実装する。

observer.hを作成し、

interface_observerのメンバに純粋仮想関数set_subjectとchangedを定義。

subject.hには、

interface_subjectメンバに純粋仮想関数set_observerとnotifyを定義。

これらのメンバ関数が重要な通知機能を担う。

さて、値を受け取るクラスの基底クラスとなるclass_viewだが、

view.hで、interface_subjectとinterface_observerを継承、加えてset_modelを定義。

set_observerでobserverをメンバに持つのと同時に、observer_のset_subjectで自身(this)を指定している。

notifyでメッセージを出力した後、observer_のchangedを呼び出す。

こちらはsubjectをメンバに格納するだけ。

ここのchangedもメッセージ出力だけ。

渡されたmodelのオブザーバーとして自身(this)をセットする。

さて、class_viewからの通知を受け取るclass_controllerは、

こちらはinterface_observerだけ継承。
あとset_viewを持っている。

subjectをメンバにセットするだけ。

メッセージ出力。

渡されたviewのオブザーバーに自身(this)をセット。

さて、class_controllerからメソッドを呼びだすclass_modelだが、

interface_subjectを継承。
実はここにはコントローラから呼び出すメソッドは定義していない。
ここまでのclass_view、class_model、class_controllerには、最低限の通知機能しか用意していない。
それぞれを継承することでMVCを実現できる。
だから、ser_observerとnotifyしかない。

observerをメンバにし、observer_のset_subjectに自身(this)をセット。

メッセージ出力とobserver_のchanged。

さて、class_viewを継承して、class_custom_viewを作成する。

class_custom_modelのオブジェクトポインタと、x_, y_, そして計算結果result_をメンバに持つ。

custom_modelはメンバにセット。
set_modelもしておく。

changedでメッセージを出力した後に、custom_modelからx_, y_, そしてresult_を取得し、メンバにセット。

さて、class_controllerを継承した、class_custom_controllerの実装は、

class_custom_viewとclass_custom_modelのポインタを持ってるので、それらをセットするメンバ関数
そしてchanged。

メッセージ出力、class_custom_viewのメンバの値の出力、そしてclass_custom_modelのfuncを呼び出す。
引数にはcustom_view_のx_, y_を渡す。

custom_viewをメンバにセットし、set_viewもする。

custom_modelをメンバにセットし、custom_view_のset_custom_modelにもセットする。

さて、class_modelを継承した、class_custom_modelの実装は、

x_, y_, result_を取得するメンバ関数と、class_custom_controllerから呼び出される関数func。

funcで引数をx_, y_に格納し、計算結果をresult_に格納、さらにnotifyを呼ぶ。

最後にmain.cppは、

各オブジェクトを生成したら、custom_controller_ptrにcustom_view_ptrとcustom_model_ptrをセットし、custom_view_ptrには値をセット。
custom_view_ptrのnotifyで値が確定するので、これをきっかけに処理が実行され、結果がcustom_view_ptrに反映される。
結果を出力したら、各オブジェクトを解放。

実行すると、

$ vi main.cpp
$ g++ -o main *.cpp
$ ./main
class_view::notify()
class_custom_controller::changed()
custom_view_->x_ = 1
custom_view_->y_ = 2
custom_view_->result_ = 0
class_model::notify()
class_custom_view::changed()
-----
custom_view_ptr->x_ = 1
custom_view_ptr->y_ = 2
custom_view_ptr->result_ = 3
$

Viewの値が確定すると、Controllerに通知が飛び、ControllerはViewの値をModelの関数に渡して値を計算、結果が出たらModelからViewに通知が飛び、ViewはModelから結果を取り寄せ出力。
このように様々な処理を経て、計算結果が出力される。

Sample/designpattern/mvc/mvc/src/mvc at master · bg1bgst333/Sample · GitHub

CSingleDocTemplate

CSingleDocTemplateは、シングルドキュメントインターフェース用のドキュメントテンプレート。

CSingleDocTemplate クラス | Microsoft Docs

MFCアプリケーション
MFCアプリケーション

MFCアプリケーションで、

現在のプロジェクト設定
現在のプロジェクト設定

現在のプロジェクト設定はそのまま。

アプリケーションの種類
アプリケーションの種類

アプリケーションの種類も前回と同様。

複合ドキュメントサポート
複合ドキュメントサポート

複合ドキュメントサポートもなし。

ドキュメントテンプレート文字列
ドキュメントテンプレート文字列

ドキュメントテンプレート文字列もそのまま。

データベースサポート
データベースサポート

データベースサポートなし。

ユーザーインターフェース機能
ユーザーインターフェース機能

ユーザーインターフェース機能もこうする。

高度な機能
高度な機能

高度な機能も何もつけない。

生成されたクラス
生成されたクラス

生成されたクラスで、ビューの基本クラスだけCEditViewに変更する。

ソリューションエクスプローラ
ソリューションエクスプローラ

このようにファイルが追加される。

このCSingleDocTemplateがどのようにシングルドキュメントを実現しているか調べてみる。

ブレークポイント
ブレークポイント

まず、ここにブレークポイントを仕込む。
デバッグ実行すると、

ここに来る
ここに来る

ここに来るわけだが、RUNTIME_CLASSというマクロ、

RUNTIME_CLASS
RUNTIME_CLASS

こうなっていて、

CRuntimeClass
CRuntimeClass

インスタンスにしてCRuntimeClassポインタにしてる(?)みたいだがよくわからない。
詳細はCRuntimeClassあたりでいずれ。

最初に戻って、ステップインでどうなるか。

new
new

newのところに入ってくるけど、ここは飛ばして、

もう一度
もう一度

ここからもう一度ステップイン。

コンストラクタ
コンストラク

ようやくコンストラクタに入れた。

m_pOnlyDoc
m_pOnlyDoc

やはりCRuntimeClassのポインタなので、インスタンス作られて渡されてるのかな。
で、m_pOnlyDocがここではNULLに初期化されてる。

NULL
NULL

この時点ではまだNULL。
ということは、どこかでここにオブジェクトポインタが入る。

検索
検索

じゃあm_pOnlyDocで検索してみよう。

検索結果
検索結果

こんな感じで検索結果が出てきたので、出てきた場所すべてにブレークポイントを貼る。

ブレークポイント
ブレークポイント

こんな感じで。

またデバッグ実行
またデバッグ実行

ここに来たら、続行。

コンストラクタ
コンストラク

まあ、ここに来るよね。
その次は、

GetFirstDocPosition
GetFirstDocPosition

GetFirstDocPosition、最初のドキュメントの位置を取得か。
m_pOnlyDocはNULLなので、NULLを返す模様。

AssertValid
AssertValid

AssertValid、ここはそのまま通りそう。

またここだ
またここだ

またここだ。

またここだ
またここだ

これも、またここだ。

OpenDocumentFile
OpenDocumentFile

2回ずつ訪れた後、ようやく新しい場所、OpenDocumentFileに着く。

NULLなので
NULLなので

この時点では、m_pOnlyDocはNULLなので、

ここに来る
ここに来る

ここに来る。
ここでステップインするはずが、間違えてステップオーバーしてしまったせいか、

AddDocument
AddDocument

どこか途中で引っかかったみたい。

pDocをもらう
pDocをもらう

ここで偶然にもpDocをもらって、m_pOnlyDocに格納している。
で、ここをステップアウトなりで抜けると、

CreateNewDocumentの中のAddDocument
CreateNewDocumentの中のAddDocument

なんとCreateNewDocumentの中のAddDocumentから抜けた状態だったのがわかる。
このあとはpDocumentを返すのか。

pDocument
pDocument

pDocumentが返される。

そのまま行くと
そのまま行くと

そのまま行くと、

pFrame
pFrame

pFrameの生成に行く。フレームよりドキュメントが先だったのね。

OnNewDocument
OnNewDocument

さらに先に進むとOnNewDocumentがある。
まあでも、

終わり
終わり

最終的にはここにたどり着く。
外へ出ると、

OpenDocumentFileの外
OpenDocumentFileの外

ここに出る。
さらに外へ出ると、

OnFileNewの外
OnFileNewの外

OnFileNewの外に出る。

エディットビュー
エディットビュー

CEditViewに変更したので、最終的にこのような編集可能なビューになっている。
文字列"ABCDE"を入力した段階で、

新規作成
新規作成

新規作成を選択。

OpenDocumentFile
OpenDocumentFile

そうすると、ここに来るが、m_pOnlyDocはもうNULLではないので、

SaveModified
SaveModified

今度はこちらに来る。
SaveModifiedは更新を保存するのかな。
続行すると、

保存するかどうか
保存するかどうか

メッセージボックスが出てくるので、いいえを選択。

中を見たらどちらもTRUE
中を見たらどちらもTRUE

中を見たらわかったけど、保存して成功でも、保存しなくても、TRUEなのか。

それでここに来る
それでここに来る

それでここに来ると。

で、このあとにOnNewDocumentがあったと思うが、

OnNewDocument
OnNewDocument

何してるのかとおもったら、CEditViewにNULLをセットして、テキストをまっさらに。

そうここ
そうここ

出てくるとこう。

InitialUpdateFrame
InitialUpdateFrame

InitialUpdateFrameで更新。

OnFileNew
OnFileNew

外に出るとOnFileNew。
で最終的に、

新規作成の状態
新規作成の状態

テキストがまっさらになって新規作成の状態。

このようにシングルドキュメントインターフェースは動いてる。
いやー、複雑・・・。

Sample/mfc/CSingleDocTemplate/CSingleDocTemplate/src/CSingleDocTemplate at master · bg1bgst333/Sample · GitHub

CWinApp::AddDocTemplate

ここからは、SDIアプリケーションについて扱っていく。

ドキュメント/ビュー アーキテクチャ | Microsoft Docs
MFC編 - ドキュメント・ビューの基本

MFCアプリケーション
MFCアプリケーション

MFCアプリケーションプロジェクトを選択。

現在のプロジェクト
現在のプロジェクト

現在こうなっているが、

シングルドキュメント
シングルドキュメント

このようなシングルドキュメントの設定にする。

複合ドキュメントなし
複合ドキュメントなし

複合ドキュメントはなし。

ドキュメントテンプレート文字列
ドキュメントテンプレート文字列

この辺はそのまま。

データベースサポートなし
データベースサポートなし

これもなし。

ユーザーインターフェース
ユーザーインターフェース

ここは重要。このように設定する。
ステータスバー、ツールバーは扱いません。

高度な機能なし
高度な機能なし

高度な機能も全部オフにする。

生成されたクラス
生成されたクラス

生成されたクラスもこんな感じ。
これでプロジェクトを作成。

ソリューションエクスプローラ
ソリューションエクスプローラ

このようにたくさんのファイルが追加される。
ビルドして実行すると、

そのままビルド実行
そのままビルド実行

このような、フレームウィンドウの中に、メニューと、ビュー(クライアント領域)の付いたウィンドウが表示される。

CWinApp_.cppで、

レジストリキー設定や、プロファイルのロードとかは置いといて、重要なのは、CSingleDocTemplateオブジェクトを作成し、できたpDocTemplateをAddDocTemplateに渡しているところである。

ここで、ドキュメントCWinApp_Doc、フレームウィンドウCMainFrame、ビューCWinApp_View、これらを結合するシングルドキュメントテンプレートCSingleDocTemplateを作成している。また、IDR_MAINFRAMEだが、これも必要。メニューやアイコンなどのリソース情報のIDだから。

ここで、CWinApp_にこのテンプレートを追加している。

CWinApp::AddDocTemplate (MFC)

で、このAddDocTemplateがないと、

CSingleDocTemplate* pDocTemplate;
	pDocTemplate = new CSingleDocTemplate(
		IDR_MAINFRAME,
		RUNTIME_CLASS(CWinApp_Doc),
		RUNTIME_CLASS(CMainFrame),       // メイン SDI フレーム ウィンドウ
		RUNTIME_CLASS(CWinApp_View));
	if (!pDocTemplate)
		return FALSE;
	//AddDocTemplate(pDocTemplate);

後の処理でウィンドウ作成に失敗して、アプリが終了してしまう。
で、この部分がどうなっているか調べてみる。

pDocTemplate
pDocTemplate

pDocTemplateは生成されている。
で、この先は飛ばして、

ProcessShellCommand
ProcessShellCommand

ProcessShellCommand、ここがどうなるか。

FALSEになってしまう
FALSEになってしまう

AddDocTemplateをコメントしてるとFALSEになってしまう。

終了
終了

結果、終了してしまう。

もう一度ここまでいく
もう一度ここまでいく

そこで、もう一度デバッグ実行して、ここまで来たら、

ステップイン
ステップイン

ステップインする。

ここに入る
ここに入る

すると、ここに入ってくる。

ステップオーバー
ステップオーバー

ここからはステップオーバー。

m_pMainWndはNULL
m_pMainWndはNULL

ここまででm_pMainWndはNULLなので、

ブレークポイント
ブレークポイント

ブレークポイント貼っておいてさらに中をステップインで調べる。

この先行くと
この先行くと

この先は、

ここに出る
ここに出る

ここに出る。
ステップオーバーすると、

ここで終わり
ここで終わり

ここで終わっちゃう。

ここに戻ってくる
ここに戻ってくる

で、もう一回ステップインすると、

ここに入ってくる
ここに入ってくる

ここに入ってくる。
CCmdTarget::OnCmdMsgに来た。
ステップオーバーしていくと、

こういうところを通る
こういうところを通る

こういうところを通っていく。

終わってしまう
終わってしまう

でも終わってしまう。

NULLのまま
NULLのまま

m_pMainWndはNULLのまま。
そこで、

AddDocTemplate
AddDocTemplate

もう一度AddDocTemplateを有効にする。

ここ来たら
ここ来たら

ここ来たら、ステップイン。

いったん飛ばして
いったん飛ばして

いったん飛ばして、

もう一回ステップイン
もう一回ステップイン

ここでもう一回ステップイン。

ここに来る
ここに来る

ここに来る。
足跡として、

ブレークポイント
ブレークポイント

ブレークポイント貼って、ここをステップオーバーしていく。
ここからの判断は難しいが、

ここをステップイン
ここをステップイン

ここをステップインする。

ここに来る
ここに来る

ここに来る。
ステップオーバーしていく。

難しい
難しい

これはなかなか難しい。
ここでステップイン。

ここに出る
ここに出る

ここに出る。

さらにステップイン
さらにステップイン

m_pDocManagerはNULLでないので、ここまでいけるので、さらにステップイン。

ここに到達
ここに到達

ここに到達するのでさらにステップオーバー。

ここまでくる
ここまでくる

ここまでくるので、さらにステップイン。

ここにくる
ここにくる

ここにくるので、さらにステップオーバーしていく。

CreateNewDocument
CreateNewDocument

CreateNewDocumentなんてのもあるけど、

pFrameがNULL
pFrameがNULL

pFrameがNULL、ここがポイント。

CreateNewFrame
CreateNewFrame

CreateNewFrame、ここでフレームを作っている。

ブレークポイント
ブレークポイント

ここにブレークポイント貼ってさらに中を見に行こう。

中はこうなっている
中はこうなっている

中はこうなっている。さらにステップオーバー。

オブジェクト作成
オブジェクト作成

これはオブジェクトの作成であって、ウィンドウの作成ではない。

LoadFrame
LoadFrame

LoadFrameだ。この中っぽい。

中はこう
中はこう

中はこう。下に行くと、

Create
Create

ついにCreateを見つけた!!
ここでCFrameWndができると、

m_pMainWndにセットされる
m_pMainWndにセットされる

m_pMainWndにセットされる。

ここが
ここが

ここはFALSEにならない。

ここはTRUE
ここはTRUE

ここはTRUEになり、

結果ここにきて
結果ここにきて

結果、ここにきて、

ウィンドウ表示
ウィンドウ表示

ウィンドウが表示される。
長かった・・・。

Sample/mfc/CWinApp/AddDocTemplate/src/CWinApp_ at master · bg1bgst333/Sample · GitHub

DDX_Scroll

DDX_Scrollは、スクロールバーのつまみの位置を取得できる。

DDX_Scroll | Microsoft Docs

スクロールバー
スクロールバー

このようにスクロールバーを配置し、

変数の追加
変数の追加

変数の追加で、

Control
Control

ControlではCScrollBar、

Value
Value

Valueではintを指定。

このように追加される。

IDC_SCROLLBAR1とm_xvScrollBar1はDDX_Scrollで紐付く。

m_xcScrollBar1.SetScrollRangeで、スクロール範囲を0から100とする。
また、m_xcScrollBar1.SetScrollPosで、つまみの位置は50とする。

Button1が押されたら、UpdateDataで更新し、m_xvScrollBar1につまみの位置が格納されるので、メッセージボックスで表示。

実行
実行

この状態で、Button1を押すと、

位置は50
位置は50

位置は50であることがわかる。

Sample/mfc/DDX_Scroll/DDX_Scroll/src/DDX_Scroll at master · bg1bgst333/Sample · GitHub