第76回 プログラミングについて【フォント変更等】
簡易テキストエディタを使ってみてもらえたでしょうか。ショートカットを作成してテキストファイルをドラッグアンドドロップしてもそのファイルは表示されないと思います。そこで、この機能を追加しましょう。
ドラッグアンドドロップでも、データファイルを開くアプリケーションを設定していても、プログラム側から見た事情は同じです。開くべきファイル名はDOSでのプログラムの引数として与えられます。例えば、DOSのプログラム dosapp.exe があったとき、
$ dosapp test.txt
として実行すると、このプログラムの起動時には第1引数にはプログラム名が、第2引数には test.txt が与えられます。Windowsのプログラムでも違いはありません。Windows用のプログラム winapp.exe があったときに、DOSプロンプトで、
$ winapp test.txt
としても殆ど同じです。
このパラメータは実行時に CWinApp クラスのデータメンバー m_lpCmdLine に代入されます。ですので、プログラムの起動時にこのパラメータが設定されているときに、ファイルをロードするようにすればいいことになります。それではこの機能を追加しましょう。これを行なうメンバー関数には CWnd クラスの OnPaint() が適当だと思われます。
OnPaint() は CWnd クラスのオブジェクトがクライアント領域を再描画する必要が生じたときに発行されるメッセージ WM_PAINT を処理するメンバー関数です。このメッセージは、CWnd クラスのオブジェクトが生成され、ウインドウのサイズが最終的に決定された後にも発生します。このときにプログラムのパラメータとして与えられたファイルをロードしようという訳です。
MyWnd クラスに、
afx_msg void OnPaint();
を追加し、メッセージマップに、
ON_WM_PAINT()
を追加します。そして MyWnd::OnPaint() 本体は、
afx_msg void MyWnd::OnPaint()
{
CPaintDC dc(this);
}
とすれば、このメンバー関数をオーバーライドすることができます。ここで非常に大切なことがあります。
MyWnd::OnPaint() に、
CPaintDC dc(this);
を記述してあるのですが、これがなかなかのくせものなのです。次のように関数の中に何も書かなくても良さそうなものです。
afx_msg void MyWnd::OnPaint()
{
}
ところが、この1行がないとプログラムがうんともすんとも言わなくなってしまうのです。最初の頃はこれが分からずに数日間も苦しんだことがありました。はっきりした原因は今のところ私には分からないのですが、とにもかくにもこの一行は、おまじないのつもりで記述して下さい。
MyWnd クラスにもう1つ次の行を追加しておきます。
int initial_load;
そして、MyWnd::MyWnd() の current_file_name[0]='\0'; の次の行に、
initial_load=1;
を追加しておきます。このようにしたのは MyWnd::OnPaint() を、
afx_msg void MyWnd::OnPaint()
{
CPaintDC dc(this);
if(initial_load){
initial_load=0;
MessageBox(
MyApp.m_lpCmdLine,
"",MB_OK);
}
}
のようにして、プログラムのパラメータを表示してみたかったからです。もし、
afx_msg void MyWnd::OnPaint()
{
CPaintDC dc(this);
MessageBox(
MyApp.m_lpCmdLine,
"",MB_OK);
}
とすると、悲惨なことになってしまうのです。メッセージボックスは表示されますが、OKボタンを押して、このボックスを終了させると、それまで非表示になっていた部分が表示されることになり、再び WM_PAINT メッセージが発生する可能性があるのです。最悪の場合は無限ループになってしまいます。
ここまで変更したらコンパイル、リンクして実行してみて下さい。DOSプロンプトでパラメータを与えて実行しても、ドラッグアンドドロップで実行してもそのパラメータ(ファイル名)が表示されます。この確認ができたら OnPaint を次の様に改修します。
afx_msg void MyWnd::OnPaint()
{
CPaintDC dc(this);
if(initial_load){
initial_load=0;
if(MyApp.m_lpCmdLine[0])LoadFile(MyApp.m_lpCmdLine);
}
}
これで、希望の機能が完成しました。
次に実際に使用してみて気になることがあったと思います。日本語のテキストだとあまり気にならないのですが、ソースプログラムなどを表示させると文字幅がまちまちで見辛かったはずです。そこで、フォントの初期値の設定とフォントを変更する機能を追加しましょう。
MyWnd クラスのデータメンバーに、
CFont edit_box_font;
を追加して MyWnd::MyWnd() のエディットボックスをクリエイトした後に、
edit_box_font.CreateStockObject(SYSTEM_FIXED_FONT);
edit_box->SetFont(&edit_box_font);
を追加します。
これでフォントの初期値が設定できます。CFont クラスはフォントに関するインターフェイスをカプセル化したものです。
edit_box_font.CreateStockObject(SYSTEM_FIXED_FONT);
は CFont クラスのオブジェクトに実際にフォントを作成させているのですが、
SYSTEM_FIXED_FONT
は、Windowsにあらかじめ用意されている固定長のフォントです。SYSTEM_FONTというものもありますが、このフォントは今までエディットボックスで使用されていた可変長のフォントです。
edit_box->SetFont(&edit_box_font);
は作成したフォントを、エディットボックスに設定しています。
初期フォントの設定ができましたので、次は実行時にフォントを変更できるようにしましょう。まず、メニューにフォントの設定を追加します。とりあえず私は、メニューが
File Setup
Open Font
Save
Exit
になるようにし、Font のID名は IDM_FONT としました。このメニューの作成はVisual C++の開発環境を使用して下さい。メニューが作成できたら RC コマンドでバイナリーファイルにコンパイルしておいて下さい。
次にフォントを変更する機能を付け加えます。MyWnd クラスの定義に、
afx_msg void OnFont();
を、メッセージマップに、
ON_COMMAND(IDM_FONT,OnFont)
を追加します。そして MyWnd::OnFont() は、
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);
}
}
とします。これもかなり難しいコードの様に見えますが、やっていることはそれ程難しくはありません。
CFontDialog クラスは、フォントの選択を簡単に行なえるようにあらかじめ用意されているもので、第1引数には LOGFONT 構造体へのポインタを、第2引数には選択するフォントの種類を指定しています。
LOGFONT 構造体はフォントの様々な情報(高さ、幅、精度、フォント名等)を持った
構造体です。また第2引数のフォントの種類 CF_SCREENFONTS はCRTディスプレイモニターなどで使用するフォントを意味しています。
このオブジェクトの第1引数に LOGFONT 構造体へのポインタを渡すために、
edit_box_font.GetLogFont(&logfont);
関数を使用して、現在 CFont クラスの edit_box_font が持っているフォントの情報を取得しています。CFileDialog と同じようにオブジェクトを生成した後は DoModal()メンバー関数を使用してフォントの選択を行ないます。選択したときには IDOK が戻り値として返ってきますので、エディットボックスに設定します。
edit_box_font.DeleteObject();
は、初期値設定で、
edit_box_font.CreateStockObject(SYSTEM_FIXED_FONT);
としたり、次の行で、
edit_box_font.CreateFontIndirect(FontDlg.m_cf.lpLogFont);
として edit_box_font にオブジェクトを生成していますので、不要になった以前のものを破棄しています。破棄をしないと、どんどんメモリー上にゴミが溜まったままになってしまいますので注意して下さい。
edit_box_font.CreateFontIndirect(FontDlg.m_cf.lpLogFont);
は選択されたフォントを新たに作成しています。FontDlg クラスにはデータメンバーにCHOOSEFONT 構造体の m_cf があり、このメンバーの LOGFONT 構造体へのポインタlpLogFont が選択されたフォントの情報保持しています。これを引数にして新たにフォントを作成します。作成した後はエディットボックスに設定します。
edit_box->SetFont(&edit_box_font);
これで実行時のフォントの設定が行なえるようになります。
長くなってしまいましたので、ここまでのプログラム全文を載せておきます。
#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;
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();
void LoadFile(char *file_name);
int OnSaveFile();
void SaveFile(char *file_name);
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;
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);
::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_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);
}
}
かなりプログラムが長くなってしまいましたが、そろそろこのエディタも最終コーナーを曲がりました。もう少し機能を追加しますので我慢して下さい。
それではまた次回。