WPF TreeViewItem Header Value After DataBinding - wpf

I have a TreeView whose contents (nested TreeViewItems) are generated from a dataset via databinding, which all seems to work fine. The issue I'm running into is that when I try and manipulate the contents of the TreeViewItem headers in code, the Header property returns the DataRowView that the TreeViewItem was generated from and not, as I was expecting, the control generated by the template.
Here's an example of the template I'm using to generate the TreeViewItems:
<DataTemplate x:Key="seasonTreeViewItemTemplate">
<TreeViewItem>
<TreeViewItem.Header>
<CheckBox Content="{Binding Path=Row.SeasonID}" Tag="{Binding}" ToolTip="{Binding Path=Row.Title}" IsEnabled="{StaticResource seasonPermitted}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" />
</TreeViewItem.Header>
<TreeViewItem Header="Championships" ItemTemplate="{StaticResource championshipTreeViewItemTemplate}">
<TreeViewItem.ItemsSource>
<Binding Path="Row" ConverterParameter="FK_Championship_Season">
<Binding.Converter>
<local:RowChildrenConverter />
</Binding.Converter>
</Binding>
</TreeViewItem.ItemsSource>
</TreeViewItem>
</TreeViewItem>
</DataTemplate>
Can anyone point out where I'm going wrong and advise me how to access the header checkboxes (ideally without delving into the VisualTree if possible)?
Thanks,
James

Well, after some searching I have found an adequate solution to the problem.
Using the following code, you can find named items in the template:
if (treeViewItem != null)
{
//Get the header content presenter.
ContentPresenter header = treeViewItem.Template.FindName("PART_Header", treeViewItem) as ContentPresenter;
if (header != null)
{
//Find a CheckBox called "checkBoxName"
CheckBox cb = treeViewItem.HeaderTemplate.FindName("checkBoxName", header) as CheckBox;
}
}
Also, for the benefit of anyone else who may not be too clued up on databinding treeviews: The template I posted in my question is not the right way to go about binding a treeview. Use a HierarchicalDataTemplate for each level of the tree. The direct content of the HierarchicalDataTemplate will specify the header content of each subtree and setting the ItemsSource and ItemTemplate properties will allow you to bind and format the subtrees children, for example:
<HierarchicalDataTemplate x:Key="templateName" ItemsSource="{Binding Path=someCollection}" ItemTemplate="{StaticResource someOtherTemplate}">
<TextBlock Text="{Binding Path=SomeProperty}" />
</HierarchicalDataTemplate>
I hope someone else will find this information useful.

Related

Data Error 26 with DataTemplate and ItemTemplate

