MFCでアプリを書いていると、スクロール処理が必要になってくる場合が多々あります。
CScrollViewで行ける場合は良いのですが、自前でやらないといけない場合には、OnHScrollとOnVScrollをインプリメントしてやらないといけません。

これが結構面倒だなーといつも思っていたので使い回せるようなイベントハンドラを作ってみました。

bool Scroll(CWnd* pWnd, int nBar, UINT nSBCode, UINT nPos, int nLine /*= 1*/)
{
	SCROLLINFO info = {sizeof(SCROLLINFO), SIF_ALL};
	if (!pWnd->GetScrollInfo(nBar, &info)) {
		return false;
	}
	int nNewPos = pWnd->GetScrollPos(nBar);
	int nOldPos = nNewPos;

	switch (nSBCode) {
	case SB_LEFT:
		nNewPos = info.nMin;
		break;
	case SB_ENDSCROLL:
		break;
	case SB_LINELEFT:
		nNewPos -= nLine;
		break;
	case SB_LINERIGHT:
		nNewPos += nLine;
		break;
	case SB_PAGELEFT:
		nNewPos -= info.nPage;
		break;
	case SB_PAGERIGHT:
		nNewPos += info.nPage;
		break;
	case SB_RIGHT:
		nNewPos = info.nMax;
		break;
	case SB_THUMBPOSITION:
		nNewPos = nPos;
		break;
	case SB_THUMBTRACK:
		nNewPos = nPos;
		break;
	}

	pWnd->SetScrollPos(nBar, nNewPos);
	return pWnd->GetScrollPos(nBar) != nOldPos;
}

スクロール位置に変更があったときにtrueを返しますので、これを見て画面更新などを行います。

こんな感じで使います。

void CHogeHogeWnd::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if (Scroll(this, SB_HORZ, nSBCode, nPos, 16)) {
		// ここで画面更新など
	}
}

void CHogeHogeWnd::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if (Scroll(this, SB_VERT, nSBCode, nPos, 16)) {
		// ここで画面更新など
	}
}

page移動量は若干の調整が必要かもしれませんね。

てか、CScrollWnd的なものを作ってテンプレートっぽくしておくのがベストなんだろうなぁ。

適当にウィザードでプロジェクトを作成したのち、「カスタマイズ」機能でツールバーやメニューコマンドをドラッグ&ドロップしようと思ったら、動かない!なんて事がありましたので原因を探ってみたところ、プロジェクト作成ウィザードでActiveXのチェックを外してしまっていたのが原因のようです。

中は見ていませんが、おそらくカスタマイズ時のドラッグ&ドロップ処理はOLEのドラッグ&ドロップなんでしょうね。
なので、
CXXXXApp::InitInstanceの適当な所で「AfxOleInit()」を呼べばカスタマイズ機能が正常に動くようになります。

GradientFill APIのラッパー関数を作ってみました。
と言っても縦のグラデしか対応させていませんが。

void FillGrad(CDC* pDC, CRect rc, COLORREF colBegin, COLORREF colEnd)
{
	TRIVERTEX av[2] = {
		rc.left, rc.top, GetRValue(colBegin) << 8, GetGValue(colBegin) << 8, GetBValue(colBegin) << 8, 0xff00,
		rc.right, rc.bottom, GetRValue(colEnd) << 8, GetGValue(colEnd) << 8, GetBValue(colEnd) << 8, 0xff00,
	};
	GRADIENT_RECT gr = {0, 1};
	GradientFill(pDC->GetSafeHdc(), av, 2, &gr, 1, GRADIENT_FILL_RECT_V);
}

GradientFill APIは古いOSでは動きませんのでご注意ください。

仕事で開発を行っていると、他の人がエクセルファイルでデータ資料を作り、プログラマがその情報をアプリに組み込む、なんて事がよくあります。
一度CSVに書き出してからプログラムでそれを読み、使いやすいデータ(XMLやCのソース)にコンバートして利用するのが定石かと思いますが、エクセルファイルを直接読めればCSVに書き出す手間が多少軽減されます。
(そもそもVBAでコンバート処理書いちゃうという人は尊敬されれば良いと思います)

