FC2ブログ

VS2017-はじめの1/10歩(15):スクロールとスワイプ

   プログラミング [2019/09/08]
VS2017を使うんだけど慣れてないからVS6でまずは作ってました。
さらに、言わばWin32アプリです。
両方の環境(クロス環境)でコンパイルできるソースとして書いてます。
これまでの書き込みも含めると、

→ナレーション再生と音声認識を使う
  →そのため音声データの作り方(多言語対応)
  →ナレーション(.WAV)再生のサンプルコード
  →音声認識のサンプルコード
→VS6で作ったC/C++コードをVS2017に持って行って.cppレベルで共通にする方法
→ダイアログベースのプログラム
  →背景に画像(.BMP)を使う
  →ラベルを透かす
  →ボタンの色を変える
  →Windowsタブレット上でピンチイン/アウト:拡大縮小
  →タブレット画面の回転への対応
  →カスタムなチェックボックス作成
  →ボタンに画像を貼付ける
  →スクロールとスワイプ←今回

となってます。

今回は、さる的に毎回引っかかるスクロールの話です。

今回作ってたタブレット向けのアプリには、スクロール動作に影響のある以下の機能課題/仕様がありました。
・ダイアログの背景を単純な塗りつぶしではなく、画像を貼り付けるようにします。
・画面のサイズは、デスクトップの横方向サイズに合わせて可変です。
・ダイアログ内のレイアウト(表示項目)がデータに応じて縦方向に可変です。
・スクロールは、縦方向にのみサポートします。
・スワイプでのスクロールをサポートします。
多分、読んでもイメージしにくいかと思いますが、ともかく元のダイアログウィンドウの表示比率がコロコロ変わるので、
それに見合ったスクロール制御の処理ルーチンが必要だということです。


◆スクロール制御の基本
Windowsでは、スクロールを制御する際、まずはSCROLLINFO構造体を使ってSetScrollInfo()関数でスクロールに関連する情報をシステム側に通知します。
これをやれば、スクロールバーの操作には勝手に反応してスクロール制御をやってくれるのか?といえば、そうではない。
スクロール関連のイベント(WM_VSCROLL/WM_HSCROLL&SB_LINEDOWN/SB_LINEUP/SB_PAGEDOWN/SB_PAGEUP/SB_THUMBPOSITION/SB_THUMBTRACK)は、個別にプログラム内でScrollWindow()/SetScrollPos()/UpdateWindow()等で処理してあげないといけない。
・ScrollWindow()
  :スクロール対象のウインドウの中身の移動
   (無効領域(今まで見えてなかった部分)の再描画動作は含まない。)
・SetScrollPos()
  :スクロールバーの表示状態の変更
・UpdateWindow()
  :ウィンドウ内容の再描画を促す
   (無効領域(今まで見えてなかった部分)の描画)

なんで、こんなに面倒なの?と素朴な疑問を感じるのですが、そうなってるんです。

◆ローカルなスクロール情報
上記の制御をプログラム内で分かりやすく管理しようと思って以下のような、構造体(テーブル)を使ってます。
//----------------------------------------------
//スクロール関係
typedef struct _myscroll_inf {
INT fullh; //本来の表示の高さ
INT disph; //見えている部分の高さ
INT range; //表示されていない部分の高さ
INT pos; //現在の表示縦位置
INT dy; //移動量の単位
INT boxh; //スライダーのサイズ※実は・・・
} MYSCROLLINF, *PMYSCROLLINF;
//----------------------------------------------


MYSCROLLINF内にboxhという名前のメンバを用意してあります。
これは、システム側で準備しているSCROLLINFO内のnPageというメンバがあって、どこぞのサイトの説明を見ると「スライダーのサイズ」という説明があったので、それを単純に解釈して準備しました。
ただし、「サイズ」とは言うものの単位のある「サイズ」ではないということを理解できていませんでした。
実際のスライダーのサイズはコントロール側がSCROLLINFO内の他メンバとの計算で決めているようです。
なので、このboxhは(セットはしてるけど)使われてません。

