Binding fails when used inside MenuItem.Icon - wpf

I want to offer a context menu with an item that has a color swatch in the space where "icons" are normally placed for such menu items, i.e. the space corresponding to MenuItem.Icon.
But the color swatch is dynamic--a Brush property on the UserControl that (in this crafted example) changes to a random color in response to the ContextMenuOpening event--and my attempt at binding to it is failing.
When run, the menu item has no content in the Icon space, and Visual Studio's output contains an error that doesn't seem like it ought to be happening.
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ContextMenu', AncestorLevel='1''. BindingExpression:Path=PlacementTarget.RandomBrush; DataItem=null; target element is 'Rectangle' (Name=''); target property is 'Fill' (type 'Brush')
Here's the XAML for the control:
<UserControl x:Class="ContextMenuItemIconTest.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="300" d:DesignWidth="300">
<UserControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Do something">
<MenuItem.Icon>
<Rectangle Width="16" Height="16" Fill="{Binding PlacementTarget.RandomBrush, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</UserControl.ContextMenu>
<Grid>
</Grid>
And the code behind:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ContextMenuItemIconTest
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
ContextMenuOpening += UserControl1_ContextMenuOpening;
}
void UserControl1_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
Random r = new Random();
RandomBrush = new SolidColorBrush(Color.FromRgb((byte)r.Next(256), (byte)r.Next(256), (byte)r.Next(256)));
}
#region RandomBrush (Dependency Property)
public Brush RandomBrush
{
get { return (Brush)GetValue(RandomBrushProperty); }
set { SetValue(RandomBrushProperty, value); }
}
public static readonly DependencyProperty RandomBrushProperty =
DependencyProperty.Register(
"RandomBrush",
typeof(Brush),
typeof(UserControl1),
new PropertyMetadata(new SolidColorBrush(Colors.Blue)));
#endregion
}
}

Not sure if there is a better solution but I think the scenario here is very tricky. The Icon content seems to be detached completely from the visual tree. So you cannot use Binding with RelativeSource or ElementName and strangely even setting the Source to some {x:Reference} causes some cyclic reference error.
I just could think of this work-around, a little hacky but it's acceptable. There is an interesting knowledge about the Freezable object. Binding inside it (set for some property) can use RelativeSource as well as ElementName even when it's just declared as a resource (added to Resources). So in this case we can try using some object deriving from Freezable to act as the proxy. Because this proxy is declared as a resource, we can set the Binding inside Icon with Source being set to some StaticResource referencing the proxy. Then it would work. There are many objects deriving from Freezable for your choice, you can even create your own class. But I would like to use something existing here. The DiscreteObjectKeyFrame is the most suitable object to use. Technically its Value property can hold any kind of object. Now's the working code:
<ContextMenu>
<ContextMenu.Resources>
<!-- the proxy here -->
<DiscreteObjectKeyFrame x:Key="o" KeyTime="0"
Value="{Binding PlacementTarget.RandomBrush,
RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu.Resources>
<MenuItem Header="Do something">
<MenuItem.Icon>
<Rectangle Width="16" Height="16"
Fill="{Binding Value, Source={StaticResource o}}" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>

Related

Custom attached property not found on resource when embedded in user control

