TabControlのタブヘッダを（半ば強引な方法で）直接編集してみよう


# 目的

タブコントロールのタブヘッダのテキストを直接編集したくなりました。
そう、Excelのシート名変更のような操作感を実現したい訳ですね。

![image](https://qiita-image-store.s3.amazonaws.com/0/136132/b1828301-eaea-5e40-c0ff-403fcc125df6.png)

これです、この「tabPage1」とか云うのを、まさにこの場所で編集したいんです。

あーそうそう、以前やった[`ListView`のカラム直接変更](https://qiita.com/Toraja/items/51dd3ec878c647583231)にちょっと似ているんですが、実現の仕方にはちょっと工夫が必要でした。

# 直接編集の仕組み

`ListView`のカラムには直接編集の機能がありません。
なので、編集したいカラムの上に`TextBox`をぴったり重ねて表示して、操作者を謀ろうと云うのがざっくりした仕組みの説明です。

あれ？
簡単に終わってしまった…

# じゃ、タブの編集も同じ方針で

タブの直接編集機能も無い様なので、タブの上に`TextBox`重ねて表示すれば良いよね。
一番問題になりそうなのは、編集したいタブの位置をどうやって取得するのかなぁー
ま、ちょっと調べればちょちょい、で出来るんじゃないの？

と思いました、最初は。
ま、方向性は間違ってないんですけどね。
<br/>
結果、ダメでした…
いや、単純な水平展開、って訳には行きませんでした、ってのが正解かな。

# 問題はどこに？

一応、おさらいしておくと、コンテナである`TabControl`の配下に複数の`TabPage`が入ります。

其々の`TabPage`がタブ(ヘッダ)を持っている様なイメージがありますが（私だけ？）、実際の所`TabPage`はただの四角です。複数の`TabPage`の切換えのセレクタであるタブを持っているのはコンテナの方`(TabControl)`だったんですね。

なのでタブの上に編集用コントロールを乗っけるには、対象となる`TextBox`を`TabControl`の子コントロールとして追加する必要があります。
んが、コンテナである`TabControl`は狭量で、自分の子供は`TabPage`だけ、と決めつけている中々の頑固者です。

これでは、タブの上での直接編集が実現できません…
何か策はないのー

# 一筋の光明

`TabControl`家への養子入りはかなり無理そう…

`Parent`にあたる`Form`の配下では`TabControl`の前面に出る事は出来ませんし、`Child`になる`TabPage`の下では、その範囲は親の`TabControl`の領域までは手が届きません。

塞がったなー…

となれば、もう親類縁者に頼る事は諦めて、赤の他人として`TabControl`の前に立ちはだかる方法を探っ…
<br/>
あぁぁ、そうか。

もういっその事新しい`Form`を作っちゃうってのでどうでしょう？
中身は`TextBox`一個、`FormBorderStyle.None`にしてサイズはタブの耳と同じ大きさにする…
みたいな感じで。

実際の所、どんな見た目になるのかや、動きにぎこちなさが残らないのか―
微妙に心配のタネはあるものの、思いついたが百年目、じゃねーや
思い立ったが吉日と云う事で、取り敢えず手を動かしてみましょう。

# こんな感じになりました

いきなり、サブクラス化してみた結果です。

コンポーネントを追加して拡張したので、`Designer.cs`の`this.AutoScaleMode = ～～`をコメントアウトする必要があります。

```csharp:TabControlEx.Designer.cs
namespace TestTabControl {
	partial class TabControlEx {
		/// <summary> 
		/// 必要なデザイナー変数です。
		/// </summary>
		private System.ComponentModel.IContainer components = null;

		/// <summary> 
		/// 使用中のリソースをすべてクリーンアップします。
		/// </summary>
		/// <param name="disposing">マネージ リソースを破棄する場合は true を指定し、その他の場合は false を指定します。</param>
		protected override void Dispose(bool disposing) {
			if(disposing && (components != null)) {
				components.Dispose();
			}
			base.Dispose(disposing);
		}

		#region コンポーネント デザイナーで生成されたコード

		/// <summary> 
		/// デザイナー サポートに必要なメソッドです。このメソッドの内容を 
		/// コード エディターで変更しないでください。
		/// </summary>
		private void InitializeComponent() {
			components = new System.ComponentModel.Container();
		//  this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
		}

		#endregion
	}
}
```

肝心のコードの方です。
ごめん…
コメントも何も一切無かったけど、長いコードじゃないので雰囲気で掴んでください。

```csharp:TabCntrolEx.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TestTabControl {
	public partial class TabControlEx : TabControl {
		public TabControlEx() {
			InitializeComponent();
		}

		public void EditTabText() {
			placeHolder = new Form();
			placeHolder.SuspendLayout();

			textbox = new TextBox();
			textbox.BackColor = SystemColors.InactiveCaption;
			textbox.BorderStyle = BorderStyle.None;
			textbox.TextAlign = HorizontalAlignment.Center;
			textbox.KeyPress += Textbox_KeyPress;

			placeHolder.AutoScaleMode = AutoScaleMode.Font;
			placeHolder.AutoSizeMode = AutoSizeMode.GrowAndShrink;
			placeHolder.Controls.Add(textbox);
			placeHolder.FormBorderStyle = FormBorderStyle.None;
			placeHolder.TopMost = true;
			placeHolder.Load += PlaceHolder_Load;
			placeHolder.Deactivate += PlaceHolder_Deactivate;

			placeHolder.ResumeLayout(false);
			placeHolder.PerformLayout();

			placeHolder.Show();
		}

		private void PlaceHolder_Load(object sender, EventArgs e) {
			Rectangle tabRect = this.GetTabRect(this.SelectedIndex);
			tabRect.Location = this.FindForm().PointToScreen(tabRect.Location);
			placeHolder.DesktopBounds = tabRect;
			textbox.Bounds = new Rectangle(0, (tabRect.Height - textbox.Height) / 2, tabRect.Width, tabRect.Height);
			textbox.Text = this.SelectedTab.Text;
		}

		private void PlaceHolder_Deactivate(object sender, EventArgs e) {
			if(!string.IsNullOrEmpty(textbox.Text))
				this.SelectedTab.Text = textbox.Text;
			placeHolder.Close();
		}

		private void Textbox_KeyPress(object sender, KeyPressEventArgs e) {
			switch((Keys)e.KeyChar) {
			case Keys.Escape:
				textbox.Text = string.Empty;
				goto case Keys.Enter;

			case Keys.Enter:
				e.Handled = true;
				placeHolder.Hide();
				return;
			}
		}

		Form placeHolder = null;
		TextBox textbox = null;
	}
}
```

使い方は、`Form`に`TabControlEx`を貼り付けてからの、以下のコード。
基本は以下の通り、`MouseDoubleClick`イベント捕まえて、`EditTabText()`を呼ぶだけ。
現在アクティブなタブの上に、小さな`Form`が乗っかってテキスト入力できるようになります。

```csharp
namespace TestTabControl {
	public partial class Form1 : Form {
		public Form1() {
			InitializeComponent();
		}

		private void tabControlEx1_MouseDoubleClick(object sender, MouseEventArgs e) {
			tabControlEx1.EditTabText();
		}
	}
}
```

実行結果はこんな感じ…

![image](https://qiita-image-store.s3.amazonaws.com/0/136132/a40593a8-5ad1-09e9-0de2-719c007b1473.png)

ここで、問題(?)と云うか奇妙な特性に気が付いてしまいました。
フォームを表示しているので、入力している最中のアクティブウィンドウのスクショは、そこしか映らないという…
ま、そこはしょうがないと思って、諦める事にしました。
<br/>
真面目に使うには、入力可能文字のチェックしたり、テキストの長さを制限したりしないといけないと思うけど、これを肉付けしていけば良いでしょう。
きっと。