« 2009年03月 | メイン | 2010年05月 »

前の10件 1  2  3  4  5  6  7

2009年04月 記事一覧

2009年04月19日

第59回 派生元をCAxWindowからCAxHostWindowに変える (タブブラウザーを作る)

tabbrowser269.gif
今回から数回に分けてフォーカス関連のバグ修正をする。現状ではドロップダウンリストが正常に選択できなくなったり、アドレスバーにフォーカスを置いたままだとIEコントロールの描画が更新されないなどなどの不具合が多い。これらをなんとか治す。

これまで、ビューウインドウはATL/WTLアプリケーションウイザードの生成したCAxWindow派生のものを利用していた。このままだと修正が大変なので、派生元をCAxHostWindowに変更する。

CAxWindowはIEコントロールなどのActiveXコントロールをホストするための簡単な仕組みが備わっているのみだが、CAxHostWindowはかなり多くの実装がされていて便利なためだ。
class CTabBrowser100View : public CComObject<CAxHostWindow>	//■変更
	,public IDispEventImpl<SINKID_EVENTS, CTabBrowser100View, &DIID_DWebBrowserEvents2>
	,public CIEUtility
{

tabbrowser270.gif CAxHostWindowはCWindowImpl派生になっているため、このままではビューウインドウ内でメッセージマップが利用できない。そのためメッセージをCAxHostWindowよりも先に取得してビューウインドウに流仕込むためのウインドウCReflectWndを作成する。
	//■追加
	//CAxHostWindowの場合、メッセージハンドラが使えなくなるので
	//このウインドウをサブクラス化してメッセージハンドラが使えるようにする
	class	CReflectWnd : public CWindowImpl<CReflectWnd>
	{
		CTabBrowser100View*	_pView;
	public:
		CReflectWnd(CTabBrowser100View* pView)
		{
			_pView = pView;
		}
		BEGIN_MSG_MAP(CReflectWnd)
			CHAIN_MSG_MAP_MEMBER((*_pView))
		END_MSG_MAP()
	};

	CReflectWnd	_wndReflect;		//■追加

public:
	DECLARE_WND_SUPERCLASS(NULL, CAxWindow::GetWndClassName())	//CAxHostWindow::ではなくCAxWindow::のままなことに注意


	CTabBrowser100View(CTabView* pTabView)
		: _wndReflect(this)						//■追加
	{
		_pTabView = pTabView;
		_bPageError = false;
	}


	BEGIN_SINK_MAP(CTabBrowser100View)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_NEWWINDOW2, OnNewWindow2)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocumentComplete)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_STATUSTEXTCHANGE, OnStatusTextChange)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_DOWNLOADCOMPLETE, OnDownloadComplete)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_COMMANDSTATECHANGE, OnCommandStateChange)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_DOWNLOADBEGIN, OnDownloadBegin)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_PROGRESSCHANGE, OnProgressChange)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_PROPERTYCHANGE, OnPropertyChange)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_TITLECHANGE, OnTitleChange)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_NAVIGATECOMPLETE2, OnNavigateComplete2)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_NAVIGATEERROR, OnNavigateError)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_PRIVACYIMPACTEDSTATECHANGE, OnPrivacyImpactedStateChange)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_NEWWINDOW3, OnNewWindow3)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_SETSECURELOCKICON, OnSetSecureLockIcon)
	END_SINK_MAP()

	BEGIN_MSG_MAP(CTabBrowser100View)
		MESSAGE_HANDLER(WM_DNP_SCRIPTERROR, OnDnpScriptError)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		MESSAGE_HANDLER(WM_DNP_SHOWSCRIPTERROR, OnShowScriptError)
		MESSAGE_HANDLER(WM_DNP_SHOWPRIVACYREPORT, OnShowPrivacyReport)
		MESSAGE_HANDLER(WM_DNP_SHOWPSECURITYREPORT, OnShowSecurityReport)
		MESSAGE_HANDLER(WM_DNP_SHOWZONECONFIGURE, OnShowZoneConfigure)
		CHAIN_MSG_MAP(CAxHostWindow)		//■追加
	END_MSG_MAP()

tabbrowser271.gif
そしてCreate()の処理を変更する。CAxHostWindow::Create()は使わずに、CWindow::Createを、CAxWindow(CAxHostWindowではない)のクラス名を使って呼び出してウインドウ生成をする。

このままだとCAxHostWindowにIEコントロールが入らないので、AttachControlとSetSiteで作成したIEコントロールをCAxHostWindowに割り当てる。
	//■変更
	//Create関数のオーバーライド。内部でIEの取得、接続などを行う
	HWND Create(HWND hWndParent, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL,
			DWORD dwStyle = 0, DWORD dwExStyle = 0,
			_U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL)
	{
		HWND	hWnd;

		//__super::ではなく、CWindow::のCreateをCAxWindow::GetWndClassName()指定で利用
		hWnd = CWindow::Create(CAxWindow::GetWndClassName(), hWndParent, rect, szWindowName, dwStyle, dwExStyle, MenuOrID, lpCreateParam);
		if(hWnd == NULL)
			return	NULL;			//Create失敗

		_wndReflect.SubclassWindow(m_hWnd);		//メッセージマップが使えるようにこのウインドウをサブクラス化

		//CWindow::Createで作れたコントロールをこのウインドウにアタッチ
		{
			CComPtr<IUnknown>	pIUnknown;

			AtlAxGetControl(m_hWnd,&pIUnknown);
			if(pIUnknown)
				AttachControl(pIUnknown,m_hWnd);

			QueryControl(IID_IWebBrowser2,(void**)&_pIWebBrowser2);		//_pIWebBrowser2にこのビューに関連づいているIEをセットする
			if(_pIWebBrowser2 == NULL)
				return	hWnd;			//WebBrowser取得失敗

			//サイトも設定
			pIUnknown = NULL;
			QueryHost(&pIUnknown);
			SetSite(pIUnknown);
		}

		Advise(_pIWebBrowser2);		//IEとの接続


		//jacascriptや画像ダウンロードのコントロール設定
		{
			CComPtr<IOleObject>		pIOleObject;
			CComObject<CAmbientControl>*		pCAmbientControl;
			CComPtr<IAxWinAmbientDispatchEx>	pIAxWinAmbientDispatchEx;

			//ダウンロードコントロールの準備
			CComObject<CAmbientControl>::CreateInstance(&pCAmbientControl);
			if(pCAmbientControl)
				pCAmbientControl->QueryInterface(&_pIAmbientControl);

//■削除CAxHostWindowを使うのでセットしない
//			//IEコントロールへのセット
//			_pIWebBrowser2->QueryInterface(&pIOleObject);
//			if(pIOleObject && pCAmbientControl)
//				pIOleObject->SetClientSite(pCAmbientControl);

			//デフォルトのダウンロードコントロールを設定
			//本来ならゾーン変化をチェックしてゾーンに応じて決定すべき?
			SetAmbientDownloadControl(DLCTL_DLIMAGES | DLCTL_VIDEOS | DLCTL_BGSOUNDS);
		}

		return	hWnd;
	}

