(X) Hide this
    • Login
    • Join
      • Say No Bots Generate New Image
        By clicking 'Register' you accept the terms of use .

Behaviors and Triggers in Silverlight

(19 votes)
Pencho Popadiyn
>
Pencho Popadiyn
Joined Apr 30, 2008
Articles:   22
Comments:   97
More Articles
30 comments   /   posted on Aug 19, 2009
Categories:   Data Binding , General

This article is compatible with the latest version of Silverlight.


1. Introduction

With the release of Silverlight 3, a lot of new cool features have been introduced. One of my favorite definitely is the support of behaviors and triggers. In WPF the triggers are extremely powerful. They allow you to declaratively associate an action with an event or property value. In the previous versions of Silverlight one of the things that were really missing were the triggers and the behaviors. It was not possible, for example, to add mouse-overs to objects declaratively (as in WPF). Currently it is still not possible, you have to write procedural code, but at least that code is separated, so the designer doesn’t have to write it or understand it. In that post I will focus exactly on that new feature in Silverlight and will show you how to create your own custom behaviors and triggers.

You can find the demos in the end of each section.

Download Source

2. Prerequisites

In order to write behaviors and triggers you need to have installed the Microsoft Expression Blend SDK on your machine. After that you need to add a reference to the System.Windows.Interactivity.dll assembly which is located in:

{Program Files}\Microsoft SDKs\Expression\Blend 3\Interactivity\Libraries\Silverlight

I don’t know why but this assembly currently is not part of the Silverlight SDK. You can download the Expression Blend product from here.

3. Behavior types

Currently you can use three types of bevahors: Behavour, TriggerAction and TargetedTriggerAction. The class diagram is shown on the next figure:

4. Using the Behavior<T> class

For simple scenarios the generic Behavior<T> class is excellent choice. This class has only two overridable methods which notify the current behavior when an object is attached and detached from it. For my post I will create two behaviors: the first one just inverts the color of an image, when the image is clicked, and the second is little more complicated – it adds an animation and creates a magnifier when the mouse is over the image.

The first thing you should do when creating behaviors is to create a new class which inherits from the generic class Behavior<T>, where T is a dependency object. In the most cases you will inherit from the Behavior<FrameworkElement> or Behavior<UIElement>.

public class InverseColorClickBehavior : 
    Behavior<FrameworkElement>
{
    public InverseColorClickBehavior() :
        base()
    {
    }
}

For that particular case I am interested in the mouse click event. That’s why in the OnAttached method I will attach to the MouseLeftButtonDown event of the associated with that behavior object. And respectively in the OnDetaching method I will detach from that event.

protected override void OnAttached()
{
    base.OnAttached();
    this.AssociatedObject.MouseLeftButtonDown += 
        new MouseButtonEventHandler( AssociatedObject_MouseLeftButtonDown );
}
 
protected override void OnDetaching()
{
    base.OnDetaching();
    this.AssociatedObject.MouseLeftButtonDown -= 
        new MouseButtonEventHandler( AssociatedObject_MouseLeftButtonDown );
}

The only thing that's left is to add an inverse color effect to the associated object in the mouse Click event handler.

private void AssociatedObject_MouseLeftButtonDown( 
    object sender, MouseButtonEventArgs e )
{
    this.AssociatedObject.Effect = 
        this.AssociatedObject.Effect == null ?
            this.AssociatedObject.Effect = this.inverseColor :
            this.AssociatedObject.Effect = null;
}
 
Once we have created the bahavior we should attach it to a particular object.
 
<Image Stretch="Fill"
       Source="/Photos/Image1.jpg">
    <interactivity:Interaction.Behaviors>
        <local:InverseColorClickBehavior/>
    </interactivity:Interaction.Behaviors>
</Image>
 
And here is the demo. First time you click the image the colors are inverted while on the second click the orignal is restored.
My second behavior is little more complicated. It hits more common scenario where you want to add an animation for example on the mouse over event. Again as the first example I will create a new class which inherits from the generic Behavior<T> where T will be a FrameworkElement.