I have a resource that need to be a different color depending on where it is used, so I use this attached property:
public static class AssetProperties
{
public static Brush GetFillBrush(DependencyObject obj)
{
return (Brush)obj.GetValue(FillBrushProperty);
}
public static void SetFillBrush(DependencyObject obj, Brush value)
{
obj.SetValue(FillBrushProperty, value);
}
public static readonly DependencyProperty FillBrushProperty =
DependencyProperty.RegisterAttached("FillBrush",
typeof(Brush),
typeof(AssetProperties),
new FrameworkPropertyMetadata(new BrushConverter().ConvertFrom("#FFE41300"), FrameworkPropertyMetadataOptions.Inherits));
}
We define the symbol and use it something like this in a window or user control (this is of course a lot simplified, the resource is for example defined in a separate file) :
<Grid>
<Grid.Resources>
<ResourceDictionary>
<Rectangle x:Key="SomeColorfulSymbol" x:Shared="False" Width="10" Height="10"
Fill="{Binding (main:AssetProperties.FillBrush), RelativeSource={RelativeSource Self}}" />
</ResourceDictionary>
</Grid.Resources>
<ContentControl Content="{StaticResource SomeColorfulSymbol}" main:AssetProperties.FillBrush="Blue"/>
</Grid>
This works as intended, a nice blue rectangle appears. Without setting the attached property, the rectangle is the default red of the FillBrush attached property.
The problem is when we try to use the symbol inside a custom user control defined like this:
OuterControl.xaml:
<UserControl x:Class="AttachedPropertyResourceTest.OuterControl"
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="300" d:DesignWidth="300">
<Grid>
<StackPanel>
<TextBlock Text="Some title"/>
<ContentControl Content="{Binding InnerContent, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"/>
</StackPanel>
</Grid>
</UserControl>
OuterControl.xaml.cs:
[ContentProperty("InnerContent")]
public partial class OuterControl
{
public FrameworkElement InnerContent
{
get { return (FrameworkElement)GetValue(InnerContentProperty); }
set { SetValue(InnerContentProperty, value); }
}
public static readonly DependencyProperty InnerContentProperty =
DependencyProperty.Register("InnerContent", typeof(FrameworkElement), typeof(OuterControl), new FrameworkPropertyMetadata(null));
public OuterControl()
{
InitializeComponent();
}
}
Now if I wrap the ContentControl in the above snippet like this instead:
<main:OuterControl>
<ContentControl Content="{StaticResource SomeColorfulSymbol}"/>
</main:OuterControl>
it looks good in the VS designer, a title plus a rectangle that is the default red of FillBrush. In runtime however we only get the title. The rectangle gets no color (UnsetValue) and we get this binding error:
System.Windows.Data Error: 40 : BindingExpression path error:
'(main:AssetProperties.FillBrush)' property not found on 'object'
''Rectangle' (Name='')'.
BindingExpression:Path=(main:AssetProperties.FillBrush);
DataItem='Rectangle' (Name=''); target element is 'Rectangle'
(Name=''); target property is 'Fill' (type 'Brush')
If I add an invisible instance of the symbol before the wrapped one, it works again, i.e., a red rectangle appears:
<ContentControl Content="{StaticResource SomeColorfulSymbol}" Visibility="Collapsed"/>
<main:OuterControl>
<ContentControl Content="{StaticResource SomeColorfulSymbol}"/>
</main:OuterControl>
One problem is that the attached property is not registered, when I put a breakpoint on the RegisterAttached method it is not called without the extra invisible ContentControl. This is however only a part of the problem, for example forcing the registration like this does not work:
<StackPanel>
<TextBlock Text="I'm red!" Background="{Binding (main:AssetProperties.FillBrush), RelativeSource={RelativeSource Self}}"/>
<main:OuterControl>
<ContentControl Content="{StaticResource SomeColorfulSymbol}"/>
</main:OuterControl>
</StackPanel>
The text "I'm red" is actually red and the attached property is registered, but we get the exact same binding error.
I also tried without the ContentProperty["InnerContent"], setting the InnerContent attribute explicitly in xaml, with the same result.
Could someone shed some light on this?
Maybe using a control template instead of OuterControl wouldn't have this problem (?), but there is a lot of behavior associated with OuterControl and I would prefer this approach.
To prevent the following issue, try specifying the path property explicitly like:{Binding Path=(main:....}
<Rectangle x:Key="SomeColorfulSymbol" x:Shared="False" Width="10" Height="10" Fill="{Binding Path=(main:AssetProperties.FillBrush), RelativeSource={RelativeSource Self}}" />

WPF Binding with DataContext on Custom Content Control

I have a custom wizard control WizardControl deriving from UserControl which has a dependency property called Pages with a data type of my custom class collection called WizardPageCollection.
The WizardControl is hosted in a Window with a view model called MainViewModel and the pages of the wizard instantiated using XAML.
I am trying to bind the pages to sub-view models Page1VM and Page2VM declared as properties on the MainViewModel.
The first page binding of DataContext to Page1VM works fine, however the binding of the second page fails with the following error message:
System.Windows.Data Error: 3 : Cannot find element that provides DataContext. BindingExpression:Path=Page2VM; DataItem=null; target element is 'MyPage' (Name=''); target property is 'DataContext' (type 'Object')
Q. Why does the binding work on the first page but fail on the second and is there a way I can get this to work whilst still keeping the MainViewModel declared within the DataContext XAML tags of MainWindow? I would prefer not to use the ViewModel as a dictionary resources as this has some implications for us which I won't go into detail about.
As suggested by a commentor, if I change the binding to use RelativeSource as follows:
<common:MyPage DataContext="{Binding DataContext.Page1VM, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
<common:MyPage DataContext="{Binding DataContext.Page2VM, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
The first binding works ok, but the second one still fails, but with a different error message (as expected):
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=DataContext.Page2VM; DataItem=null; target element is 'MyPage' (Name=''); target property is 'DataContext' (type 'Object')
Thanks for your time!
My code listing is shown below:
MainWindow XAML:
<Window.DataContext>
<common:MainViewModel />
</Window.DataContext>
<Grid>
<common:WizardControl>
<common:WizardControl.Pages>
<common:WizardPageCollection>
<common:MyPage DataContext="{Binding Page1VM}" />
<common:MyPage DataContext="{Binding Page2VM}" />
</common:WizardPageCollection>
</common:WizardControl.Pages>
</common:WizardControl>
</Grid>
MainViewModel and PageViewModel:
public class MainViewModel
{
public PageViewModel Page1VM
{
get;
set;
}
public PageViewModel Page2VM
{
get;
set;
}
public MainViewModel()
{
this.Page1VM = new PageViewModel("Page 1");
this.Page2VM = new PageViewModel("Page 2");
}
}
public class PageViewModel
{
public string Title { get; set; }
public PageViewModel(string title) { this.Title = title; }
}
WizardControl XAML:
<Grid>
<ContentPresenter Grid.Row="0" x:Name="contentPage"/>
</Grid>
WizardControl code-behind:
public partial class WizardControl : UserControl
{
public WizardControl()
{
InitializeComponent();
}
public WizardPageCollection Pages
{
get { return (WizardPageCollection)GetValue(PagesProperty); }
set { SetValue(PagesProperty, value); }
}
public static readonly DependencyProperty PagesProperty =
DependencyProperty.Register("Pages", typeof(WizardPageCollection), typeof(WizardControl), new PropertyMetadata(new WizardPageCollection(), new PropertyChangedCallback(Pages_Changed)));
static void Pages_Changed(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
WizardPageCollection col = e.NewValue as WizardPageCollection;
WizardControl ctrl = obj as WizardControl;
ctrl.contentPage.Content = col.First();
}
}
public class WizardPageCollection : ObservableCollection<WizardPageBase> { }
public class WizardPageBase : ContentControl { }
MyPage XAML:
<Grid>
<Label Content="{Binding Title}" />
</Grid>
Your approach depends on the value inheritance of the Window's DataContext property, which doesn't work with your WizardPageCollection because it doesn't form a WPF element tree.
You should instead create your MainViewModel as a resource, and then reference it by StaticResource:
<Window ...>
<Window.Resources>
<common:MainViewModel x:Key="MainViewModel"/>
</Window.Resources>
<Window.DataContext>
<Binding Source="{StaticResource MainViewModel}"/>
</Window.DataContext>
<Grid>
<common:WizardControl>
<common:WizardControl.Pages>
<common:WizardPageCollection>
<common:MyPage DataContext="{Binding Page1VM,
Source={StaticResource MainViewModel}}"/>
<common:MyPage DataContext="{Binding Page2VM,
Source={StaticResource MainViewModel}}"/>
</common:WizardPageCollection>
</common:WizardControl.Pages>
</common:WizardControl>
</Grid>
</Window>
#Clemens answer workarounds the problem but the problem is something else, imho.
When item is added to WizardPageCollection, it should be added to LogicalTree as well. Look at sources of ItemsControl for inspiration. It is definitelly possible to make your binding works as they were.
I would use viewmodel first approach here. Define pages as collection of page viewmodels and genereate the views. At the end the xaml would look like this:
<common:WizardControl PagesSource="{Binding Pages}">
<common:WizardControl.PageTemplate>
<DataTemplate>
<common:MyPage DataContext="{Binding }" />
</DataTemplate>
</common:WizardControl.PageTemplate>
</common:WizardControl>
alternativelly, consider your WizardControl derive from Selector class instead usercontrol. (Selector is base class from listbox. It has itemssource and selected item).
<common:WizardControl ItemsSource="{Binding Pages}"
SelectedItem="{Binding SelectedPage}">
<common:WizardControl.ItemTemplate>
<DataTemplate>
<common:MyPage DataContext="{Binding }" />
</DataTemplate>
</common:WizardControl.ItemTemplate>
</common:WizardControl>

