リストビューで最下行から最上行にスクロールした際の描画問題

このQ&Aのポイント
  • VC++2005のMFC環境でプログラムを作成しています。リストコントロールのイベント(LVN_KEYDOWN)よりハンドラを作成し、最下行から最上行にスクロールする際に描画に問題が発生しています。問題のソースコードは以下のとおりです。
  • 青いフォーカスは上に移動しますが、点線のボックスが最下行に残ってしまい、その後もおかしな動きをしてしまいます。この問題を解決する方法をご存知の方はいらっしゃいますか?
  • 解決策をご教示いただけると幸いです。
回答を見る
  • ベストアンサー

リストビューで最下行から最上行にスクロールした際の描画問題

VC++2005のMFC環境でプログラムを作成しています。 リストコントロールのイベント(LVN_KEYDOWN)よりハンドラを作成し、 下記のような実装を行い、最下行からさらに下キーを押下した際に、 最上行に選択フォーカスを移動させたいのですが、 青いフォーカスは上に移動しますが、点線のボックスが最下行に そのまま残ってしまい、その後もおかしな動きをしてしまいます。 何か実装で間違っている点があるのでしょうか? 解決策をご存知の方おられましたら、ご教示お願いいたします。 下記が問題のソースコードです。 void CtestListDlg::OnLvnKeydownList1(NMHDR *pNMHDR, LRESULT *pResult) { LV_KEYDOWN *pNMLV = reinterpret_cast<LV_KEYDOWN*>(pNMHDR); int ret = m_listTest.GetNextItem(-1, LVNI_ALL | LVNI_SELECTED); if(ret==-1) return; if(pNMLV->wVKey==VK_UP){ if(ret==0){ int count = m_listTest.GetItemCount(); m_listTest.SetItemState(count-1, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED); } else{ m_listTest.SetItemState(ret-1, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED); } } if(pNMLV->wVKey==VK_DOWN){ int count = m_listTest.GetItemCount(); if(ret==count-1){ m_listTest.SetItemState(0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED); } else{ m_listTest.SetItemState(ret+1, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED); } } *pResult = 0; }

質問者が選んだベストアンサー

  • ベストアンサー
