HateDread
Topic Author
Posts: 74
Joined: 06 Feb 2020, 10:27

How do I "pass" variables/data into a child control/element with minimal/no code behind? Esp. for binding.

19 Jun 2023, 13:00

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!
<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>
This is how I use them
<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>
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.
<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>
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.
<Control ControlTemplate="{StaticResource ArmourPiecesControl}" Width="{StaticResource ControlWidth}" Height="{StaticResource ControlHeight}" ArmourImagesBase="/NameOfShipGoesHere"/>
Which would resolve in the control to something like this, using the (ideally) re-usable rectangles from earlier:
<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!
 
mshvern23
Posts: 35
Joined: 22 May 2023, 02:00

Re: How do I "pass" variables/data into a child control/element with minimal/no code behind? Esp. for binding.

20 Jun 2023, 00:05

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:
<ContentControl Content="{Binding Route}" Template="{StaticResource Template.Route}" Style="{StaticResource Style.Route}" />
And the template for it is defined elsewhere with
<ControlTemplate x:Key="Template.Route" TargetType="{x:Type ContentControl}">
...
</ControlTemplate>
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:
<noesis:DataEventTrigger Source="{Binding DataContext,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" EventName="OnRouteSpawn">
 
User avatar
sfernandez
Site Admin
Posts: 3363
Joined: 22 Dec 2011, 19:20

Re: How do I "pass" variables/data into a child control/element with minimal/no code behind? Esp. for binding.

20 Jun 2023, 19:17

You can define some attached properties that you can use as a common point between the control and the style/template:
<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>
<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>
 
HateDread
Topic Author
Posts: 74
Joined: 06 Feb 2020, 10:27

Re: How do I "pass" variables/data into a child control/element with minimal/no code behind? Esp. for binding.

21 Jun 2023, 12:46

You can define some attached properties that you can use as a common point between the control and the style/template:
<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>
<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>
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 like
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
 
User avatar
sfernandez
Site Admin
Posts: 3363
Joined: 22 Dec 2011, 19:20

Re: How do I "pass" variables/data into a child control/element with minimal/no code behind? Esp. for binding.

21 Jun 2023, 13:09

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
struct PropHelper
{
  static const DependencyProperty* ArmourProperty;
  static const DependencyProperty* MaxArmourProperty;

  NS_DECLARE_REFLECTION(PropHelper, NoParent)
};
PropHelper.cpp
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));
}
XAML
<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>
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.

Who is online

Users browsing this forum: Bing [Bot] and 4 guests