I want when the mouse is over the element to add a magnifier shader effect on the element and when the mouse leaves the element area to remove the effect. That’s why I want to handle the MouseEnter and MouseLeave events in order to enable and disable the effect. On the analogy of the previous case in the OnAttached method I will attach to the MouseEnter and MouseLeave events of the associated with that behavior object. And respectively in the OnDetaching method I will detach from that events. When the mouse is over the element I want to track the mouse move event. That’s why I will attach also to the mouse move event on entering and will detach from it on leaving the element area. 

protected override void OnAttached()
{
    base.OnAttached();
 
    this.AssociatedObject.MouseEnter += 
        new MouseEventHandler( AssociatedObject_MouseEnter );
    this.AssociatedObject.MouseLeave += 
        new MouseEventHandler( AssociatedObject_MouseLeave );
}
 
protected override void OnDetaching()
{
    base.OnDetaching();
 
    this.AssociatedObject.MouseEnter -= 
        new MouseEventHandler( AssociatedObject_MouseEnter );
    this.AssociatedObject.MouseLeave -= 
        new MouseEventHandler( AssociatedObject_MouseLeave );
}
 
private void AssociatedObject_MouseLeave( object sender, MouseEventArgs e )
{
    this.AssociatedObject.MouseMove -= 
        new MouseEventHandler( AssociatedObject_MouseMove );
    this.AssociatedObject.Effect = null;
}
 
private void AssociatedObject_MouseEnter( object sender, MouseEventArgs e )
{
    this.AssociatedObject.MouseMove += 
        new MouseEventHandler( AssociatedObject_MouseMove );
    this.AssociatedObject.Effect = this.magnifier;
}

 The whole work is done in the mouse move event handler.

private void AssociatedObject_MouseMove( object sender, MouseEventArgs e )
{
    ( this.AssociatedObject.Effect as Magnifier ).Center =
        e.GetPosition( this.AssociatedObject );
 
    Point mousePosition = e.GetPosition( this.AssociatedObject );
    mousePosition.X /= this.AssociatedObject.ActualWidth;
    mousePosition.Y /= this.AssociatedObject.ActualHeight;
    this.magnifier.Center = mousePosition;
 
    Storyboard zoomInStoryboard = new Storyboard();
    DoubleAnimation zoomInAnimation = new DoubleAnimation();
    zoomInAnimation.To = this.magnifier.Magnification;
    zoomInAnimation.Duration = TimeSpan.FromSeconds( 0.5 );
    Storyboard.SetTarget( zoomInAnimation, this.AssociatedObject.Effect );
    Storyboard.SetTargetProperty( zoomInAnimation, 
        new PropertyPath( Magnifier.MagnificationProperty ) );
    zoomInAnimation.FillBehavior = FillBehavior.HoldEnd;
    zoomInStoryboard.Children.Add( zoomInAnimation );
    zoomInStoryboard.Begin();
}

You can add this behavior in XAML the same way as the first one. And here is the demo for the second behavior (which covers maybe the most commonly used events for animations, effects, etc.). Just move your mouse cursor over the image.

To summarize before continuing with the triggers, in my opinion the behaviors is very similar to the extension methods in C#. The only difference is that the behavior is a component. It encapsulates some functionality and can be attached to another component to extend its built-in functionality.

5. Using the TriggerAction<T> class

In simple cases the Behavior<T> class is perfect. The TriggerAction<T> class is useful in much more common cases than the simple Behavior<T>. The TriggerAction<T> offers invoke method which is fired once an event trigger happens. When you use the Behavior<T> class you require that the behavior is responsible for the attaching and detaching of the item events. In comparison when using the TriggerAction<T> you specify which event triggers the action in the XAML, as you will see in the next demo.

I will create a trigger that will apply an animation on the click event. The first step is to create a new class which inherits from the generic TriggerAction<T> class.

public class WaveTrigger : TriggerAction<FrameworkElement>
{
    protected override void Invoke( object parameter )
    {
        this.waveStoryboard.Begin();
    }
}
As you can see the Invoke method is the only required method that you need to have in your trigger. But often in the practice you will need to override several other methods of the class. In the constructor of our trigger I need to configure the animation and the storyboard.
public WaveTrigger() :
   base()
{
   this.waveAnimation = new DoubleAnimation();
   this.waveStoryboard = new Storyboard();
   this.waveEffect = new WaveEffect();
   this.InitializeTrigger();
   this.waveAnimation.AutoReverse = false;
   this.waveStoryboard.Children.Add( this.waveAnimation );
}
private void InitializeTrigger()
{
    this.waveAnimation.From = -this.WaveFrequency;
    this.waveAnimation.To = this.WaveFrequency;
    this.waveAnimation.Duration = this.WaveDuration;
}

