Specify binding for HeaderTemplate programmatically - wpf

I'm having various problems with the virtualisation of views in the TabControl. Luckily, I've found something that I think will solve all my problems in this CodeProject article.
One problem is introduced by this solution though, and that is that it destroys my HeaderTemplate. The tab header has the same content as the content control.
My view uses a TabControl like this:
<TabControl
behaviors:TabItemGeneratorBehavior.ItemsSource="{Binding MyItems, Mode=OneWay}"
behaviors:TabItemGeneratorBehavior.SelectedItem="{Binding MySelectedItem, Mode=TwoWay}">
<TabControl.Resources>
<Style TargetType="TabItem">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<ContentPresenter>
<ContentPresenter.Content>
<TextBlock Text="{Binding Title}"/>
</ContentPresenter.Content>
</ContentPresenter>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The behaviors:TabItemGeneratorBehavior is from the CodeProject article. Inside the TabItemGeneratorBehavior is a method that generates TabItems:
private void AddTabItem(object item)
{
var contentControl = new ContentControl();
var tab = new TabItem
{
DataContext = item,
Content = contentControl,
HeaderTemplate = _tabControl.ItemTemplate
};
contentControl.SetBinding(ContentControl.ContentProperty, new Binding());
tab.SetBinding(HeaderedContentControl.HeaderProperty, new Binding());
_tabControl.Items.Add(tab);
}
I think my problem is with the line that sets the binding for the HeaderProperty. How can I set the binding so that it uses the HeaderTemplate defined in my XAML above?

OP here.
The solution is to remove the HeaderTemplate assignment when creating the TabItem:
var tab = new TabItem
{
DataContext = item,
Content = contentControl,
// HeaderTemplate = _tabControl.ItemTemplate
};

Related

How to loop through ItemsControl in WPF?

How can I loop through this ItemsControl and change it's TextBlock background in this Xaml's code behind page on some mouse event. I am new to WPF.
<ItemsControl ItemsSource="{Binding Path= HeaderList}" Name="Headers">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Name="Data" Text="{Binding }" Width="100" HorizontalAlignment="Left" PreviewMouseLeftButtonDown="MouseLeftButtonDown_Handler"
MouseEnter="MouseEnter_Handler" MouseLeave="MouseLeave_Handler">
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Thanks in advance!!
Actually my requirement is to change individual TextBlock's background color on different mouse events. So i need to get access of TextBlock in code behind and depending upon login I can change that Textblock's background color accordingly. So i think need to iterate ItemsControl. in case if I bind Background Property then all on property change would have effect on all the Textblocks in that ItemsControl. I don't want it in this way. I want to set and change every individual textblock's color differently.
I have access to single one in the eventhandlers that caused that event, but I want to access all the textblocks that are in itemscontrol and change their color acoording to some logic
Solution with background binding like axelle suggested:
You can iterate through the items in the HeaderList and set the background-property.
The Header class must implement the INotifyPropertyChanged Interface
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1" x:Class="WpfApplication1.MainWindow"
Title="MainWindow" Height="350" Width="525">
<ItemsControl ItemsSource="{Binding Path=HeaderList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" Background="{Binding Background}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
public partial class MainWindow : Window
{
public class Header : NotificationObject
{
public string Text { get; set; }
public Brush Background { get; set; }
}
public IList<Header> HeaderList { get; set; }
public MainWindow()
{
HeaderList = new List<Header>
{
new Header {Text = "header1", Background = Brushes.Red},
new Header {Text = "header2", Background = Brushes.Blue},
new Header {Text = "header3", Background = Brushes.Chartreuse},
};
DataContext = this;
InitializeComponent();
}
}
If I understand your question correctly, you'd want to bind the TextBlock background to a value in your datacontext, and change that value on your mouse event.
don't loop through the itemscontrol, better use a Trigger to apply the changes to your textblock :)
<ItemsControl ItemsSource="{Binding Path= HeaderList}" Name="Headers">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>

Dynamic template for a ComboBox in ListBox

