Attached Properties in templates/styles and used in binding?
I have a situation with a few templates/styles that sometimes need one type of binding path prefix and sometimes another. I have a base ObjectIconBase control, and it branches on the presence of specific components to figure out if it's a specific type of icon that needs direct entity component access (using ["ComponentName"] format), or needs a redirect to another entity, e.g. the ViewModel is actually for a tree node wrapping something more like the former, but eventually needs to get into that same accessor.
That is to say that sometimes, I'm looking for this binding, using [] directly:
And sometimes this, with a redirect to the target entity that this tree node is wrapping (the node is a FleetNodeEntityViewModel, also an EntityViewModel, but when that UI node VM is representing an underlying ship in the world, we want to be able to selectively drill down to that underlying EntityViewModel and check its components instead, rather that duplicating every style/template to have one for each pathway)
In trying to make this configurable, I'm using Attached Properties, initially I tried using a string, hoping I could build the above example in a kind of 'entityPathProperty + "[IconOverrideComponent].IconName"' way, but obviously you can't get that kind of dynamicism in bindings, so now I'm trying to just expose my entity, as an EntityViewModel, directly.
My base icon control switches which template it uses based on the presence of certain components. I've tried setting the EntityProperties:Entity property in a million different ways, whether it's directly on the template we switch to (where 'DirectOwnerEntity' is the ship EntityViewModel we're wrapping, else our DataContext must be a ship's EntityViewModel directly):
or inside the triggers' setters:
And no matter what, I can't get it to work inside the ObjectIconOverrideTemplate I posted at the start.
Directly binding
Path and/or relative source
I usually don't get any errors if I use Path, it just silently fails, or if just binding I get something like this, where FleetNodeEntityViewModel is an EntityViewModel subclass, and is the ViewModel underneath
Was looking at this previous suggestion (viewtopic.php?p=15757#p15760) and a StackOverflow post about attached properties (https://stackoverflow.com/questions/303 ... 7#30385037) but can't get it to work.
Any help making this entity-component look-up redirectable in this manner would be appreciated.
That is to say that sometimes, I'm looking for this binding, using [] directly:
Code: Select all
<ControlTemplate x:Key="ObjectIconOverrideTemplate">
<Grid>
<Image Source="{Binding [IconOverrideComponent].IconName}" Stretch="Uniform" Height="Auto" Width="Auto"/>
<Rectangle Height="Auto" Width="Auto" Stretch="Uniform" Style="{StaticResource ObjectIconColourStyle}">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="{Binding [IconOverrideComponent].IconName}"/>
</Rectangle.OpacityMask>
</Rectangle>
</Grid>
</ControlTemplate>
Code: Select all
<ControlTemplate x:Key="ObjectIconOverrideTemplate">
<Grid>
<Image Source="{Binding SomeEntityRedirectPropertyHere[IconOverrideComponent].IconName}" Stretch="Uniform" Height="Auto" Width="Auto"/>
<Rectangle Height="Auto" Width="Auto" Stretch="Uniform" Style="{StaticResource ObjectIconColourStyle}">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="{Binding SomeEntityRedirectPropertyHere[IconOverrideComponent].IconName}"/>
</Rectangle.OpacityMask>
</Rectangle>
</Grid>
</ControlTemplate>
Code: Select all
namespace rtg.ViewModels
{
public static class EntityProperties
{
public static readonly DependencyProperty EntityProperty = DependencyProperty.RegisterAttached(
"Entity", typeof(EntityViewModel), typeof(EntityProperties));
public static void SetEntity(DependencyObject element, EntityViewModel value)
{
element.SetValue(EntityProperty, value);
}
public static EntityViewModel GetEntity(DependencyObject element)
{
return (EntityViewModel)element.GetValue(EntityProperty);
}
}
}
Code: Select all
<UserControl x:Class="rtg.Controls.ObjectIconBase"
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:local="clr-namespace:rtg.Controls"
xmlns:viewModels="clr-namespace:rtg.ViewModels"
mc:Ignorable="d" HorizontalAlignment="Stretch"
>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.Resources>
<ControlTemplate x:Key="ShipIconControlTemplate">
<local:SpaceObjectIcon
viewModels:EntityProperties.Entity="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}">
</local:SpaceObjectIcon>
</ControlTemplate>
<ControlTemplate x:Key="WorldObjectIconTemplate">
<local:WorldObjectIcon viewModels:EntityProperties.Entity="{Binding DirectOwnerEntity}"></local:WorldObjectIcon>
</ControlTemplate>
<!-- Switch to ShipIconControlTemplate if the VM has a valid DirectOwnerEntity-->
<Style x:Key="ObjectIconTemplateControlStyle" TargetType="Control">
<Style.Triggers>
<DataTrigger Binding="{Binding DirectOwnerEntity, Converter={StaticResource IsNotNullConverter}}" Value="True">
<Setter Property="Template" Value="{StaticResource ShipIconControlTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding DirectOwnerEntity, Converter={StaticResource IsNotNullConverter}}" Value="False">
<Setter Property="Template" Value="{StaticResource WorldObjectIconTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
Code: Select all
<!-- Switch to ShipIconControlTemplate if the VM has a valid DirectOwnerEntity-->
<Style x:Key="ObjectIconTemplateControlStyle" TargetType="Control">
<Style.Triggers>
<DataTrigger Binding="{Binding DirectOwnerEntity, Converter={StaticResource IsNotNullConverter}}" Value="True">
<Setter Property="Template" Value="{StaticResource ShipIconControlTemplate}"/>
<Setter Property="viewModels:EntityProperties.Entity" Value="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}"/>
</DataTrigger>
<DataTrigger Binding="{Binding DirectOwnerEntity, Converter={StaticResource IsNotNullConverter}}" Value="False">
<Setter Property="Template" Value="{StaticResource WorldObjectIconTemplate}"/>
<Setter Property="viewModels:EntityProperties.Entity" Value="{Binding DirectOwnerEntity}"/>
</DataTrigger>
</Style.Triggers>
</Style>
Directly binding
Code: Select all
<ControlTemplate x:Key="ObjectIconOverrideTemplate">
<Grid>
<Image Source="{Binding (viewModels:EntityProperties.Entity)[IconOverrideComponent].IconName}" Stretch="Uniform" Height="Auto" Width="Auto"/>
<Rectangle Height="Auto" Width="Auto" Stretch="Uniform" Style="{StaticResource ObjectIconColourStyle}">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="{Binding (viewModels:EntityProperties.Entity)[IconOverrideComponent].IconName}"/>
</Rectangle.OpacityMask>
</Rectangle>
</Grid>
</ControlTemplate>
Code: Select all
<ControlTemplate x:Key="ObjectIconOverrideTemplate">
<Grid>
<Image Source="{Binding RelativeSource={RelativeSource Self}, Path=(viewModels:EntityProperties.Entity)[IconOverrideComponent].IconName}" Stretch="Uniform" Height="Auto" Width="Auto"/>
<Rectangle Height="Auto" Width="Auto" Stretch="Uniform" Style="{StaticResource ObjectIconColourStyle}">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="{Binding RelativeSource={RelativeSource Self}, Path=(viewModels:EntityProperties.Entity)[IconOverrideComponent].IconName}"/>
</Rectangle.OpacityMask>
</Rectangle>
</Grid>
</ControlTemplate>
And sometimes a "Cannot find governing FrameworkElement for target element" type of error.System.Windows.Data Error: 17 : Cannot get 'Entity' value (type 'EntityViewModel') from '' (type 'FleetNodeEntityViewModel'). BindingExpression:Path=(0)[IconOverrideComponent].IconName; DataItem='FleetNodeEntityViewModel' (HashCode=15114764); target element is 'Image' (Name=''); target property is 'Source' (type 'ImageSource') InvalidCastException:'System.InvalidCastException: Unable to cast object of type 'rtg.ViewModels.FleetNodeEntityViewModel' to type 'System.Windows.DependencyObject'.
at MS.Internal.Data.PropertyPathWorker.GetValue(Object item, Int32 level)
at MS.Internal.Data.PropertyPathWorker.RawValue(Int32 k)'
Was looking at this previous suggestion (viewtopic.php?p=15757#p15760) and a StackOverflow post about attached properties (https://stackoverflow.com/questions/303 ... 7#30385037) but can't get it to work.
Any help making this entity-component look-up redirectable in this manner would be appreciated.
-
sfernandez
Site Admin
- Posts: 3013
- Joined:
Re: Attached Properties in templates/styles and used in binding?
If you expose a property "Target" in your viewmodel that returns the component that you want to bind (the node or the target entity) it will simplify the binding to just:
Is this what you are trying to do?
Regarding the attached properties remember they can only be used in DependencyObjects, they won't work on your view models. So a binding like the following might not be correct because is trying to look for a DependencyProperty in the DataContext (your view model?):
Code: Select all
<Image Source="{Binding Target[IconOverrideComponent].IconName}"/>
Regarding the attached properties remember they can only be used in DependencyObjects, they won't work on your view models. So a binding like the following might not be correct because is trying to look for a DependencyProperty in the DataContext (your view model?):
Code: Select all
<Image Source="{Binding (viewModels:EntityProperties.Entity)[IconOverrideComponent].IconName}"/>
Re: Attached Properties in templates/styles and used in binding?
Not exactly, I don't think - I'm trying to have these templates/styles be able to re-target to a different path based on something like an Attached Property, but not all of them will do that. So you can imagine if I had a Target property, but only want some of my shared templates/styles to use that, and some to use the ViewModel itself, and those are mixed together, setting a single Target property and using it for everything breaks that. Specifically, the node _is_ an entity as well, and sometimes we want to read components off of it, and sometimes off the target entity, but only these node entities _have_ a target entity, so it gets a bit funky trying to have a single point set at the ViewModel level for all things that might use it. Sometimes Target would be the self/datacontext, sometimes a nested entity, and those are mixed in the same overall control.
An example might be a shield/health bar that is reusable on a side-panel/menu built out of nodes, but also used in-world in an object's clickable world icon - the node might wants that reusable control/template to access TargetEntity[HealthComponent], but an in-world object icon that has no node, only the target entity, will just directly look up the component as [HealthComponent] because its view model is the entity itself. The control will know, based on e.g. DataTrigger, whether it should go one way or the other, so I figured Attached Properties were the fix. Right now I have a copy-pasted version of every case like this so it can go down one path or the other, and that's not great.
To your point - am I not setting my attached properties on a control template, thus on a control and not the view model, like a Canvas.Left or other such attached property examples? Is the issue that I can't use them to talk to/access view models, even if they are set on a control? I'm referring to the "ObjectIconTemplateControlStyle" that swaps the template based on DataTriggers - I figured that meant I was in DependencyObject territory but may be wrong (am new to these!).
An example might be a shield/health bar that is reusable on a side-panel/menu built out of nodes, but also used in-world in an object's clickable world icon - the node might wants that reusable control/template to access TargetEntity[HealthComponent], but an in-world object icon that has no node, only the target entity, will just directly look up the component as [HealthComponent] because its view model is the entity itself. The control will know, based on e.g. DataTrigger, whether it should go one way or the other, so I figured Attached Properties were the fix. Right now I have a copy-pasted version of every case like this so it can go down one path or the other, and that's not great.
To your point - am I not setting my attached properties on a control template, thus on a control and not the view model, like a Canvas.Left or other such attached property examples? Is the issue that I can't use them to talk to/access view models, even if they are set on a control? I'm referring to the "ObjectIconTemplateControlStyle" that swaps the template based on DataTriggers - I figured that meant I was in DependencyObject territory but may be wrong (am new to these!).
-
sfernandez
Site Admin
- Posts: 3013
- Joined:
Re: Attached Properties in templates/styles and used in binding?
So it is the control that uses the data who knows (through the attached property) if it has to reference the entity directly or the target. Then, if the attached property is set in the control, and assuming the control is the root of the xaml you could have:
So the binding points to the control, where the attached property is set. Is this what you are looking for?
Code: Select all
<UserControl x:Class="TheNamespace.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="MyControlRoot">
...
<Image Source="{Binding (viewModels:EntityProperties.Entity)[IconOverrideComponent].IconName, ElementName=MyControlRoot}"/>
...
</UserControl>
Who is online
Users browsing this forum: No registered users and 0 guests