DataTrigger on parent property - wpf

How can I set the "IsAtLeastOneUserAvailable" dependency property correctly if at least one of my button is available? The dependency property is set in the code of the xaml. So, we can bind to it like that
"{Binding IsAtLeastOneUserAvailable, ElementName=control}"
I want to hide the label if no control is visible in the ItemsControl.
<UserControl ... Name="control">
<Label Content="Test" Visibility={Binding IsAtLeastOneUserAvailable, ElementName=control">
<ItemsControl ItemsSource="{Binding Users, ElementName=control}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Visibility="{Binding IsAvailable, Converter={StaticResource BooleanToVisibilityConverter}}">
<Button.Triggers>
<DataTrigger Binding="IsAvailable" Value="True">
<Setter Property="IsAtLeastOneUserAvailable" Value="True" />
</DataTrigger>
</Button.Triggers>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>

Instead of trying to set the value with a trigger, Why not just set your UserControl.IsAtLeastOneUserAvailable property inside your UserControl based on a linq query?
public bool IsAtLeastOneUserAvailable
{
get
{
return Users.Any(p => p.IsAvailable);
}
}
You can also raise this PropertyChanged event in the User's changed event:
public MyUserControl()
{
Users.CollectionChanged += Users_CollectionChanged;
}
void Users_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach(User user in e.NewItems)
user.PropertyChanged += User_PropertyChanged;
if (e.OldItems != null)
foreach(User user in e.OldItems)
user.PropertyChanged -= User_PropertyChanged;
}
void User_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsAvailable")
RaisePropertyChanged("IsAtLeastOneUserAvailable");
}

Related

How to focus item in listbox from view model

I make autocomplete and I want go throught with keys up and down in listbox with results.
Before that I have to focus the first item of listbox from textbox where I write the text.
<TextBox Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Name="Client" Text="{Binding Client}" cal:Message.Attach="[Event KeyUp] = [Action ExecuteFilterView($executionContext)]" Validation.ErrorTemplate="{x:Null}" >
<TextBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding UserCanChooseClient}" Value="True">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=Clients}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<ListBox x:Name="Clients" Width="190" Height="auto" MaxHeight="400" Margin="5 28 0 0" cal:Message.Attach="[Event KeyUp] = [Action ExecuteClientsView($executionContext)]; [Event MouseLeftButtonUp]=[Action HandleClientChosenClick($eventArgs)]" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden" SelectedItem="{Binding Path=SelectedClient}" ItemsSource="{Binding Path=Clients}" DisplayMemberPath="Description" SelectedValuePath="Code"></ListBox>
But the focusmanager focus the listbox, not the first item. I have to press button down twice to start to walk throught the list. Changing property SelectedClient in listbox didnt help.
on the event SelectionChanged or other event sent by listbox:
cal:Message.Attach="[Event SelectionChanged] = [Action LstBox_OnSelectionChanged($source, $eventArgs]"
public void LstBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var lstbox = (ListBox) sender;
var item = (ListBoxItem)lstbox.ItemContainerGenerator.ContainerFromItem(lstbox.SelectedItem);
if (item != null)
item.Focus();
}
if you want to have the first item of listbox selected at startup, you could use event Loaded:
cal:Message.Attach="[Event Loaded] = [Action LstBox_OnLoaded($source)]"
public void LstBox_OnLoaded(object sender)
{
var lstbox = (ListBox)sender;
lstbox.SelectedIndex = 0;
var item = (ListBoxItem)lstbox.ItemContainerGenerator.ContainerFromItem(lstbox.SelectedItem);
if (item != null)
item.Focus();
}

How to disable expanding/collapsing of tree items in WPF TreeView on double-click

