プリント基板設計・シミュレーション

TOP > アポロレポート > コラム > 第77回 プログラミングについて:CEdit クラスのメンバー関数 SetTabStops
コラム
2024/07/22

第77回 プログラミングについて:CEdit クラスのメンバー関数 SetTabStops

アポロレポート

 前回まで作ってきた簡易テキストエディタもゴールが見えてきました。予定している機能は、クリップボードとのやり取りとタブストップの設定です。

 クリップボートのやり取りはエディットボックスが CEdit クラスを使用していますので非常に簡単に行えます。まずメニューが、

 File  Edit  Setup
  Open  Copy   Font
  Save  Paste
  Exit

になるように作成します。Copy には IDM_COPY、Paste には IDM_PASTE とID名称を設定しておきます。このようにメニューを作成したらバイナリファイルにコンパイルして下さい。

 次に MyWnd クラスに、

 afx_msg void OnCopy();
 afx_msg void OnPaste();

を、メッセージマップに、

 ON_COMMAND(IDM_COPY,OnCopy)
 ON_COMMAND(IDM_PASTE,OnPaste)

を追加します。そして、MyWnd::onCopy() と MyWnd::onPaste() の実体は、

 afx_msg void MyWnd::OnCopy()
 {
  edit_box->Copy();
 }

 afx_msg void MyWnd::OnPaste()
 {
  edit_box->Paste();
 }

とします。なんだか拍子抜けの感がありますが CEdit クラスはこの機能をサポートしていますのでそのまま利用することにします。本来ならば、メニュー Paste がクリップボードにテキストがないときには灰色で、あるときには黒にした方がいいのですがこのエディタではこのままとしておきます。

 最後にタブストップの設定機能を追加しましょう。エディットボックスにタブストップを設定するには CEdit クラスのメンバー関数 SetTabStops を使用します。MyWnd クラスに、

 int tab_stop;

データと、

 void SetTabStop(int tab_stop);

関数を追加します。初期設定は、MyWnd::MyWnd() の initial_load=1; の次の行に

 tab_stop=8;

を追加し(ここではタブストップを8とします)、エディットボックスにフォントを設定する行 edit_box->SetFont(&edit_box_font); の後に、

 SetTabStop(tab_stop);

を追加します。更に、フォントを変更した後にも必要ですので MyWnd::OnFont(); のedit_box->SetFont(&edit_box_font); の後にも、

 SetTabStop(tab_stop);

を追加します。MyWnd::SetTabStop() の本体は、

 void MyWnd::SetTabStop(int tab_stop)
 {
  edit_box->SetTabStops(tab_stop*4);
 }

です。詳細は CEdit のヘルプを参照して下さい。

実行時のタブストップの変更は、まずメニューを、

 File  Edit  Setup
  Open  Copy   Font
  Save  Paste  Tab Stop
  Exit

になるように変更して下さい。Tab のID名には IDM_TABSTOP としておきます。メニューの変更が終了したら、MyWnd クラスに、

 afx_msg void OnSetTabStop();

を、メッセージマップには、

 ON_COMMAND(IDM_TABSTOP,OnSetTabStop)

を追加します。今までのやり方ではこのまま MyWnd::OnSetTabStop() の本体を作成するのですが、タブストップの値をダイアログボックスを表示させて入力したいので、タブストップを入力するためのダイアログボックスを作成します。Visual C++の開発環境でメニューを追加したときと同じようにダイアログボックスを追加します。ダイアログボックスのキャプションはなんでも構いませんが、私は Set tab stop とし、ダイアログボックスのIDは "SETTABSTOP" と文字列の名称にしておきました。
 このダイアログボックスにエディットボックスを1つ追加し、このエディットボックスのIDは ID_SETTAB_TABSTOP にしておきます。多分ここまで頑張って簡易テキストエディタを自分でも作ってきた方なら、それほど苦労しないで作成できると思います。

 リソースファイルをバイナリにコンパイルした後は、MyWnd::OnSetTabStop() の本体の作成です。ここで考えておかなければならないことは、先ほど作成したダイアログボックスのリソースをどのように使用するかです。
 直接このリソースを MyWnd::OnSetTabStop() では使用できませんので、このリソースをテンプレートとした新しいクラスを作成しなければなりません。MyWnd クラスを派生させたときと殆ど変りませんが、違うところは CWnd クラスから派生させるのではなく、CDialog クラスから派生させるところです。タブストップを設定する新しいクラス名を、TabStopDialog とすると、

 class TabStopDialog : public CDialog
 {
  public:
   int tab_stop;

   TabStopDialog(CWnd *parent,int tab_stop_arg)
               : CDialog("SETTABSTOP",parent){
    tab_stop=tab_stop_arg;
   };
 };

