steveh
Topic Author
Posts: 42
Joined: 07 Oct 2019, 12:50

EventTriggers inside ControlTemplates - avoiding leaks

05 May 2022, 23:53

Hi guys,

So I'm attempting to fix the last remaining leaks in our game and I can't find a nice solution to the following problem.

So currently I'm using the NsApp EventTrigger inside a ControlTemplate to invoke a custom command action when a bespoke routed event is raised:
    <ControlTemplate x:Key="CustomControl.Template"  TargetType="{x:Type Namespace:CustomControl}">
        <Grid x:Name="Root" 
              Background="{TemplateBinding Background}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="CustomEvent" SourceObject="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}}">
                    <i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Namespace:CustomControl}}, Path=(Namespace:CustomControl.CustomAction)}" />
                </i:EventTrigger>
                <i:EventTrigger EventName="CustomEvent2" SourceObject="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}}">
                    <i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Namespace:CustomControl}}, Path=(Namespace:CustomControl.CustomAction2)}" CommandParameter="{Binding}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Grid>
    </ControlTemplate>
The issue is that SourceObject takes a ref counted pointer to the templated parent so it creates a cyclic reference which prevents the custom control from being cleaned up. I've managed to fix this in most other cases by using the NsGui.TriggerAction instead, i.e:
    <ControlTemplate x:Key="CustomControl.Template"  TargetType="{x:Type Namespace:CustomControl}">
        <ControlTemplate.Triggers>
            <EventTrigger RoutedEvent="Namespace:CustomControl.CustomEvent">
                <BeginStoryboard x:Name="CustomEventStoryboard" Storyboard="{StaticResource Storyboard}" />
            </EventTrigger>
            <EventTrigger RoutedEvent="Namespace:CustomControl.CustomEvent2">
                <BeginStoryboard x:Name="CustomEvent2Storyboard" Storyboard="{StaticResource Storyboard}" />
            </EventTrigger>
        </ControlTemplate.Triggers>
        <Grid x:Name="Root" 
                  Background="{TemplateBinding Background}" >
        </Grid>
    </ControlTemplate>
This fixes the bugs and allows me to replace NsApp.EventTrigger so long as the only action I do is BeginStoryboard, StopStoryboard or ControlStoryboard (as those are the only implemented actions which derive from NsGui.TriggerAction).

Is there a replacement for InvokeCommandAction? How would you suggest I go about doing this? Is there any other way for me to reference an event on the control template's templated parent without causing a cyclic dependency? A relative source binding or template binding do not work.

Cheers,

-Steven
 
User avatar
sfernandez
Site Admin
Posts: 3363
Joined: 22 Dec 2011, 19:20

Re: EventTriggers inside ControlTemplates - avoiding leaks

06 May 2022, 10:54

Hi Steve,

If the event is going to be raised by the templated parent you should be able to do the same by moving the EventTrigger to the control, using a Style if it needs to be reused:
<Grid>
  <Grid.Resources>
    <ControlTemplate x:Key="CustomControl.Template"  TargetType="{x:Type local:CustomControl}">
      <Grid x:Name="Root" Background="{TemplateBinding Background}" .../>
    </ControlTemplate>
    <Style x:Key="CustomControl.Style" TargetType="{x:Type local:CustomControl}">
      <Setter Property="Background" Value="Silver"/>
      <Setter Property="Template" Value="{StaticResource CustomControl.Template}"/>
      <Setter Property="noesis:StyleInteraction.Triggers">
        <Setter.Value>
          <noesis:StyleTriggerCollection>
            <b:EventTrigger EventName="CustomEvent">
              <b:InvokeCommandAction
                Command="{Binding (local:CustomControl.CustomAction),
                    RelativeSource={RelativeSource AncestorType={x:Type local:CustomControl}}}"/>
            </i:EventTrigger>
            <b:EventTrigger EventName="CustomEvent2">
              <b:InvokeCommandAction
                Command="{Binding (local:CustomControl.CustomAction2),
                    RelativeSource={RelativeSource AncestorType={x:Type local:CustomControl}}}"
                CommandParameter="{Binding}"/>
            </i:EventTrigger>
          </noesis:StyleTriggerCollection>
        </Setter.Value>
      </Setter>
    </Style>
  </Grid.Resources>
  <local:CustomControl Width="200" Height="100" Style="{StaticResource CustomControl.Style}"/>
</Grid>
Hope this helps.

Who is online

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