How can I disable tree items collapsing/expanding when I double-click on tree item? I still would like to do this by clicking on toggle button, but not when I double-click on item.
This is XAML I have:
<TreeView Grid.Column="0" Grid.Row="0" ItemsSource="{Binding Categories}" helpers:TreeViewHelper.SelectedItem="{Binding SelectedCategory, Mode=TwoWay}" >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type core:Category}" ItemsSource="{Binding SubCategories}">
<Label Content="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CreateGroupsFromCategoryCommand , Mode=OneWay}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Label>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I would like to do this only in XAML.
Thank you for your help.
You should suppress the double click event on treeviewitem:
<TreeView Grid.Column="0" Grid.Row="0" ItemsSource="{Binding Categories}" helpers:TreeViewHelper.SelectedItem="{Binding SelectedCategory, Mode=TwoWay}" TreeViewItem.PreviewMouseDoubleClick="TreeViewItem_PreviewMouseDoubleClick" >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type core:Category}" ItemsSource="{Binding SubCategories}">
<Label Content="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CreateGroupsFromCategoryCommand , Mode=OneWay}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Label>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Code Behind:
private void TreeViewItem_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
//this will suppress the event that is causing the nodes to expand/contract
e.Handled = true;
}
You could implement a custom EventTrigger in your views code behind, as suggested here: https://stackoverflow.com/a/7688249/1206431
public class HandlingEventTrigger : System.Windows.Interactivity.EventTrigger
{
protected override void OnEvent(System.EventArgs eventArgs)
{
var routedEventArgs = eventArgs as RoutedEventArgs;
if (routedEventArgs != null)
routedEventArgs.Handled = true;
base.OnEvent(eventArgs);
}
}
Add the namespace to your view like this xmlns:views="clr-namespace:YourNamespace.Views"
And then replace <i:EventTrigger EventName="MouseDoubleClick"> with <local:HandlingEventTrigger EventName="PreviewMouseDoubleClick">.
The HandlingEventTrigger will stop the event from being passed further up the visual tree and therefore not expanding/collapsing your tree, and using PreviewMouseDoubleClick instead of MouseDoubleClick will allow you to still fire your own command.
XAML:
<TreeView x:Name="TreeView1" MouseDoubleClick="TreeView1_MouseDoubleClick" />
C# Code:
private void TreeView1_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
e.Handled = true;
}
Works fine for me.
I know this issue is old, but I found a slightly different way that preserves the wanted double-click behavior but stops the expand/collapse in case it helps someone.
Keep your typical double click method:
private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
// Do something
}
Create an inherited class from TreeView (such as MyTreeView). Add the following to that inherited class:
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
var tvi = GetTreeViewItemFromObject(ItemContainerGenerator, SelectedItem);
if (tvi != null) tvi.IsExpanded = true;
base.OnMouseDoubleClick(e);
}
/// <summary>
/// Use the object to get its Tree View item
/// </summary>
/// <param name="container"></param>
/// <param name="targetObject"></param>
/// <returns></returns>
private TreeViewItem GetTreeViewItemFromObject(ItemContainerGenerator container, object targetObject)
{
if (container.ContainerFromItem(targetObject) is TreeViewItem target) return target;
for (int i = 0; i < container.Items.Count; i++)
if ((container.ContainerFromIndex(i) as TreeViewItem)?.ItemContainerGenerator is ItemContainerGenerator childContainer)
if (GetTreeViewItemFromObject(childContainer, targetObject) is TreeViewItem childTarget) return childTarget;
return null;
}
The GetTreeViewItemFromObject method was lifted from someplace else. I don't remember where.

how to access the label inside datatemplate