として派生させます。ここで見慣れないのが、TabStopDialog のコンストラクタです。

 TabStopDialog(CWnd *parent,int tab_stop_arg)
             : CDialog("SETTABSTOP",parent){ ... }

この式の : CDialog("SETTABSTOP",parent){ ... } が問題です。CDialog クラスのコンストラクタは、第1引数のダイアログリソースのテンプレート名、第2引数には親ウインドウへのポインタを指定して作成すると、モーダルダイアログというダイアログボックスが生成されます。モーダルダイアログボックスというのは、そのダイアログボックスが存在する限りはその親ウインドウには入力フォーカスが戻らないものです。ですので、値の入力等をした後はそのダイアログボックスを終了させる必要があるのです。それに対し、モードレスダイアログボックスはそのダイアログボックスが存在していても、他のウインドウをアクティブにして入力することが可能です。
 問題はここではありません。TabStopDialog のコンストラクタに CDialog のコンストラクタがくっついて表現されているところです。これはWindowsのプログラムどうのこのではなく、C++言語のコンストラクタの継承を行うときの表現なのです。このように表現すると、TabStopDialog のコンストラクタは CDialog のコンストラクタをそのまま引継ぎ、更に { ... } の中で TabStopDialog 独自の生成時のコードを追加することができるのです。そこで TabStopDialog クラスはデータメンバーにパブリック属性で int 型の tab_stop を1つ持つことにします。ここまで定義したら、

 MyWnd::OnSetTabStop() 本体は、

 afx_msg void MyWnd::OnSetTabStop()
 {
  TabStopDialog TSDlg(this,tab_stop);

  TSDlg.DoModal();
 }

とすることで、タブストップを設定するダイアログボックスが表示することができますが、まだ実際に設定する機能は何も備わってはいません。その機能を追加しましょう。まず TabStopDialog クラスを次の様にします。
 
class TabStopDialog : public CDialog
 {
  public:
   int tab_stop;

   TabStopDialog(CWnd *parent,int tab_stop_arg)
               : CDialog("SETTABSTOP",parent){
    tab_stop=tab_stop_arg;
   };

   virtual BOOL OnInitDialog();
   afx_msg void OnOk();

   DECLARE_MESSAGE_MAP()
 };

 virtual BOOL OnInitDialog() メンバー関数は CDialog クラスから派生したクラスのオブジェクトが生成されたときに自動的に呼び出されるダイアログボックスの初期化関数です。よっぽどのことがない限り、この初期化関数を使用しないことはありません。afx_msg void OnOk() メンバー関数はタブストップ値を入力するエディットボックスでリターンキーを押したときや OK ボタンをクリックしたときに呼び出される関数とします。

 メッセージマップは、

 BEGIN_MESSAGE_MAP(TabStopDialog,CDialog)
  ON_COMMAND(IDOK,OnOk)
 END_MESSAGE_MAP()

とするだけです。これについては説明の必要はないと思います。

TabStopDialog::OnInitDialog() 本体は、

 BOOL TabStopDialog::OnInitDialog()
 {
  char tmp[21];

  sprintf(tmp,"%d",tab_stop);
  SetDlgItemText(ID_SETTAB_TABSTOP,tmp);
  GetDlgItem(ID_SETTAB_TABSTOP)->SetFocus();

  return FALSE;
 }

とタブストップを入力するエディットボックスに、現在のタブストップを初期状態で表示させるためにその内容を設定しているだけです。

 GetDlgItem(ID_SETTAB_TABSTOP)->SetFocus();

は、ダイアログボックスのリソースを作成したときのタブストップの順番によってはディフォルトでは初期状態でエディットボックスにフォーカスがないときもありますので、念のために強制的に行っています。