ToolBar item DataTemplate binding RelativeSource search failing to find parent ToolBar

I have a ToolBar containing Buttons, some of the Buttons have only an Image for content, others have only Text. I am trying to bind the width property of the Button Image to a custom Property on my derived ToolBar class. It works sometimes but fails other times with the following error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='NuiWpfCore.Controls.ToolBar', AncestorLevel='1''. BindingExpression:Path=IconSize; DataItem=null; target element is 'Image' (Name=''); target property is 'Width' (type 'Double')
Here is the xaml containing the element binding that is failing. The DataTemplate is returned from a DataTemplateSelector which is created inline:
<pres:ToolBar x:Class="NuiWpfCore.Controls.ToolBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:pres="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:core="clr-namespace:NuiWpfCore"
xmlns:ctrls="clr-namespace:NuiWpfCore.Controls"
xmlns:select="clr-namespace:NuiWpfCore.Selectors"
xmlns:converters="clr-namespace:NuiWpfCore.Converters"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase">
<ToolBar.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/NuiWpfCore;component/Controls/MenuBarTemplate.xaml" />
</ResourceDictionary.MergedDictionaries>
<converters:ListPairToStringConverter x:Key="ListPairToStringConverter" />
<converters:IconMetaDataToImageConverter x:Key="IconMetaDataToImageConverter" />
<converters:IconMetaDataToImageConverterParameter x:Key="IconToImageConverterParameter"
ConvertToImage="False" Width="16" Height="16" />
</ResourceDictionary>
</ToolBar.Resources>
<ToolBar.ItemTemplateSelector>
<select:ToolBarItemDataTemplateSelector>
<!-- other DataTemplates omitted for brevity -->
<select:ToolBarItemDataTemplateSelector.IconCommand>
<DataTemplate DataType="{x:Type core:PropertyElement}">
<Button IsEnabled="{Binding Path=CanEdit}" Command="{Binding}">
<Button.Content>
<Image
Width="{Binding Path=IconSize,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ctrls:ToolBar}} }"
Height="{Binding Path=Width,
RelativeSource={RelativeSource Self}}"
Source="{Binding Path=MetaData,
Converter={StaticResource IconMetaDataToImageConverter},
ConverterParameter={StaticResource IconToImageConverterParameter}}"/>
</Button.Content>
</Button>
</DataTemplate>
</select:ToolBarItemDataTemplateSelector.IconCommand>
<!-- other DataTemplates omitted for brevity -->
</select:ToolBarItemDataTemplateSelector>
</ToolBar.ItemTemplateSelector>
</pres:ToolBar>
Here is the ToolBar class with the Source Property for the binding.
public partial class ToolBar : System.Windows.Controls.ToolBar, Views.IView
{
public ToolBar() : base()
{
InitializeComponent();
IconSize = 32;
}
public int IconSize { get; set; }
}
This ToolBar class is sometimes used in a ToolBarTray and other times it is not, but the bind search fails in both cases in certain scenarios.
Does anybody have any ideas as to why this might be failing?
Have you considered making IconSize on your ToolBar an inherited property?
public static readonly DependencyProperty IconSizeProperty =
DependencyProperty.RegisterAttached( "IconSize", typeof(double), typeof(ToolBar ),
new FrameworkPropertyMetadata(32, FrameworkPropertyMetadataOptions.Inherits));
public static double GetIconSize(DependencyObject target)
{
return (double)target.GetValue(IconSizeProperty);
}
public static void SetIconSize(DependencyObject target, double value)
{
target.SetValue(IconSizeProperty, value);
}
Then you can just access the IconSize like
<Button.Content>
<Image
Width="{Binding RelativeSource={RelativeSource Self}, ctrls::ToolBar.IconSize}"
Height="{Binding Path=Width,RelativeSource={RelativeSource Self}}"
Source="{Binding Path=MetaData,
Converter={StaticResource IconMetaDataToImageConverter},
ConverterParameter={StaticResource IconToImageConverterParameter}}"/>
First you should set it on your toolbar, and every other element down the tree can access this property.
Sorry out of my head, not 100% guarenteed to be correct. But the overall idea of
Value Inheritance is a good way to solve this.
The DataTemplate looks like it is being defined inside a DataTemplateSelector declaration, which isn't part of the Visual Tree, and so won't be able to navigate up from there if the Binding were being evaluated in that spot. Where is the template actually being applied?

