VC.netでRedo機能の追加

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら

VC.netでRedo機能の追加

#1

投稿記事 by » 16年前

http://www.play21.jp/board/formz.cgi?ac ... &rln=38165
上記に以前、投稿し助けていただきました。
その節はありがとうございまいした。

今回はその続きの質問となります。

if (this->m_redoStack.Count > 0)と書いているのに起動してからすぐに
Ctrl+ZをやるとUndoのスタックがEmptyとエラーが出てしまいます。
またUndo、Redo時にカーソルの位置が0番目になってしまうのですが前回の位置にと直せないでしょうか?

// カーソル操作
this->TextBox1.Focus()
this->TextBox1.Select(番目, 0)

でできるとわかったのですがどこに移動させればいいのかということと
このソースで作ったテキストボックスだと強制的に0番目にカーソルがあってしまいます。

どのようにすれば直せるでしょうか。
お忙しいかとは思いますがアドバイスを頂けますと助かります。
#pragma once

public ref class UndoTextBox : public System::Windows::Forms::TextBox
{
private:
	System::Collections::Generic::Stack<System::String^> m_redoStack;
	System::Collections::Generic::Stack<System::String^> m_undoStack;
	bool m_change;

public:
	UndoTextBox(void)
	{
		InitializeComponent();

		// メンバの初期化
		init();
	}

	UndoTextBox(System::ComponentModel::IContainer ^container)
	{
		container->Add(this);
		InitializeComponent();

		// メンバの初期化
		init();
	}

protected:
	~UndoTextBox()
	{
		if (components)
		{
			delete components;
		}
	}

	virtual void OnTextChanged(System::EventArgs^ e) override
	{
		// フラグがtrueの場合は…
		if (this->m_change == true)
		{
			// UNDOかREDOによってTEXTが変化したので、フラグをfalseにするだけで何もしない
			this->m_change = false;
		}
		else
		{
			// REDO用Stackの中身を削除する
			this->m_redoStack.Clear();

			// UNDO用Stackへ最新のTEXTを挿入する
			this->m_undoStack.Push(this->Text);
		}
		TextBox::OnTextChanged(e);
	}

	virtual void OnKeyDown(System::Windows::Forms::KeyEventArgs^ e) override
	{
		if (e->Control == true)
		{
			switch (e->KeyCode)
			{
			case System::Windows::Forms::Keys::A:
				// 全選択
				SelectAll();
				return;
			case System::Windows::Forms::Keys::Z:
				// UNDO処理				
				undo();
				return;		
			case System::Windows::Forms::Keys::Y:
				// REDO処理
				redo();
				return;
			}
		}
		TextBox::OnKeyDown(e);
	}

private:
	/// <sammary>
	/// メンバの初期化
	/// <sammary>
	void init(void)
	{
		// Stackの中身を削除する
		this->m_redoStack.Clear();
		this->m_undoStack.Clear();

		// UNDO用Stackの最初の要素として、空文字を挿入しておく
		this->m_undoStack.Push(System::String::Empty);

		// フラグをfalseで初期化
		this->m_change = false;
	}

	/// <sammary>
	/// UNDO
	/// <sammary>
	void undo(void)
	{
		// UNDO用のStackが空っぽでなければ…
		if (this->m_undoStack.Count > 0)
		{
			// Stackの先頭要素がTextBox内のTEXTと同じの場合は…
			if (this->m_undoStack.Peek() == this->Text)
			{
				// 一度だけ空読みして先頭要素を削除する
				this->m_undoStack.Pop();
			}

			// UNDOによってTEXTが変化するのでフラグをTRUEにする
			this->m_change = true;

			// TextBox内のTEXTをREDO用Stackの先頭へ挿入する
			this->m_redoStack.Push(this->Text);

			// UNDO用のStackの先頭要素をTextBox内のTEXTへコピーする
			this->Text = this->m_undoStack.Pop();
		}
	}

	/// <sammary>
	/// REDO
	/// <sammary>
	void redo(void)
	{
		// REDO用のStackが空っぽでなければ…
		if (this->m_redoStack.Count > 0)
		{
			// REDOによってTEXTが変化するのでフラグをTRUEにする
			this->m_change = true;

			// TextBox内のTEXTをUNDO用Stackの先頭へ挿入する
			this->m_undoStack.Push(this->Text);

			// REDO用のStackの先頭要素をTextBox内のTEXTへコピーする
			this->Text = this->m_redoStack.Pop();
		}
	}

private:
	System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code
	void InitializeComponent(void)
	{
		components = gcnew System::ComponentModel::Container();
	}
#pragma endregion
};