hello everybody i have a listbox within which is a datatemplate.Inside it is checkbox,textbox,label...Wat i want is to get the value of the label wen the checkbox is unchecked? or any alternative as to how to access the label value but only wen the checkbox is unselected............PLease help me out.
the code is as
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="sp" Orientation="Horizontal" Margin="3,3,3,3" >
<CheckBox Name="chkSubject" IsChecked="{Binding RelativeSource{RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" VerticalAlignment="Center" Margin="0,0,4,0" Unchecked="chkSubject_Unchecked">
<TextBlock FontSize="11" Text="{Binding subject_name}" />
</CheckBox>
<Label Name="lbl_idOfSub" Content="{Binding subject_id}" Visibility="Visible">
</Label>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Since you're using binding on label, I'd go for accessing subject_id from the object the datatemplate is describing. Like this:
var subjectId = dataBoundItem.subject_id;
That's the correct way to go with MVVM and bindings.
UPDATE:
Here's the basic MVVM approach to solving this problem. First of all, I've cleaned up a bit your listbox declaration and added a trigger that sets IsSelected binding:
<ListBox ItemsSource="{Binding}">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="sp" Orientation="Horizontal" Margin="3,3,3,3" >
<CheckBox Name="chkSubject" IsChecked="{Binding IsSelected}" VerticalAlignment="Center" Margin="0,0,4,0" Unchecked="chkSubject_Unchecked_1">
<TextBlock FontSize="11" Text="{Binding SubjectName}" />
</CheckBox>
<Label Name="lbl_idOfSub" Content="{Binding SubjectId}" Visibility="Visible"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here, whenever value IsSelected on individual ListBoxItem changes, the "IsSelected" binding of the viewModel is changed. Here's the model:
public class SelectableItem : INotifyPropertyChanged
{
private string _subjectId;
private bool _isSelected;
private string _subjectName;
public string SubjectId
{
get { return _subjectId; }
set { _subjectId = value; OnPropertyChanged("SubjectId"); }
}
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; OnPropertyChanged("IsSelected"); }
}
public string SubjectName
{
get { return _subjectName; }
set { _subjectName = value; OnPropertyChanged("SubjectName"); }
}
// .. INotifyPropertyChangedImplementation
Your IsSelected will be set to true whenever relevant item is selected and to false whenever it is unselected. You may put your code in to the "set" item of the "IsSelected" property and check (value == false) and execute necessary piece of code as you see fit. This would be MVVM approach to the matter.
Using the event, you can do as follows:
private void chkSubject_Unchecked_1(object sender, RoutedEventArgs e)
{
FrameworkElement control = sender as FrameworkElement;
if (control == null)
return;
SelectableItem item = control.DataContext as SelectableItem;
if (item == null)
return;
string yourValue = item.SubjectId;
}
I strongly recommend you read about MVVM and bindings.
What about using the Checked and UnChecked events of your CheckBox, so that you can retrieve the value of subject_id which is binded to your Label.

WPF DataGridTemplateColumn. Am I missing something?

