Why does ItemContainerGenerator return null? - wpf

I have a ListBox, and I need to set its ControlTemplate to a Virtualizing WrapPanel which is a class that extends VirtualizingPanel, using a style that looks like this:
<Style TargetType="{x:Type ListBox}" x:Key="PhotoListBoxStyle">
<Setter Property="Foreground" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}" >
<s:VirtualizingVerticalWrapPanel>
</s:VirtualizingVerticalWrapPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, in the private method of Virtualizing WrapPanel below I try to access this.ItemContainerGenerator, but I get null value, any idea what's the problem ??
private void RealizeFirstItem()
{
IItemContainerGenerator generator = this.ItemContainerGenerator;
GeneratorPosition pos = generator.GeneratorPositionFromIndex(0);
using (generator.StartAt(pos, GeneratorDirection.Forward))
{
UIElement element = generator.GenerateNext() as UIElement;
generator.PrepareItemContainer(element);
this.AddInternalChild(element);
}
}

I think I had a similar problem and this helped:
var necessaryChidrenTouch = this.Children;
IItemContainerGenerator generator = this.ItemContainerGenerator;
... for some reason you have to "touch" the children collection in order for the ItemContainerGenerator to initialize properly.

For Windows 8.1 Metro apps, the ItemContainerGenerator was depricated and will return null. New Apis:
ItemsControl.ItemContainerGenerator.ItemFromContainer = ItemsControl.ItemFromContainer
ItemsControl.ItemContainerGenerator.ContainerFromItem = ItemsControl.ContainerFromItem
ItemsControl.ItemContainerGenerator.IndexFromContainer = ItemsControl.IndexFromContainer
ItemsControl.ItemContainerGenerator.ContainerFromIndex = ItemsControl.ContainerFromIndex
http://msdn.microsoft.com/en-us/library/windows/apps/dn376326.aspx

Falck is mostly correct. Actually, you need to reference the 'InternalChildren' of the virtualized stack panel. The decompiled code for this property is:
protected internal UIElementCollection InternalChildren
{
get
{
this.VerifyBoundState();
if (this.IsItemsHost)
{
this.EnsureGenerator();
}
else if (this._uiElementCollection == null)
{
this.EnsureEmptyChildren(this);
}
return this._uiElementCollection;
}
}
The 'EnsureGenerator' does the work of making sure that a generator is available. Very poor 'just in time' design, IMO.

