MFCでアプリケーションを作る
by K.I
2009/07/10
Index
- SDKでアプリケーションを作るのは楽しいけど、ちょっと面倒なこともある。
- MFCは、AppWizardでスケルトンを作ってくれるので、ちょっとだけ楽が出来るはず1だ。
- MFCで、ごく簡単なアプリケーションを作る方法を試行錯誤したメモ。
- 環境は、VC5とVS2005。
- VC5はライセンスの関係で使わざるを得ないので、基本的にVC5で作ってVS2005でもコンパイルしてみる。
- ファイルのデータを読書きする。
- データをグラフとかで表示する。
- 情報表示や設定をする。
- 出来ればグラフの印刷とかしたい。
- でも、いざ実際にプログラムを作ろうとすると、ちょっと戸惑ってしまう。
- デフォルトでは、CViewが基本クラスになるけど、これはコントロールを貼りつけるのが面倒。
- CFormViewを使うと、ボタンやEditBox等のコントロールは自由に配置出来るけど、印刷等はどうする?
- まず、SDIプログラムで十分。
- MDIは、ウィンドウの中にウィンドウがあるような(例えばExcelとか)プログラムだが、
- プログラムが複雑になりそうだし、なによりあまり好きじゃないのでパス。
- 基本的には、デフォルトのCViewでOK。
- ファイルの読書き、印刷、データ表示が出来る。
- 大きなデータを表示する場合は、CScrollViewを使えば良い。
- 問題は情報表示や設定をするためのコントロールをどうするか。
- まず、別ウィンドウでDialogを表示してコントロールする方法。
- もう1つは、同じウィンドウ上に、もう1つのViewを置いてコントロールする。
- 結局、1つのDocumentに複数のViewを割り当てれば良いことになる。
- まずメインのViewとして、CViewかCScrollViewのスケルトンをAppWizardで生成する。
- 次に新規クラスで、コントロール用のViewを作成する。
- あとは、Documentから複数のViewに表示すれば良いだろう。
- 1つのドキュメントに複数のViewを対応させるやり方が、良く分からなかった。
- CSplitWndを使って、Windowを分割する方法があるらしいけど。。
- でも、もっと簡単に出来そうな気もするんだけど。。。
- そのため、ダイアログバーを使ってコントロールを表示するようにした。
- CView或いはCScrollViewを使って基本的なアプリケーションを作成。
- あとは、ダイアログバーでコントロールを表示する。
- ドキュメントビューで、ファイルアクセスも纏められる。
- 自分なりのスケルトンを作るところまでは、何とか出来たんだけど。。
- 複数Viewを使うやり方もそのうち出来るようにしたいが。。
- MFCに振り回されるのは、もうコリゴリという気もする。。。
1と思ったのが間違いの元か。
[top]
- メインのウィンドウに、データとコントロールを両方表示する様なプログラムを作ってみる。
- 実際は、うまく行かなかったので、 仕切りなおしの方を見て下さい。。
- VC5のファイルメニュー→新規作成で、
- MFC AppWizard(exe)を選ぶ。
- まずプロジェクト名を適当に、例えばMyTestとか設定する。
- SDIアプリケーションを選ぶ。
- あとは、デフォルトで「次へ」ボタンを押して進む。
- 最後に、生成されるクラスを確認する。
- Viewの基本クラスは、CView、或いはCScrollViewを選んでおく。
- これでコンパイルすれば、ファイルの読書き・印刷が出来るアプリケーションになっている。
- しかし中身は無いので、白紙が印刷されるだけだし、空のファイルが保存されるだけだが。
- この例では、メインViewのプログラムは、MyTestView.cpp
- ドキュメントのプログラムは、MyTestDoc.cppとなる。
- 次に、コントロールを配置するためのDialogを作成する。
- ResourceViewで右クリック→挿入で、Dialogを選択、新規作成ボタンを押す。
- 生成されたDialogのプロパティで、IDを適当に、例えばIDD_MYCTRLとか設定する。
- これは、メインのウィンドウ上に配置するので、スタイルをChildにしておく。
- ダイアログバーを使う場合は、この操作は不要だけどメモとして残す。
- 挿入メニュー→クラスの新規作成を選ぶ。
- クラス名を適当に、例えばCMyCtrlとか設定して、基本クラスをCFormViewにする。
- DialogIDに、作成しておいたDialogのIDを設定する。
- これで新しいViewが生成され、この例では MyCtrl.cpp,MyCtrl.h の2つのファイルが作られる。
- ダイアログバーを使う場合は、これも不要だけど、一応残しておこう。
- 作成したクラスにアクセスするために、グローバル変数にクラスのポインタ変数を作っておく。
- MyCtrl.cppの最初の部分に、以下のようにポインタ変数を定義する。
CMyCtrl* pMyCtrl = NULL;
- それで、MyCtrl.cppのコンストラクタ(クラスと同じ名前の関数、この場合はCMyCtrl)で、自分のポインタを設定しておく。
pMyCtrl = this;
extern CMyCtrl* pMyCtrl;
外部からこのViewにアクセスする時は、このポインタを使えば良い。
- まず、メインViewに描画してみる。
- とりあえずテスト的に、XY軸と楕円を2個描画する
CRect rt;
GetClientRect(rt); // 描画可能範囲を求める
// 描画用の座標系を設定
pDC->SetMapMode(MM_ANISOTROPIC); // マッピングモードを変える
pDC->SetWindowExt(2000, 2000); // 仮想的なWindowサイズの指定
pDC->SetViewportExt(rt.right, -rt.bottom); // ビューポートの指定
pDC->SetViewportOrg(rt.right/2, rt.bottom/2); // 原点の指定
// 描画用のペンやブラシを設定
CPen mypen(PS_SOLID, 1, RGB(0, 0, 255)); // 描画用のPENを作る
CPen* pen0 = pDC->SelectObject(&mypen); // 作ったPENをセット(現状を保存)
CBrush nullbrush;
nullbrush.CreateStockObject(NULL_BRUSH); // 透明ブラシを用意
CBrush* brush0 = pDC->SelectObject(&nullbrush); // 透明ブラシをセット(現状を保存)
// pDC->SetBkMode(TRANSPARENT); // 透明モードは効かなかった
// XY軸と楕円を描画
pDC->MoveTo(-1000,0); pDC->LineTo(1000,0); // X軸を表示
pDC->MoveTo(0,-1000); pDC->LineTo(0,1000); // Y軸を表示
pDC->Ellipse(-500,-500,500,500); // 楕円を描く
pDC->Ellipse(-1000,-1000,1000,1000); // 楕円を描く
// 後始末
pDC->SelectObject(pen0); // PENを元に戻す
pDC->SelectObject(brush0); // ブラシを元に戻す
- Windowsのグラフィック座標は、左上が原点で、Y軸の方向が通常の座標系と異なっている。
- グラフ表示の場合、それだけではなくScaleの変換も行う必要がある。
- ViewPortを使うと、仮想的な座標系を使って描画出来るので、便利だ。
- デフォルトの座標系は、MM_TEXTで左上が原点になっている。
- 次に仮想座標系と物理座標系の大きさをそれぞれ指定する。(WidthとHeight)
- あとは仮想座標系と物理座標系の原点をそれぞれ指定する。
pDC->SetWindowOrg(0,0); // 仮想座標系の原点
pDC->SetViewportOrg(rt.right/2, rt.bottom/2); // ビューポートの原点
- これは、自分もSDKでは スクリーン座標を求める時に同じようなことをやってたけど、
- SetWindowExtとSetViewportExtの場合は、範囲ではなく大きさを指定するだけになっている。→そのため原点指定を別途行う必要がある
- 変換ルーチンが初めから用意されているのは有り難い。
- sdkのプログラムなら、CreateDialogでDialogを作っておいて、それをMoveWindowで配置するようにしたんだけど、、
- スプリットウィンドウとか使うと良いんだろうか?
- ここだけsdkで書くと整合とれなくなっちゃうかなぁ、結局、MFCなりのやり方が分からず挫折した。。
[top]
- 1つのWindow上に、2つのViewを表示するので挫折したので、ダイアログバーを使って最初からやり直すことにした。
- ダイアログバーというのが有るらしい。初めはウィンドウの上部にDialogが貼り付いているが、下部に移動したり、独立させたり出来る。
- まず、新規プロジェクト作成で、SDIアプリケーションを選択してスケルトンを作る。
- 次に、CMainFrameにDialogBarのメンバ変数を追加。
- 本命のダイアログバーのリソースを作る。
- リソースマネージャのウィンドウで、右クリック→挿入でDialogのIDD_DIALOGBARを選び、新規作成する。
- 結局のところ、スタイルをチャイルドにして、境界線なしのDialogを作れば良い。
- ダイアログバーと言っても横長に作る必要は無い。適当にEditBoxとボタンを配置しておく。
- ダイアログバー,EditBox,ボタンのIDは、とりあえずデフォルトのIDD_DIALOGBAR,IDC_EDIT1,IDC_BUTTON1とする。
- 次に、CMainFrameのON_Createで、ダイアログバーとしてドッキングさせる。
- MainFrm.cppのCMainFrame::OnCreateルーチンには、既にツールバーがドッキング済みなので、その後に以下のコードを追加する。
// ダイアログバーの生成→最初にドッキングする場所も指定可能(この例では左側)
if(!m_wndDialogBar.Create(this, IDD_DIALOGBAR, WS_VISIBLE | CBRS_LEFT,
AFX_IDW_TOOLBAR))
{
TRACE0("Failed to create dialogbar\n");
return -1; // DialogBar作成失敗
}
EnableDocking(CBRS_ALIGN_ANY); // これは前の方でやってあれば不要
m_wndDialogBar.EnableDocking(CBRS_ALIGN_LEFT | CBRS_ALIGN_RIGHT); // ドッキング可能な場所の指定
DockControlBar(&m_wndDialogBar); // これで実際にドッキングする?
m_wndDialogBar.SetWindowText("ControlPanel"); // フローティングになったときのタイトル
- これでコンパイルすると、ダイアログバーとして、ちゃんと表示された。
- このインターフェースは好き嫌いありそうだが、まぁこれで良いんじゃないかな〜。
- でも、ダイアログバー上のボタンが無効になったままだ。
- これはダイアログバーの場合、ボタンのメッセージハンドラが無いと無効にされてしまう2らしい。
- メッセージハンドラは、ダイアログバーの場合、ClassWizardが使えないらしい。
- DialogリソースとMainFrameの関連付けがうまく出来ないので、直接ソースに書き込む必要がある。
- Dialogを操作した時のイベント処理をCMainFrm.cppに追加する。
- MFCの場合、最初の方に、BEGIN_MESSAGE_MAP〜END_MESSAGE_MAPというのがあり、ここで応答するイベントを列挙する。
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
// メモ - ClassWizard はこの位置にマッピング用のマクロを追加または削除します。
// この位置に生成されるコードを編集しないでください。
ON_WM_CREATE()
ON_EN_CHANGE(IDC_EDIT1,OnEnChange) // EditBoxの変更時のイベントを追加
ON_BN_CLICKED(IDC_BUTTON1,OnBnClicked) // マウスクリック時のイベントを追加
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
END_MESSAGE_MAP()
- //{{AFX_MSG_MAP(CMainFrame)〜//}}AFX_MSG_MAPの部分は、ClassWizardが管理している部分なので、その中に記述して良いのか分からないけど、
- EditBoxの変更時のイベントと、Button用にマウスクリック時のイベントを追加する。
- ヘッダファイルCMainFrm.hの最後の方に、関数の宣言を同様に追加しておく。
// 生成されたメッセージ マップ関数
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
// メモ - ClassWizard はこの位置にメンバ関数を追加または削除します。
// この位置に生成されるコードを編集しないでください。
afx_msg void OnEnChange(); // EditBox変更時に実行する関数を追加
afx_msg void OnBnClicked(); // マウスクリック時に実行する関数を追加
//}}AFX_MSG
- 実際の関数を、CMainFrm.cppに記述する。
/////////////////////////////////////////////////////////////////////////////
// CMainFrame メッセージ ハンドラ
void CMainFrame::OnEnChange() // EditBox変更時に実行する関数を追加
{
}
void CMainFrame::OnBnClicked() // マウスクリック時に実行する関数を追加
{
}
ハンドラは空だが、これでめでたくボタンが有効になった。
Warningなので放っておいても良さそうだが、ここを参考に対策。
- ボタンのIDのStringTableリソースに文字列を設定すると、ボタンを押した時に3ステータスバーにその文字列が表示されるようになり、デバッグタブには何も表示されなくなる。
- 具体的には、ResourceViewタブ→StringTable→StringTableを選ぶと、StiringTableリソースの一覧が表示されるので、
- 一覧を右クリック、ストリングの新規作成を選び、
- この例では、IDをIDC_BUTTON1で、キャプションを適当にボタン1とかに設定すれば良い。
- これで、ボタンを押した時にはステータスバーには、ボタン1と表示されるようになる。
- プロジェクトの新規作成で作ったスケルトンでは、ツールバーやステータスバーのON/OFFが出来るようになっている。
- ダイアログバーをフローティングにして、クローズしてしまうと無くなってしまうため、ON/OFF出来ないと困るので、同様にON/OFF出来るようにする必要がある。
- まず、ResourceViewで、メニューリソースIDR_MAINFRAMEを開いて、表示メニューに項目を追加
- ID : IDM_DIALOGBAR
- キャプション : ダイアログバー(&D)
- 次に、今追加したメニューリソースIDM_DIALOGBARからClassWizardを開いて、
- CMainFrameのIDM_DIALOGBARのメッセージ、COMMANDとUPDATE_COMMAND_UIにメンバ関数を追加する。
- するとデフォルトでは、MainFrm.cppに、OnDialogbar関数とUpdateDialogbar関数が追加されるので、以下のようにコードを追加。
void CMainFrame::OnDialogbar() // 表示メニューのダイアログバー項目の処理
{
// TODO: この位置にコマンド ハンドラ用のコードを追加してください
if( m_wndDialogBar.IsWindowVisible() )
ShowControlBar(&m_wndDialogBar, FALSE, FALSE);
else
ShowControlBar(&m_wndDialogBar, TRUE, FALSE);
}
void CMainFrame::OnUpdateDialogbar(CCmdUI* pCmdUI) // 表示メニュー→ダイアログバーの表示直前に呼ばれる
{
// TODO: この位置に command update UI ハンドラ用のコードを追加してください
pCmdUI->SetCheck(m_wndDialogBar.IsWindowVisible()); // メニュー項目にチェックを付ける
}
- ダイアログバーのON/OFFも出来るようになりました。
- これでスケルトンは完成!でもSDKより手間掛かったよ〜。疲れた〜〜。
2これで結構悩んだ。全く余計なことしやがって。。
3ツールボタンのようにマウスオーバー時ではないのがちょっと残念。
[top]
- MFCのスケルトンでは、ファイルの読書きはDocumentクラスで行われる。
- OnNewDocument →ドキュメントの新規作成時に呼ばれる
- Serialize →ドキュメントの読込み、保存時に呼ばれる
- データは、基本的にドキュメントクラスで管理しておく。
- で、表示を担当するViewクラスはどうやってデータにアクセスするのかと言うと、
- GetDocument()関数でDocumentクラスのポインタが得られる。
- Viewクラスの、OnDrawメソッドに入っている以下のコードがそれ。
CDialogBarTestDoc* pDoc = GetDocument();
- デフォルトで作成されたスケルトンは、全てのファイルを表示するようになっているが、特定の拡張子だけ表示する方法が分からない。
- これは簡単に分かると思ったんだけど、調べるのに物凄く手間取った。→MFCやってる人は常識なんだるけど。。
- 別に複数のファイル拡張子に対応したいわけじゃなくて、1つの拡張子だけで良いのに。。
- ヒントが2chにあった。StringTableのIDR_MAINFRAMEに書けと。
- でもキーワードが分かれば調べられる。
- \nで区切られた7つのパラメータが含まれているらしい。
- MSDNのCDocTemplate::GetDocStringの解説にある7つのパラメータそのものらしい。
- CDocTemplate::windowTitle アプリケーション ウィンドウのタイトル バーに表示されている名前 (例 : "Microsoft Excel") です。SDI アプリケーション用のドキュメント テンプレートの場合だけ有効です。
- CDocTemplate::docName 既定のドキュメント名のルート (例 : "Sheet") です。[ファイル] メニューの [新規作成] を選択したとき、このルートに数字を付加すると、この型の新しいドキュメントの既定の名前 (例 : "Sheet1" または "Sheet2") となります。指定されていない場合、"Untitled" を使います4。
- CDocTemplate::fileNewName ドキュメントの型名です。アプリケーションが 1 つ以上のドキュメントの型をサポートしていると、この文字列が [ファイル新規作成] ダイアログ ボックスに表示されます (例 : "Worksheet")。指定されていないと、[ファイル] メニューの [新規作成] を選択したときにドキュメントの型を特定できません。
- CDocTemplate::filterName ドキュメントの型の説明と、この型のドキュメントを検索するときのフィルタとなるワイルドカードです。この文字列は、[ファイルを開く] ダイアログ ボックスの [ファイルの種類] ボックスに表示されます (例 : "Worksheets (*.xls)")。指定されていないと、[ファイル] メニューの [開く] を選択したときにドキュメントの型を特定できません。
- CDocTemplate::filterExt この型のドキュメントのファイル拡張子 (例 : ".xls") です。指定されていないと、[ファイル] メニューの [開く] を選択したときにドキュメントの型を特定できません。
- CDocTemplate::regFileTypeId Windows が管理している登録データベースに格納されるドキュメントの型の識別子です。この文字列は内部でだけ使用されます (例 : "ExcelWorksheet")。指定されていないと、Windows のファイル マネージャは、このドキュメントの型を登録できません。
- CDocTemplate::regFileTypeName 登録データベースに格納されるドキュメントの型の名前です。この文字列は、登録データベースをアクセスするアプリケーションのダイアログ ボックスに表示されます (例 : "Microsoft Excel Worksheet")。
- つまり拡張子に関しては、IDR_MAINFRAMEの4番目5番目のパラメータを書き換えれば良い。
- 。。。こんなの分かるか!!拡張子の指定でこんなに手間取るなんて。。
- 2chでIDR_MAINFRAMEのキーワードが無かったら、結局分かんなかっただろうなぁ。。。
- スケルトンでは、xxxxDoc.cppに、シリアライズ関数が追加される。
- UpdateAllViewは、Viewにデータの更新を知らせるもの。
- ViewのOnUpdateメソッドが呼び出されるらしい。
- シリアライズというのは、もっと奥が深いメカニズムに思えるが、
- 良く分からないので、今のところは単なる読書きルーチンと考える。
- データ保管用に、Documentクラスのメンバ変数を用意しておく必要がある。
- テキストデータ(CString)の読込みは以下のようにやれば良い。→lineがCStringとして
while(ar.ReadString(line)) {
:
}
同様に書込みは、こんな感じ
ar.WriteString(line);
読込み、書込みは以下のようにしても良い。
ar >> line; // 読込み
ar << line; // 書込み
この書き方は何か違和感があるなぁ。シフト記号に見える。或いはPerl?
- 最初にファイルサイズを調べて、そのバイト数だけ読み込む例。
CFile *fp;
int flen,i;
BYTE fdata;
fp = ar.GetFile();
flen = fp->GetLength();
for (i=0; i<flen ;i++) {
ar >> fdata; // 1byte読む
}
ヘッダのように構造が固定のデータであれば、構造体を定義して、
ar << fdata;
構造体で一度に書き込んでも良い。
ar.Write(&m_struct,sizeof(struct_name));
CFile* pFile = ar.GetFile();
pFile->SeekToBegin();
CArchive arc( pFile, CARchive::load );
arc.ReadString( line );
arc.Close();
4日本語版では無題。こんなの訳さなくても良いのに。。
5バイトオーダーには注意。バイナリデータには、BigEndianのものが多く存在する。
[top]
- Viewクラスでは、Documentクラスで読み込んだデータを表示する。
- DocumentとViewだけなら、これでめでたしめでたしということなんだけど。。
- ダイアログバーはMainFrameの関数で読書きするようになっている。さて困った。。。
- 結局、MainFrm.cppの先頭で、ドキュメントクラスのヘッダファイルをインクルード
#include "DialogBarTestDoc.h"
後は、以下のようにドキュメントのポインタを求めればOK!
CDialogBarTestDoc* pDoc = (CDialogBarTestDoc*) GetActiveDocument();
- 具体的には、例えばEditBoxへDocument構造体の文字列を出力する場合
- これは、MainFrameをグローバルでアクセスできるように、
- MainFrm.cppの先頭で、グローバル変数を用意して、
CMainFrame* pMainFrame;
- コンストラクタで、MainFrame自身のポインタを設定
CMainFrame::CMainFrame()
{
pMainFrame = this;
}
MainFrm.hの最後でexternしておけば、これを使って他からMainFrameにアクセスできる。
extern CMainFrame* pMainFrame;
ここまでやって、やっと基本的なスケルトンとして使えるようになった。
- では実際にDocumentを読み込んだ時に、Viewでは何をするか考えてみる。
- まず、xxxxView.cppの先頭で、MainFrm.hをインクルードする。
#include "MainFrm.h"
- Docのシリアライズで、読込み時には UpdateAllViewsが実行されるので、
- ClassWizardでOnUpdateを追加して、更新処理を書けば良い。
- View上に描く部分は、Viewで行えば良いけど、DialogBarの変更はどうしよう。
- 以下のように記述したいところだが。。
void CMapViewView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
CDialogBarTestDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CString line = pDoc->m_struct.data;
pMainFrame->m_wndDialogBar.SetDlgItemText(IDC_EDIT1,line);
}
- m_wndDialogBarはプロテクトメンバになっているので、エラーになる。
- m_wndDialogBarをpublicにすれば、とりあえずOKなんだけど。。。
- やっぱり、MainFrameのことはMainFrameにやらせた方が良いかも。
- ということで、MainFrameにDialog更新用の関数を作って、
- ClassViewで、CMainFrameにdialogbar_update関数を追加する。
void CMainFrame::dialogbar_update()
{
// Docのポインタを求めてから
CDialogBarTestDoc* pDoc = (CDialogBarTestDoc*) GetActiveDocument();
CString line = pDoc->m_mapheader.lot_no;
m_wndDialogBar.SetDlgItemText(IDC_EDIT1,line);
}
この方が、DocumentはDocで、ViewのことはViewで、DialogBarのことはMainFrameでやることになるので、分かりやすい気がする。
[top]
- MFCでやるより、SDKでやった方がここまでの時間は短くて済むだろう。
- 以下は、VCによるMFCのドキュメントビュー形式のスケルトンを使う場合の感想。
- MFCスケルトンの利点として、
- CScrollViewを使うと画面スクロールが簡単
- CViewとかCScrollViewを使うと印刷が容易
- ツールバーやステータスバー等の部品が初めから付いている。
- ファイルの読書きの仕組みが出来ている。
- Dialogの内容の更新はDDX,DDVが使える6ので便利。
- MFCスケルトンの欠点
- ダイアログバーでは、DDX,DDVが使えないのでSDKと同様に直接読書きする必要がある。
- 複数のドキュメント形式を扱う場合、面倒というか良く分からん。
- クラスマネージャーが使えないクラスがあったりして、ちょっと迷う。
- いろいろ制約があって、ちょっと変わったことをやろうとすると調べるだけで、とんでもなく時間が掛かる。→これが一番の欠点
- 標準的なインターフェースを持ったアプリケーションを作りたいなら、CView,CScrollViewのスケルトンは使いやすいので、利用した方が良いと思う。
- ただ、ドキュメントビューは複数のファイル形式を扱う場合はかなりプログラムスキルを必要とするので、使わなくても良いと思う。
- ちょっと変なインターフェースのアプリケーションを作るというなら、SDKでイベント毎に書いていった方が、全然早いと思う。
- Dialogとか使えば、SDKとMFCの差はあまり無い。むしろSDKの方が自由。
- 但し、ダイアログバーのような機能はちょっと難しい。
- 印刷とかスクロールとかの機能を付けるのはかなり面倒になる。
- いずれにせよ、SDKは制約が少ないので、自分の考えでそれなりにプログラムが進む。
- しかし、MFCは制約が多いのでちゃんと調べながら進めないと出来ない。
- 但し、一般的な機能は意外と簡単に出来たりする。
- とりあえず、アプリケーションの形を決めて使う分には便利そうなので、
6DialogBarはDoDataExchangeが呼び出されないという問題があるらしいので、使えないのが残念。
[top]
[top]
[プログラムの部屋に戻る]
⇒ Disqusの広告がうるさすぎるので基本は非表示にしました