How to create TextBlock's MouseLeftButtonUp event as a Command

Topics: Prism v2 - Silverlight 3
Aug 18, 2009 at 4:28 AM

Hi, Admin,

I post a problem yesterday, but no response: http://compositewpf.codeplex.com/Thread/View.aspx?ThreadId=65833.

I donot know if it is an issue of Prism. Because the command in Prism is base on that the control must inherit from Content, but TextBlock inherit directly from FrameworkElement, so we must overload class:

    public class CommandBehaviorBase
        where T : FrameworkElement
 
Although I try my best to find a way to implement it, but faild. I attach this event to the command, but no response after I click on the TextBlock.
      
    <TextBlock x:Name="FlightNO" Tag="FLT/Date" Text="{Binding FLTDATE}"   
      Infrastructure:MouseLeftButtonUp.Command="{Binding Path=MouseLeftButtonUpCommand}" />
the code snippet behind:
        ICommand MouseLeftButtonUpCommand = new DelegateCommand<MouseButtonEventArgs>(OnMouseLeftButtonUp11);

        public void OnMouseLeftButtonUp11(MouseButtonEventArgs e)
        {
            MessageBox.Show(e.ToString());
        }
Anyboy meet this problem? and reference can help me to resolve it? Thank you!
Aug 18, 2009 at 5:21 AM

Hi guy

First of all create a class with two attached property like this one.

#region SingleEventCommand Class
        /// 
        /// This class allows a single command to event mappings.  
        /// It is used to wire up View events to a
        /// ViewModel ICommand implementation.  
        /// 
        /// 
        /// <![CDATA[
        /// 
        /// <Grid Background="WhiteSmoke"
        ///    Cinch:SingleEventCommand.RoutedEventName="MouseDown"
        ///      Cinch:SingleEventCommand.TheCommandToRun=
        ///       "{Binding Path=ShowWindowCommand}" />
        /// 
        /// ]]>
        /// 
        public static class SingleEventCommand
        {
            #region TheCommandToRun

            /// 
            /// TheCommandToRun : The actual ICommand to run
            /// 
            public static readonly DependencyProperty TheCommandToRunProperty =
                DependencyProperty.RegisterAttached("TheCommandToRun",
                    typeof(ICommand),
                    typeof(SingleEventCommand),
                    new FrameworkPropertyMetadata((ICommand)null));

            /// 
            /// Gets the TheCommandToRun property.  
            /// 
            public static ICommand GetTheCommandToRun(DependencyObject d)
            {
                return (ICommand)d.GetValue(TheCommandToRunProperty);
            }

            /// 
            /// Sets the TheCommandToRun property.  
            /// 
            public static void SetTheCommandToRun(DependencyObject d, ICommand value)
            {
                d.SetValue(TheCommandToRunProperty, value);
            }
            #endregion

            #region RoutedEventName

            /// 
            /// RoutedEventName : The event that should actually execute the
            /// ICommand
            /// 
            public static readonly DependencyProperty RoutedEventNameProperty =
                DependencyProperty.RegisterAttached("RoutedEventName", typeof(String),
                typeof(SingleEventCommand),
                    new FrameworkPropertyMetadata((String)String.Empty,
                        new PropertyChangedCallback(OnRoutedEventNameChanged)));

            /// 
            /// Gets the RoutedEventName property.  
            /// 
            public static String GetRoutedEventName(DependencyObject d)
            {
                return (String)d.GetValue(RoutedEventNameProperty);
            }

            /// 
            /// Sets the RoutedEventName property.  
            /// 
            public static void SetRoutedEventName(DependencyObject d, String value)
            {
                d.SetValue(RoutedEventNameProperty, value);
            }

            /// 
            /// Hooks up a Dynamically created EventHandler (by using the 
            /// EventHooker class) that when
            /// run will run the associated ICommand
            /// 
            private static void OnRoutedEventNameChanged(DependencyObject d,
                DependencyPropertyChangedEventArgs e)
            {
                String routedEvent = (String)e.NewValue;

                if (d == null || String.IsNullOrEmpty(routedEvent))
                    return;


                //If the RoutedEvent string is not null, create a new
                //dynamically created EventHandler that when run will execute
                //the actual bound ICommand instance (usually in the ViewModel)
                EventHooker eventHooker = new EventHooker();
                eventHooker.ObjectWithAttachedCommand = d;

                EventInfo eventInfo = d.GetType().GetEvent(routedEvent,
                    BindingFlags.Public | BindingFlags.Instance);

                //Hook up Dynamically created event handler
                if (eventInfo != null)
                {
                    eventInfo.RemoveEventHandler(d,
                        eventHooker.GetNewEventHandlerToRunCommand(eventInfo));

                    eventInfo.AddEventHandler(d,
                        eventHooker.GetNewEventHandlerToRunCommand(eventInfo));
                }

            }
            #endregion
        }
        #endregion

        #region EventHooker Class
        /// 
        /// Contains the event that is hooked into the source RoutedEvent
        /// that was specified to run the ICommand
        /// 
        sealed class EventHooker
        {
            #region Public Methods/Properties
            /// 
            /// The DependencyObject, that holds a binding to the actual
            /// ICommand to execute
            /// 
            public DependencyObject ObjectWithAttachedCommand { get; set; }

            /// 
            /// Creates a Dynamic EventHandler that will be run the ICommand
            /// when the user specified RoutedEvent fires
            /// 
            /// <param name="eventInfo" />The specified RoutedEvent EventInfo
            /// An Delegate that points to a new EventHandler
            /// that will be run the ICommand
            public Delegate GetNewEventHandlerToRunCommand(EventInfo eventInfo)
            {
                Delegate del = null;

                if (eventInfo == null)
                    throw new ArgumentNullException("eventInfo");

                if (eventInfo.EventHandlerType == null)
                    throw new ArgumentException("EventHandlerType is null");

                if (del == null)
                    del = Delegate.CreateDelegate(eventInfo.EventHandlerType, this,
                          GetType().GetMethod("OnEventRaised",
                            BindingFlags.NonPublic |
                            BindingFlags.Instance));

                return del;
            }
            #endregion

            #region Private Methods

            /// 
            /// Runs the ICommand when the requested RoutedEvent fires
            /// 
            private void OnEventRaised(object sender, EventArgs e)
            {
                ICommand command = (ICommand)(sender as DependencyObject).
                    GetValue(SingleEventCommand.TheCommandToRunProperty);

                if (command != null)
                {
                    command.Execute(null);
                }
            }
            #endregion
        }
        #endregion

 

