简体   繁体   中英

Silverlight 4 : Making Closeable Tabitems

I would like to extend the tab control to have closeable tab items.

I have found this WPF solution of Kent: On the WPF TabControl - can I add content next to the tab headers?

I opened a copy of the existing silverlight tabcontrol in Blend. However the structure looks quite different to the WPF tabcontrol. I can't get it right into the Silverlight control template.

Does anyone know a good resource for me?

I had the same problem before, and then I decided to use an extended TabControl . I don't know where I've found it, but it doesn't matter, now it is in my project.

With this TabControl I can add or remove items from a collection of ViewModel and my changes will be reflected on user interface.

MyTabControl.cs

public class MyTabControl : TabControl
{
    public MyTabControl()
        : base()
    {
        this.SelectionChanged += OnSelectionChanged;
    }

    #region Tabs with databinding and templates
    /// <summary>
    /// Template for a TabItem header
    /// </summary>
    public DataTemplate TabHeaderItemTemplate
    {
        get { return (DataTemplate)GetValue(TabHeaderItemTemplateProperty); }
        set { SetValue(TabHeaderItemTemplateProperty, value); }
    }
    public static readonly DependencyProperty TabHeaderItemTemplateProperty =
        DependencyProperty.Register("TabHeaderItemTemplate", typeof(DataTemplate), typeof(MyTabControl), new PropertyMetadata(
            (sender, e) =>
            {
                ((MyTabControl)sender).InitTabs();
            }));

    /// <summary>
    /// Template for a content
    /// </summary>
    public DataTemplate TabItemTemplate
    {
        get { return (DataTemplate)GetValue(TabItemTemplateProperty); }
        set { SetValue(TabItemTemplateProperty, value); }
    }
    public static readonly DependencyProperty TabItemTemplateProperty =
        DependencyProperty.Register("TabItemTemplate", typeof(DataTemplate), typeof(MyTabControl), new PropertyMetadata(
            (sender, e) =>
            {
                ((MyTabControl)sender).InitTabs();
            }));

    /// <summary>
    /// Source of clr-objects
    /// </summary>
    public IEnumerable MyItemsSource
    {
        get
        {
            return (IEnumerable)GetValue(MyItemsSourceProperty);
        }
        set
        {
            SetValue(MyItemsSourceProperty, value);
        }
    }

    public static readonly DependencyProperty MyItemsSourceProperty =
        DependencyProperty.Register("MyItemsSource", typeof(IEnumerable), typeof(MyTabControl), new PropertyMetadata(
            (sender, e) =>
            {
                MyTabControl control = (MyTabControl)sender;
                INotifyCollectionChanged incc = e.OldValue as INotifyCollectionChanged;
                if (incc != null)
                {
                    incc.CollectionChanged -= control.MyItemsSourceCollectionChanged;
                }
                control.InitTabs();

                incc = e.NewValue as INotifyCollectionChanged;
                if (incc != null)
                {
                    incc.CollectionChanged += control.MyItemsSourceCollectionChanged;
                }
            }));


    /// <summary>
    /// Selected item as object
    /// </summary>
    public object MySelectedItem
    {
        get { return (object)GetValue(MySelectedItemProperty); }
        set { SetValue(MySelectedItemProperty, value); }
    }

    // Using a DependencyProperty as the backing store for MySelectedItem.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty MySelectedItemProperty =
        DependencyProperty.Register("MySelectedItem", typeof(object), typeof(MyTabControl), new PropertyMetadata(
            (sender, e) =>
            {
                MyTabControl control = (MyTabControl)sender;

                if (e.NewValue == null)
                    control.SelectedItem = null;
                else
                {
                    var tab = control.Items.Cast<TabItem>().FirstOrDefault(ti => ti.DataContext == e.NewValue);
                    if (tab != null && control.SelectedItem != tab)
                        control.SelectedItem = tab;
                }
            }));

    private void InitTabs()
    {
        Items.Clear();
        if (MyItemsSource != null && MyItemsSource.OfType<object>().Any())
        {
            int i = 0;
            foreach (var item in MyItemsSource)
            {
                var newitem = new TabItem();

                if (TabItemTemplate != null)
                    newitem.Content = TabItemTemplate.LoadContent();

                if (TabHeaderItemTemplate != null)
                    newitem.Header = TabHeaderItemTemplate.LoadContent();

                newitem.DataContext = item;
                Items.Add(newitem);
            }
            VisualStateManager.GoToState(this, "Normal", true);
        }
        else VisualStateManager.GoToState(this, "NoTabs", true);
    }