<data:DataGridTemplateColumn Header="Name">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}">
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}">
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
It's clear example of Template column, right? What could be wrong with that?
So, here is the thing - when a user navigates through DataGrid with hitting TAB-key, it needs to hit the TAB twice(!) to be able to edit text in TextBox. How could I make it editable as soon as the user gets the column focus, I mean even if he just starts typing?
Ok. I found a way - into Grid.KeyUp() I put the code below:
if (Grid.CurrentColumn.Header.ToString() == "UserName")
{
if (e.Key != Key.Escape)
{
Grid.BeginEdit();
// Simply send another TAB press
if (Keyboard.FocusedElement is Microsoft.Windows.Controls.DataGridCell)
{
var keyEvt = new KeyEventArgs(Keyboard.PrimaryDevice, Keyboard.PrimaryDevice.ActiveSource, 0, Key.Tab) { RoutedEvent = Keyboard.KeyDownEvent };
InputManager.Current.ProcessInput(keyEvt);
}
}
}
your issue stems from the fact that each cell puts its editor in a content control which first receives focus, then you have to tab once again to the editor. If you have a look at the code for DataGridTemplateColumn in the GenerateEditingElement method it calls a method LoadTemplateContent which does this:
private FrameworkElement LoadTemplateContent(bool isEditing, object dataItem, DataGridCell cell)
{
DataTemplate template = ChooseCellTemplate(isEditing);
DataTemplateSelector templateSelector = ChooseCellTemplateSelector(isEditing);
if (template != null || templateSelector != null)
{
ContentPresenter contentPresenter = new ContentPresenter();
BindingOperations.SetBinding(contentPresenter, ContentPresenter.ContentProperty, new Binding());
contentPresenter.ContentTemplate = template;
contentPresenter.ContentTemplateSelector = templateSelector;
return contentPresenter;
}
return null;
}
see how it creates a new content presenter to put the template in. Other people have dealt with this problem in a variety of ways, I derive my own column type to deal with this stuff. (so i dont create an extra element or set the content presenter to not receive focus) In this example they are using focus manager to deal with the same issue (i havent tested this code)
<tk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid FocusManager.FocusedElement="{Binding ElementName=txt1}">
<TextBox Name="txt1" Text="{Binding XPath=#ISBN}"
BorderThickness="0" GotFocus="TextBox_GotFocus"/>
</Grid>
</DataTemplate>
</tk:DataGridTemplateColumn.CellEditingTemplate>
If you have a user control as your editor then you can use the pattern with the focus manager or use an event handler for the OnLoaded event.
The issue that you faced is that the control (e.g. TextBox) within the DataGridTemplateColumn is contained within a DataGridCell. By default the DataGridCell has tab-stop functionality. Thus the reason for having to hit TAB twice to get focus to your TextBox control. The solution is to disable the tab-stop functionality for the DataGridCell. This can be done via a style for the DataGridCell.
Here's the solution:
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
</Style>
Here is my approach. Its very close to #Nalin Jayasuriya answer, but I didn't want to create a style. Also this solution selects the text in the TextBox. Anyway - the XAML for the hole DataGrid looks like this.
<DataGrid Name="TextBlockDataGrid" ItemsSource="{Binding Path=Rows}" Style="{StaticResource DefaultSettingsDataGrid}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Text}" IsReadOnly="True"/>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
</Style>
</DataGridTemplateColumn.CellStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border BorderThickness="{Binding ErrorBorderThickness}" BorderBrush="{Binding ErrorBorderBrush}">
<TextBox Text="{Binding UserText, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
HorizontalAlignment="Right"
GotKeyboardFocus="TextBox_GotKeyboardFocus"
PreviewMouseDown="TextBox_PreviewMouseDown"
Style="{StaticResource DefaultTextBox}"/>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
And the code-behind.
private void TextBox_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
try
{
((TextBox)sender).SelectAll();
}
catch (Exception ex) { GlobalDebug.debugForm.WriteText(ex); }
}
private void TextBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
try
{
// If its a triple click, select all text for the user.
if (e.ClickCount == 3)
{
((TextBox)sender).SelectAll();
return;
}
// Find the TextBox
DependencyObject parent = e.OriginalSource as UIElement;
while (parent != null && !(parent is TextBox))
{
parent = System.Windows.Media.VisualTreeHelper.GetParent(parent);
}
if (parent != null)
{
if (parent is TextBox)
{
var textBox = (TextBox)parent;
if (!textBox.IsKeyboardFocusWithin)
{
// If the text box is not yet focussed, give it the focus and
// stop further processing of this click event.
textBox.Focus();
e.Handled = true;
}
}
}
}
catch (Exception ex) { GlobalDebug.debugForm.WriteText(ex); }
}
For a more info, have a look at my blog:
http://blog.baltz.dk/post/2014/11/28/WPF-DataGrid-set-focus-and-mark-text
My approach is to use a TriggerAction which sets the focus to the template element you want when it loads.
The trigger is very simple:
public class TakeFocusAndSelectTextOnVisibleBehavior : TriggerAction<TextBox>
{
protected override void Invoke(object parameter)
{
Dispatcher.BeginInvoke(
DispatcherPriority.Loaded,
new Action(() =>
{
AssociatedObject.Focus();
AssociatedObject.SelectAll();
}));
}
}
The DataTemplate looks like this:
<DataTemplate>
<TextBox Text="{Binding Path=Price, Mode=TwoWay}"
MinHeight="0"
Padding="1,0"
Height="20">
<Interactivity:Interaction.Triggers>
<Interactivity:EventTrigger EventName="Loaded">
<Behaviors:TakeFocusAndSelectTextOnVisibleBehavior />
</Interactivity:EventTrigger>
</Interactivity:Interaction.Triggers>
</TextBox>
</DataTemplate>
You can write other triggers for other element types.

WPF ComboBox - Showing something different when selecting a value

What I need to accomplish is a ComboBox that shows People. When you expand the drop-down it shows FirstName and LastName, but when you select a person, the value shown at the combobox should be just the person's first name.
I have the following ItemTemplate:
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
What else should I do to display only the first name when one item is selected?
Thanks!
EDIT
Changed the question slightly: What if I have the person's picture and instead of showing just the first name when a person is selected, I want to show only the picture. In other words, how can I have two separate templates - one for the drop-down and one for the selected item?
Here's the solution:
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ContentControl x:Name="content" Content="{Binding}" ContentTemplate="{StaticResource ComplexTemplate}"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBoxItem}}" Value="{x:Null}">
<Setter TargetName="content" Property="ContentTemplate" Value="{StaticResource SimpleTemplate}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Basically, you create one more layer of DataTemplate here. ComboBox'es ItemTemplate always stays the same. But the content inside that template adjusts to the condition you are interested in.
The trick to discriminate dropped-down combobox items against selected-area combobox item is that selected-area is not really enclosed in ComboBoxItem object, it's part of ComboBox control itself. So FindAncestor for ComboBoxItem returns null, which we use in the trigger above.
I got it. I just needed to add the following to my ComboBox:
IsEditable="True" IsReadOnly="True" TextSearch.TextPath="FirstName"
Put a Trigger on the DataTemplate. The trigger should check the IsSelected property (the DataTemplate will need a TargetType set for this to work). If it is selected, you can set the Visibility of your TextBlocks to Collapsed, and set the Visibility of the Image to Visible. Then do the opposite for the case that it is not selected.
Another option is to use ItemTemplateSelector instead of ItemTemplate. I've been using it the following way.
ComboBoxItemTemplateSelector derives from DataTemplateSelector and has two attached properties, SelectedTemplate and DropDownTemplate. Then we set the DataTemplates from Xaml like this
<ComboBox ItemsSource="{Binding Persons}"
ItemTemplateSelector="{StaticResource ComboBoxItemTemplateSelector}">
<ts:ComboBoxItemTemplateSelector.SelectedTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</ts:ComboBoxItemTemplateSelector.SelectedTemplate>
<ts:ComboBoxItemTemplateSelector.DropDownTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</ts:ComboBoxItemTemplateSelector.DropDownTemplate>
</ComboBox>
In SelectTemplate we check if the current container is wrapped in a ComboBoxItem and if it is, we return the DropDownTemplate. Otherwise we return SelectedTemplate.
public class ComboBoxItemTemplateChooser : DataTemplateSelector
{
#region SelectedTemplate..
#region DropDownTemplate..
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
ComboBox parentComboBox = null;
ComboBoxItem comboBoxItem = container.GetVisualParent<ComboBoxItem>();
if (comboBoxItem == null)
{
parentComboBox = container.GetVisualParent<ComboBox>();
return ComboBoxItemTemplateChooser.GetSelectedTemplate(parentComboBox);
}
parentComboBox = ComboBox.ItemsControlFromItemContainer(comboBoxItem) as ComboBox;
return ComboBoxItemTemplateChooser.GetDropDownTemplate(parentComboBox);
}
}
A small demo project that uses this can be downloaded here: ComboBoxItemTemplateDemo.zip
I also made a short blog-post about this here: Different ComboBox ItemTemplate for dropdown. It also shows the other obvious way of doing the same thing but with properties instead of attached properties in ComboBoxItemTemplateSelector.
Oh, and GetVisualParent. Everyone seems to have their own implementations of this but anyway, here's the one I'm using
public static class DependencyObjectExtensions
{
public static T GetVisualParent<T>(this DependencyObject child) where T : Visual
{
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
}
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

Resources