LoginSignup
1
1

More than 5 years have passed since last update.

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

Posted at

目的

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

image

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

あーそうそう、以前やったListViewのカラム直接変更にちょっと似ているんですが、実現の仕方にはちょっと工夫が必要でした。

直接編集の仕組み

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

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

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

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

と思いました、最初は。
ま、方向性は間違ってないんですけどね。


結果、ダメでした…
いや、単純な水平展開、って訳には行きませんでした、ってのが正解かな。

問題はどこに?

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

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

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

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

一筋の光明

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

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

塞がったなー…

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


あぁぁ、そうか。

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

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

こんな感じになりました

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

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

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
    }
}

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

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;
    }
}

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

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

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

実行結果はこんな感じ…

image

ここで、問題(?)と云うか奇妙な特性に気が付いてしまいました。
フォームを表示しているので、入力している最中のアクティブウィンドウのスクショは、そこしか映らないという…
ま、そこはしょうがないと思って、諦める事にしました。


真面目に使うには、入力可能文字のチェックしたり、テキストの長さを制限したりしないといけないと思うけど、これを肉付けしていけば良いでしょう。
きっと。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1