I have a TabControl with a single specific tab and a collection bound to a collection of VMs, using a different user control. To do this I use a CompositeCollection and DataTemplates defined in the control's resources, selecting correct user control based on the VM type (acting as ContentTemplate).
I also set an ItemTemplate to define the tab item's name with binding, but it's not defined in the resource as I guess would conflict with the "ContentTemplate" ones.
It works fine, but I see the following error traced:
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='TabItem'
It looks like there's some conflict between ContentTemplate and ItemTemplate, but I don't know how to fix it?
Code is the following:
<TabControl HorizontalAlignment="Left" Height="300" Width="500">
<TabControl.Resources>
<CollectionViewSource x:Key="personCollection" Source="{Binding Persons}" />
<DataTemplate DataType="{x:Type viewModel:Main}">
<local:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:Person}">
<local:PersonView />
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemsSource>
<CompositeCollection>
<TabItem Header="General" Content="{Binding }"/>
<CollectionContainer Collection="{Binding Source={StaticResource personCollection}}" />
</CompositeCollection>
</TabControl.ItemsSource>
<TabControl.ItemTemplate>
<DataTemplate DataType="viewModel:Person">
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
The error you observe is pretty obvious.
You define the ItemsSource of your TabControl as a CompositeCollection that contains elements of different types:
a TabItem "General";
a bunch of Person viewmodels.
So you're just mixing in one collection a view and some viewmodels - that's not neat. WPF informs you about this with the error message. The engine tries to create views (using DataTemplates) for the items and suddenly encounters an already specified view (a TabItem) that is exactly of type of the item container (because for the TabControl, a view for each viewmodel will be inserted in a TabItem container). So WPF simply inserts the TabItem into the TabControl and notifies that it has not used any ItemTemplate or ItemTemplateSelector for creating it.
You could simply ignore this error, because in the end the control should look like you want it to (I suppose).
An alternative (and probably neater) way is not to mix views and viewmodels in one collection, but rather specify a "general" viewmodel for the "General" tab:
<TabControl.ItemsSource>
<CompositeCollection>
<viewModel:GeneralViewModel/>
<CollectionContainer Collection="{Binding Source={StaticResource personCollection}}" />
</CompositeCollection>
</TabControl.ItemsSource>
And of course you then need to tell WPF how to visualize it:
<TabControl.Resources>
<DataTemplate DataType="{x:Type viewModel:GeneralViewModel}">
<local:GeneralView />
</DataTemplate>
<!-- ... -->
</TabControl.Resources>
Update
To address the issues in your comments.
1.
How do I bind the GeneralViewModel to the one that exist in my DataContext?
This is possible, but with some overhead. You have to create a binding proxy for this. (Take a look here.)
The second thing you will need is a markup extension:
class BindingProxyValue : MarkupExtension
{
public BindingProxy Proxy { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Proxy.DataContext;
}
}
Use this markup extension together with the binding proxy in your collection:
<TabControl.Resources>
<local:BindingProxy x:Key="Proxy" DataContext="{Binding GeneralViewModel}"/>
</TabControl.Resources>
<!--...-->
<CompositeCollection>
<local:BindingProxyValue Proxy="{StaticResource Proxy}"/>
<CollectionContainer Collection="{Binding Source={StaticResource personCollection}}" />
</CompositeCollection>
You can extend the markup extension as you like, e.g. in such a way that it can observe the object updates and replace the item in the target CompositeCollection.
2.
How do I specify general tab's header name?
You can use ItemTemplates, but it becomes a little bit complicated. You have to implement a DataTemplateSelector for your TabControl:
class YourTabItemDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null)
{
if (item is GeneralViewmodel)
{
return (DataTemplate)element.FindResource("GeneralTabItemTemplate");
}
else
{
return (DataTemplate)element.FindResource("PersonTabItemTemplate");
}
}
return null;
}
}
Then you can define the different ItemTemplates for different TabItems:
<TabControl.Resources>
<!-- ... -->
<DataTemplate x:Key="GeneralTabItemTemplate">
<TextBlock Text="General" />
</DataTemplate>
<DataTemplate x:Key="PersonTabItemTemplate">
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</TabControl.Resources>
The question is: is this effort worth it or are you okay with that error message 26? You decide.

Change DataTemplate textblock Visibility determined by parent container type

