<EventTrigger RoutedEvent="Binding.TargetUpdated"> not firing with ContentTemplateSelector - wpf

I'm trying to animate the Background Color of a Border in a DataTemplate for a DataObject when a Child Property of the DataObject changes.
The DataObject is a Class called Test with two Properties, Number and Text.
I have an ObservableCollection of DataObjects called Numbers.
In a Task I update the Number Property at a regular Interval.
<Window
x:Class="WpfAnimationTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfAnimationTest"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
DataContext="{Binding Main, Source={StaticResource Locator}}"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="NumberTemplate">
<TextBlock Text="{Binding NotifyOnTargetUpdated=True}" />
</DataTemplate>
<local:ValueTemplateSelector x:Key="TemplateSelector">
<local:ValueTemplateSelector.NumberTemplate>
<DataTemplate>
<ContentControl Content="{Binding NotifyOnTargetUpdated=True}" ContentTemplate="{StaticResource NumberTemplate}" />
</DataTemplate>
</local:ValueTemplateSelector.NumberTemplate>
</local:ValueTemplateSelector>
<DataTemplate DataType="{x:Type local:Test}">
<Border x:Name="UpdateBorder" Background="Aqua">
<StackPanel Orientation="Horizontal">
<TextBlock
Width="50"
Margin="10"
Text="{Binding Text}" />
<ContentControl
Width="50"
Margin="10"
Content="{Binding Number}"
ContentTemplateSelector="{StaticResource TemplateSelector}" />
<!--
ContentTemplate="{StaticResource NumberTemplate}"
-->
</StackPanel>
</Border>
<DataTemplate.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard AutoReverse="True">
<ColorAnimation
FillBehavior="Stop"
Storyboard.TargetName="UpdateBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#C5AFFFAA"
Duration="00:00:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Numbers}" />
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
namespace WpfAnimationTest
{
public class Locator
{
public Locator()
{
Main = new Main();
}
public Main Main { get; set; }
}
public class Main
{
public ObservableCollection<Test> Numbers { get; set; } = new ObservableCollection<Test>();
public Main()
{
var rnd = new Random(42);
for (int i = 0; i < 10; i++)
{
Numbers.Add(new Test(){Number = i, Text = $"#: {i}"});
}
Task.Run(() =>
{
while (true)
{
try
{
Application.Current?.Dispatcher.Invoke(() =>
{
Numbers[rnd.Next(9)] = new Test
{
Number = rnd.Next(30),
Text = $"# {rnd.Next(30) + 30}"
};
});
Thread.Sleep(1000);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
});
}
}
public class Test
{
public int Number { get; set; }
public string Text { get; set; }
}
public class ValueTemplateSelector : DataTemplateSelector
{
/// <summary>
///
/// </summary>
/// <param name="item"></param>
/// <param name="container"></param>
/// <returns></returns>
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return !(item is Test value)
? null
: NumberTemplate;
}
/// <summary>
///
/// </summary>
public DataTemplate NumberTemplate { get; set; }
/// <summary>
///
/// </summary>
public DataTemplate DefaultTemplate { get; set; }
}
}
When using a ContentTemplate for the Number Property in the ContentControl the animation is working.
But when I use a ContentTemplateSelector the Animation is not triggered anymore.
What am I missing here?

Your data bindings are wrong, resulting in unhandled or unexpected data types.
Currently, your ContentControl.Content property (inside the DataTemplate for the type Test) binds to the Test.Number property of type int. Therefore, the object that is passed to the DataTemplateSelector is of type int and not Test. The condition in the DataTemplateSelector.SelectTemplate method will return false (as int is not Test is true) and make the DataTemplateSelector.SelectTemplate return null - no template. The XAML engine will call ToString on the int instance and is therefore able to display the value correctly (despite the lack of a DataTemplate).
Solution
You must fix the binding on the ContentControl.Content property to bind to the Test instance instead of binding to the Test.Number property:
<DataTemplate DataType="{x:Type local:Test}">
<Border x:Name="UpdateBorder"
Background="Aqua">
<StackPanel Orientation="Horizontal">
<TextBlock Width="50"
Margin="10"
Text="{Binding Text}" />
<ContentControl Width="50"
Margin="10"
Content="{Binding}"
ContentTemplateSelector="{StaticResource TemplateSelector}" />
</StackPanel>
</Border>
...
</DataTemplate>
Now that that the DataTemplateSelector can work properly, you must also adjust the NumberTemplate, so that it can display the Test.Number property properly. Since we have modified the binding source for the ContentControl.Content property, the new data type is now Test (instead of int):
<DataTemplate x:Key="NumberTemplate"
DataType="{x:Type Test}">
<TextBlock Text="{Binding Number, NotifyOnTargetUpdated=True}" />
</DataTemplate>
Remarks
There is a lot of redundant code here. You will achieve the same results by dropping all the templates and the DataTemplateSelector by defining the ItemTemplate for the Test items properly.
The following drastically simplified version should also work:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:Test}">
<Border x:Name="UpdateBorder"
Background="Aqua">
<StackPanel Orientation="Horizontal">
<TextBlock Width="50"
Margin="10"
Text="{Binding Text}" />
<TextBlock Width="50"
Margin="10"
Text="{Binding Number, NotifyOnTargetUpdated=True}" />
</StackPanel>
</Border>
<DataTemplate.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard AutoReverse="True">
<ColorAnimation FillBehavior="Stop"
Storyboard.TargetName="UpdateBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#C5AFFFAA"
Duration="00:00:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Numbers}" />
</Grid>