tabbrowser272.gif CAxHostWidnowにはQueryHostが実装されておらず、ちょっとだけ不便なのでCAxWindowからコピーして実装しておく。
	//■追加
	//CAxWindowのソースからコピー。ホスト取得用関数
	template <class Q>
	HRESULT QueryHost(Q** ppUnk)
	{
		return QueryHost(__uuidof(Q), (void**)ppUnk);
	}
	HRESULT QueryHost(REFIID iid, void** ppUnk)
	{
		ATLASSERT(ppUnk != NULL);
		if (ppUnk == NULL)
			return E_POINTER;
		HRESULT hr;
		*ppUnk = NULL;
		CComPtr<IUnknown> spUnk;
		hr = AtlAxGetHost(m_hWnd, &spUnk);
		if (SUCCEEDED(hr))
			hr = spUnk->QueryInterface(iid, ppUnk);
		return hr;
	}

tabbrowser273.gif
これでとりあえず今回は終了だ。まだCAxWindowからCAxHostWindowに切り替えただけでバグは修正されていない。

次回はバグ修正のためにフォーカスがIEコントロールにあるのか、それともそれ以外にあるのかを検出する処理を実装する。

プロジェクトファイルをダウンロード

第60回 フォーカス変更を検出して処理する (タブブラウザーを作る)

tabbrowser274.gif
前回はフォーカス関連のバグ修正のためビューウインドウをCAxWindowからCAxHostWindowに移行した。今回はIEコントロールがフォーカスを受け取ったことを検出して、正常に処理されるようにする。

まずはCMainFrameのPreTranslateMessage内で、ビューウインドウのPreTranslateMessageが呼ばれるようにする。ATL/WTLアプリケーションウイザードでは標準で同じ様な処理が実装されているが、ウイザードのバグにより正常にビューウインドウのPreTranslateMessageが呼ばれていない。次のバージョンでは修正してもらいたいものだ。。。
	virtual BOOL PreTranslateMessage(MSG* pMsg)
	{
		//■追加
		{
			CTabBrowser100View*	pView;

			pView = GetActivePageView();
			if(pView && pView->PreTranslateMessage(pMsg))
				return	TRUE;
		}

		if(TranslatePopupMessage(pMsg->hwnd,pMsg->message,pMsg->wParam,pMsg->lParam,pMsg))
			return	TRUE;

		if(_wndAddressBar.IsChild(::GetFocus()) == FALSE)		//アドレスバーにフォーカスがあるときはPreTranslateMessageを回さない(アドレスバーでCtrl+Cなどを使えるようにするため)
		{
			if(CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg))
				return TRUE;
		}

		return m_view.PreTranslateMessage(pMsg);
	}

tabbrowser275.gif
そしてビューウインドウ内にPreTranslateMessageを追加する。ビューに対してWM_FORWARDMSGを投げると、CAxHostWindow内で自動的にTranslateAcceleatorが呼び出される。

また、フォーカスの変更を取得するためのウインドウCSubclassIEWndを追加する。今回はWM_SETFOCUSもしくはWM_KILLFOCUSがIEコントロールに届いたらそれをビューウインドウへ送るようにした。
	//■追加
	//「Internet Explorer_Server」をサブクラス化
	class	CSubclassIEWnd : public CWindowImpl<CSubclassIEWnd>
	{
		CTabBrowser100View*	_pView;
	public:
		CSubclassIEWnd(CTabBrowser100View* pView)
		{
			_pView = pView;
		}
		BEGIN_MSG_MAP(CSubclassIEWnd)
			if(uMsg == WM_SETFOCUS || uMsg == WM_KILLFOCUS)// || uMsg == WM_MOUSEACTIVATE)
			{
				if(_pView)
					_pView->SendMessage(uMsg,wParam,lParam);
			}

			if(uMsg == WM_DESTROY)
			{
				UnsubclassWindow();
			}
		END_MSG_MAP()
	};

	//■追加
	CSubclassIEWnd	_wndIEServer;

public:
	DECLARE_WND_SUPERCLASS(NULL, CAxWindow::GetWndClassName())	//CAxHostWindow::ではなくCAxWindow::のままなことに注意


	CTabBrowser100View(CTabView* pTabView)
		: _wndReflect(this)
		,_wndIEServer(this)
	{
		_pTabView = pTabView;
		_bPageError = false;
	}


	//■追加
	BOOL PreTranslateMessage(MSG* pMsg)
	{
		if((pMsg->message < WM_KEYFIRST || pMsg->message > WM_KEYLAST) &&
		   (pMsg->message < WM_MOUSEFIRST || pMsg->message > WM_MOUSELAST))
		{
			return FALSE;
		}

		BOOL bRet = FALSE;

		//IOleInPlaceActiveObject::TranslateAccelerator(pMsg)の呼び出し
		if(pMsg->hwnd == m_hWnd || IsChild(pMsg->hwnd))
			bRet = (BOOL)SendMessage(WM_FORWARDMSG, 0, (LPARAM)pMsg);

		return bRet;
	}