TabStopDialog::OnOk() 本体は、

 afx_msg void TabStopDialog::OnOk()
 {
  char tmp[81];

  GetDlgItemText(ID_SETTAB_TABSTOP,tmp,sizeof(tmp));
  if(atoi(tmp)<=0)return;

  tab_stop=atoi(tmp);

  EndDialog(IDOK);
 }

とします。この関数はあまり難しいものではありませんが、1つだけ気にしておいた方がいいのは、タブストップを入力するエディットボックスでリターンキーを押しても、OKボタンをクリックしてもこのメンバー関数が呼び出されることです。このダイアログボックスでは1つの値しか設定しませんので、無条件にタブストップの値を検査し、値に誤りがなければ CDialog クラスのメンバー関数 EndDialog() を呼び出して、呼び出し側に制御を戻せばいいのですが、複数の値を設定する場合などでは、メッセージの種類は IDOK ですので、複数のエディットボックスから1つを特定するには現在入力フォーカスを持っているものを探し出すようにしなければなりません。
 値が設定されたときには TSDlg.DoModal() には IDOK が返ってきますので、このときにエディタのエディットボックスのタブストップを設定すればいいことになります。そこで、MyWnd::OnSetTabStop() 本体は、

 afx_msg void MyWnd::OnSetTabStop()
 {
  TabStopDialog TSDlg(this,tab_stop);

  if(TSDlg.DoModal()==IDOK){
   tab_stop=TSDlg.tab_stop;
   SetTabStop(tab_stop);
  }
 }