Related

WPF Data Binding From UserControl

I want to bind 'SomeText' from my UserControl, into the Content of my Label.
I currently have a UserControl which just displays my 'SomeText'. The XAML, and Code Behind file can be seen below.
<UserControl x:Class="TabHeader.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="183" d:DesignWidth="235"
x:Name="uc">
<Grid>
<Label Height="43" HorizontalAlignment="Left" Margin="57,102,0,0" Name="textBlock1" Content="{Binding Path=SomeText, ElementName=uc}" VerticalAlignment="Top" Width="86" />
</Grid>
</UserControl>
namespace TabHeader
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
private string someText;
public UserControl1()
{
this.SomeText = "23";
InitializeComponent();
}
public string SomeText
{
get
{
return someText;
}
set
{
someText = value;
}
}
}
}
I then have my main XAML page where I have, a Tab Control within a Grid. I'm using a Style to generate two Labels within the Columns Header. I am able to pull through the Header field, but I am unable to pull through the controls field.
<Window x:Class="TabHeader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vw="clr-namespace:TabHeader"
Title="MainWindow" Height="350" Width="525" Name="Tabs">
<Grid>
<TabControl Height="262" HorizontalAlignment="Left" Margin="47,26,0,0" Name="tabControl1" VerticalAlignment="Top" Width="366">
<TabControl.Resources>
<Style TargetType="TabItem" x:Key="tabItemHeaderStyle" >
<Setter Property="HeaderTemplate" >
<Setter.Value>
<DataTemplate DataType="{x:Type TabItem}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Header, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabItem}}"/>
<Label Content="{Binding Path=SomeText, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=vw:UserControl1}}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem Style="{StaticResource tabItemHeaderStyle}" Header="TI 1" Name="tabItem1" Width="100">
<vw:UserControl1 x:Name="UserControl11"></vw:UserControl1>
</TabItem>
<TabItem Style="{StaticResource tabItemHeaderStyle}" Header="TI 2" Name="tabItem2">
</TabItem>
</TabControl>
</Grid>
</Window>
Any assistance with this would be greatly appreciated.
Cheers.
Edit 1
For anyone interested added my working code below, where I have used the DependencyProperty.
MainWindow.xaml
<Grid>
<TabControl Height="262" HorizontalAlignment="Left" Margin="47,26,0,0" Name="tabControl1" VerticalAlignment="Top" Width="366">
<TabControl.Resources>
<Style TargetType="TabItem" x:Key="tab1ItemHeaderStyle">
<Setter Property="HeaderTemplate" >
<Setter.Value>
<DataTemplate DataType="{x:Type TabItem}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Header, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabItem}}"/>
<Label Content="{Binding Path=UC1Figure, ElementName=uc1}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem Style="{StaticResource tab1ItemHeaderStyle}" Header="[Tab 1]" Name="tabItem1" Width="100">
<vw:UserControl1 x:Name="uc1"></vw:UserControl1>
</TabItem>
<TabControl>
</Grid>
UserControl1.xaml
<Grid>
<Label Height="43" HorizontalAlignment="Left" Margin="69,128,0,0" Name="textBlock1" Content="{Binding Path=UC1Figure, ElementName=uc}" VerticalAlignment="Top" Width="100" />
<Button Name="updateSomeFigure" Content="Press Me" Click="updateSomeFigure_Click" Width="100" Height="100" Margin="69,12,66,71" />
</Grid>
UserControl1.xaml.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public static readonly DependencyProperty SomeFigureProperty =
DependencyProperty.Register("UC1Figure", typeof(int), typeof(UserControl1));
public int UC1Figure
{
get { return (int)this.GetValue(SomeFigureProperty); }
set { this.SetValue(SomeFigureProperty, value); }
}
private void updateSomeFigure_Click(object sender, RoutedEventArgs e)
{
UC1Figure = UC1Figure + 1;
}
}
If you want to data bind a property to the UI of your UserControl, you have two options. The first is to implement the INotifyPropertyChanged Interface in your code behind. The second is to define DependencyPropertys instead of regular CLR properties. You can find out how to do that in the Dependency Properties Overview page on MSDN.
You might also want to read the Data Binding Overviewā€ˇ page on MSDN before you start data Binding.

Pulling different data into ListBox

How I should to do this? I tried the following:
In Xaml:
<DataTemplate x:Key="LogDataTemplate" DataType="data:Type1">
<TextBlock Text="Type1" />
</DataTemplate>
<DataTemplate x:Key="LogDataTemplate" DataType="data:Type2">
<TextBlock Text="Type2" />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<ListBox ItemsSource="{Binding source}"
ItemTemplate="{StaticResource LogDataTemplate}" />
</UserControl>
In view model(which is set as DataContext of the UserControl):
member x.source = new ObservableCollection<Object>()
But have an error about duplicates of DataTemplate
Remove the x:Key parameter. Implicit DataTemplates is what you want here.
Edit: Here is a really small working example :
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.Windows;
namespace StackOverflow
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
Rectangles = new ObservableCollection<object>() { new RedRectangle(), new BlueRectangle() };
}
public ObservableCollection<object> Rectangles { get; set; }
}
public class RedRectangle { }
public class BlueRectangle { }
}
MainWindow.xaml
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow"
Width="500" Height="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:RedRectangle}">
<Rectangle Width="16" Height="16" Fill="Red" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:BlueRectangle}">
<Rectangle Width="16" Height="16" Fill="Blue" />
</DataTemplate>
</Window.Resources>
<ListBox ItemsSource="{Binding Rectangles}" />
</Window>
Well there's implicit data templates like #Sisyphe mentions.
But your real problem is, you've named both templates the same thing. x:Key is a dictionary key, it needs to be unique within its scope. That's what the error is about.
Having said that, you'll be better off with implicit data templates in this case as #Sisyphe mentions.

Icon badge overlay for notifications in silverlight/xaml

I've got a ribbon bar in my silverlight app and on one of the icons I would like for there to be a badge icon showing the number of items in the view that the icon activates.
Picture the Mail icon in OS X showing the number of unread messages or the notifications counter on an IOS app icon.
I don't know much about xaml styles, but it seems to me I could duplicate the default style for the ribbon bar button, then add to it with some sort of red circle, and a white text that took in its value from a new property on the ribbon bar button somehow so I would be able to bind to it.
Does anyone have an example of something like this I can start from?
Thanks Shawn for the answer. This is what I ended up doing:
In the xaml:
<telerikRibbonBar:RadRibbonRadioButton
Text="Expired Active Call Factors"
Size="Large"
LargeImage="/CallFactorDatabase.UI;component/Images/Ribbon/Large/ExpiredActiveView.png"
Command="{Binding ActivateViewCommand}"
CommandParameter="ExpiredActiveView">
<Grid>
<Grid.Resources>
<converters:BooleanToVisibilityConverter x:Key="visibleWhenTrueConverter" VisibilityWhenTrue="Visible" VisibilityWhenFalse="Collapsed" />
</Grid.Resources>
<Grid Width="27" Height="27" Visibility="{Binding ExpiredActiveCallFactors, Converter={StaticResource visibleWhenTrueConverter}}" Margin="50,-40,0,0">
<Ellipse Fill="Black" Width="27" Height="27"/>
<Ellipse Width="25" Height="25" VerticalAlignment="Center" HorizontalAlignment="Center">
<Ellipse.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="Coral" Offset="0.0" />
<GradientStop Color="Red" Offset="1.0" />
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Viewbox Width="25" Height="25" VerticalAlignment="Center" HorizontalAlignment="Center" >
<TextBlock Text="{Binding ExpiredActiveCallFactorsCount}" Foreground="White"/>
</Viewbox>
</Grid>
</Grid>
</telerikRibbonBar:RadRibbonRadioButton>
How it looks:
No luck getting it in front of the ribbon button but oh well.
This can be accomplished with a few bindings and an optional value converter. This samples assumes you are binding to a model that has an Items property and that that property is of type ObservableCollection so that the Count property of the collection will fire property changed when items are added/removed.
<Grid>
<Grid.Resources>
<local:CountToVisbilityConverter x:Key="CountToVis"/>
</Grid.Resources>
....
<Grid Width="25" Height="25" Visibility="{Binding Items.Count, Converter=CountToVis}">
<Ellipse Fill="Red" Width="25" Height="25"/>
<ViewBox Width="25" Height="25">
<TextBlock Text="{Binding Itmes.Count}" Foreground="White"/>
</Viewbox>
</Grid>
</Grid>
And the value converter:
public class CountToVisibilityConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value == null) return Visibility.Collapsed;
int count = System.Convert.ToInt32(value);
return count == 0 ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
The reason I saw optional" converter is because you can also use the Interaction DataTriggers like such
<Grid x:Name="UnreadNotification" Width="25" Height="25">
<Ellipse Fill="Red" Width="25" Height="25"/>
<ViewBox Width="25" Height="25">
<TextBlock Text="{Binding Itmes.Count}" Foreground="White"/>
</Viewbox>
</Grid>
<i:Interaction.Triggers>
<ei:DataTrigger Binding="{Binding Items.Count, Comparison="Equal"
Value="0">
<ei:ChangePropertyAction PropertyName="IsEnabled"
Value="True"
TargetName="UnreadNotification" />
</ei:DataTrigger>
</i:Interaction.Triggers>
Here's my solution for this. By default, the badge will show in the upper right corner. You can change this by setting the "BadgeMarginOffset" property. I've attached a couple of images to show how it appears. One, which shows the badge wrapping a Telerik RadRibbonButton. You can also change the background and foreground colors of the badge (BadgeBackground, BadgeForeground). The defaults are shown below.
The UserControl's XAML
<UserControl x:Class="Foundation.Common.Controls.Wpf.Badge"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:converters="clr-namespace:Foundation.Common.Controls.Wpf.Converters"
Background="Transparent" x:Name="UserControl"
mc:Ignorable="d" >
<UserControl.Resources>
<converters:GreaterThanZeroBooleanConverter x:Key="GreaterThanZeroBooleanConverter" />
<converters:GreaterThanZeroVisibilityConverter x:Key="GreaterThanZeroVisibilityConverter"/>
</UserControl.Resources>
<UserControl.Template>
<ControlTemplate>
<Grid HorizontalAlignment="Stretch" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border CornerRadius="10"
UseLayoutRounding="True"
x:Name="BadgeBorder"
Grid.ZIndex="99"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Visibility="{Binding ElementName=UserControl, Path=Count, Mode=TwoWay, Converter={StaticResource GreaterThanZeroVisibilityConverter}}"
Grid.Row="0"
Margin="{Binding ElementName=UserControl, Path=BadgeMarginOffset}"
Height="22"
Padding="6.7,2,7,3"
Background="{Binding ElementName=UserControl, Path=BadgeBackground}">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=UserControl, Path=Count, Mode=TwoWay, Converter={StaticResource GreaterThanZeroBooleanConverter}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation From="0.0"
To="1.0"
Duration="0:0:0.7"
Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=UserControl, Path=ShowDropShadow}" Value="True">
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="6" ShadowDepth="4" Color="#949494"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{Binding ElementName=UserControl, Path=Count}"
Foreground="{Binding ElementName=UserControl, Path=BadgeForeground}"
FontWeight="Bold"
FontSize="12">
</TextBlock>
</Border>
<ContentPresenter Grid.Row="0"
Grid.RowSpan="2"
DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext}"
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />
</Grid>
</ControlTemplate>
</UserControl.Template>
The UserControl's code behind
public partial class Badge : UserControl
{
#region Dependency Properties
public static readonly DependencyProperty CountProperty =
DependencyProperty.Register("Count", typeof(int), typeof(Badge));
public static readonly DependencyProperty ShowDropShadowProperty =
DependencyProperty.Register("ShowDropShadow", typeof(bool), typeof(Badge), new PropertyMetadata(true));
public static readonly DependencyProperty BadgeMarginOffsetProperty =
DependencyProperty.Register("BadgeMarginOffset", typeof(Thickness), typeof(Badge));
public static readonly DependencyProperty BadgeBackgroundProperty =
DependencyProperty.Register("BadgeBackground", typeof(Brush), typeof(Badge), new PropertyMetadata(Brushes.Red));
public static readonly DependencyProperty BadgeForegroundProperty =
DependencyProperty.Register("BadgeForeground", typeof(Brush), typeof(Badge), new PropertyMetadata(Brushes.White));
#endregion Dependency Properties
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="Badge"/> class.
/// </summary>
public Badge()
{
this.InitializeComponent();
}
#endregion Constructor
#region Properties
/// <summary>
/// Gets or sets a value indicating whether [show drop shadow].
/// </summary>
/// <value>
/// <c>true</c> if [show drop shadow]; otherwise, <c>false</c>.
/// </value>
public bool ShowDropShadow
{
get => (bool)this.GetValue(ShowDropShadowProperty);
set => this.SetValue(ShowDropShadowProperty, value);
}
/// <summary>
/// Gets or sets the badge margin offset.
/// </summary>
/// <value>
/// The badge margin offset.
/// </value>
public Thickness BadgeMarginOffset
{
get => (Thickness)this.GetValue(BadgeMarginOffsetProperty);
set => this.SetValue(BadgeMarginOffsetProperty, value);
}
/// <summary>
/// Gets or sets the badge background.
/// </summary>
/// <value>
/// The badge background.
/// </value>
public Brush BadgeBackground
{
get => (Brush)this.GetValue(BadgeBackgroundProperty);
set => this.SetValue(BadgeBackgroundProperty, value);
}
/// <summary>
/// Gets or sets the badge foreground.
/// </summary>
/// <value>
/// The badge foreground.
/// </value>
public Brush BadgeForeground
{
get => (Brush)this.GetValue(BadgeForegroundProperty);
set => this.SetValue(BadgeBackgroundProperty, value);
}
/// <summary>
/// Gets or sets the count.
/// </summary>
/// <value>
/// The count.
/// </value>
public int Count
{
get => (int)this.GetValue(CountProperty);
set => this.SetValue(CountProperty, value);
}
#endregion Properties
}
Sample code for the top two images
<wpf:Badge Count="108" Margin="20" HorizontalAlignment="Left" BadgeMarginOffset="0,-5,-5,0">
<Button Height="100" Width="100">
<Button.Content>
<Image Source="Resources/about.png" />
</Button.Content>
</Button>
</wpf:Badge>

Problems with binding in DataTemplate

Im having some problems with databinding inside a DataTemplate. In the ControlTemplate below the textbox in the Grid works and prints out the correct value. But the TextBlock inside the HyperlinkButtons DataTemplate does not work. Though the HyperlinkButtons NavigateUri is bound correctly. Could anyone plz help me with this
I have created a simple example that illustrates my problem.
MainPage.xaml
<UserControl x:Class="SilverlightApplication8.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<ControlTemplate x:Key="EventControlTemplate" TargetType="Button">
<Grid>
<!--WORKS-->
<!--<Grid >
<TextBlock x:Name="TitleTextBlock" Text="{Binding Title}" Foreground="Red" FontWeight="Bold" />
</Grid>-->
<!--DOES NOT WORK-->
<HyperlinkButton TargetName="_blank" NavigateUri="{Binding Url}" >
<HyperlinkButton.ContentTemplate>
<DataTemplate>
<Grid>
<TextBlock Foreground="Green" Text="{Binding Title}"/>
</Grid>
</DataTemplate>
</HyperlinkButton.ContentTemplate>
</HyperlinkButton>
</Grid>
</ControlTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<ItemsControl x:Name="Links" Foreground="White" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border >
<Button Template="{StaticResource EventControlTemplate}" Click="Button_Click"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
MainPage.xaml.cs
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace SilverlightApplication8
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
List<Events> events = new List<Events>();
events.Add(new Events(){Title = "This is title 1", Url = "http://www.thesun.co.uk"});
events.Add(new Events(){Title = "This is title 2", Url = "http://www.thesun.co.uk"});
events.Add(new Events() { Title = "This is title 3", Url = "http://www.thesun.co.uk" });
Links.ItemsSource = events;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
}
}
public class Events
{
public string Url { get; set; }
public string Title { get; set; }
}
}
You're setting the ContentTemplate, the DataContext of which will be the Content of the HyperlinkButton. Since you haven't set the Content, you're attempting to bind against null.
This should work:
<HyperlinkButton TargetName="_blank" NavigateUri="{Binding Url}" Content="{Binding Title}">
<HyperlinkButton.ContentTemplate>
<DataTemplate>
<Grid>
<TextBlock Foreground="Green" Text="{Binding .}"/>
</Grid>
</DataTemplate>
</HyperlinkButton.ContentTemplate>
</HyperlinkButton>
But then that begs the question as to why you're even setting the ContentTemplate instead of just doing this:
<HyperlinkButton TargetName="_blank" NavigateUri="{Binding Url}" Content="{Binding Title}"/>

In WPF how to change a DataTemplate's Textblock's text binding in code?

I have a ListBox whose ItemsSource is bound to a list of objects. The Listbox has a ItemTemplate with a DataTemplate containing a TextBlock. The textblock's Text is bound to the object's Name property (i.e. Text="{Binding Name}").
I would like to provide a radio button to show different views of the same list. For example allow a user to toggle between the Name property and an ID property.
I found a SO answer for this at 2381740 but I also have border and a textbox style set in data template (see code below).
Is there anyway to just reset the Textblock binding? I don't want to have to recreate the entire datatemplate. Actually I'm not even sure how to do that, is there an easy way to translating xaml to code?.
Thanks
Cody
<DataTemplate>
<Border Margin="0 0 2 2"
BorderBrush="Black"
BorderThickness="3"
CornerRadius="4"
Padding="3">
<TextBlock Style="{StaticResource listBoxItemStyle}"
Text="{Binding Name}" />
</Border>
</DataTemplate>
Wallstreet Programmer's solution works well for you because you are using radio buttons. However there is a more general solution that I thought I should mention for future readers of this question.
You can change your DataTemplate to use plain "{Binding}"
<DataTemplate x:Key="ItemDisplayTemplate">
<Border ...>
<TextBlock ...
Text="{Binding}" />
</Border>
</DataTemplate>
Then in code you don't have to recreate a full DataTemplate. All you have to do is recreate this:
<DataTemplate>
<ContentPresenter Content="{Binding Name}" ContentTemplate="{StaticResource ItemDisplayTemplate}" />
</DataTemplate>
which is easy:
private DataTemplate GeneratePropertyBoundTemplate(string property, string templateKey)
{
var template = FindResource(templateKey);
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(ContentPresenter));
factory.SetValue(ContentPresenter.ContentTemplateProperty, template);
factory.SetBinding(ContentPresenter.ContentProperty, new Binding(property));
return new DataTemplate { VisualTree = factory };
}
This is particularly convenient if you have many properties, even in your radio button example.
Just make it simple for yourself and use two textblocks and hide one of them.
XAML:
<Window x:Class="Test.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<StackPanel>
<RadioButton Name="nameRadioBtn" Content="Name" IsChecked="True"/>
<RadioButton Name="lengthRadioBtn" Content="Length" />
<ListBox
ItemsSource="{Binding Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Red" BorderThickness="1">
<Grid>
<TextBlock
Text="{Binding .}"
Visibility="{Binding Path=IsChecked, ElementName=nameRadioBtn,
Converter={StaticResource BooleanToVisibilityConverter}}" />
<TextBlock
Text="{Binding Path=Length}"
Visibility="{Binding Path=IsChecked, ElementName=lengthRadioBtn,
Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
Code behind:
using System.Collections.Generic;
using System.Windows;
namespace Test
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = this;
}
public IEnumerable<string> Items
{
get
{
return new List<string>() {"Bob", "Sally", "Anna"};
}
}
}
}
You can also use a value converter to pick any property of your data object. You will need to bind to the whole object instead of individual properties. If your data object implements INotifyPropertyChanged then this solution will not work for you.
XAML
<Window x:Class="Test.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Test="clr-namespace:Test"
Height="300" Width="300">
<Window.Resources>
<Test:PropertyPickerConverter x:Key="PropertyPickerConverter" />
</Window.Resources>
<StackPanel>
<RadioButton Content="Name" Click="OnRadioButtonClick" IsChecked="True"/>
<RadioButton Content="Length" Click="OnRadioButtonClick" />
<ListBox
ItemsSource="{Binding Path=Items}"
Name="_listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Red" BorderThickness="1">
<StackPanel>
<TextBlock
Text="{Binding ., Converter={StaticResource PropertyPickerConverter}}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
code behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace Test
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
_propertyPickerConverter = FindResource("PropertyPickerConverter") as PropertyPickerConverter;
_propertyPickerConverter.PropertyName = "Name";
DataContext = this;
}
public IEnumerable<string> Items
{
get
{
return new List<string>() {"Bob", "Sally", "Anna"};
}
}
private void OnRadioButtonClick(object sender, RoutedEventArgs e)
{
_propertyPickerConverter.PropertyName = (sender as RadioButton).Content as string;
_listBox.Items.Refresh();
}
private PropertyPickerConverter _propertyPickerConverter;
}
public class PropertyPickerConverter : IValueConverter
{
public string PropertyName { get; set; }
#region IValueConverter Members
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string item = value as string;
switch (PropertyName)
{
case "Name": return item;
case "Length": return item.Length;
default: return null;
}
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new System.NotImplementedException();
}
#endregion
}
}

Resources