I have a ListBox with an embedded ComboBox:
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<ComboBox Width="100" IsEditable="False" Height="20">
<TextBlock Text="Opt#1"></TextBlock>
<TextBlock Text="Opt#2"></TextBlock>
<TextBlock Text="Opt#3"></TextBlock>
</ComboBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'd like to present the ComboBox as a simple text (e.g. TextBlock) when a ListBox row is not selected, and show it as a ComboBox when the ListBox row is selected.
I was thinking that replacing ComboBox template dynamically would do the trick. How to accomplish that?
Thanks,
Leszek
The best way to swap templates is to use the ItemTemplateSelector propery of the ListBox and set it to a class you create which inherits from DataTemplateSelector.
Here is a link that provides an example: http://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector.aspx
I would simply use a style that replace the ListBox.ItemTemplate whenever the ListBoxItem becomes selected.
Here's a quick example
<ListBox.Resources>
<DataTemplate x:Key="TextBoxTemplate">
<TextBlock Text="{Binding }" />
</DataTemplate>
<DataTemplate x:Key="ComboBoxTemplate">
<ComboBox SelectedItem="{Binding }">
<ComboBoxItem>Opt#1</ComboBoxItem>
<ComboBoxItem>Opt#2</ComboBoxItem>
<ComboBoxItem>Opt#3</ComboBoxItem>
</ComboBox>
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template" Value="{StaticResource TextBoxTemplate}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Template" Value="{StaticResource ComboBoxTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
I'd actually suggest using IsKeyboardFocusWithin instead of IsSelected as the trigger property, because templates can let you interact with them without setting the item as selected.
Thanks Josh and Rachel for pointing me in a right direction.
I came up with a solution similar to the one suggested by Rachel. My problem was I could not make ItemTemplateSelector work and I did not know how to pass the state IsSelected from my listbox. I also could not use DataTemplate because my ListBox item is much more complex than a single element (I simplified it in my previous post for the sake of example).
Anyway, I came up with the following solution. It's not very elegant but it works:
I defined a new style in Application resources:
<Style x:Key="TextBlockTemplate" TargetType="ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="{Binding}" Margin="3" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I attached SelectionChanged and PreviewMouseDown handlers to my ListBox:
I defined MyListBox_PreviewMouseDown:
private void MyListBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
// Grab the selected list box item.
object element = (e.OriginalSource as FrameworkElement).DataContext;
var item = MyListBox.ItemContainerGenerator.ContainerFromItem(element)
as ListBoxItem;
// Mark the row in the ListBox as selected.
if (item != null)
item.IsSelected = true;
}
I defined MyListBox_SelectionChanged:
private ComboBox prevComboBox = null;
private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Grab the list box.
ListBox list = sender as ListBox;
// Although there could be only one item selected,
// we iterate over all selected items.
foreach (MyDataItem dat in list.SelectedItems)
{
var item = list.ItemContainerGenerator.ContainerFromItem(dat) as ListBoxItem;
// FindElement is a helper method to find an element in a visual tree.
ComboBox cbo = FindElement(item, "MyComboBox") as ComboBox;
if (cbo != prevComboBox)
{
cbo.Style = null;
if (prevComboBox != null)
prevComboBox.Style =
(Style)Application.Current.Resources["TextBlockTemplate"];
prevComboBox = cbo;
}
}
}
Thanks,
Leszek

Load UserControl in TabItem

I have a Usercontrol(TabUserControl) which contains a TabControl. The Viewmodel of that UserControl loads ab Observable collection of TabItems. One od those items is another user control. When I just load text in the tabcontrol there is no problem, but how can I load the other user control into the tabitem of the TabUserControl.
I'm using MVVM.
Here's my code:
public class TabItem
{
public string Header { get; set; }
public object Content { get; set; } // object to allow all sort of items??
}
The Viewmodel of the TabUserControl
public class TabViewModel
{
public ObservableCollection<TabItem> Tabs {get;set;}
public TabViewModel()
{
Tabs = new ObservableCollection<TabItem>();
//Tabs.Add(new TabItem { Header = "Overview", Content = new OverviewViewModel() }); How to load a usercontrol here if it's in the ItemCollection?
Tabs.Add(new TabItem { Header = "Overview", Content = "Bla bla bla" });
Tabs.Add(new TabItem { Header = "Two", Content = "Two's content" });
}
}
And then the TabControl XAML:
<TabControl x:Name="_tabControl"
ItemsSource="{Binding Tabs}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header"
Value="{Binding Header}" />
<Setter Property="Content"
Value="{Binding Content}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
It works as long as I dont load the viewmodel of the usercontrol in the tabItems collection. how can I make the UserTabControl load on to the TabItem?
The intention is that every tabitem will contain a usercontrol. Each usercontrol then does it's own thing.
Hope someone can help me as I am a WPF beginner.
Thx!
Ideally, the TabControl.ItemsSource should be set to a collection of ViewModels, and DataTemplates should be used to tell the WPF to draw each ViewModel with a specific UserControl.
This keeps between your business logic (ViewModels) completely separate from your UI (Views)
For example,
<TabControl x:Name="MyTabControl"
ItemsSource="{Binding TabViewModels}"
SelectedItem="{Binding SelectedTabViewModel}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type my:ViewModelA}">
<my:ViewAUserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type my:ViewModelB}">
<my:ViewBUserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type my:ViewModelC}">
<my:ViewCUserControl />
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
ViewModel containing TabControl's DataContext:
TabViewModels = new ObservableCollection<ITabViewModel>();
TabViewModels.Add(new ViewModelA { Header = "Tab A" });
TabViewModels.Add(new ViewModelB { Header = "Tab B" });
TabViewModels.Add(new ViewModelC { Header = "Tab C" });
SelectedTabViewModel = TabViewModels[0];
Thanks Rachel for your answer. But it enforces declaring the DataContext during compile time itself. Like you did, relating each of the Views to their respective ViewModels in the DataTemplate of TabControl. We can achieve dynamic View-ViewModel linking when move this out to ViewModel. Here's how:
XAML:
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="Content" Value="{Binding Content}" />
</Style>
</TabControl.ItemContainerStyle>
VM:
public ObservableCollection<TabItem> TabItems { get; set; }
public MainWindowViewModel()
{
TabItems = new ObservableCollection<TabItem>
{
new TabItem{Content = new TabAView() {DataContext = new TabAViewModel()}, Header = "Tab A"},
new TabItem{Content = new TabBView(), Header = "Tab B"}
};
}
We can even make use of Action delegates to delay and invoke initialization of the TabItems only upon Tab SelectionChangedEvent. This achieves lot of memory saving if the UserControl Views have many UI elements.