もうちょっとさるの解釈を説明すると、
SCROLLINFOのnMin/nMaxは、nPosの取り得る範囲を示しています。
nPosはスライダーの位置を示します。
nPageは、nMin~nMaxの何個分を1ページと扱うかを示すもので、1ページとして見せるサイズです。
SCROLLINFOは、あくまでもスクロールバーの見せ方を指定するものであって、nMin、nMax、nPage、nPosの値に単位の概念はないと思ってください。

なので、実際のスクロールバーの稼動範囲の大きさがH(pixel)だとすれば、スライダーの実際の大きさHsはおそらく
 Hs=H/((nMax-nMin+1)/nPage) だと予想します。
なので、スクロールバーのサイズを変えれば、スライダーのサイズは変わる。
バー全体に対する大きさの比率は変わらないと思ってます。(試してないので、予想です。)

20190908_1.jpg

前にも説明しましたが、実際のウインドウの内容のスクロールは別のAPIでpixel単位で制御しなくちゃならないので、
単位なしのSCROLLINFOと、同時に実際のpixel単位の実際の表示域で2系統の数値が出てくることになります。
それはちょっと混乱の元だと思ったので、SCROLLINFOにはどうせ単位の概念がないのだから、pixel単位の数値で指定することにしました。

それでは、処理サンプルです。

◆ウィンドウプロシジャ内の処理
//----------------------------------------------
case WM_INITDIALOG:
{


//ResetScrollInfo(・・・); //※1

return TRUE;
}



//****** スクロール関連
case WM_SIZE:
//lParam:上位がウィンドウの高さ
SetMyScrollInfo(hDlg, (INT) HIWORD(lParam), &g_ScEdit);
return TRUE;
case WM_VSCROLL:
//wParam:下位がスクロールタイプ、上位がスクロール位置
MoveMyVScroll(hDlg, LOWORD(wParam), HIWORD(wParam), &g_ScEdit);
return TRUE;
case WM_MOUSEWHEEL:
if ((SHORT) LOWORD(wParam) == MK_CONTROL) {
//**** ズーム処理
}
else {
//**** スクロール処理
if ((SHORT) HIWORD(wParam) > 0)
PostMessage(hDlg, WM_VSCROLL, SB_PAGEUP, 0);
else
PostMessage(hDlg, WM_VSCROLL, SB_PAGEDOWN, 0);
}
break;



//----------------------------------------------


※1:このブログの「VS2017-はじめの1/10歩(12):タブレット画面の回転への対応」で、RelocationControlles()という関数経由でコールする例を載せてあります。
それをご参照ください。

・ウィンドウのサイズが変更された場合(WM_SIZE発生)のSCROLLINFOの設定しなおしは、SetMyScrollInfo()関数
・スクロールバー操作等で発生するイベント(WM_VSCROLL)は、MoveMyVScroll()関数
・マウスホイール操作(WM_MOUSEWHEEL)イベントでは、WM_VSCROLLイベントを自分自身に発行するようにしています。

・g_ScEditというMYSCROLLINF型のテーブルを外部変数として確保しています。
 複数のダイアログを同時制御するような場合は、ダイアログ毎にMYSCROLLINF型のテーブルを準備する必要があります。
 (これも、本当はクラス化すれば、もっとスッキリするんですけど・・・。先送りです。)

WM_MOUSEWHEELイベントは、VS6の普通の状態ではサポートされていなかったようで、
以下の定義を共通ヘッダ等に入れる必要がありました。(昔から使ってたので、たぶん。)

//----------------------------------------------
//******************************************************************************
// マウスホイールサポート用(V6.0)
#ifndef _INC_WHEELMOUSE_H_
#define _INC_WHEELMOUSE_H_
#ifndef WM_MOUSEWHEEL
#include "zmouse.h"
#endif
#endif
//----------------------------------------------