とします。これで完成です。最後に出来上がったプログラム全体。

 #include <afxwin.h>
 #include <afxdlgs.h>
 #include "resource.h"
 #include "dummy.h"

 class MyApp : public CWinApp
 {
  public:
   virtual BOOL InitInstance();
 };

 class MyWnd : public CFrameWnd
 {
  private:
  CEdit *edit_box;
  CFont edit_box_font;

  char current_file_name[_MAX_PATH];
  int  initial_load;

  int  tab_stop;

  public:
   MyWnd();

   afx_msg void OnOpen();
   afx_msg void OnSave();
   afx_msg void OnClose();
   afx_msg void OnSize(UINT nType,int cx,int cy);
   afx_msg void OnPaint();
   afx_msg void OnFont();
   afx_msg void OnCopy();
   afx_msg void OnPaste();
   afx_msg void OnSetTabStop();

   void LoadFile(char *file_name);
   int OnSaveFile();
   void SaveFile(char *file_name);
   void SetTabStop(int tab_stop);

  DECLARE_MESSAGE_MAP()
 };

 class MyApp MyApp;

 BOOL MyApp::InitInstance()
 {
  m_pMainWnd = new class MyWnd;

  m_pMainWnd->SetIcon(LoadIcon(ID_MYAPP_ICON),TRUE);

  return TRUE;
 }

 MyWnd::MyWnd()
 {
  RECT rect;

  edit_box=NULL;
  current_file_name[0]='\0';
  initial_load=1;
  tab_stop=8;

  Create(NULL,"",WS_OVERLAPPEDWINDOW | WS_VISIBLE ,rectDefault,NULL,
     "MYAPP_MENU");

  GetClientRect(&rect);

  edit_box=new CEdit();
  edit_box->Create(
        WS_CHILD | WS_VISIBLE | WS_DLGFRAME | WS_HSCROLL | WS_VSCROLL
       | ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL ,
       rect,this,ID_EDIT_BOX);

  edit_box_font.CreateStockObject(SYSTEM_FIXED_FONT);
  edit_box->SetFont(&edit_box_font);

  SetTabStop(tab_stop);

  ::SetFocus(edit_box->m_hWnd);
 }

 BEGIN_MESSAGE_MAP(MyWnd,CFrameWnd)
  ON_COMMAND(IDM_OPEN,OnOpen)
  ON_COMMAND(IDM_SAVE,OnSave)
  ON_COMMAND(IDM_EXIT,OnClose)
  ON_COMMAND(IDM_FONT,OnFont)
  ON_COMMAND(IDM_COPY,OnCopy)
  ON_COMMAND(IDM_PASTE,OnPaste)
  ON_COMMAND(IDM_TABSTOP,OnSetTabStop)

  ON_WM_SIZE()
  ON_WM_CLOSE()
  ON_WM_PAINT()
 END_MESSAGE_MAP()

 afx_msg void MyWnd::OnSize(UINT nType,int cx,int cy)
 {
  RECT rect;

  if(edit_box==NULL)return;

  GetClientRect(&rect);

  edit_box->MoveWindow(&rect);
 }

 afx_msg void MyWnd::OnOpen()
 {
  if(edit_box->GetModify()){
   if(MessageBox(
      "Text was modified !\n"
      "Save ?",
      "Open error",MB_YESNO)==IDYES){
      if(!OnSaveFile())return;
   }
  }

  CFileDialog FileDlg(
        TRUE,"txt",NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
        "Text files (*.txt)|*.txt|All files (*.*)|*.*||",this);

  if(FileDlg.DoModal()==IDOK){
   LoadFile(FileDlg.GetPathName().GetBuffer(_MAX_PATH));
  }
 }

 void MyWnd::LoadFile(char *file_name)
 {
 #define MAX_BUF  60000

  FILE *fp;
  char buf[MAX_BUF];
  int lbuf;
  char data[513];
  int ldata;

  fp=fopen(file_name,"rb");
  if(fp==NULL){
   sprintf(data,"--- Can not open ---\n%s",file_name);
   MessageBox(data,"Open error",MB_OK);
   return;
  }

  edit_box->SetSel(0,-1);
  edit_box->Clear();

  strcpy(current_file_name,file_name);

  buf[0]='\0';
  lbuf=0;

  while(1){
   if(fgets(data,sizeof(data),fp)==NULL)break;
   if(data[0]==0x1a)break;

   ldata=strlen(data);

   if(data[ldata-1]==0x1a)data[--ldata]='\0';

   if(ldata+lbuf>MAX_BUF-1)break;

   strcpy(buf+lbuf,data);
   lbuf+=ldata;
  }

  if(lbuf>2){
   if(buf[lbuf-2]=='\r' && buf[lbuf-1]=='\n')buf[lbuf-2]='\0';
  }

  edit_box->ReplaceSel(buf);
  edit_box->SetSel(0,0);
  edit_box->SetModify(FALSE);

  fclose(fp);

 #undef MAX_BUF
 }

 afx_msg void MyWnd::OnSave()
 {
  OnSaveFile();
 }

 int MyWnd::OnSaveFile()
 {
  CFileDialog FileDlg(
         FALSE,"txt",current_file_name,
         OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
         "Text files (*.txt)|*.txt|All files (*.*)|*.*||",this);

  if(FileDlg.DoModal()==IDOK){
   SaveFile(FileDlg.GetPathName().GetBuffer(_MAX_PATH));
   return 1;
  }

  return 0;
 }

 void MyWnd::SaveFile(char *file_name)
 {
 #define MAX_BUF  60000
  FILE *fp;

  char buf[MAX_BUF];
  int lbuf;
  char data[512];
  int ldata;

  int total_line,line;

  fp=fopen(file_name,"wb");
  if(ferror(fp)){
   sprintf(data,"--- Can not open ---\n%s",file_name);
   MessageBox(data,"Open error",MB_OK);
   return;
  }

  total_line=edit_box->GetLineCount();

  buf[0]='\0';
  lbuf=0;

  for(line=0;line<total_line;line++){
   ldata=edit_box->GetLine(line,data,sizeof(data));
   if(total_line==1 && ldata==0)break;
   if(ldata+lbuf>MAX_BUF-3)break;

   data[ldata++]='\r'; data[ldata++]='\n'; data[ldata]='\0';

   strcpy(buf+lbuf,data);
   lbuf+=ldata;
  }

  fprintf(fp,"%s",buf);

  fclose(fp);

  edit_box->SetModify(FALSE);

 #undef MAX_BUF
 }

 afx_msg void MyWnd::OnClose()
 {
  if(edit_box->GetModify()){
   if(MessageBox(
     "Text was modified !\n"
     "Save ?",
     "Open error",MB_YESNO)==IDYES){
      if(!OnSaveFile())return;
   }
  }

  CFrameWnd::OnClose();
 }

 afx_msg void MyWnd::OnPaint()
 {
  CPaintDC dc(this);

  if(initial_load){
   initial_load=0;

   if(MyApp.m_lpCmdLine[0])LoadFile(MyApp.m_lpCmdLine);
  }
 }

 afx_msg void MyWnd::OnFont()
 {
  LOGFONT logfont;

  edit_box_font.GetLogFont(&logfont);

  CFontDialog FontDlg(&logfont,CF_SCREENFONTS);

  if(FontDlg.DoModal()==IDOK){
   edit_box_font.DeleteObject();
   edit_box_font.CreateFontIndirect(FontDlg.m_cf.lpLogFont);
   edit_box->SetFont(&edit_box_font);
   SetTabStop(tab_stop);
  }
 }

 afx_msg void MyWnd::OnCopy()
 {
  edit_box->Copy();
 }

 afx_msg void MyWnd::OnPaste()
 {
  edit_box->Paste();
 }

 void MyWnd::SetTabStop(int tab_stop)
 {
  edit_box->SetTabStops(tab_stop*4);
 }

 class TabStopDialog : public CDialog
 {
  public:
   int tab_stop;

   TabStopDialog(CWnd *parent,int tab_stop_arg)
                : CDialog("SETTABSTOP",parent){
    tab_stop=tab_stop_arg;
   };

   virtual BOOL OnInitDialog();
   afx_msg void OnOk();

  DECLARE_MESSAGE_MAP()
 };

 afx_msg void MyWnd::OnSetTabStop()
 {
  TabStopDialog TSDlg(this,tab_stop);

  if(TSDlg.DoModal()==IDOK){
   tab_stop=TSDlg.tab_stop;
   SetTabStop(tab_stop);
  }
 }

 BOOL TabStopDialog::OnInitDialog()
 {
  char tmp[21];

  sprintf(tmp,"%d",tab_stop);
  SetDlgItemText(ID_SETTAB_TABSTOP,tmp);
  GetDlgItem(ID_SETTAB_TABSTOP)->SetFocus();

  return FALSE;
 }

 BEGIN_MESSAGE_MAP(TabStopDialog,CDialog)
  ON_COMMAND(IDOK,OnOk)
 END_MESSAGE_MAP()

 afx_msg void TabStopDialog::OnOk()
 {
  char tmp[81];

  GetDlgItemText(ID_SETTAB_TABSTOP,tmp,sizeof(tmp));
  if(atoi(tmp)<=0)return;

  tab_stop=atoi(tmp);

  EndDialog(IDOK);
 }