WPF: How to customize SelectionBoxItem in ComboBox

I want to display a custom template/item as selected item in ComboBox (this item does not actually exist in the list of items and is updated differently). This does not even needs to be an item, just providing a custom view would work.
How can I do this while staying within current ComboBox theme (so no ControlTemplate replacement possible)? As far as I see, all of SelectionBox* properties are not editable and internally ComboBox uses unnamed ContentPresenter.
I would do it like this:
<Window.Resources>
<DataTemplate x:Key="NormalItemTemplate" ...>
...
</DataTemplate>
<DataTemplate x:Key="SelectionBoxTemplate" ...>
...
</DataTemplate>
<DataTemplate x:Key="CombinedTemplate">
<ContentPresenter x:Name="Presenter"
Content="{Binding}"
ContentTemplate="{StaticResource NormalItemTemplate}" />
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource FindAncestor,ComboBoxItem,1}}"
Value="{x:Null}">
<Setter TargetName="Presenter" Property="ContentTemplate"
Value="{StaticResource SelectionBoxTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
...
<ComboBox
ItemTemplate="{StaticResource CombinedTemplate}"
ItemsSource="..."
... />
The reason this works is that CombinedTemplate normally just uses NormalItemTemplate to present its data, but if there is no ComboBoxItem ancestor it assumes it is in the selection box so it uses SelectionBoxTemplate.
Note that the three DataTemplates could be included in any level of ResourceDictionary (not just at the Window level) or even directly within the ComboBox, depending on your preference.
If I have this straight, you want a control that has something arbitrary displayed along with a drop-down button that displays a list of items with checkboxes next to them?
I wouldn't even bother trying to restyle a ComboBox to achieve this. The problem is that ComboBox is more specialized down a different path than what you need. If you look at the ComboBox ControlTemplate Example, you'll see that it simply uses a Popup control to display the list of possible values.
You can take pieces of that template as guidance to creating a UserControl that is easier to understand and better provides what you want. You'll even be able to add a SelectedItems property and such that ComboBox doesn't provide.
An example of what I mean by guidance: the Popup has an IsOpen property. In the control template, it's set to {TemplateBinding IsDropDownOpen}, which means that the ComboBox class has an IsDropDownOpen property that is changed in order to control the expand/collapse of the Popup.
Alexey Mitev's comment on Ray Burns' answer inspired me to write the following reasonably short utility class, which I now use in all my WPF projects:
public class ComboBoxItemTemplateSelector : DataTemplateSelector
{
public List<DataTemplate> SelectedItemTemplates { get; } = new List<DataTemplate>();
public List<DataTemplate> DropDownItemTemplates { get; } = new List<DataTemplate>();
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return GetVisualParent<ComboBoxItem>(container) == null
? ChooseFrom(SelectedItemTemplates, item)
: ChooseFrom(DropDownItemTemplates, item);
}
private static DataTemplate ChooseFrom(IEnumerable<DataTemplate> templates, object item)
{
if (item == null)
return null;
var targetType = item.GetType();
return templates.FirstOrDefault(t => (t.DataType as Type) == targetType);
}
private static T GetVisualParent<T>(DependencyObject child) where T : Visual
{
while (child != null && !(child is T))
child = VisualTreeHelper.GetParent(child);
return child as T;
}
}
With that in the toolbox, it's possible to write XAML like this:
<UserControl.Resources>
<DataTemplate x:Key="SelectedItemTemplateForInt" DataType="{x:Type system:Int32}">
<!-- ... -->
</DataTemplate>
<DataTemplate x:Key="SelectedItemTemplateForDouble" DataType="{x:Type system:Double}">
<!-- ... -->
</DataTemplate>
<DataTemplate x:Key="DropDownItemTemplateForInt" DataType="{x:Type system:Int32}">
<!-- ... -->
</DataTemplate>
<DataTemplate x:Key="DropDownItemTemplateForDouble" DataType="{x:Type system:Double}">
<!-- ... -->
</DataTemplate>
</UserControl.Resources>
<ComboBox>
<ComboBox.ItemTemplateSelector>
<local:ComboBoxItemTemplateSelector>
<local:ComboBoxItemTemplateSelector.SelectedItemTemplates>
<StaticResource ResourceKey="SelectedItemTemplateForInt" />
<StaticResource ResourceKey="SelectedItemTemplateForDouble" />
</local:ComboBoxItemTemplateSelector.SelectedItemTemplates>
<local:ComboBoxItemTemplateSelector.DropDownItemTemplates>
<StaticResource ResourceKey="DropDownItemTemplateForInt" />
<StaticResource ResourceKey="DropDownItemTemplateForDouble" />
</local:ComboBoxItemTemplateSelector.DropDownItemTemplates>
</local:ComboBoxItemTemplateSelector>
</ComboBox.ItemTemplateSelector>
</ComboBox>
You need to look into Triggers and Styles. You might also want to look into some of my older questions here on StackOverflow that helped me conquer these problems:
Displaying Content only when ListViewItem is Selected
Using Styles in Windows Presentation Foundation