In order to set the animated object as well as the animated property I need to override that the OnAttached method. If you try to do that, for example, in the constructor you won’t succeed due to the fact that the associated object is still unknown.

protected override void OnAttached()
{
    base.OnAttached();
    
    this.AssociatedObject.Effect = this.waveEffect;
    Storyboard.SetTarget( this.waveAnimation, 
        this.AssociatedObject.Effect );
    Storyboard.SetTargetProperty( this.waveAnimation, 
        new PropertyPath( WaveEffect.WavinessProperty ) );
}
The only required method you need to do in the Invoke method is to start the animation.
 
protected override void Invoke( object parameter )
{
    this.waveStoryboard.Begin();
}
In order to make my WaveTrigger configurable I will add several dependency properties (for the duration and for the frequency). This is pretty straightforward. Once we have our trigger the final step is to use it in the XAML.
 
<Image Stretch="Fill"
       Source="/Photos/Image3.jpg">
    <interactivity:Interaction.Triggers>
        <interactivity:EventTrigger 
                EventName="MouseLeftButtonUp">
            <local:WaveTrigger WaveFrequency="1.9" 
                WaveDuration="00:00:05"/>
        </interactivity:EventTrigger>
    </interactivity:Interaction.Triggers>
</Image>
And here you can see the result of our trigger. Just click the image and the trigger will be fired.

6. Using the TargetedTriggerAction<T> class

The third type behavior is offered by the generic TargetedTriggerAction<T> class. It represents an action that can be targeted to affect a totally different object rather than its associated object. I can’t remember any practical solution for that type of triggers, but in a few words it allows you to associate the trigger with an object, but to manipulate totally different element. For example in the next demo I will associate a targeted trigger with a button, but the target element (which will be flipped) will be an image.Again as a first step I will create a new class which inherits from the TargetedTriggerAction<T> class.

public class TurnImageTargetedTrigger : 
    TargetedTriggerAction<FrameworkElement>
{
    protected override void Invoke( object parameter )
    {
    }
 
    protected override void OnAttached()
    {
        base.OnAttached(); 
        ( this.AssociatedObject as FrameworkElement ).Loaded += 
            new RoutedEventHandler( TurnImageTargetedTrigger_Loaded );
    }
 
    protected override void OnDetaching()
    {
        base.OnDetaching();
        ( this.AssociatedObject as FrameworkElement ).Loaded -= 
            new RoutedEventHandler( TurnImageTargetedTrigger_Loaded );
    }
 
    private void TurnImageTargetedTrigger_Loaded( object sender, RoutedEventArgs e )
    {
    }
}

The trick here is that the target object can be access only when the associated object is loaded. That’s why I need to attach to the Loaded event of the associated object. An Invoke method must also be defined with the only purpose to start the animation. After the trigger is ready we need to use it in the XAML. You can see how this can be done in the next code snippet.

<Image x:Name="imgToFlip" Stretch="Fill"
    Source="/Photos/Image4.jpg"/>
<Button Content="TurnImage">
    <interactivity:Interaction.Triggers>
        <interactivity:EventTrigger 
                EventName="Click">
            <local:TurnImageTargetedTrigger 
                TargetName="imgToFlip"/>
        </interactivity:EventTrigger>
    </interactivity:Interaction.Triggers>
</Button>
And the demo can be seen on the next figure.

7. Behaviors and Expression Blend 3

Once our behavior (trigger) is ready, we can add it directly in XAML or via Expression Blend 3.

Once added to any element the behavior becomes nested inside the object.

The Properties pane you may adjust the behavior (setting properties, which event will fire it, etc.).

If you note in the Assets library except our behaviors created for the current solution there are several other behaviors which are listed in all Expression Blend projects. If you want your own custom behaviors also to be listed for all Blend project you need to register your assembly in the registry. You need to use the following path in the Registry Editor:

HKEY_CURRENT_USER(or HKEY_LOCAL_MACHINE) \Software\Microsoft\Expression\Blend\v3.0\Toolbox\Silverlight\v3.0

8. Using the DefaultTriggerAttribute

