🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
MFC

MFC (Microsoft Fouondation Class)とは、MicrosoftがVC++用に開発したWindows用アプリケーションのフレームワークです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

1回答

3848閲覧

CCheckListBoxの初期表示がずれる

fana

総合スコア11985

MFC

MFC (Microsoft Fouondation Class)とは、MicrosoftがVC++用に開発したWindows用アプリケーションのフレームワークです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

1グッド

1クリップ

投稿2019/09/27 03:22

編集2019/09/27 05:45

環境:VS2017, Win7

困り事

MFCのCCheckListBoxの項目表示が下図左側のように崩れる.

イメージ説明

現象が再現する状態:

  • VS2017で新規にMFCアプリケーション(ダイアログベース)のプロジェクトを作る.
    (アプリケーションの種類の選択を「ダイアログベース」に変えて他の設定はデフォルトのまま.)
  • ダイアログ上にListBoxを1つ配置し,プロパティを2か所(下図赤枠内)だけ変更
  • ダイアログクラスのメンバとして CCheckListBox型のm_CLBを追加.
  • ダイアログクラスのOnInitDialog()内に下記コードを追加
BOOL CMFCApplication1Dlg::OnInitDialog() { ... // TODO: 初期化をここに追加します。 m_CLB.SubclassDlgItem( IDC_LIST1, this ); //m_CLBはCCheckListBox型メンバ m_CLB.AddString( _T("Ragamuffin") ); m_CLB.AddString( _T("Australian Mist") ); m_CLB.AddString( _T("Maine Coon") ); m_CLB.AddString( _T("Japanese Bobtail") ); m_CLB.AddString( _T("Sphynx") ); return TRUE; }

イメージ説明

自分でやったこと

リストボックスに再描画がかかれば以降の表示はまともな状態になるようなので,下記コードようのにOnInitDialog()の末尾でとりあえずメッセージをポストして,そのメッセージハンドラからInvalidate()を実行することで,一応現象には対処できている(とは思う).

BOOL CMFCApplication1Dlg::OnInitDialog() { ... //※ここにm_CLB.Invalidate();とか書いても効果がないのでメッセージをポスト. PostMessage( MY_MSG ); return TRUE; // フォーカスをコントロールに設定した場合を除き、TRUE を返します。 } //MY_MSGのハンドラ afx_msg LRESULT CMFCApplication1Dlg::OnMyMsg(WPARAM wParam, LPARAM lParam) { m_CLB.Invalidate(); //再描画を要求 return 0; }

質問

  • そもそも何故表示が崩れるのか?→最初から崩れないようにする方法はありますか?
  • 表示が崩れるのが仕方ない場合,上記よりももっと(何らかの意味で)楽な対処方法はありませんか?

追記:
デスクトップのテーマを「Aeroテーマ」の「Windows7」に変えて実行してみましたが,やはりずれています.
イメージ説明

k8o👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

takabosoft

2019/09/27 05:34

VS2015+Win10では再現せず・・・ ちなみにエアロの有効化無効化で挙動が変わったりしませんかね?
fana

2019/09/27 05:47

テーマを変えて実行してみた結果を追記しました.やはり項目の初期表示がずれていて,再描画するとまともになります.
dodox86

2019/09/27 05:59

VS2017(15.9.15)+Windows 7 x64で再現しました。MFCのDebug Assertionに引っ掛かりますが。。。再描画でまともになるのも同じです。
guest

回答1

0

ベストアンサー

興味をおぼえましたので、調べてみました。

そもそも何故表示が崩れるのか?→最初から崩れないようにする方法はありますか?

初回の描画で、2つ目のアイテムから高さを変えられてしまっているようです。それ以後はその高さが適用されるので、初回の描画が崩れているように見えます。

表示が崩れるのが仕方ない場合,上記よりももっと(何らかの意味で)楽な対処方法はありませんか?

若干面倒ですが、CCheckBox::AddStringの実行後に高さを明示的にセットすることで対処できました。以下、ご説明します。

どうもCCheckListBoxの初回の描画で、2行目(Item[1])以降の項目の高さが低くセットされてしまっているようです。その為、2回目以降の描画ではその低い高さのまま、全項目(リストボックスのスタイルはLBS_OWNERDRAWFIXEDなので)に適用されてしまっている様子です。

確認の為、CCheckListBoxを派生させたCCheckListBox2クラスを定義し、CCheckListBox::DrawItemが呼び出されるようにしてみました。

C++

1// CCheckListBox2.h 2#pragma once 3#include <afxwin.h> 4class CCheckListBox2 : 5 public CCheckListBox 6{ 7public: 8 CCheckListBox2(); 9 virtual ~CCheckListBox2(); 10 virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); 11 virtual void MeasureItem(LPMEASUREITEMSTRUCT ps); 12}; 13 14// CCheckListBox2.cpp 15#include "stdafx.h" 16#include "CCheckListBox2.h" 17 18CCheckListBox2::CCheckListBox2() 19{ 20} 21 22CCheckListBox2::~CCheckListBox2() 23{ 24} 25 26static int n = 0; 27void CCheckListBox2::DrawItem(LPDRAWITEMSTRUCT ps) 28{ 29 int h = ps->rcItem.bottom - ps->rcItem.top; 30 TRACE(_T("DrawItem item:[%d], h=%d\n"), ps->itemID, h); 31 CCheckListBox::DrawItem(ps); 32} 33 34// LBS_OWNERDRAWFIXEDだから、呼ばれない 35void CCheckListBox2::MeasureItem(LPMEASUREITEMSTRUCT ps) 36{ 37 TRACE(_T("Measuretem item:[%d], h=%d\n"), ps->itemID, ps->itemHeight); 38 CCheckListBox::MeasureItem(ps); 39}

上記CCheckListBox2をメンバー変数m_CLBとして使うよう、差し替えます。

C++

1 //CCheckListBox m_CLB; 2 CCheckListBox2 m_CLB;

これで試すと、仮想関数CCheckListBox2::DrawItemの呼び出し(WM_DRAWITEMのメッセージハンドラのはず)で、描画しようとしている項目の高さが分かります。

# デバッグ端末のTRACE出力 # 初回のDraw - item[0]だけ高さ18で、Item[1]以降は14と、小さくなっている。 cchecklistbox2.cpp(18) : atlTraceGeneral - DrawItem item:[0], h=18 cchecklistbox2.cpp(18) : atlTraceGeneral - DrawItem item:[1], h=14 cchecklistbox2.cpp(18) : atlTraceGeneral - DrawItem item:[2], h=14 cchecklistbox2.cpp(18) : atlTraceGeneral - DrawItem item:[3], h=14 cchecklistbox2.cpp(18) : atlTraceGeneral - DrawItem item:[4], h=14 # 2回目以降のDraw # 高さ14のまま cchecklistbox2.cpp(18) : atlTraceGeneral - DrawItem item:[0], h=14 cchecklistbox2.cpp(18) : atlTraceGeneral - DrawItem item:[1], h=14 cchecklistbox2.cpp(18) : atlTraceGeneral - DrawItem item:[2], h=14 cchecklistbox2.cpp(18) : atlTraceGeneral - DrawItem item:[3], h=14 cchecklistbox2.cpp(18) : atlTraceGeneral - DrawItem item:[4], h=14

これで分かるように、初回のDrawItem呼び出しでItem[1]のときに高さが変わっています。あくまで私の頼りない推測ですが、CCheckListBoxの実装でチェックボックスの存在などを加味した高さの計算とセットがなされてしまうようです。
>>>>追記 2019/09/28 01:51
尚、コード例は示しませんが、OnInitDialog内でのAddString実行直後にCDC::GetTextExtentで高さを確認すると全ての項目で高さ18であったものが、初回の描画、Item[1]以降で14になってしまったことから「高さを変えられている」と判断しました。
<<<<追記ここまで

そこで、対処としてSubclassDlgItemAddStringの実行後にあえて明示的にCListBox::SetItemHeightでItem[0]の高さを指定してみました。
リストボックスのスタイルはLBS_OWNERDRAWFIXEDなので、ひとつセットすれば他の項目も同じになるだろう、との意図です。
一応、これで最初の描画から固定の高さになることは確認できました。

C++

1 // ...OnInitDialogの実装 2 3 // TODO: 初期化をここに追加します。 4 m_CLB.SubclassDlgItem(IDC_LIST1, this); 5 m_CLB.AddString(_T("Ragamuffin")); 6 m_CLB.AddString(_T("Australian Mist")); 7 m_CLB.AddString(_T("Maine Coon")); 8 m_CLB.AddString(_T("Japanese Bobtail")); 9 m_CLB.AddString(_T("Sphynx")); 10 11 // デバッグ用スイッチ 12 bool sw1 = true; 13 if (sw1) { 14 /* 15 https://docs.microsoft.com/ja-jp/cpp/mfc/reference/clistbox-class?view=vs-2019 16 を参考に、項目にセットした文字列の高さを基に、明示的にセットする。 17 */ 18 19 CString str; 20 CSize sz; 21 CDC *pDC = m_CLB.GetDC(); 22 23 // LBS_OWNERDRAWFIXED だから、ひとつセットすれば充分(?) 24 m_CLB.GetText(0, str); 25 sz = pDC->GetTextExtent(str); 26 TRACE(_T("i=%d, sz=%d\n"), 0, sz); 27 m_CLB.SetItemHeight(0, sz.cy); 28 m_CLB.ReleaseDC(pDC); 29 } 30 31 return TRUE; // フォーカスをコントロールに設定した場合を除き、TRUE を返します。 32

MFC内部のソースまで追っていませんので、推測の域を出ない意見でしたが、「一応、対処できた」ということでご参考まで。

なお、Windows 7(x64)上で、Visual Studio 2015、2017でのビルドとも同じ結果でした。

投稿2019/09/27 09:26

編集2019/09/27 16:52
dodox86

総合スコア9254

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fana

2019/09/28 02:47

調査していただき感謝です. OnInitDialog()の内部だけで対処が完結できそうなのが良いですね. こちらで試してみるのに時間を頂きたく.
fana

2019/09/30 02:21

OnInitDialog()内でSetItemHeight()を実施することでアイテム群の表示高さを最初から均一にできることを確認できました. ただ,私の環境だとCDC::GetTextExtent()やCDC::GetTextMetrics()で取得した「高さ」を用いると,デフォルトの(何もしない場合の)アイテムの高さよりもかなり大きい値になる(表示間隔が空く)ようです. 上記2つのメソッドで得られる高さは22で,これはOnInitDialog()内でCCheckListBox.GetItemHeight()で得られる値と同じですが,(表示が安定した後で)CCheckListBox.GetItemHeight()で得られる高さは15でした. (ひょっとしたらデフォルトのアイテムの高さはテキストではなくチェックボックス側のサイズから定まるのかも?)
fana

2019/09/30 02:25

私が示したメッセージを投げる対処方法は稀にうまく働かない場合がある(?)ようで,利便性だけでなく確実性の点でも回答いただいたSetItemHeight()を用いる方法の方が良さそうに思います.
dodox86

2019/09/30 02:43 編集

> (ひょっとしたらデフォルトのアイテムの高さはテキストではなくチェックボックス側のサイズから定まるのかも?) 私もそう推測しています。私の検証用コードでも、回答中で示したようにOnInitDialog中の初期化で高さ18だったものが初回描画のItem[1]の時に14に変化しています。Item[0]の描画後にチェックボックスを加味して高さが確定するかんじです。リストボックスのスタイルがLBS_OWNERDRAWFIXED(オーナー描画で高さ固定)なので、LBS_OWNERDRAWVARIABLE にして高さを自力で制御する方法もあります。以下はCCheckListBox::DrawItemのリファレンスからです。 https://docs.microsoft.com/ja-jp/cpp/mfc/reference/cchecklistbox-class?view=vs-2019#drawitem > If checklist box items are not all the same height, the checklist box style (specified in Create) must be **LBS_OWNERVARIABLE, and you must override the MeasureItem function. 今はLBS_OWNERDRAWFIXED ですが、LBS_OWNERDRAWVARIABLE(可変)にして自力で高さをセット。面倒ですけど。
dodox86

2019/09/30 03:07

私の回答はリストボックス描画用デバイスコンテキストでテキストの高さを測って明示的にセットしていますので、LBS_HASSTRINGSの文字列ベースで高さを考える形となり、例えば極端に小さいフォントの場合、チェックボックスの描画が崩れてしまうかもしれませんので完璧ではないとは思います。(未検証です)
fana

2019/10/01 04:36

SetItemHeight()で現象に対処するとして,ではその際に指定する「適当な高さの値」をどう決めるか? に関して少し検討していたのですが,小さすぎる値(0とか1)を指定するとSetItemHeight()はエラーを返すでもなく,結果としてデフォルトの高さでの対処が実現できそうです(正当な仕様の範囲なのかは不明ですが…).
fana

2019/10/01 04:39

簡便な対処方法の提案を頂けたということで,(他回答が付きそうな気配もありませんので)本質問は閉じさせて頂こうかと思います.ありがとうございました.
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問