ドナドナされるプログラマのメモ

Windows用アプリのプログラミングメモ

重いイベントハンドラの処理状況をダイアログで表示する

背景

重い処理の進捗状況をダイアログで表示する場合、自分がよくやるのは

  • 進捗状況を示すモードレスダイアログを作成
  • 重い処理をAfxBeginThread()により別のワーカースレッドとして起動
  • モードレスダイアログは適宜タイマによって状況を更新
  • モードレスダイアログ上のキャンセルボタンが押された場合はフラグを立てる
  • 重い処理内では適宜フラグを監視し必要に応じて処理を中断する

というもの。たいていはこれで問題なく動く。

問題

しかし、この実装だと例えばOnUpdate()にてCTreeViewやCListViewに数十万個のアイテムを追加する場合、フレームワークがOnUpdate()を呼び出している間モードレスダイアログのGUIが無反応・無更新となる問題がある。

原因

この原因は、ワーカースレッドとして作られたスレッドで新たにダイアログを作っても、そのスレッドプロシージャはウインドウプロシージャの一部となってしまうことにある。すなわち、メインウィンドウのウィンドウプロシージャが非常に重い処理を呼び出してしまった場合はモードレスダイアログ上のGUI向けのものも含めウィンドウメッセージがキューにたまり続け、処理されないのである。

解決方法

  • 対症療法:重いイベントハンドラ内の処理もマルチスレッド化してしまう
  • 根本対策:ダイアログのウィンドウプロシージャをメインウィンドウから独立させる(ワーカースレッドではなく、ユーザーインターフェーススレッドにする)

An example of user interface thread implementation

参考

dodonpa.la.coocan.jp

 

CListViewにポップアップメニューを追加したけどON_UPDATE_COMMAND_UIが送られてこない件について

事象

CListViewにポップアップメニューを追加し、条件に応じてメニューのEnable/Disableを切り替えようとMFCの流儀に則りON_UPDATE_COMMAND_UIのハンドラを追加した。しかし、ポップアップメニュー表示時になぜかON_UPDATE_COMMAND_UIが来ない。

原因

CListViewは、ON_UPDATE_COMMAND_UIの送信に必要なOnInitMenuPopup()を実装していない。

対策

ここを参考に、OnInitMenuPopup()を実装する。

https://support.microsoft.com/sr-latn-me/help/242577/you-cannot-change-the-state-of-a-menu-item-from-its-command-user-inter

ダイアログバー上のエディットボックスから文字列を取得する方法

ダイアログバー上のエディットボックスから文字列を取得するのにちょっと苦労したのでメモ。

問題の背景

ダイアログを作る場合、通常はCDialog等を継承した、そのダイアログ固有のクラスを作成する。しかしMicrosoft曰く、CDialogBarは通常固有のクラスを作成しないらしい。これは、ダイアログバーはメインウィンドウの一部で、各コントロールからのメッセージはメインウィンドウのウィンドウプロシージャで処理するという設計だからだろう。このため、メッセージハンドラはCMainFrameに書く必要がある。*1

問題

しかし、固有のクラスを作らなかった場合、メッセージハンドラにてダイアログバー上のエディットボックスにはどうやってアクセスすればよいのだろうか?単純にCMainFrameからGetDlgItemText(ID_DlgBar_Edit)を呼び出しても失敗するし、固有のクラスを作っても上手く行かない。

解決方法

実は答えは単純で、CMainFrame上のメッセージハンドラから、ダイアログバーのオブジェクト(デフォルトだとm_wndDlgBarだと思う)に対しGetDlgItemText(ID_DlgBar_Edit)すればよい。

m_wndDlgBar.GetDlgItemText(ID_DlgBar_Edit, str);

その他の操作をしたい場合は、同様にGetDlgItem()すればよい。

*1:なお、リソースエディタはこの仕様に非対応なため、メッセージハンドラは手動で追加する必要があるようだ。

mozjpegをつかった画像変換ソフトの開発 その8

mozjpegを多並列で実行するGUIを備えた画像変換ソフト、無事Vectorにて公開。

www.vector.co.jp

 そのうち、英語版もどこかで公開予定だけど、どこにしよう?Vectorってどう見ても国内オンリーなのよね。英語ページを作れる気がしない。

CDocumentからCViewを取得する一例

今時ドキュメントビューアーキテクチャなんて人は居ないだろうけど、メモ。

CDocumentからCViewにメッセージを投げたいときや関数を直接呼び出したいときに困るのが、CViewのアドレス取得方法。特に、自分はCSplitterWndを多用したためCViewが複数あり決め打ち取得ができなずに困ってしまった。そこで以下のような簡単なコードを書いてみた。

Example of finding CView from CDocument

ここにたどり着くまでに4時間かかってしまったorz

CArchive::ReadString, CArchive::WriteStringの罠

CArchive::ReadString()とCArchive::WriteString()を使って文字列を読み書きしようとしたら文字化けしてハマったのでメモ。

以下のようなコードを書いたら、読み込み時に文字化けした。

 

void Serialize(CArchive &ar){

 TCHAR path[MAX_PATH+1];

 if(ar.IsStoring()){  // save

  (中略)

  ar.WriteString(path);

} else {  // load

  ar. ReadString(path, MAX_PATH);

}

 原因はCArchive::WriteString()とCArchive::ReadString()の設計が非対称なため。ReadString()が'\0'までを読み込む関数だと思ったら大間違いで、こいつは'\n'までを読み込む関数。一方、WriteString()は’\0'の直前までを書き出す関数で、書き終わっても'\n'を付与しない。つまり、読み込みと書き込みで区切り文字が異なるのだ。そのため上記プログラムだとMAX_PATHまで読んでしまい、本来読むべき領域からオーバーランし文字化けしたデータとなる。

また、この仕様だと'\n'を含む文字列をWriteStringした場合、ReadStringの処理がかなり困ったことになりそうである。すなわち、改行がある場合は意図したよりも短い文字列が読み込まれてしまう。

なんというか、getstr()との互換性をもたせようとした?せいで最悪な事態になっている気がする。この仕様を審査承認した人たちの罪は大きいと思う。

リソースエディタがWS_EX_COMPOSITEDを認識しない

とあるリソースをVisual Studio2019で開こうとしたら、error RC2104: undefined keyword or key name: WS_EX_COMPOSITED というエラーが出た。

f:id:donadonasan:20200305004914p:plain

error RC2104: undefined keyword or key name: WS_EX_COMPOSITED

でも、おかしい。このキーワードはWindowsに存在しているものなのだ。色々と調べてみたところ、Visual Studio2017からあるバグのようだ。WS_EX_COMPOSITEDを設定したダイアログはWindows8で正常に動作しないことと関係あるのかもしれない。

解決方法はとても簡単で、resource.hの先頭に以下を追加すればよい。

#define WS_EX_COMPOSITED 0x02000000L