WPF如何将混合交互触发器添加到样式资源

标签 wpf mvvm mvvm-light expression-blend eventtocommand

我正在使用带有 WPF 4.5 的 VS 2012

我希望能够将混合交互触发器添加到样式资源,以便我可以在一个地方(资源字典)定义它并在我的应用程序中的许多地方使用。

具体来说,我想使用 MVVM-Light 框架附带的 EventToCommand 并将其插入文本框样式并附加到文本框的 LostFocus 事件。我计划使用它来标记某些文本框,其 ValidationStyle 触发绑定(bind)命令(在 View 模型中)到文本框的 LostFocus 事件。此验证样式将使用 IDataErrorInfo 通过 UI 向用户显示错误。

这个问题类似于以下问题(但它们没有完整的解决方案):

EventToCommand in button style

How to add a Blend Behavior in a Style Setter

问题:
如何将混合 EventToCommand 添加到绑定(bind)到 View 模型数据上下文中的命令的文本框 lostfocus(我不想使用代码隐藏或附加属性,我希望它完全在 XAML 中定义)?

最佳答案

所以我必须承认,当我写这篇文章时,我有一个可行的答案,但我花了很长时间才弄清楚,所以我在这里发布它希望它可以帮助其他人,即使它是一个非常具体的场景。

我正在为我的应用程序使用 MVVM 模型,所以我不想在 xaml 页面后面有代码。我还想要一种将文本框绑定(bind)到 IDataErrorInfo 属性的方法,其中该文本框的验证是通过文本框的 lostfocus 事件触发的。此事件将绑定(bind)到 View 模型上的中继命令,该命令将验证适用对象并添加实际错误。

所以我需要让文本框 lostfocus 事件命令将文本框名称(与数据库中的列名匹配)作为命令参数。

这是我想要完成的屏幕截图
WPF Screen Shot of Textbox Validation STyle

我是这样做的:

首先我在 View 模型上定义了命令:

Imports GalaSoft.MvvmLight.Command
Private _LostFocusValidateCommand As RelayCommand(Of String)

    Public ReadOnly Property LostFocusValidateCommand() As RelayCommand(Of String)
        Get
            If _LostFocusValidateCommand Is Nothing Then
                _LostFocusValidateCommand = New RelayCommand(Of String)(AddressOf LostFocusValidateExecute)
            End If
            Return _LostFocusValidateCommand
        End Get
    End Property
    Private Sub LostFocusValidateExecute(sParam As String)
        NewClient.PropertyValitaion(False, sParam)
    End Sub

这是使用 IDataErrorInfo 的属性验证(我省略了 IDataErrorInfo 的基本实现以节省空间,如果您希望我发布,请留下评论)
 Public Sub PropertyValitaion(bAllProperties As Boolean, Optional sProperty As String = "")
    'initialize validation helper
    Dim vhelper As New ValidationHelper

    If bAllProperties Or sProperty = "chrCompany" Then
        If String.IsNullOrEmpty(chrCompany) Then
            AddError("chrCompany", "You must enter a Company Name")
        Else
            RemoveError("chrCompany")
        End If
    End If
    If bAllProperties Or sProperty = "chrFirst" Then
        If String.IsNullOrEmpty(chrFirst) Then
            AddError("chrFirst", "You must enter a First Name")
        Else
            RemoveError("chrFirst")
        End If
    End If
    If bAllProperties Or (sProperty = "chrPhone1" Or sProperty = "chrPhone1Ext") Then
        If String.IsNullOrEmpty(Trim(chrPhone1Ext)) = False And String.IsNullOrEmpty(Trim(chrPhone1)) Then
            Me.AddError("chrPhone1", "Provide a phone number or remove extension")
        Else
            RemoveError("chrPhone1")
        End If
        If String.IsNullOrEmpty(Trim(chrPhone1)) = False Then
            If vhelper.CheckPhoneNumber(Me.chrPhone1) = False Then
                Me.AddError("chrPhone1", "Phone 1 format invalid")
            Else
                RemoveError("chrPhone1")
            End If
        End If
    End If

End Sub