I have a <DataTemplate> as defined below, which contains a <TextBlock>
The <DataTemplate> is used in several instances of a <ListBox> and reused elsewhere in a <ContentControl>
Note code simplified for brevity
<DataTemplate x:Key="SetsItemTemplate" DataType="viewModel:SetVm">
<TextBlock
Visibility="{Binding <somethign here i guess>,
ConverterParameter=collapse,
Converter={StaticResource BoolToVisConverter}}">
</TextBlock>
</DataTemplate>
<ListBox ItemTemplate="{StaticResource SetsItemTemplate}" />
<ContentControl ContentTemplate="{StaticResource SetsItemTemplate}" />
The <TextBlock> has a boolToVisibility Converter to collapse the <TextBlock> on a condition, however i really need that condition to check if the parent container is a <ContentControl>
I.e If the <DataTemplate> parent is a <ContentControl> collapse the <TextBlock>
Maybe i could use Names to make this easier (i'm not sure)
In order to access the parent, you need to get the sender or the source object. There is no way you can get this using IValueConverter. But, they already have a solution for this:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/9f3e4f6d-20d2-4c13-90a2-7c157ed4f8c3/ivalueconverter-pass-calling-object-as-converterparameter?forum=wpf
Now, you can access the element and get the parent through:
element = VisualTreeHelper.GetParent(element) as UIElement;
Hope it helps!
You can change the visibility based on the parent element as mentioned in the above msdn link. you can achieve this by using parent element name property with BoolToVisibilityConverter. Like bind the element name to TextBlock Visibility property with converter and define the visibility in converter based on the bounded ElementNameProperty.

Changing entire controls in grid cells in xaml only

I am currently in the process of catching up on the theoretical basics of WPF and have come across a problem customizing grids.
My data source fills a grid with a number of properties that require different controls for editing. (say: combobox, date picker, tetbox etc.)
So far I have found 2 ways of accomplishing this:
listen to some event in the code behind and programmatically creating these controls in each grid cell.
Adding all edit controls I could need in each cell and binding their visibility to some selector variable that disables all the controls that aren't needed.
Both of these ways seem very untidy and hard to maintain. Is there a way to accomplish this in XAML only without having 6 or 7 active bindings to invisible and useless controls? What would be the proper way of doing this?
You can use an ItemsControl and use Templates to achieve this.
This is one of my recent controls. Basically I had several Options that were determined at runtime and each needed a visual representation on the UI. So I added an ItemsControl that - based on the bound property - selected the right ItemTemplate to display.
<ItemsControl ItemsSource="{Binding Path=DeliveryOrderOptions}" HorizontalContentAlignment="Stretch">
<ItemsControl.Resources>
<DataTemplate x:Key="DateTimeDataTemplate" DataType="{x:Type services:DeliveryOrderOption}">
<Grid Visibility="{Binding ConditionValid, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Path=Name}" />
<DatePicker Grid.Column="1"
SelectedDate="{Binding Path=Value, ConverterCulture=de-DE, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}"
ToolTip="{Binding Path=Error}" />
</Grid>
</DataTemplate>
<!-- more templates -->
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type services:DeliveryOrderOption}">
<GroupBox Header="{Binding Path=Title}" HorizontalContentAlignment="Center" Padding="6">
<ItemsControl ItemsSource="{Binding}" ItemTemplateSelector="{Binding Source={StaticResource OptionDataTemplateSelector}}" />
</GroupBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It utilizes an DataTemplateSelector like this to find the right DataTemplate to apply to the item based on its property Type and if it has a Dictionary of acceptable values.
public class OptionDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
DeliveryOrderOption option = item as DeliveryOrderOption;
DataTemplate template = null;
if (element != null && option != null)
{
switch (Nullable.GetUnderlyingType(option.Type)?.Name ?? option.Type.Name)
{
case "String":
if (option.HasDictionary)
{
template = element.FindResource("StringComboBoxDataTemplate") as DataTemplate;
}
else
{
template = element.FindResource("DefaultDataTemplate") as DataTemplate;
}
break;
case "Int32":
if (option.HasDictionary)
{
template = element.FindResource("IntComboBoxDataTemplate") as DataTemplate;
}
else
{
template = element.FindResource("IntDataTemplate") as DataTemplate;
}
break;
case "DateTime":
template = element.FindResource("DateTimeDataTemplate") as DataTemplate;
break;
//... more options
default:
template = null;
break;
}
}
return template;
}
}
If the controls you wish to display at any given time have some sort of obvious logical grouping you could try creating a custom control for each group. This would make your xaml neater as your main page would only need to contain your custom controls (you might also be able to re-use them elsewhere).
Unfortunately I believe that you would still need to either place all controls on and then collapse them as required or add/remove them dynamically but you would only need to bind the visibility of your custom controls once rather than for each child element, you would also not have to see all the xaml they contain cluttering up your page.

WPF TabControl, switching to another TabItem does not work, sticks to first TabItem

I have a control that contains following XAML code. It works fine excepted that I caInnot switch to another TabItem. I read that TabControl virtualizes the TabItem, I suspect the strange behaviour, namely that I cannot get to display any other TabItem as the first one, is related to this.
<TabControl ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate> <!-- header -->
<TextBlock Text="{Binding Title}"></TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate x:Shared="False"> <!-- tabitem content -->
<controls:ItemControl Item="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I tried to set the x:Shared attribute of the DataTemplate to False but has not the expected effect. Is there a way to reach this without going the way of using a custom style and replacing the TabControl with an ItemsControl. I mean the functionality of TabControl is what I would like, I would like to simply use it with ItemsSource binding...
This behaviour will happen if you are binding to a collection that has duplicate objects in it.
Duplication can occur due to having added an object multiple times or because equality has been redefined for the objects in question.

Using data binding on value which is a FrameworkElement

One of my data sources produces a collection of values which are typed to the following interface
public interface IData
{
string Name { get; }
FrameworkElement VisualElement { get; }
}
I'd like to use data binding in WPF to display a collection of IData instances in a TabControl where the Name value becomes the header of the tab and the VisualElement value is displayed as the content of the corresponding tab.
Binding the header is straight forward. I'm stuck though on how to define a template which allows me to display the VisualElement value. I've tried a number of solutions with little success. My best attempt is as follows.
<TabControl ItemsSource="{Binding}">
<TabControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
How do I display VisualElement here?
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I'm still very new to WPF so I could be missing the obvious here.
ContentPresenters were made for this. The content template becomes:
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding VisualElement}" />
</DataTemplate>
</TabControl.ContentTemplate>
I tested it with a TextBlock and a TextBox.

Resources