Binding for TabItem's content controls - wpf

I have a TabControl with ItemsSource set to ObservableCollection<BookTab> and using ContentTemplateSelector to create different tabs.
class BookTab
{
public string Name { get; set; }
public string Type { get; set; }
public object Data { get; set; }
}
<TabControl Name="tabControl"
ContentTemplateSelector="{StaticResource tabTemplateSelector}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Content" Value="{Binding}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
Type in BookTab determines DataTemplate used in the appropriate tab, Name is displayed on the tab header, and Data supposed to be displayed in tab's content, i.e. DataGrid.
Data is set to ObservableCollections of different types.
DataTemplate may look like this:
<DataTemplate x:Key="bookTabTemplate">
<TabItem Name="bookTab">
<Grid>
<DataGrid Name="bookGrid">
...
</DataGrid>
</Grid>
</TabItem>
</DataTemplate>
I tried different ways to bind Data property to DataGrid's ItemsSource, but all I got is grid displaying word "Book" (BookTab's Name property value).
My guess is I have to somehow propagate TabControl's binding down to DataGrid, but I cannot figure it out.

I would do it like this:
<TabControl SelectedItem="{Binding CurrentBook}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding BookList}">
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<ContentControl Content="{Binding Data}"
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
... and later you define in your app.xaml how the content of your data is presented...
<DataTemplate DataType="{x:Type viewmodel:bookviewmodel1}">
<view:bookview1/>
</DataTemplate>
All you have to do, is creating a view (usercontrol) for each type.
HTH

your datagrid data context is probably the BookTab (you can confirm this using snoop )
If this is correct, all you have to do is bind the datagrid itemssource to the BookTab Data property
<DataGrid Name="bookGrid" ItemsSource="{Binding Path=Data}" />
This should do the trick

Related

Control template for ListViewItem - how to access property of item

Solved:
I was an idiot and trusted the editors information that the DataContext is wrong. The solution is simply
<TextBlock Text="{Binding A}" />
I added a TextBlock beneath each displayed Item of a ListView. For this I used a ControlTemplate with the target type set to "ListViewItem". I put the GridViewRowPresenter and the TextBlock into a StackPanel.
<ListView ItemsSource="{Binding Items}">
<ListView.Resources>
<ControlTemplate x:Key="CustomListViewItemTemplate" TargetType='{x:Type ListViewItem}'>
<StackPanel>
<GridViewRowPresenter Content="{TemplateBinding Content}"
Columns="{TemplateBinding GridView.ColumnCollection}"/>
<TextBlock Text="{Binding }" /> <!-- here I fail -->
</StackPanel>
</ControlTemplate>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template" Value="{StaticResource CustomListViewItemTemplate}"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
...
</GridView>
</ListView.View>
</ListView>
The ItemsSource of the ListView is a ObservableCollection Items = new ObservableCollection<Item>();
with Item as
public class Item
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
I can access the ListViewItem in the ControlTemplate, but not the Item itself. Is there a possibility bind the TextBlock in the ControlTemplate to e.g. the Property "A" of each instance of Item?
With <ListView ItemsSource="{Binding Items}"> you specify which collection (In this case Items) provides data for the ListView. Within the template, if you assign the value "{Binding}", you effectively assign an Item from your collection you specified as ItemsSource.
To assign a binding to a property of your Item, you already found the solution
<TextBox Text="{Binding A}"/>
Additionally to your current solution,
<TextBlock Text="{Binding Path=A, Mode=TwoWay}"/>
above syntax allows you to navigate to nested members, and set additional binding related parameters like Mode, UpdateSourceTrigger, Converter etc.

Is it possible to have a DataTemplate around another DataTemplate?

