WPF Databinding TabItem Headers - c#

I am binding a ObservableCollection of data objects to my tab control item source. I have correctly figured out how to bind the controls within the tabitem that is generated, however I cannot figure out how to change the header property of the tabitem that is generated using the a property within the Observable Collection. Sorry if I am wording this incorrectly. Here is my XAML for the tabitem data template:
<DataTemplate x:Key="TabItemTemplate">
<TreeView Height="461" VerticalAlignment="Top"
Width="625" ItemTemplateSelector="{StaticResource TreeviewDataSelector}" ItemsSource="{Binding}" />
</DataTemplate>

Create a Style for your TabItems that sets the Header property, and apply the style to TabControl.ItemContainerStyle
<TabControl>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding PathToYourProperty}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>

Set the DisplayMemberPath on the TabControl to the name of the property.
<TabControl ItemsSource="{Binding items}" DisplayMemberPath="headerPropertyName">

Related

Set Header of TabItem

I have a TabControl which is bound to a List of UserControls (MyControls)
<TabControl Background="{x:Null}" x:Name="MyView" ItemsSource="{Binding MyControls}" >
I want to bind the header of each tab item to a property(Title) in each UserControl. Which I did as below
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Title}"/>
</Style>
</TabControl.ItemContainerStyle>
However since I override the ItemContainerStyle I lost all the default style for the application. My tab header looks different from other tab headers in the application
Is there any way to just bind to the Title without changing any style?
Define an ItemTemplate:
<TabControl Background="{x:Null}" x:Name="MyView" ItemsSource="{Binding MyControls}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
MyControls shouldn't return an IEnumerable<UserControl> though. It should return an IEnumerable<YourObject> where YourObject is a POCO class with a Title property along with any other properties. You should then use DataTemplates to define the appearance of a YourObject.

WPF ItemsControl and bugged DataGrid

Basically I'm having two related problems:
1) The same problem as in this question How do I make an ItemsControl stretch to fill all availible space?, basically the ItemsControl doesn't fill in the whole space and the provided solution with DockedPanel doesn't work for me, because I need the items to stack vertically and not horizontally. Is there any other way to achieve this than using complex dynamic Grids?
2) There seems to be something off with the way DataGrid is presented when I'm using the method in this question WPF MVVM Multiple ContentControls in TabControl.
The headers are for some reason crushed to the side, also adding info to the table makes it look very bad. This wouldn't normally happen if I wasn't using this method of ItemsControl to display the table.
Code for TabControl:
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Content}"/>
</ScrollViewer>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Code for DataGrid that is in the UserControl (view.xaml) which is bound to the above TabControl:
<DataGrid Grid.Row="0" Margin="10 10 10 0" ItemsSource="{Binding List}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ID">
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</DataGridTemplateColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Path=Id}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
//...
//Same for other columns
//...
</DataGrid>
Removing Header style does not help.
Remove the ScrollViewer element from your ContentTemplate. The DataGrid has its own ScrollViewer element built-in into its default ControlTemplate.
I actually found the answer and the accepted answer is correct (there's a workaround):
WPF ScrollViewer around DataGrid affects column width
As for my first question, I just sticked to UniformGrid and binded its row count to the ViewModel.

Unable to set the GroupStyle for a ListBox via Style?

I'm trying to create a Style that will set the GroupStyle property for my ListBox controls however I am getting a compile time error:
The Property Setter 'GroupStyle' cannot be set because it does not have an accessible set accessor.
My Style setter looks like this:
<Setter Property="ListBox.GroupStyle">
<Setter.Value>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</Setter.Value>
</Setter>
Is there a work-around for this, and also, if there is no setter for this property, then how are we able to use property-setter syntax for it in XAML to define it inline, in the first place? (still new to WPF)
I just figured out the answer - it's because of the way the XAML compiler treats any content between the element tags, based on the type of the property mapped to the content I just remembered!
If the property is a ContentControl, then the element you define between two tags gets assigned to that Content property, however, if the element is an instance of an IList (which is what GroupStyle is), then .NET actually calls .Add() underneath the covers
In this case, the GroupStyle is actually an ObservableCollection and hence an IList, therefore we are not actually assigning to the GroupStyle object, we are ADDING to the collection.
In otherwords, the type of the property that is represented by content (mapped via the ContentProperty attribute of a control) in between the element tags influences the way the XAML compiler interprets it (direct assignment or calling .Add())
You can the follwing:
<ListBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource listContainerStyle}"/>
</ListBox.GroupStyle>
And than
<Style x:Key="listContainerStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander Header="{Binding Name}" IsExpanded="True">
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Avi.
//set you datatemplate as a resource
<DataTemplate x:Key="categoryTemplate">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
//set header template binding to staticresource
<ListBox Name="lst">
<ListBox.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource categoryTemplate}" />
</ListBox.GroupStyle>
</ListBox>
in order to understand well
you can add groupstyle in code (it is what XAML do)
GroupStyle g = new GroupStyle();
ListBox ls = new ListBox();
ls.GroupStyle.Add(g);
but you can't set GroupStyle
GroupStyle g = new GroupStyle();
ListBox ls = new ListBox();
ls.GroupStyle=g;//error because GroupStyle has only a getter

