WPF MVVM BindingExpression.UpdateSource を実行するビヘィビアを作成した。
TextBox の Validation.ErrorTemplate や Validation.HasError の Trigger は便利なのだが Exception や ValidationRule を使っていると保存前の Validate でセットされない。
BindingExpression.UpdateSource をかけてやればセットされるのだが、VMからはBindingExpressionが取得できない。
そんな時は メッセージ+ビヘイビア というわけで作ってみた。
こんな風に書けるようになる。 Mnow_Blend_Behavior:UpdateSourceTrigger のブロックです。
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:T4MvvmValidation.ViewModel"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Mnow_Blend_Behavior="clr-namespace:Mnow.Blend.MvvmLightBehavior;assembly=Mnow.Blend.MvvmLightBehavior"
xmlns:Mnow_Windows_Utility="clr-namespace:Mnow.Windows.Utility"
xmlns:GalaSoft_MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"
mc:Ignorable="d"
>
<Mnow_Windows_Utility:NullableIntegerStringConverter x:Key="NullableIntegerStringConverter"/>
<DataTemplate DataType="{x:Type local:OriginalTypeExceptionViewModel}">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40*"/>
<ColumnDefinition Width="60*"/>
</Grid.ColumnDefinitions>
<i:Interaction.Triggers>
<Mnow_Blend_Behavior:AttachedEventTrigger AttachedEventName="System.Windows.Controls.Validation.Error">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding ValidationErrorCommand, Mode=OneWay}" PassEventArgsToCommand="True"/>
</Mnow_Blend_Behavior:AttachedEventTrigger>
<Mnow_Blend_Behavior:UpdateSourceTrigger>
<Mnow_Blend_Behavior:UpdateSourceAction>
<Mnow_Blend_Behavior:UpdateSourceAction.UpdateSourceTargetCollection>
<Mnow_Blend_Behavior:UpdateSourceTarget ElementName="Id" PropertyName="System.Windows.Controls.TextBox.Text"/>
<Mnow_Blend_Behavior:UpdateSourceTarget ElementName="Name" PropertyName="System.Windows.Controls.TextBox.Text"/>
<Mnow_Blend_Behavior:UpdateSourceTarget ElementName="Birthday" PropertyName="System.Windows.Controls.TextBox.Text"/>
<Mnow_Blend_Behavior:UpdateSourceTarget ElementName="TelNumber" PropertyName="System.Windows.Controls.TextBox.Text"/>
<Mnow_Blend_Behavior:UpdateSourceTarget ElementName="MailAddress" PropertyName="System.Windows.Controls.TextBox.Text"/>
</Mnow_Blend_Behavior:UpdateSourceAction.UpdateSourceTargetCollection>
</Mnow_Blend_Behavior:UpdateSourceAction>
</Mnow_Blend_Behavior:UpdateSourceTrigger>
</i:Interaction.Triggers>
<TextBlock Text="Id" FontSize="26.667" VerticalAlignment="Center"/>
<TextBlock Text="Name" Grid.Column="0" Grid.Row="1" FontSize="26.667" VerticalAlignment="Center"/>
<TextBlock Text="Birthday" Grid.Column="0" Grid.Row="2" FontSize="26.667" VerticalAlignment="Center"/>
<TextBlock Text="TelNumber" Grid.Column="0" Grid.Row="3" FontSize="26.667" VerticalAlignment="Center"/>
<TextBlock Text="MailAddress" Grid.Column="0" Grid.Row="4" FontSize="26.667" VerticalAlignment="Center"/>
<TextBox x:Name="Id" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Id, Converter={StaticResource NullableIntegerStringConverter}, NotifyOnValidationError=True, ValidatesOnExceptions=True}" d:LayoutOverrides="Height" MinWidth="240" Grid.Column="1" FontSize="26.667" Validation.ErrorTemplate="{StaticResource validationTemplate}" Style="{StaticResource textBoxInError}"/>
<TextBox x:Name="Name" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Name, NotifyOnValidationError=True, ValidatesOnExceptions=True}" MinWidth="240" Grid.Column="1" FontSize="26.667" d:LayoutOverrides="Height" Grid.Row="1" Validation.ErrorTemplate="{StaticResource validationTemplate}" Style="{StaticResource textBoxInError}"/>
<TextBox x:Name="Birthday" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Birthday, NotifyOnValidationError=True, StringFormat=yyyy/MM/dd, ValidatesOnExceptions=True}" MinWidth="240" Grid.Column="1" FontSize="26.667" d:LayoutOverrides="Height" Grid.Row="2" Validation.ErrorTemplate="{StaticResource validationTemplate}" Style="{StaticResource textBoxInError}"/>
<TextBox x:Name="TelNumber" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding TelNumber, NotifyOnValidationError=True, ValidatesOnExceptions=True}" MinWidth="240" Grid.Column="1" FontSize="26.667" d:LayoutOverrides="Height" Grid.Row="3" Validation.ErrorTemplate="{StaticResource validationTemplate}" Style="{StaticResource textBoxInError}"/>
<TextBox x:Name="MailAddress" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding MailAddress, NotifyOnValidationError=True, ValidatesOnExceptions=True}" MinWidth="240" Grid.Column="1" FontSize="26.667" d:LayoutOverrides="Height" Grid.Row="4" Validation.ErrorTemplate="{StaticResource validationTemplate}" Style="{StaticResource textBoxInError}"/>
<ListView Grid.RowSpan="1" Grid.Row="5" Grid.ColumnSpan="2" ItemsSource="{Binding ErrorCollection}" Foreground="Red" FontSize="26.667">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Item"/>
<GridViewColumn DisplayMemberBinding="{Binding Message}" Header="Message"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.ColumnSpan="2" Orientation="Horizontal" Grid.Row="6" HorizontalAlignment="Right">
<Button Content="OK" IsDefault="True" FontSize="26.667" Command="{Binding OkCommand, Mode=OneWay}"/>
<Button Content="Cancel" IsCancel="True" FontSize="26.667"/>
</StackPanel>
</Grid>
</DataTemplate>
</ResourceDictionary>
ここからソース。
UpdateSourceTarget.cs
using System.Windows;
namespace Mnow.Blend.MvvmLightBehavior
{
public class UpdateSourceTarget : DependencyObject
{
public static readonly DependencyProperty ElementNameProperty =
DependencyProperty.Register("ElementName", typeof(string),
typeof(UpdateSourceTarget),
new FrameworkPropertyMetadata(null));
public string ElementName
{
get
{
return (string)GetValue(ElementNameProperty);
}
set
{
SetValue(ElementNameProperty, value);
}
}
public static readonly DependencyProperty PropertyNameProperty =
DependencyProperty.Register("PropertyName", typeof(string),
typeof(UpdateSourceTarget),
new FrameworkPropertyMetadata(null));
public string PropertyName
{
get
{
return (string)GetValue(PropertyNameProperty);
}
set
{
SetValue(PropertyNameProperty, value);
}
}
}
}
UpdateSourceMessage.cs
using GalaSoft.MvvmLight.Messaging;
namespace Mnow.Blend.MvvmLightBehavior
{
public class UpdateSourceMessage : MessageBase
{
public UpdateSourceMessage(object sender) :
base(sender)
{
}
}
}
UpdateSourceTrigger.cs
using System.Windows;
using System.Windows.Interactivity;
using GalaSoft.MvvmLight.Messaging;
namespace Mnow.Blend.MvvmLightBehavior
{
public class UpdateSourceTrigger : TriggerBase<DependencyObject>
{
protected override void OnAttached()
{
base.OnAttached();
FrameworkElement element = GetFrameworkElement();
Messenger.Default.Register<UpdateSourceMessage>(base.AssociatedObject, element.DataContext, p => this.InvokeActions(p));
}
protected override void OnDetaching()
{
base.OnDetaching();
Messenger.Default.Unregister<UpdateSourceMessage>(base.AssociatedObject);
}
private FrameworkElement GetFrameworkElement()
{
Behavior behavior = AssociatedObject as Behavior;
FrameworkElement element = AssociatedObject as FrameworkElement;
if (behavior != null)
{
element = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
}
return element;
}
}
}
UpdateSourceAction.cs
using System;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace Mnow.Blend.MvvmLightBehavior
{
[DefaultTrigger(typeof(Grid), typeof(UpdateSourceTrigger), (object)null)]
public class UpdateSourceAction : TriggerAction<DependencyObject>
{
public static readonly DependencyProperty UpdateSourceTargetCollectionProperty =
DependencyProperty.Register("UpdateSourceTargetCollection", typeof(Collection<UpdateSourceTarget>),
typeof(UpdateSourceAction),
new FrameworkPropertyMetadata(new Collection<UpdateSourceTarget>()));
public UpdateSourceAction()
{
}
protected override void Invoke(object parameter)
{
UpdateSourceMessage message = parameter as UpdateSourceMessage;
FrameworkElement element = GetFrameworkElement();
if (message != null && message.Sender == element.DataContext)
{
foreach (UpdateSourceTarget target in UpdateSourceTargetCollection)
{
FrameworkElement targetElement = GetChildFromName(element, target.ElementName);
if (targetElement != null)
{
DependencyProperty property = GetDependencyProperty(target.PropertyName);
if (property != null)
{
BindingExpression expression = BindingOperations.GetBindingExpression(targetElement, property);
expression.UpdateSource();
}
}
}
}
}
protected override void OnDetaching()
{
base.OnDetaching();
}
public Collection<UpdateSourceTarget> UpdateSourceTargetCollection
{
get
{
return (Collection<UpdateSourceTarget>)GetValue(UpdateSourceTargetCollectionProperty);
}
}
private FrameworkElement GetFrameworkElement()
{
Behavior behavior = AssociatedObject as Behavior;
FrameworkElement element = AssociatedObject as FrameworkElement;
if (behavior != null)
{
element = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
}
return element;
}
private static DependencyProperty GetDependencyProperty(string propertyName)
{
DependencyProperty dependencyProperty = null;
string[] split = propertyName.Split('.');
if (split.Length != 2)
{
string className = string.Empty;
for (int index = 0; index < split.Length - 1; index++)
{
if (!string.IsNullOrWhiteSpace(className))
{
className += ".";
}
className += split[index];
}
split[0] = className;
split[1] = split[split.Length - 1];
}
Type type = null;
AppDomain currentDomain = AppDomain.CurrentDomain;
Assembly[] assemblies = currentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
type = assembly.GetType(split[0]);
if (type != null)
{
break;
}
}
if (type != null)
{
FieldInfo fieldInfo = type.GetField(split[1] + "Property", BindingFlags.Public | BindingFlags.Static);
object field = fieldInfo.GetValue(null);
dependencyProperty = field as DependencyProperty;
}
return dependencyProperty;
}
private FrameworkElement GetChildFromName(DependencyObject parent, string name)
{
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int index = 0; index < count; index++)
{
DependencyObject target = VisualTreeHelper.GetChild(parent, index);
FrameworkElement element = target as FrameworkElement;
if (element != null && element.Name == name)
{
return element;
}
element = GetChildFromName(target, name);
if (element != null)
{
return element;
}
}
return null;
}
}
}