実際の処理関数の中身は・・・


◆スクロール関係の処理関数
//----------------------------------------------
//name :ResetScrollInfo
//function :スクロール情報のリセット
//parameter :pSc -スクロール情報テーブル
// iScr_h -スクリーンの高さ
// iMax_y -対象ダイアログの最下部のコントロールy座標
// ※ただし、スクロールされた状態で得られた値
// flExp -元のウィンドウサイズからの拡大率
//global :なし
//return :なし

VOID ResetScrollInfo(
PMYSCROLLINF pSc,
INT iScr_h,
INT iMax_y,
float flExp)
{
INT scrlc; //スクロール移動数

//スクロール関連情報の初期化
pSc->disph = iScr_h - GetSystemMetrics(SM_CYDLGFRAME) * 2;
pSc->fullh = max(pSc->disph, iMax_y + (INT)(flExp * 20));
pSc->range = pSc->fullh - pSc->disph;
pSc->pos = 0;
pSc->dy = (INT)roundf((float)20 * flExp); //元の移動量を20
scrlc = pSc->range / pSc->dy;
if (scrlc <= 0) scrlc = 1;
pSc->boxh = (pSc->disph - GetSystemMetrics(SM_CYVSCROLL) * 2) / scrlc;

return;
}

//----------------------------------------------
//name :SetMyScrollInfo
//function :スクロール情報の再設定
//parameter :hDlg -親ダイアログハンドル
// iClienth -表示領域(クライアント領域)の高さ
// pSc -スクロール情報
//global :なし
//return :なし

VOID SetMyScrollInfo(
HWND hDlg,
INT iClienth,
PMYSCROLLINF pSc)
{
SCROLLINFO scinf;

pSc->disph = iClienth;
pSc->range = pSc->fullh - pSc->disph;
pSc->pos = min(pSc->pos, pSc->range);
scinf.cbSize = sizeof(SCROLLINFO);
scinf.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
scinf.nMin = 0;
scinf.nMax = pSc->fullh - 1;
scinf.nPage = pSc->disph;
scinf.nPos = pSc->pos;
SetScrollInfo(hDlg, SB_VERT, &scinf, TRUE);
InvalidateRect(hDlg, NULL, FALSE);

return;
}

//----------------------------------------------
//name :MoveMyVScroll
//function :スクロールする
//parameter :hDlg -親ダイアログハンドル
// wScTyp -スクロールタイプ
// wNewPos -新規のスクロール位置
// pSc -スクロール情報
//global :なし([u]g_ScXxxx)
//return :なし

VOID MoveMyVScroll(
HWND hDlg,
WORD wScTyp,
WORD wNewPos,
PMYSCROLLINF pSc)
{
INT dy;

switch (wScTyp) {
case SB_LINEDOWN:
dy = pSc->dy;
break;
case SB_LINEUP:
dy = -pSc->dy;
break;
case SB_THUMBPOSITION:
dy = (INT) wNewPos - pSc->pos;
//スクロールバーのドラッグは、ドロップするまでSB_THUMBTRACK、ドロップすると
//SB_THUMBPOSITIONが発生する。一方、タッチのスワイプは、細かく
//SB_THUMBTRACK&SB_THUMBPOSITIONが発生するため移動量が小さく画面のパタパタが
//激しくなる。よって、WM_GESTUREを取得してSB_THUMBPOTION時の移動量を増やす。
break;
case SB_THUMBTRACK:
dy = 0;
break;
case SB_PAGEDOWN:
dy = pSc->dy * 5;
break;
case SB_PAGEUP:
dy = -pSc->dy * 5;
break;
default:
dy = 0;
break;
}
//下方向(+)移動の場合、可動域は見えてない領域(range)-現在位置(pos)
//上方向(-)移動の場合、可動域は現在位置(pos)のマイナス値
dy = max(-pSc->pos, min(dy, pSc->range - pSc->pos));
if (dy != 0) {
ScrollWindow(hDlg, 0, -dy, NULL, NULL);
pSc->pos += dy;
SetScrollPos(hDlg, SB_VERT, pSc->pos, TRUE);
//※背景画像を全画面拡大描画(背景は動かない)にすると、UpdateWindowだと
//スクロールして追加表示部分のみを描画するためスクロールにより新たに
//見えた部分だけに動かない状態の画像が表示される。
//そこで、InvaridateRectで画面全体を再描画させたが、スクロール時に子コント
//ロールも再描画されるためパタパタした動きになる。ダイアログの属性で、
//Clip Children=True(WS_CLIPCHILDREN)を設定したが、今度はスクロール時に
//子コントロールの内容にゴミが表示される。
//結局、背景画像をタイル貼り描画する(背景も動く)ことで、UpdateWindowで
//再描画を促すこととした。
UpdateWindow(hDlg); //スクロール分しか背景が再描画されない。
//InvalidateRect(hDlg, NULL, FALSE);
}

return;
}

