[MFC]編集済のファイルを閉じると保存する

MFC
この記事は約8分で読めます。

テキストエディタで作業していて、途中で中断する際に保存もせずにバツボタンを押してウィンドウを閉じる操作をしてしまうことはよくあることです。

しかし、そのせいで作業していた内容が消えてしまうのはよろしくないですよね。基本的にWindowsのエディタは親切なので、ユーザーに「本当に閉じてもいいですか?」と確認を促すのがほとんどのはずです。

この記事では、テキストエディタでファイルが編集済の場合、ファイルを閉じる前に保存するか確認するように実装します。

実装内容の洗い出し

今回の対応では以下の点を実装します。

  1. キー入力があった場合、ファイル編集済状態とする。ファイルが保存された場合は、ファイル未編集状態に戻す。その後再びキー入力があった場合はファイル編集済状態に戻す。
  2. 編集済のファイルを閉じる場合、「ファイルを保存するか?」を尋ねる。YESならファイルを保存、NOなら保存せずにファイルを閉じる。

機能の実装

ファイルの編集状態の管理

ファイルの編集状態の管理を行うために、状態を保持するフラグを追加しましょう。

CMyEditDlgクラスのメンバにm_lUpdateFlgを追加します。このフラグはTRUEで編集済、FALSEで未編集を表します。このメンバを以下のタイミングで次のように更新します。

タイミングフラグ更新後の状態
コンストラクタファイル未編集
ファイル開くファイル未編集
キー入力ファイル編集済
ファイル保存 ファイル未編集
ファイル閉じる ファイル未編集

以上を実際に実装すると次のようになります。

m_lUpdateFlg メンバの追加

このフラグはダイアログクラスの中でしか使わないので、protectedとして定義しました。

// CMyEditorDlg ダイアログ
class CMyEditorDlg : public CDialogEx
{
   ~~省略~~
// 実装
protected:
	HICON m_hIcon;
	long	m_lUpdateFlg;
   ~~省略~~
}

フラグ更新処理-コンストラクタ

// コンストラクタ
CMyEditorDlg::CMyEditorDlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_MYEDITOR_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	m_lUpdateFlg = FALSE;
}

フラグ更新処理-ファイルを開く

ファイルを開く場合のフラグの更新処理は単純で、ファイルが開かれた場合に必ず「ファイル未編集」の状態にすることです。そのため条件も何も見ずに新たなファイルが開かれたらFALSEを代入するようにしています。

// ファイルを開く
void CMyEditorDlg::OnMOpenfile()
{
	// TODO: ここにコマンド ハンドラー コードを追加します。

	CFileDialog		FDlg(TRUE, _T("txt"), _T(""), OFN_FILEMUSTEXIST, "Text Files(*.txt)|*.txt|All Files(*.*)|*.*||");
	CString			ss;
	CString			ssall;
	char			c[256];

	// OKが押されたらファイルを開く
	if (FDlg.DoModal() == IDOK)
	{
		// ファイルがOpen済の場合はクローズする
		CloseFile();
		if (m_file.Open(FDlg.GetPathName(), CFile::modeReadWrite | CFile::typeText))
		{
              ~~省略~~
			m_lUpdateFlg = FALSE;
		}
	}
}

フラグ更新処理-キー入力

キー入力によるフラグの更新処理です。キー入力があった場合にこのイベントが走り、m_lUpdateFileがTRUEとなります。

void CMyEditorDlg::OnEnUpdateEdit()
{
	// TODO: これが RICHEDIT コントロールの場合、このコントロールが
	// この通知を送信するには、CDialogEx::OnInitDialog() 関数をオーバーライドし、
	// EM_SETEVENTMASK メッセージを、
	// OR 状態の ENM_UPDATE フラグを lParam マスクに入れて、このコントロールに送信する必要があります。

	// TODO: ここにコントロール通知ハンドラー コードを追加してください。
	// 未編集状態の場合編集済にする
	if (m_lUpdateFlg == FALSE) {
		m_lUpdateFlg = TRUE;
	}
}

フラグ更新処理- ファイル保存

さて今回大きく変わった処理の一つ、「ファイル保存」です。

