How to obtain the panel within a treeview (WPF) - wpf

How can one obtain the panel that is used within a TreeView? I've read that by default TreeView uses a VirtualizingStackPanel for this. When I look at a TreeView template, all I see is <ItemsPresenter />, which seems to hide the details of what panel is used.
Possible solutions:
1) On the treeview instance ("tv"), from code, do this: tv.ItemsPanel.
The problem is, this does not return a panel, but an ItemsPanelTemplate ("gets or sets the template that defines the panel that controls the layout of the items").
2) Make a TreeView template that explicitly replaces <ItemsPresenter /> with your own ItemsControl.ItemsPanel. I am providing a special template anyways, so this is fine in my scenario. Then give a part name to the panel that you place within that template, and from code you can obtain that part (i.e. the panel). The problem with this? see below.
(I am using a control named VirtualTreeView which is derived from TreeView, as is seen below):
<ControlTemplate x:Key="VirtualTreeViewTemplate" TargetType="{x:Type local:VirtualTreeView}">
<Border>
<local:VirtualScrollViewer
Style="{StaticResource VirtualScrollViewer}"
x:Name="PART_VirtualScrollViewer"
CanContentScroll="True">
<!-- instead of: <ItemsPresenter />, use following: -->
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
Name="PART_ItemsStackPanel"
IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</local:VirtualScrollViewer>
</Border>
</ControlTemplate>
[I stripped out all clutter here for visibility...]
The problem with this is: this immediately overrides any TreeView layout mechanism. Actually, you just get a blank screen, even when you have TreeViewItems filling the tree. Well, the reason I want to get a hold of the panel is to take some part in the MeaureOverride, but without going into all of that, I certainly do not want to rewrite the book of how to layout a treeview. I.e., doing this the step #2 way seems to invalidate the point of even using a TreeView in the first place.
Sorry if there is some confusion here, thanks for any help you can offer.

There is a class called VisualTreeHelper that allows you to get non-exposed child controls/elements of a control.
Here is a link to the GetChild method on that class which allows you to enumerate all the children, etc:
http://msdn.microsoft.com/en-us/library/system.windows.media.visualtreehelper.getchild.aspx

The following is a supplement to the answer given by David above. This offers a way to get all of the Visual Children that are UIElements via an extension method.
public static class WPFXtensions
{
static public IEnumerable<UIElement> GetDescendantUIElements(this UIElement uiElement)
{
int childrenCnt = VisualTreeHelper.GetChildrenCount(uiElement);
for (int i = 0; i < childrenCnt; i++)
{
Visual childVisual = VisualTreeHelper.GetChild(uiElement, i) as Visual;
if(childVisual == null)
continue;
if (childVisual is UIElement)
{
yield return childVisual as UIElement;
// note: by recursively calling within the loop, we walk the tree all the way down for each
// UIElement encountered before moving to the next UIElement sibling.
foreach (UIElement e in GetDescendantUIElements(childVisual as UIElement))
yield return e;
}
}
}
}
// Use this then like this to retrieve an expected child StackPanel
StackPanel sp1 = (StackPanel)this.GetDescendantUIElements()
.First(uiElem => uiElem is StackPanel);
// Get all of this UIElement's descendants (for a diagnostic look);
// In a run where this was a TreeView, 78 UIElements returned
List<UIElement> uiElements = this.GetDescendantUIElements().ToList();

Related

Create Silverlight Control that can be used as the layout root