//----------------------------------------------
//name :SetDispPosition
//function :指定コントロールを基準とるす位置にスクロールする
//parameter :hDlg -親ダイアログハンドル
// iCtlId -基準とするコントールのID
// pSc -スクロール情報
//global :なし([u]g_ScXxxx)
//return :なし

VOID SetDispPosition(
HWND hDlg,
INT iCtlId,
PMYSCROLLINF pSc)
{
RECT rect;
POINT pt;
INT sc_y;

GetWindowRect(GetDlgItem(hDlg, iCtlId), &rect);
pt.x = rect.left;
pt.y = rect.top;
ScreenToClient(hDlg, &pt);

sc_y = pSc->pos + pt.y;
SendMessage(hDlg, WM_VSCROLL, MAKEWPARAM(SB_THUMBPOSITION, sc_y), NULL);

return;
}
//----------------------------------------------


最後のSetDispPosition()関数は、何かを契機にダイアログ内のどこかにスクロールさせたいときに呼ぶ関数です。
「ダイアログ内のどこか」は目安のコントロールIDを指定するようになってます。
このページのサンプルでは使うところは出てきてません。

それと、MoveMyVScroll()のところで長々と紆余曲折コメントを書いてます。
背景描画の内容が気になる方は、 「VS2017-はじめの1/10歩(8):ダイアログの背景に画像を使う」をご参照ください。

その他の細かい処理内容の説明は省きます。
興味のある方は、温かい気持ちでよしなに解釈してやってください。


おっと、忘れそうでした。

◆スワイプ/パン動作でスクロールさせる処理
スワイプ/パン動作でスクロールさせるには、以下の2種類のメッセージのいずれかを処理すればいいようです。
・WM_GESTURE
・WM_TOUCH系?
後者を使用する場合は、RegisterTouchWindow()という前処理とUnregisterTouchWindow()という後処理が必要になります。
それぞれ、WM_INITDIALOGとWM_DESTORYメッセージ処理辺りでやればいいかと思います。
たしか、WM_TOUCH系の方が、いろんなタッチ処理に対応できそうだった気がしますが、
今回、WM_GESTUREで十分だったのでそちらを使うことにしました。
(前者の前処理/後処理は、WM_GESTUREを使う場合は、入れてはいけません。)

いずれにしろ、VS6では使えません。
なので、クロス環境ソースの場合は以下ような定義を入れる必要があります。

<共通ヘッダへの追加>
//----------------------------------------------
#if _MSC_VER < 1300

#else

#define MYTOUCH_VALID //タッチ系処理有効

#endif
//----------------------------------------------


スワイプ/パン動作でスクロールは、以下のメッセージ処理を追加するだけです。

<ウィンドウプロシジャ内への追加処理>
//----------------------------------------------