Visual C++6.0ではよくそういう事をやっていたのですが、Visual Studio 2008 + MFCだとどうやるんだろう?と思いましてちょっとやってみました。

メニューからクラスの追加を実行します。
xls00

「TypeLibからのMFCクラス」を選びます。
xls01

エクセルのタイプライブラリを選び、使うインターフェースを追加して(今回は面倒なので全部追加)完了ボタンを押します。
xls02

ちょっと生成に時間が掛かりますが、ヘッダーファイルが生成されます。
ただ、何故か#import文が入っていて、コンパイル時にエラー(名前の競合)が出てしまうので一括置換でコメントアウトします。
xls03

VC6ではできなかった芸当ですね(ソリューション内一括置換)
xls04

あとはごりごりコードを書けばエクセルの操作ができます。

既存のファイルを開いて、最初のシートのA1のテキストを取得するにはこんな感じになるかと思います。
(Excel 2007使用)

#include "CApplication.h"
#include "CWorkbooks.h"
#include "CWorksheets.h"
#include "CWorkbook.h"
#include "CWorksheet.h"
#include "CRange.h"

void CHogeHogeDlg::OnBnClickedButton1()
	CApplication App;
	if (!App.CreateDispatch(_T("Excel.Application"))) {
		ASSERT(0);
		return;
	}
	App.put_Visible(TRUE);

	CWorkbooks Workbooks = App.get_Workbooks();

	// 既存のエクセルファイルを開く
	COleVariant varNull;
	varNull.ChangeType(VT_NULL);
	CWorkbook Workbook = Workbooks.Open(_T("c:\\hogehoge.xls"), varNull, varNull, varNull, varNull, varNull, varNull, varNull, varNull, varNull, varNull, varNull, varNull,varNull, varNull);

	CWorksheets Worksheets = Workbook.get_Worksheets();
	CWorksheet Worksheet = Worksheets.get_Item(COleVariant((long)1));

	CRange Cells = Worksheet.get_Cells();
	CRange Cell = Cells.get_Item(COleVariant((long)1), COleVariant((long)1)).pdispVal;
	CString str = Cell.get_Text().bstrVal;
}

CRange::DialogBoxとかいう関数でコンパイルエラーが出ますが関数名変えるなどして回避します。

当然ながら実行する環境にエクセルがインストールされている必要があります。
また、使用したタイプライブラリと実行時のエクセルのバージョンが違うと、うまく動かない事があるかもしれません。うろ覚えですが。上位互換はあるかも?

もしいろんな操作をやりたい場合、エクセルのマクロのヘルプが参考になると思います。若干関数名は違いますが、構造などはほぼ同じです。

CSplitterWndと言えば、CView系のみスプリット表示するというイメージがありますが、ヘルプを見ていたら

静的分割ウィンドウを作成するとき、同時にすべてのペインを作成する必要があります。CreateView メンバ関数は、この目的のために使われます。しかし、さらにほかの非ビュー クラスも作成できます

と書いてあるではありませんか。

が、具体的にどうやって実装すれば良いかは書かれておらず、ソースを読んでいたところ、

  • 親にCSplitterWndのインスタンスを指定してコントロール等を作成
  • IDをCSplitterWnd::IdFromRowCol関数で算出した値にしておく

だけで良いみたいです。

m_wndSplitter.CreateStatic(this, 2, 1, WS_CHILD | WS_VISIBLE);
m_edit1.CreateEx(WS_EX_CLIENTEDGE, _T("Edit"), _T(""), WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_WANTRETURN | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_MULTILINE, CRect(0, 0, 300, 300), &m_wndSplitter, m_wndSplitter.IdFromRowCol(0, 0));
m_edit2.CreateEx(WS_EX_CLIENTEDGE, _T("Edit"), _T(""), WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_WANTRETURN | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_MULTILINE, CRect(0, 0, 300, 300), &m_wndSplitter, m_wndSplitter.IdFromRowCol(1, 0));

あとはm_wndSplitter自体をウィンドウ全体に広げて配置したりだとか、m_wndSplitter.SetRowInfoで行の高さを変更したりするだけです。
ダイアログベースのアプリでも気軽に使えそうですね。

記事検索

アーカイブ