Silverlight 3 Behaviors – Double click selection on a DataGrid

by Tom 15. July 2009 17:45

If you hadn’t noticed, behaviors created in Silverlight 3 beta will be broken in Silverlight 3 release. The reason for this is that the assembly Microsoft.Expression.Interactivty.dll (that used to be found in C:\Program Files\Microsoft Expression\Blend 3\Libraries\Silverlight\v3.0) no longer exists. Instead, it’s replaced by System.Windows.Interactivity.dll (which is found in C:\Program Files\Microsoft SDKs\Expression\Blend 3\Interactivity\Libraries\Silverlight). Seems simple enough, sure, but it took a good hour (with fists clenched) for me to work this out today since I couldn’t find any docs on it at all.

So, to turn this rant into something useful, I’ll talk about a useful trigger for grabbing a double click on UIElements (such as DataGrid).

The scenario is you have a list of items displayed in a DataGrid and want to select one by double clicking it. We’ll need to code two classes: 1. a trigger that fires on double click; 2. an action that can be fired in response to the trigger (e.g. an action that executes an ICommand).

The first step, is to create the trigger .cs file. Visual Studio 2008 and Blend 3 now provide templates for Triggers, Actions & Behaviors to get you started. Here’s what the template looks like in Visual Studio:

15-07-2009 10-11-13 PM

This creates the class, adds the necessary overrides and adds a reference to System.Windows.Interactivity.dll.

Now to implement the double click trigger. There’s not a lot of mystery about how to do this, and I borrowed the logic from this useful blog post: Silverlight 3 Behaviors : Double Click Trigger – but extended it in several key areas.

Firstly, I used UIElement.Addhandler() to attach the mouse events (MouseLeftButtonDown, MouseLeftButtonUp) in order to ensure the trigger works on UIElements that mark these events handled (like Button for instance).

Secondly, I wanted to be selective and intelligent about the parameter passed in to TriggerBase.InvokeActions(). The important thing here is that, in different circumstances, I’ll want to pass in different parameters. For instance, if the control that fired the trigger is a DataGrid, I’ll want to pass in the SelectedItem; if the control is a Selector, I’ll want to pass in the SelectedItem; or if the control is something else, I may want to pass in the DataContext.

In order to be as generic and extensible as possible, I decided to build a base class that I could inherit other classes from. Here’s my implementation:

using System;
using System.Windows;
using System.Windows.Threading;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace GridClicker.Behaviors
{
    public class DoubleClickTriggerBase : TriggerBase<UIElement>
    {
        private readonly DispatcherTimer _timer;

        public DoubleClickTriggerBase()
        {
            _timer = new DispatcherTimer
            {
                Interval = new TimeSpan(0, 0, 0, 0, 200)
            };
            _timer.Tick += OnTimerTick;
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.AddHandler(UIElement.MouseLeftButtonDownEvent,
                                        new MouseButtonEventHandler(OnMouseAction), true);
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.RemoveHandler(UIElement.MouseLeftButtonDownEvent,
                                           new MouseButtonEventHandler(OnMouseAction));
            if (_timer.IsEnabled)
            {
                _timer.Stop();
            }
        }

        private void OnMouseAction(object sender, MouseButtonEventArgs e)
        {
            if (!_timer.IsEnabled)
            {
                _timer.Start();
                return;
            }
            _timer.Stop();
            DoInvoke();
        }

        private void OnTimerTick(object sender, EventArgs e)
        {
            _timer.Stop();
        }

        protected virtual void DoInvoke()
        {
            InvokeActions(null);
        }
    }
}

The key thing here is the virtual DoInvoke() method that can be overridden in descendent classes if a special value need to be passed as parameter to InvokeActions().

Here’s an inherited class, DataGridDoubleClickTrigger.cs:

using System.Windows.Controls;

namespace GridClicker.Behaviors
{
    public class DataGridDoubleClickTrigger : DoubleClickTriggerBase
    {
        protected override void DoInvoke()
        {
            var dg = AssociatedObject as DataGrid;
            var o = dg == null ? null : dg.SelectedItem;
            if (o != null)
            {
                InvokeActions(o);
            }
        }
    }
}

This simply passes in the SelectedItem of the DataGrid as the InvokeAction() parameter. Of course you could make any number of classes that inherit from DoubleClickTriggerBase. I should point out that there is an assumption here that the user will double click on a row of the DataGrid and not the header, though it will not break in any case. If a user does double click on the header of the DataGrid, if the SelectedItem is not null it will be returned, otherwise nothing will happen.

Now a trigger is no good without an action to fire, so here is a very quick and simplistic action that executes an ICommand. Firstly, create the action in Visual Studio:

15-07-2009 11-27-48 PM

And here’s the implementation:

using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace GridClicker.Behaviors
{
    public class ExecuteCommandAction : TriggerAction<FrameworkElement>
    {
        public string CommandName { get; set; }

        protected override void Invoke(object o)
        {
            if (CommandName == null) return;
            var dataContext = AssociatedObject.DataContext;
            if (dataContext == null) return;
            var command = dataContext.GetType().GetProperty(CommandName).GetValue(dataContext, null) as ICommand;
            if (command != null && command.CanExecute(o))
            {
                command.Execute(o);
            }
        }
    }
}

Even though TriggerAction is a DependencyObject, it doesn’t seem to want to let you make CommandName a DependencyProperty and bind it’s value. No idea why this is, hence the roundabout route to grab the command.

To put it altogether, here is the XAML:

<StackPanel x:Name="ContentStackPanel">

    <TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}" 
                       Text="Home"/>

    <data:DataGrid ItemsSource="{Binding Categories}" IsReadOnly="True">
        <i:Interaction.Triggers>
            <b:DataGridDoubleClickTrigger>
                <b:ExecuteCommandAction CommandName="SelectCommand" />
            </b:DataGridDoubleClickTrigger>
        </i:Interaction.Triggers>
    </data:DataGrid>
    
    <Button Content="Double Click Button" Margin="10" VerticalAlignment="Center" HorizontalAlignment="Center">
        <i:Interaction.Triggers>
            <b:DoubleClickTriggerBase>
                <b:ExecuteCommandAction CommandName="ButtonCommand"/>
            </b:DoubleClickTriggerBase>
        </i:Interaction.Triggers>
    </Button>

</StackPanel>

Note that I also put in a Button that demonstrates that the base class – DoubleClickTriggerBase - is useful in instances where you have no need to pass a parameter to the InvokeActions().

Finally, to round things off, here is the view’s presenter:

public class HomePresenter : INotifyPropertyChanged
{
    public ICommand SelectCommand { get; private set; }
    public ICommand ButtonCommand { get; private set; }

    public HomePresenter()
    {
        SelectCommand = new DelegateCommand<object>(o =>
        {
            var cat = o as Category;
            var dw = new DetailsWindow {Width = 300.0, Height = 300.0};
            var g = dw.FindName("LayoutRoot") as Grid;
            g.Children.Add(new TextBlock() 
            { 
                Text = "Category Name: " + cat.CategoryName,
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center
            });
            dw.Show();
        });

        ButtonCommand = new DelegateCommand<object>(o => HtmlPage.Window.Alert("Button was double clicked!"));
    }