Hello everyone and welcome to yet another nested DataTemplate question!
In this one, I'd like to have a DataTemplate like this, written on a ResourceDictionary:
<DataTemplate x:Key="Vector3Template">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="X" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding X}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="Y" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding Y}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="Z" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding Z}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
Being surrounded by a DataTemplate with a border, like the following, also written on a ResourceDictionary (in the future it's gonna have a couple more elements to it):
<DataTemplate x:Key="ComponentTemplate">
<Border Margin="5" BorderThickness="2" BorderBrush="Gray"/>
</DataTemplate>
Why would I want this, you ask? Well, I'm trying to display an ObservableCollection of IComponent named _components and I want all instances to share the same Borders, but with its core being specific to every class type that inherits from IComponent.
In order to display the list with its differents type, I'm using the following code on a UserControl:
<Grid x:Name="LayoutRoot" Background="White">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<StackPanel>
<ListView x:Name="_componentsList"
ItemsSource="{Binding Components}"
HorizontalContentAlignment="Stretch">
<ListView.Resources>
<DataTemplate DataType="{x:Type models:Transform}">
<ContentControl Content="{StaticResource ComponentTemplate}" ContentTemplate="{StaticResource TransformTemplate}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type models:Vector3}">
<ContentPresenter ContentTemplate="{StaticResource Vector3Template}"/>
</DataTemplate>
</ListView.Resources>
</ListView>
</StackPanel>
</ScrollViewer>
Trying to build this system with Prism 6.3 and with almost no code-behind, every c# code that I have is just for models, so no real logic here so far.
Is this possible? How so? I've started playing with WPF a few days ago and still have a lot to learn.
I believe what you're looking for is to simply use a DataTemplateSelector in which the DataTemplate used is determined by the data. You can find a full tutorial here. Once you've setup your DataTemplateSelector you simply would pass it in as the DataTemplate to your control.
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultnDataTemplate { get; set; }
public DataTemplate BooleanDataTemplate { get; set; }
public DataTemplate EnumDataTemplate { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
DependencyPropertyInfo dpi = item as DependencyPropertyInfo;
if (dpi.PropertyType == typeof(bool))
{
return BooleanDataTemplate;
}
if (dpi.PropertyType.IsEnum)
{
return EnumDataTemplate;
}
return DefaultnDataTemplate;
}
}

Custom Control for a ComboBox with Default Element

What I want:
I'm trying to create a ComboBox which has a 'Default' element seperated from the rest of the items.
What I have:
So by following the countless threads about grouping the items and overwritting the ItemTemplate I came to this result:
.CS
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ObservableCollection<GroupItem>()
{
new GroupItem() { Name = "Default Item", GroupName = "Default"},
new GroupItem() { Name = "Item 1", GroupName = "Item"},
new GroupItem() { Name = "Item 2", GroupName = "Item"},
new GroupItem() { Name = "Item 3", GroupName = "Item"}
};
}
public class GroupItem
{
public string Name { get; set; }
public string GroupName { get; set; }
}
}
}
XAML
<Window.Resources>
<CollectionViewSource Source="{Binding}" x:Key="GroupedDataCollView">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="GroupName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate x:Key="GroupHeaderTemplate">
<TextBlock Text="{Binding GroupName}" Margin="10,0,0,0" Foreground="#989791" />
</DataTemplate>
<!--Here, we tell that the URL's description should be displayed as item text.-->
<DataTemplate x:Key="NameTemplate">
<TextBlock Text="{Binding Name}" Margin="10,0,0,0"/>
</DataTemplate>
<Style x:Key="GroupStyleContainerStyle" TargetType="{x:Type GroupItem}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<Separator />
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Window>
<!-- ... -->
<ComboBox Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding Source={StaticResource GroupedDataCollView}}"
Width="200" Margin="10">
<ComboBox.GroupStyle>
<GroupStyle
ContainerStyle="{StaticResource GroupStyleContainerStyle}"
HeaderTemplate="{StaticResource GroupHeaderTemplate}" />
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- ... -->
</Window>
The Problem: Since we use Dictionaries for styling and templates which lie in another project, I can't use this 'solution' because this would require the style to know what kind of items he has to expect (Destroys the isolation of the custom styles and templates). Furthermore it's not really a group that I want to create rather a 'special' item, which is seperated by the others visually.
So I tried to create a custom UserControl to solve the problem so I could use it like this:
<ComboBox ItemsSource="{Binding Items}" DefaultItem="{Binding DefaultItem}" SelectedItem="{Binding SelectedItem}" />
In this CustomControl I would create the necessary grouping and handling hidden from the user. But how can I do this? And how can I overwrite the whole selection behaviour since I don't want to return a Key-Value-Pair (GroupItem) instead of the item the user expects.
So in short: How can I create a CustomControl which provides the necessary functionality shown at the start combined with the simplicity shown above?

I want to template an object property, by type, within another datatemplate

I have a view model for a DataGrid that is like this:
public class Cell
{
public CellValue CellValue { get; }
}
And the CellValue property can be of several types:
public class CellValue
{
public double Value { get; }
}
public class TwoValueCell : CellValue
{
public double Value2 { get; }
}
The DataGrid is binding the ItemsSource to a list of my rows and cells.
The DataGrid binds the data as expected: I get a Cell in each DataGridCell. So I have a style in the resources like this (local resources for now):
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<ContentPresenter x:Name="ContentHost"
Margin="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate DataType="{x:Type viewModel:Cell}">
<Grid>
<TextBlock Text="{Binding CellValue}"/>
</Grid>
</DataTemplate>
</DataGrid.Resources>
I like having the template bound to the view model type ...
The question is: instead of the Binding on the TextBlock, I want to inflate another DataTemplate there which I can somehow define again by specific CellValue Types.
In other words, in psudocode it might look like this:
<DataTemplate DataType="{x:Type viewModel:Cell}">
<Grid>
<DataTemplate DataType="{x:Type viewModel:CellValue}">
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
</Grid>
</DataTemplate>
But I would need to define the specific CellValue data templates somewhere ...
The bottom line is in having one Type for the DataGridCell --- and having a data template for that type --- but then having several specific types on the property of the cell, and I want to define custom data templates based on the actual type of the PROPERTY.
Any help?
You can use ContentControl.
<DataTemplate ...>
<Grid>
<ContentControl DataContext="{Binding SomeProperty}">
<ContentControl.ContentTemplate>
<DataTemplate ...> ... </DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</DataTemplate>

Can I use a different Template for the selected item in a WPF ComboBox than for the items in the dropdown part?

I have a WPF Combobox which is filled with, say, Customer objects. I have a DataTemplate:
<DataTemplate DataType="{x:Type MyAssembly:Customer}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Address}" />
</StackPanel>
</DataTemplate>
This way, when I open my ComboBox, I can see the different Customers with their Name and, below that, the Address.
But when I select a Customer, I only want to display the Name in the ComboBox. Something like:
<DataTemplate DataType="{x:Type MyAssembly:Customer}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
Can I select another Template for the selected item in a ComboBox?
Solution
With help from the answers, I solved it like this:
<UserControl.Resources>
<ControlTemplate x:Key="SimpleTemplate">
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</ControlTemplate>
<ControlTemplate x:Key="ExtendedTemplate">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Address}" />
</StackPanel>
</ControlTemplate>
<DataTemplate x:Key="CustomerTemplate">
<Control x:Name="theControl" Focusable="False" Template="{StaticResource ExtendedTemplate}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
<Setter TargetName="theControl" Property="Template" Value="{StaticResource SimpleTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</UserControl.Resources>
Then, my ComboBox:
<ComboBox ItemsSource="{Binding Customers}"
SelectedItem="{Binding SelectedCustomer}"
ItemTemplate="{StaticResource CustomerTemplate}" />
The important part to get it to work was Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}" (the part where value should be x:Null, not True).
The issue with using the DataTrigger/Binding solution mentioned above are two-fold. The first is you actually end up with a binding warning that you can't find the relative source for the selected item. The bigger issue however is that you've cluttered up your data templates and made them specific to a ComboBox.
The solution I present better follows WPF designs in that it uses a DataTemplateSelector on which you can specify separate templates using its SelectedItemTemplate and DropDownItemsTemplate properties as well as ‘selector’ variants for both.
Note: Updated for C#9 with nullability enabled and using pattern matching during the search
public class ComboBoxTemplateSelector : DataTemplateSelector {
public DataTemplate? SelectedItemTemplate { get; set; }
public DataTemplateSelector? SelectedItemTemplateSelector { get; set; }
public DataTemplate? DropdownItemsTemplate { get; set; }
public DataTemplateSelector? DropdownItemsTemplateSelector { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
var itemToCheck = container;
// Search up the visual tree, stopping at either a ComboBox or
// a ComboBoxItem (or null). This will determine which template to use
while(itemToCheck is not null
and not ComboBox
and not ComboBoxItem)
itemToCheck = VisualTreeHelper.GetParent(itemToCheck);
// If you stopped at a ComboBoxItem, you're in the dropdown
var inDropDown = itemToCheck is ComboBoxItem;
return inDropDown
? DropdownItemsTemplate ?? DropdownItemsTemplateSelector?.SelectTemplate(item, container)
: SelectedItemTemplate ?? SelectedItemTemplateSelector?.SelectTemplate(item, container);
}
}
To make it easier to use in XAML, I've also included a markup extension that simply creates and returns the above class in its ProvideValue function.
public class ComboBoxTemplateSelectorExtension : MarkupExtension {
public DataTemplate? SelectedItemTemplate { get; set; }
public DataTemplateSelector? SelectedItemTemplateSelector { get; set; }
public DataTemplate? DropdownItemsTemplate { get; set; }
public DataTemplateSelector? DropdownItemsTemplateSelector { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
=> new ComboBoxTemplateSelector(){
SelectedItemTemplate = SelectedItemTemplate,
SelectedItemTemplateSelector = SelectedItemTemplateSelector,
DropdownItemsTemplate = DropdownItemsTemplate,
DropdownItemsTemplateSelector = DropdownItemsTemplateSelector
};
}
And here's how you use it. Nice, clean and clear and your templates stay 'pure'
Note: 'is:' here is my xmlns mapping for where I put the class in code. Make sure to import your own namespace and change 'is:' as appropriate.
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource MySelectedItemTemplate},
DropdownItemsTemplate={StaticResource MyDropDownItemTemplate}}" />
You can also use DataTemplateSelectors if you prefer...
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplateSelector={StaticResource MySelectedItemTemplateSelector},
DropdownItemsTemplateSelector={StaticResource MyDropDownItemTemplateSelector}}" />
Or mix and match! Here I'm using a template for the selected item, but a template selector for the DropDown items.
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource MySelectedItemTemplate},
DropdownItemsTemplateSelector={StaticResource MyDropDownItemTemplateSelector}}" />
Additionally, if you don't specify a Template or a TemplateSelector for the selected or dropdown items, it simply falls back to the regular resolving of data templates based on data types, again, as you would expect. So, for instance, in the below case, the selected item has its template explicitly set, but the dropdown will inherit whichever data template applies for the DataType of the object in the data context.
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{is:ComboBoxTemplateSelector
SelectedItemTemplate={StaticResource MyTemplate} />
Enjoy!
Simple solution:
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Address}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
(Note that the element that is selected and displayed in the box and not the list is not inside a ComboBoxItem hence the trigger on Null)
If you want to switch out the whole template you can do that as well by using the trigger to e.g. apply a different ContentTemplate to a ContentControl. This also allows you to retain a default DataType-based template selection if you just change the template for this selective case, e.g.:
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}}"
Value="{x:Null}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- ... -->
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ComboBox.ItemTemplate>
Note that this method will cause binding errors as the relative source is not found for the selected item. For an alternate approach see MarqueIV's answer.
In addition to what said by H.B. answer, the Binding Error can be avoided with a Converter. The following example is based from the Solution edited by the OP himself.
The idea is very simple: bind to something that alway exists (Control) and do the relevant check inside the converter.
The relevant part of the modified XAML is the following. Please note that Path=IsSelected was never really needed and ComboBoxItem is replaced with Control to avoid the binding errors.
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Control}},
Converter={StaticResource ComboBoxItemIsSelectedConverter}}"
Value="{x:Null}">
<Setter TargetName="theControl" Property="Template" Value="{StaticResource SimpleTemplate}" />
</DataTrigger>
The C# Converter code is the following:
public class ComboBoxItemIsSelectedConverter : IValueConverter
{
private static object _notNull = new object();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// value is ComboBox when the item is the one in the closed combo
if (value is ComboBox) return null;
// all the other items inside the dropdown will go here
return _notNull;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I was going to suggest using the combination of an ItemTemplate for the combo items, with the Text parameter as the title selection, but I see that ComboBox doesn't respect the Text parameter.
I dealt with something similar by overriding the ComboBox ControlTemplate. Here's the MSDN website with a sample for .NET 4.0.
In my solution, I change the ContentPresenter in the ComboBox template to have it bind to Text, with its ContentTemplate bound to a simple DataTemplate that contains a TextBlock like so:
<DataTemplate x:Uid="DataTemplate_1" x:Key="ComboSelectionBoxTemplate">
<TextBlock x:Uid="TextBlock_1" Text="{Binding}" />
</DataTemplate>
with this in the ControlTemplate:
<ContentPresenter Name="ContentSite" IsHitTestVisible="False" Content="{TemplateBinding Text}" ContentTemplate="{StaticResource ComboSelectionBoxTemplate}" Margin="3,3,23,3" VerticalAlignment="Center" HorizontalAlignment="Left"/>
With this binding link, I am able to control the Combo selection display directly via the Text parameter on the control (which I bind to an appropriate value on my ViewModel).
I used next approach
<UserControl.Resources>
<DataTemplate x:Key="SelectedItemTemplate" DataType="{x:Type statusBar:OffsetItem}">
<TextBlock Text="{Binding Path=ShortName}" />
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Horizontal">
<ComboBox DisplayMemberPath="FullName"
ItemsSource="{Binding Path=Offsets}"
behaviors:SelectedItemTemplateBehavior.SelectedItemDataTemplate="{StaticResource SelectedItemTemplate}"
SelectedItem="{Binding Path=Selected}" />
<TextBlock Text="User Time" />
<TextBlock Text="" />
</StackPanel>
And the behavior
public static class SelectedItemTemplateBehavior
{
public static readonly DependencyProperty SelectedItemDataTemplateProperty =
DependencyProperty.RegisterAttached("SelectedItemDataTemplate", typeof(DataTemplate), typeof(SelectedItemTemplateBehavior), new PropertyMetadata(default(DataTemplate), PropertyChangedCallback));
public static void SetSelectedItemDataTemplate(this UIElement element, DataTemplate value)
{
element.SetValue(SelectedItemDataTemplateProperty, value);
}
public static DataTemplate GetSelectedItemDataTemplate(this ComboBox element)
{
return (DataTemplate)element.GetValue(SelectedItemDataTemplateProperty);
}
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uiElement = d as ComboBox;
if (e.Property == SelectedItemDataTemplateProperty && uiElement != null)
{
uiElement.Loaded -= UiElementLoaded;
UpdateSelectionTemplate(uiElement);
uiElement.Loaded += UiElementLoaded;
}
}
static void UiElementLoaded(object sender, RoutedEventArgs e)
{
UpdateSelectionTemplate((ComboBox)sender);
}
private static void UpdateSelectionTemplate(ComboBox uiElement)
{
var contentPresenter = GetChildOfType<ContentPresenter>(uiElement);
if (contentPresenter == null)
return;
var template = uiElement.GetSelectedItemDataTemplate();
contentPresenter.ContentTemplate = template;
}
public static T GetChildOfType<T>(DependencyObject depObj)
where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
}
worked like a charm. Don't like pretty much Loaded event here but you can fix it if you want
Yes. You use a Template Selector to determine which template to bind at run-time. Thus if IsSelected = False then Use this template, if IsSelected = True, use this other template.
Of Note:
Once you implement your template selector, you will need to give the templates keynames.
I propose this solution without DataTemplateSelector, Trigger, binding nor behavior.
The first step is to put the ItemTemplate (of the selected element) in the ComboBox resources and the ItemTemplate (of the item in the drop down menu) in the ComboBox.ItemsPanel resources and give both resources the same key.
The second step is to postpone the ItemTemplate resolution at run time by using both a ContentPresenter and a DynamicResource in the actual ComboBox.ItemTemplate implementation.
<ComboBox ItemsSource="{Binding Items, Mode=OneWay}">
<ComboBox.Resources>
<!-- Define ItemTemplate resource -->
<DataTemplate x:Key="ItemTemplate" DataType="viewModel:ItemType">
<TextBlock Text="{Binding FieldOne, Mode=OneWay}" />
</DataTemplate>
</ComboBox.Resources>
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Grid.IsSharedSizeScope="True"
IsItemsHost="True">
<StackPanel.Resources>
<!-- Redefine ItemTemplate resource -->
<DataTemplate x:Key="ItemTemplate" DataType="viewModel:ItemType">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="GroupOne" />
<ColumnDefinition Width="10" SharedSizeGroup="GroupSpace" />
<ColumnDefinition Width="Auto" SharedSizeGroup="GroupTwo" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding FieldOne, Mode=OneWay}" />
<TextBlock Grid.Column="2" Text="{Binding FieldTwo, Mode=OneWay}" />
</Grid>
</DataTemplate>
</StackPanel.Resources>
</StackPanel>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentPresenter ContentTemplate="{DynamicResource ItemTemplate}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Resources