How to access parent's DataContext from a UserControl

I need to access the container's DataContext from a UserControl (a grid containing textboxes and a listbox: I need to insert items in this list box) that I created in WPF: which is the best way to do it?
I was thinking to pass the DataContext as parameter to user control but think there is a cleaner way to do it.
Normally the DataContext will be inherited, just do not explicitly set it on the UserControl and it will get it from its parent. If you have to set it you could still use the Parent property to get the parent, which you then can safe-cast to a FrameworkElement and if it is not null you can grab its DataContext.
I sometimes have nested User controls and a grandchild usercontrol sometimes needs the grandparent's view's data context. The easiest way I have found so far (and I'm somewhat of a newbie) is to use the following:
<Shared:GranchildControl DataContext="{Binding RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type GrandparentView}},
Path=DataContext.GrandparentViewModel}" />
I wrote up a more detailed example on my blog if you want more specifics.
Add this BindingProxy class to your project:
using System.Windows;
namespace YourNameSpace
{
/// <summary>
/// Add Proxy <ut:BindingProxy x:Key="Proxy" Data="{Binding}" /> to Resources
/// Bind like <Element Property="{Binding Data.MyValue, Source={StaticResource Proxy}}" />
/// </summary>
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy));
}
}
Add the BindingProxy to your UserControl's resources.
Set the 'Data' property of the BindingProxy to whatever you need, e.g. search for a parent Window. Data="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=DataContext}" If you needed something more complex you could use a custom converter.
Now you have access to that parent's DataContext: {Binding Data.MyCommand, Source={StaticResource BindingProxy}}
<UserControl
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:common="clr-namespace:YourNameSpace;assembly=YourAssembly"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<common:BindingProxy x:Key="BindingProxy" Data="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=DataContext}" />
</UserControl.Resources>
<Border>
<Button Command="{Binding Data.MyCommand, Source={StaticResource BindingProxy}}">Execute My Command</Button>
<!-- some visual stuff -->
</Border>
</UserControl>
H.B. answers the question in your title.
However the text poses a different design question. I'd ask you to reconsider your design.
A control inherits the DataContext property of its ancestor as long as no one in between explicitly overrides.
If the user control needs data, it should get it from its data source (a viewmodel for the user control). So in this case, the user control can obtain the data it needs from the ListItemsForDisplay property exposed on the SomeViewModel instance. No need to get parent and cast.. much cleaner.
<ContainerType DataSource={Binding SomeViewModel}>
<YourUserControl>
<ListBox ItemsSource={Binding ListItemsForDisplay}"/>
...
In this case, UserControl will get DataContext windows
<Window>
<local:MyUserControl DataContext="{Binding}"/>
</Window>