困难的部分是弄清楚如何定义风格。样式很长,对不起,“可读” xml 的乐趣:
    <Style x:Key="FTC_ValidateTextBox" BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
    <Style.Setters>
        <Setter Property="FontFamily" Value="Open Sans Condensed"/>
        <Setter Property="FontSize" Value="19" />
        <Setter Property="Margin" Value="3,3,15,6"/>
        <Setter Property="Padding" Value="10,3"/>
        <Setter Property="TextWrapping" Value="Wrap" />
        <Setter Property="HorizontalAlignment" Value="Stretch" />
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="Background" Value="{StaticResource DetailTextBox}" />
        <Setter Property="BorderBrush" Value="{StaticResource MediumGray}" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="Foreground" Value="Black" />
        <Setter Property="AllowDrop" Value="true"/>
        <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
        <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
        <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TextBox}">
                    <Border Name="Bd" SnapsToDevicePixels="true" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                        <ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="LostFocus">
                                    <cmd:EventToCommand Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},Path=DataContext.LostFocusValidateCommand}"
                                                        CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBox}},Path=Name}"/>
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </ScrollViewer>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <Border BorderBrush="{StaticResource MediumRed}" >
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <AdornedElementPlaceholder Name="parentTextBox" />
                            <TextBlock Grid.Row="1" Style="{StaticResource FTC_DetailError}"
                                       Text="{Binding ElementName=parentTextBox, Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"/>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style.Setters>
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
            <Setter Property="BorderBrush" Value="{StaticResource MediumRed}"/>
            <Setter Property="Foreground" Value="{StaticResource MediumRed}"/>
            <Setter Property="Margin" Value="3,3,15,31"/>
        </Trigger>
    </Style.Triggers>
</Style>

<Style x:Key="FTC_DetailError" TargetType="TextBlock">
    <Style.Setters>
        <Setter Property="FontFamily" Value="Open Sans Condensed"/>
        <Setter Property="Control.FontWeight" Value="Light" />
        <Setter Property="Foreground" Value="{StaticResource TitleWhite}"/>
        <Setter Property="FontSize" Value="15" />
        <Setter Property="Margin" Value="0"/>
        <Setter Property="Padding" Value="10,3"/>
        <Setter Property="HorizontalAlignment" Value="Stretch"/>
        <Setter Property="Background" Value="{StaticResource MediumRed}"/>
    </Style.Setters>             
</Style>

所有的魔法都发生在属性模板中。以下内容必须包含在资源字典的顶部声明中:
> xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
> xmlns:cmd="http://www.galasoft.ch/mvvmlight"

所有的魔法都发生在定义控件模板的模板属性中。您不能在控件模板本身中包装 i:interaction,它必须包含在派生对象中,几乎任何东西,边框,滚动查看器,包装面板等......然后设置通风触发器和命令属性。它们应该很容易理解,我将文本框名称作为命令参数传递。您在屏幕截图中看到的客户端“框”是一个网格,其数据上下文设置为父 View 模型的新客户端对象属性。所以为了访问父 View 模型中的命令,我必须引用父 View 模型的数据上下文并调用命令属性。

再一次,我意识到这是一个非常具体的场景,但我认为它有一些例子可以帮助其他人。我现在能够为应用程序中的所有文本框定义一种样式,这些文本框是数据输入并且我想触发基本的验证过程。这将使我不必在所有这些文本框上单独定义自定义命令行为,这一切都在 xaml 中完成,没有代码。

干杯

关于WPF如何将混合交互触发器添加到样式资源,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14986045/

相关文章:

c# - 按下鼠标左键时更改鼠标光标?

c# - 在特定文本上方显示文本

asp.net-mvc-3 - MVC : Repository and Viewmodels both pattern together for better structure?

c# - 如何在ViewModel中处理列表框keydown事件?

mvvm-light - 如何从 GestureListener.Gesture 触发 EventToCommand

mvvm - 如何从 ViewModel (MVVM Light Toolkit) 访问 View 中的 UserControl (MediaElement)

c# - 在 ICollectionView 中过滤对象的子项

c# - 从代码背后设计样式

c# - WPF MVVM DataGrid 过滤使用组合框

c# - 如何使用 MVVM light 处理 WP 8.1 上的后退按钮?