EternalWindows
MCI / MCIWnd

Windows Multimediaの中で最も高水準な関数群は、MCIWndです。 MCIWndは、MCIのように各種マルチメディアファイルの違いをアプリケーションから抽象化し、 どのようなファイルでも統一的に再生できる方法を提供しています。 さらに、MCIWndではこのような再生の基盤だけではなく、 マルチメディアアプリケーションに有用となるUIも提供しています。 たとえば、マルチメディアアプリケーションでは、再生の現在位置を表すためにスクロールバーを 用いることがありますが、MCIWndを使用すればこのようなUIをウインドウに表示することができます。 次に、MCIWndによって提供されるUIを示します。

最も左に配置されたボタンを押すことで、ファイルを再生することができます。 その横にあるボタンは、ファイルを開いたり閉じたりする場合に使用します。 動画ファイルを開いた場合は、動画の内容がウインドウに表示され、 動画ファイル上でマウスの右ボタンを押すことで、再生スピードや音量を調整することができます。

MCIWndの核となる関数は、MCIWndCreateです。 この関数は、MCIWndのUIを持ったウインドウ(MCIWnd ウインドウ)を作成するため、 これを親ウインドウに貼り付ければよいことになります。 MCIWndCreateは、次のように定義されています。

HWND MCIWndCreate(
  HWND hwndParent,     
  HINSTANCE hInstance, 
  DWORD dwStyle,       
  LPSTR szFile         
);

hwndParentは、MCIWnd ウインドウの親ウインドウとするウインドウハンドルを指定します。 NULLを指定した場合は、独立したウインドウとして作成されます。 hInstanceは、インスタンスハンドルを指定します。 dwStyleは、MCIWnd ウインドウのウインドウスタイルを指定します。 ウインドウスタイルは既定で、WS_CHILD | WS_VISIBLE | WS_BORDERが含まれています。 szFileは、既定でオープンしたいメディアファイルの名前を指定します。 NULLを指定した場合は、明示的にファイルをオープンすることになります。 戻り値は、作成されたMCIWnd ウインドウのハンドルになります。

今回のプログラムは、MCIWndを使用してメディアファイルを再生します。 MCIWndを使用する場合は、vfw.hのインクルードとvfw32.libへのリンクが必要です。

#include <windows.h>
#include <vfw.h>