How can I create a control in silverlight that can be used as the layout root, but still have a "Template" property so I can wrap the users content inside another control using a style?
My current implementation is close, it takes the content that the user places in the control and wraps it but the user has to put a grid or panel in if there is multiple controls for the content.
--Update --
This is the code I'm using that will not work as the rootlayout for multiple children unless the user puts a grid around their content. If I inherit from Grid or Panel I get an error about the DefaultStyleKey property not being available.
public class BusyControl :ContentControl
{
public BusyControl()
{
this.DefaultStyleKey = typeof(BusyControl);
}
}
<Style TargetType="local:BusyControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:BusyControl">
<telerik:RadBusyIndicator DisplayAfter="0:0:0.5" IsBusy="{Binding IsBusy}" BusyContent="{Binding BusyMessage}">
<ContentPresenter Content="{TemplateBinding Content}" Margin="{TemplateBinding Padding}"/>
</telerik:RadBusyIndicator>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This is how I want the user to be able to use my new control with out having to wrap their content in a panel or grid.
<cdc:BusyControl x:Name="BusyControl">
<some:Control x:Name="Control1" />
<some:Control x:Name="Control2" />
</cdc:BusyControl>
Seems to me that what you want is to derive your control from an ItemsControl not a ContentControl. In any ControlTemplate that you use you can place the controls using an ItemsPresenter instead of the ContentPresenter you would have used in a ContentControl.
You can have your control inherit from Panel if you want it to be able to have multiple children. You will have to handle laying out the panel's child controls if you do this. See this MSDN article on creating custom panels.
You can then specify your Template and stick the user content into the template.
I think that if you want multiple controls, i.e. children, as content then you have to use some sort of panel, which is the base class for the .Children property.
Not sure if this is applicable to your situation. I had trouble grasping exactly what's going on in your question. Maybe you can make a custom user control that inherits from ContentControl. As you may or may not know, custom user controls need a default style key. With a custom user control you need to define a template in the default style. Now the template can have a ContentControl somewhere inside of it and its content property should be template binding to the contentcontrol.content property. Or you can override the OnContentChanged function and do whatever you want in that override function (like put a single object in the control by itself... or for multiple objects create a new grid/panel and then set the objects as the grid/panels children for the user and then do what ever it is you are doing with the grid/panel. You would have to set/bind the content property on your new control. Make sense?
I don't know about your error with the default style key, and i don't have any telerik controls, but couldn't you just inherit from your telerik busy indicator? Would something like this work for you, (or put you on the right track).
protected override void OnContentChanged( object oldContent, object newContent )
{
//I dont know how you are assigning content,
//but i would say if it's IEnumerable and count is > 1 it should use your panel
var newMultiContent = newContent as System.Collections.IEnumerable;
if ( newMultiContent!=null && newMultiContent.Cast<object>().Count()>1)
{
var myNewContentContainer = new StackPanel();//or grid or whatever
myNewContentContainer.Children.Clear();
//add children
foreach (var item in newMultiContent.OfType<UIElement>())
myNewContentContainer.Children.Add(item);
//instead of the old content that wasn't what you wanted, use the new content container
base.OnContentChanged( oldContent, myNewContentContainer );
//or maybe try this and call the base method at the beginning...
Content = myNewContentContaint
}
else
base.OnContentChanged( oldContent, newContent );
}

How do you navigate a complex Visual Tree in order to re-bind an existing element?

In the above image, child is a ContentPresenter. Its Content is a ViewModel. However, its ContentTemplate is null.
In my XAML, I have a TabControl with the following structure:
<local:SuperTabControlEx DataContext="{Binding WorkSpaceListViewModel}"
x:Name="superTabControl1" CloseButtonVisibility="Visible" TabStyle="OneNote2007" ClipToBounds="False" ContentInnerBorderBrush="Red" FontSize="24" >
<local:SuperTabControlEx.ItemsSource>
<Binding Path="WorkSpaceViewModels" />
</local:SuperTabControlEx.ItemsSource>
<TabControl.Template>
<ControlTemplate
TargetType="TabControl">
<DockPanel>
<TabPanel
DockPanel.Dock="Top"
IsItemsHost="True" />
<Grid
DockPanel.Dock="Bottom"
x:Name="PART_ItemsHolder" />
</DockPanel>
<!-- no content presenter -->
</ControlTemplate>
</TabControl.Template>
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:WorkSpaceViewModel}">
....
WorkSpaceViewModels is an ObservableCollection of WorkSpaceViewModel. This code uses the code and technique from Keeping the WPF Tab Control from destroying its children.
The correct DataTemplate - shown above in the TabControl.Resource - appears to be rendering my ViewModel for two Tabs.
However, my basic question is, how is my view getting hooked up to my WorkSpaceViewModel, yet, the ContentTemplate on the ContentPresenter is null? My requirement is to access a visual component from the ViewModel because a setting for the view is becoming unbound from its property in the ViewModel upon certain user actions, and I need to rebind it.
The DataTemplate is "implicitly" defined. The ContentPresenter will first use it's ContentTemplate/Selector, if any is defined. If not, then it will search for a DataTemplate resource without an explicit x:Key and whose DataType matches the type of it's Content.
This is discussed here and here.
The View Model shouldn't really know about it's associated View. It sounds like there is something wrong with your Bindings, as in general you should not have to "rebind" them. Either way, an attached behavior would be a good way to accomplish that.
I think the full answer to this question entails DrWPF's full series ItemsControl: A to Z. However, I believe the gist lies in where the visual elements get stored when a DataTemplate is "inflated" to display the data item it has been linked to by the framework.
In the section Introduction to Control Templates of "ItemsControl: 'L' is for Lookless", DrWPF explains that "We’ve already learned that a DataTemplate is used to declare the visual representation of a data item that appears within an application’s logical tree. In ‘P’ is for Panel, we learned that an ItemsPanelTemplate is used to declare the items host used within an ItemsControl."
For my issue, I still have not successfully navigated the visual tree in order to get a reference to my splitter item. This is my best attempt so far:
// w1 is a Window
SuperTabControlEx stc = w1.FindName("superTabControl1") as SuperTabControlEx;
//SuperTabItem sti = (SuperTabItem)(stc.ItemContainerGenerator.ContainerFromItem(stc.Items.CurrentItem));
ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(stc);
//ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(sti);
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;
The above code is an attempt to implement the techniques shown on the msdn web site. However, when I apply it to my code, everything looks good, except myDataTemplate comes back null. As you can see, I attempted the same technique on SuperTabControlEx and SuperTabItem, derived from TabControl and TabItem, respectively. As described in my original post, and evident in the XAML snippet, the SuperTabControlEx also implements code from Keeping the WPF Tab Control from destroying its children.
At this point, perhaps more than anything else, I think this is an exercise in navigating the Visual Tree. I am going to modify the title of the question to reflect my new conceptions of the issue.

