EventTriggerでプロパティを設定できるようにしたいのですが、これには多くの問題があります。
1)EventTriggersはアクションのみをサポートするため、storyBoardを使用してプロパティを設定する必要があります。
2)ストーリーボードを使用したら、2つのオプションがあります。
以下の例では、ボタンがクリックされたときにIsCheckedプロパティをFalseに設定し、ユーザーがIsCheckedを変更できるようにしたい、および/またはコードでプロパティを変更できるようにしたいです。
例:
<EventTrigger
SourceName="myButton"
RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames
Storyboard.TargetName="myCheckBox"
Storyboard.TargetProperty="IsChecked"
FillBehavior="Stop">
<DiscreteBooleanKeyFrame
KeyTime="00:00:00"
Value="False" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
ストーリーボードが完了した後に「Completed」イベントを使用して値をFalseに設定できることに気付きました。ただし、このインスタンスでは、XAML内にロジックを含める必要があります。このロジックはカスタムコントロールで使用され、UIにのみ固有であるためです。
私はXAMLが大好きなので、この種のタスクではコードビハインドに切り替えます。 Attached behaviors はこれに適したパターンです。 Expression Blend 3 標準的な方法を提供します を使用して、ビヘイビアをプログラミングおよび使用することを忘れないでください。 Expression Communityサイトには いくつかの既存のもの があります。
独自のアクションを作成するだけです。
namespace WpfUtil
{
using System.Reflection;
using System.Windows;
using System.Windows.Interactivity;
/// <summary>
/// Sets the designated property to the supplied value. TargetObject
/// optionally designates the object on which to set the property. If
/// TargetObject is not supplied then the property is set on the object
/// to which the trigger is attached.
/// </summary>
public class SetPropertyAction : TriggerAction<FrameworkElement>
{
// PropertyName DependencyProperty.
/// <summary>
/// The property to be executed in response to the trigger.
/// </summary>
public string PropertyName
{
get { return (string)GetValue(PropertyNameProperty); }
set { SetValue(PropertyNameProperty, value); }
}
public static readonly DependencyProperty PropertyNameProperty
= DependencyProperty.Register("PropertyName", typeof(string),
typeof(SetPropertyAction));
// PropertyValue DependencyProperty.
/// <summary>
/// The value to set the property to.
/// </summary>
public object PropertyValue
{
get { return GetValue(PropertyValueProperty); }
set { SetValue(PropertyValueProperty, value); }
}
public static readonly DependencyProperty PropertyValueProperty
= DependencyProperty.Register("PropertyValue", typeof(object),
typeof(SetPropertyAction));
// TargetObject DependencyProperty.
/// <summary>
/// Specifies the object upon which to set the property.
/// </summary>
public object TargetObject
{
get { return GetValue(TargetObjectProperty); }
set { SetValue(TargetObjectProperty, value); }
}
public static readonly DependencyProperty TargetObjectProperty
= DependencyProperty.Register("TargetObject", typeof(object),
typeof(SetPropertyAction));
// Private Implementation.
protected override void Invoke(object parameter)
{
object target = TargetObject ?? AssociatedObject;
PropertyInfo propertyInfo = target.GetType().GetProperty(
PropertyName,
BindingFlags.Instance|BindingFlags.Public
|BindingFlags.NonPublic|BindingFlags.InvokeMethod);
propertyInfo.SetValue(target, PropertyValue);
}
}
}
この場合、ビューモデルのDialogResultというプロパティにバインドしています。
<Grid>
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<wpf:SetPropertyAction PropertyName="DialogResult" TargetObject="{Binding}"
PropertyValue="{x:Static mvvm:DialogResult.Cancel}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Cancel
</Button>
</Grid>
Neutrinoのソリューションを修正して、値を指定するときにxamlの冗長性が低くなるようにしました。
レンダリングされたxamlの写真がなくてすみません。クリックする[=]ハンバーガーボタンを想像してください。これは[<-]戻るボタンに変わり、グリッドの表示も切り替えます。
xmlns:i="clr-namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity"
...
<Grid>
<Button x:Name="optionsButton">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<local:SetterAction PropertyName="Visibility" Value="Collapsed" />
<local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsBackButton}" Value="Visible" />
<local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsPanel}" Value="Visible" />
</i:EventTrigger>
</i:Interaction.Triggers>
<glyphs:Hamburger Width="10" Height="10" />
</Button>
<Button x:Name="optionsBackButton" Visibility="Collapsed">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<local:SetterAction PropertyName="Visibility" Value="Collapsed" />
<local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsButton}" Value="Visible" />
<local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsPanel}" Value="Collapsed" />
</i:EventTrigger>
</i:Interaction.Triggers>
<glyphs:Back Width="12" Height="11" />
</Button>
</Grid>
...
<Grid Grid.RowSpan="2" x:Name="optionsPanel" Visibility="Collapsed">
</Grid>
Neutrinoのソリューションのように、この方法で値を指定することもできます。
<Button x:Name="optionsButton">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<local:SetterAction PropertyName="Visibility" Value="{x:Static Visibility.Collapsed}" />
<local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsBackButton}" Value="{x:Static Visibility.Visible}" />
<local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsPanel}" Value="{x:Static Visibility.Visible}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<glyphs:Hamburger Width="10" Height="10" />
</Button>
そして、ここにコードがあります。
using System;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Interactivity;
namespace Mvvm.Actions
{
/// <summary>
/// Sets a specified property to a value when invoked.
/// </summary>
public class SetterAction : TargetedTriggerAction<FrameworkElement>
{
#region Properties
#region PropertyName
/// <summary>
/// Property that is being set by this setter.
/// </summary>
public string PropertyName
{
get { return (string)GetValue(PropertyNameProperty); }
set { SetValue(PropertyNameProperty, value); }
}
public static readonly DependencyProperty PropertyNameProperty =
DependencyProperty.Register("PropertyName", typeof(string), typeof(SetterAction),
new PropertyMetadata(String.Empty));
#endregion
#region Value
/// <summary>
/// Property value that is being set by this setter.
/// </summary>
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(SetterAction),
new PropertyMetadata(null));
#endregion
#endregion
#region Overrides
protected override void Invoke(object parameter)
{
var target = TargetObject ?? AssociatedObject;
var targetType = target.GetType();
var property = targetType.GetProperty(PropertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
if (property == null)
throw new ArgumentException(String.Format("Property not found: {0}", PropertyName));
if (property.CanWrite == false)
throw new ArgumentException(String.Format("Property is not settable: {0}", PropertyName));
object convertedValue;
if (Value == null)
convertedValue = null;
else
{
var valueType = Value.GetType();
var propertyType = property.PropertyType;
if (valueType == propertyType)
convertedValue = Value;
else
{
var propertyConverter = TypeDescriptor.GetConverter(propertyType);
if (propertyConverter.CanConvertFrom(valueType))
convertedValue = propertyConverter.ConvertFrom(Value);
else if (valueType.IsSubclassOf(propertyType))
convertedValue = Value;
else
throw new ArgumentException(String.Format("Cannot convert type '{0}' to '{1}'.", valueType, propertyType));
}
}
property.SetValue(target, convertedValue);
}
#endregion
}
}
ストーリーボードの停止は、ニーズがどこから来るかに応じて、コードビハインドまたはxamlで実行できます。
EventTriggerがボタンの外側に移動した場合、ストーリーボードに停止するよう指示する別のEventTriggerでターゲットを設定できます。この方法でストーリーボードを停止すると、ストーリーボードは以前の値に戻りません。
ここでは、Button.Click EventTriggerを周囲のStackPanelに移動し、CheckBox.Clickに新しいEventTriggerを追加して、CheckBoxがクリックされたときにButtonのストーリーボードを停止します。これにより、CheckBoxがクリックされたときにCheckBoxをチェックしたりチェックを外したりすることができ、ボタンからも必要なチェックを外すことができます。
<StackPanel x:Name="myStackPanel">
<CheckBox x:Name="myCheckBox"
Content="My CheckBox" />
<Button Content="Click to Uncheck"
x:Name="myUncheckButton" />
<Button Content="Click to check the box in code."
Click="OnClick" />
<StackPanel.Triggers>
<EventTrigger RoutedEvent="Button.Click"
SourceName="myUncheckButton">
<EventTrigger.Actions>
<BeginStoryboard x:Name="myBeginStoryboard">
<Storyboard x:Name="myStoryboard">
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="myCheckBox"
Storyboard.TargetProperty="IsChecked">
<DiscreteBooleanKeyFrame KeyTime="00:00:00"
Value="False" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="CheckBox.Click"
SourceName="myCheckBox">
<EventTrigger.Actions>
<StopStoryboard BeginStoryboardName="myBeginStoryboard" />
</EventTrigger.Actions>
</EventTrigger>
</StackPanel.Triggers>
</StackPanel>
コードビハインドでストーリーボードを停止するには、少し異なる操作を行う必要があります。 3番目のボタンは、ストーリーボードを停止し、コードでIsCheckedプロパティをtrueに戻すメソッドを提供します。
IsControllableパラメーターを設定するコードからストーリーボードを開始しなかったため、myStoryboard.Stop()を呼び出すことはできません。代わりに、ストーリーボードを削除できます。これを行うには、ストーリーボードが存在するFrameworkElement、この場合はStackPanelが必要です。ストーリーボードが削除されたら、UIに永続化してIsCheckedプロパティを再度設定できます。
private void OnClick(object sender, RoutedEventArgs e)
{
myStoryboard.Remove(myStackPanel);
myCheckBox.IsChecked = true;
}