How do I use DynamicResource with TreeView so that I can have 2 TreeViews using the same key

I've done this with list view in that I can define a resource:
<UserControl.Resources>
<GridView x:Key="MyGrid" x:Shared="False">
<!-- Defines what's in the grid view -->
</GridView>
</UserControl.Resources>
Then I can have two views using the same Grid:
<ListView View="{DynamicResource MyGrid}" ItemsSource="{Binding Path=TodaysItems}"/>
<ListView View="{DynamicResource MyGrid}" ItemsSource="{Binding Path=TomorrowsItems}"/>
I'm trying to do the same thing with TreeViews. I've defined my tree view:
<UserControl.Resources>
<TreeView x:Key="MyTreeView" x:Shared="False">
<!-- Defines what's in the Tree view -->
</TreeView>
</UserControl.Resources>
But I can't find what I need to do
<TreeView ???="{DynamicResource MyTreeView}" ItemsSource="{Binding Path=ClientData}"/>
<TreeView ???="{DynamicResource MyTreeView}" ItemsSource="{Binding Path=CustomerData}"/>
Can I even do this?
You cannot do this with TreeView. The reason why you can do it with the ListView is that it has a property View that can be set to different views. The view in this case is not a standalone UI element - it just, lets say, "settings" for the ListView. While the TreeView is a UI element just like ListView.
The command approach to re-use in XAML is Styles. You can define a style for you TreeView where you can define common properties and then apply it to as many elements as you like.
Here is an example how you can define a style:
<Style x:Key="MyTreeStyle"
TargetType="{x:Type TreeView}">
<Setter Property="Background"
Value="Red"/>
<!-- Other property setters go here -->
</Style>
And here is how you apply it:
<TreeView Style="{StaticResource MyTreeStyle}" ItemsSource="{Binding Path=ClientData}"/>
<TreeView Style="{StaticResource MyTreeStyle}" ItemsSource="{Binding Path=CustomerData}"/>

WPF Binding ListBox and TabItems

New to WPF, am trying to do something basic (I think!). I have a TabControl and a ListBox that shows what tabitems are open:
<ListBox Width="170" Height="188" ItemsSource="{Binding Items, ElementName=tabControl}" Name="ListTabs" Canvas.Left="0" Canvas.Top="27">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
El
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Is it possible to bind to specific tabitems (tabitem2 and tabitem3) rather than the whole tabcontrol? Reason being is the first tabitem1 is a welcome tab and I don't want it to be shown in the listbox.
UPDATE:
Would someone be so kind to post some code on how to use an IValueConverter to hide/filter a tabitem? I have been searching for hours with no luck. Many many thanks!
In your current set up the only way would be to run it through an IValueConverter.
<Window.Resources>
<converters:StripOutFirstTabConverter x:Key="StripOutFirstTabConverter"/>
</Window.Resources>
<ListBox Width="170" Height="188" ItemsSource="{Binding Items, ElementName=tabControl, Converter={StaticResource StripOutFirstTabConverter}}" Name="ListTabs" Canvas.Left="0" Canvas.Top="27">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
El
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If you were willing to modify your approach you could bind the ListBox.ItemsSource to a ICollectionView and then make use of the Filter property.
public ICollectionView Tabs
{
get
{
if (_view == null)
{
_view = CollectionViewSource.GetDefaultView(tabControl.Items);
_view.Filter = Filter;
}
return _view;
}
}
private bool Filter(object arg)
{
//arg will be a TabItem, return true if you want it, false if you don't
}
you can add a converter to ItemSource and then in the converter remove the welcome page or do any changes that you want .
I recommend not doing this. Use a common data source instead with both Listbox and Tabcontrol.
To filter/intercept any data binding, you can use IValueConverter.
You would have to filter out the welcome tab so you will need to add a Filter on a CollectionView Instead of binding to the items of the tab control you'd bind to the collectionview.
Although a ValueConverter might work, I consider that a kind of hack.

Categories

Resources