Second Life of a Hungarian SharePoint Geek

January 8, 2012

Implementing double click in Silverlight

Filed under: Silverlight — Tags: — Peter Holpar @ 23:01

Recently I faced with the problem, that double click is not supported by Silverlight 4. On the web I found several solutions how to implement it for individual controls (regarding Silverlight 5, read this), however I wished a more general solution.

Inspired by this default button example (another missing feature in SL 4), I’ve implemented the following proof of concept (PoC) solution. I’ve created an attached property (called DoubleClick) where you can configure a RoutedEventHandler for your controls. We will use  this property to return our RoutedEventHandler instance that should handle double click events.

  1. using System;
  2. using System.Windows;
  3. using System.Windows.Controls;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6.  
  7. namespace DblClickTest
  8. {
  9.  
  10.     public static class DoubleClickService
  11.     {
  12.         // time limit between mouse up / click events to be handled as a double click
  13.         private static readonly int TimeLimit = 1000;
  14.  
  15.         // list to track earlier mouse up / click events and related controls + handlers
  16.         private static List<Tuple<UIElement, DateTime, RoutedEventHandler>> dict = new List<Tuple<UIElement, DateTime, RoutedEventHandler>>();
  17.  
  18.         public static DependencyProperty DoubleClickProperty = DependencyProperty.RegisterAttached("DoubleClick", typeof(RoutedEventHandler), typeof(DoubleClickService), new PropertyMetadata(null, DoubleClickChanged));
  19.  
  20.         private static void DoubleClickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  21.         {
  22.             UIElement uiElement = d as UIElement;
  23.             RoutedEventHandler clickFunc = e.NewValue as RoutedEventHandler;
  24.             if (uiElement != null && clickFunc != null)
  25.             {
  26.                 Button button = uiElement as Button;
  27.                 if (button != null)
  28.                 {
  29.                     button.Click += (sender, arg) => HandleClick(sender, arg, clickFunc);                    
  30.                 }
  31.                 else
  32.                 {
  33.                     uiElement.MouseLeftButtonUp += (sender, arg) => HandleClick(sender, arg, clickFunc);
  34.                 }
  35.             }
  36.         }
  37.  
  38.         private static void HandleClick(object sender, RoutedEventArgs e, RoutedEventHandler handler)
  39.         {
  40.             UIElement uie = sender as UIElement;
  41.             // just do the cleanup: remove old entries            
  42.             dict = dict.Where(p => (p.Item2 > DateTime.Now.AddMilliseconds(-1 * TimeLimit))).ToList();
  43.             Tuple<UIElement, DateTime, RoutedEventHandler> item = dict.FirstOrDefault(p => (p.Item1 == sender));
  44.             if (item == null)
  45.             {
  46.                 dict.Add(new Tuple<UIElement, DateTime, RoutedEventHandler>(uie, DateTime.Now, handler));
  47.             }
  48.             else
  49.             {
  50.                 item.Item3.Invoke(sender, e);
  51.  
  52.             }
  53.         }
  54.  
  55.         public static RoutedEventHandler GetDoubleClick(UIElement obj)
  56.         {
  57.             return (RoutedEventHandler)obj.GetValue(DoubleClickProperty);
  58.         }
  59.  
  60.         public static void SetDoubleClick(DependencyObject obj, RoutedEventHandler clickFunc)
  61.         {
  62.             obj.SetValue(DoubleClickProperty, clickFunc);
  63.         }
  64.     }
  65. }

My static class is built around a generic List of Tuple objects, that is used to track UIElement instances and related click events and handlers.

In the DoubleClickChanged method I register my handler method. If the control is a Button, then I attach to the Click event, otherwise the MouseLeftButtonUp event is used. That is needed because the Button control handles the MouseLeftButtonDown /MouseLeftButtonUp  events internally and set these methods as handled, that means it does not forward these events forward.

The HandleClick handler method first remove outdated entries from the list (you can set the value of TimeLimit  to match your needs), then if the source control is not found in the list, it is registered with the current time and the specified event handler, otherwise the event is considered a double click, and the specified RoutedEventHandler is invoked, passing the original sender and RoutedEventArgs parameters.

Note: The referenced post above uses the MouseLeftButtonDown event. Based on my experience this event is not triggered for example on ListBox control, so I switched to the MouseLeftButtonUp event, that is a bit different from the standard Windows double click behavior.

The MainPage.xaml for the test application looks like this:

  1. <UserControl x:Class="DblClickTest.MainPage"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
  4.     xmlns:d="http://schemas.microsoft.com/expression/blend/2008&quot;
  5.     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006&quot;
  6.     xmlns:ap="clr-namespace:DblClickTest"
  7.     mc:Ignorable="d"
  8.     d:DesignHeight="300" d:DesignWidth="400"
  9.     x:Name="Top">
  10.  
  11.     <StackPanel>
  12.         <TextBlock Text="Double click me!"
  13.                    ap:DoubleClickService.DoubleClick="{Binding ElementName=Top, Path=DblClickHandler}"/>
  14.         <TextBox Name="Output" />
  15.                    <Button x:Name="MyButton"
  16.                 Content="Or me!"
  17.                 ap:DoubleClickService.DoubleClick="{Binding ElementName=Top, Path=DblClickHandler}"/>
  18.         <ListBox ap:DoubleClickService.DoubleClick="{Binding ElementName=Top, Path=DblClickHandler}">
  19.             <ListBoxItem Content="Item 1"></ListBoxItem>
  20.             <ListBoxItem Content="Item 2"></ListBoxItem>
  21.         </ListBox>
  22.  
  23.     </StackPanel>
  24. </UserControl>

The related code-behind:

  1. using System;
  2. using System.Linq;
  3. using System.Net;
  4. using System.Windows;
  5. using System.Windows.Controls;
  6.  
  7. using System.ComponentModel;
  8.  
  9. namespace DblClickTest
  10. {
  11.     public partial class MainPage : UserControl
  12.     {
  13.         public MainPage()
  14.         {
  15.             InitializeComponent();
  16.         }
  17.        
  18.         public RoutedEventHandler DblClickHandler
  19.         {
  20.             get
  21.             {
  22.                 return new RoutedEventHandler(OnDblClick);
  23.             }
  24.         }
  25.  
  26.         private void OnDblClick(object sender, RoutedEventArgs e)
  27.         {
  28.             Output.Text = String.Format("Double click on {0}", sender);
  29.         }
  30.  
  31.     }
  32. }

The sample application in action, after double-clicking on the second list box item:

image

Note: In a real application you might have to add synchronization code for the static list object access and / or exception handling.

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: