BindingExpression error when binding to DependencyProperty WPF - c#

I have a problem when I try to bind a int value to a DependencyProperty in a custom control from a style.
MyClassVM contains an int named Number. It shows perfectly in the Label, when I bind in the same way, but will not set on my custom control. If I change from "{Binding Number}" To "15" for example, everything works great also on the custom control.
<Style TargetType="{x:Type ItemsControl}" x:Key="TestKey">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Style.Resources>
<DataTemplate DataType="{x:Type local:MyClassVM}">
<StackPanel Orientation="Horizontal">
<StackPanel.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding DataContext.OpenProjectCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" CommandParameter="{Binding .}" />
</StackPanel.InputBindings>
<ctrl:MyCustomControl Margin="5" Width="50" ctrl:MyCustomControl.ValueProperty="{Binding Number}"/>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Number}" FontSize="14" Foreground="{DynamicResource CeriseBrush}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</Style.Resources>
</Style>
This is how the MyCustomControl-class looks.
public partial class MyCustomControl: CustomUserControl
{
public int Value { get { return _value; } set { _value = value; } }
public MyCustomControl()
{
InitializeComponent();
DataContext = this;
}
public string ValueProperty
{
get { return (string)GetValue(ValuePropertyProperty); }
set { SetValue(ValuePropertyProperty, value); }
}
public static readonly DependencyProperty ValuePropertyProperty =
DependencyProperty.Register("ValueProperty", typeof(int), typeof(MyCustomControl), new UIPropertyMetadata(ValuePropertyChangedHandler));
public static void ValuePropertyChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
((MyCustomControl)sender).Value = (int)e.NewValue;
}
}
The error I get looks like this:
System.Windows.Data Error: 40 : BindingExpression path error: 'Number' property not found on 'object' ''MyCustomControl' (Name='')'. BindingExpression:Path=Number; DataItem='MyCustomControl' (Name=''); target element is 'MyCustomControl' (Name=''); target property is 'ValueProperty' (type 'Int32')

Not sure, but I think you have to provide a relative source in your binding like:
Label Content="{Binding Number, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
Replace Window with UserControl if you're in a Control

Related

Struggling to template a WPF MenuItem's Icon with control

Following on from a previous question here, I'm struggling to style a MenuItem's Icon with a control I have that inserts icon images based upon a string dependency property.
Initially I started with:
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<Style TargetType="MenuItem">
...
<Setter Property="Icon">
<local:StringToIcon IconName="{Binding IconName}" />
</Setter>
</Style>
</ContextMenu.Resources>
</ContextMenu>
This had the predictable effect of only displaying one of the icons in the menu, usually the last one, as the instance was shared around.
I then tried the non-shared resource approach:
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<local:StringToIcon x:Key="MenuIcon" x:Shared="False" IconName="{Binding IconName}" />
<Style TargetType="MenuItem">
...
<Setter Property="Icon" Value="{StaticResource MenuIcon} />
</Style>
</ContextMenu.Resources>
</ContextMenu>
This had no effect. It didn't offer me x:Shared in Intellisense, so I wonder if that's an invalid property here.
Out of desperation, I threw the thing into a template:
<Setter Property="Icon">
<Setter.Value>
<ContentControl>
<ContentControl.Template>
<ControlTemplate>
<local:StringToIcon IconName="{Binding IconName}" />
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
</Setter.Value>
</Setter>
Again, no effect. My StringToIcon looks like this at the moment, hard-coded with a single image to check the problem doesn't lie there. (Or does it?)
<UserControl x:Class="RAP.Admin3.Components.StringToIcon"
...
>
<Image DataContext="{Binding ElementName=StringIconControl}" Source="pack://application:,,,/Resources/Icons/lorry.png"/>
</UserControl>
How do I get this darn thing to template and allow multiple uses? It's probably something basic I'm overlooking.
I've looked at various similar questions, and most seem to have success with the non-shared resource method.
Edit: Let me add substantially more code as requested. I've come up with a minimal replication of the problem:
The context menu is part of a TreeView resource.
<UserControl x:Class="MyApp.ItemHierarchy"
...
Name="ItemHierarchyControl">
<Grid>
<TreeView ItemsSource="{Binding ElementName=ItemHierarchyControl, Path=Items}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:HierarchyItem}" ItemsSource="{Binding Subitems}">
<StackPanel Orientation="Horizontal" Margin="0,1,4,1">
<TextBlock Text="My text" VerticalAlignment="Center" />
<StackPanel.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<local:StringToIcon x:Key="MenuIcon" x:Shared="False" IconName="{Binding IconName}" />
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Name}" />
<Setter Property="Icon" Value="{StaticResource MenuIcon}" />
</Style>
</ContextMenu.Resources>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</UserControl>
This is backed by a dependency property for the items.
public ObservableCollection<HierarchyItem> Items
{
get { return (ObservableCollection<HierarchyItem>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<HierarchyItem>), typeof(ItemHierarchy), new PropertyMetadata(new ObservableCollection<HierarchyItem>()));
StringToIcon is also backed by a string dependency property for the icon name, which is summarily ignored because of the hard-coded image at the moment.
HierarchyItems are simple for the example:
public ObservableCollection<HierarchyItem> Subitems { get; set; }
public ObservableCollection<BindableMenuItem> MenuItems { get; set; }
Just to get this proof working, I attached the ItemHierarchy to some properties of the main window:
public ObservableCollection<BindableMenuItem> MenuItems { get; set; }
public ObservableCollection<HierarchyItem> IHItems { get; set; }
public MainWindow()
{
MenuItems = new ObservableCollection<BindableMenuItem>();
MenuItems.Add(new BindableMenuItem("Item", null));
MenuItems.Add(new BindableMenuItem("Item", null));
MenuItems.Add(new BindableMenuItem("Item", null));
MenuItems.Add(new BindableMenuItem("Item", null));
IHItems = new ObservableCollection<HierarchyItem>();
IHItems.Add(new HierarchyItem() { MenuItems = this.MenuItems });
InitializeComponent();
}
Edit 2: Here's BindableMenuItem also:
public class BindableMenuItem
{
public BindableMenuItem(string name, ICommand command)
{
this.Name = name;
this.Command = command;
}
public string Name { get; set; }
public ICommand Command { get; set; }
public string IconName { get; set; }
public ObservableCollection<BindableMenuItem> Children { get; set; }
}
Try to move the StringToIcon to <TreeView.Resources>:
<TreeView ItemsSource="{Binding ElementName=ItemHierarchyControl, Path=Items}">
<TreeView.Resources>
<local:StringToIcon x:Key="MenuIcon" x:Shared="False" IconName="{Binding IconName}" />
<HierarchicalDataTemplate DataType="{x:Type local:HierarchyItem}" ItemsSource="{Binding Subitems}">
<StackPanel Orientation="Horizontal" Margin="0,1,4,1">
<TextBlock Text="My text" VerticalAlignment="Center" />
<StackPanel.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Name}" />
<Setter Property="Icon" Value="{StaticResource MenuIcon}" />
</Style>
</ContextMenu.Resources>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>

WPF DataTemplate, TemplateSelectors, ContentPresenter based on SelectedItem

I am very confused what to use and how to start implementations, but I want is based on a Enum property when changed a certain grid should display.
Currently I have like 20 grids, working with visibility when the property change.
This is not ideal for 2 things. All 20 grids will bind from startup and it isnot good for performance. Secondly some "Grids" are the same for some values of the enum property. So I have duplicate code in some grids.
Now what I have is an enum:
public enum MyEnumsForDropDown
{
Enum1= 1,
Enum2= 2,
Enum3= 3,
Enum4= 4,
Enum5= 5
}
My Object in my ViewModel, which I bind to is for :
Public class MyObject
{
private Enums.MyEnumsForDropDown _myChosenEnum;
public Enums.MyEnumsForDropDown MyChosenEnum
{
get { return _myChosenEnum; }
set
{
_myChosenEnum = value;
this.NotifyPropertyChanged( x => x.MyChosenEnum );
}
}
}
My XAML:
<ComboBox ItemsSource="{Binding CollectionOfEnums}"
DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding
MyObject.MyChosenEnum}"></ComboBox>
<Grid Grid.Row="1" Grid.Column="3" Visibility="{Binding
Path=MyObject.MyChoseEnum, Converter={StaticResource
EnumToVisibleCollapseConverter}, ConverterParameter={x:Static
myenumsNameSpace:Enums+MyEnumsForDropDown.Enum1}}">
<TextBlock Content"This Grid displays when Enum1 is chosen"/>
</Grid>
<Grid Grid.Row="1" Grid.Column="3" Visibility="{Binding
Path=MyObject.MyChoseEnum, Converter={StaticResource
EnumToVisibleCollapseConverter}, ConverterParameter={x:Static
myenumsNameSpace:Enums+MyEnumsForDropDown.Enum2}}">
<TextBlock Content"This Grid displays when Enum2 is chosen"/>
</Grid>
How do I change the Grids to work somehow like ContentPresenters or DataTemplates or whatever I need to use depended on when the property MyChosenEnum changes in my object??
You could define a DataTemplate for each enum value and then use a ContentControl with a Style to display the correct one:
<ContentControl Content="{Binding MyObject.MyChosenEnum}">
<ContentControl.Resources>
<DataTemplate x:Key="Enum1">
<Grid />
</DataTemplate>
<DataTemplate x:Key="Enum2">
<Grid />
</DataTemplate>
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding MyObject.MyChosenEnum}"
Value="{x:Static myenumsNameSpace:Enums+MyEnumsForDropDown.Enum1}">
<Setter Property="ContentTemplate" Value="{StaticResource Enum1}" />
</DataTrigger>
<DataTrigger Binding="{Binding MyObject.MyChosenEnum}"
Value="{x:Static myenumsNameSpace:Enums+MyEnumsForDropDown.Enum2}">
<Setter Property="ContentTemplate" Value="{StaticResource Enum2}" />
</DataTrigger>
<!-- and so on for each enum value -->
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Or you could use a DataTemplateSelector:
public class YourSelector : DataTemplateSelector
{
public DataTemplate Enum1 { get; set; }
public DataTemplate Enum2 { get; set; }
//...
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
MyEnumsForDropDown value = (MyEnumsForDropDown)item;
switch(value)
{
case MyEnumsForDropDown.Enum1:
return Enum1;
case MyEnumsForDropDown.Enum2:
return Enum2;
}
return base.SelectTemplate(item, container);
}
}
<Grid>
<Grid.Resources>
<DataTemplate x:Key="Enum1">
<Grid />
</DataTemplate>
<DataTemplate x:Key="Enum2">
<Grid />
</DataTemplate>
<local:YourSelector x:Key="selector" Enum1="{StaticResource Enum1}" Enum2="{StaticResource Enum2}" />
</Grid.Resources>
<ContentControl Content="{Binding MyObject.MyChosenEnum}"
ContentTemplateSelector="{StaticResource selector}" />
</Grid>

Lookless Control's ItemTemplatePanel Not Able to See Dependency Property on Control

So when I use the below style, it is applied to my control as expected. However, the templates inside of the GridView (ItemsPanelTemplate and ItemsTemplate) look at the view model the consumer applies for its data context.
The problem is that I want to set the item dimensions in my control.
So my question is, how do I apply the control template as the data context to the ItemsPanelTemplate and the ItemTemplate?
My first thought was to use ancestral binding but that doesn't appear to be a feature in UWP.
My Control Class
public class FilterableImageWrapGrid : FilterableContentList
{
private GridView _partGridView;
public Point ItemDimensions
{
get { return (Point)GetValue(ItemDimensionsProperty); }
set { SetValue(ItemDimensionsProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemDimensions. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemDimensionsProperty =
DependencyProperty.Register("ItemDimensions", typeof(Point), typeof(FilterableImageWrapGrid), new PropertyMetadata(new Point()));
public FilterableImageWrapGrid()
{
DefaultStyleKey = typeof(FilterableImageWrapGrid);
}
protected override void OnApplyTemplate()
{
_partGridView = GetTemplateChild("PART_FilterableImageList") as GridView;
base.OnApplyTemplate();
}
private static void OnItemDimensionsChanged(object sender, DependencyPropertyChangedEventArgs args)
{
FilterableImageWrapGrid wrapGrid = sender as FilterableImageWrapGrid;
if (wrapGrid != null && wrapGrid._partGridView != null)
{
wrapGrid._partGridView.ItemTemplate.SetValue(GridViewItem.WidthProperty, wrapGrid.ItemDimensions.X);
wrapGrid._partGridView.ItemTemplate.SetValue(GridViewItem.HeightProperty, wrapGrid.ItemDimensions.Y);
}
}
}
My style in my Generic.xaml file
<Style TargetType="controls:FilterableImageWrapGrid">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid VerticalAlignment="Stretch">
<GridView
x:Name="PART_FilterableImageList"
ItemsSource="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=FilteredItems, Mode=TwoWay}"
SelectedItem="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=SelectedContentItem, Mode=TwoWay}">
<GridView.ItemContainerTransitions>
<TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="True"/>
<AddDeleteThemeTransition />
<EdgeUIThemeTransition Edge="Left"/>
</TransitionCollection>
</GridView.ItemContainerTransitions>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid
x:Name="PART_ItemsWrapGrid"
ItemHeight="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ItemDimensions.Y}"
ItemWidth="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ItemDimensions.X}"
Margin="2" Orientation="Horizontal"
HorizontalAlignment="Center"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate>
<DataTemplate>
.... Data template that binds to the view model the consumer provides....
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If I understand you correctly it doesn't work to have the RelativeSource Mode=TemplatedParent binding in a template inside a template. Is that it?
then define the ItemDimensions property as an attached Dependency Property:
public class FilterableImageWrapGrid : FilterableContentList
public static Point GetItemDimensions(DependencyObject obj)
{
return (Point)obj.GetValue(ItemDimensionsProperty);
}
public static void SetItemDimensions(DependencyObject obj, Point value)
{
obj.SetValue(ItemDimensionsProperty, value);
}
public static readonly DependencyProperty ItemDimensionsProperty =
DependencyProperty.RegisterAttached("ItemDimensions", typeof(Point), typeof(ItemsWrapGrid), new PropertyMetadata(new Point()));
...
}
and then add this to the template:
<GridView
x:Name="PART_FilterableImageList"
...
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid
x:Name="PART_ItemsWrapGrid"
ItemHeight="{Binding RelativeSource=
{RelativeSource Mode=Self}, Path=ItemDimensions.Y}"
ItemWidth="{Binding RelativeSource=
{RelativeSource Mode=Self}, Path=ItemDimensions.X}"
Margin="2" Orientation="Horizontal"
HorizontalAlignment="Center"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
...
</GridView>
That would be like an inheritable property (which don't exist in uwp (yet?)) that you manually push downward the visual tree to the inner template with the template binding.

Bind nested Command in Control Template

I have following control template:
public sealed partial class ItemSelectorControl : Control
{
...
public ICommand SelectionCommand
{
get { return (ICommand)GetValue(SelectionCommandProperty); }
set { SetValue(SelectionCommandProperty, value); }
}
public static readonly DependencyProperty SelectionCommandProperty =
DependencyProperty.Register("SelectionCommand", typeof(ICommand), typeof(ItemSelectorControl), new PropertyMetadata(null));
public ItemSelectorControl()
{
DefaultStyleKey = typeof(ItemSelectorControl);
}
...
}
and the corresponding theme style:
<Style TargetType="controls:ItemSelectorControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:ItemSelectorControl">
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock
Style="{StaticResource SectionHeader}"
Text="{TemplateBinding Headline}"
Visibility="{TemplateBinding HeadlineVisibility}"/>
<ItemsControl
x:Name="CurrencyItemPanel"
ItemsSource="{TemplateBinding ItemCollection}"
MaxHeight="{TemplateBinding MaximumItemsControlHeight}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Style="{StaticResource ItemSelectorButtonStyle}"
Command="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=SelectionCommand}"
CommandParameter="{Binding Path=Code}">
<interactivity:Interaction.Behaviors>
<behaviour:ItemSelectorVisualStateBehaviour
StateChangeTrigger="{Binding Selected, Mode=TwoWay}"/>
</interactivity:Interaction.Behaviors>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" HorizontalAlignment="Left" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
How do I have to write the command binding?
Command="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=SelectionCommand}"
In WPF I would work with FindAncestor but since that is not available in WinRT I don't not how to bind? I can't use DataContext.SelectionCommand because the current DataContext is the ViewModel.

Binding expression exception when clicking on TreeView top level (root) item

I'm seeing the following binding expression being thrown from a TreeView control ONLY when I've clicked on any of the root items in the tree view - if I click on any other item I DO NOT see a binding expression until I click on a root Item then I see the binding exception all the time.
The binding expression path is missing leading, 'Asset' & 'Trade' are values of a Name property on the Node class, which is a hierarchical data structure, it has a child collection of type Node etc...
The items are bound to the TreeView using a ListCollectionView.
.Net 4.0 WPF app running on Win7.
The class bound to the TreeView is as follows:
internal class Node : INotifyPropertyChanged
{
public string Id { get; private set; }
public string Name { get; private set; }
public Node Parent { get; private set; }
public IEnumerable<Node> Children { get; private set; }
}
Any ideas why this is happening?
System.Windows.Data Error: 40 : BindingExpression path error: 'Asset'
property not found on 'object' ''Node' (HashCode=-729436854)'.
BindingExpression:Path=Asset; DataItem='Node' (HashCode=-729436854);
target element is 'TreeView' (Name='FieldTreeView'); target property
is 'NoTarget' (type 'Object')
System.Windows.Data Error: 40 :
BindingExpression path error: 'Trade' property not found on 'object'
''Node' (HashCode=-729436854)'. BindingExpression:Path=Trade;
DataItem='Node' (HashCode=-729436854); target element is 'TreeView'
(Name='FieldTreeView'); target property is 'NoTarget' (type 'Object')
This is the XAML for the TreeView, it being used as part of a WPF user control:
<TreeView Name="FieldTreeView"
Grid.Row="1"
Margin="0,15,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
SelectedItemChanged="FieldTreeViewOnSelectedItemChanged">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children, Mode=OneWay}">
<Grid>
<TextBlock Text="{Binding Path=Name, Mode=OneWay}" />
<Grid.ContextMenu>
<ContextMenu ItemsSource="{Binding Path=FieldMenu, Mode=OneWay}"
DisplayMemberPath="Title"
Visibility="{Binding Path=HasFieldMenu, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding}" />
<Setter Property="CommandParameter"
Value="{Binding Path=DataContext.Id, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded, Mode=TwoWay}" />
<EventSetter Event="MouseDoubleClick" Handler="FilterTreeViewOnItemMouseDoubleClick" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
The code used to populate the Node classes is shown below:
private void InitialiseFields()
{
_rootNode.RemoveAllChildren();
foreach (var field in _fields.OrderBy(x => x.Id))
{
NodeHelper.TokenizeIntoNodes(field, field.Id.Split(Node.IdSeperator), _menuMediator, _rootNode);
}
_nodes = _rootNode.Children.Cast<Node>().ToArray();
FieldTreeView.ItemsSource = _rootNode.Children;
}

Categories