#pragma comment (lib, "vfw32.lib")

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndMci = NULL;

	switch (uMsg) {

	case WM_CREATE:
		hwndMci = MCIWndCreate(hwnd, ((LPCREATESTRUCT)lParam)->hInstance, 0, NULL);
		return 0;
	
	case WM_SIZE:
		MoveWindow(hwndMci, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

MCIWndCreateは、WM_CREATEで実行されています。 第1引数にウインドウのハンドルを指定しているため、 作成されたMCIWnd ウインドウは第1引数のウインドウに貼り付けられることになります。 また、そのサイズは親ウインドウのクライアント領域と同一になります。 これは、WM_SIZEでhwndMciに対してMoveWindowを呼び出しているからです。 WM_SIZEのlParamの下位ワードはクライアント領域の幅であり、 上位ワードはクライアント領域の高さです。

MCIWndを用いたプログラムは、基本的にMCIWndCreateの呼び出しだけで十分です。 後は作成されたMCIWnd ウインドウに対して、UI経由でオープンや再生を行うことになるでしょう。 予め再生したいファイルが決まっている場合は、MCIWndCreateを次のように呼び出します。

hwndMci = MCIWndCreate(hwnd, ((LPCREATESTRUCT)lParam)->hInstance, 0, TEXT("sample.avi"));
// MCIWndSetRepeat(hwnd, TRUE);
// MCIWndPlay(hwndMci);

この例では、MCIWnd ウインドウを作成すると共にsample.aviをオープンしようとしていますから、 UI経由でオープンを行う作業は省略されます。 また、MCIWndPlayマクロを実行した場合は、ファイルが直ちに再生されることになるため、 UI経由で再生を行う作業も省略されます。 MCIWndPlayマクロの前にMCIWndSetRepeatマクロを呼び出した場合、 ファイルがリピート再生されることになります。

MCIWnd ウインドウのカスタマイズ

既に述べてきたように、MCIWndはメディアファイルを再生するためのUIを表示するわけですが、 場合によってはこのUIを好まないこともあると思われます。 こうなった場合、MCIWndによる再生は断念しなければならないように思えますが、 必ずしもそうとは限りません。 たとえば、スクロールバーやその横にあるボタンなどを表示したくない場合は、 MCIWndCreateの第3引数にMCIWNDF_NOPLAYBARを指定すればよいだけです。 また、MCIWndでは、動画ファイルの再生時にマウスの右クリックからポップアップメニューを表示することができます。 この機能は、mciSendStringやmciSendCommandにはありませんから、 動画ファイルの再生はできるだけMCIWndで行った方がよいと思われます。 次に示すプログラムは、MCIWnd ウインドウ上で表示されるメニュー項目の一部をカスタマイズします。

#include <windows.h>
#include <vfw.h>

#pragma comment (lib, "vfw32.lib")

#define ID_OPEN 104
#define ID_CLOSE 105
#define ID_PLAY 2054
#define ID_REPEAT 1000

WNDPROC g_lpfnDefMciWndProc = NULL;

BOOL SelectVideoFile(HWND hwnd, LPTSTR lpszFileName);
HWND CreateMciWindow(HWND hwndParent, LPTSTR lpszFileName);
LRESULT CALLBACK MciWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {

	case WM_RBUTTONDOWN: {
		TCHAR szFileName[MAX_PATH];

		if (SelectVideoFile(hwnd, szFileName))
			CreateMciWindow(hwnd, szFileName);
		return 0;
	}

	case WM_DESTROY:
		CreateMciWindow(NULL, NULL);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

HWND CreateMciWindow(HWND hwndParent, LPTSTR lpszFileName)
{
	static HWND hwndMci = NULL;

	if (hwndMci != NULL) {
		SetWindowLong(hwndMci, GWL_WNDPROC, (LONG)g_lpfnDefMciWndProc);
		MCIWndStop(hwndMci);
		MCIWndDestroy(hwndMci);
	}

	if (lpszFileName == NULL)
		return NULL;

	hwndMci = MCIWndCreate(hwndParent, GetModuleHandle(NULL), MCIWNDF_NOPLAYBAR, lpszFileName);

	g_lpfnDefMciWndProc = (WNDPROC)GetWindowLong(hwndMci, GWL_WNDPROC);
	SetWindowLong(hwndMci, GWL_WNDPROC, (LONG)MciWndProc);

	MoveWindow(hwndMci, 0, 0, 400, 300, TRUE);

	return hwndMci;
}

BOOL SelectVideoFile(HWND hwnd, LPTSTR lpszFileName)
{
	OPENFILENAME ofn;

	lpszFileName[0] = '\0';

	ZeroMemory(&ofn, sizeof(OPENFILENAME));
	ofn.lStructSize = sizeof(OPENFILENAME);
	ofn.hwndOwner   = hwnd;
	ofn.lpstrFilter = TEXT("Video File (*.avi;*.mpg;*.wmv)\0*.avi;*.mpg;*.wmv\0\0");
	ofn.lpstrFile   = lpszFileName;
	ofn.nMaxFile    = MAX_PATH;
	ofn.lpstrTitle  = TEXT("動画ファイルの読み込み");
	ofn.Flags       = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

	if (!GetOpenFileName(&ofn))
		return FALSE;

	return TRUE;
}

LRESULT CALLBACK MciWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static BOOL bFirstMenuOpen = FALSE;
	static BOOL bRepeat = FALSE;

	switch (uMsg) {

	case WM_INITMENUPOPUP: {
		MENUITEMINFO mii;

		if (!bFirstMenuOpen) {
			mii.cbSize     = sizeof(MENUITEMINFO);
			mii.fMask      = MIIM_ID | MIIM_TYPE;
			mii.fType      = MFT_STRING;
			mii.wID        = ID_REPEAT;
			mii.dwTypeData = TEXT("リピート(&R)");
			
			InsertMenuItem((HMENU)wParam, ID_REPEAT, FALSE, &mii);

			bFirstMenuOpen = TRUE;
		}

		mii.cbSize = sizeof(MENUITEMINFO);
		mii.fMask  = MIIM_ID | MIIM_STATE;
		mii.fState = bRepeat ? MFS_CHECKED : MFS_UNCHECKED;
		mii.wID    = ID_REPEAT;

		SetMenuItemInfo((HMENU)wParam, ID_REPEAT, FALSE, &mii);
		
		break;
	}

	case WM_COMMAND:
		if (LOWORD(wParam) == ID_OPEN) {
			TCHAR szFileName[MAX_PATH];
			
			if (SelectVideoFile(hwnd, szFileName))
				CreateMciWindow(GetParent(hwnd), szFileName);
			bFirstMenuOpen = FALSE;
			return 0;
		}
		else if (LOWORD(wParam) == ID_PLAY) {
			MCIWndSetRepeat(hwnd, bRepeat);
			break;
		}
		else if (LOWORD(wParam) == ID_CLOSE) {
			CreateMciWindow(NULL, NULL);
			return 0;
		}
		else if (LOWORD(wParam) == ID_REPEAT) {
			bRepeat = !bRepeat;
			return 0;
		}
		break;
	}

	return CallWindowProc(g_lpfnDefMciWndProc, hwnd, uMsg, wParam, lParam);
}

初期状態でウインドウには何も表示されていませんが、 マウスの右ボタンを押すことでダイアログ経由から動画ファイルを選択することができます。 これによって、MCIWnd ウインドウが表示され、 ポップアップメニューの再生を押せば、実際にファイルが再生されることになります。 別のファイルを再生したい場合は、「開く」コマンドを選択することになりますが、 ここで表示されるダイアログは本来のダイアログとは異なっています。 GetOpenFileNameによって表示されるダイアログの方が普段から見慣れていますから、 このダイアログを表示できるようカスタマイズしています。 もう1つのカスタマイズは、「リピート」という独自の項目の追加です。 この項目が選択された状態で再生を実行した場合、 再生がリピートされます。

CreateMciWindowという自作関数は、MCIWnd ウインドウの親とするウインドウハンドルと、 オープンしたいファイル名を受け取ります。 まず、静的に宣言されたhwndMciがNULLでないかを確認し、 NULLでない場合は既に作成されているMCIWnd ウインドウを破棄します。 手順としては、MCIWndStopマクロで再生を停止し、 MCIWndDestroyでウインドウを破棄します。 続いて、MCIWndCreateで新しいMCIWnd ウインドウを作成します。 インスタンスハンドルはGetModuleHandleで取得可能であり、 第3引数にMCIWNDF_NOPLAYBARを指定しているため、MCIWnd独自のバーが表示されることはありません。 SetWindowLongでMCIWnd ウインドウに独自のプロシージャを指定しているのは、 ポップアップメニューが表示される際に送られるメッセージを取得するためです。 このような処理はウインドウのサブクラス化と呼ばれ、 メッセージの取得が不要になった場合は、元のプロシージャに戻すべきとされています。 よって、GetWindowLongで既定のプロシージャを指定し、 ウインドウを破棄する前にSetWindowLongで既定のプロシージャを設定します。 MoveWindowに指定するサイズは、自由に決定して構いません。

独自のプロシージャであるMciWndProcで処理すべきメッセージは2つあります。 1つは、WM_INITMENUPOPUPであり、これはポップアップメニューが表示されようとしている際に送られます。 bFirstMenuOpenがFALSEである場合は、初めてメニューがオープンされるということなので、 この時にInsertMenuItemで「リピート」という項目を追加するようにしています。 また、この項目が選択された場合は、bRepeatがTRUEになりますが、 この時にはSetMenuItemInfoで項目にチェックが付くことになります。 もう1つの処理すべきメッセージはWM_COMMANDです。 このメッセージは項目が選択された場合などに送られ、 wParamの下位ワードには項目の識別子が格納されています。 これがID_OPENである場合は「開く」コマンドが押されたことを意味するので、 SelectVideoFileという自作関数ででGetOpenFileNameのダイアログを表示し、 新しいMCIWnd ウインドウを作成するようにしています。 また、「開く」コマンドの本来の処理が行われないように、 breakではなくreturn 0を実行するようにします。 少し注意しなければならないのは、 ID_OPENなど定義の値が実際に該当コマンドの識別子と一致するかどうかです。 現在の環境で正しく動作しても、別のWindowsのバージョンによっては異なっている可能性があります。 ID_REPEATに関しては、独自に追加したコマンドを表しているため、 これは基本的に自由に決定することができます。



戻る