WPF Custom Control: TemplateBinding to Image

I am creating a WPF custom control, a Button with an Image and Text. I have added two dependency properties to the control, ImagePath and Text, and the control template (in Themes\Generic.xaml) is a simple stack panel that arranges the image and text horizontally.
The Text property works fine. But for some reason, the sample image in my test project doesn't appear when I use TemplateBinding to the ImagePath dependency property to get its path. I have tested the image by temporarily replacing the TemplateBinding in the custom control with a path to the image, in which case it appears.
I am hoping that someone with more experience in this area can take a look and tell me why the control isn't working as expected. Thanks for your help.
My VS 2008 solution contains one project, CustomControlDemo. The project contains a custom control, TaskButton.cs, and a main window, Window1.xaml, that I use to test the control. My test image, calendar.png, is located in a Resources folder at the root level of the project, and Generic.xaml is located in a Themes folder, also at the root level of the project.
Here is the code for my custom 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 control 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 am using to test 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 isn't working? Thanks again.
I am going to leave cwap's answer as the accepted answer, because it is technically correct. However, it turns out that there is an easier way to solve this problem.
TemplateBindings aren't first-class Binding objects. They are designed to be lightweight, so they are one-way, and they lack some features of other Binding objects. Most notably, they don't support known type converters associated with a target. See MacDonald, Pro WPF in C# 2008, p. 872. That's why cwap responds correctly that I would probably need to create a type converter and reference it specifically in the control template for my custom button.
But I don't have to use a TemplateBinding to bind the control template to the ImagePath property of my custom control. I can use a plain old Binding object. Here is the revised markup for my custom control's 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 the ImageControl in the template, you can see the change. Note the RelativeSource property in the same object. Setting this property to ={RelativeSource TemplatedParent} is what lets me enter a relative path in my Window1 instance of the TaskButton and have it resolved correctly in the custom control.
So my recommendation for others researching this thread would be to skip the value converter and simply switch from TemplateBinding to Binding for the Image property.
Thanks also to Marco Zhou, who provided this answer to a similar question in the MSDN WPF forum.
Image doesn't take a string as a source :) You can see this in intellisense. You need to bind on an ImageSource (Or use an IValueConverter to convert the string to an ImageSource)
See this question for some tips on how to do this conversion.
Actually neither of these answers are correct.
{TemplateBinding ImagePath} is nothing more than a shortcut for {Binding Path=ImagePath, RelativeSource={RelativeSource TemplatedParent}} and as such is almost completely identical.
Also if you provide a string for ImagePath it will correctly resolve to an ImageSource although you take a hit in application performance. The real issue has to do with relative and absolute image path on the supplied ImagePath="Resources\calendar.png" in the xaml for the test. This clues the compiler to think that the supplied path is absolute because of the use of \ instead of / in defining the path.
The reason that the long form of the binding works and the shortcut doesn't is that it provides clues to the compiler that the source of the image supplied (Resources\calendar.png) is a relative path not an absolute path, therefore the image is found and the binding works. If you debug the binding you will see that the shortcut tries resolve the supplied string into an image source but can not find the file "Resources\calendar.png" If you provide a full URI to the image i.e "C:\...\Resources\calendar.png" or the corresponding blend notation of "/application;component/Resources/calendar.png" then the image will be found and the binding resolved.
This point becomes really important when you are trying to reference images from an external source instead of those compiled as resources into the final compilation.
simple way(tested)
1-make your valueConverter like this
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-bind your Image Source to parent's string properety (i used "tag" property)
like this 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}}"/>

Resources