設計メモ


目次

    ツリーをたたむ(2000/01/12)
      ExpandとSelect(2000/01/12)
    内部構造の見直し(2000/01/06)
      (1)セクションオブジェクトの汎用化(2000/01/09)
        ・定数(2000/01/10)
        CMyNode::AddNewNode 新規セクションの追加/挿入(2000/01/09)
        MoveSubTree 部分木の移動(2000/01/10)
        CopySubTree 木構造のコピー
        ・補足(ブックマーク)(2000/01/09)
        セクションオブジェクトクラス ヘッダファイル
          // MyNode.h: CMyNode クラスのインターフェイス
    検索処理の見直し(2000/01/01)
      追加(2000/01/03)
      修正(2000/01/03)
    INIファイルの使用(1999/12/17)
    単語を選択(Ctrl+W)(1999/11/23)
      単語を選択(その2)(1999/11/25)
    カーソル位置のインジケータ表示(1999/11/21)
    現在のクラス構成(1999/11/06)
      現在のデータ構造の問題点(1999/11/06)
      実現手段(1999/11/07)
    セクション・レベル編集(1999/10/30)
      セクション分割(1999/10/31)
      削除セクション(1999/11/05)
    α版 残作業(H11.10.24)
    改ページコード(H11.10.17)
    ブックマーク(3)
    ブックマーク(2)
      キャレットの移動
      メニュー
      現在カーソルの有る行が、ブックマーク行か否かの判断
    ブックマーク
      関連するメンバ関数
        GetLineCount
        GetFirstVisibleLine
        LineIndex
        LineLength
      アルゴリズム
      ブックマークオブジェクトおよびブックマーク管理オブジェクト
        ブックマーク行のソート
    RichEditViewへの変更
      RichEditViewの使用に関して(1)
    ビューへのアクセス
      ビューへのアクセス U(2000/01/12)
    セクションオブジェクト
      リンクメンバ(2000/01/12)
ツリーをたたむ(2000/01/12)




[表示][ツリーをたたむ]メニューを選んだ時の動作は、
現在選択中のセクションは表示したまま、他の開いている
ノードは全て閉じるというものである。

これを実現するために、現在は、

1.選択中のセクションを記録
2.すべてのノードを閉じる
3.記録したセクションを、選択し直す

という処理を行なっている。現在選択中のセクションが子セクションの
場合、2の処理で一旦隠れるが、3の処理で必要最小限のノードのみ
開いて元の選択中のセクションが表示される仕組みである。

ただ、問題は現在、ツリーの開閉状態に変更があるか無いかのチェックを
2の処理の中でのみ行なっており、3の処理でキャンセルされても、
ツリーの開閉状態に変更があったと認識される点である。
まあ、実用上なんの問題も無いとは思うのだが。

これを以下の考えで解決する。
トップレベルのセクションをレベル1、その子セクションをレベル2と
すると、すべてのノードが閉じられている状態からレベルnのノードを
表示するためには、(n-1)個のノードを開けば、必要充分である。
したがって、「2.すべてのノードを閉じる」処理で閉じたノードの数と
選択中のセクションのレベルを比較する事で、ツリーの開閉状態に
変更があったかどうかを判定する。



ExpandとSelect(2000/01/12)

すでに1回以上開かれているノードに対し、
Expand()を実行しても、
CStviView::OnSelchanged()は呼ばれないのに、
Select()を実行して、ノードを開くと
CStviView::OnSelchanged()が呼ばれるのはなぜ?

まあ、理由はともあれ、Select()1行のきれいなコードにはならない。

内部構造の見直し(2000/01/06)




一応、Version1.00を公開できて、一段落着いたんで、
内部のいもくさい処理をなんとかしたいと思っています。

作業途中で、わかったこと、思い出したことなど、いくつか
あったのですが、とりあえず、Version1.00公開を最優先に
してしまいました。
以下に、重点項目を列挙します。

(1)セクションオブジェクトの汎用化
(2)ビュー、ドキュメントのカプセル化

(1)セクションオブジェクトの汎用化(2000/01/09)

基本的な構造は、現在のままとする。
すなわち、