How to add new user control in TabControl.ContentTemplate?

I am little stuck with adding new instances of a usercontrol in a TabControl.ContentTemplate?
My Xaml is here:
<TabControl ItemsSource="{Binding Tables}">
<TabControl.ItemTemplate>
<DataTemplate>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type uc:mytest1}">
<uc:mytest1>
</uc:mytest1>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I am binding TabControl.ItemsSource property to an ObservableCollection and in the content template I am adding a user control, but when this app runs I am getting new items as TabItems but the content page is holding same user control, but I want new user controls to be added for each new TabItem.
I am very new to the WPF and may be I am doing a very basic mistake, kindly guide me.
The ControlTemplate determines the appearance of the elements of the tab control that are not part of the individual tab items. The ItemTemplate handles the content of the individual tab items. Additionally, a TabItem is a headered content control, which means it has two content type properties Content and Header with two separate templates ContentTemplate and HeaderTemplate. In order to be able to populate the tab items using binding, you need to style the TabItem using the above properties.
Example:
<Window x:Class="Example.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Window"
Title="Window2" Height="300" Width="300">
<Window.DataContext>
<Binding ElementName="Window" Path="VM"/>
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="TabItemHeaderTemplate">
<Grid>
<TextBlock Text="{Binding Header}"/>
<Ellipse Fill="Red" Width="40" Height="40" Margin="0,20,0,0"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="TabItemContentTemplate">
<Ellipse Fill="Green"/>
</DataTemplate>
<Style x:Key="TabItemContainerStyle" TargetType="TabItem">
<Setter Property="Header" Value="{Binding}"/>
<Setter Property="HeaderTemplate"
Value="{StaticResource TabItemHeaderTemplate}"/>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate"
Value="{StaticResource TabItemContentTemplate}"/>
</Style>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding Items}"
ItemContainerStyle="{StaticResource TabItemContainerStyle}"/>
</Grid>
</Window>
The code behind:
public partial class Window2 : Window
{
public TabControlVM VM { get; set; }
public Window2()
{
VM = new TabControlVM();
InitializeComponent();
}
}
And the view model classes:
public class TabControlVM
{
public ObservableCollection<TabItemVM> Items { get; set; }
public TabControlVM()
{
Items = new ObservableCollection<TabItemVM>();
Items.Add(new TabItemVM("tabitem1"));
Items.Add(new TabItemVM("tabitem2"));
Items.Add(new TabItemVM("tabitem3"));
Items.Add(new TabItemVM("tabitem4"));
}
}
public class TabItemVM
{
public string Header { get; set; }
public TabItemVM(string header)
{
Header = header;
}
}
Saurabh, When you set Template, usually DataTemplate, ControlTemplate etc, the visual elements inside these templates are reused in WPF with concept of UI Virtualization. TabControl typically displays only one item at a time, so it does not create new Visual Item for every tab item, instead it only changes that DataContext and refreshes bindings of "Selected Visual Item". Its loaded/unloaded events are fired, but the object is same always.
You can use loaded/unload events and write your code accordingly that your "Visual Element" which is your usercontrol, so that control should be stateless and is not dependent on old data. When new DataContext has applied you should refresh everything.
DataContextChanged, Loaded and Unloaded events can help you remove all dependencies on old data.
Otherwise, you an create a new TabItem manually with your UserControl as its Child and add it in TabControl instead of adding Data Items.
Adding TabItems manually will create new control for every item and in selected area different elements will appear based on selection.

Resources