after that you can use them in your xaml

        
<TextBlock cmd:SingleEventCommand.RoutedEventName="MouseLeftButtonUp" cmd:SingleEventCommand.TheCommandToRun="{Binding YourCommand}"/>
 


Aug 18, 2009 at 9:50 AM
Edited Aug 18, 2009 at 9:57 AM
Hi, Blochaou,

Thank you for your code, it works fine.

When I click on the TextBlock, the MouseLeftButtonUp event is actually fired, but cannot pass the MouseButtonEventArgs.

Another issue is that when I put your code into a DataGrid, the TextBlock still cannot be invoked.

<data:DataGrid x:Name="FlightPlanning" ItemsSource="{Binding FlightList, Mode=OneWay}" >
            <data:DataGrid.Columns>
                <data:DataGridTemplateColumn Header="FLT/DT"      Width="60"   SortMemberPath="FLTDATE" >
                    <data:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock x:Name="FlightNO" Tag="FLT/Date" Text="{Binding FLTDATE}"
                                       Infrastructure:SingleEventCommand.RoutedEventName="MouseLeftButtonUp"
                                       Infrastructure:SingleEventCommand.TheCommandToRun="{Binding Path=MouseLeftButtonUpCommand}" />
                        </DataTemplate>
                    </data:DataGridTemplateColumn.CellTemplate>
                </data:DataGridTemplateColumn>
            </data:DataGrid.Columns>
        </data:DataGrid>

Aug 18, 2009 at 11:30 AM

Hi

To get the MouseButtonEventAgrs, you have to change a little bit the codes.

1. Define you command like this

   private DelegateCommand<MouseButtonEventArgs> _testParameter;

        public DelegateCommand<MouseButtonEventArgs> TestParameter
        {
            get {
                if (_testParameter == null)
                    _testParameter = new DelegateCommand<MouseButtonEventArgs>(args => OnTest(args), param => true);
                return _testParameter;
            }
        }

        private void OnTest(MouseButtonEventArgs args)
        {
            System.Windows.Forms.MessageBox.Show("Test");
        }

 

