Page 1 of 1

i:Interaction.Triggers on Templates and Custom Controls are not instanced

Posted: 08 Apr 2021, 06:02
by BigAnt-JustenG
Currently trying to implement a Custom Control or Style that will act as a standard template for most of our views.
This standard view contains the following triggers;
<ControlTemplate>
    <Grid>
        <Grid.Resources>
        ... Intro and Outro anims
        </Grid.Resources>
        <ContentPresenter/>
        <i:Interaction.Triggers>
            <ei:DataTrigger Binding="{Binding m_playIntro, Mode=OneWay}" Value="True">
                <ei:ControlStoryboardAction Storyboard="{StaticResource Intro}" ControlStoryboardOption="Play" />
            </ei:DataTrigger>
            <ei:StoryboardCompletedTrigger Storyboard="{StaticResource Intro}">
                <noesis:SetFocusAction TargetName="RootGrid"/>
            </ei:StoryboardCompletedTrigger>
            <ei:DataTrigger Binding="{Binding m_playOutro, Mode=OneWay}" Value="True">
                <ei:ControlStoryboardAction Storyboard="{StaticResource Outro}" ControlStoryboardOption="Play" />
            </ei:DataTrigger>
            <ei:StoryboardCompletedTrigger Storyboard="{StaticResource Outro}">
                <i:InvokeCommandAction Command="{Binding OnOutroComplete}" />
            </ei:StoryboardCompletedTrigger>
        </i:Interaction.Triggers>
    </Grid>
</ControlTemplate>
This Style contains the required Intro and Outro storyboards.
In the regular occurrences that there is more than one object using this style at the same time, when the StoryboardCompletedTrigger is fired, it is fired on all instances at the same time.
This then calls the InvokeCommandAction on the OnOutroComplete callback on all instances that are using this style, even though they were not specifically playing the Outro animation.
Is there a way to create an instance of this resource so that these trigger only function per instance?
Rather than a static global instance being used for all objects using this style.

Alternatively, is there another way to implement a standard set of Triggers across a collection of views without needing to have the same code written out a numerous amount of times?

Another thought I had;
Looking through the Custom Controls documentation I found the use of ResourceKeyType and Overriding the DefaultStyleKey property.
Here -> https://www.noesisengine.com/docs/Gui.C ... trols.html
Is the ResourceKeyType::Create(type) function creating an instance of this resource? If so this might be what I'm looking for.
However, it seems that this ResourceKeyType has been removed from Noesis 3.0

Re: i:Interaction.Triggers on Templates and Custom Controls are not instanced

Posted: 09 Apr 2021, 16:50
by sfernandez
Hi Justen,

The correct way of doing this in WPF is by defining the Storyboards as part of the resources of an element of the template VisualTree:
<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
  xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">
  <Grid.Resources>
    <ControlTemplate x:Key="t" TargetType="Control">
      <Border x:Name="root" Background="Red">
        <Border.Resources>
          <Storyboard x:Key="intro">
            <ColorAnimation To="Blue" Duration="0:0:0.5" Storyboard.TargetName="root" Storyboard.TargetProperty="Background.Color"/>
          </Storyboard>
          <Storyboard x:Key="outro">
            <ColorAnimation To="Red" Duration="0:0:0.5" Storyboard.TargetName="root" Storyboard.TargetProperty="Background.Color"/>
          </Storyboard>
        </Border.Resources>
        <i:Interaction.Triggers>
          <i:EventTrigger EventName="MouseEnter">
            <ei:ControlStoryboardAction Storyboard="{StaticResource intro}"/>
          </i:EventTrigger>
           <i:EventTrigger EventName="MouseLeave">
            <ei:ControlStoryboardAction Storyboard="{StaticResource outro}"/>
          </i:EventTrigger>
          <ei:StoryboardCompletedTrigger Storyboard="{StaticResource intro}">
            <ei:ChangePropertyAction TargetName="root" PropertyName="Width" Value="150"/>
          </ei:StoryboardCompletedTrigger>
          <ei:StoryboardCompletedTrigger Storyboard="{StaticResource outro}">
            <ei:ChangePropertyAction TargetName="root" PropertyName="Width" Value="200"/>
          </ei:StoryboardCompletedTrigger>
       </i:Interaction.Triggers>
      </Border>
    </ControlTemplate>
  </Grid.Resources>
  <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <Control Width="200" Height="100" Margin="0,10" Template="{StaticResource t}"/>
    <Control Width="200" Height="100" Margin="0,10" Template="{StaticResource t}"/>
  </StackPanel>
</Grid>
Unfortunately there is a known issue in Noesis template instantiation that causes the problem you are seeing, that all instances of the template share the same Storyboard and trigger the Completed event for all controls. Could you please report it to keep track of this issue?

In the meantime there is a workaround you can use:
<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
  xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">
  <Grid.Resources>
    <ControlTemplate x:Key="t" TargetType="Control">
      <Border x:Name="root" Background="Red">
        <i:Interaction.Triggers>
          <i:EventTrigger EventName="MouseEnter">
            <ei:ControlStoryboardAction>
              <ei:ControlStoryboardAction.Storyboard>
                <Storyboard>
                  <ColorAnimation To="Blue" Duration="0:0:0.5" Storyboard.TargetName="root" Storyboard.TargetProperty="Background.Color"/>
                </Storyboard>
              </ei:ControlStoryboardAction.Storyboard>
            </ei:ControlStoryboardAction>
          </i:EventTrigger>
           <i:EventTrigger EventName="MouseLeave">
            <ei:ControlStoryboardAction>
              <ei:ControlStoryboardAction.Storyboard>
                <Storyboard>
                  <ColorAnimation To="Red" Duration="0:0:0.5" Storyboard.TargetName="root" Storyboard.TargetProperty="Background.Color"/>
                </Storyboard>
              </ei:ControlStoryboardAction.Storyboard>
            </ei:ControlStoryboardAction>
          </i:EventTrigger>
          <ei:StoryboardCompletedTrigger Storyboard="{Binding (i:Interaction.Triggers)[0].Actions[0].Storyboard, ElementName=root}">
            <ei:ChangePropertyAction TargetName="root" PropertyName="Width" Value="150"/>
          </ei:StoryboardCompletedTrigger>
          <ei:StoryboardCompletedTrigger Storyboard="{Binding (i:Interaction.Triggers)[1].Actions[0].Storyboard, ElementName=root}">
            <ei:ChangePropertyAction TargetName="root" PropertyName="Width" Value="200"/>
          </ei:StoryboardCompletedTrigger>
       </i:Interaction.Triggers>
      </Border>
    </ControlTemplate>
  </Grid.Resources>
  <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <Control Width="200" Height="100" Margin="0,10" Template="{StaticResource t}"/>
    <Control Width="200" Height="100" Margin="0,10" Template="{StaticResource t}"/>
  </StackPanel>
</Grid>
Another thought I had;
Looking through the Custom Controls documentation I found the use of ResourceKeyType and Overriding the DefaultStyleKey property.
Here -> https://www.noesisengine.com/docs/Gui.C ... trols.html
Is the ResourceKeyType::Create(type) function creating an instance of this resource? If so this might be what I'm looking for.
However, it seems that this ResourceKeyType has been removed from Noesis 3.0
That documentation is not correctly updated to the latest version, thanks for pointing it out. We are going to review and update it for the next release.
The way you should override the DefaultStyleKey property is like this:
data->OverrideMetadata<const Type*>(FrameworkElement::DefaultStyleKeyProperty,
    "DefaultStyleKey", PropertyMetadata::Create<const Type*>(TypeOf<MyCustomControl>()));
Anyway, adding this to your control won't fix the problem you described. That property is only used to specify the type that should be looked when getting the default Style.

Re: i:Interaction.Triggers on Templates and Custom Controls are not instanced

Posted: 17 Jun 2021, 17:09
by steveh
Hey Sergio, I've just encountered the bug where the storyboard is triggered for each DataTemplate instance inside an ItemsControl. I resolved it by using a TimerTrigger instead with the time set to the same length as the storyboard.

Did this bug ever get reported on the bugtracker? I just had a quick look and couldn't see it.

Cheers,

-Steven

Re: i:Interaction.Triggers on Templates and Custom Controls are not instanced

Posted: 21 Jun 2021, 10:03
by sfernandez
I created the ticket #2052 to keep track of this issue, thanks a lot for the reminder.