Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 488
Houman Avatar asked Jan 21 '23 15:01

Houman


1 Answers

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; }      
}
like image 151
vortexwolf Avatar answered Jan 29 '23 09:01

vortexwolf