Justy

Re:VC.netでRedo機能の追加

#2

投稿記事 by Justy » 16年前


>と書いているのに起動してからすぐに
>Ctrl+ZをやるとUndoのスタックがEmptyとエラーが出てしまいます

 ソースを目で追った限りだと、出そうですね、そのエラー。
 
 init関数で m_undoStackに空の文字列が1つ追加されます。
 続いて undoが実行されると最初のチェックは 0以上なので空ではないと
判定されます。

 その次の「先頭要素が TextBoxと同じかどうか」チェックで、
まだ TextBoxには文字を入力していないのであれば this->Textは空なので
m_undoStackの唯一の中身と一致、「空読みして先頭要素が削除」されます。

 この段階で m_undoStackの要素数は0なのですが、undo関数のラストで、というわけです。
 なので、空読みした後要素が0なら何らかの対処をして Popしないようにして下さい。


# init関数で余計な空文字列を追加しないで処理する方式にすれば空読みしないで済んで、
すっきりしそうです。



>でできるとわかったのですがどこに移動させればいいのかということと
>このソースで作ったテキストボックスだと強制的に0番目にカーソルがあってしまいます。

 TextBoxには SelectionStartと SelectionLengthというプロパティがあります。

TextBox.SelectionStart プロパティ (System.Windows.Controls)
ttp://msdn.microsoft.com/ja-jp/library/system.windows.controls.textbox.selectionstart.aspx

TextBox.SelectionLength プロパティ (System.Windows.Controls)
ttp://msdn.microsoft.com/ja-jp/library/system.windows.controls.textbox.selectionlength(VS.95).aspx

 今は Textプロパティだけを Push/Popしているようですが、これらのメンバも Push/Popすれば
カーソルの位置も一緒に Undo/Redoされるはずです。

#ひょっとしたら SelectionStartだけでもいいのかもしれません。

バグ

Re:VC.netでRedo機能の追加

#3

投稿記事 by バグ » 16年前

うわっ、そんなバグがあったとは…
また、時間のある時に修正しときますわ(^_^;)

Re:VC.netでRedo機能の追加

#4

投稿記事 by » 16年前

皆様ご回答ありがとうございます。

>>Justyさん
カーソルを移動させる関数の使い方はわかりました。
ただどこにどのようにかけばよいのかわかりません。

>>バグs
その節はお世話になりました。
お手数掛けて申し訳ありませんが
私では修正できそうにないのでアドバイスを頂けますと助かります。

お手数おかけしますがご教授いただけますと助かります。

Justy

Re:VC.netでRedo機能の追加

#5

投稿記事 by Justy » 16年前


>ただどこにどのようにかけばよいのかわかりません

 Textの Push/Popと同じ場所で Textと同じように Push/Popして下さい。
 
 今は m_redoStackや m_undoStackは string型の Stackとしていますが、
string(Text)と int2つ(SelectionStartと SelectionLength)を持つクラスを
1つ作り、そのクラスに変えます。

 あとは Push/Popしちるところをそれに合わせて変更すればいいかと。

Re:VC.netでRedo機能の追加

#6

投稿記事 by » 16年前

>>Justy様

カーソルの移動について修正できました。
ありがとうございます。

またバグ?を一点みつけたのですが
Enterキーを連打した後にUNDOを繰り返すと消えない文字があります。
これは仕様なのでしょうか?

またControl+Zでをやりまくると稀にスタックがEmptyとエラーが出ることがあります。

#pragma once

