TabControl caching with code behind layout changes - c#

In regarding with this SO question here, it is posible to update other tab content from code behind and let caching allow to re-cache changed UI elements? Like in scenario I have updated DataGrid scroll index for some tabs on TabControl on some event,
dgvLogs.ScrollIntoView( log );
Now since tab is already cached and above change not reflecting when user switch to tab where dgvLogs located.
EDIT
I have tab control (ExTabControl) in main window and multiple tab holding datagrid which displaying some application logs inside it. Like this:
ExTabControl like this:
<controls:ExTabControl Grid.Row="1" ItemsSource="{Binding Tabs, Mode=OneWay}" >
<controls:ExTabControl.Resources>
<Style TargetType="{x:Type TabItem}">
</Style>
</controls:ExTabControl.Resources>
</controls:ExTabControl>
Single tab having datagrid like this:
<DataGrid Name="dgvLogs" ItemsSource="{Binding Logs}" VerticalScrollBarVisibility="Auto" FrozenColumnCount="4">
Problem:
Lets say I have 3 tab in ExTabControl, selected tab is 1 and from code behind have have update scroll index for tab 2 using dgvLogs.ScrollIntoView( someInbetweenlog );. Ideally if I do select tab 2 then select scroll index inside dgvLogs should be where someInbetweenlog is located. But unfortunately tab 2 scroll not moving as per changes made code behind..
If I do make use of default tab control i.e. TabControl insted of ExTabControl then it is working fine as expected. but if I move scroll in any of tab for dgvLogs then it is reflecting in other tabs also..
Please add comment I'll post more code if required.
EDIT 2
I have created sample application in which I tried to demonstrate the issue. In this app I have added context menu for grid in tab and using Sync option I am trying to scroll to view where first matching log found with closed selected log, in other opened tabs.
Issue: ExTabControl unable to scroll to required log item in different opened tab.
https://github.com/ankushmadankar/StackOverflow54198246/

If I do make use of default tab control i.e. TabControl instead of ExTabControl then it is working fine as expected. But if I move scroll in any of tab for dgvLogs then it is reflecting in other tabs also.
There are two uses of TabControl, extending on this post:
When we bind ItemsSource to a list of items, and we have set the same DataTemplate for each item, TabControl will create only one "Content" view for all items. And when a different tab item is selected, the View doesn't change but the backing DataContext is bound to the viewmodel of the newly selected item.
Is it possible to update other tab content from code behind and let caching allow to re-cache changed UI elements?
The reason the updates won't work is because of another WPF optimization, from UIElement.IsVisible:
Elements where IsVisible is false do not participate in input events (or commands), do not influence either the measure or arrange passes of layout, are not focusable, are not in a tab sequence, and will not be reported in hit testing.
You can change properties on cached elements, but some operations require that an UIElement is visible in order to take effect.
Worth noting:
If you call ScrollIntoView on a DataGrid that's not visible, it won't scroll to the given object. So the ScrollToSelectedBehavior from your linked project is intended to scroll a datagrid that is visible during the process.
In the code of ExTabControl the method UpdateSelectedItem sets the visibility of non active contentpresenters to collapsed.
Given you've explicitly asked for code behind,
A quick hack
TraceViewerView.xaml
<DataGrid IsVisibleChanged="dgvLogs_IsVisibleChanged" ... >
TraceViewerView.xaml.cs
private void dgvLogs_IsVisibleChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
if (sender is DataGrid dataGrid && dataGrid.IsVisible)
{
TraceViewerViewModel viewModel = (TraceViewerViewModel)DataContext;
if (viewModel.Log != null)
dataGrid.ScrollIntoView(viewModel.Log);
}
}
A couple of remarks:
You can now remove the line local:ScrollToSelectedBehavior.SelectedValue="{Binding Log}" as we are fetching the sync value straight from the viewmodel.
This is a hack, the view is hard coded to your viewmodel, which is likely to blow up at some time.
A better way
First, to keep our code loosely coupled, an interface.
interface ISync
{
object SyncValue { get; }
}
TraceViewerModel.cs
public class TraceViewerViewModel : PropertyObservable, ITabItem, ISync
Rename Log to SyncValue, and replace the original code
private TraceLog synclog;
public TraceLog Log
{
get { return synclog; }
private set
{
synclog = value;
OnPropertyChanged();
}
}
with
public object SyncValue { get; set; }
Basically, we're trading in a Binding for an interface. The reason I went for the interface in this specific use case, is that you only need to check a tab's sync value when you move to it (making a full fledged Binding a bit overkill).
Next, let's create a Behavior that does what you want.
Instead of an Attached Property I'll use Interactivity Behaviors, which provide a more encapsulated way to extend functionality (requires System.Windows.Interactivity).
ScrollToSyncValueBehavior.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace WpfApp1
{
public class ScrollToSyncValueBehavior : Behavior<DataGrid>
{
protected override void OnAttached()
{
this.AssociatedObject.IsVisibleChanged += OnVisibleChanged;
}
protected override void OnDetaching()
{
this.AssociatedObject.IsVisibleChanged -= OnVisibleChanged;
}
private static void OnVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (sender is DataGrid dataGrid && dataGrid.IsVisible)
{
ISync viewModel = dataGrid.DataContext as ISync;
if (viewModel?.SyncValue != null)
dataGrid.ScrollIntoView(viewModel.SyncValue);
}
}
}
}
TraceViewerView.xaml
<UserControl x:Class="WpfApp1.TraceViewerView"
...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<DataGrid CanUserAddRows="false" GridLinesVisibility="None" AutoGenerateColumns="False"
ItemsSource="{Binding Logs}">
<i:Interaction.Behaviors>
<local:ScrollToSyncValueBehavior />
</i:Interaction.Behaviors>
...
</DataGrid>
</Grid>
</UserControl>

