简体   繁体   中英

TabControl renaming in run-time

I'm trying to create simple interface where user can rename tab in TabControl. Problem is that since I can get position of tab by GetTabRect method, I can't set TextBox into a place of tab. Here is the desired effect (from foobar):

在此输入图像描述

And here is my effect:

在此输入图像描述

Below I insert my code where I try to make this effect:

      private void renameToolStripMenuItem_Click(object sender, EventArgs e)
    {
        if (selectedTab >= 0 && selectedTab < MainTabs.TabCount)
        {
            Debug.WriteLine("Rename " + MainTabs.Controls[selectedTab].Text);
            Debug.WriteLine(sender.ToString());

            //var CurrentTab = MainTabs.TabPages[selectedTab];
            //TabControl tabControl = MainTabs.Con

            Rectangle rect = MainTabs.GetTabRect(selectedTab);
            Point point = MainTabs.Location;

            Debug.WriteLine(point.ToString() + " " + rect.ToString());

            RenameBox = new TextBox();

            MainTabs.GetControl(selectedTab).Controls.Add(RenameBox);

            //MainTabs

            RenameBox.Show();

            RenameBox.SetBounds(rect.X, rect.Y, rect.Width, rect.Height);


            //MainTab.Controls.Add(RenameBox);
            //this.Controls.Add(RenameBox);
            //components.Add(RenameBox);

            //RenameBox.Location = point;

            //MainTabs.SelectedTab.Controls.Add(RenameBox);

            //MainTabs.GetControl(0).Controls.Add(RenameBox);

            //MainTabs.SelectedTab.

            RenameBox.KeyPress += textBox1_KeyPress;
            //RenameBox.Show();
            //RenameBox.Location = new Point(0, 0);
            //RenameBox.Focus();
            //RenameBox.SetBounds(.GetTabRect(selectedTab));

        }
    }

How should I change the code to be able to rename Tab name in runtime?

GetTabRect() returns the position relative to the tab control. But you are adding the textbox to the tabpage, it is located below the tabstrip. Which is why your screenshot looks like that. You cannot give the textbox a negative position, it would be clipped. Ideally you would add it to the tab control, but TabControl explicitly forbids this. The only other thing you can do is add it the form.

You'll have to calculate the correct position, mapping the tabrect to form coordinates. And ensure it overlaps the tabcontrol. Removing it again is surely best done with its Leave event. Like this:

private TextBox AddTextBoxToTab(TabControl tabctl, int index = -1) {
    if (index < 0) index = tabctl.SelectedIndex;
    var rc = tabctl.GetTabRect(index);
    rc = tabctl.RectangleToScreen(rc);
    rc = tabctl.Parent.RectangleToClient(rc);
    var box = new TextBox();
    box.Font = tabctl.Font;
    box.Leave += delegate { box.Dispose(); };
    box.SetBounds(rc.Left, rc.Top, rc.Width, rc.Height);
    tabctl.Parent.Controls.Add(box);
    box.BringToFront();
    box.Focus();
    return box;
}

Personally, I would implement this as a lightweight form, positioned over the top of the tab.

在此输入图像描述

To make a lightweight form, set the following properties:

FormBorderStyle = FormBorderStyle.FixedSingle;
Text            = string.Empty;
StartPosition   = FormStartPosition.Manual;
ControlBox      = false;
ShowInTaskbar   = false;

A lightweight form can have a larger TextBox and could display validation errors with an ErrorProvider .

Oops...

I didn't realize this question was for WinForms until just now (Edit history shows the tag was added later). I'm going to leave the answer in place though, in case it is useful for anyone wanting to know how to do this with WPF.


You can do this rather easily using the MVVM pattern without having to worry about positioning code at all. Full Source is available at this link .

在此输入图像描述

This uses a list of DocumentVm with a Name , and an IsRenaming property. The TabControl has its ItemsSource bound to the list of DocumentVm so that it has one tab per Document. The header of the TabItem contains a TextBlock , a TextBox , and Button . When you click the button, the IsRenaming flag is toggled. The TextBox and TextBlock have their visibility controlled by IsRenaming (using a normal, and inverted converter). So clicking the button lets you edit the tab's title.

MainWindow.Xaml:

<Window
    x:Class="RenamableTabs.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:RenamableTabs"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="525"
    Height="350"
    d:DataContext="{d:DesignInstance local:MainWindowVm}"
    mc:Ignorable="d">
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
        <local:InvertedBooleanToVisibilityConverter x:Key="InvertedBooleanToVisibilityConverter" />
    </Window.Resources>
    <Grid Margin="8">
        <TabControl ItemsSource="{Binding Path=Documents}">
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Path=Name}" Visibility="{Binding Path=IsRenaming, Converter={StaticResource InvertedBooleanToVisibilityConverter}}" />
                        <TextBox Text="{Binding Path=Name}" Visibility="{Binding Path=IsRenaming, Converter={StaticResource BooleanToVisibilityConverter}}" />
                        <Button Command="{Binding Path=RenameCommand}" Content="R" />
                    </StackPanel>
                </DataTemplate>
            </TabControl.ItemTemplate>
        </TabControl>
    </Grid>
</Window>

MainWindowVm.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AgentOctal.WpfLib;
using AgentOctal.WpfLib.Services;
using AgentOctal.WpfLib.Services.Message;

namespace RenamableTabs
{
    class MainWindowVm : ViewModel
    {
        public MainWindowVm()
        {
            _messageService = ServiceManager.GetService<AgentOctal.WpfLib.Services.Message.IMessageService>();
            _messageService.Subscribe<DocumentVm.DocumentRenaming>(message =>
            {
                var m = message as DocumentVm.DocumentRenaming;
                foreach (var documentVm in Documents)
                {
                    documentVm.IsRenaming = documentVm == m.Document;
                }
            });

            Documents = new ObservableCollection<DocumentVm>();

            Documents.Add(new DocumentVm() { Name = "Document 1" });
            Documents.Add(new DocumentVm() { Name = "Document 2" });
            Documents.Add(new DocumentVm() { Name = "Document 3" });
        }

        private IMessageService _messageService;

        public ObservableCollection<DocumentVm> Documents { get; }

        private string _name;

        public string Name
        {
            get {return _name;}
            set {SetValue(ref _name, value);}
        }

    }
}

DocumentVm.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Xaml;
using AgentOctal.WpfLib;
using AgentOctal.WpfLib.Commands;
using AgentOctal.WpfLib.Services;
using AgentOctal.WpfLib.Services.Message;

namespace RenamableTabs
{
    class DocumentVm : ViewModel
    {
        public DocumentVm()
        {
            _messageService = ServiceManager.GetService<AgentOctal.WpfLib.Services.Message.IMessageService>();
        }

        private IMessageService _messageService;

        private string _name;
        public string Name
        {
            get { return _name; }
            set { SetValue(ref _name, value); }
        }

        private ICommand _renameCommand;
        public ICommand RenameCommand
        {
            get
            {
                return _renameCommand ?? (_renameCommand = new SimpleCommand((obj) =>
                {
                    _messageService.SendMessage(new DocumentRenaming() { Document = this });
                }));
            }
        }

        private bool _isRenaming = false;
        public bool IsRenaming
        {
            get { return _isRenaming; }
            set { SetValue(ref _isRenaming, value); }
        }

        public class DocumentRenaming : IMessage
        {
            public DocumentVm Document { get; set; }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM