WPF User Control: Binding a Template to an Image

I am creating a custom WPF control, Button with Image and Text . I added two dependency properties to the ImagePath and Text control, and the control template (in Themes \ Generic.xaml) is a simple stack panel that arranges the image and text horizontally.

The Text property is working fine. But for some reason, the sample image in my test project does not appear when I use TemplateBinding for the ImagePath dependency property to get its path. I tested the image by temporarily replacing the TemplateBinding in the user element with the image path, in which case it will appear.

I hope that someone with more experience in this area can take a look and say why control is not working properly. Thank you for your help.

My VS 2008 solution contains one CustomControlDemo project. The project contains a custom TaskButton.cs control and the main Window1.xaml window, which I use to validate the control. My test image, calendar.png, is in the Resources folder at the root level of the project, and Generic.xaml is in the Themes folder, also at the root level of the project.

Here is the code for my user control (from TaskButton.cs):

 using System.Windows; using System.Windows.Controls; namespace CustomControlDemo { public class TaskButton : RadioButton { #region Fields // Dependency property backing variables public static readonly DependencyProperty ImagePathProperty; public static readonly DependencyProperty TextProperty; #endregion #region Constructors /// <summary> /// Default constructor. /// </summary> static TaskButton() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TaskButton), new FrameworkPropertyMetadata(typeof(TaskButton))); // Initialize ImagePath dependency properties ImagePathProperty = DependencyProperty.Register("ImagePath", typeof(string), typeof(TaskButton), new UIPropertyMetadata(null)); TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(TaskButton), new UIPropertyMetadata(null)); } #endregion #region Dependency Property Wrappers /// <summary> /// The ImagePath dependency property. /// </summary> public string ImagePath { get { return (string)GetValue(ImagePathProperty); } set { SetValue(ImagePathProperty, value); } } /// <summary> /// The Text dependency property. /// </summary> public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } } #endregion } } 

And here is the management template (from Generic.xaml):

 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CustomControlDemo"> <Style TargetType="{x:Type local:TaskButton}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:TaskButton}"> <StackPanel Height="Auto" Orientation="Horizontal"> <Image Source="{TemplateBinding ImagePath}" Width="24" Height="24" Stretch="Fill"/> <TextBlock Text="{TemplateBinding Text}" HorizontalAlignment="Left" Foreground="{DynamicResource TaskButtonTextBrush}" FontWeight="Bold" Margin="5,0,0,0" VerticalAlignment="Center" FontSize="12" /> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary> 

And finally, here is the Window1 markup that I use to validate the control:

 <Window x:Class="CustomControlDemo.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:customControl="clr-namespace:CustomControlDemo" Title="Window1" Height="300" Width="300"> <Grid> <customControl:TaskButton ImagePath="Resources\calendar.png" Text="Calendar" /> </Grid> </Window> 

Any ideas why the image path is not working? Thanks again.

+7
image wpf wpf-controls controltemplate
source share
4 answers

The image does not take the string as a source :) You can see it in intellisense. You need to bind to ImageSource (or use IValueConverter to convert a string to ImageSource)

See this question on how to do this.

+4
source share

I am going to leave the cwap request as an accepted answer because it is technically correct. However, it turns out that there is an easier way to solve this problem.

TemplateBindings are not first-class binding objects. They are designed for light weight, so they are one-way and lack the functions of other Binding objects. In particular, they do not support known type converters associated with the target. See MacDonald, Pro WPF in C # 2008 , p. 872. Why does cwap respond correctly that I probably need to create a type converter and specify it in the control template for my custom button.

But I do not need to use TemplateBinding to bind the control pattern to the ImagePath property of my custom control. I can use the plain old Binding object. Here is the revised markup for my custom control template:

 <!-- Task Button Default Control Template--> <Style TargetType="{x:Type local:TaskButton}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:TaskButton}"> <StackPanel Height="Auto" Orientation="Horizontal"> <Image Source="{Binding Path=ImagePath, RelativeSource={RelativeSource TemplatedParent}}" Width="24" Height="24" Stretch="Fill" Margin="10,0,0,0" /> <TextBlock Text="{TemplateBinding Text}" HorizontalAlignment="Left" Foreground="{TemplateBinding Foreground}" FontWeight="Bold" Margin="5,0,10,0" VerticalAlignment="Center" FontSize="12" /> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> 

If you look at ImageControl in the template, you will see a change. Notice the RelativeSource property in the same object. Setting this property to = {RelativeSource TemplatedParent} is what allows me to enter the relative path in my instance of Window1 TaskButton and whether it is correctly resolved in the user control.

Thus, my recommendation to other researchers on this topic was to skip the value converter and simply switch from the TemplateBinding property to Binding for the Image property.

Thanks also to Marco Zhou, who provided this answer to a similar question in the MSDN WPF forum.

+9
source share

In fact, none of these answers is correct.

{TemplateBinding ImagePath} is nothing more than a shortcut to {Binding Path=ImagePath, RelativeSource={RelativeSource TemplatedParent}} and as such is almost completely identical.

Also, if you provided a string for ImagePath , it will be correctly resolved for ImageSource , although you get into application performance. The real problem is related to the relative and absolute direction of the image on the supplied ImagePath="Resources\calendar.png" in xaml for the test. This tells the compiler to think that the provided path is absolute due to the use of \ instead of / in the path definition.

The reason the long form of linking work and the shortcut is not because it provides keys to the compiler, because the source of the provided image (Resources \ calendar.png) is a relative path and not an absolute path, so the image is found and the link works. If you debug the binding, you will see that the shortcut tries to resolve the supplied string in the image source, but cannot find the file "Resources \ calendar.png". If you provide the full URI for the image, that is, "C:\...\Resources\calendar.png" or the corresponding mixture designation "/application;component/Resources/calendar.png" , then the image will be found and the binding will be resolved .

This point becomes very important when you try to reference images from an external source, rather than being compiled as resources into the final compilation.

+2
source share

easy way (tested) 1 - make your ValueConverter this way

  public class objectToImageSourceConverter:IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { string packUri =value.ToString(); ImageSource Source = new ImageSourceConverter().ConvertFromString(packUri) as ImageSource; return Source; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } 

2 - associate your image source with the parent string (I used the "tag" property) for example, xaml:

 <Image HorizontalAlignment="Right" Height="Auto" Margin="0,11.75,5.5,10.75" VerticalAlignment="Stretch" Width="40.997" Source="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}"/> 
0
source share

All Articles