2. Change the OnEventRaise in the EventHook class

     
private void OnEventRaised(object sender, EventArgs e)
            {
                Console.WriteLine(e.GetType().ToString());
                ICommand command = (ICommand)(sender as DependencyObject).
                    GetValue(SingleEventCommand.TheCommandToRunProperty);

                if (command != null)
                {
                    
                    if (e is MouseEventArgs)
                    {
                        command.Execute(e as MouseEventArgs);
                    }
                }
            }
This means that you send the argument to your Handler.

But as i can guess all your handlers don't receive a MouseEventArgs as parameter so  you have to check the type of the EventArgs in  OneventRaised method before you call command.Execute.

 

For the orther issue i'm not sure that the problem is the datagrid. Try to use directly  MouseLeftButtonUp event handler in your code behind. And if it works my code should work.

Let me know.

Thanks

Aug 18, 2009 at 2:35 PM
Edited Aug 19, 2009 at 4:05 AM

Hi, blochaou

I test your code. In TextBlock, it looks fine, still not suitable to Datagrid.

I am sorry not to tell you that my project is Silverlight. So the class FrameworkPropertyMetadata can not be used in SL. I try to replace FrameworkPropertyMetadata of PropertyMetadata, it can be complied, but I think some function is blocked. So which action shall I do something about FrameworkPropertyMetadata?

And also, about MouseEventArgs in your method OnEventRaised, I think it should be MouseButtonEventArgs, right?

Last, I test your code in DataGrid, debug on the method OnEventRaised, I find the parameter sender is a TextBlock, and when execute this code line as follow:

ICommand command = (ICommand)(sender as DependencyObject).
                    GetValue(SingleEventCommand.TheCommandToRunProperty);

the command result in a null value. So no action to do finally. I try to seperate your code into 3 statements:

            DependencyObject dp = sender as DependencyObject;
            Object obj= dp.GetValue(SingleEventCommand.TheCommandToRunProperty);

            ICommand command = obj as ICommand;

I find obj is null, it means DP TheCommandToRunProperty doesn't exist in sender, but when I change the 2nd statement as follow:

Object obj= dp.GetValue(SingleEventCommand.RoutedEventNameProperty);

My God, obj is not null, which means DP RoutedEventNameProperty exists in sender. Why these 2 DP are different, one is registered but the other one does not?

Aug 18, 2009 at 3:15 PM

Hi

You are right about MouseButtonEventArgs.

if you get null for the command this mean that the second attached property (TheCommandToRun) is not set or your command is not defined.

if you are sure that your command is properly defined in your viewmodel then TheCommandToRun is not set. May be it is the binding in the Datatable.

As i work in WPF i can not give you more detail about how to work arround with SL. I'm sorry

Thanks

 

Aug 19, 2009 at 4:04 AM

For the DataGrid, your context is no longer the ViewModel but rather the item in the collection. There are several ways to get around this including using the {Binding DataContext.YourCommand, ElementName=RootElement} where the RootElement is the name of the user control. There is also an older pattern I used (sorry I don't have it here) where you can use an attached property to reset another properties binding. Try the ElementName approach first and if no luck, I will reply again with the other site I found.

Aug 23, 2009 at 6:56 PM

Hi, all the friends above,

I have implemented this function as blochaou just said, put command into DataGrid's property:

<data:DataGrid x:Name="FlightPlanning"
                       ItemsSource="{Binding FlightList, Mode=OneWay}"
                       Infrastructure:SingleEventCommand.RoutedEventName="MouseLeftButtonUp"
                       Infrastructure:SingleEventCommand.TheCommandToRun="{Binding Path=MouseLeftButtonUpCommand}" >

so, evenything looks find.

Thank you.

Mar 26, 2010 at 4:48 PM

Great work on this, blochaou.  Very appreciated!

Mar 26, 2010 at 5:40 PM

I was able to do set up a MouseDoubleClick using the information above, which worked great.  I would now like to set up multiple events on the datagrid.  For example, I'd like to do some action on the MouseDoubleClick event but also handle the Sorting event.  Is this possible?

Tks.

Mar 26, 2010 at 6:59 PM

Hi Gorter

Take a look at this article.

http://www.codeproject.com/KB/WPF/CinchII.aspx

Blochaou Francois