Prism-MEF-MVVM Light: using EventToCommand to handle XAML events

Topics: Prism v4 - WPF 4
Apr 22, 2013 at 7:34 PM
Edited Apr 22, 2013 at 7:40 PM
Hi Prism Experts,
I have TextBox resides in DataGridColumnHeader in style Resource Dictionary, the TextBox used as a Filter so i need to trigger TextChanged event and handle it in View Model to implement filteration process.The point is i need to commit to followinh requirements:
1.I use Prism+MEF
2.No Code Behind
3.Event handeling in View Model
4.USe style to be able to apply it for all DataGrids consistently
5.Pass the Event arguments to the View Model to use in filteration process

So i tried to use MVVM Light: EventToCommand in the DataGrideStyle ResourceDictionary
like this
<ResourceDictionary 
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"
 xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"      
 xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"                   
 xmlns:local="clr-namespace:PreCommissioning.Infrastructure.Filter;assembly=PreCommissioning.Infrastructure"
 xmlns:localx="clr-namespace:PreCommissioning.Module.Subsystem.ViewModel;assembly=PreCommissioning.Module.Subsystem"    
 xmlns:my="http://schemas.microsoft.com/wpf/2008/toolkit"
>

    <local:HeaderFilterConverter x:Key="headerConverter"/>

    <Style x:Key="ColumnHeaderTextBoxStyle" TargetType="{x:Type TextBox}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TextBox}">
                    <Border x: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="TextChanged">

                                    <cmd:EventToCommand  Command="{Binding local:TextChangedBehavior.TextChangedCommand, ElementName=filterTextBox}"
                                                         PassEventArgsToCommand="True"
                                                         CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBox}}, Path=Name}"/>
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
__                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

   
    <!-- -->
    <Style TargetType="{x:Type DataGridColumnHeader}">
        <!--xaml code removed to cut it short-->    
                            <ContentPresenter x:Name="contentPresenter"
                                    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                                    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                                    ContentStringFormat="{TemplateBinding ContentStringFormat}" 
                                    ContentTemplate="{TemplateBinding ContentTemplate}">
                                <ContentPresenter.Content>
                                    <MultiBinding Converter="{StaticResource headerConverter}">
                                        <MultiBinding.Bindings>
                                            <Binding ElementName="filterTextBox" Path="Text" />
                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Content" />
                                        </MultiBinding.Bindings>
                                    </MultiBinding>
                                </ContentPresenter.Content>
                            </ContentPresenter>
                            
                            __<!--Filter Text Box-->
                            <TextBox x:Name="filterTextBox" HorizontalAlignment="Right" 
                                         MinWidth="25" Height="Auto" OpacityMask="Black" Visibility="Collapsed" 
                                         Text="{Binding Path=DataContext.FilterText, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}} ,Mode=TwoWay,UpdateSourceTrigger=Explicit}"                                                                              
                                         Style="{StaticResource ColumnHeaderTextBoxStyle}"
                                         TextWrapping="Wrap" Grid.Column="0" Grid.ColumnSpan="1">                                
                            </TextBox>
__
                        </Grid>
                    </my:DataGridHeaderBorder>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                      <!--rest of xaml code-->
And here is my Attached Behavior class:

public static class TextChangedBehavior
    {
        #region TextChangedCommand-----------------------------------------------------------

        static ICommand command; //1

        public static ICommand GetTextChangedCommand(TextBox target)
        {

            return (ICommand)target.GetValue(TextChangedCommandProperty);

        }

        public static void SetTextChangedCommand(TextBox target, ICommand value)
        {

            target.SetValue(TextChangedCommandProperty, value);

        }


        public static readonly DependencyProperty TextChangedCommandProperty =
            DependencyProperty.RegisterAttached("TextChangedCommand",
                                                 typeof(ICommand),
                                                 typeof(TextChangedBehavior),
                                                 new PropertyMetadata(TextChangedCommandChanged));
                                                 //new UIPropertyMetadata(new PropertyChangedCallback(TextChangedCommandChanged)));


        static void TextChangedCommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {

            TextBox element = target as TextBox;

            if (element != null)
            {
                if (e.NewValue != null)
                {
                    element.TextChanged += Element_TextChanged;
                }
                else
                {
                    element.TextChanged -= Element_TextChanged;
                }
            }

        }

        static void Element_TextChanged(object sender, TextChangedEventArgs e)
        {

            // Get the textbox
            //TextBox filterTextBox = e.OriginalSource as TextBox;

            // Get the header of the textbox
            //DataGridColumnHeader header = TryFindParent<DataGridColumnHeader>(filterTextBox);
            //if (header != null)
            //{
                
            //}
        }
        #endregion
    }
The problems are: When i enter filter text in the text box in one of the columns headers of the data grid nothing happen and break points at following points not hit:

1-GetTextChangedCommand SetTextChangedCommand

2-The TextChangedChanged() method. (not Called)

3- The Attached Property statement

i don't know if i have errors in the code or the problem between MVVM Light and Prism+MEF
So i need your help fix this.

Thanks in advance
Apr 24, 2013 at 6:31 AM
Help [:(]
Developer
Apr 24, 2013 at 7:56 PM
Hi Jivara,

In the last few days we tried to implement the functionality you are describing using the code snippets you posted but without any success...
However, we tried to implement something similar from scratch and we were able to set an style to a TextBox control that could be reused in different views to execute a Command in the view model of the view when the text is changed:
<Style TargetType="{x:Type TextBox}">
    <Setter Property="Template">
        <Setter.Value>
                
            <ControlTemplate TargetType="{x:Type TextBox}">
                <TextBox>
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="TextChanged">

                            <cmd:EventToCommand Command="{Binding Mode=OneWay, Path=TextChangedCommand}" PassEventArgsToCommand="True"/>

                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </TextBox>
            </ControlTemplate>

        </Setter.Value>
    </Setter>
</Style>
Where TextChangedCommand is simply a command in the view model of the view containing the TextBox. I hope this helps as a starting point to implement the feature you are describing.

Also, as topic doesn't seem to be related to Prism but to WPF, I believe you might find better support regarding how to implement such features in the following forums:
Regards,

Damian Cherubini
http://blogs.southworks.net/dcherubini
Apr 25, 2013 at 2:47 PM
Hi Damian,
I'm grateful for tying to help, knowing that i already sent same question to forums you mentioned but w/o response. So i'm gratefull for your commitment to help us .
I already started another approach similar to the one you advised beacuse the Attached Behavior is not working but the point now is: how to pass the event sender and eventArgs to the command in the view model.
Sorry if i'm asking agin but truly i didn't find response from the other froums.
Thanks.
Developer
Apr 25, 2013 at 7:27 PM
Hi,

I am not sure it this is what you need, but I believe you can obtain the control who fired the event trough the Source property of the TextChangedEventArgs received as the parameter of the command (thanks to the PassEventArgsToCommand property).

I hope this helps,

Damian Cherubini
http://blogs.southworks.net/dcherubini