ファイル保存は従来、メニューキーが押された時にOnMSave()で行っていましたが、今回中身の処理をSaveFile()というメンバ関数に任せるようにしました。これは、保存処理を関数化して、ファイルのクローズ処理から呼び出す必要が出てきたからです。なので動作自体に大きな変更点はありません。SaveFile()の一番最後でm_lUpdateFlg をFALSEにして、ファイル未編集状態に戻しているくらいですね。

// ファイル保存
void CMyEditorDlg::OnMSave()
{
	// TODO: ここにコマンド ハンドラー コードを追加します。
	SaveFile();

}

void CMyEditorDlg::SaveFile(void)
{
	// TODO: ここにコマンド ハンドラー コードを追加します。
	CString		ss;
	CString		filepath;
	long		mode;


	if (m_file.m_hFile != CFile::hFileNull)
	{
		// ファイルが有る場合
	}
	else
	{
		// ファイルがない場合は開く
		mode = CFile::modeCreate | CFile::modeReadWrite | CFile::typeText;

		if (m_file.Open(TEXT("C:\\temp\\test.txt"), mode))
		{
		}
		else
		{
			// ファイルがなくて、ファイルを新規で開けなかったらreturnする。
			return;
		}
	}

	m_edit.GetWindowText(ss);

	// 今開いているファイルを一旦消す
	filepath = m_file.GetFilePath();
	m_file.Close();
	m_file.Remove(filepath);
	m_file.Flush();

	// ファイルを開き直す。
	mode = CFile::modeCreate | CFile::modeNoTruncate | CFile::modeReadWrite | CFile::typeText;
	m_file.Open(filepath, mode);

	// ファイルを保存する
	m_file.WriteString(ss);

	// 保存後は必ず未編集となる。
	m_lUpdateFlg = FALSE;

}

ファイルクローズ時の保存確認

ファイルを閉じる

ファイル閉じる処理も同様に関数化しています。またCloseFile()内でファイル更新のチェックを行い、更新ありの場合はSaveFile()を呼び出すようにしています。これはファイルを閉じる機能の一部にファイルを保存するという機能があるからです。

CloseFile()を関数化したことによって、今後ファイルを閉じる操作パターンが増えた場合でも、非常に楽にコーディングすることができます。

void CMyEditorDlg::OnMClosefile()
{
	// TODO: ここにコマンド ハンドラー コードを追加します。
	CloseFile();
	m_edit.SetWindowText("");
}

void CMyEditorDlg::CloseFile(void)
{
	// TODO: ここにコマンド ハンドラー コードを追加します。
	if (m_file.m_hFile != CFile::hFileNull)
	{
		if (m_lUpdateFlg == TRUE)
		{
			if (MessageBox(TEXT("ファイルが保存されていません。\n\rファイルの編集内容を保存しますか?")
				, TEXT("MyEditor"), MB_YESNO) == IDYES)
			{
				SaveFile();
			}
		}
		m_file.Close();
		// セーブしなかった場合でも、ファイルをクローズしたら未編集状態に戻る。
		m_lUpdateFlg = FALSE;
	}
}

また、ファイル自体ではなくアプリケーションそのものを終了する際に走るイベントのOnClose()にあったファイルを閉じる処理も、今回追加したCloseFile()を呼ぶように変更しました。

void CMyEditorDlg::OnClose()
{
	// TODO: ここにメッセージ ハンドラー コードを追加するか、既定の処理を呼び出します。
	CDialogEx::OnClose();
	CloseFile();
}

まとめ

というわけで今回は、メンバにフラグを追加して、フラグによって動作を管理する処理を実装してみました。

まだもっといい作りにできると思いますが(フラグのセット/リセットを関数化するとか? 毎回現在の状態を見てチェックするのも…しかし呼び出しのオーバーヘッドが無駄? そもそもLongじゃなくてBoolにすべきだよね今回の使い方だと)ひとまずこれでよしとします。

今回実装した処理は非常に単純ですが、アプリケーションの便利さへの貢献は大きいと思います。

これで間違えてウィンドウを閉じてしまっても安心ですね。

コメント

タイトルとURLをコピーしました