public ref class UndoTextBox : public System::Windows::Forms::TextBox
{
private:
	System::Collections::Generic::Stack<System::String^> m_redoStack;
	System::Collections::Generic::Stack<System::String^> m_undoStack;
	
	System::Collections::Generic::Stack<int> c_redoStack;
	System::Collections::Generic::Stack<int> c_undoStack;
	
	bool m_change;

public:
	UndoTextBox(void)
	{
		InitializeComponent();

		// メンバの初期化
		init();
	}

	UndoTextBox(System::ComponentModel::IContainer ^container)
	{
		container->Add(this);
		InitializeComponent();

		// メンバの初期化
		init();
	}

protected:
	~UndoTextBox()
	{
		if (components)
		{
			delete components;
		}
	}

	virtual void OnTextChanged(System::EventArgs^ e) override
	{
		// フラグがtrueの場合は…
		if (this->m_change == true)
		{
			// UNDOかREDOによってTEXTが変化したので、フラグをfalseにするだけで何もしない
			this->m_change = false;
		}
		else
		{
			// REDO用Stackの中身を削除する
			this->m_redoStack.Clear();
			this->c_redoStack.Clear();

			// UNDO用Stackへ最新のTEXTを挿入する
			this->m_undoStack.Push(this->Text);
			this->c_undoStack.Push(this->SelectionStart);
		}
		TextBox::OnTextChanged(e);
	}

	virtual void OnKeyDown(System::Windows::Forms::KeyEventArgs^ e) override
	{
		if (e->Control == true)
		{
			switch (e->KeyCode)
			{
			case System::Windows::Forms::Keys::A:
				// 全選択
				SelectAll();
				return;
			case System::Windows::Forms::Keys::Z:
				// UNDO処理				
				undo();
				return;		
			case System::Windows::Forms::Keys::Y:
				// REDO処理
				redo();
				return;
			}
		}
		TextBox::OnKeyDown(e);
	}

private:
	/// <sammary>
	/// メンバの初期化
	/// <sammary>
	void init(void)
	{
		// Stackの中身を削除する
		this->m_redoStack.Clear();
		this->m_undoStack.Clear();
		this->c_redoStack.Clear();
		this->c_undoStack.Clear();

		// UNDO用Stackの最初の要素として、空文字を挿入しておく
		this->m_undoStack.Push(System::String::Empty);
		this->c_undoStack.Push(0);

		// フラグをfalseで初期化
		this->m_change = false;
	}

	/// <sammary>
	/// UNDO
	/// <sammary>
	void undo(void)
	{
		// UNDO用のStackが空っぽでなければ…
		if (this->m_undoStack.Count > 0)
		{
			// Stackの先頭要素がTextBox内のTEXTと同じの場合は…
			if (this->m_undoStack.Peek() == this->Text)
			{
				// 一度だけ空読みして先頭要素を削除する
				this->m_undoStack.Pop();
				this->c_undoStack.Pop();
			}

			// UNDOによってTEXTが変化するのでフラグをTRUEにする
			this->m_change = true;

			// TextBox内のTEXTをREDO用Stackの先頭へ挿入する
			this->m_redoStack.Push(this->Text);
			this->c_redoStack.Push(this->SelectionStart);

			// UNDO用のStackの先頭要素をTextBox内のTEXTへコピーする
			this->Text = this->m_undoStack.Pop();
			this->SelectionStart = this->c_undoStack.Pop();
		}
	}

	/// <sammary>
	/// REDO
	/// <sammary>
	void redo(void)
	{
		// REDO用のStackが空っぽでなければ…
		if (this->m_redoStack.Count > 0)
		{
			// REDOによってTEXTが変化するのでフラグをTRUEにする
			this->m_change = true;

			// TextBox内のTEXTをUNDO用Stackの先頭へ挿入する
			this->m_undoStack.Push(this->Text);
			this->c_undoStack.Push(SelectionStart);

			// REDO用のStackの先頭要素をTextBox内のTEXTへコピーする
			this->Text = this->m_redoStack.Pop();
			this->SelectionStart = this->c_redoStack.Pop();
		}
	}

private:
	System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code
	void InitializeComponent(void)
	{
		components = gcnew System::ComponentModel::Container();
	}
#pragma endregion
};

Justy

Re:VC.netでRedo機能の追加

#7

投稿記事 by Justy » 16年前


>これは仕様なのでしょうか?
>またControl+Zでをやりまくると稀にスタックがEmptyとエラーが出ることがあります

 Enterキーを押して起こるかどうかはソースから読み取れないのですが、
Backspaceとかならありえそうです。

 本来、1回の undoなら m_undoStackを1回 popすればいいはずなのですが、
1回だったり2回だったりと不自然な挙動をしているのが問題な気がします。


 改善方法としては(今のコードのバックアップをとった上でお試し下さい)

・ initで m_undoStackへの 無駄な空文字列の追加を止め、
代わりにメンバに文字列 m_oldTextを追加して、m_oldTextに対して
空文字列を入れておく(c_undoStackも同様)。

・ OnTextChangedで、m_changeがどうであろうと、this->Textの文字列を
m_oldTextに保存しておく。

・ undoで空読みを止める

・ あちらこちらで行われている全ての Push(undoだろうが redoだろうが)処理で
this->Textの代わりに m_oldTextを使う。
 とりあえず、(試してはいませんが)これでうごくかな、と思います。
 ……たぶん。


#あと、redoStackや undoStackがそれぞれ2つあるのは非効率的です。
 余力があれば1つに纏めておきましょう。

閉鎖

“C言語何でも質問掲示板” へ戻る