    private void MyItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            if (e.NewStartingIndex > -1)
            {
                foreach (var item in e.NewItems)
                {
                    var newitem = new TabItem();

                    if (TabItemTemplate != null)
                        newitem.Content = TabItemTemplate.LoadContent();

                    if (TabHeaderItemTemplate != null)
                        newitem.Header = TabHeaderItemTemplate.LoadContent();

                    newitem.DataContext = item;
                    Items.Add(newitem);
                }
            }
        }
        else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
        {
            if (e.OldStartingIndex > -1)
            {
                var ti = (TabItem)this.Items[e.OldStartingIndex];
                Items.RemoveAt(e.OldStartingIndex);
            }
        }
        else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)
        {
            Items.RemoveAt(e.OldStartingIndex);

            var newitem = new TabItem();

            if (TabItemTemplate != null)
                newitem.Content = TabItemTemplate.LoadContent();

            if (TabHeaderItemTemplate != null)
                newitem.Header = TabHeaderItemTemplate.LoadContent();

            newitem.DataContext = e.NewItems[0];

            Items.Add(newitem);
            Items.Insert(e.NewStartingIndex, newitem);
        }
        else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
        {
            InitTabs();
        }
    }

    #endregion

    private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var si = e.AddedItems.Cast<TabItem>().FirstOrDefault();
        if (si != null)
            this.MySelectedItem = si.DataContext;
        else this.MySelectedItem = null;
    }
}

MainPage.xaml

<my:MyTabControl MyItemsSource="{Binding Items}" MySelectedItem="{Binding SelectedITem}">
        <my:MyTabControl.TabHeaderItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="{Binding Title}" VerticalAlignment="Center"/>
                    <Button Content="X" Margin="3" Width="20" Height="20" Grid.Column="1"
                            Command="{Binding RequestCloseCommand}"/>
                </Grid>
            </DataTemplate>
        </my:MyTabControl.TabHeaderItemTemplate>
        <my:MyTabControl.TabItemTemplate>
            <DataTemplate>
                <ContentControl Content="{Binding Content}"/>
            </DataTemplate>
        </my:MyTabControl.TabItemTemplate>
    </my:MyTabControl> 

Notice, that the properties are called MyItemsSource and MySelectedItem , because this TabControl use objects, not TabItem .

And two ViewModels: MainViewModel.cs

public class MainViewModel
{
    public MainViewModel()
    {
        this.Items = new ObservableCollection<TabItemViewModel>
                         {
                             new TabItemViewModel("Tab 1", OnItemRequestClose),
                             new TabItemViewModel("Tab item 2", OnItemRequestClose)
                         };
    }

    public ObservableCollection<TabItemViewModel> Items { get; set; }

    public void OnItemRequestClose(TabItemViewModel item)
    {
        this.Items.Remove(item);
    }
}

TabItemViewModel.cs

public class TabItemViewModel
{
    public TabItemViewModel(string title, Action<TabItemViewModel> onClose)
    {
        this.Title = title;
        this.RequestCloseCommand = new DelegateCommand(_ => onClose(this));

        //Just a demontration
        this.Content = "Test content "+title;
    }

    public string Title { get; set; }

    public ICommand RequestCloseCommand { get; set; }

    public object Content { get; set; }      
}

You can Template TabItem to have some sort of close button that you can hook up in code behind to close the currently selected tab.

<Style TargetType="TabItem">
            <Setter.Value>
                <ControlTemplate TargetType="sdk:TabItem">
                            <Button x:Name="PART_btnClose"
                                            Height="15"
                                            Width="15"
                                            Grid.Column="1"
                                            HorizontalAlignment="Right"
                                            VerticalAlignment="Center"
                                            Margin="20,0,3,8" BorderThickness="1" Cursor="Hand" />
</ControlTemplate>
</Setter.Value>
</Style>

After this, in on apply template you can subscribe to the ButtonClicked Event.

Something like this:

public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        PART_btnClose = GetTemplateChild("PART_btnClose") as Button;

        if (PART_btnClose != null)
        {
            PART_btnClose.Click += new RoutedEventHandler(PART_btnClose_Click);
        }

In that event, you can close your tab.

Hope This helps, code might not work as is, just did it quickly.

Ty Rozak

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