You need to derive TabControl as described in this answer. Using that technique, the visual tree for each tab will be preserved.
Please note that if you have lots of tabs the caching will imply a significant performance impact. I would recommend using it for at most 10 tabs.

Related

Dynamic controls switching in WPF

Title might be misleading but i'm not sure how to describe it.
Lets say i have 2 containers - one on the left, one on the right. Left container has multiple buttons. Pressing them will change whats inside 2nd container.
If i press 1st button a set of buttons and calendar will appear, 2nd - datagridview etc. Its example.
How can i achieve it? I'm not asking for solution (it can't be solved in one line of code, obviously), but what should i search for. Some specific control? Displaying other window inside it? Etc.
I am not sure if I understood the question well, so I wrote the following scenario from what I understood.
As you mentioned, you have a main window that contains 2 panels, one on the left and the other on the right. In the left panel, there is a list of buttons placed as a group of menus, which, when clicked, show other content in the right panel, something like a navigation to another system module (see the gif):
If this is your scenario, you can design your WPF application as follows:
Create UserControls for each screen you want to navigate to. In the previous example, you could create a UserControl for the module of the task list, and another UserControl for the module of My Agenda. Check this link so you know what a UserControl is.
Manage navigation on the main window. Just like in WinForms, you could handle the click event on each button in the left panel, however, an elegant way to handle the click event is that your handle it in the parent container, since, unlike Winforms, the click event is a bubbling event. Check this link, so you know what a routed event and what is a bubbling event.
In the example video, could you notice that each module is in a container that has a header and that the header text changes when the button is clicked and the header text is updated with the button text? This can be done in many ways, but a good way to do it is through data binding, check this link to understand what this concept is. With experience, you will realize when it will be advisable to apply this and when it will not.
As you can see, there are many concepts that you should review and learn to be able to make a good design of an application taking advantage of all the benefits that WPF has and to continue with the philosophy of WPF.
I write an example code that I also publish on GitHub. I explain some things about the code, but I suggest that you expand these concepts in the links that I left you and in other reliable sources of knowledge, such as books or tutorials from Microsoft itself.
The Xaml MainWindow:
<Window
x:Class="WpfApp26.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:WpfApp26"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800" Height="450"
d:DataContext="{d:DesignInstance Type=local:ViewModel}"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<!-- A GroupBox is a control with a header -->
<GroupBox Header="Options">
<!-- Look that the click event is handled in the StackPanel, the container for the buttons -->
<StackPanel Button.Click="ModuleSelected_OnClick">
<Button
Margin="5" Padding="5"
Content="To Do List" Tag="ToDoListModule" />
<Button
Margin="5" Padding="5"
Content="My Agenda" Tag="MyAgendaModule" />
</StackPanel>
</GroupBox>
<!-- The header property is binding to the CurrentModuleName property in the DataContext -->
<GroupBox Name="GbCurrentModule" Grid.Column="1" Header="{Binding CurretModuleName}" />
</Grid>
</Window>
The MainWindow code behind (review the INotifyProperyChanged):
public partial class MainWindow : Window {
private readonly ViewModel vm;
public MainWindow() {
InitializeComponent();
// Setting the Window's DataContext to a object of the ViewModel class.
this.DataContext = this.vm = new ViewModel();
}
private void ModuleSelected_OnClick(object sender, RoutedEventArgs e) {
// The Source property of the RoutedEventArgs gets the Element that fires the event (in this case, the button).
var clickedButton = (Button) e.Source;
this.vm.CurretModuleName = clickedButton.Content.ToString();
// Getting the Tag property of the button.
var tag = clickedButton.Tag.ToString();
// Performing the navigation.
switch (tag) {
case "ToDoListModule":
NavigateToModule(new UcToDoListModule());
break;
case "MyAgendaModule":
NavigateToModule(new UcMyAgendaModule());
break;
}
#region Internal methods
void NavigateToModule(UserControl uc) {
this.GbCurrentModule.Content = uc;
}
#endregion
}
}
The ViewModel class:
// The class implementents the INotifyPropertyChanged interface, that is used
// by the WPF notifications system.
public class ViewModel : INotifyPropertyChanged {
private string curretModuleName;
public string CurretModuleName {
get => this.curretModuleName;
set {
this.curretModuleName = value;
this.OnPropertyChanged();
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
You can use DataTemplates with Data Binding: https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/data-templating-overview
This will allow you to define templates that are automatically applied to objects of specific types. So you could have a calendar object, list view, data grid, etc apply individually.
You could also use the visibility to show/hide the view as desired when your button(s) are clicked.
MVVM frameworks use this quite often: https://compiledexperience.com/blog/posts/using-caliburn-micro-as-a-data-template-selector
Another example https://www.codemag.com/article/0907111/Dressing-Up-Your-Data-with-WPF-DataTemplates
There are also other MVVM approaches that use activators to show/hide/generate new objects of specific types and display them.

WPF ToolBarControl in multiple UserControls

I'm working on a project where the request is to implement ToolBarControl in multiple UserControls.
The UserControl would mostly have that toolbar and GridView (Devexpress).
I'm using WPF with MVVM and Caliburn.Micro framework for development.
The problem is that, that I need to c/p code of the ToolBarControl in the XAML and then in ViewModel to implement the properties.
I'm searching for a better way, and for now I guess it would be reflection.
Any advice would be helpful, code examples too.
Update #2
The controls who would be in the custom toolbar, would should be able to move selected row up or down, delete item, edit and create (last two should open a new window).
Let's say I have CustomersListViewModel, who in it's CustomersListView has the custom ToolBarControl and GridControl.
When I click add button, it should open me CustomersEditViewModel.
When I click delete, it should delete selected item in list.
When I click move up, it should move up selected row.
You could use a datatemplate toolbarviemodel and toolbarview in your app.xaml and then use a contentcontrol to show the toolbar binding it to an instance of your toolbarviewmodel
app.xaml:
<ResourceDictionary>
<DataTemplate DataType="{x:Type ViewModelToolBar}">
<startViews:ViewToolBar />
</DataTemplate>
</ResourceDictionary>
and in your usercontrol:
<ContentControl Content="{Binding MyViewModelToolBar}"/>
and to execute your commands you could use a notify event with a tag or so as parameter to tell your usercontrol viewmodel wich operation should be performed.Means you bind your toolbar buttons to the notifycommand and use button name or tag as parameter.
ViewModelToolBar:
public event EventHandler Notify;
private void OnNotify(object sender)
{
Notify?.Invoke(sender, new EventArgs());
}
public ICommand NotifyCommand => new DelegateCommand<object>(OnNotify);
and in your usercontrol ViewModel:
MyViewModelToolBar = new ViewModelToolBar();
ViewModelToolBar.Notify += ViewModelToolBar_Notify;
private void ViewModelToolBar_Notify(object sender, EventArgs e)
{
switch (sender.ToString())
{
case "Case1":
"perform your operation"
break;
case "Case2":
...
break;
case "Case3":
...
break;
}
}

C# referencing a Grid in WPF to change properties

Hello im new to making apps with WPF and XAML in Visual Studio. So I have a grid I want to change its properties in the code.
My Grid's properties:
<Grid HorizontalAlignment="Left"
Height="603"
Margin="0,51,0,0"
x:Name="usersPan"
VerticalAlignment="Top"
Width="1286">
How I have been trying to change its properties
this.usersPan.SetValue(Grid.WidthProperty, PAN_SIZE);
usersPan.SetValue(Grid.WidthProperty, PAN_SIZE);
usersPan.Width = 0;
usersPan.Visibility = Visibility.Collapsed;
When I try to do that^ it says null reference for userPan
Thanks
Noooooooo, Don't ever do that. Make a ViewModel that is bound to the Grid's Width property, and then just change the value.
My suspicion is that you do not need this at all. Have a look into containers, and how to position them.
In all of this years, there have been rare occasions I needed to do that and I suspect you do not need to. Tell me what you are doing.
EDIT:
You have a VM which needs to implement the NotifyPropertyChanged interface (I won't do that here, there are plenty of examples on hoew to do that)
public class MainVM
{
public ObservableCollection<TabVM> TabsVms {get;set;}
public int SelectedIndex {get;set}
}
bound to the control
<TabControl DataContext={TabsVMs} SelectedIndex="{Binding SelectedIndex}">
...
</TabControl>
And in runtime you create a couple of Tabs
var TabsVMs = new ObservableCollection<TabVM>();
TabsVMs.add(new TabVM());
TabsVMs.add(new TabVM());
TabsVMs.add(new TabVM());
Then in runtime you change the value of the index.
MainVm.SelectedIndex = 1
and the the coresponding tab will become selected.
EDIT:
I can also recommend you to use Fody for the MVVM notification.
Also, when it comes to bindings, I can recommend you to use WPF inspector. a handy little tool
The best way to write WPF programs is to use the MVVM (Model-View-View Model) design pattern. There are two (2) ideas behind MVVM:
Write as little code as possible in the view's code-behind and put all of the logic in the View Model object, using WPF's data binding feature to connect the properties of the View Model object to the view's controls.
Separate the logic from the display so you can replace the view with some other construct without having to change the logic.
MVVM is a huge topic on its own. There are lots of articles about it, and frameworks that you can use to build your program. Check out MVVM Light, for example.
Don't know exactly why Grid is invisible in code-behind, but You can access it's properties using events (but don't think it is perfect solution).
For example add to your grid event Loaded
<Grid HorizontalAlignment="Left"
Height="603"
Margin="0,51,0,0"
x:Name="usersPan"
VerticalAlignment="Top"
Width="1286"
Loaded="FrameworkElement_OnLoaded">
and then from code-behind you can access grid in next way:
private void FrameworkElement_OnLoaded(object sender, RoutedEventArgs e)
{
var grid = sender as Grid;
if (grid != null)
{
grid.Width = 0;
}
}
Better solution :
Add some boolean property to your ViewModel like public bool IsGridVisible{get;set;}
And bind it to your Grid
<Grid HorizontalAlignment="Left"
Height="603"
Margin="0,51,0,0"
x:Name="usersPan"
VerticalAlignment="Top"
Width="1286"
Visibility="{Binding Path=IsGridVisible, Converter={StaticResource BoolToVis}">
where BoolToVis is converter which converts true to Visible and false to Hidden. You can define it in App.xaml like :
<BooleanToVisibilityConverter x:Key="BoolToVis" />
I was able to do something like this so I can change properties outside of an event.
private Grid userGrid;
private void onUserGridLoaded(object sender, RoutedEventArgs e)
{
userGrid = sender as Grid;
}

DataTemplate not loading in pivot windows phone 8.1

I'm trying to display different layouts in pivot items in a wp8.1 app (UNIAPP ). Ideally I would like to load different pages but since I could figure this out, I thought I'd try with the basics first as I'd use this before but for some reason I can't get this to work.
My pivot items are loaded dynamically based on the provided ViewModel
<Pivot.ItemTemplate>
<DataTemplate>
<controls:DataTemplateSelector Content="{Binding}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
</controls:DataTemplateSelector>
/DataTemplate>
</Pivot.ItemTemplate>
My resources are defined as follows within the same xaml page
<Page.Resources>
<DataTemplate x:Key="MyApp.ViewModel.PIDetailsVM">
<Button Content="test" Foreground="White"></Button>
</DataTemplate>
<DataTemplate x:Key="MyApp.ViewModel.PIListVM">
<Button Content="test" Foreground="White"></Button>
</DataTemplate>
</Page.Resources>
My DataTemplateSelector is defined as follows:
public class DataTemplateSelector : ContentControl
{
protected override void OnContentChanged(object oldContent,
object newContent)
{
ContentTemplate = this.FindResource<DataTemplate>(newContent.GetType
().FullName);
}
}
It is being triggered whenever I go to a new pivot item, but the ContentTemplate is always null.
The newContent.GetType().FullName returns the relevant viewmodel name which I can see being displayed in the relevant pivot.
One thing I noticed is that the DataTemplateSelector class (this) has no resources when I check it via this.Resources.count(), so it's obviously not finding them but how do I fix this?
UPDATE:
My DataTemplates are not getting loaded in my Pivot Items. There is obviously a problem with the .NET IDE as whenever I add or remove a from Content="{Binding}" it displays the button within the pivot item but that's within the IDE. Unfortunately, at run-time, it just displays the name of my viewmodel.
Thought the behaviour is erratic in the IDE, the fact that the button from my DataTemplate is displaying when messing around with the Content="{Binding<space>" would make you think that the code and xaml are correct but it's definitely not working at run-time.
Any idea what's wrong why my DataTemplates are not displaying in pivot item?
Thanks.
This is a partial answer. By this I mean that I did find a work-around to my problem but I did not resolve the issue itself.
My DataTemplateSelector which gets triggered whenever the pivot changes call a extension function called FindResource:
public static class ControlExtensions
{
public static T FindResource<T>(this DependencyObject initial,
string key) where T : DependencyObject
{
DependencyObject current = initial;
while (current != null)
{
if (current is FrameworkElement)
{
if ((current as FrameworkElement).Resources.
ContainsKey(key))
{
return (T)(current as FrameworkElement).Resources[key];
}
}
current = VisualTreeHelper.GetParent(current);
}
if (Application.Current.Resources.ContainsKey(key))
{
return (T)Application.Current.Resources[key];
}
return default(T);
}
}
For some strange reason, Windows Phone 8.1 (WinRT) does not like having the data templates in while it is not a problem in WP8/WP8.1 Silverlight.
As mentioned, this is unstable in the IDE where it sometimes displays the DataTemplate, and sometimes it doesn't depending on whether or not I add a space after the Binding keyword to the Content="{Binding}". One thing for sure is that it never works at run-time, well not at least not with the above code.
VisualTreeHelper.GetParent(current) always returns null no matter what. I've checked at debug time if I somehow could access the resources, but to no avail.
How did I fix it? Well, I moved my data templates to a resource dictionary
<DataTemplate x:Key="MyApp.ViewModel.PIDetailsVM">
<Button Content="test" Foreground="White"></Button>
</DataTemplate>
<DataTemplate x:Key="MyApp.ViewModel.PIListVM">
<Button Content="test" Foreground="White"></Button>
</DataTemplate>
The second I did this, the second part of my FindResources kicks in since the Current object is always null, no matter what
if (Application.Current.Resources.ContainsKey(key))
{
return (T)Application.Current.Resources[key];
}
and it finds the relevant DataTemplate and displays it accordingly in my pivot control based on the relevant PivotItem ViewModel.
Now, I'm not out of the woods yet as I have no idea if binding to the relevant viewmodel will work but that's a whole other story!
If anyone knows why DataTemplate cannot be found when defined in Pages.Resources or Grid.Resources, please update the post as I'd love to know why.
Thanks.

Getting parent of new tab after adding to bound TabControl (mvvm)

I'm adding a close button to my tabs using the following guide:
http://www.codeproject.com/Articles/84213/How-to-add-a-Close-button-to-a-WPF-TabItem
This has become a problem because the event uses the 'parent' of the added tab to remove that tab from the tabcontrol. I'm binding the tab control using mvvm, so the parent property is apparently not being set and giving me a null reference exception for the parent when the event tries to remove from it.
Here's the binding so you get the idea:
<TabControl Name="tabControl" Margin="0,22,0.2,-5.2" ItemsSource="{Binding Tabs}" Background="#FF4C76B2"/>
Heres where the tabs are being added.
private void AddTab(object tabName)
{
ClosableTab newTab = new ClosableTab();
newTab.Title = "title?";
//newTab.Header = tabName;
TextBox test = new TextBox();
test.Text = "CONTENT (" + tabName + ") GOES HERE";
newTab.Content = test;
Tabs.Add(newTab);
OnPropertyChanged("Tabs");
}
Here is the event where the null reference is taking place:
void button_close_Click(object sender, RoutedEventArgs e)
{
((TabControl)this.Parent).Items.Remove(this);
}
As I see it there are two options:
try to find another way to remove the tab (without the parent
property)
try to find a way to somehow set the parent property (which cant be
done directly, it throws a compiler error)
That doesn't sound like MVVM to me. We work with data, not UI elements. We work with collections of classes that contain all of the properties required to fulfil some requirement and data bind those properties to the UI controls in DataTemplates. In this way, we add UI controls by adding data items into these collections and let the wonderful WPF templating system take care of the UI.
For example, you have a TabControl that we want to add or remove TabItems from... in a proper MVVM way. First, we need a collection of items that can represent each TabItem:
public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<string>), typeof(TestView));
public ObservableCollection<string> Items
{
get { return (ObservableCollection<string>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
I'm just using a DependencyProperty because I knocked this up in a UserControl and I'm just using a collection of strings for simplicity. You'll need to create a class that contains all of the data required for the whole TabItem content. Next, let's see the TabControl:
<TabControl ItemsSource="{Binding Items}" ItemTemplate="{StaticResource ItemTemplate}" />
We data bind the collection to the TabControl.ItemsSource property and we set the TabControl.ItemTemplate to a Resource named ItemTemplate. Let's see that now:
xmlns:System="clr-namespace:System;assembly=mscorlib"
...
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type System:String}">
<TabItem Header="{Binding}" />
</DataTemplate>
This DataTemplate defines what each item in our collection will look like. For simplicity's sake, our strings are just data bound to the TabItem.Header property. This means that for each item we add into the collection, we'll now get a new TabItem with its Header property set to the value of the string:
Items.Add("Tab 1");
Items.Add("Tab 2");
Items.Add("Tab 3");
Note that I included the System XML Namespace Prefix for completeness, but you won't need that because your DataType will be your own custom class. You'll need more DataTemplates too. For example, if your custom class had a Header property and a Content property, which was another custom class, let's say called Content, that contained all of the properties for the TabItem.Content property, you could do this:
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type YourPrefix:YourClass}">
<TabItem Header="{Binding Header}" Content="{Binding Content}" />
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:Content}">
<YourPrefix:SomeUserControl DataContext="{Binding}" />
</DataTemplate>
So this would give you TabItems with Headers set and Content that comes from SomeUserControl which you could design. You don't need to use UserControls, you could just add more UI controls to either DataTemplate. But you will need to add more controls somewhere... and more classes and properties, always remembering to correctly implement the essential INotifyPropertyChanged interface.
And finally, to answer your question in the proper MVVM way... to remove a TabItem, you simply remove the item that relates to that TabItem from the collection. Simple... or it would have been if you really had been using MVVM like you claim. It's really worth learning MVVM properly as you'll soon see the benefits. I'll leave you to find your own tutorials as there are many to chose from.
UPDATE >>>
Your event handling is still not so MVVM... you don't need to pass a reference of any view model anywhere. The MVVM way is to use commands in the view model. In particular, you should investigate the RelayCommand. I have my own version, but these commands enable us to perform actions from data bound Buttons and other UI controls using methods or inline delegates in the view model (where action and canExecute in this example are the CommandParameter values):
<Button Content="Close Tab" Command="{Binding CloseTabCommand}"
CommandParameter="{Binding}" />
...
public ICommand CloseTabCommand
{
get { return new ActionCommand(action => Items.Remove(action),
canExecute => canExecute != null && Items.Contains(canExecute)); }
}
So whatever view model has your Tabs collection should have an AddTabCommand and a CloseTabCommand that add and remove items from the Tabs collection. But just to be clear, for this to work properly, your ClosableTab class should be a data class and not a UI control class. Use a DataTemplate to specify it if it is a UI control.
You can find out about the RelayCommand from this article on MSDN.

Categories

Resources