Yet another blog about WPF, Surface, SL, MVVM, NUI....

2011

2010

2009

2008

Tag - MouseEvent

Entries feed - Comments feed

 

Execute a command on a specified control when clicking on a button

16 June 2010

Today another example of the powerful ramora pattern : execute a RoutedCommand on an Control when you click on a button outside of the aimed control scope.

When is it useful ? : for example when you use the infragistics grid (xamdatagrid) and you want to execute the command which remove the selected row from a button outside of the grid. You then have to exectue the DeleteSelectedDataRecords command on the grid from a button which is outside of the scope of the grid ... then this behavior is useful !

Our implementation will also show how we can apply the "Weak Events pattern" with the dependency property trick (attached property / ramora pattern).

And of course this example can be adapted to subscribe on any event you wants and not only the click of a button.

The behavior itself

Our behavior will listen to the click button, and the handling of the click event will execute the routed command on the target element.

public class ExecuteCommandOnControl : DependencyObject
{
  #region Command
 
  /// <summary>
  /// Command Attached Dependency Property
  /// </summary>
  public static readonly DependencyProperty RoutedCommandProperty =
    DependencyProperty.RegisterAttached("RoutedCommand", typeof(RoutedCommand)
	     , typeof(ExecuteCommandOnControl));
 
  /// <summary>
  /// Gets the Command property.  This dependency property 
  /// indicates ....
  /// </summary>
  public static RoutedCommand GetRoutedCommand(DependencyObject d)
  {
    return (RoutedCommand)d.GetValue(RoutedCommandProperty);
  }
 
  /// <summary>
  /// Sets the Command property.  This dependency property 
  /// indicates ....
  /// </summary>
  public static void SetRoutedCommand(DependencyObject d, RoutedCommand value)
  {
    d.SetValue(RoutedCommandProperty, value);
  }
 
  #endregion
 
  #region IInputElement
  /// <summary>
  /// IInputElement Attached Dependency Property
  /// </summary>
  public static readonly DependencyProperty TargetProperty =
    DependencyProperty.RegisterAttached("Target", typeof(IInputElement), 
	   typeof(ExecuteCommandOnControl),
      new FrameworkPropertyMetadata(null,
        new PropertyChangedCallback(OnTargetChanged)));
 
  /// <summary>
  /// Gets the IInputElement property.  This dependency property 
  /// indicates ....
  /// </summary>
  public static IInputElement GetTarget(DependencyObject d)
  {
    return (IInputElement)d.GetValue(TargetProperty);
  }
 
  /// <summary>
  /// Sets the IInputElement property.  This dependency property 
  /// indicates ....
  /// </summary>
  public static void SetTarget(DependencyObject d, IInputElement value)
  {
    d.SetValue(TargetProperty, value);
  }
 
  /// <summary>
  /// Handles changes to the IInputElement property.
  /// </summary>
  private static void OnTargetChanged(DependencyObject d, 
     DependencyPropertyChangedEventArgs e)
  {
    Button button = d as Button;
 
    if (button != null)
    {
      ButtonClickEventManager.AddListener(button, _customEventListener);
 
    } else
    {
      throw 
	  new ArgumentException("This behavior can only be installed on non-null Button !");
    }
  }
 
  #endregion
 
  private static CustomEventListener _customEventListener = new CustomEventListener();
 
}



WeakEvents

So what is the class CustomEventListener ? This is the object which will handle the click on the button. This class implements the interface IWeakEventListener which is a necessary things to use the Weak event pattern.

We will then not subscribe the click event with the usual way ( using the operator +=) but by calling a WeakEventManager of our own : ButtonClickEventManager.

This manager will then keep a weak reference of our listener and the button on which we operate will then be free to be garbage collected. Otherwise, every button on which we subscribe would be keeped in the memory because each one would be connected to our behavior...

Here is the CustomEventListener class :

public class CustomEventListener : IWeakEventListener
{
  #region IWeakEventListener Members
  public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
  {
    if (managerType == typeof(ButtonClickEventManager))
    {
      var target = 
	  ExecuteCommandOnControl.GetTarget(sender as DependencyObject);
      var command = 
	  ExecuteCommandOnControl.GetRoutedCommand(sender as DependencyObject);
 
      if (target == null || command == null) return true;
 
      command.Execute(null, target);
      return true;
    } else
    {
      return false;
    }
  }
  #endregion
}

And here is our WeakEventMAnager :

public class ButtonClickEventManager : WeakEventManager
{
 
  public static void AddListener(Button source, 
       IWeakEventListener listener)
  {
    ButtonClickEventManager.
	   CurrentManager.ProtectedAddListener(source, listener);
  }
 
  public static void RemoveListener(Button source, 
       IWeakEventListener listener)
  {
    ButtonClickEventManager.
	   CurrentManager.ProtectedRemoveListener(source, listener);
  }
 
  protected override void StartListening(object source)
  {
    Button button = (Button)source;
    button.Click += this.OnButtonClick;
  }
 
  protected override void StopListening(object source)
  {
    Button button = (Button)source;
    button.Click -= this.OnButtonClick;
  }
 
  private void OnButtonClick(Object sender, RoutedEventArgs args)
  {
    base.DeliverEvent(sender, args);
  }
 
  private static ButtonClickEventManager CurrentManager
  {
    get
    {
      Type managerType = typeof(ButtonClickEventManager);
      ButtonClickEventManager manager = 
	  (ButtonClickEventManager)WeakEventManager.GetCurrentManager(managerType);
      if (manager == null)
      {
        manager = new ButtonClickEventManager();
        WeakEventManager.SetCurrentManager(managerType, manager);
      }
      return manager;
    }
  }
}



