Silverlight TabControl with deletable TabItems

Topics: UI Architecture
Jun 27, 2011 at 3:18 PM

Hi guys,
I would like to have a Silverlight application that works like a Browser with Tabs but in the Silverlight application. Not oppening a new tab in the browser!!
If u click a item in the menue, a new Tab in the TabControl will open with the corresponding View.

For that I created a ActionBehavior like this:

    public class AddTabToTabControlAction : TargetedTriggerAction<TabControl>
    {

        private TabControl tabControl;

        #region NewViewModelProperty

        public static readonly DependencyProperty NewViewModelProperty = DependencyProperty.Register("NewViewModel",
            typeof(IScreen), typeof(AddTabToTabControlAction), null);

        public IScreen NewViewModel
        {
            get {
                return (IScreen)base.GetValue(NewViewModelProperty);
            }
            set {
                base.SetValue(NewViewModelProperty, value);
            }
        }
        #endregion

        protected override void OnAttached()
        {
            base.OnAttached();
            tabControl = (TabControl)(this.AssociatedObject);
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
        }


        protected override void Invoke(object parameter)
        {
            if (NewViewModel != null) { 
                //see if the viewmodel is already added
                var tab = (from tabs in tabControl.Items.Cast<TabItem>()
                           where (tabs.Tag as string) == NewViewModel.DisplayName
                           select tabs).FirstOrDefault();

                if (tab == null) {

                    AddTabToTabControl();

                }
                else {  // Tab already exists
                    
                    
                    // Set the Tab as selected
                    tab.IsSelected = true;
                
                }
            }
        }


        private void AddTabToTabControl()
        {
            TabItem tabItem = new TabItem()
            {
                Tag = NewViewModel.DisplayName
            };

            // ** Tab Content **
            var viewContent = LocateViewFor(NewViewModel);

            ViewModelBinder.Bind(NewViewModel, tabItem, null);
            tabItem.Content = viewContent; //TODO: can this be done by xaml caliburn.View

            // ** Tab Header **
            TabHeaderViewModel headerViewModel = new TabHeaderViewModel()
            {
                HeaderDisplay = NewViewModel.DisplayName,
                TabItem = tabItem,
                TabControl = tabControl
            };

            var viewHeader = LocateViewFor(headerViewModel);
            ViewModelBinder.Bind(headerViewModel, tabItem, null);
            tabItem.Header = viewHeader;


            tabItem.IsSelected = true;
            tabControl.Items.Add(tabItem);
        }


        private object LocateViewFor(object viewModel)
        {
            var view = ViewLocator.LocateForModelType(viewModel.GetType(), nullnull);
            return view;
        }

    }

TabControl:
        <sdk:TabControl x:Name="Tab" 
                        Margin="0,0,0,138" 
                        Height="162">
            <i:Interaction.Triggers>
                        <ei:PropertyChangedTrigger Binding="{Binding NewViewModel, Mode=OneWay}">
                            <behaviors:AddTabToTabControlAction NewViewModel="{Binding NewViewModel, Mode=OneWay}"/>
                        </ei:PropertyChangedTrigger>
            </i:Interaction.Triggers>
        </sdk:TabControl>


The problem is the Binding ViewModel with View. If I click something in the new view. The action comes not to the ViewModel.

Please help!!
Jun 27, 2011 at 5:12 PM

I couldn't figure out the problem... the binding code seems OK.
If you could put together a small repro, that would really help me to understand the cause.

There is a reason why you chose the behavior approach?
This scenario could be well addressed using model-first for the entire tab, not just for its pages.
Have a look at the SimpleMDI sample in the source code: the scenario should be very similar to yours, and it is implemented using a Conductor.Collection.OneActive (a VM representing a set of child VMs).

Jun 28, 2011 at 8:20 AM

thx for your answer!!
The problem is the red colored part with the binding. Unfortunatly just the last binding does work. I dont know why. Even I dont know why I have to bind the ViewModel with the TabItem.
Why I have chosen a behavior? Because in this code I have the TabControl and the TabItem in access. I wanna delete the TabItem from the TabControl as well. So I give these information to the TabHeaderViewModel.

Your example is pretty good. But It doesnt worke in Silverlight. So I looked for another example and I found this article:
http://devlicio.us/blogs/rob_eisenberg/archive/2010/10/19/caliburn-micro-soup-to-nuts-part-6c-simple-mdi-with-screen-collections.aspx

In this is written this paragrath:

Rebuild this sample in Silverlight. Unfortunately, Silverlight’s TabControl is utterly broken and cannot fully leverage databinding. Instead, try using a horizontal ListBox as the tabs and a ContentControl as the tab content. Put these in a DockPanel and use some naming conventions and you will have the same affect as a TabControl.

But I have no ideer how i should work with a ListBox?
Maybe u can give me the xaml?

Thank you for your help!!

Jun 28, 2011 at 7:21 PM
Edited Jun 28, 2011 at 7:23 PM

It should be something along this path (assuming the VM is a Conductor<>.Collection.OneActive):

 

<Grid>
	<Grid.RowDefinitions>
		<RowDefinition Height="auto" />
		<RowDefinition Height="*" />
	</Grid.RowDefinitions>
	
	
	<!-- tabs -->
	<ListBox Name="Items" Grid.Row="0">
		<ListBox.ItemTemplate>
			<DataTemplate>
				<!-- template of tab header -->
				<Border ...>
					<TextBlock Text="{Binding DisplayName}" />
				</Border>
			</DataTemplate>
			<ListBox.ItemsPanel>
				<PanelTemplate>
					<!-- configuration of panel holding the various tab header; lays out item horizontally -->
					<StackPanel Orientation="Horizontal" />
				</PanelTemplate>
			</ListBox.ItemsPanel>
		</ListBox.ItemTemplate>
		<
	</ListBox>

	<!-- active item area -->
	<ContentControl Name="ActiveItem" Grid.Row="1" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" />
</Grid>

(sorry for possible errors, I'm writing without VS)