By default when you create a new TriggerAction, Expression Blend will associate it with the MouseLeftButtonDown event where it can, or with the Loaded event if the MouseLeftButtonDown is not available. However sometimes you may want a custom Action to be connected with a different default event. In order to do this you should use the DefaultTriggerAttibute:

public DefaultTriggerAttribute(Type targetType, Type triggerType, 
    params object[] parameters)
The first parameter is the type for which to create this Trigger, the second is the type of Trigger to create and the final is a list of constructor arguments to the Trigger when it is created. If you specify more than one attribute, the most derived targetType that is applicable when the user create the behavior will be used.
[DefaultTrigger(typeof(UIElement), typeof(EventTrigger), 
    "MouseLeftButtonDown")] 
[DefaultTrigger(typeof(ButtonBase), typeof(EventTrigger), 
    "Click")] 
public class WaveAction : TriggerAction<UIElement> 
{ 
}
If the WaveAction is dragged onto a Button, the Action will be created under a Click EventTrigger. However, if the WaveAction is dragged onto any other UIElement, a MouseLeftButtonDown trigger will be created.

9. Using the TypeConstraintAttribute

You can use the TypeConstraintAttribute to specify type constraints on the AssociatedObject of TargetedTriggerAction and EventTriggerBase.

10. Final words

The motivation for adding behaviors in Silverlight is twofold. First, the behaviors are somehow work-around for the missing triggers in Silverlight. They allow closing the gap between WPF and Silverlight. Second, they allow designers to add interactivity without needing to write any code. I like the behaviors and definitely will use them in any future projects. This post covers quite a bit ground. I hope it was useful for you. In the official site of Expression Blend you can find a lot of ready for use behaviors. Also see the References section for more information.

11. References


Subscribe