ツリー構造のための情報は、CTreeCtrlアイテムに管理させる。
初期化、移動処理用にサブ的なツリー構造のための情報は持つ
(この情報をもつ変数を、今後リンクメンバ変数と呼ぶ)

今回の、追加変更仕様として、
今までのセクションオブジェクトは、
ツリーのノード情報であるHTREEITEMの値のみを保持していたが、
ツリー自体を保持するCTreeCtrlオブジェクトの値も保持する。

そして、変更のための指針として
・CTreeCtrlの木構造に対する操作は許可する
・CtreeViewやドキュメントオブジェクトに対する操作は許可しない
・アプリケーションObject、ビューObject、ドキュメントObjectに関しては、
MFCライブラリレベルまでのアクセスは必要に応じて許可するが、
アプリケーション依存の派生クラスのヘッダは、インクルードしない。

まず、次の関数群を修正する。(細かい事は無視して下さい。
現状では整理されていない事を示しているだけです。)
void CMyNode::AddNewNode(CTreeCtrl *pTree, CMyNode *obj, int mode) ;
int CMyNode::MapSubTree(CTreeCtrl *pTree);
int CMyNode::CrearTreeItem(void);
int CMyNode::GetOrder(void) { return iNodeOrder; }
static int CMyNode::SetOrder(CTreeCtrl *pTree);