tabbrowser276.gif IEコントロールが持つ「Internet Explorer_Server」ウインドウはドキュメントがそろったときに生成される。そのためOnDocumentComplete内でサブクラス化する。
	//HTMLページが読み終わったときに呼ばれる処理
	void	_stdcall OnDocumentComplete(IDispatch* pDisp, VARIANT* vURL)
	{
		if(pDisp != _pIWebBrowser2)
			return;			//フレームなどの読み込み終了は無視

		//■追加
		//IEをサブクラス化
		if(_wndIEServer.IsWindow() == FALSE && _pIWebBrowser2)
		{
			HWND	hWnd;
			HRESULT	hr = E_FAIL;
			CComPtr<IDispatch>	pIDispatch;
			CComPtr<IOleWindow>	pIOleWindow;

			_pIWebBrowser2->get_Document(&pIDispatch);
			if(pIDispatch)
				pIDispatch->QueryInterface(&pIOleWindow);
			if(pIOleWindow)
				hr = pIOleWindow->GetWindow(&hWnd);
			if(SUCCEEDED(hr))
				_wndIEServer.SubclassWindow(hWnd);
		}

		ChangeUIHandler();			//UIHandlerを変更する

		HICON		hIcon;
		CAtlString	strFile;

tabbrowser278.gif そしてビューウインドウに届いたWM_SETFOCUSなどをメッセージマップで処理する。
	BEGIN_MSG_MAP(CTabBrowser100View)
		MESSAGE_HANDLER(WM_DNP_SCRIPTERROR, OnDnpScriptError)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		MESSAGE_HANDLER(WM_DNP_SHOWSCRIPTERROR, OnShowScriptError)
		MESSAGE_HANDLER(WM_DNP_SHOWPRIVACYREPORT, OnShowPrivacyReport)
		MESSAGE_HANDLER(WM_DNP_SHOWPSECURITYREPORT, OnShowSecurityReport)
		MESSAGE_HANDLER(WM_DNP_SHOWZONECONFIGURE, OnShowZoneConfigure)
		MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFocus)	//■追加
		MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)	//■追加
		CHAIN_MSG_MAP(CAxHostWindow)
	END_MSG_MAP()


private:

	//■追加
	LRESULT OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		ATLTRACE("●OnSetFocus\n");

		SetFocusChange(true);
		bHandled = FALSE;
		return	0;
	}

	//■追加
	LRESULT OnKillFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		ATLTRACE("●OnKillFocus\n");

		SetFocusChange(false);
		bHandled = FALSE;
		return	0;
	}

tabbrowser277.gif これまでアドレスバーの中でフォーカスの検出をしていた。この処理はもう不要なので削除する。クラス自体必要性がなくなってしまうのだが、とりあえずこのまま残しておく。
//アドレスバー用クラス
class	CAddressBarCtrl	: public CWindowImpl<CAddressBarCtrl,CComboBoxEx>
{
	//■内部処理削除
	class	CInnerComboBox	: public CWindowImpl<CInnerComboBox>
	{
	public:
		BEGIN_MSG_MAP(CInnerComboBox)
			//MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFocus)
			//MESSAGE_HANDLER(WM_MOUSEACTIVATE, OnMouseActivate)
		END_MSG_MAP()


		////マウスによるフォーカス取得時の処理
		//LRESULT OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
		//{
		//	GetTopLevelParent().SendMessage(WM_DNP_CHANGEFOCUS,(WPARAM)TRUE);		//メインウインドウへフォーカスを受け取ったことを通知
		//	bHandled = FALSE;
		//	return	0;
		//}

