dotnet/wpf · error · InvalidOperationException
SR.TriggersSupportsEventTriggersOnly
Error message
SR.TriggersSupportsEventTriggersOnly
What it means
During trigger processing (StyleHelper.ProcessTriggerCollection), a trigger in a FrameworkTemplate's trigger collection is not an EventTrigger, so ProcessOneTrigger cannot attach its event handler and throws. Template triggers support only EventTrigger; property triggers belong in Style.Triggers.
Solutions
- Convert the trigger to an EventTrigger (only type allowed in ControlTemplate.Triggers).
- Move property/data triggers from the template into the Style's Triggers collection (Style.Triggers).
- Use VisualStateManager states inside the template instead of property triggers.
Example fix
<!-- before -->
<ControlTemplate>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">...</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<!-- after -->
<Style>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">...</Trigger>
</Style.Triggers>
</Style> Defensive patterns
Strategy: validation
Validate before calling
foreach (var t in template.Triggers)
if (t is not EventTrigger)
throw new InvalidOperationException("ControlTemplate.Triggers supports only EventTrigger."); Type guard
bool IsTemplateTrigger(TriggerBase t) => t is EventTrigger;
Try / catch
try { ApplyTemplate(el); }
catch (InvalidOperationException) { /* move non-event triggers into Style.Triggers */ } Prevention
- Only declare EventTrigger in ControlTemplate.Triggers.
- Place Trigger/DataTrigger/MultiTrigger in Style.Triggers instead.
- Prefer VisualStateManager inside templates for state-based visuals.
When it happens
Trigger: Placing a Trigger, MultiTrigger, or DataTrigger inside a ControlTemplate/FrameworkTemplate's Triggers collection, which is then processed when the template is instantiated.
Common situations: Copying trigger XAML from a Style into a ControlTemplate; believing ControlTemplate.Triggers accepts DataTrigger; tooling that emits generic trigger elements inside templates.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- SR.TemplateTargetTypeMismatch
- SR.Animation_ChildMustBeKeyFrame
- SR.Animation_ChildMustBeKeyFrame
- SR.Animation_ChildMustBeKeyFrame
- SR.Animation_ChildMustBeKeyFrame
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/499c7edb79a7ba3a.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/EventTrigger.cs:321
if( eventTrigger != null )
{
Debug.Assert( eventTrigger._routedEventHandler == null && eventTrigger._source == null);
// PERF: Cache this result if it turns out we're doing a lot of lookups on the same name.
eventTrigger._source = FrameworkElement.FindNamedFrameworkElement( triggersHost, eventTrigger.SourceName );
// Create a statefull event delegate (which keeps a ref to the FE).
EventTriggerSourceListener listener = new EventTriggerSourceListener( eventTrigger, triggersHost );
// Store the RoutedEventHandler & target for use in DisconnectOneTrigger
eventTrigger._routedEventHandler = new RoutedEventHandler(listener.Handler);
eventTrigger._source.AddHandler( eventTrigger.RoutedEvent, eventTrigger._routedEventHandler,
false /* HandledEventsToo */ );
}
else
{
throw new InvalidOperationException(SR.TriggersSupportsEventTriggersOnly);
}
}
////////////////////////////////////////////////////////////////////////
//
// DisconnectAllTriggers
//
// Call DisconnectOneTrigger for each trigger in the Triggers collection.
internal static void DisconnectAllTriggers( FrameworkElement triggersHost )
{
TriggerCollection triggerCollection = TriggerCollectionField.GetValue(triggersHost);
if( triggerCollection != null )
{
for( int i = 0; i < triggerCollection.Count; i++ )
{
DisconnectOneTrigger( triggersHost, triggerCollection[i] );View on GitHub (pinned to 81131a70a4)