Comments

  • -_-

    RE: Behaviors and Triggers in Silverlight 3


    posted by Fallon Massey on Aug 20, 2009 21:18

    The TargetedTriggerAction can be used to effectively implement the isolation between the view and the model, because it's essentially commanding without having binding(it's done manually).

    I love this addition for all the reasons you listed, and see a lot of use in our development.

    Thanks for making these concepts clearer.

  • -_-

    RE: Behaviors and Triggers in Silverlight 3


    posted by qotsa on Sep 18, 2009 01:05
    when i try to run the first proyect i got an error because it doesnt find the inverseColor method, and i dont have the code,

    tnks
  • ppopadiyn

    RE: Behaviors and Triggers in Silverlight 3


    posted by ppopadiyn on Sep 18, 2009 07:31
    Hi qotsa

    Did you try to open the whole solution? It must include 3 projects.


  • -_-

    RE: Behaviors and Triggers in Silverlight 3


    posted by roboblob on Sep 23, 2009 10:02
    Thanks for the tutorial this is maybe the best resource on subject i found so far!

     

     


  • -_-

    RE: Behaviors and Triggers in Silverlight 3


    posted by Adam Kinney on Jan 08, 2010 02:05
    This is a great resource on Behaviors, nicely done.  Definitely worth sharing.
  • -_-

    RE: Behaviors and Triggers in Silverlight 3


    posted by Harish Kumar on Apr 04, 2010 08:46
    Great Post. Can you please provide some info regarding the project "SharedEffectsLibrary" in the solution?
  • -_-

    RE: Behaviors and Triggers in Silverlight 3


    posted by Samiha on Apr 13, 2010 14:12
    excellent
  • -_-

    RE: Behaviors and Triggers in Silverlight 3


    posted by SuperMan on May 20, 2010 02:52
    it's nice
  • -_-

    RE: Behaviors and Triggers in Silverlight 3


    posted by Ray Akkanson on Jul 08, 2010 17:08

    Any differences in Behaviors in Silverlight 4

    Ray Akkanson

  • ppopadiyn

    RE: Behaviors and Triggers in Silverlight 3


    posted by ppopadiyn on Jul 08, 2010 17:12

    Hi Ray,

    As far as I know, there are no differences with SL 4. Everything that is written in this article is valid also for SL4. Probably there are some predefined behaviors which are part of the new version of Expression Blend, but I am not familiar with them.

  • -_-

    RE: Behaviors and Triggers in Silverlight 3


    posted by Braulio Diez on Oct 11, 2010 23:46
    Great article, nice to learn, but the best... nice to use as reference material ;-).
  • ppopadiyn

    RE: Behaviors and Triggers in Silverlight 3


    posted by ppopadiyn on Oct 12, 2010 12:59
    Thanks Braulio ;)
  • lnikolov

    RE: Behaviors and Triggers in Silverlight


    posted by lnikolov on Dec 22, 2010 18:11
    The article has been updated to the latest version of Silverlight and Visual Studio.
  • Cathleen

    RE: Behaviors and Triggers in Silverlight


    posted by Cathleen on Jan 02, 2011 23:47
    excellent resource
  • -_-

    RE: Behaviors and Triggers in Silverlight


    posted by Sae1962 on Feb 25, 2011 17:09

    Hi!

    Behaviours and triggers are new to me. I tried the first and second example under Silverlight 4 with Microsoft Visual Studio 2010. Interestingly, the program runs, but there is no action: no colour change, no magnification. Can you help me?

    Thanks!

  • ppopadiyn

    RE: Behaviors and Triggers in Silverlight


    posted by ppopadiyn on Feb 25, 2011 17:20

    Hi Sae1962,

    I think, you should set the Behaviors.Web project as a start up project. It seems that after the conversion of the demo to SL4, the start up project get changed. Sorry for the inconvenience caused.

  • -_-

    RE: Behaviors and Triggers in Silverlight


    posted by Sae1962 on Feb 25, 2011 17:29

    Thank you verrry much! :-> That was the reason. Now, the first one runs properly. If other problems arise, I will let you know.

    Have a nice weekend!

  • -_-

    RE: Behaviors and Triggers in Silverlight


    posted by Sae1962 on Feb 25, 2011 18:15

    All the demos run very well. Also, I can see on this page the animations.

    I have only one question about the code. In the MainPage.xaml code, you use for the first click behaviour in the block 

    <interactivity:Interaction.Behaviors>

      <local:InverseColorClickBehavior/>

    </interactivity:Interaction.Behaviors>

    From where does the system find this local element? I created in another test project for my own a , but the local element remains unknown. The underlined part in italic is underlined with a blue waved line in my MVS2010. From where does the system "knows" the "local" in your project?

    Thank you for your information!

    public class DeleteOnRightMouseButtonDownBehavior : Behavior<FrameworkElement>

    ...

     <interactivity:Interaction.Behaviors>

      <local:DeleteOnRightMouseButtonDownBehavior/>

    </interactivity:Interaction.Behaviors>



  • -_-

    ad Question on local


    posted by Sae1962 on Feb 25, 2011 18:25
    I forgot to write you the error message MVS2010 gives for local. It reads "'local' is an undefined prefix. Line 24, position 7."
  • -_-

    Sorry for the double entries!


    posted by Sae1962 on Feb 25, 2011 18:30
    I pressed twice F5 (refresh) on the Microsoft Internet Explorer 8 and clicked OK on the message that warned that it would not be a good idea to refresh, if I am shopping. Interestingly, the last entered message appears a second time with a new timestamp.
  • ppopadiyn

    RE: Behaviors and Triggers in Silverlight


    posted by ppopadiyn on Feb 25, 2011 18:42

    Hi Sae1962,

    "local" is an alias for the namespace where the behavior (or generally, the target element you want to use in xaml) resides. For example I use "local" always when I want to import the namespace for the current project. Take a look at the declaration of UserControl with the examples in xaml:

    xmlns:local="clr-namespace:Behaviors".

     

     

  • -_-

    RE: Behaviors and Triggers in Silverlight


    posted by Nam Cao on Apr 27, 2011 21:26
    Very good articale. Thanks
  • blacklight

    Re: Behaviors and Triggers in Silverlight


    posted by blacklight on Jun 20, 2011 18:58
    I very much liked your article on behaviors. However, I have found that they can cause memory leaks unless you explicitly remove the behaviours. I did some investigation and found that the Unloaded event of a control is called when you navigate away from a page in Silverlight or WP7. This releases the reference to the AssociatedObject and frees up memory which is a big problem in WP7.

    public abstract class WeakBehavior<T> : Behavior<T> where T : FrameworkElement
    {
        protected virtual void OnAssociatedObjectUnloaded(object sender, RoutedEventArgs e)
        {
            Interaction.GetBehaviors(this.AssociatedObject).Remove(this);
        }

        protected override void OnAttached()
        {
            base.OnAttached();

            this.AssociatedObject.Unloaded += this.OnAssociatedObjectUnloaded;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();

            this.AssociatedObject.Unloaded -= this.OnAssociatedObjectUnloaded;
        }
    }
  • Philip

    Re: Behaviors and Triggers in Silverlight


    posted by Philip on Jun 23, 2011 23:01

    Thanks! Got my trigger written!

     

    public class DatePickerDefaultContextMenuBehavior : WeakBehavior<DatePicker> 
        {
            protected override void OnAttached()
            {
                base.OnAttached();
                this.AssociatedObject.MouseRightButtonUp += AssociatedObject_MouseRightButtonUp;
                this.AssociatedObject.MouseRightButtonDown += AssociatedObject_MouseRightButtonDown;         
            }
     
            protected override void OnDetaching()
            {
                base.OnDetaching();
                this.AssociatedObject.MouseRightButtonUp -= AssociatedObject_MouseRightButtonUp;
                this.AssociatedObject.MouseRightButtonDown -= AssociatedObject_MouseRightButtonDown;            
            }
            
            void AssociatedObject_MouseRightButtonDown(object senderMouseButtonEventArgs e)
            {
                e.Handled = true;
            }
     
            ContextMenu contextmenu;
     
            void AssociatedObject_MouseRightButtonUp(object senderMouseButtonEventArgs e)
            {
                if (contextmenu == null)
                {
                    Action<stringAdd = delegate(string header)
                    {
                        if (header == "-")
                        {
                            contextmenu.Items.Add(new Separator());
                        }
                        else
                        {
                            var item = new MenuItem();
                            item.Header = header;
                            item.Click += menuItem_Click;
                            contextmenu.Items.Add(item);
                        }
                    };
     
                    contextmenu = new ContextMenu();
                    Add("Clear Value");
                    Add("-");
                    Add("Today's Date");
                }
     
                var layoutRoot = AssociatedObject.GetBaseParent();
     
                contextmenu.IsOpen = true;
                contextmenu.HorizontalOffset = e.GetPosition(layoutRoot).X;
                contextmenu.VerticalOffset = e.GetPosition(layoutRoot).Y;
            }
     
            private void menuItem_Click(object senderRoutedEventArgs e)
            {
                switch ((sender as MenuItem).Header.ToString())
                {
                    case "Clear Value":
                        this.AssociatedObject.SelectedDate = null;
                        break;
     
                    default:
                        this.AssociatedObject.SelectedDate = DateTime.Today;
                        break;
                }
            }
        }
    
     
  • Ezra

    Re: Behaviors and Triggers in Silverlight


    posted by Ezra on Aug 02, 2011 13:27

    Thx for your article.Much helpful to me.

  • junyongmao

    Re: Behaviors and Triggers in Silverlight


    posted by junyongmao on Feb 02, 2013 06:44
    Very good ! Really appreciate for your sharing!
  • loxindudes2

    Re: Behaviors and Triggers in Silverlight


    posted by loxindudes2 on Jul 01, 2014 10:58
    And now HDB has launched a new Cool Ideas Fund to help winning participants transform their prototype creations into workable ones which can be tested in HDB blocks. There is a possibility the prototypes could be commercialised. north park in yishun
  • loxaindudes1

    Re: Behaviors and Triggers in Silverlight


    posted by loxaindudes1 on Jul 05, 2014 05:33
    Over at Marina Collection, a three-bedroom apartment of 2,098 sq ft on the first level and a 2,185-sq-ft four-bedder on the third level are marketed. north park tender
  • theterace

    Re: Behaviors and Triggers in Silverlight


    posted by theterace on Jul 15, 2014 07:43
    The biggest reason why Singapore has gone from position 17 in 2005 to the most expensive in 2012 is due to the exchange rate effect,” said Tan Kee Giap, ACI’s Co-director for Public Policy. The Terrace EC
  • yishunec1

    Re: Behaviors and Triggers in Silverlight


    posted by yishunec1 on Jul 17, 2014 10:44
    Resale prices of completed non-landed private homes slipped 0.4 percent in February from the month before as prices dropped in both the central and non-central regions, mcl land new ec

Add Comment

Login to comment:
  *      *