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.
- using System;
- using System.Windows;
- using System.Windows.Controls;
- using System.Collections.Generic;
- using System.Linq;
- namespace DblClickTest
- {
- public static class DoubleClickService
- {
- // time limit between mouse up / click events to be handled as a double click
- private static readonly int TimeLimit = 1000;
- // list to track earlier mouse up / click events and related controls + handlers
- private static List<Tuple<UIElement, DateTime, RoutedEventHandler>> dict = new List<Tuple<UIElement, DateTime, RoutedEventHandler>>();
- public static DependencyProperty DoubleClickProperty = DependencyProperty.RegisterAttached("DoubleClick", typeof(RoutedEventHandler), typeof(DoubleClickService), new PropertyMetadata(null, DoubleClickChanged));
- private static void DoubleClickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- UIElement uiElement = d as UIElement;
- RoutedEventHandler clickFunc = e.NewValue as RoutedEventHandler;
- if (uiElement != null && clickFunc != null)
- {
- Button button = uiElement as Button;
- if (button != null)
- {
- button.Click += (sender, arg) => HandleClick(sender, arg, clickFunc);
- }
- else
- {
- uiElement.MouseLeftButtonUp += (sender, arg) => HandleClick(sender, arg, clickFunc);
- }
- }
- }
- private static void HandleClick(object sender, RoutedEventArgs e, RoutedEventHandler handler)
- {
- UIElement uie = sender as UIElement;
- // just do the cleanup: remove old entries
- dict = dict.Where(p => (p.Item2 > DateTime.Now.AddMilliseconds(-1 * TimeLimit))).ToList();
- Tuple<UIElement, DateTime, RoutedEventHandler> item = dict.FirstOrDefault(p => (p.Item1 == sender));
- if (item == null)
- {
- dict.Add(new Tuple<UIElement, DateTime, RoutedEventHandler>(uie, DateTime.Now, handler));
- }
- else
- {
- item.Item3.Invoke(sender, e);
- }
- }
- public static RoutedEventHandler GetDoubleClick(UIElement obj)
- {
- return (RoutedEventHandler)obj.GetValue(DoubleClickProperty);
- }
- public static void SetDoubleClick(DependencyObject obj, RoutedEventHandler clickFunc)
- {
- obj.SetValue(DoubleClickProperty, clickFunc);
- }
- }
- }
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:
- <UserControl x:Class="DblClickTest.MainPage"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:ap="clr-namespace:DblClickTest"
- mc:Ignorable="d"
- d:DesignHeight="300" d:DesignWidth="400"
- x:Name="Top">
- <StackPanel>
- <TextBlock Text="Double click me!"
- ap:DoubleClickService.DoubleClick="{Binding ElementName=Top, Path=DblClickHandler}"/>
- <TextBox Name="Output" />
- <Button x:Name="MyButton"
- Content="Or me!"
- ap:DoubleClickService.DoubleClick="{Binding ElementName=Top, Path=DblClickHandler}"/>
- <ListBox ap:DoubleClickService.DoubleClick="{Binding ElementName=Top, Path=DblClickHandler}">
- <ListBoxItem Content="Item 1"></ListBoxItem>
- <ListBoxItem Content="Item 2"></ListBoxItem>
- </ListBox>
- </StackPanel>
- </UserControl>
The related code-behind:
- using System;
- using System.Linq;
- using System.Net;
- using System.Windows;
- using System.Windows.Controls;
- using System.ComponentModel;
- namespace DblClickTest
- {
- public partial class MainPage : UserControl
- {
- public MainPage()
- {
- InitializeComponent();
- }
- public RoutedEventHandler DblClickHandler
- {
- get
- {
- return new RoutedEventHandler(OnDblClick);
- }
- }
- private void OnDblClick(object sender, RoutedEventArgs e)
- {
- Output.Text = String.Format("Double click on {0}", sender);
- }
- }
- }
The sample application in action, after double-clicking on the second list box item:
Note: In a real application you might have to add synchronization code for the static list object access and / or exception handling.