In WPF how can I restrict the type of children in a Panel?

We want to create a subclass of Canvas that only allows children of a specific type (they need to have intimate knowledge of our subclass and vice-versa.) That said, is there any way to force a panel to only accept children of a certain type (or types)?
M
The solution we came up with was to simply subclass the Canvas, then monitor the children. If one is added that's not of the type we want, we instantly remove it and throw an error. Won't stop compile-time errors but does the trick.
Extending this further I was thinking about also subclassing the canvas, then Newing over the Children property to return our own collection which we've internally synced to the panel's children via binding. That way we can also have compile-time support. Granted if someone casts our subclass to a straight canvas, then obviously the 'new'd Children property won't be accessed (its a 'new' not an override) but the aforementioned collection monitoring will still give us what we want.
It would have been nice if the WPF team had come up with a generic canvas so we could do something like canvas but that obviously wouldn't work in XAML unless they somehow came up with syntax for that. Then again, a canvas is pretty damn basic so maybe we'll just roll our own geeneric version where we could do something like this...
public class TypedCanvas<t> : PanelBase
{
// Implementation here
}
public class FooCanvas : TypedCanvas<Foo>{}
public class LaaCanvas : TypedCanvas<Laa>{}
...of which we could then use FooCanvas and LaaCanvas via XAML while still getting all the benefits of using generics.
Even better, make it TypedPanelBase so we could use it with any other custom panel as the base type.
Actually, now that I've typed this... I think I'm about to go re-write our canvas to try this approach! (Either way, I now have a solution which is what we were after.)
Actually... no way.
Besides, I don't understand your goals. If you need to work with some specific containers just cast Panel.InternalChildren:
this.InternalChildren.OfType<MyType>().Do(...);
Consider about scenario: you have a collection of strings, which is the source for ItemsControl. In DataTemplate we have button which content is binded to item from mentioned collection. And ItemsControl.ItemsPanel is Canvas.
public IEnumerable<string> Items
{
get;
}
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
So, what items types do you want to restrict? Buttons or strings?
The problem in this scenario is that ContentPresenters will be effective visual children of Canvas. But in overriden method OnVisualChildrenChanged (where you could try to check item type) Content and ContentTemplate properties are set to null due to deferred binding.
So the one acceptable solution I can propose is creating your own ItemsControl, which returns some concrete container instead of ContentPresenter:
public class MyItemsControl : ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new Button();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is Button;
}
}
<self:MyItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<self:MyPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</self:MyItemsControl>
With this approach, you guarantee that your item containers (Panel.InternalChilder) are buttons (or something) and in MyPanel you could safely cast:
this.InternalChildren.Cast<Button>()

WPF ListBox with a ListBox - UI Virtualization and Scrolling