    public ObservableCollection<Category> Categories
    {
        get { return CategoryHelper.GetCategories(); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

 

In the example I am using Prism’s DelegateCommand implementation of ICommand.

There you have it!

Source Code

Categories: Work

Comments

Namoro na internet
Namoro na internet United States on 8/7/2009 12:59:02 AM

It is indeed a great resource to obtain information about Silverlight 3.  Keep posting. Thanks.

Surplus Salvage Merchandise
Surplus Salvage Merchandise United States on 10/8/2009 2:26:43 PM

The post was great..I enjoyed it a lot! Smile

cash loans
cash loans United States on 10/17/2009 12:32:18 PM

Hmmm interesting stuff

cash loans
cash loans United States on 10/17/2009 12:33:15 PM

Do you make money out of this blog? just curious

Britney Spears
Britney Spears United States on 10/20/2009 7:14:57 PM

Hi Tom,

This is mindblowing! I finally start to grasp the usage of triggers in silverlight. Thanks to your post!

Yours,
Britney

buy cheap aion items
buy cheap aion items United States on 10/24/2009 3:07:00 AM

I bookmarked your post will read this latter


Regards

hops kalis

buy cheap aion accounts
buy cheap aion accounts United States on 10/24/2009 6:47:10 AM

Now this is hghly recommeded post for me. I will surely email this to my friend.


Regards

paul

personal loans
personal loans United States on 11/4/2009 12:23:02 PM

Like your writing! Still you can do some things to improve it.

Josh Noble
Josh Noble United Kingdom on 11/9/2009 7:30:25 PM

Super helpful thanks for sharing tom.

Discount Abercrombie and Fitch
Discount Abercrombie and Fitch United Kingdom on 12/10/2009 9:40:58 AM

Keep up the good work bro.Your article is really great and I truly enjoyed reading it.Waiting for some more great articles like this from you in the coming days.

anonymous proxy
anonymous proxy Thailand on 12/17/2009 1:32:15 AM

Great insights. I loved to read your article. You must be putting a lot of time into your blog!

Bikes
Bikes Thailand on 12/17/2009 4:05:05 PM

Nice post.

proxy
proxy Thailand on 12/18/2009 12:18:15 AM

Great share. Keep up the good work.

Skin care
Skin care Thailand on 12/18/2009 10:41:45 AM

Your post give me some idea. Thank you.

Camera lense
Camera lense on 12/18/2009 12:49:12 PM

Excellent post.

Digital camera
Digital camera United States on 12/18/2009 1:56:36 PM

Great post. I like it.

payday loans
payday loans United States on 12/20/2009 7:31:42 PM

Do you make money out of this blog? just curious

ผลบอล
ผลบอล Thailand on 12/22/2009 1:30:44 AM

This is a very nice post.

unblock myspace
unblock myspace on 12/22/2009 3:29:04 PM

Great blog, this could be the best blog I ever visited thi month. Never stop to write something useful dude!.

Ggler
Ggler United States on 12/29/2009 9:38:37 AM

Nice post. I love it.

Hand blender
Hand blender United States on 12/30/2009 11:05:35 AM

Your post is so nice.

Niche Blueprint 2.0 Review
Niche Blueprint 2.0 Review United States on 12/30/2009 1:02:13 PM

I love reading your posts. I wish you a Happy New Year!

Schnell Geld verdienen im Internet
Schnell Geld verdienen im Internet Germany on 1/7/2010 8:59:09 PM

Nice article neede something like that, thanks!!

Apartments Buenos Aires
Apartments Buenos Aires United States on 1/15/2010 11:20:54 PM

For the great lucidity in your writing. I will right away grab your rss feed to stay informed of any updates.

online payday loans
online payday loans United States on 1/20/2010 2:42:19 AM

Attempt the impossible in order to improve your work.

Loans in TX
Loans in TX United States on 1/21/2010 1:31:19 PM

Nothing is so much fun as business. I do not expect to do anything but work as long as I can stand up.

pay day loans
pay day loans United States on 1/23/2010 12:21:38 PM

Just as appetite comes by eating, so work brings inspiration, if inspiration is not discernible at the beginning.

video chat software
video chat software United States on 1/23/2010 4:03:04 PM

I really appreciate posts, which might be of very useful for beginners in blogging as I am. I already have a small collection of blog posts and other articles, from which I step by step learn various aspects of life. Thank you for your resource.

Buenos Aires apartments
Buenos Aires apartments United States on 1/27/2010 4:58:05 PM

Thanks for keeping us informed with all these news!

payday advance
payday advance United States on 1/28/2010 3:53:00 PM

Nice post and very helpfull

online advance
online advance United States on 1/30/2010 3:55:44 AM

Haste in every business brings failure.

Gautham
Gautham India on 2/1/2010 4:38:57 PM

Thank you ..

This helped me to learn basics of 'Triggers'.

Please help me to subscribe your blog.

       With Regards,
              Gautham

personal loans
personal loans United States on 2/5/2010 9:21:42 PM

A weed is no more than a flower in disguise.

ehliyet sınav sonuçları
ehliyet sınav sonuçları Qatar on 2/7/2010 4:36:48 AM

Thanks for good information.

ssk sorgulama
ssk sorgulama Qatar on 2/8/2010 8:45:51 PM

Thanks for good information.

bagkur sorgulama
bagkur sorgulama Puerto Rico on 2/11/2010 6:38:44 PM

I admire the valuable information you offer in your articles. I will bookmark your blog and have my children check up here often. I am quite sure they will learn lots of new stuff here than anybody else!

alta white teeth whitening
alta white teeth whitening United States on 2/15/2010 11:49:42 PM

If you think of vision and mission as an organization's head and heart, the values it holds are its soul.

Amy24Pz
Amy24Pz Canada on 2/16/2010 3:14:27 AM

A lot of students know techniques of argumentative essay creating, but it doesn't mean they are able compose high quality essays, but a <a href="quality-papers.com/.../communication_and_media_essays">media essay</a> service will aid to write the argumentative essay of great quality and improve writing skills of some students.

Coupons
Coupons United States on 2/20/2010 11:41:50 AM

This is a pretty fluent writing style you have here. keep up the good work.

personal loans
personal loans Canada on 3/4/2010 11:44:31 AM

I took my first <a href="lowest-rate-loans.com/.../home-loans">home loans</a> when I was very young and it supported my relatives very much. Nevertheless, I require the consolidation loans once more time.

autism characteristics
autism characteristics United Kingdom on 3/4/2010 5:03:30 PM

Having revised your blog, I’ve found many peculiar things that requires the special attention of specialist.    

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading