-
Notifications
You must be signed in to change notification settings - Fork 142
Description
Is your feature request related to a problem? Please describe.
I was looking at a use case where I wanted multiple triggers to fire the same action. I could have repeated the action for every trigger, but it smells bad. I had a hard time writing a solution because the access modifiers for various structures are limited. My solution was a "MultiTrigger" implementation that utilizes a TriggerCollection, but due to its accessibility, I had to had it together using the Interaction.GetTriggers method (see additional context).
Describe the solution you'd like
TriggerCollection is general enough that it could have a public constructor.
https://github.com/microsoft/XamlBehaviorsWpf/blob/master/src/Microsoft.Xaml.Behaviors/TriggerCollection.cs#L17
The only usage of the class is in Interaction and that is locked down to prevent a TriggerCollection from overwriting an existing TriggerCollection on the TriggersProperty.
https://github.com/microsoft/XamlBehaviorsWpf/blob/master/src/Microsoft.Xaml.Behaviors/Interaction.cs#L36
https://github.com/microsoft/XamlBehaviorsWpf/blob/master/src/Microsoft.Xaml.Behaviors/Interaction.cs#L62
While its understandable that future changes could break the TriggerCollection and leaving it as internal could prevent a breaking change, I think this risk is warranted.
Describe alternatives you've considered
- Writing my own TriggerCollection implementation. It would essentially amount to a copy paste of existing code and thereby smells bad.
- Having XamlBehaviorsWpf implement a MultiTrigger (this may be preferable to control breaking changes)
Additional context
Here is my MultiTrigger implementation. I have my reasons for using Return/Tab instead of a DataTrigger and I believe that conversation is moot to the general problem of a multitrigger.
XAML:
xmlns:CoreBehaviors="clr-namespace:MyCode.Core.Behaviors"
...
<TextBox
x:Name="tbLookup">
<b:Interaction.Triggers>
<CoreBehaviors:MultiTrigger>
<CoreBehaviors:MultiTrigger.Triggers>
<b:KeyTrigger Key="Return" />
<b:KeyTrigger Key="Tab" />
</CoreBehaviors:MultiTrigger.Triggers>
<b:InvokeCommandAction Command="{Binding LookupCommand}" CommandParameter="{Binding Text, ElementName=tbLookup}" />
</CoreBehaviors:MultiTrigger>
</b:Interaction.Triggers>
</TextBox>MultiTrigger.cs:
using Microsoft.Xaml.Behaviors;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using TriggerBase = Microsoft.Xaml.Behaviors.TriggerBase;
namespace MyCode.Core.Behaviors
{
public class MultiTrigger : TriggerBase<DependencyObject>
{
public MultiTrigger()
{
Triggers = Interaction.GetTriggers(this); //HACK: use GetTriggers to "construct" TriggerCollection
Triggers.Detach(); //HACK: We need to immediately detach the associated object (this) so we can attach it to this instances associated object in OnAttached
((INotifyCollectionChanged)Triggers).CollectionChanged += MultiTrigger_CollectionChanged;
}
public Microsoft.Xaml.Behaviors.TriggerCollection Triggers { get; }
private void MultiTrigger_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
foreach (TriggerBase item in e.OldItems.Cast<TriggerBase>())
foreach(MultiTriggerActionAdapter? action in item.Actions.OfType<MultiTriggerActionAdapter>())
item.Actions.Remove(action);
if (e.NewItems != null)
foreach (TriggerBase item in e.NewItems.Cast<TriggerBase>())
item.Actions.Add(new MultiTriggerActionAdapter(this));
}
protected override void OnAttached()
{
Triggers.Attach(AssociatedObject);
base.OnAttached();
}
protected override void OnDetaching()
{
Triggers.Detach();
base.OnDetaching();
}
private class MultiTriggerActionAdapter : TriggerAction<DependencyObject>
{
private readonly MultiTrigger _parent;
public MultiTriggerActionAdapter(MultiTrigger parent)
{
_parent = parent;
}
protected override void Invoke(object parameter)
=> _parent.InvokeActions(parameter);
}
}
}