How do I "pass" variables/data into a child control/element with minimal/no code behind? Esp. for binding.
A great example of this requirement is; I have the desire to display multiple single-coloured images (using rectangles) to represent different parts of a ship, e.g. one rectangle for the front, one for the left, the right, the rear, and the central hull. They should have some binding and logic to change colour based on the health of that part of the ship. The logic is the same; the only thing that should change is the binding for the values. Below, each "ArmourRectangle" should bind to a different source for armour value and max armour value. Imagine this 5+ times... kinda redundant!
This is how I use them
How can I pass the binding paths through to some re-usable ArmourRectangle control? I'd like to point each one at a different binding for the armour value, and the max armour value. This isn't correct XAML but you get the idea. It should use the same style (or control template if that's easier) but be able to take the binding path set from this parent.
Not sure how to pass a string through and then use it in a binding. I tried Attached Properties but it would take it as a string literal and not resolve into the [] operator.
Furthermore, if I wanted to pool these rectangles into a user control, it'd be great to have all of the above together, plus being able to pass a string for the base of the image path and do some string concat for the ImageSource, e.g.
Which would resolve in the control to something like this, using the (ideally) re-usable rectangles from earlier:
-----------------------------
I'm not asking for necessarily fixing all of these issues, just some ideas on how to get these values through. I've hit this frustration so many times; having to have many many duplicated styles or templates where the parent control should surely be able to pass in values and re-use the same one multiple times.
Thanks!
Code: Select all
<Style x:Key="ArmourRectangle_Front" TargetType="Rectangle">
<Setter Property="Fill" Value="{StaticResource DefaultArmourColour}"/>
<Style.Triggers>
<!-- Change colour if value < amount -->
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource IsLessThanPercentageConverter}" Mode="OneWay">
<Binding Path="[ArmourComponent].Front"/>
<Binding Path="[ArmourComponent].MaxFront"/>
<Binding Source="{StaticResource ArmourLowPercentage}"></Binding>
</MultiBinding>
</DataTrigger.Binding>
<DataTrigger.Setters>
<Setter Property="Fill" Value="{StaticResource LowArmourColour}"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="ArmourRectangle_Back" TargetType="Rectangle">
<Setter Property="Fill" Value="{StaticResource DefaultArmourColour}"/>
<Style.Triggers>
<!-- Change colour if value < amount -->
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource IsLessThanPercentageConverter}" Mode="OneWay">
<Binding Path="[ArmourComponent].Back"/>
<Binding Path="[ArmourComponent].MaxBack"/>
<Binding Source="{StaticResource ArmourLowPercentage}"></Binding>
</MultiBinding>
</DataTrigger.Binding>
<DataTrigger.Setters>
<Setter Property="Fill" Value="{StaticResource LowArmourColour}"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
Code: Select all
<Rectangle Style="{StaticResource ArmourRectangle_Front}" Width="{StaticResource ControlWidth}" Height="{StaticResource ControlHeight}">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="/Galactica_FrontPiece.png"/>
</Rectangle.OpacityMask>
</Rectangle>
<Rectangle Style="{StaticResource ArmourRectangle_Back}" Width="{StaticResource ControlWidth}" Height="{StaticResource ControlHeight}">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="/Galactica_BackPiece.png"/>
</Rectangle.OpacityMask>
</Rectangle>
Code: Select all
<Rectangle Style="{StaticResource ArmourRectangle}" Width="{StaticResource ControlWidth}" Height="{StaticResource ControlHeight}" ArmourPath="[ArmourComponent].Front" MaxArmourPath="[ArmourComponent].MaxFront">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="/Galactica_FrontPiece.png"/>
</Rectangle.OpacityMask>
</Rectangle>
<Rectangle Style="{StaticResource ArmourRectangle}" Width="{StaticResource ControlWidth}" Height="{StaticResource ControlHeight}" ArmourPath="[ArmourComponent].Back" MaxArmourPath="[ArmourComponent].MaxBack">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="/Galactica_BackPiece.png"/>
</Rectangle.OpacityMask>
</Rectangle>
Furthermore, if I wanted to pool these rectangles into a user control, it'd be great to have all of the above together, plus being able to pass a string for the base of the image path and do some string concat for the ImageSource, e.g.
Code: Select all
<Control ControlTemplate="{StaticResource ArmourPiecesControl}" Width="{StaticResource ControlWidth}" Height="{StaticResource ControlHeight}" ArmourImagesBase="/NameOfShipGoesHere"/>
Code: Select all
<Rectangle Style="{StaticResource ArmourRectangle}" Width="{StaticResource ControlWidth}" Height="{StaticResource ControlHeight}" ArmourPath="[ArmourComponent].Front" MaxArmourPath="[ArmourComponent].MaxFront">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="{ArmourImagesBase}_FrontPiece.png"/>
</Rectangle.OpacityMask>
</Rectangle>
<Rectangle Style="{StaticResource ArmourRectangle}" Width="{StaticResource ControlWidth}" Height="{StaticResource ControlHeight}" ArmourPath="[ArmourComponent].Back" MaxArmourPath="[ArmourComponent].MaxBack">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="{ArmourImagesBase}_BackPiece.png"/>
</Rectangle.OpacityMask>
</Rectangle>
I'm not asking for necessarily fixing all of these issues, just some ideas on how to get these values through. I've hit this frustration so many times; having to have many many duplicated styles or templates where the parent control should surely be able to pass in values and re-use the same one multiple times.
Thanks!
Re: How do I "pass" variables/data into a child control/element with minimal/no code behind? Esp. for binding.
I would create a control template with the Rectangle and then path a "{Binding ArmourComponent}" as a Content property.
E.g. in my project I have a concept of Route which I reference in XAML as:
And the template for it is defined elsewhere with
And inside that I can use any part of the Route object.
And If I have to pull something not from Route but from parent I can do that as well, in my case a UserControl has a DataContext with needed information. Consider the following code for example:
E.g. in my project I have a concept of Route which I reference in XAML as:
Code: Select all
<ContentControl Content="{Binding Route}" Template="{StaticResource Template.Route}" Style="{StaticResource Style.Route}" />
Code: Select all
<ControlTemplate x:Key="Template.Route" TargetType="{x:Type ContentControl}">
...
</ControlTemplate>
And If I have to pull something not from Route but from parent I can do that as well, in my case a UserControl has a DataContext with needed information. Consider the following code for example:
Code: Select all
<noesis:DataEventTrigger Source="{Binding DataContext,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" EventName="OnRouteSpawn">
-
-
sfernandez
Site Admin
- Posts: 3363
- Joined:
Re: How do I "pass" variables/data into a child control/element with minimal/no code behind? Esp. for binding.
You can define some attached properties that you can use as a common point between the control and the style/template:
Code: Select all
<Style x:Key="ArmourRectangle" TargetType="Rectangle">
<Setter Property="Fill" Value="{StaticResource DefaultArmourColour}"/>
<Style.Triggers>
<!-- Change colour if value < amount -->
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource IsLessThanPercentageConverter}" Mode="OneWay">
<Binding Path="(local:PropHelper.Armour)" RelativeSource="{RelativeSource Self}"/>
<Binding Path="(local:PropHelper.ArmourMax)" RelativeSource="{RelativeSource Self}"/>
<Binding Source="{StaticResource ArmourLowPercentage}"/>
</MultiBinding>
</DataTrigger.Binding>
<DataTrigger.Setters>
<Setter Property="Fill" Value="{StaticResource LowArmourColour}"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>Code: Select all
<Rectangle Style="{StaticResource ArmourRectangle}" Width="{StaticResource ControlWidth}" Height="{StaticResource ControlHeight}"
local:PropHelper.Armour="{Binding [ArmourComponent].Front}" local:PropHelper.ArmourMax="{Binding [ArmourComponent].MaxFront}">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="/Galactica_FrontPiece.png"/>
</Rectangle.OpacityMask>
</Rectangle>Re: How do I "pass" variables/data into a child control/element with minimal/no code behind? Esp. for binding.
This looks great, but what is "PropHelper" in this context and how do I set that up? I'm not familiar with attached properties. Should this "just work"? Get things likeYou can define some attached properties that you can use as a common point between the control and the style/template:Code: Select all<Style x:Key="ArmourRectangle" TargetType="Rectangle"> <Setter Property="Fill" Value="{StaticResource DefaultArmourColour}"/> <Style.Triggers> <!-- Change colour if value < amount --> <DataTrigger Value="True"> <DataTrigger.Binding> <MultiBinding Converter="{StaticResource IsLessThanPercentageConverter}" Mode="OneWay"> <Binding Path="(local:PropHelper.Armour)" RelativeSource="{RelativeSource Self}"/> <Binding Path="(local:PropHelper.ArmourMax)" RelativeSource="{RelativeSource Self}"/> <Binding Source="{StaticResource ArmourLowPercentage}"/> </MultiBinding> </DataTrigger.Binding> <DataTrigger.Setters> <Setter Property="Fill" Value="{StaticResource LowArmourColour}"/> </DataTrigger.Setters> </DataTrigger> </Style.Triggers> </Style>Code: Select all<Rectangle Style="{StaticResource ArmourRectangle}" Width="{StaticResource ControlWidth}" Height="{StaticResource ControlHeight}" local:PropHelper.Armour="{Binding [ArmourComponent].Front}" local:PropHelper.ArmourMax="{Binding [ArmourComponent].MaxFront}"> <Rectangle.OpacityMask> <ImageBrush ImageSource="/Galactica_FrontPiece.png"/> </Rectangle.OpacityMask> </Rectangle>
Error XDG0008 PropHelper is not supported in a Windows Presentation Foundation (WPF) project. rtg C:\_Development\Repositories\rtg\tools\NoesisGUI\RTG_Noesis\Resources.xaml 463
Error XDG0008 PropHelper is not supported in a Windows Presentation Foundation (WPF) project. rtg C:\_Development\Repositories\rtg\tools\NoesisGUI\RTG_Noesis\Resources.xaml 464
Error XLS0415 The attachable property 'Armour' was not found in type 'PropHelper'. rtg C:\_Development\Repositories\rtg\tools\NoesisGUI\RTG_Noesis\Controls\ShipListEntryView.xaml 77
Error XDG0008 The name "PropHelper" does not exist in the namespace "clr-namespace:rtg.Controls". rtg C:\_Development\Repositories\rtg\tools\NoesisGUI\RTG_Noesis\Controls\ShipListEntryView.xaml 77
-
-
sfernandez
Site Admin
- Posts: 3363
- Joined:
Re: How do I "pass" variables/data into a child control/element with minimal/no code behind? Esp. for binding.
An attached property is a dependency property defined by a class that can be applied to other dependency objects. Examples of this kind of properties are Canvas.Left, Canvas.Top... Grid.Column, Grid.Row... ScrollViewer.HorizontalScrollBarVisibility...
You can define your own dependency properties to be used as attached properties in xaml:
PropHelper.h
PropHelper.cpp
XAML
Note: Make sure you call TypeOf<PropHelper>() during your application startup (usually when you register the components in the factory), so reflection is created and ready to be used when loading xamls.
You can define your own dependency properties to be used as attached properties in xaml:
PropHelper.h
Code: Select all
struct PropHelper
{
static const DependencyProperty* ArmourProperty;
static const DependencyProperty* MaxArmourProperty;
NS_DECLARE_REFLECTION(PropHelper, NoParent)
};Code: Select all
const DependencyProperty* PropHelper::ArmourProperty;
const DependencyProperty* PropHelper::MaxArmourProperty;
NS_IMPLEMENT_REFLECTION(PropHelper, "SomeNamespace.PropHelper")
{
Noesis::UIElementData* data = NsMeta<Noesis::UIElementData>(Noesis::TypeOf<SelfClass>());
data->RegisterProperty<float>(ArmourProperty, "Armour", Noesis::PropertyMetadata::Create(0.0f));
data->RegisterProperty<float>(MaxArmourProperty, "MaxArmour", Noesis::PropertyMetadata::Create(0.0f));
}Code: Select all
<Grid
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SomeNamespace">
<Rectangle local:PropHelper.Armour="10" local:PropHelper.MaxArmour="50"/>
</Grid>Who is online
Users browsing this forum: Bing [Bot] and 4 guests