回答No.2

 こんにちは。  SetItemStateの最後のフラグが不完全なのでは。  以下ですと、選択状態に成るだけで、フォーカスまでは入らないです。  m_listTest.SetItemState(count-1, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED);  m_listTest.SetItemState(count-1, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);  とすればフォーカスも移動出来ます。  後は「↓または↑」を押して、「最下行⇔最上行へスクロール」する処理を行った場合は、pResultに1を入れて直ちに制御を戻します。  其れ以外の場合は、pResultは0です。   void CTestDlg::OnLvnKeyDown(NMHDR *pNMHDR, LRESULT *pResult) { LPNMLVKEYDOWN pNMLV = reinterpret_cast<LPNMLVKEYDOWN>(pNMHDR); // TODO: ここにコントロール通知ハンドラ コードを追加します。 int ret = m_listTest.GetNextItem(-1, LVNI_ALL | LVNI_SELECTED); if(ret==-1) return; int count = m_listTest.GetItemCount(); int LVIS = LVIS_SELECTED | LVIS_FOCUSED; if(pNMLV->wVKey == VK_UP) { //最上行にぶつかったら if(ret == 0) { m_listTest.SetItemState(count-1, LVIS, LVIS); *pResult = 1; return; } } if(pNMLV->wVKey == VK_DOWN) { //最下行にぶつかったら if(ret == count-1) { m_listTest.SetItemState(0, LVIS, LVIS); *pResult = 1; return; } } //其れ以外の位置なら m_listTest.SetItemState(ret, LVIS, LVIS); *pResult = 0; }

zigen8513
質問者

補足

ご回答ありがとうございます。 確かにご提示頂いた方法で、期待動作をさせることが出来ました。 ありがとうございました。 ここからは、方法についての疑問点なのですが、 >pResultに1を入れて直ちに制御を戻します。其れ以外の場合は、pResultは0です。 とありますが、pResultの値が0と1とでは、 どのような意味の違いがあるのかが分かりません。 もし宜しければ、お手数おかけしますが、 こちらの疑問点につきましても、お答え願えませんでしょうか?

その他の回答 (2)

回答No.3

 こんにちは。補足頂きました。此れを説明するのは困難です。  http://msdn.microsoft.com/ja-jp/library/1ssc6038(VS.80).aspx  イベントハンドラの種類やコントロールの種類によってまちまちですので、一概には言えないのですが、少なくともLVN_KEYDOWNイベントハンドラにおいて、簡単に言えば、pResult = 0でデフォルト処理へ渡され、pResult = 1で、自分自身の行った処理が全てになる、と言う事でしょうか。  其の侭デフォルト処理が行われると、折角自前で行った処理が上重ねされてしまい、台無しになる事があります。  例えば提示したサンプル中のpResult = 1の部分をpResult = 0にした場合、移動すると、もう一つ分余計に移動してしまいます(2個分移動してしまう)。  要は、自前で「最上行⇔最下行」へ移動する処理を自前で行っておきながら、デフォルト処理へ渡してしまうと、其の中でも移動処理が成される為、一つ分余計に移動する現象が起きる、と言った具合です。  MFCに関わらず、WindowsAPI関連の実装は、常にこの様な病的な現象が呪いの如くについて回ります。  此れに太刀打ちするは、経験や体験、実験を重ねる事でしかないと思っています。

zigen8513
質問者

お礼

ご回答ありがとうございます。 確かにpResult = 1の部分をpResult = 0にした場合、 期待動作をさせることは出来ませんでした。 勉強になりました。ありがとうございました。

  • chie65535
  • ベストアンサー率43% (8522/19371)
回答No.1

SetItemState()は「見た目」しか変わりません。見た目だけ変えても「今、本当に選ばれているもの」は変化しません。 「今、本当に選ばれているもの」はSetHotItem()じゃないと変わりません。 つまり、現状のままでは「見た目だけ最上行⇔最下行に行き来してるように見えるけど、本当に選ばれているものは何も変わってないから、その後もおかしな動きをしているように見える」のです。 試してないですが、SetHotItem()を行うと、自動的に見た目も変わるかも知れません(SetItemState()を呼ばなくても良いかも知れない?) なお、SetHotItem()を行う時に「選ぼうとしたアイテムがリストの外にスクロールアウトして見えてない状態」の時は、自動でスクロールしてくれないかも知れないので、その場合、EnsureVisible()を使って、選ぼうとしたアイテムがリスト内に表示されるようにすれば、スクロールインしてくると思います。 まとめると、選択アイテムを変更する場合は SetItemState()で見た目を変える。 SetHotItem()で選択アイテムを変える。 EnsureVisible()で必ず見える状態にする。 と言う3つの処理をセットで行わないと駄目だと思います。

zigen8513
質問者

お礼

ご回答ありがとうございます。 複数処理を組み合わせることで、実現可能なのですね。 また検証してみたいと思います。

関連するQ&A

  • VC++のLV_KEYDOWNについて

    キーボードのショートカット処理の関数を作成しているのですが、Ctrl + sでデフォルトの保存処理ではなく 別の処理を作成したいと考えております。 ですが下記のように実装してもCtrl + sを検出できません。(Ctrl + a)は検出するのですが。 何か原因があるのでしょうか? ご存知の方がいましたらご教授いただけないでしょうか? void OnKeydown(NMHDR* pNMHDR, LRESULT* pResult) { LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNMHDR; SHORT nCtrl = 0; switch(pLVKeyDow->wVKey) { case 's': case 'S': nCtrl = GetKeyState(VK_CONTROL); if(nCtrl >= 0) { // Ctrlキーが押下されていない場合 break; } // Ctrl + sの処理 break; case 'a': case 'A': nCtrl = GetKeyState(VK_CONTROL); if(nCtrl >= 0) { // Ctrlキーが押下されていない場合 break; } // Ctrl + aの処理 break; } *pResult = 0; }

  • MSGFILTER lParmで得られるキーコード?

    いつもお世話になっております。 以下のような関数で押されたキーを判定しています。 lParmで得られるキーコードはVK_??のコードと違うようなのですが、 (キーボードの並び方順になってる?) これをVK_??に変換するような方法はあるのでしょうか? (本当はSHIFTデータもプラスして文字コードに変換したいのですが・・・) 何か良い方法があればアドバイスお願いいたします。 void CCtypeView::OnMsgfilterEdit1(NMHDR* pNMHDR, LRESULT* pResult) { MSGFILTER *pMsgFilter = reinterpret_cast<MSGFILTER *>(pNMHDR); if( pMsgFilter->msg == WM_KEYDOWN ) { SHORT sho = GetAsyncKeyState(VK_SHIFT); TRACE( "%d : \nWM_LBUTTON x=%x,y=%d sho=%x", pMsgFilter->msg, HIWORD( pMsgFilter->lParam), LOWORD( pMsgFilter->lParam), sho ); } *pResult = 0; }

  • テキストボックスからフォーカス外す

     editBox = CreateWindow(   "EDIT",   "あああ",   SS_CENTER | WS_CHILD | WS_VISIBLE,   0,0,100,20,   hWnd,   NULL,   hInstance,   NULL); テキストボックスのフォーカスを外すにはどうしたら いいんですか? ESCキーでフォーカスを外そうと思ったら、 テキストボックスにフォーカスがあるせいで case WM_KEYDOWN が動作しません。 フォーカスを外すというソース自体も分かっていません。  case WM_KEYDOWN:   if( wParam == VK_ESCAPE )    ?  break;

  • ListViewの複数項目削除で再度質問です

    ArrayListのソートの件とListViewのおかしな挙動は解決できたのですが 最初に質問して解決したはずの削除の部分で再び分からなくなって しまったので質問させて頂きます。 今回は本体に今までのコードを正式に組み込んで、動作の確認を 行っていたのですが、複数件(連続や個別選択)のデータ削除を 行おうとしたら意図しないデータ(未整列状態の並びのデータ)が 削除されてしまうという事態が発生してしまいました。 これはどうすれば解決できるのでしょうか? また複数件の未読既読を制御(選択された項目に対してフラグを 入れ替える)するのも同様の方法で出来るのでしょうか? 環境はVS2005(C#)、.NET Framework2.0です。 以下に問題の削除時のコードを載せます。 private void menuDelete_Click(object sender, EventArgs e) { // 選択アイテムが0のときは反応にしない if(listView1.SelectedItems.Count == 0){ return; } if(listView1.Columns[0].Text == "差出人"){ // 受信メールのとき for (int sel_index = listView1.Items.Count - 1; sel_index > -1; sel_index--) { if (listView1.Items[sel_index].Selected) { collectionMail[DELETE].Add(collectionMail[RECEIVE][sel_index]); collectionMail[RECEIVE].RemoveAt(sel_index); } } this.textBody.Text = ""; } else if(listView1.Columns[0].Text == "宛先"){ // 送信メールのとき for (int sel_index = listView1.Items.Count - 1; sel_index > -1; sel_index--) { if (listView1.Items[sel_index].Selected) { collectionMail[DELETE].Add(collectionMail[SEND][sel_index]); collectionMail[SEND].RemoveAt(sel_index); } } this.textBody.Text = ""; } else if(listView1.Columns[0].Text == "差出人または宛先"){ // 削除メールのとき if(MessageBox.Show("選択されたメールは完全に削除されます。\nよろしいですか?", "確認", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation) == DialogResult.OK){ for (int sel_index = listView1.Items.Count - 1; sel_index > -1; sel_index--) { if (listView1.Items[sel_index].Selected) { collectionMail[DELETE].RemoveAt(sel_index); } } this.textBody.Text = ""; } } // ツリービューとリストビューの表示を更新する UpdateTreeView(); UpdateListView(); // 選択している位置がリストの件数よりも少ないとき if(currentRow < listView1.Items.Count){ // 選択していた位置-1の行にフォーカスを当て直す listView1.Items[currentRow - 1].Selected = true; listView1.Items[currentRow - 1].Focused = true; listView1.SelectedItems[0].EnsureVisible(); listView1.Select(); listView1.Focus(); } else{ // リストの件数が1以上の時 if(listView1.Items.Count > 0){ // ListViewの行数位置-1の行にフォーカスを当て直す listView1.Items[listView1.Items.Count - 1].Selected = true; listView1.Items[listView1.Items.Count - 1].Focused = true; listView1.SelectedItems[0].EnsureVisible(); listView1.Select(); listView1.Focus(); } } }

  • アドレス

    利用環境はMFCのVC++6.0です。 void CTestDlg::OnDblclkList(NMHDR* pNMHDR, LRESULT* pResult) { CString str; int num=m_list.GetSelectionMark(); str=m_list.GetItemText(num,0); if(Tstr!=NULL){ *Tstr=str; } CTest2 dlg2; dlg2.DoModal(); ・ ・ ・ } ヘッダ:CString *Tstr; とすると*Tstr=str;がおかしいらしく、アドレスがきちんと渡っていないようです。Tstr!=NULLの所ではアドレスは0xcccccc"???"となっていて if文は意味をなしません。 値を受け取るダイアログでは CTest Tdlg; CString aa; Tdlg.Tstr=&aa; MessageBox(aa); としています。 TestDlgのリストビューの内容をうけとりそれを新しくダイアログを作って そちらに移すプログラムです。

  • こんにちは

    こんにちは java初心者です。早速ですが質問させていただきます。 スペースキーを押したときに、ラベルにテキストを表示させる意図で 以下のようなコードを作りましたが、うまくいきません。 どなたか、ダメな点を指摘していただけないでしょうか。 よろしくお願いします。 import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; class KeyClass extends KeyAdapter { int keyshot; public KeyClass() { keyshot = 0; } public void keyPressed(KeyEvent e) { int kc = e.getKeyCode(); if( kc == KeyEvent.VK_SPACE) { if( keyshot == 0 ) { keyshot = 2 ;} else keyshot = 1; } } public void keyReleased(KeyEvent e) { int kc = e.getKeyCode(); if( kc == KeyEvent.VK_SPACE) { keyshot = 0; } } public int checkShotKey() { int ret = keyshot; if(keyshot == 2) keyshot =1; return ret; } } import java.applet.Applet; import java.awt.*; import java.awt.event.*; /*<applet code="keytest.class" width="300" height="100"></applet> */ public class keytest extends Applet implements Runnable { KeyClass keyclass; Thread th; Label label1; Label label2; String st1; int shotkey_state; int count; public void init() { setBackground( new Color(120,60,80) ); keyclass = new KeyClass(); addKeyListener(keyclass); label1 = new Label("スペース押してね"); label2 = new Label(); add(label1); add(label2); th = new Thread(this); th.start(); shotkey_state=0; } public void run() { while(true) { try{ Thread.sleep(1000); for(count=0; ; count++) { shotkey_state = keyclass.checkShotKey(); if( (shotkey_state==1 || shotkey_state==2)&&(count%4==0) ) { st1 = "スペース押したよ!!"; } else st1 = ""; label2.setText(st1); } } catch(InterruptedException ie) {} } } }

    • ベストアンサー
    • Java
  • C言語 

    #include <stdio.h> int main(void) { int ch; int count = 1; while( (ch = getchar()) != EOF) { if(ch == '\n') count++; } printf("行数は%dです。", count); return(0); } このコードの if(ch == '\n') count++; の部分がわかりません。 aを入力したとしてchにaが入っても、a=='\n'にはならないと思うのですが、実行して見ると行数がカウントされます。 (ch=='\n')というのはchと改行が同じなら行数を1増やすと言う事になると思うのですがよくわかりません。 教えてくださいm(_ _ )m

  • visualstudioについて

    visual studio2008 c++ を使用しています. ダイアログ形式でアプリケーションを作成しています. ボタンを押すと画像のようなアプリケーションを起動してenterキーを送るようにしたいのですがうまく出来ません. void CMy6Dlg::OnBnClickedButton1() { // TODO: ここにコントロール通知ハンドラ コードを追加します。 HINSTANCE ret = ShellExecute(m_hWnd, "open", "---Release\\scip_20_gd.exe", NULL, NULL, SW_SHOW); HWND hWnd = ::FindWindowEx(NULL, NULL, NULL, "---Release\scip_20_gd.exe"); ::SendMessage(hWnd,WM_SETFOCUS,0,0); ::SendMessage(hEdit, WM_KEYDOWN, VK_RETURN,0); if (ret <= (HINSTANCE)32) AfxMessageBox("シェル処理ができません.", MB_OK); } よろしくお願いします.

  • 基本選択法

    宜しくお願いします。 基本選択法のプログラムを書いていますが、コンパイルは通りますが、うまくソートされません。 改良点をご教示ください。 #課題ではありません。 #include <stdio.h> int getMin(int in[], int n, int a); void swap(int *m, int *n); int main(void) {   int a[10] = { 84, 121, 43, 93, 140, 83, 14, 93, 181, 58};   int i, j, k;   for(i=0; i<10; i++){     k = getMin(a, 10, i);     swap(&a[i], &a[k]);     for(j=0; j<10; j++){       printf("a[%d] = %d ", j, a[j]);     }     printf("\n");   }   return 0; } void swap(int *m, int *n) {   int tmp;   tmp = *m;   *m = *n;   *n = tmp; } int getMin(int in[], int n, int a) {   int i, ret, min;   min = in[a];   ret = 0;   for(i = a; i < n; i++)   {     if(in[i] < min){       min = in[i];       ret = i;     }   }   return ret; }

  • ロータリーエンコーダがうまく動かない.

    よろしくお願いします. arduino UNOと秋月で購入したロータリーエンコーダ(RE-160F-40E3-(L)A-24P)を用いて, シリアルモニタ上で数値のインクリメント・デクリメントを見ようとしています. 初心者のため, http://www.ct-robo.sakura.ne.jp/371 を参考にまずはやっています. 配線は同じ,ソースはほんの少しだけいじっていますが, デクリメントされるのに,インクリメントされません. ソースはこんなかんじです. // ロータリーエンコーダーの配線に合わせる int dRotAPin = 2; int dRotBPin = 4; int R_count = 0; //時刻表示 int minute = 0; int second = 0; int msecond = 0; // ロータリーエンコーダーの状態を記憶する // 割り込み中に変化する変数はvolatileはをつけて宣言する volatile int m_nOldRot = 0; volatile int m_nValue = 0; void setup() { // ピンの設定 // INPUTモードにします。 pinMode(dRotAPin, INPUT); pinMode(dRotBPin, INPUT); // プルアップを有効にします digitalWrite(dRotAPin, HIGH); digitalWrite(dRotBPin, HIGH); // 外部割り込みを設定します // D2ピンが 変化 した時にrotRotEnd()を呼び出します attachInterrupt(0, rotRotEnc, CHANGE); //シリアル通信速度 Serial.begin(9600); } void loop() { //シリアルモニター角度表示 if(m_nValue == 1){ R_count++; m_nValue = 0; //if(R_count == 12){ // R_count = -11; //} } if(m_nValue == -0.5){ R_count--; m_nValue = 0; //if(R_count == -12){ // R_count = 11; //} } //Serial.println(R_count); //delay(10); Serial.print("m_nValue:"); Serial.print(m_nValue); Serial.print("---m_nOldRot:"); Serial.println(m_nOldRot); //delay(500); } // 外部割り込みから呼び出される変数 void rotRotEnc(void){ if(!digitalRead(dRotAPin)){ // ロータリーエンコーダー回転開始 if(digitalRead(dRotBPin)){ //右回転 m_nOldRot = 'R'; }else{ //左回転 m_nOldRot = 'L'; } }else{ // ロータリーエンコーダー回転停止 if(digitalRead(dRotBPin)){ if(m_nOldRot == 'L'){ // 左回転の時の処理 m_nValue--; } }else{ if(m_nOldRot == 'R'){ //右回転の時の処理 m_nValue++; } } // ここでロータリーエンコーダーの状態をクリア m_nOldRot = 0; } } 完全にインクリメントされないわけではなく, 何回か回している時に何度かだけはインクリメントされるときがあります. http://just-k.mydns.jp/blog/?p=6619 このサイトのやり方も参考に,同じように回路を組んで動作させてみたのですが, やはりインクリメントされません. 解決策ありましたら教えていただきたいです. よろしくお願いいたします.

専門家に質問してみよう