#ifdef MYTOUCH_VALID
//******* タッチ(ジェスチャ)イベント処理
case WM_GESTURE:
GESTUREINFO gstinf;
INT dy, ny;
gstinf.cbSize = sizeof(GESTUREINFO);
if (GetGestureInfo((HGESTUREINFO)lParam, &gstinf)
&& gstinf.dwID == GID_PAN) {
//パン終了、あるいは慣性動作開始でスクロールは終了
if (gstinf.dwFlags & (GF_END | GF_INERTIA)) {
g_ptPan.x = -1;
//****debug
//if (gstinf.dwFlags & GF_END) {
// MessageBox(hDlg, g_szMsgBuf, g_szMyTitle, MB_ICONINFORMATION | MB_OK);
//}
}
else {
if (g_ptPan.x != -1) {
//タッチ座標をピクセル座標に直すと1/100で小さ過ぎる。
//dy = TOUCH_COORD_TO_PIXEL(gstinf.ptsLocation.y - g_ptPan.y);
dy = gstinf.ptsLocation.y - g_ptPan.y;
//再描画によるパタパタ表示を抑えるために1移動値を超えない限りVSCROLLを送らない。
if (abs(dy) > g_ScEdit.dy) {
ny = g_ScEdit.pos - dy;
if (ny < 0) ny = 0;
else if (ny > g_ScEdit.range) ny = g_ScEdit.range;
SendMessage(hDlg, WM_VSCROLL, MAKEWPARAM(SB_THUMBPOSITION, ny), 0);
g_ptPan = gstinf.ptsLocation;
}
//****debug
//wsprintf(g_szMsgBuf + strlen(g_szMsgBuf), "\npos=%04d→%04d, f=%d", g_ScEdit.pos, dy, gstinf.dwFlags);
}
else {
//タッチ開始位置保持
g_ptPan = gstinf.ptsLocation;
}
}
CloseGestureInfoHandle((HGESTUREINFO)lParam);
return 0;
}
//****debug
//g_szMsgBuf[0] = 0x00;
break;
#endif

//----------------------------------------------


WM_GESTUREは、タッチをなぞるとかなり頻繁に発生します。
それは、タッチパネルの感度(座標)が細かいせいのようです。
なので、来たイベントをピクセル座標の感度に間引く処理を入れてあります。

その他の詳細説明は、やっぱり省きます。
ジェスチャ系の処理内容の詳細は、MSサイトやその他の説明してくれているサイトの方が詳しく解説してくれますから。


あと、
実際の指の動きですが、スクロールバー上のスライダーにタッチして下向きに指をスライドさせると、
:画面の内容は現在よりも下の内容に変化していきます。

一方、
スクロールバーやその他のコントロールが無いところをタッチして、下向きに指をスライドさせると、
:画面の内容は現在よりも上の内容に変化していきます。

「あたりまえでしょ。」
確かに間違いではないけど、なんかややこしいですよね。


描画は終わりにして、次回はHTTPでのファイル転送の話になります。
やっと。

では、この辺で。
m(__)m
スポンサーサイト





コメントの投稿

非公開コメント

カレンダー
01 | 2024/02 | 03
- - - - 1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 - -
プロフィール

さるもすなる

Author:さるもすなる
さるです。別HPサイト「さるもすなる」から侵食してきました。 山菜/きのこ、それとタイトルにしたPPバンド籠のことをメインに徒然に・・・・暇を持て余したさるの手仕事:男手芸のブログってことで。

最新記事
最新コメント
月別アーカイブ
カテゴリ
天気予報

-天気予報コム- -FC2-
本家のHPのトップ
山菜や茸の話です
PPバンドの籠作品と作り方です
投稿をお待ちしております



PVアクセスランキング にほんブログ村 にほんブログ村 ハンドメイドブログへ



マニュアルのお申し込み



検索フォーム
リンク
RSSリンクの表示
ブロとも申請フォーム

この人とブロともになる

QRコード
QR