リソースファイルはこれまでの説明を順に好みで作成していただければいいと思います。

 最終的にはかなり長くなってしまいましたが、Windowsのアプリケーションを作るときの重要な要素の殆どが網羅されていますので、他のプログラムを作成するときの参考にして下さい。

 簡易テキストエディタについての補足説明になりますが、このプログラムでは編集用のエディットボックスを CEdit クラスをそのまま使用しました。ですので、自分独自のキー操作は全く使用することができません。もし、もう少し機能を追加したいのであれば、CEdit そのものを使うのではなく、CEdit から派生させたクラスを作成し、そのクラスを使用するようにして下さい。そして、このクラスに送られてくるキー入力等のメッセージを CEdit のディフォルトの処理をさせるのではなく、任意な処理を行う様にして下さい。このようにすればかなり実用になるエディタになるはずです。
 しかしながら、あくまでも簡易テキストエディタの域は脱することはできませんので、そのことは忘れないで下さい。もし本格的なテキストエディタを作成するというのであれば、このプログラムの様な安易な方法ではなく、すべてを自分で作成する必要があります。もっとも、どうしても自分の欲しいテキストエディタが世の中にないとか、テキストエディタを作ることが仕事ではない限り、あまり自分で作成する機会もないかも知れません。

 ということで、簡易テキストエディタは今回で終了です。ではまた次回。


そのお悩み、
アポロ技研に話してみませんか?

アポロ技研でワンランク上の物創りへ!
そのお悩み、アポロ技研に話してみませんか?