質問: リストコントロールに配置するデータファイルをメニューバーの操作から読み込み、反映させたい。 現在メインフレームにメニューバーを設置し、開くメニューからファイルを読み込むことができたのですが、この情報をどのようにviewに伝えればいよいかわかりません。 どなたかアドバイスを頂ければ幸いです。 (VS2015で作成したSDIのプロジェクトのviewを改造してリストコントロールを配置しています) プロジェクト構成 -------- 以下の名前のヘッダとソースが自動で生成されています。 app appDoc appView FileView MainFrm FileViewにlistCtrlを持たせています。 ファイル読み込みの処理はapp.cppのOnFileOpen()で処理しております。この結果をFileViewに反映するやり方がわかりません。 環境 -- VS2015 MFC C++ win10 64bit
回答: MFC = 過去の遺物の世界へようこそ。 C# や CLI など .Net 系の方が参考サイトも豊富ですし、 Win32 なら 「猫でもわかる Windows プログラミング」といった 実装主体のサイトもあるので、今更なぜ MFC なのか理解に苦しみますネ。 MFCは習得が難しく、そしてもはや色々と壊れていますが、 ゆっくりしていってください。 前提として、わたしは ・Visual Studio 2019 Preview を使用しています。 環境の違いはご容赦ください。 ・Visual Studio 2015 では、文字列をデリミタで分割して配列化する Split 関数のよいのが無いので、自分で作るしかないのかなと思います。 C++11 のコレクションクラスの std::vector がよくなってますし、 Visual Studio 2019 へ移行を一応、推奨しておきます。 ・必ずロケール設定をしてください。 普通に関数を使っているつもりでも、 おかしな動作をすることが往々にしてあります。 ファイル周りや画面回りは特に省略しないでください。 ・MFC に限らず VC++ は痒いところに手が届かないということは往々にしてあります。 このあたりもご容赦のほどを。 ここで紹介する方法は、回答者 hiroyuki さんが示した方法ではなく、簡易版です。 方針的には、ドキュメントクラスにデータを保持せず、ビュークラスで読み書きする方法となります。 本来の方法では、リンク先で示されたとおり、 ドキュメントクラスで読み込みとデータのため込み処理を行い、 ビューでドキュメントクラスのデータを表示するといった方法になると考えられます。 参考サイトでは抽象的な表現が多く実装例があまりなかったですが、 理解できましたでしょうか。 方針としては、 1まずプロジェクトを作り直します。 2ロケールを設定します。 3ビュークラスに void OnUpdate(CView*pView, UINT lHint, CObject*pHint); を作り、読み込み処理を実装します。 こんな感じで行きます。 1まずプロジェクトを作りなおしてください。 appView があるのに FileView というのが謎なのですが、 プロジェクト名を app で、作り直します。 1)「アプリケーションの種類」タブの、 「アプリケーションの種類(T)」ドロップダウンリストで 「単一のドキュメント」を選択します。 !アプリケーション・ウィザード「アプリケーションの種類」タブ 2)「ドキュメントテンプレートプロパティ」タブはお好みで 「ファイル拡張子(X)」「ファイル名(N)」「メインフレームのキャプション(P)」を 設定してください。 !アプリケーション・ウィザード「ドキュメントテンプレートプロパティ」タブ 3)「ユーザインターフェイス機能」タブについては、 お好みで設定を変更してください。 4)「高度な機能」タブでは、余計なペインを削除します。 「□ドッキング可能なエクスプローラーペイン(D)」 「□ドッキング可能な出力ペイン(&S)」 「□ドッキング可能なプロパティペイン」 のチェックを外します。 !アプリケーション・ウィザード「高度な機能」タブ 5)「生成されたクラス」タブで 「生成されたクラス(&G)」ドロップダウンリストの内容を 「View」に変更し、 「基本クラス(A)」を「CListView」へ変更して、 「完了」ボタンを押します。 !アプリケーション・ウィザード「生成されたクラス」タブ 2ロケールを設定します。 1) ファイル「app.cpp」へ #include を書き加えます。 2) コンストラクタへ _tsetlocale(LC_ALL, T("japanese")); を書き加えます。 ``` #include "pch.h" #include "framework.h" #include "afxwinappex.h" #include "afxdialogex.h" #include "app.h" #include "MainFrm.h" #include "appDoc.h" #include "appView.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // ▽▽▽ ココから ▽▽▽ #include // △△△ ココまで △△△ // CappApp BEGIN_MESSAGE_MAP(CappApp, CWinAppEx) ON_COMMAND(ID_APP_ABOUT, &CappApp::OnAppAbout) // 標準のファイル基本ドキュメント コマンド ON_COMMAND(ID_FILE_NEW, &CWinAppEx::OnFileNew) ON_COMMAND(ID_FILE_OPEN, &CWinAppEx::OnFileOpen) // 標準の印刷セットアップ コマンド ON_COMMAND(ID_FILE_PRINT_SETUP, &CWinAppEx::OnFilePrintSetup) END_MESSAGE_MAP() // CappApp の構築 CappApp::CappApp() noexcept { m_bHiColorIcons = TRUE; // 再起動マネージャーをサポートします m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS; #ifdef _MANAGED // アプリケーションが共通言語ランタイム サポート (/clr) を使用して作成されている場合: // 1) この追加設定は、再起動マネージャー サポートが正常に機能するために必要です。 // 2) 作成するには、プロジェクトに System.Windows.Forms への参照を追加する必要があります。 System::Windows::Forms::Application::SetUnhandledExceptionMode (System::Windows::Forms::UnhandledExceptionMode::ThrowException); #endif // TODO: 下のアプリケーション ID 文字列を一意の ID 文字列で置換します。推奨される // 文字列の形式は CompanyName.ProductName.SubProduct.VersionInformation です SetAppID(_T("app.AppID.NoVersion")); // TODO: この位置に構築用コードを追加してください。 // ここに InitInstance 中の重要な初期化処理をすべて記述してください。 // ▽▽▽ ココから ▽▽▽ _tsetlocale(LC_ALL,_T("japanese")); // △△△ ココまで △△△ } ``` ※ 例では #include "pch.h"] に、なっていますが VS2015 では、[#include "stdafx.h"]です。 環境によりさまざまですので、この辺りはご容赦ください。 3ビュークラスに OnUpdate() を追加して実装します。 1)クラスビューから「CappView」を選択し、「プロパティ」ペインから 「オーバーライド」ボタンを選択。 OnUpdaye の右のドロップダウンリストから「 OnUpdate」を選ぶと、 ソースとヘッダとに関数と関数宣言が追加されます。 ![View クラスへOnUpdate を追加 2)追加された OnUpdate()の第2引数の lHint のコメントアウトを外して以下の様に実装します。 ``` // CappView メッセージ ハンドラー // スプリッタ: int Split(CStringArray& arFields, LPCTSTR pFields = nullptr, LPCTSTR pDelimiters = nullptr) { int nResult = 0; arFields.RemoveAll(); // Do not process empty strings. if (pFields && *pFields && pDelimiters && *pDelimiters) { CString sFields = pFields, sDelimiters = pDelimiters; int nEnter = 0, nLeave = sFields.Find(pDelimiters, nEnter); while (-1 != nLeave) { // By using SetAtGrow(), empty strings are automatically added to the array. arFields.SetAtGrow(nResult, sFields.Mid(nEnter, nLeave - nEnter)); nResult++; nEnter = nLeave + sDelimiters.GetLength(); nLeave = sFields.Find(pDelimiters, nEnter); } arFields.SetAtGrow(nResult, sFields.Mid(nEnter)); nResult++; } return nResult; } // OnFileNew()とOnFileOpen()成功時に呼び出されるビューの書き換えを目的にしたオーバーライド関数の実装 void CappView::OnUpdate(CView* /*pSender*/, LPARAM lHint, CObject* /*pHint*/) { // TODO: ここに特定なコードを追加するか、もしくは基底クラスを呼び出してください。 CListCtrl& rList = GetListCtrl(); BOOL bResult = TRUE; int nMaxCols = 0; if (!lHint) { CHeaderCtrl* pHead = rList.GetHeaderCtrl(); if (pHead) { nMaxCols = pHead->GetItemCount(); } for (int nCol = nMaxCols - 1; bResult && 0 <= nCol; nCol--) { bResult = rList.DeleteColumn(nCol); }; bResult = rList.DeleteAllItems(); } CString sValue = GetDocument()->GetPathName(); if (bResult && !sValue.IsEmpty()) { bResult = false; FILE* fp = nullptr; errno_t nError = _tfopen_s(&fp, sValue, _T("r")); if (!nError && fp) { if (lHint) { } else { bResult = true; for (int nRow = -1; bResult && !feof(fp); nRow++) { bResult = nullptr != _fgetts(sValue.GetBuffer(8193), 8192, fp); sValue.ReleaseBuffer(); if (bResult) { sValue.Replace(_T("\n"), _T("")); CStringArray ar; nMaxCols = Split(ar, sValue, _T(",")); for (int nCol = 0; bResult && nCol < nMaxCols; nCol++) { bResult = false; sValue = ar[nCol]; if (-1 == nRow) { bResult = nCol == rList.InsertColumn(nCol, sValue, LVCFMT_LEFT, 96); } else { if (!nCol) { bResult = nRow == rList.InsertItem(nRow, sValue); } else { bResult = rList.SetItemText(nRow, nCol, sValue); } } }; Split(ar); } else { bResult = !feof(fp); } }; } fclose(fp); } } } ``` これで一応完成です。 実行してみてください。 あと、 3)ビューの切り替えを実装 4)PreCreateWindowで初期属性を変更 5)OnCreate()で拡張属性を設定 6)OnInitialUpdateで未読み込み時と読み込み時の表示の変更を追加 などありますが、がんばってみてください。