Most probably this is a virtualization-related issue so ListBoxItem containers get generated only for currently visible items (e.g. https://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel(v=vs.110).aspx#Anchor_9)
I'd suggest switching to ListView instead of ListBox - it inherits from ListBoxand it supports ScrollIntoView() method which you can utilize to control virtualization;
targetListView.ScrollIntoView(itemVM);
DoEvents();
ListViewItem itemContainer = targetListView.ItemContainerGenerator.ContainerFromItem(itemVM) as ListViewItem;
(the example above also utilizes the DoEvents() static method explained in more detail here; WPF how to wait for binding update to occur before processing more code?)
There are a few other minor differences between the ListBox and ListView controls (What is The difference between ListBox and ListView) - which should not essentially affect your use case.

This is because you changed the Template of the Listbox, while u should have just changed the ItemsPanel:
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<s:VirtualizingVerticalWrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>

Related

The MVVM way of presenting controls dynamically

I have a need to display some kind animation as different processes gets started. My initial idea was to simply add some <ContentControl> tags to the XAML and bind them to a property in the View Model object which then simply assigned this property a ProgressBar, some busy spinner or whatever.
This works but I don't like it. The primary reason I don't like it is because the View Model should not involve itself in presentation matters and this pattern clearly breaks that paradigm.
This is pretty much what my (ugly) code looks like atm:
XAML:
<ContentControl Content="{Binding ProcessAAnimation}" />
In View Model class:
public object ProcessAAnimation
{
get { return _processAAnimation; }
private set
{
_processAAnimation = value;
OnPropertyChanged("ProcessAAnimation");
}
}
public object IsProcessARunning
{
get { return _processARunning; }
private set
{
if (value == _processARunning)
return;
_processRunnings = value;
if (value)
ProcessAAnimation = SomeNiftyAnimationControl();
else
{
if (ProcessAAnimation is IDisposable)
((IDisposable)ProcessAAnimation).Dispose();
ProcessAAnimation = null;
}
}
}
// (clipped: More properties for "Process B", "Process C" and so on)
So, is there a better pattern to achieve this. Preferrably, a pattern where I can create my animation controls dynamically using XAML alone?
Please note that I have already tested a solution where I declare three different animation controls and then bind their Visibility property to the View Model state. That, however, is below par in my book because I don't want to just hide the controls, I want them to be gone unless needed. Besides, that would also make it impossible to dynamically use different types of animations for whatever needs may be.
Anyone?
Well your ViewModel knows about the operation and the progress itself. The rest can be accomplished via Triggers. At least thats the way we do it. So your ViewModel has a property "IsLoadingImage" for example, which is set when your viewmodel starts a BackgroundWorker for loading a big image, it also returns the progress reported by the BackgroundWorker "ImageLoadingProgress" now these two properties are enough to pass to your View. Your view, consists of a Progress bar or a custom control for your special animation. You could now bind the "IsLoadingImage" in a Trigger to toggle the ProgressBar/Animation control visibility and the Value of these is bound to "ImageLoadingProgress".
Like i said, thats how we handle it, and our application makes heavy use of MVVM.
Edit respond to a comment: How to change the template in a trigger
<ControlTemplate x:Name="ActiveTemplate" TargetType="{x:Type MyType}">
<!-- Template when active -->
</ControlTemplate>
<ControlTemplate x:Name="DeactivatedTemplate" TargetType="{x:Type MyType}">
<!-- Template when deactivated -->
</ControlTemplate>
<Style TargetType="{x:Type MyType}">
<Setter Property="Template" Value="{StaticResource DeactivatedTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Template" Value="{StaticResource ActiveTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
This assumes that MyType is a control that can has a ControlTemplate and that the DataContext has a Property IsActive to toggle the Template.

Silverlight Defaulting ContentPresenter Content

Why won't this work?
In generic.xaml for a custom control:
In the style applied to the custom control...
<Setter Property="ChromeContent">
<Setter.Value>
<Grid />
</Setter.Value>
</Setter>
...
Later, in the control template...
<ContentPresenter Grid.Column="0"
x:Name="ChromeContentPresenter"
Content="{TemplateBinding ChromeContent}" />
Here's the dependency property for ChromeContent...
public Object ChromeContent
{
get { return (Object)GetValue(ChromeContentProperty); }
set { SetValue(ChromeContentProperty, value); }
}
public static readonly DependencyProperty ChromeContentProperty =
DependencyProperty.Register("ChromeContent", typeof(Object),
typeof(casPopup), null);
As you can see, it takes any object. I tried changing it to a Grid, but that did not help.
It throws this error (from javascript): _Failed to assign to property 'System.Windows.Controls.ContentPresenter.Content'
Oddly, the following will work fine if I remove the Grid from the setter nd just use text:
<Setter Property="ChromeContent" Value="DEFAULT" />
Also, this will work too from the OnApplyTemplate method in the control class:
Grid g = new Grid();
g.Width = 100;
g.Height = 25;
g.Background = new SolidColorBrush(Colors.LightGray);
ChromeContent = g;
I'm having a hard time understanding what is preventing the default content of a grid, defined in the generic.xaml from working. Does anyone have any knowledge on this matter?
Many thanks in advance for your help!
This is the problem:-
<Setter Property="ChromeContent">
<Setter.Value>
<Grid />
</Setter.Value>
</Setter>
You should not include a UIElement directly in a resource dictionary or as a value of a style. You might see the style as being some kind of descriptor but it isn't. The values in a style are constructed instances of the objects they hold. Your style holds a single instance of Grid. Whenever that style is used to assign to a ChromeContent property it will attempt to assing the same single instance of the Grid.
A UIElement can only be a child of one parent. What would happen if two instances your control were constructed? There would (if silverlight let you) be an attempt to assign the same single instance of the Grid to both controls.
This is one reason for templates such as ControlTemplate and DataTemplate. The markup inside these is invoked each time the template is used rather than when the Xaml is first parsed.
Edit:
To answer you supplementary question, you should default another property of type DataTemplate:-
<Setter Property="ChromeContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid />
</DataTemplate>
</Setter.Value>
</Setter>
Property:-
public Object ChromeContentTemplate
{
get { return (DataTemplate)GetValue(ChromeContentTemplateProperty); }
set { SetValue(ChromeContentTemplateProperty, value); }
}
public static readonly DependencyProperty ChromeContentTemplateProperty=
DependencyProperty.Register("ChromeContentTemplate", typeof(DataTemplate),
typeof(casPopup), null);
Control Template:-
<ContentPresenter Grid.Column="0"
x:Name="ChromeContentPresenter"
Content="{TemplateBinding ChromeContent}"
ContentTemplate="{TemplateBinding ChromeContentTemplate" />

WPF style depending on checkbox state

I am creating a settings editor where plugin writers can define their own user interface for configuring their plugins. I am implementing a feature to hide certain "advanced" elements if a checkbox is unchecked.
The checkbox XAML is trivial:
<CheckBox Name="isAdvanced">_Advanced</CheckBox>
Ideally (more on this later), implementors would just add a flag to advanced controls (which should be hidden when the "advanced" checkbox is unchecked) like so:
<Button library:MyLibraryControl.IsAdvanced="True">My Button</Button>
The problem lies in making the magic of hiding the IsAdvanced="True" elements when isAdvanced.IsChecked == false. I have the desired behaviour with this style on the window element:
<Window.Resources>
<Style TargetType="Button">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding (library:MyLibraryControl.IsAdvanced), RelativeSource={RelativeSource Mode=Self}}" Value="True" />
<Condition Binding="{Binding IsChecked, ElementName=isAdvanced}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="UIElement.Visibility" Value="Collapsed" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
However, this method presents two problems:
It only adds functionality to buttons and nothing else. The IsAdvanced flag can (should be able to) be added to any visual element.
It replaces/overrides the styles which would otherwise be on the button.
Is there some other way to produce the functionality I want? I'm not afraid of working in the code-behind, but an elegant XAML solution is ideal (as this is purely a UI change, aside from saving the state of the checkbox in the user's preferences).
Some other methods of signifying advanced elements have come to mind. These include using a dynamic resource and directly binding:
<Button Visibility="{DynamicResource IsAdvancedVisibility}">My Button</Button>
<Button Visibility="{Binding IsChecked, RelativeSource={...}, ValueConverter={...}}">My Button</Button>
Using a resource dictionary would probably work, but it seems like a really bad solution as UI state doesn't seem like it should belong in a dictionary. Binding manually is quite the mess because the state of the checkbox has to be sent somehow to the element, and aside from hardcoding values I don't see it not becoming a mess.
Both of these alternate solutions tie semantics ("this is an advanced option") to appearance ("advanced options should be collapsed"). Coming from the HTML world, I know this is a very bad thing, and I refuse to submit to these methods unless absolutely necessary.
How about moving this into the ViewModel instead of XAML because this looks like behavior to me.
The behavior you want seems to me - each plugin registers a bunch of properties (mapping to UI Controls) as advanced. There is a global setting to turn on/off advanced properties. When this happens, update all plugins to show/hide their advanced properties
Have plugin writers implement an interface containing a set only property AreAdvancedControlsVisible. Let them take care of hiding/showing the controls in their UI via property change handler. The advanced UI controls can bind to a ShowAdvancedControls flag on the pluginVM, which is toggled on/off from the prop changed handler.
The framework can just loop over the available plugins and set this flag whenever the ShowAdvanced checkbox is set.
There are probably alot of better ways to solve this problem but I tried to work past the two issues you had with your solution. Small sample project with this can be downloaded here.
1.It only adds functionality to buttons and nothing else. The
IsAdvanced flag can (should be able
to) be added to any visual element.
Adding an Attached Property, that make all children inherit the value, to the top-most container could fix this.
2.It replaces/overrides the styles which would otherwise be on the
button.
Bea Stollnitz has a nice blog article about merging Styles here.
It has an extension method for Style called Merge which could be used.
Sounded pretty straight forward but the following problems made the code more complex.
1. The Visual elements doesn't have a style when the Attached Property is inherited. Required Loaded event.
2. A Style can't be modified when it is in use. Required a copy method for the Style.
So, we want this Style to be merged with the active Style for all children in the parent container.
<Style x:Key="IsAdvancedStyle">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding (library:MyLibraryControl.IsAdvanced), RelativeSource={RelativeSource Mode=Self}}" Value="True" />
<Condition Binding="{Binding IsChecked, ElementName=isAdvanced}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="Control.Visibility" Value="Collapsed" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
If the root container is a StackPanel we then add this. The style IsAdvancedStyle will then be inherited by all the children and merged with the active Style.
<StackPanel local:StyleChildsBehavior.StyleChilds="{StaticResource IsAdvancedStyle}">
StyleChildsBehavior.cs
public class StyleChildsBehavior
{
public static readonly DependencyProperty StyleChildsProperty =
DependencyProperty.RegisterAttached("StyleChilds",
typeof(Style),
typeof(StyleChildsBehavior),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.Inherits,
StyleChildsCallback));
public static void SetStyleChilds(DependencyObject element, Style value)
{
element.SetValue(StyleChildsProperty, value);
}
public static Style GetStyleChilds(DependencyObject element)
{
return (Style)element.GetValue(StyleChildsProperty);
}
private static void StyleChildsCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (DesignerProperties.GetIsInDesignMode(d) == true)
{
return;
}
Style isAdvancedStyle = e.NewValue as Style;
if (isAdvancedStyle != null)
{
FrameworkElement element = d as FrameworkElement;
if (element != null)
{
if (element.IsLoaded == false)
{
RoutedEventHandler loadedEventHandler = null;
loadedEventHandler = new RoutedEventHandler(delegate
{
element.Loaded -= loadedEventHandler;
MergeStyles(element, isAdvancedStyle);
});
element.Loaded += loadedEventHandler;
}
else
{
MergeStyles(element, isAdvancedStyle);
}
}
}
}
private static void MergeStyles(FrameworkElement element, Style isAdvancedStyle)
{
if (element != null)
{
Style advancedStyle = GetStyleCopy(isAdvancedStyle);
advancedStyle.Merge(element.Style);
element.Style = advancedStyle;
}
}
private static Style GetStyleCopy(Style style)
{
string savedStyle = XamlWriter.Save(style);
using (MemoryStream memoryStream = new MemoryStream(Encoding.ASCII.GetBytes(savedStyle)))
{
ParserContext parserContext = new ParserContext();
parserContext.XmlnsDictionary.Add("library", "clr-namespace:HideAll;assembly=HideAll");
return XamlReader.Load(memoryStream, parserContext) as Style;
}
}
}
After this the IsAdvancedStyle will be merged in all children of the StackPanel and this goes for children that are added in run-time as well.
Modified Merge extension method from the blog link.
public static void Merge(this Style style1, Style style2)
{
if (style1 == null || style2 == null)
{
return;
}
if (style1.TargetType.IsAssignableFrom(style2.TargetType))
{
style1.TargetType = style2.TargetType;
}
if (style2.BasedOn != null)
{
Merge(style1, style2.BasedOn);
}
foreach (SetterBase currentSetter in style2.Setters)
{
style1.Setters.Add(currentSetter);
}
foreach (TriggerBase currentTrigger in style2.Triggers)
{
style1.Triggers.Add(currentTrigger);
}
}
I decided to invert the problem a little bit, and it worked well.
Instead of dealing with styles, I used property binding as suggested by Gishu. However, instead of placing the UI in the VM (where properties would propagate several layers manually), I used an attached property named ShowAdvanced which propagates down via property inheritance.
Creating this property is trivial:
public static readonly DependencyProperty ShowAdvancedProperty;
ShowAdvancedProperty = DependencyProperty.RegisterAttached(
"ShowAdvanced",
typeof(bool),
typeof(MyLibraryControl),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.OverridesInheritanceBehavior
)
);
The checkbox sets the ShowAdvanced property above on the entire window. It could set it elsewhere (e.g. on the grid), but putting it on the window makes more sense IMO:
<CheckBox Grid.Column="0"
IsChecked="{Binding (library:MyLibraryControl.ShowAdvanced), ElementName=settingsWindow}"
Content="_Advanced" />
Changing the visibility (or whatever other properties desired) depending on the ShowAdvanced property becomes easy:
<Foo.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Foo.Resources>
<Button Visibility="{Binding (library:MyLibraryControl.ShowAdvanced), RelativeSource={RelativeSource Self}, Converter={StaticResource BooleanToVisibilityConverter}}">I'm Advanced</Button>
Ditching styles allows plugin writers to completely change the layout of their controls if they need to. They can also show advanced controls but keep them disabled if desired. Styles brought up a lot of problems and, as Meleak showed, the workarounds were messy.
My main problem with putting the 'advanced' display logic in the VM is that it is now less likely you can get away with binding multiple views to the same VM while maintaining the flexibility desired. If the 'advanced' logic is in the VM, advanced controls must be shown for all views or no views; you can't show them for one and hide them for another. This, IMO, breaks the principles of having a VM in the first place.
(Thanks to all who posted here; it's been helpful!)

Find Control Inside ListBox?

<Style TargetType="ListBoxItem" x:Key="ListBoxItemTemplate">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Button Content="{TemplateBinding Content}"></Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ListBox ItemsSource="{Binding S}"
x:Name="listBox"
ItemContainerStyle="{StaticResource ListBoxItemTemplate}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid x:Name="grid" Columns="5"></UniformGrid>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
I want to Find "grid" from ListBox Control.Please Help Me,Thank you.
A couple of things to add to Meleak's answer (and this was a bit too long to put in a comment.)
Normally, the way you obtain a named element from a template in WPF is to call the template's FindName method. However, because templates are basically factories, you also needs to say which particular instance of the template you require - a single ItemsPanelTemplate may have been instantiated several times over. So you'd need something like this:
var grid = (UniformGrid) listBox.ItemsPanel.FindName("grid", ???);
But what goes in that ??? placeholder? It's not the ListBox itself - the ListBox doesn't actually use that ItemsPanel directly. Ultimately, the it's used by the ItemsPresenter in the ListBox's template. So you'd need to do this:
var grid = (UniformGrid) listBox.ItemsPanel.FindName("grid", myItemsPresenter);
...except, there's no reliable way to get hold of the ItemsPresenter either. In fact, there might not even be one - it's legal to create a template for a ListBox that just provides the hosting panel directly - there's even a special property, Panel.IsItemsHost, for this very purpose.
And that leads onto the second point I wanted to add. In scenarios where the ListBox's template doesn't use the ItemsPresenter, the ItemsPanel will go unused. So it's actually possible that the UniformGrid you're trying to get hold of doesn't even exist.
One way to do it is to store it in code behind once it is Loaded.
<ListBox ItemsSource="{Binding S}"
x:Name="listBox"
ItemContainerStyle="{StaticResource ListBoxItemTemplate}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid x:Name="grid" Columns="5" Loaded="grid_Loaded"></UniformGrid>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
And in code behind
private UniformGrid m_uniformGrid = null;
private void grid_Loaded(object sender, RoutedEventArgs e)
{
m_uniformGrid = sender as UniformGrid;
}
If you want to find it from the ListBox then you can use the Visual Tree.
UniformGrid uniformGrid = GetVisualChild<UniformGrid>(listBox);
public static T GetVisualChild<T>(object parent) where T : Visual
{
DependencyObject dependencyObject = parent as DependencyObject;
return InternalGetVisualChild<T>(dependencyObject);
}
private static T InternalGetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}

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