Example of use

Here a little example of how you can use it to launch the deleteSelected command of the infragistics datagrid from a button outside of the grid :

<Button Content="Delete selected"
  tools:ExecuteCommandOnControl.RoutedCommand="{x:Static igDP:DataPresenterCommands.DeleteSelectedDataRecords}"
  tools:ExecuteCommandOnControl.Target="{Binding ElementName=dataGrid}" />
<igDP:XamDataGrid Name="dataGrid" DataSource="{Binding Data}" />



Interesting readings



Shout it kick it on DotNetKicks.com Awaaz Up


 

Create your VideoPlayer with JavaFX, part two.

14 December 2008

Let's continue pur story about creating our own media player

What will we do in our second part ? :

  1. Display the meta-information of our video.
  2. Create animation and triggers to show it in a non-disturbing way.
  3. Add some triggers to our video : play/pause on click.
  4. Double-clic to resume the video
  5. Enjoy ourselves!



Display the meta-informations in a non-disturbing way

We already have all the informations to display because we gived them to our component.
Now we will display them in an header: Videoplayer's metainformation header

First we create the gray rectangle which is behind the meta-information and the two differents text. We will add them to a group to ease the use of trigger to show/hide them.

/** ###########################################
    * The Top Rectangle of description
     *   ########################################### */
    var topRDescription : Rectangle = Rectangle{
        x:x
        y:y
        width:vidWith
        height: 40
        opacity:0.5
        fill:Color.DARKGRAY
}
 
    var titleText: Text =  Text{
        x:topRDescription.x + 10
        y: topRDescription.y + 25
        textOrigin: TextOrigin.BOTTOM
        content: bind vidTitle
        fill:Color.ORANGE
        font: Font.font("Verdana", FontWeight.EXTRA_BOLD, 18)
}
 
    var descriptionText: Text =  Text{
        x:topRDescription.x + 10
        y: topRDescription.y + 35
        content: bind vidDescription
        fill:Color.WHITE
        font: Font.font("Verdana", FontWeight.MEDIUM, 10)
}
 
    var groupVidMETA : Group = Group{
        content: [topRDescription,titleText,descriptionText]
        cursor: Cursor.DEFAULT
        opacity: 0.0
        visible: bind showInfoBar
}


Don't forget to add it in the returned Group of our component:

return Group{
            content: [mediaView,groupVidMETA]
        }


You can see that the visibility of our meta-information's bar is bind to a var showInfoBar. This is on this var that we will act to show/hide the bar. Sets this var to true and the informations will be displayed all the time... this is cool but quite boring when you want watch the movie which is behind. We will so add some animations to display it in a non disturbing way.

Create animation and triggers to show it in a non-disturbing way.

We will now display the bar only when the mouse is hover the video or when the video is paused.
To do this we will use the opacity of the bar and add animation in the MediaView's node triggers. Remember the MediaView is the node in which the media is displayed.
Resume of the things to do :

  • Add the onMouseEntered trigger to the MediaView,
  • Creates and play an animation in it which will set the groupVidMETA'opacity from it's current state to 1 in 500ms.
  • Add the onMouseExited trigger to the MediaView and add an animation to hide the meta's info only if the video is not paused.


The resulting mediaView var is then :

//The node displaying the video   
    var mediaView : MediaView = MediaView{
        x:x
        y:y
        mediaPlayer: bind mediaPlayer
        fitHeight:vidHeight
        fitWidth:vidWith
          //Show the informations in an animation.
        onMouseEntered: function(me: MouseEvent){
            Timeline {
                keyFrames: [
               at(0s) {
                    groupVidMETA.opacity =>    groupVidMETA.opacity tween Interpolator.LINEAR;
                    groupControlBar.opacity => groupControlBar.opacity  tween Interpolator.LINEAR;
                }
                  at(500ms) {
                    groupVidMETA.opacity => 1.0 tween Interpolator.LINEAR;
                    groupControlBar.opacity => 1.0 tween Interpolator.LINEAR;
            }
                ]
            }.play()
 
        }
        //Hide the informations in an animation if the video is not paused.
        onMouseExited: function(me: MouseEvent){
            if(mediaPlayer.status != MediaPlayer.PAUSED){
                Timeline {
                    keyFrames: [
               at(0s) {
                        groupVidMETA.opacity => groupVidMETA.opacity  tween Interpolator.LINEAR;
              }
                  at(500ms) {
                        groupVidMETA.opacity => 0.0 tween Interpolator.LINEAR;
            }
                    ]
                }.play();
 
            }
        }
};


We are abble to know if the video is paused by accessing the mediaPlayer's status and comparing it to it's PAUSED def.
Now that informations are displayed, we will add triggers to play or pause the video.

Add some triggers to our video : play/pause on click.

This is done by adding a onMouseClicked trigger to our mediaView Component. We will also check that it's the left button that is clicked to perform the play/pause and reserve the right button to maybe future functionnality.
Also we will set the cursor to the HAND def: tell our watcher that they can do something by clicking the video.
This is what we add to our mediaView component:

cursor:Cursor.HAND
 
        onMouseClicked: function(me: MouseEvent){
            //Only a left click play/pause the video
            if(me.button == MouseButton.PRIMARY ) {
 
                if(mediaPlayer.status == mediaPlayer.PAUSED){
                    mediaPlayer.play();
                }else {
                    mediaPlayer.pause();
                }
            }
        }


Enjoy ourselfs

You can find our component source in this post and view a demo on this page: demo of the videoPlayer - part 2.
It can be long to load... My server fault ...