		//LRESULT OnKillFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
		//{
		//	GetTopLevelParent().SendMessage(WM_DNP_CHANGEFOCUS,(WPARAM)FALSE);		//メインウインドウへフォーカスを失ったことを通知
		//	bHandled = FALSE;
		//	return	0;
		//}
	};

tabbrowser279.gif 最後に今回のバグの元凶となっていたSetFocusChange内の処理を変更する。
	//■変更
	//IEコントロールがフォーカスを受け取ったときbGetFocus=true、
	//IEコントロールがフォーカスを失ったときbGetFocus=falseとして呼び出す
	bool	SetFocusChange(bool bGetFocus)
	{
		if(_pIWebBrowser2 == NULL)
			return	false;

		if(bGetFocus)
		{
		}
		else
		{
			CComPtr<IOleInPlaceObject> pIOleInPlaceObject;

			if(_pIWebBrowser2)
				_pIWebBrowser2->QueryInterface(&pIOleInPlaceObject);
			if(pIOleInPlaceObject)
				pIOleInPlaceObject->UIDeactivate();
		}

		return	true;
	}

tabbrowser280.gif
これでとりあえず、フォーカス関連のバグは修正された。しかし今回のバグ修正作業によりスクリプトの実行や画像の表示のON/OFF機能が使えなくなってしまっている。

次回はその部分を修正する。

プロジェクトファイルをダウンロード

第61回 CAxHostWindowのInvokeを上書きする (タブブラウザーを作る)

tabbrowser281.gif
今回は画像表示やスクリプト実行のON/OFFなどを制御するダウンロードコントロールが正常に働くようにする。

とはいうものの前回の作業でほとんどの部分が実装されているのでInvokeを上書きするだけでいい。
	//■追加
	STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams,
			VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
	{
		HRESULT	hr = E_FAIL;
		CComPtr<IDispatch>	pIDispatch;

		//ダウンロードコントロール用のInvoke
		if(_pIAmbientControl)
			_pIAmbientControl->QueryInterface(&pIDispatch);
		if(pIDispatch)
			hr = pIDispatch->Invoke(dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr);
		if(SUCCEEDED(hr))
			return	hr;

		//IEコントロールのイベント用のInvoke
		hr = IDispEventImpl<SINKID_EVENTS, CTabBrowser100View, &DIID_DWebBrowserEvents2>::Invoke(dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr);
		if(SUCCEEDED(hr))
			return	hr;

		//デフォルト(CAxHostWindow)用のInvoke
		return CComObject<CAxHostWindow>::Invoke(dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr);
	}

tabbrowser283.gif これは関係ないが、IEツールバーホスト用のクラスの中のフォーカス変更処理は前回の作業で不要になったので削除する。
	//IInputObjectSite
	STDMETHOD(OnFocusChangeIS)(IUnknown *punkObj,BOOL fSetFocus)
	{
		//■内部処理削除
		//HRESULT	hr = E_FAIL;
		//CComPtr<IOleInPlaceObject> pIOleInPlaceObject;

		//if(_pIDispatch)
		//	_pIDispatch->QueryInterface(&pIOleInPlaceObject);
		//if(pIOleInPlaceObject == NULL)
		//	return	S_OK;

		//if(fSetFocus)
		//{
		//	hr = pIOleInPlaceObject->UIDeactivate();
		//	pIOleInPlaceObject->InPlaceDeactivate();
		//}
		//else
		//{
		//	HWND hWnd;
		//	hr = pIOleInPlaceObject->GetWindow(&hWnd);
		//	if(SUCCEEDED(hr) && ::IsWindow(hWnd))
		//		::ShowWindow(hWnd,SW_NORMAL);
		//}

		return	S_OK;
	}

tabbrowser282.gif
これでビルド/実行すると画像表示のON/OFFなどのダウンロード関連機能が再び使えるようになった。

次回は「お気に入り」メニューに「お気に入りの整理」などのメニュー項目を追加する。

プロジェクトファイルをダウンロード

第62回 IShellMenuによる「お気に入り」にメニュー項目を追加する (タブブラウザーを作る)

tabbrowser284.gif
今回は再び「お気に入り」メニュー関連項目を実装する。

「お気に入り」メニューの表示方法をIShellMenuに変えてから、「お気に入りに追加」などの項目が表示されなくなった。今回はこれらの項目を再び表示、使えるようにする。

IShellMenuへのメニュー追加はIShellMenu::SetMenuで行える。ここにリソースから読みだしたHMENUを渡すだけでいい。内部でDestroyMenuされるため、m_Cmdbar.GetMenu()などの引数は渡せない。
	bool	PopupMenu(int nX,int nY)
	{
		LPITEMIDLIST	pidl;

		CComPtr<IShellFolder>		pIShellFolder;

		if(_pIMenuBand)
		{
			//すでにメニューが開いていたら、まずはそのメニューを閉じる

			CComPtr<IOleCommandTarget>	pIOleCommandTarget;

			_pIMenuBand->QueryInterface(&pIOleCommandTarget);
			if(pIOleCommandTarget)
				pIOleCommandTarget->Exec(&CLSID_MenuBand,22,0,NULL,NULL);
		}


		//「お気に入り」フォルダのPIDLとIShellFolderを取得
		{
			CComPtr<IShellFolder>	pIShellFolderDesktop;

			::SHGetDesktopFolder(&pIShellFolderDesktop);
			if(pIShellFolderDesktop == NULL)
				return	false;
			::SHGetSpecialFolderLocation(NULL,CSIDL_FAVORITES,&pidl);
			pIShellFolderDesktop->BindToObject(pidl,NULL,IID_IShellFolder,(void **)&pIShellFolder);
		}



		CComPtr<IDeskBand>	pIDeskBand;

		//PIDLとIShellFolderの示すIShellMenu(IDeskBand)を取得
		{
			HRESULT		hr;
			CRegKey		cRegOrder;
			CComPtr<IShellMenu>	pIShellMenu;
			CComPtr<IDeskBar>	pIDeskBar;
			CComPtr<IMenuPopup>	pIMenuPopup;

			//並び順用レジストリ。必要ならCreateしてもいいが今回は「お気に入り」のみなのでopen
			cRegOrder.Open(HKEY_CURRENT_USER,_T("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MenuOrder\\Favorites"));

			::CoCreateInstance(CLSID_MenuBand,NULL,CLSCTX_INPROC_SERVER,IID_IShellMenu,(void**)&pIShellMenu);

			//■追加
			//「お気に入りに追加」などのメニュー項目をメニューリソースから追加
			//IShellMenu::Initializeよりも先にIShellMenu::SetMenuを使わないとだめなことに注意
			if(pIShellMenu)
			{
				HMENU	hMenu;
				HMENU	hSubMenu = NULL;

				hMenu = ::LoadMenu(::_Module.m_hInst,MAKEINTRESOURCE(IDR_MAINFRAME));
				if(hMenu)
					hSubMenu = ::GetSubMenu(hMenu,4);			//■メニュー位置固定

				if(hSubMenu)
					pIShellMenu->SetMenu(hSubMenu,m_hWnd,SMSET_TOP);
				//if(hMenu)
				//	::DestroyMenu(hMenu);		//不要
			}

tabbrowser285.gif そのままでは追加したメニュー項目にデフォルトのアイコンが表示されてしまう。アイコンが表示されないように、SMC_GETINFOを受けとったときに、SMIM_ICONで-1を指定して、アイコンを消す。
	//普通のメニュー項目に関する情報
	HRESULT	OnSMC_GETINFO(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		SMINFO*		pSmInfo = (SMINFO*)lParam;

		if(pSmInfo->dwMask & SMIM_FLAGS)
			pSmInfo->dwFlags |= SMIF_DROPCASCADE | SMIF_TRACKPOPUP;

		//■追加
		if(pSmInfo->dwMask & SMIM_ICON)
			pSmInfo->iIcon = -1;			//追加メニューのアイコンを非表示にする。iIcon==0のときのみ-1にすべき?

		return	S_OK;
	}

tabbrowser286.gif
またIShellMenuを使う前にお気に入りが開いたことを検出するために「dummy」というメニュー項目を用意していた。それはもう不要なのでリソースから消しておく。

tabbrowser287.gif
これでビルド/実行すると「お気に入りの整理」などのメニュー項目が追加され、利用できるようになった。

だんだんソースコードが荒れてきて利用されていない関数も多くなってきた。そのため次回は再びソースコードの整理をする。

プロジェクトファイルをダウンロード

第63回 ソースコードを少し整理する (タブブラウザーを作る)

これまでの作業でまたソースコードが煩雑化し、まったく利用されていない部分も多く残っている。今回は機能の追加などはせずに少しソースコードを整理する。



過去の「お気に入り」関連処理を削除

「お気に入り」メニューはIShellMenuにより表示しているが、IShellMenuによる実装の前にはフォルダからインターネットショートカットを検索して直にメニューを生成/表示していた。それらの処理はもう利用しなくなったので削除する。

まず「FavoritesMenu.h」と「DnpFolderMenu.h」の2つのファイルをプロジェクトから取り除く。プロジェクトからの削除に加えて、ファイル自体も削除してしまってかまわない。

そして「MainFrm.h」の中にある「#include "FavoritesMenu.h"」や「CHAIN_MSG_MAP_MEMBER(_cFovMenu)」、関数「CMainFrame::OnMenuSelect」や「CMainFrame::OnFavorite」を削除する。



現在の「お気に入り」関連処理を整理

「CShellMenuCallback」を独立したヘッダーファイルに移動する。

そしてCMainFrame::PopupMenuの引数に「HWND hWnd」を追加する。この関数の内部処理では「m_hWnd」を参照する部分を「hWnd」に変えて、ウインドウを引数で渡せるようにする。
	bool	PopupMenu(int nX,int nY,HWND hWnd)

次はこれまでメニューリソースの「お気に入り」メニューの位置をソースコードの中で「4」と固定して指定していた。それを簡単に変更できるようにする。 そのためにまず「MainFrm.h」の先頭に以下の宣言を追加する。
#define FAVORITE_MENU_POSITION	4		//メインウインドウ用リソースでの「お気に入り」メニューの位置

そして「CMainFrame::PopupMenu」、「CCommandBarCtrl2::DoPopupMenu」、「CMainFrame::OnMenuSelectForPopup」内の「4」というメニュー位置の指定をすべて「FAVORITE_MENU_POSITION」に置き換える。


最後に「お気に入り」関連処理を「CMainFrame」から離した場所に移動する。
独立したヘッダーファイルにクラス「CIShellMenuPopup」を新しく作り、
「CMainFrame::PopupMenu」、「_pIMenuBand」、
「CMainFrame::TranslatePopupMessage」をそのクラスの中に移動する。そして「CMainFrame」の派生元に「CIShellMenuPopup」を追加する。



IEツールバー関連処理を整理

「CMainFrame」内に実装しているインストールされているIEツールバーを検索する処理などを離した場所に移動する。

独立したヘッダーファイルにクラス「CIEToolbarManager」を新しく作り、CMainFrame内の以下のメンバ関数、クラス、変数をその中に移動する。
・CDummyWnd
・CAvailableIEToolbar
・_acAvailableIEToolbar、
・CLSIDtoUIString
・RegEnumKeyName
・EnumAvailableIEToolbar、

そして「CMainFrame」の派生元に「CIEToolbarManager」を追加する。



その他

・CAmbientControl
・CTabBrowser100View::CIEToolbarInfo
を独立したヘッダーファイルへ移動する。


・CTabBrowser100View::GetZone
を「CIEUtility」へ移動する。

「CTabBrowser100View」と「CMainFrame」内のメンバ関数やメンバ変数にprotectedやpublicの指定を追加する。

・「CTabBrowser100View」内の変数の記述位置を前後に移動

・その他、少しだけコメントを追加





今回は以上の細かなソースコード整理をした。次回は現在のページを開いたままリンクをバックグラウンドのタブで開けるようにする。

プロジェクトファイルをダウンロード

2009年04月20日

第64回 「タブ」をバックグラウンドで開けるようにする (タブブラウザーを作る)

tabbrowser288.gif
今回は新しいページをバックグラウンドのタブで開けるようにする。

現状では「ファイルメニュー」の「新規作成」でも、リンクを右クリックして現れるメニューから「新しいウインドウで開く」を選択したときも、新しく開いたタブにフォーカスがあたり、それまで開いていたタブから新しいタブに切り替わってしまう。

それを
・「ファイルメニュー」の「新規作成」で開いたタブはアクティブで
・リンクを右クリックして現れるメニューから「新しいウインドウで開く」から開いたタブはバックグラウンドで
開くようにする。

「タブ」コントロールはWTLのCTabViewを利用している。このクラスには標準ではタブをバックグラウンドで開くオプションがなく、常にアクティブにしてタブが作られてしまう。 CTabViewに新しいタブをバックグラウンドで開くオプションを追加するために、派生クラスを作り、AddPageとInsertPageを上書きする。
//■追加
class	CTabViewEx : public CTabView
{

public:

	//WTLのCTabView::InsertPageからソースをコピー&改変
	//
	//bOpenInBackground=trueなら新しいタブにフォーカスを与えない(バックグラウンドで開く)
	//
	bool AddPage(HWND hWndView, LPCTSTR lpstrTitle, int nImage = -1, LPVOID pData = NULL,bool bOpenInBackground=false)
	{
		return InsertPage(GetPageCount(), hWndView, lpstrTitle, nImage, pData, bOpenInBackground);
	}

	//WTLのCTabView::InsertPageからソースをコピー&改変
	//
	//bOpenInBackground=trueなら新しいタブにフォーカスを与えない(バックグラウンドで開く)
	//
	bool InsertPage(int nPage, HWND hWndView, LPCTSTR lpstrTitle, int nImage = -1, LPVOID pData = NULL,bool bOpenInBackground=false)
	{
		ATLASSERT(::IsWindow(m_hWnd));
		ATLASSERT(nPage == GetPageCount() || IsValidPageIndex(nPage));

		CTabViewEx* pT = static_cast<CTabViewEx*>(this);		//■変更 CTabViewExでキャスト

		int cchBuff = lstrlen(lpstrTitle) + 1;
		LPTSTR lpstrBuff = NULL;
		ATLTRY(lpstrBuff = new TCHAR[cchBuff]);
		if(lpstrBuff == NULL)
			return false;

		SecureHelper::strcpy_x(lpstrBuff, cchBuff, lpstrTitle);

		CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
		LPTSTR lpstrTabText = buff.Allocate(m_cchTabTextLength + 1);
		if(lpstrTabText == NULL)
			return false;

		pT->ShortenTitle(lpstrTitle, lpstrTabText, m_cchTabTextLength + 1);

		SetRedraw(FALSE);

		TCITEMEXTRA tcix = { 0 };
		tcix.tciheader.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_PARAM;
		tcix.tciheader.pszText = lpstrTabText;
		tcix.tciheader.iImage = nImage;
		tcix.tvpage.hWnd = hWndView;
		tcix.tvpage.lpstrTitle = lpstrBuff;
		tcix.tvpage.pData = pData;
		int nItem = m_tab.InsertItem(nPage, tcix);
		if(nItem == -1)
		{
			delete [] lpstrBuff;
			SetRedraw(TRUE);
			return false;
		}

		if(bOpenInBackground == false || GetPageCount() == 1)		//■条件を追加
		{
			//新しいタブをアクティブにする
			SetActivePage(nItem);
			pT->OnPageActivated(m_nActivePage);
		}

		if(GetPageCount() == 1)
			pT->ShowTabControl(true);

		pT->UpdateLayout();

		SetRedraw(TRUE);
		RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);

		return true;
	}
};

tabbrowser289.gif そしてCMainFrame::m_viewをCTabViewから新しい派生クラスのCTabViewExに変更する。
class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>,
		public CMessageFilter, public CIdleHandler
		,protected CIShellMenuPopup					//「お気に入り」メニュー用クラス。publicの必要なし
		,protected CIEToolbarManager				//IEツールバー管理クラス。publicの必要なし
{
	CAddressBarCtrl	_wndAddressBar;			// アドレスバー用コンボボックス

	CTabViewEx			m_view;			//■変更
	CCommandBarCtrl2 m_CmdBar;

public:

tabbrowser290.gif 新しいタブを作るための関数CreateNewTabでも新しいタブをアクティブにするかバックグラウンドにするかの選択ができるようにする。
	//■変更(bOpenInBackgroundへの対応)
	//タブの新規作成
	//nPosはタブを追加する場所。nPos<0(もしくはnPos=m_view.GetPageCount())で一番後ろ、
	//そのほかの数値はCTabView::InsertPageにそのまま渡す
	//
	//戻ったポインタは自動削除されるため、deleteの必要なし
	//
	//bOpenInBackground=trueならバックグラウンドでタブを開く
	//
	CTabBrowser100View*	CreateNewTab(LPCTSTR pszURL,LPCTSTR pszTitle,int nPos=-1,int nImage=-1,bool bOpenInBackground=false)
	{
		bool	ret;
		CTabBrowser100View*	pView;

		//■追加
		//bOpenInBackgroundが指定されていても現在タブが1つもない状態ならbOpenInBackgroundをfalseに強制設定する
		if(bOpenInBackground && m_view.GetPageCount() == 0)
			bOpenInBackground = false;

		pView = new CTabBrowser100View(&m_view);	//タブビューのポインタを渡す
		if(pView == NULL)
			return	NULL;

		//ビューウインドウ生成
		if(pszURL == NULL || *pszURL == NULL)
			pView->Create(m_view, rcDefault, _T("about:blank"), WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_HSCROLL | WS_VSCROLL, 0);
		else
			pView->Create(m_view, rcDefault, pszURL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_HSCROLL | WS_VSCROLL, 0);
		if(pView->IsWindow() == FALSE)
		{
			delete	pView;
			return	NULL;		//ビューウインドウ生成失敗
		}

		//タブにビューを割り当てる
		if(nPos < 0)
			nPos = m_view.GetPageCount();
		if(pszTitle)
			ret = m_view.InsertPage(nPos,pView->m_hWnd,pszTitle,nImage,pView,bOpenInBackground);	//■変更
		else
			ret = m_view.InsertPage(nPos,pView->m_hWnd,_T(""),nImage,pView,bOpenInBackground);		//■変更

		if(ret == false)
		{
			delete	pView;
			return	NULL;		//タブへの追加失敗
		}

		//IEツールバーの生成
		{
			size_t	i;
			size_t	nSize;

			nSize = _acAvailableIEToolbar.GetCount();
			for(i = 0; i < nSize; i++)
			{
				if(_acAvailableIEToolbar[i].pwndDummy)
					pView->AddIEToolbar(*_acAvailableIEToolbar[i].pwndDummy,bOpenInBackground);		//IEツールバー生成
			}
		}

		return	pView;
	}

tabbrowser292.gif 独自メッセージ用のクラスに新しいタブをどう開くかのオプション用変数を追加する。
#pragma	once

class	CTabBrowser100View;

// CMainFrame::nDnpCreateNewTab() 用の情報クラス
class	CDnpCreateNewTabInfo
{
public:
	CAtlString	strURL;
	CAtlString	strTitle;
	int		nImage;
	int		nPos;
	bool	bOpenInBackground;		//■追加

	CTabBrowser100View*	pView;

	CDnpCreateNewTabInfo()
	{
		strURL	= _T("about:blank");
		pView	= NULL;
		nImage	= -1;
		nPos	= -1;
		bOpenInBackground = false;	//■追加
	}
};

tabbrowser291.gif そして独自メッセーによるタブ生成処理時にも指定ができるようにする。
	//WM_DNP_CREATENEWTABによりタブの作成
	//WPARAMはCDnpCreateNewTabInfoへのポインタを渡す
	LRESULT OnDnpCreateNewTab(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		CDnpCreateNewTabInfo*	pInfo = (CDnpCreateNewTabInfo*)wParam;

		if(pInfo == NULL)
			return	0;

		//■変更 pInfo->bOpenInBackgroundを追加
		pInfo->pView = CreateNewTab(pInfo->strURL,pInfo->strTitle,pInfo->nPos,pInfo->nImage,pInfo->bOpenInBackground);

		return	0;
	}

tabbrowser293.gif 最後にリンクを右クリックして「新しいウインドウで開く」を選択したときに、バックグラウンドでタブを開くように指定すれば終わりだ。
	//新しいウインドウで開く前に呼ばれる処理
	//新しいタブで開く
	void	__stdcall OnNewWindow2(IDispatch **ppDisp,VARIANT_BOOL *Cancel)
	{
		CDnpCreateNewTabInfo	cInfo;

		cInfo.bOpenInBackground = true;		//■追加 リンクを新しいタブで開くときはバックグラウンドで開く

		//新しいタブを作る
		GetTopLevelWindow().SendMessage(WM_DNP_CREATENEWTAB,(WPARAM)&cInfo);
		if(cInfo.pView == NULL || cInfo.pView->_pIWebBrowser2 == NULL)
			return;

		//新しいタブで開くようにIEコントロールにポインタを渡す
		cInfo.pView->_pIWebBrowser2->put_RegisterAsBrowser(VARIANT_TRUE);	//これしなくても開くようになっているのはなぜ?IE8から?一応実行しておく
		cInfo.pView->_pIWebBrowser2->get_Application(ppDisp);
	}

tabbrowser294.gif
これで新しいタブをバックグラウンドで開けるようになった。

しかし今回の修正によりリンクを新しいタブで開いたときに「Googleツールバー」の下端が欠けてしまい、きちんと表示されなくなった。次回はその問題を修正する。

プロジェクトファイルをダウンロード

第65回 IE用ツールバーを再びきちんと表示する (タブブラウザーを作る)

tabbrowser295.gif
前回の作業で、「タブ」をバックグラウンドを開けるようになった。しかしその副作用で、タブの開き方によって「Googleツールバー」などのIE用ツールバーが表示されなかったり、下端が欠けるなどの不具合が生じた。今回はそれらを修正する。

まずCIEToolbarInfoの中でIEツールバーを生成する処理でbVisibleを利用した条件分岐を入れる。 IDeskBandからのGetBandInfoによる情報はShowDW(TRUE)を実行しないときちんと取得できない。そのためShowDWを実行しないときはGetBandInfoによる情報を利用せずに、既存のリバー情報のみを利用するようにした。
	//hWndParentはCDummyWnd(Googleツールバーの親となるウインドウハンドル)
	//clsidIDeskBandはGoogleツールバーのCLSIDを指定する
	bool	CreateIEToolbar(IWebBrowser2* pIWebBrowser2,HWND hWndParent,IDeskBand** ppIDeskBand,const IID& clsidIDeskBand,bool bVisible)
	{
		HRESULT	hr;

		if(ppIDeskBand == NULL || pIWebBrowser2 == NULL)
			return	false;
		if(*ppIDeskBand)
		{
			ATLASSERT(0);
			(*ppIDeskBand)->Release();
			*ppIDeskBand = NULL;
		}

		hr = ::CoCreateInstance(clsidIDeskBand,NULL,CLSCTX_INPROC_SERVER,IID_IDeskBand,(void**)ppIDeskBand);
		if(FAILED(hr) || *ppIDeskBand == NULL)
			return	false;

		CComPtr<IObjectWithSite>	pIObjectWithSite;

		(*ppIDeskBand)->QueryInterface(IID_IObjectWithSite,(void**)&pIObjectWithSite);

		//デスクバンドをホスト用クラスに割り当てる
		{
			CComPtr<IIEToolbar>	pIIEToolbar;

			pIIEToolbar = new CComObject<CIEToolbar>;

			pIIEToolbar->put_hwnd(hWndParent);
			if(pIWebBrowser2)
				pIIEToolbar->put_IWebBrowser2(pIWebBrowser2);
			if(pIObjectWithSite)
				hr = pIObjectWithSite->SetSite(pIIEToolbar);

			if(SUCCEEDED(hr) && bVisible)
				hr = (*ppIDeskBand)->ShowDW(TRUE);		//表示
		}

		DESKBANDINFO	sDeskBandInfo;

		//別に必要ないが情報をたくさん取得
		::ZeroMemory(&sDeskBandInfo,sizeof(DESKBANDINFO));
		sDeskBandInfo.dwMask = DBIM_MINSIZE | DBIM_MAXSIZE | DBIM_INTEGRAL | DBIM_ACTUAL | DBIM_TITLE | DBIM_MODEFLAGS | DBIM_BKCOLOR;
		hr = (*ppIDeskBand)->GetBandInfo(0,DBIF_VIEWMODE_NORMAL,&sDeskBandInfo);

		CRect	rect;		//■変更 不要なサイズ指定が残っていたので変更

		//rebar情報の設定
		{
			HWND	hWndRebar;
			UINT	nIndex;

			hWndRebar = ::GetParent(hWndParent);
			nIndex = -1;
			::SendMessage(hWndParent,WM_DNP_GETRBINDEX,(WPARAM)&nIndex,NULL);
			if(nIndex != -1 && hWndRebar)
			{
				//ShowDW(TRUE)を実行していないとGetBandInfoでサイズが
				//取得できないので、bVisible=falseのときはリバーの大きさを
				//調整しない
				if(bVisible)		//■条件を追加
				{
					REBARBANDINFO	info;

					//MinSizeのみをリバーへ設定。本来ならもっと情報を設定すべき
					::ZeroMemory(&info,sizeof(REBARBANDINFO));
					info.cbSize	= sizeof(REBARBANDINFO);
					info.fMask	= RBBIM_CHILDSIZE;
					::SendMessage(hWndRebar,RB_GETBANDINFO,nIndex,(LPARAM)&info);
					info.cyMinChild	= sDeskBandInfo.ptMinSize.y;
					info.cyChild	= sDeskBandInfo.ptMinSize.y;
					info.cxMinChild	= sDeskBandInfo.ptMinSize.x;
					::SendMessage(hWndRebar,RB_SETBANDINFO,nIndex,(LPARAM)&info);
				}

				//位置調整
				::SendMessage(hWndRebar,RB_GETRECT,nIndex,(LPARAM)&rect);
				::SendMessage(hWndParent,WM_SIZE,SIZE_RESTORED,MAKELPARAM(rect.Width(),rect.Height()));
			}
		}

		return	true;
	}

tabbrowser296.gif また、前回の実装でCMainFrame::CreateNewTab内にバグがあったのでそれを修正する。 pView->AddIEToolbarの呼び出し時に「!」が抜けていた。
	//タブの新規作成
	//nPosはタブを追加する場所。nPos<0(もしくはnPos=m_view.GetPageCount())で一番後ろ、
	//そのほかの数値はCTabView::InsertPageにそのまま渡す
	//
	//戻ったポインタは自動削除されるため、deleteの必要なし
	//
	//bOpenInBackground=trueならバックグラウンドでタブを開く
	//
	CTabBrowser100View*	CreateNewTab(LPCTSTR pszURL,LPCTSTR pszTitle,int nPos=-1,int nImage=-1,bool bOpenInBackground=false)
	{
		bool	ret;
		CTabBrowser100View*	pView;

		//bOpenInBackgroundが指定されていても現在タブが1つもない状態ならbOpenInBackgroundをfalseに強制設定する
		if(bOpenInBackground && m_view.GetPageCount() == 0)
			bOpenInBackground = false;

		pView = new CTabBrowser100View(&m_view);	//タブビューのポインタを渡す
		if(pView == NULL)
			return	NULL;

		//ビューウインドウ生成
		if(pszURL == NULL || *pszURL == NULL)
			pView->Create(m_view, rcDefault, _T("about:blank"), WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_HSCROLL | WS_VSCROLL, 0);
		else
			pView->Create(m_view, rcDefault, pszURL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_HSCROLL | WS_VSCROLL, 0);
		if(pView->IsWindow() == FALSE)
		{
			delete	pView;
			return	NULL;		//ビューウインドウ生成失敗
		}

		//タブにビューを割り当てる
		if(nPos < 0)
			nPos = m_view.GetPageCount();
		if(pszTitle)
			ret = m_view.InsertPage(nPos,pView->m_hWnd,pszTitle,nImage,pView,bOpenInBackground);
		else
			ret = m_view.InsertPage(nPos,pView->m_hWnd,_T(""),nImage,pView,bOpenInBackground);

		if(ret == false)
		{
			delete	pView;
			return	NULL;		//タブへの追加失敗
		}

		//IEツールバーの生成
		{
			size_t	i;
			size_t	nSize;

			nSize = _acAvailableIEToolbar.GetCount();
			for(i = 0; i < nSize; i++)
			{
				if(_acAvailableIEToolbar[i].pwndDummy)
					pView->AddIEToolbar(*_acAvailableIEToolbar[i].pwndDummy,! bOpenInBackground);		//■変更 IEツールバー生成
			}
		}

		return	pView;
	}

tabbrowser297.gif
これでビルド/実行するとIE用ツールバーが再びきちんと表示されるようになった。



これまでの作業で

・「お気に入り」メニューを開いているときにメニューバーの「お気に入り」がハイライトにならない
・「お気に入り」メニューでフォーカスが変わるため、メニューを開きながらメニューバーを順に開けない
・「お気に入り」メニューを開くとステータスバーのデザインが壊れる
・「お気に入り」メニュー内の項目選択でステータスバーへ解説表示ができない
・「お気に入り」メニュー内の項目の有効/無効を切りかえれない
・「編集」メニュー内の「コピー」などの各項目をアドレスバーなどIEコントロール以外で利用できない
・スクリプトエラーダイアログの抑制がNavigate直後に働かない
・Navigate直後のタブ名が「about:blank」になる
・アドレスバーの表示URLが長い場合、先頭から表示されずに後方が表示される
・「戻る」「進む」ボタンにIE履歴を選択するためのドロップメニューがない
・IEツールバーを利用するのに毎回手動で表示する必要がある(終了時の状態を保存していない)
・IEツールバーを新規表示するときに必ずリバーが1行表示される
・リンクの右クリックで「新しいタブで開く」でタブを開き、「新しいウインドウで開く」では新しくウインドウを生成するようにすべき
・ページ読み込み中を示す表示がない
・アドレスバーがオートコンプリートによるURL入力に対応していない
・メニュー項目で利用できないものが多い
・実装上のバグからUNICODEビルドしかできない

などなどざっと挙げただけどもまだ不具合や未実装な必要最低限の機能が多く残っている。

しかしだんだん疲れてきたのでしばらくタブブラウザーからは離れて保留にします。お疲れ様でした。また気が向くか要望があれば再開するかもしれませんが。。。

プロジェクトファイルをダウンロード

前の10件 1  2  3  4  5  6  7





usefullcode@gmail.com

About 2009年04月

2009年04月にブログ「UsefullCode.net」に投稿されたすべてのエントリーです。

前の記事は2009年03月です。

次の記事は2010年05月です。

他にも多くのエントリーがあります。メインページ記事一覧も見てください。