static HTREEITEM CMainFarme::CopySubTree(CTreeCtrl *pTree,
HTREEITEM CMainFarme::MoveSubTree(CTreeCtrl *pTree,
int CMainFarme::SlideNode(int mode) ;
int CMainFarme::AddNewNode(int mode);

int CStviView::AddNewNode(int mode)

木構造の変更自体は、すべて、セクションオブジェクト(CMyNode)で処理する
ドキュメントへの変更通知、ビューへのアップデート通知は、これらの関数を
呼び出した側で行なうものとする。以下に新しい設計の詳細を示す

・定数(2000/01/10)

// セクション追加処理に関する定数
enum {
} ; /* AddNewNode */

// セクション移動(コピー)処理に関する定数
enum {
} ; /* MoveSubTree */

// セクションのスライドに関する定数
enum {
} ; /* SlideNode */

CMyNode::AddNewNode 新規セクションの追加/挿入(2000/01/09)

// 新規セクションの追加
static void AddNewNode(CTreeCtrl *pTree, CMyNode *obj, int mode) ;

指定されたツリーpTreeの選択されたノードに対して、
指定された空セクションobjを、指定されたmodeの位置に追加する。


MoveSubTree 部分木の移動(2000/01/10)

// 部分木の移動
static int MoveSubTree(
指定されたセクションpSrcをルートセクションとする部分木を
指定されたツリーpTreeの指定されたノードpDstの
指定された関係modeの位置に移動する。
置換モード(mode=MST_REPLACE)が指定された場合には、pDstをpSrcで置き換える。
bChangeOrderがTRUEの場合には、移動処理の後にセクション順序の更新を行なう。


CopySubTree 木構造のコピー

// 木構造のコピー
static int CopySubTree(CMyNode *pSrc, CTreeCtrl *pTree,
リンクメンバ変数により、構成された木構造pSrcを、
指定されたツリーpTreeの指定されたノードpDstの
指定された関係modeの位置に移動する。
置換モード(mode=MST_REPLACE)が指定された場合には、pDstをpSrcで置き換える。
bChangeOrderがTRUEの場合には、移動処理の後にセクション順序の更新を行なう。

木構造pSrcは、必ずしも部分木とは限らない。

・補足(ブックマーク)(2000/01/09)

本アプリケーションではブックマーク検索の効率を上げるため、
ブックマークは別オブジェクトで管理している。
そして、ブックマーク検索/登録の効率を良くするために、
セクション順序を、セクションオブジェクト自身に保持させている。
セクション順序とは、テキストファイルの中でセクションに最初から
順に0から始まる番号を振っていったものである。
セクション順序は、挿入・削除・分割・移動といった処理により
更新される。

ブックマークを別オブジェクトで管理するか、セクションオブジェクトを
スキャンして管理するかは、アプリケーションに依存する問題なので
そのセクション内のブックマークをブックマーク管理オブジェクトに
登録する処理は、セクションオブジェクトの処理から除外する。

セクション順序の更新を内部的に行なうか、挿入・削除・分割・移動といった
処理を呼び出した側で行なうかは、悩むところだが、セクション順序保持の
ためのメンバ変数をこのまま維持することとし、メンバ変数の保守は
内部的に行なうのが筋と判断する。


セクションオブジェクトクラス ヘッダファイル

// MyNode.h: CMyNode クラスのインターフェイス

//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_MYNODE_H)
#define AFX_MYNODE_H

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

class CMyLine ;
class CMyLineMng ;

class CMyNode
{
// コンストラクタ、デストラクタ
public:
// 初期化処理
public:
public:
public:
public:
// TreeCtrl関連
public:
HTREEITEM GetTreeItem(void) { return m_hTreeViewNode ; }
public:
public:
// ツリー構造操作
// TreeCtrlのアイコンイメージ関連
// 改行コード関連
public:
// セクション順序関連
public:
public:
protected:
// 行管理オブジェクト
public:
protected:
// リンクメンバ関数
public:
private:
public:
public:
protected:
protected:
// リンクメンバ変数
protected:
protected:
// シーケンスライン作成用パラメータ
protected:
protected:
protected:
// セクション間の関係
};

// セクション追加処理に関する定数
enum {
} ; /* AddNewNode */

// セクション移動(コピー)処理に関する定数
enum {
} ; /* MoveSubTree */

// セクションのスライドに関する定数
enum {
} ; /* SlideNode */

inline
LPCSTR StrEmpty(void) {
inline
LPCSTR StrBranch(void) {
inline
LPCSTR StrNULL(void) {
inline
LPCSTR StrUntitled(void) {
// ツリービュー管理用メソッド
inline
void CMyNode::SetTreeItem(HTREEITEM hTreeItem, CTreeCtrl *pTree) {
}

inline
void CMyNode::ClearLink(void) {
inline
void CMyNode::AddBrother(CMyNode *new_node) {
inline
void CMyNode::AddChild(CMyNode *new_node) {
inline
void CMyNode::SetImageList(CTreeCtrl *pTree) {
#endif // !defined(AFX_MYNODE_H)

検索処理の見直し(2000/01/01)




検索処理の見直しを行なうこととする。

はじめに、STEDの検索動作を調査する。

+---STED検索動作
| 現在のキャレット位置からテキストの最後まで検索する
|+---検索に成功した場合
|| エディットペインに検索部分を選択状態にし表示する
|+---検索に失敗した場合
||テキストの最初からキャレット位置まで検索する
||+---検索に成功した場合
||| サウンドAを鳴らす
||| エディットペインに検索部分を選択状態にし表示する
||+---検索に失敗した場合
||| サウンドBを鳴らす
||+---
|+---
+---

このようにSTEDでは、テキストの最後までいったら、再び先頭に戻り
繰り返し検索動作を行なっている。
一方、メモ帳の検索動作を調べてみると

+---メモ帳の検索動作
| 現在のキャレット位置からテキストの最後まで検索する
|+---検索に成功した場合
|| エディットペインに検索部分を選択状態にし表示する
|+---検索に失敗した場合
|| サウンドAを鳴らす
|| 見つかりませんダイアログを表示する
|+---
+---

ここで大決心。Simple is BEST!!!
Stviの検索動作は、次のようにしよう。

+---Stviの検索動作
| 現在のキャレット位置からテキストの最後まで検索する
|+---検索に成功した場合
|| エディットペインに検索部分を選択状態にし表示する
|+---検索に失敗した場合
|| サウンドAを鳴らす
|+---
+---

さて、ここで簡単に利用できるCRichEditViewの便利な関数として
以下のものがある。
CRichEditView::FindText
CRichEditView::OnFindNext
CRichEditView::OnTextNotFound
これらを組み合わせて、セクション間にまたがる
・指定した文字列の検索
・上方向に検索
・下方向に検索
を実現する。

ここで既知の以下の問題点がある。

(1) CRichEditView::OnFindNext には、検索方向を指示するパラメータが
あるが、これがうまく動作せず常に下方向に検索される。
(2)FindText,OnFindNextともに、検索は現在のキャレット位置から最後まで
行い、ここで見つからない場合、すぐにはOnTextNotFoundが呼びだされず、
次に先頭から現在のキャレット位置までの検索が行われる。このように現在、
表示中のテキスト全てを検索した後でないと、OnTextNotFoundが呼びだされない。

そこで仕方なく、次のCRichEditCtrlのメンバ関数も使用することとする。

CRichEditCtrl::FindText
また、本当は基本クラスで保持しているはずの情報だけど、
「上方向に検索」を実現するために、
は、ローカルクラスCMyEditViewでも保持していた方がよさそうである。
この情報のことを以下の説明では「単語検索情報」と呼ぶこととする。

さて、ここから以降がいよいよ本当の設計。

本プログラムでは、次の3つの仮想関数をオーバーライドする。
void FindText(LPCTSTR lpszFind, BOOL bCase, BOOL bWord) ;
void OnFindNext(LPCTSTR lpszFind, BOOL bNext, BOOL bCase, BOOL bWord) ;
void OnTextNotFound(LPCTSTR lpszFind) ;
これにより、
・指定した文字列の検索
・上方向に検索
を実現する。また、
・下方向に検索
の処理がよばれた時のハンドラとして、
afx_msg void OnSectionFindprev();
を用意する。

まず、はじめにOnTextNotFoundの処理を示す。

+--- CMyEditView::OnTextNotFound
| (NotFoundフラグ)をセットする。
+---

次に、OnFindNextの処理内容を示す。

+--- CMyEditView::OnFindNext
| 「単語検索情報」を保存する
| 現在のセクションIDおよびキャレット位置を保存
| (NotFoundフラグ)をクリア
|
| CRichEditView::OnFindNext を Call
|
|+---(NotFoundフラグ)がクリアのままの時
|| キャレット位置を調べる
||+---キャレット位置が下方向に移動している時(検索成功)
||| return
||+---
|+---
|
| /* 現在表示中のセクションで検索に失敗した時の処理 */
|+=== 順方向セクション間検索処理
||次のセクションを求める
||+---次のセクションが存在する時
||| 新しいテキストをCRichEditCtrlに登録
||| NotFoundフラグをクリア
||| CRichEditView::OnFindNext を Call
|||
|||+---NotFoundフラグがクリアのままの時(検索成功)
||||マークアップ処理
||||return
|||+---NotFoundフラグがセットされている時(検索失敗)
||||continue
|||+---
||+---次のセクションが存在しない時(検索失敗)
||| 保存していたセクションのテキストおよびキャレット位置を復帰
||| マークアップ処理
||| CRichEditView::OnTextNotFound を Call
||| break
||+---
|+---
+---

ここで「マークアップ処理」とは次の内容を指す。

+---「マークアップ処理」
| 現在の選択範囲(キャレット位置)を保存する。
| ツリービューの選択アイテムを変更
| タイトル行のフォントを変更
| ブックマーク行のフォントを変更
| 選択範囲(キャレット位置)を復帰する。
+---

FindTextの処理内容は、OnFindNextの処理の中で

CRichEditView::OnFindNext を Callするところを
CRichEditView::FindText を Callするように変更すれば

OnFindNextの処理と同様である。
さて、最後にOnSectionFindprevの処理を示す

+--- CMyEditView::OnSectionFindprev
| 現在のセクションIDおよびキャレット位置を保存
|
| 「単語検索情報」を取得する
| 検索範囲を0からキャレット位置までに設定する
| CRichEditCtrl::OnFindNext を Call
|
|+---検索に成功した時
|| 検索範囲の中で最も後方の単語の範囲を求める
|| 単語の範囲を選択状態にする
|| return
|+---
|
| /* 現在表示中のセクションで検索に失敗した時の処理 */
|+=== 逆方向セクション間検索処理
||前のセクションを求める
||+---前のセクションが存在する時
||| 新しいテキストをCRichEditCtrlに登録
||| 検索範囲を0からテキストの最後の位置までに設定する
||| CRichEditCtrl::FindText を Call
|||
|||+---検索に成功した時
|||| 検索範囲の中で最も後方の単語の範囲を求める
|||| 単語の範囲を選択状態にする
|||| マークアップ処理
|||| return
|||+---検索に失敗した時
||||continue
|||+---
||+---前のセクションが存在しない時(検索失敗)
||| 保存していたセクションのテキストおよびキャレット位置を復帰
||| マークアップ処理
||| CRichEditView::OnTextNotFound を Call
||| break
||+---
|+---
+---

さて、ここで「検索範囲の中で最も後方の単語の範囲を求める」処理は
次の内容となる。

+---「検索範囲の中で最も後方の単語の範囲を求める」
|+===
||現在の「単語の範囲」を保存する。
||検索範囲の開始を「単語の範囲」の最後に変更する
||CRichEditCtrl::OnFindNext を Call
||+---検索に成功した場合
||| contine
||+---検索に失敗した場合
||| break
||+---
|+---
|
|「単語の範囲」を復帰する。
+---

以上。

PS:こんだけまとめるのに、ほぼ朝から晩までかかってしまった。
「stviでは検索処理はサポートしない」と決断した方が良かったかしら?


追加(2000/01/03)

[編集]メニューの[次を検索]を選べば、中で
FindTextが呼ばれていると思ったのだが
あてが外れてしまった。
[編集]メニューの[次を検索]について別にハンドラを用意する必要があった。

あと、ステータスバーの更新処理が抜けていた。

修正(2000/01/03)

どうも、
CRichEditView::OnFindNext
CRichEditView::FindText
の検索動作がおかしい?
・検索すべき単語をみつけられない。
・無指定で見つからない単語が
を設定すると見つかる場合がある。

とりあえず、
CRichEditCtrl::FindText
は、期待通りの動きをするので、これで置き換えることとする。

結局
void FindText(LPCTSTR lpszFind, BOOL bCase, BOOL bWord) ;
void OnTextNotFound(LPCTSTR lpszFind) ;
のオーバーライトは止めにする。



INIファイルの使用(1999/12/17)




本当はレジストリを使う形式のまま、行きたかったのですが、
「インストールとアンインストール」のREADMEを作成しようとすると、
(かつアンインストーラは作成しないとすると)、説明が非常に
煩雑になるので、INIファイルを使用するようプログラムを
変更します。

単語を選択(Ctrl+W)(1999/11/23)




この機能に関しては、ダブルクリック動作と同じなので
デバッガでダブルクリック時動作を確認し、
そのコードに(Ctrl+W)を割り当てることとする。

単語を選択(その2)(1999/11/25)

デバッガで追ってみても、よう解らないので、
Ctrl+Wが押されたら、ダブルクリックメッセージが
ポストされるようにコーディングを行なう。
CRichEditViewにポストしてもだめで、CRichEditCtrl
にポストすると単語選択の動作を行なう。

これでいいこととしよう。

カーソル位置のインジケータ表示(1999/11/21)




インジケータにエディットペイン内のカーソル位置を表示する機能が
現在の構造化エディタにはある。

エディットペイン内のカーソル位置は、
行列ともに0からはじまる。

これと似た機能として折り返しが無効の時使用できる
[カーソル位置の取得]機能がある。ヘルプには、
「Ctrl + P で、ファイル全体の中でのカーソルの位置が
分かります。」と記述されている。

これは、行列ともに1からはじまる。

本ツールにおいては、
「エディットペイン内のカーソル位置」
「ファイル全体の中でのカーソルの行番号」
をステータスバーに表示することとする。

現在のクラス構成(1999/11/06)




さて、削除したセクションを、次のテキストファイルを読み込む度に
破棄して良いのなら、話は簡単なのだが、破棄しないとなると
若干のデータ構造の修正が必要になってくる。

修正する前に、現状をまとめて、頭をすっきりさせるのが
この文書化の目的です。


CStviApp アプリケーションクラス
CStviDoc ドキュメントクラス
CStviCntrItem OLEクライアントアイテムに関連したクラス
ウインドウ
+---CMainFrame メインウインドウ
| CMyEditView エディットビュー
| CStviView ツリービュー
+------------

ダイアログおよび(呼び出しクラス)
CAboutDlg (CStviApp) バージョン情報
CBMList (CStviDoc) ブックマーク一覧/タイトル一覧
CHVSelect (CMainFrame) 分割方向
CMyFontSelect (CMainFrame) フォント
CSaveOpt (CStviApp) オプション

CMyBMMng ブックマーク管理オブジェクト
CMyBookMark ブックマークオブジェクト
CMyFileMng ファイル管理オブジェクト
CMyLine 行オブジェクト
CMyLineAry 行管理オブジェクト
CMyNode セクションオブジェクト
CMyNodeMng セクション管理オブジェクト

VC++のClass Viewに表示されているクラスは以上の通り。

各管理オブジェクトは全てドキュメントに組込まれている。

+---CStviDoc
| CMyBMMng ブックマーク管理オブジェクト
| CMyFileMng ファイル管理オブジェクト
| CMyLineAry 行管理オブジェクト
| CMyNodeMng セクション管理オブジェクト
+------------

現在のデータ構造の問題点(1999/11/06)

CMyNodeオブジェクトがクラス変数(メンバ変数ではない)で、
CMyLineAryへのポインタを保持し、これにアクセスしている。

このため、下記クラスを

もう一組用意しても正常動作しない。


実現手段(1999/11/07)

削除されたノードのもつデータは、
CFileMngとは別のオブジェクトで保持する。

CLineAry, CNodeMngは、新規および削除されたもの、両方の
オブジェクトを保持する。

ブックマーク操作は、メインウインドウのツリービューに表示されて
いるセクションについてのみ行う。

したがって、「ファイルを開く」のメニューが選択された後は、

(1)削除ノードのデータをsted formatでメモリーファイルに書き込む
(2)選択されたファイルを従来の方法で読み込む。
(3)メモリーファイルの内容を、各オブジェクトに設定する

セクション・レベル編集(1999/10/30)




セクション単位の追加、削除機能を実現する。
ツリービューをもうひとつ用意し、削除したセクションを一時保管する。
追加セクションは、空もしくはタイトル行のみとする。

セクション分割機能を実現する。
カーソル行より上と、カーソル行以下を別セクションに分割する。
テキストファイルの"sted format"化のための支援機能である。
(セクションマージおよび分割のundoは、取敢えずパス。)

次の文章を開く、直前に、削除したセクション群は保存することとする。
こうすることにより、実質的にある文書から別の文書への
Drug&Dropによるセクション(ツリー)コピーを実現する。
([全セクション削除]、[第2ビューを空にする]メニューを追加する。
第2ビューの内容はアプリケーション終了時に破棄するものとする。)

セクション分割(1999/10/31)

セクション分割の実現のためには

・セクションオブジェクトの供給機構
・テキストバッファへNULL文字の代入。最後の改行記号に上書き。
・元のセクションオブジェクトを前半のセクションとし、
追加セクションオブジェクトを後半のセクションとする。
・フォーカスは後半のセクションに設定
・1行目および最終行では分割できないようにする。
(空セクション追加という手段も代替案としてある)
・タイトル行の調整
・ブックマークの調整

削除セクション(1999/11/05)

セクションの削除が可能になった
そうなると、当然、削除セクションの復活機能が欲しくなる。

本当は、第2ツリービューを用意して、対応したいのだが、
私に手のあまる状況なので、とりあえずは、
ダイアログボックスに削除セクションの一覧を表示し、
ここで選択したものを、復活するということで
お茶を濁したい。


α版 残作業(H11.10.24)




現在の0.8x台をα版と位置付け、
次の作業をクリアした段階で0.90βとする。

・ツリーペインのコピー処理
・上向き検索

そして、β版に対して次の作業をクリアした段階で1.00とする。

・ショートカットキーに関して、STEDとの整合性をとる。
・(印刷関係のメニュー、ボタンの削除)
・不正な"sted format"ファイルをテキストファイルとして読み込む

改ページコード(H11.10.17)




シェアウェア「秀丸」の印刷機能においては、[ctrl]+[L]が、
改ページコードとして認識されることが判明した。

すなわち、「セクション毎に改ページして印刷してくれる」状態に
"sted format"の文書を編集できることが判明した。

「印刷ツール」を実現するにしても、

・指定レベルのセクション間で改ページ動作を行う。

といった、セマンティックな処理をツールに実行させるよりは、
(セマンティックってこんな使い方で良かったっけ?)

・改ページコードがあれば改ページ動作を行う。

とした方が簡単で使いやすいと考えた。

このための修正は、

・セクションオブジェクトに改ページコードフラグを用意する。
・セクション先頭の改ページコードは、エディットビューに表示しない。

といったところかな?

ブックマーク(3)




複数のブックマーク間を移動する場合、
メニューで「次のブックマークへ」を選ぶのは
かなりかったるい。
これは、ツールバーで実現した方がよさそうだ。

また、ブックマーク行のフォントを変えて表示するというのも
面白そうだ。

ブックマーク(2)



キャレットの移動

キャレットの移動に次のメンバ関数を使用しようとしたが
CWnd::SetCaretPos
なぜか思ったような動作をしない。

仕方がないので、ブックマーク行の一字目をセレクトした後、
選択を解除することでカーソルを移動を行うことにした。

メニュー

ブックマークの移動に関するメニューを
[表示][ブックマーク]の下に列挙する予定だったが、
各メニュー項目の(有効/無効)の判断処理が
[ブックマーク]サブメニューを選択した時ではなく、
[表示]メニューを選んだ段階で呼ばれることがわかったので、
ブックマーク関連に関しては、別にトップメニューとして
作ることにする。

現在カーソルの有る行が、ブックマーク行か否かの判断

現在カーソルの有る行が、ブックマーク行か否かの判断は、
現在のカーソル位置が、ブックマーク行リストのデータと
一致するかどうか順次比較することにより行うものとする。

カーソル行から行オブジェクトを求め、行オブジェクトに
問い合わせるという方法も考えられるが、
行オブジェクト特定するまでの比較回数と、
ブックマークの数では、ブックマーク数の方が少ないだろう
という判断である。

ブックマーク




まず、キャレットを文章中の任意の場所に移動する方法を
みつけなければ。
CRichEditCtrlのメンバ関数にあるのでは無いかと期待したのだが、
ようわからん。試行錯誤するしかないか。

MFCにおけるキャレットの取扱いを勉強せねば。


関連するメンバ関数

取敢えず関係しそうなメンバ関数の動作を調べる。(10/3)
手始めは、次の4つ

GetLineCount
GetFirstVisibleLine
LineIndex
LineLength


GetLineCount

この関数はオブジェクトの行数を返す。
ここでいう行数とは、現在オブジェクトが保持している
テキストを現在のウインドウ幅で表示するのに必要な行数である。

GetFirstVisibleLine

0行目から数えはじめ
現在ウインドウの先頭に表示されている
最初の行が全体で何行目かを返す。

LineIndex

指定した行の文字インデックスを返す。
文字インデックスは、先頭から指定された行までの
文字列のバイト数。

LineLength

指定された行のテキストの長さをバイト単位で返す。

アルゴリズム

・ブックマーク行の行オブジェクトおよび
セクションオブジェクトを求める。
・そのセクションのテキストをRichEditCtrlに登録する。
・文字Indexを得る。
・文字Indexから行番号を得る。
・その行が先頭になるようにスクロールする。
・キャレットをそこに移動する

結局、最終的に使用するメンバ関数は

CRichEditCtrl::LineIndex
CRichEditCtrl::LineFromChar
CRichEditCtrl::LineScroll
CRichEditCtrl::GetCharPos
CWnd::SetCaretPos

になるのかな?


ブックマークオブジェクトおよびブックマーク管理オブジェクト

ブックマークオブジェクトは次のメンバ変数を持つ
・セクションオブジェクト
・行オブジェクト


ブックマーク管理オブジェクトはCPtrArrayを用いて実現する。
これは、ブックマーク行の数に制限を付けたくないが、
行数分だけ用意するのは非常に無駄な気がするからである。

ブックマーク管理オブジェクトは
ブックマークオブジェクトの生成、消滅を行い、
必要に応じソートし、次の条件に対応した
ブックマークオブジェクトを返すメンバ関数を持つ。

・最初のブックマークオブジェクト
・最後のブックマークオブジェクト
・指定行の次のブックマークオブジェクト
・指定行の前のブックマークオブジェクト



ブックマーク行のソート

ブックマーク行が複数ある場合のソートに関し、
ブックマーク行の大小判断は次のように考える。

セクションが違う場合、早いセクションのブックマーク行が小さい
セクションが同じ場合、行番号の小さいブックマーク行が小さい

セクション間のノード判断のために次のメンバ変数を
セクションオブジェクトに追加する。

int iNodeOrder


RichEditViewへの変更




現在のCEditViewをCRichEditViewに変更した方が良いのでは
無いかと思案中。

理由1:CEditViewだと、64Kの壁に制約を受ける。

理由2:HTMLで保存等の機能を実現する際に、WYSIWYGを
実現しやすい。

で、変更するとすれば、なるべく早い方が(機能の少ない
内にやった方が)良いのかなと思っています。

RichEditViewの使用に関して(1)

とりあえず、CRichEditViewを使用したアプリケーションを
AppWizardで作成してみる。
どうやら、CRichEditViewを使用するためには、
OLEコンテナサポートが必要なようだ。
OKを選択すると、コンテナアイテムのクラスが
作成されていた。

今までは、CTreeViewで、アプリの骨格を作り、これに、
CEditViewを追加してきたが、今回は、CTreeViewを後から追加した
方がよさそうである。

あと、CEditViewをCRichEditViewに変更するかどうかの
判断材料は、私的には、
・実行速度
・プレーンテキストの扱いやすさ
・タブの指定
となりますが、CRichEditViewを使用して作られているといわれる
ワードパッドアプリケーションにテキストファイルを読み込ませて
みても、問題はなさそうですし、タブの設定に関するメニューも
あるので、思いきって移行してみます。



ビューへのアクセス




現在(9/4)、アプリケーションオブジェクトにツリーコントロールと
エディトボックスを管理させているが、メインウインドウにツリービューと
エディットビューを管理させた方がよいのではないかと思っている。
また、実際にツリーコントロールおよびエディトボックスが
ウインドウとして存在する段階で登録しようと考え、OnInitialUpdateを
登録のタイミングとして選んだが、それぞれのコンストラクタで登録して
問題無いのではないだろうか?
また、void CStviApp::UpdateViewCtrl(CMyNode *obj)に関しても、
メインウインドウ(CMainFrame クラス)に移した方が美しい気がする。

ビューへのアクセス U(2000/01/12)

ドキュメントオブジェクトがビューを変更したい場合には
UpdateAllViewsメソッドを使用するのが、
ドキュメント/ビューモデルらしいコーディングの仕方である。

ビューが、他のビューを変更したい場合は、
GetDocument()->UpdateAllViews(this, lHint, pHint) ;
とするのが、正当な更新の仕方のようだ。

どうしても、特定のビューにアクセスしたい場合には、
CMainFrame(アプリケーション固有のフレームウインドウ)に
特定のビューを返すメソッド(ex. GetSpecialView())を用意し、
CView *pvWant = ((CMainFrame *)AfxGetMainWnd())->GetSpecialView() ;
とすれば、アクセスできる。

たとえば、本アプリケーションでツリービューを返すメソッドは、
次のようになる。

但し、
CStviView :ツリービューのクラス名
m_wndSplitter :次のように定義されたCmainFrameのメンバ変数
CSplitterWnd m_wndSplitter ;

セクションオブジェクト




現在(9/4)、セクションオブジェクト(CMyNode オブジェクト)の中には、
初期化処理にしか使用しない属性およびメソッドが含まれている。
これらを完全に分離した方が、プログラムは見やすくなるのでは
ないだろうか?

リンクメンバ(2000/01/12)

これらのメンバ変数、メンバ関数は、
リンクメンバ変数
リンクメンバ関数
として、セクションツリーの移動やコピーの際に、
大活躍することになりました。