My prototype displays "documents" that contain "pages" that are
represented by thumbnail images. Each document can have
any number of pages. For example, there might be
1000 documents with 5 pages each, or 5 documents with 1000 pages
each, or somewhere inbetween. Documents do not contain other documents.
In my xaml markup I have a ListBox, whose ItemsTemplate
references an innerItemsTemplate that also has a ListBox. I want the
2 levels of selected items so that I can perform various operations
on documents or pages (delete, merge, move to new location, etc).
The innerItemsTemplate ListBox uses a WrapPanel as the ItemsPanelTemplate.
For the scenario where I have a large number of documents with a few
pages each (say, 10000 documents with 5 pages each), the scrolling
works great thanks to the UI Virtualization by the VirtualizingStackPanel.
However, I have problems if I have a large number of pages. A document
with 1000 pages will only display about 50 at a time (whatever fits on the screen), and when I scroll down, the outer ListBox moves to the next document, skipping the 950
pages or so that were not visible. Along with that, there is no
VirtualzingWrapPanel so the app memory really increases.
I'm wondering if I am going about this the right way, especially
since it is sort of difficult to explain! I would like to be able to display
10000 documents with 1000 pages each (only showing whatever fits on the screen),
using UI Virtualization, and also smooth scrolling.
How can I make sure the scrolling moves through all of the pages in document
before it displays the next document, and still keep UI virtualization?
The scrollbar seems to only move to the next document.
Does it seem logical to represent "documents" and "pages" -
with my current method of using a ListBox within a ListBox?
I would very much appreciate any ideas you have.
Thank You.
It is possible to achieve smooth scrolling VirtualizingStackPanels in WPF 4.0 without sacrificing virtualization if you're prepared to use reflection to access private functionality of the VirtualizingStackPanel. All you have to do is set the private IsPixelBased property of the VirtualizingStackPanel to true.
Note that in .Net 4.5 there's no need for this hack as you can set VirtualizingPanel.ScrollUnit="Pixel".
To make it really easy, here's some code:
public static class PixelBasedScrollingBehavior
{
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(PixelBasedScrollingBehavior), new UIPropertyMetadata(false, HandleIsEnabledChanged));
private static void HandleIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var vsp = d as VirtualizingStackPanel;
if (vsp == null)
{
return;
}
var property = typeof(VirtualizingStackPanel).GetProperty("IsPixelBased",
BindingFlags.NonPublic | BindingFlags.Instance);
if (property == null)
{
throw new InvalidOperationException("Pixel-based scrolling behaviour hack no longer works!");
}
if ((bool)e.NewValue == true)
{
property.SetValue(vsp, true, new object[0]);
}
else
{
property.SetValue(vsp, false, new object[0]);
}
}
}
To use this on a ListBox, for example, you would do:
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel PixelBasedScrollingBehavior.IsEnabled="True">
</VirtualizingStackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
The answer here is surprising:
If you use ItemsControl or ListBox you will get the behavior you are experiencing, where the control scrolls "by item" so you jump over a whole document at once, BUT
If you use TreeView instead, the control will scroll smoothly so you can scroll through your document and into the next one, but it will still be able to virtualize.
I think the reason the WPF team chose this behavior is that TreeViewcommonly has items that are larger than the visible area, whereas typically ListBoxes don't.
In any case, it is trivial in WPF to make a TreeView look and act like a ListBox or ItemsControl by simply modifying the ItemContainerStyle. This is very straightforward. You can roll your own or just copy over the appropriate template from the system theme file.
So you will have something like this:
<TreeView ItemsSource="{Binding documents}">
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<ContentPresenter /> <!-- put your desired container style here with a ContentPresenter inside -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<DataTemplate TargetType="{x:Type my:Document}">
<Border BorderThickness="2"> <!-- your document frame will be more complicated than this -->
<ItemsControl ItemsSource="{Binding pages}">
...
</ItemsControl>
</Border>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Getting pixel-based scrolling and ListBox-style multiselect to work together
If you use this technique to get pixel-based scrolling, your outer ItemsControl which shows the documents cannot be a ListBox (because ListBox is not a subclass of TreeView or TreeViewItem). Thus you lose all of ListBox's multiselect support. As far as I can tell, there is no way to use these two features together without including some of your own code for one feature or the other.
If you need both sets of functionality in the same control, you have basically several options:
Implement multi-selection yourself in a subclass of TreeViewItem. Use TreeViewItem instead of TreeView for the outer control, since it allows multiple children to be selected. In the template inside ItemsContainerStyle: Add a CheckBox around the ContentPresenter, template bind the CheckBox to IsSelected, and style the CheckBox with control template to get the look you want. Then add your own mouse event handlers to handle Ctrl-Click and Shift-Click for multiselect.
Implement pixel-scrolled virtualization yourself in a subclass of VirtualizingPanel. This is relatively simple, since most of VirtualizingStackPanel's complexity is related to non-pixel scrolling and container recycling. Dan Crevier's Blog has some useful infromation for understanding VirtualizingPanel.
.NET 4.5 now has the VirtualizingPanel.ScrollUnit="ScrollUnit" property. I just converted one of my TreeViews to a ListBox and the performance was noticeably better.
More information here: http://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingpanel.scrollunit(v=vs.110).aspx
This worked for me. Seems a couple of simple attributes will do it (.NET 4.5)
<ListBox
ItemsSource="{Binding MyItems}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.ScrollUnit="Pixel"/>
Please allow me to preface this answer with a question: Does the user have to see each and every thumbnail within every item in the list at all times?
If the answer to that question is 'no', then perhaps it would be feasible to limit the number of visible pages within the inner item template (given that you have indicated the scrolling works well with, say, 5 pages) and use a separate 'selected item' template that is larger and displays all pages for that document? Billy Hollis explains how to 'pop' a selected item out in a listbox on dnrtv episode 115

Create TabItems with differing content at runtime based on templates in WPF

I'm writing an application with WPF and part of it involves managing for the user various files which are used configure custom, in-house devices. I need to be able to manipulate different types of configurations in tabs in the same TabControl, meaning that the content of the TabItems must be dynamically generated. I'd like to do this with ControlTemplates, but I haven't been successful in getting a working template yet. I have a ControlTemplate called "pendantConfigurationTabItemTemplate" defined in my Window resources, and I use the following code to apply the template (which contains a named item I need to access) to the TabItems and add them to their parent TabControl :
<ControlTemplate x:Key="pendantConfigurationTabItemTemplate" TargetType="TabItem">
<StackPanel Orientation="Vertical">
<my:PendantConfigurationFileEditor x:Name="configurationEditor"/>
<StackPanel Style="{StaticResource defaultOkCancelButtonsContainerStyle}">
<Button Style="{StaticResource defaultOkCancelButtonStyle}"/>
<Button Style="{StaticResource defaultOkCancelButtonStyle}" Click="OkButton_Click"/>
</StackPanel>
</StackPanel>
</ControlTemplate>
Code behind :
TabItem ConfigTab = new TabItem();
switch (ConfigFile.Device)
{
case DeviceType.PENDANT:
{
ControlTemplate TabTemplate = Resources["pendantConfigurationTabItemTemplate"] as ControlTemplate;
ConfigTab.Template = TabTemplate;
ConfigTab.ApplyTemplate();
object Editor = TabTemplate.FindName("configurationEditor", ConfigTab);
PendantConfigurationFileEditor ConfigFileEditor = Editor as PendantConfigurationFileEditor;
ConfigFileEditor.PendantConfiguration = DeviceConfig;
break;
}
default:
/* snipped */
return;
}
ConfigTab.Header = ConfigFile.ConfigurationName;
this.EditorTabs.Items.Add(ConfigTab);
this.EditorTabs.SelectedIndex = this.EditorTabs.Items.Count - 1;
However, whenever I run the program, no tabs get added to the tab control, instead the tab control (seemingly) gets replaced or covered by the content of the template. Can somebody please help me out with this ?
Effectively, what I want to do is use the WPF templates as TabItem factories
TabControl.ItemsSource plus DataTemplates is effectively the "templates as factories" solution you are asking for, but it demands a slightly different approach to your current one.
Rather than writing procedural code to create and template TabItems and calling Items.Add, use the ItemsSource property and data binding. This will cause WPF to create a TabItem for each object in the ItemsSource. You can then use ContentTemplateSelector to select appropriate templates for the object displayed on this tab, according to whatever criteria are appropriate (e.g. the Device property) -- though in this case you will be using DataTemplates rather than ControlTemplates.
Your selector will look something like this:
public class DeviceTypeSelector : DataTemplateSelector
{
public DataTemplate PendantTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override SelectTemplate(object item, DependencyObject container)
{
ConfigFile cf = (ConfigFile)item;
switch (cf.Device)
{
case DeviceType.Pendant: return PendantTemplate;
default: return DefaultTemplate;
}
}
}
and will be instantiated in XAML like this:
<local:DeviceTypeSelector x:Key="dts"
PendantTemplate="{StaticResource pt}"
DefaultTemplate="{StaticResource dt}" />
(where pt and dt are suitable DataTemplates defined elsewhere in the resources).
Finally, your TabControl will look like this:
<TabControl Name="EditorTabs"
ContentTemplateSelector="{StaticResource dts}" />
and you set it up as EditorTabs.ItemsSource = myConfigFiles; (or better still let it acquire the ItemsSource in XAML from the DataContext).
You'll also want to set up the headers of the TabItems: to do this, use TabControl.ItemContainerStyle, with a Setter for the Header property. I think this would look something like this:
<TabControl ...>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding ConfigurationName}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
(You can also inline the ContentTemplateSelector, by the way: I broke it out into a resource mostly so as to show things in smaller chunks.)

Resources