WPF DomainUpDown control

There is an MSDN page which made me chuckle recently when I was looking for a WPF equivalent of winform’s DomainUpDown control: according to the page the equivalent control in WPF is a TextBlock and two repeater buttons :) Cobbling together controls and code every time a need arises is not very appealing, so I decided to give it a go and create a WPF equivalent. The idea behind the DomainUpDown control is to allow the user to pick a value from a predefined list in a fashion similar to that of a ComboBox. The difference however is the fact that DomainUpDown does not require as much screen estate as the combo.  As you can imagine the control requires two dependency properties: one containing currently selected value and the other containing the list of “options”; when you click up or down either the next or the previous value from the list is selected. The following XAML illustrates how to use the control:

   1:         <Controls:DomainUpDown 
   2:                                 Value="{Binding CurrentValue}"
   3:                                 Items="{Binding Strings}" />

 

The sample application (screenshot below) illustrates various use cases of the control, including setting the value which is not present in the list of predefined “options” as well as behaviour of control when the list of has not been set.

image 

The code for the control and the sample app is available as part of the SharpFellows.Toolkit so I won’t bother you with the details here, but the majority of it is related to synchronisation of current value and its index in the list of options. This is important  when you set the value through binding (or code behind) and click either up or down buttons. One would expect that the correct next/previous value from the list is selected but this is where the original fails miserably and I found this behaviour rather annoying. The control also supports selecting the value using up/down keys and for those really inquisitive the full code is listed below.

using System;
using System.Collections;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace SharpFellows.Toolkit.Controls
{
    /// <summary>
    /// WPF DomainUpDown control
    /// </summary>
    [TemplatePart(Name = &quot;PART_UpButton&quot;, Type = typeof(RepeatButton))]
    [TemplatePart(Name = &quot;PART_DownButton&quot;, Type = typeof(RepeatButton))]
    [TemplatePart(Name = &quot;PART_TextBox&quot;, Type = typeof(TextBox))]
    public class DomainUpDown : Control
    {
        #region Fields
        private int _selectedIndex;
        private RepeatButton _upBuppton;
        private RepeatButton _downButton;
        #endregion

        #region Dependency properties
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register(&quot;Value&quot;, typeof (object), typeof (DomainUpDown), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged));

        public static readonly DependencyProperty ItemsProperty =
            DependencyProperty.Register(&quot;Items&quot;, typeof(IEnumerable), typeof(DomainUpDown), new PropertyMetadata(OnItemsChanged));
        #endregion

        /// <summary>
        /// Initializes the <see cref="DomainUpDown" /> class.
        /// </summary>
        static DomainUpDown()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DomainUpDown), new FrameworkPropertyMetadata(typeof(DomainUpDown)));
            BorderBrushProperty.OverrideMetadata(typeof(DomainUpDown), new FrameworkPropertyMetadata(SystemColors.ControlLightBrush));
        }

        /// <summary>
        /// Gets or sets the selected value.
        /// </summary>
        /// <value>The value.</value>
        public object Value
        {
            get { return GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        /// <summary>
        /// Gets or sets the items.
        /// </summary>
        /// <value>The items.</value>
        public IEnumerable Items
        {
            get { return GetValue(ItemsProperty) as IEnumerable; }
            set { SetValue(ItemsProperty, value);}
        }

        /// <summary>
        /// Gets or sets the index of the selected value.
        /// </summary>
        /// <value>The index of the selected.</value>
        protected int SelectedIndex
        {
            get { return _selectedIndex; }
            set
            {
                if (_selectedIndex == value)
                    return;

                _selectedIndex = value;

                Value = Items.Cast<object>().Skip(SelectedIndex).First();
            }
        }

        protected override void OnPreviewKeyDown(KeyEventArgs e)
        {
            base.OnPreviewKeyDown(e);

            if (e.Key == Key.Down)
            {
                if (_downButton != null)
                    _downButton.Focus();

                OnDown(this, null);
                e.Handled = true;
            }

            if (e.Key == Key.Up)
            {
                if (_upBuppton != null)
                    _upBuppton.Focus();

                OnUp(this, null);
                e.Handled = true;
            }
        }

        /// <summary>
        /// When overridden in a derived class, is invoked whenever application code or internal processes call 
        /// <see cref="M:System.Windows.FrameworkElement.ApplyTemplate"/>.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (_upBuppton != null)
                _upBuppton.Click -= OnUp;

            if (_downButton != null)
                _downButton.Click -= OnDown;

            // Get the parts and attach event handlers to them
            _upBuppton = GetTemplateChild("PART_UpButton") as RepeatButton;
            _downButton = GetTemplateChild("PART_DownButton") as RepeatButton;

            if (_upBuppton != null)
                _upBuppton.Click += OnUp;

            if (_downButton != null)
                _downButton.Click += OnDown;
        }

        /// <summary>
        /// Invoked whenever an unhandled <see cref="E:System.Windows.UIElement.GotFocus"/> event reaches this element in its route.
        /// </summary>
        /// <param name="e">The <see cref="T:System.Windows.RoutedEventArgs"/> that contains the event data.</param>
        protected override void OnGotFocus(RoutedEventArgs e)
        {
            base.OnGotFocus(e);

            // Move focus immediately to the buttons
            if (_upBuppton != null)
                _upBuppton.Focus();
        }

        private void OnUp(object sender, RoutedEventArgs routedEventArgs)
        {
            if (SelectedIndex > 0)
                SelectedIndex--;
        }

        private void OnDown(object sender, RoutedEventArgs routedEventArgs)
        {
            if (Items != null && SelectedIndex < Items.Cast<object>().Count() - 1)
                SelectedIndex++;
        }

        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var updown = d as DomainUpDown;
            SynchroniseValueWithIndex(updown, e.NewValue);
        }


        private static void OnItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var updown = d as DomainUpDown;
            SynchroniseValueWithIndex(updown, updown == null? null : updown.Value);
        }

        private static void SynchroniseValueWithIndex(DomainUpDown updown, object newValue)
        {
            if (updown == null || updown.Items == null)
                return;

            int i = 0;

            foreach (var element in updown.Items)
            {
                if (element.Equals(newValue))
                {
                    updown.SelectedIndex = i;
                    break;
                }

                i++;
            }
        }    
    }
}
September 30 2010

WPF Spinner – take two

The WPF wait indicator which I have published a while ago proved to be good as a starting point but somewhat limited when it came to using it in production: the major problem was the fact that changing its appearance required pretty much  total rework. As soon as I put it to use, new requirements arrived and it  was clear that we need a slightly different approach. First of all there was the issue of size, as sometimes you may need a smaller wait indicator, secondly the content was not very flexible and if it was to be changed, the key frame animation driving the whole thing needed to be reworked from scratch. So after a bit of head scratching I opted for a “lookless” approach and decided to implement a custom WPF ContentControl which would provide scaling and animation capabilities. To cut the long story short you can see the final effects below:

image

The control is dead easy to use as the following piece of XAML illustrates.

   1:         <Spinner:ContentSpinner Margin="10"
   2:                                  BorderBrush="Black"
   3:                                  BorderThickness="2" />

 

To make our life easier, the ContentSpinner provides a couple of dependency properties: first there is the Scale property which allows the user to scale the content, secondly NumberOfFrames property controls how many frames will be included in the animation and thirdly RevolutionsPerSecond drives a number of rotations the content will make each second. Also if you are not happy with the default content feel free to provide your own as in the XAML below:

   1:          <Spinner:ContentSpinner Margin="10"
   2:                                  BorderBrush="Black"
   3:                                  BorderThickness="2"
   4:                                  NumberOfFrames="8"
   5:                                  Content="{StaticResource blueDotsCanvas}">
   6:   
   7:          </Spinner:ContentSpinner>

 

The following code fragment illustrates the most important aspects of the control. The basic idea is that the control applies RotateTransform to its content and the transform is then animated using DoubleAnimationUsingKeyFrames. The number of frames is driven by the NumberOfFrames property and duration of the animation is calculated based on the number of required RevolutionsPerSecond.

   1:  private void StartAnimation()
   2:  {
   3:      if (_content == null)
   4:          return;
   5:   
   6:      var animation = GetAnimation();
   7:   
   8:      _content.LayoutTransform = GetContentLayoutTransform();
   9:      _content.RenderTransform = GetContentRenderTransform();
  10:   
  11:      _storyboard = new Storyboard();
  12:      _storyboard.Children.Add(animation);
  13:   
  14:      _storyboard.Begin(this);
  15:  }
  16:   
  17:  private void StopAnimation()
  18:  {
  19:      if (_storyboard != null)
  20:      {
  21:          _storyboard.Remove(this);
  22:          _storyboard = null;
  23:      }
  24:  }
  25:   
  26:  private void RestartAnimation()
  27:  {
  28:      StopAnimation();
  29:      StartAnimation();
  30:  }
  31:   
  32:  private Transform GetContentLayoutTransform()
  33:  {
  34:      return new ScaleTransform(ContentScale, ContentScale);
  35:  }
  36:   
  37:  private Transform GetContentRenderTransform()
  38:  {
  39:      var rotateTransform = new RotateTransform(0, _content.ActualWidth / 2 * ContentScale, _content.ActualHeight / 2 * ContentScale);
  40:      RegisterName(ANIMATION, rotateTransform);
  41:   
  42:      return rotateTransform;
  43:  }
  44:   
  45:  private DoubleAnimationUsingKeyFrames GetAnimation()
  46:  {
  47:      NameScope.SetNameScope(this, new NameScope());
  48:   
  49:      var animation = new DoubleAnimationUsingKeyFrames();
  50:   
  51:      for (int i = 0; i < NumberOfFrames; i++)
  52:      {
  53:          var angle = i * 360.0 / NumberOfFrames;
  54:          var time = KeyTime.FromPercent(((double)i) / NumberOfFrames);
  55:          DoubleKeyFrame frame = new DiscreteDoubleKeyFrame(angle, time);
  56:          animation.KeyFrames.Add(frame);
  57:      }
  58:   
  59:      animation.Duration = TimeSpan.FromSeconds(1 / RevolutionsPerSecond);
  60:      animation.RepeatBehavior = RepeatBehavior.Forever;
  61:   
  62:      Storyboard.SetTargetName(animation, ANIMATION);
  63:      Storyboard.SetTargetProperty(animation, new PropertyPath(RotateTransform.AngleProperty));
  64:   
  65:      return animation;
  66:  }

There are a couple of things which I need to mention though when it comes to the custom content: if you want it to be a part of resource dictionary, you may need to set the x:Shared property to False as otherwise it will be impossible to display two spinners simultaneously. Secondly the content should be symmetrical and its horizontal and vertical alignment needs to be set to Center as otherwise the spinner may get a bit wonky. Sample code and the control are available as part of the SharpFellows.Toolkit.

September 20 2010

Asynchronous WPF Commands

Programming applications for Windows has always been and event-driven affair: we’d place a control on a form, attach an event handler to it and whack some code behind to handle it. Job done. Unfortunately this approach is not very testable and as you’ve probably gathered by now, we’re very much interested in testability around here. There are couple of possible solutions to this problem and one of them (which works particularly well in WPF) is to replace event handlers with commands. This allows the view to be loosely coupled to the underlying “view model” and Darius Collins blogged about this approach using delegate WPF commands. There is also an excellent article by Josh Smith available on MSDN which discusses this pattern (Model-View-ViewModel) in detail.

To cut the long story short, the version of self-updateable DelegateCommand<T> (or RelayCommand<T> as Josh Smith calls it) is now part of the Sharpfellows.Toolkit but as it is being discussed extensively in the posts I have already mentioned I will not repeat myself here. Having said that, I feel I need to mention a minor deficiency of the UpdateableCommand<T> (as we call it): there are sadly some rare cases when the command does not work as expected when it comes to enabling/disabling the associated UI element. This is simply due to the RequerySuggested event is being raised in rather mysterious circumstances when the command manager “thinks” something may require refresh. This may not always work well with your app, so you may need to call CommandManager.InvalidaterequerySuggested() method to force it. Luckily the behaviour is repeatable and easy to spot so you do not have to worry about the app working erratically.

Asynchronous command

As it often happens, in order to keep the UI responsive, some of the commands need to execute the handler on a background thread. This is usually done using BackgroundWorker which provides convenient completion callback executing on the UI thread. There are however two major problems with this approach: first of all the threading code creates unnecessary and repeatable noise, cluttering the view model with “how” rather than letting it focus on the “what”, and secondly the multithreading introduces serious complexity into unit tests of the view model. To solve both of those issues I came up with UpdateableAsyncCommand<T> which, as the name implies, is capable of executing the command asynchronously. The clever bit about it is the fact that instead of directly using the threading API to execute the handler, the async command delegates the task of executing it to an object implementing IThreadingService interface (see below).

   1:  /// <summary>
   2:  /// Provides facilities for background tasks
   3:  /// </summary>
   4:  public interface IThreadingService
   5:  {
   6:      /// <summary>
   7:      /// Queues the execution of a task
   8:      /// </summary>
   9:      /// <param name="work"></param>
  10:      void QueueExecution(Action work);
  11:   
  12:      /// <summary>
  13:      /// Queues the execution of a task and calls back when it is complete
  14:      /// </summary>
  15:      /// <param name="work"></param>
  16:      /// <param name="completionCallback"></param>
  17:      void QueueExecution(Action work, Action completionCallback);
  18:   
  19:      /// <summary>
  20:      /// Queues the execution of a task with callback for successful completion and failure
  21:      /// </summary>
  22:      /// <param name="work"></param>
  23:      /// <param name="completionCallback"></param>
  24:      /// <param name="errorCallback"></param>
  25:      void QueueExecution(Action work, Action completionCallback, Action<Exception> errorCallback);
  26:  }

You may be wondering why on earth would we introduce an abstraction to be put on top of relatively simple threading API, but the reasons become clear when it comes to unit testing. UpdateableAsyncCommand<T> allows us to inject it with an implementation of the IThreadingService which executes the method synchronously. This fake implementation (provided for your convenience in the form of SynchronousThreadingService) allows the unit tests to run synchronously and shifts their focus to the business flow and interaction of components, rather than obscuring the picture with complex synchronisation code. The async command exposes IsBusy property which is set to true whenever the command handler is executing. As the UpdateableAsyncCommand<T> implements INotifyPropertyChanged you can even use the IsBusy property to drive the state of your UI. The following code fragment illustrates the most important aspects of the UpdateableAsyncCommand<T>

   1:  /// <summary>
   2:  /// Implements an asynchronous command
   3:  /// </summary>
   4:  /// <typeparam name="T"></typeparam>
   5:  public class UpdateableAsyncCommand<T> : NotifyPropertyChangedBase, IAsyncCommand
   6:  {
   7:      private readonly IThreadingService _threadingService;
   8:      private readonly Action<T> _executeMethod;
   9:      private readonly Func<T, bool> _canExecuteMethod;
  10:      private readonly Action _completionCallback;
  11:      private readonly Action<Exception> _errorCallback;
  12:      private bool _isBusy;
  13:   
  14:      /// <summary>
  15:      /// Initializes a new instance of the <see cref="UpdateableAsyncCommand&lt;T&gt;"/> class.
  16:      /// </summary>
  17:      /// <param name="threadingService">The threading service </param>
  18:      /// <param name="executeMethod">The execute method.</param>
  19:      /// <param name="canExecuteMethod">The method which determines if the command can be executed.</param>
  20:      /// <param name="completionCallback">The method to be executed when the command handler completes</param>
  21:      /// <param name="errorCallback">The error callback.</param>
  22:      public UpdateableAsyncCommand(IThreadingService threadingService, Action<T> executeMethod, Func<T, bool> canExecuteMethod, Action completionCallback, Action<Exception> errorCallback)
  23:      {
  24:          // Sanity checks omitted...
  25:          _threadingService = threadingService;
  26:          _executeMethod = executeMethod;
  27:          _canExecuteMethod = canExecuteMethod;
  28:          _completionCallback = completionCallback;
  29:          _errorCallback = errorCallback;
  30:      }
  31:   
  32:      /// <summary>
  33:      /// Defines the method that determines whether the command can execute in its current state.
  34:      /// </summary>
  35:      /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
  36:      /// <returns>
  37:      /// true if this command can be executed; otherwise, false.
  38:      /// </returns>
  39:      public bool CanExecute(object parameter)
  40:      {
  41:          return !IsBusy && _canExecuteMethod((T) parameter);
  42:      }
  43:   
  44:      /// <summary>
  45:      /// Occurs when changes occur that affect whether or not the command should execute.
  46:      /// </summary>
  47:      event EventHandler ICommand.CanExecuteChanged
  48:      {
  49:          add { CommandManager.RequerySuggested += value; }
  50:          remove { CommandManager.RequerySuggested -= value; }
  51:      }
  52:   
  53:      /// <summary>
  54:      /// Executes the command with given parameter.
  55:      /// </summary>
  56:      /// <param name="parameter">The parameter.</param>
  57:      public void Execute(object parameter)
  58:      {
  59:          if ( IsBusy )
  60:              throw new InvalidOperationException("Concurrent execution of the command is not supported.");
  61:   
  62:          IsBusy = true;
  63:   
  64:          _threadingService.QueueExecution(() => _executeMethod((T) parameter), CommandFinished, CommandError );
  65:      }
  66:   
  67:      /// <summary>
  68:      /// Gets or sets a value indicating whether this instance is busy.
  69:      /// </summary>
  70:      /// <value><c>true</c> if this instance is busy; otherwise, <c>false</c>.</value>
  71:      public bool IsBusy
  72:      {
  73:          get { return _isBusy; }
  74:          private set
  75:          {
  76:              if (_isBusy == value)
  77:                  return;
  78:   
  79:              _isBusy = value;
  80:              // As the IsBusy may be set from the background thread, we ned to "poke" the 
  81:              // command manager to let it know it may be a good idea to check the command's CanExecute
  82:              CommandManager.InvalidateRequerySuggested();
  83:              OnPropertyChanged(() => IsBusy);
  84:          }
  85:      }
  86:   
  87:      private void CommandFinished()
  88:      {
  89:          _completionCallback();
  90:          IsBusy = false;
  91:      }
  92:   
  93:      private void CommandError(Exception ex)
  94:      {
  95:          _errorCallback(ex);
  96:          IsBusy = false;
  97:      }
  98:  }

 

More on the IThreadingService

The contract for the threading service is quite similar to that of IScheduler (found in reactive extensions) and for a while I was thinking if we should not use it instead. It became clear however that more often than not we need to call back to UI thread when the command handler is finished. This in turn forces use of SynchronisationContext, which is not available when running unit tests on the background thread (as some test runners do). As already mentioned we use SynchronousThreadingService in unit tests which alleviates the need to use SynchronizationContext altogether.

The concept of the IThreadingService has one more benefit: as the service is usually injected by an IoC container, it is very easy to switch from asynchronous to synchronous execution mode simply by replacing the implementation. This may be very helpful when debugging an application when we suspect the issue may be caused a misbehaving thread.

PS: To pay credit where it is due, I feel I need to mention that the threading service code included in the toolkit has been authored by John Rayner. The original idea however came from Dave Hanson who since then patented it and made enough money in royalties to leave his job and embark on a year long journey across Australia :)

Putting it all together

To illustrate the use of asynchronous commands I developed a simple (and rather silly) Web search application called Bingo. The app uses Microsoft’s Bing engine API and the search, as you would expect, is being executed using async command. For what it is, the sample may seem seriously over-engineered but I wanted to illustrate how the async command fits into the big picture of MVVM application.

image

As you can see from the screenshot above, in spite of all the years spent mastering Microsoft technologies, our website still does not deserve #1 spot in the Bing search results for “sharpfellows”… How disappointing ;) 

PS: The sample is obviously available as part of the toolkit.

September 1 2010

WPF “Drag and Drop” – doing it the MVVM way

I have been recently involved in a WPF/Prism project where one of the requirements was to allow the user to move rather large sums of money with the mouse. Think of moving the money from one account to another but with potential of serious loss if you drop it in between. When I trawled the net for examples of drag & drop in WPF, I noticed that the vast majority of them deal with the visual aspects of the operation, mixing business logic with the code behind which in turn results in code which is pretty much un-testable (in a unit testing sense). If  you take a closer look at any of the drag and drop code, it becomes obvious that the operation has a number of distinct aspects to it, some of which can be delegated to the “business logic component” offering potentially better testability:

  1. Drag and drop is initiated when the user presses the mouse and moves it by a certain distance. This requires event handling somewhere in the code behind. This code has to be potentially repeated over and over again for different controls.
  2. At the time the drag and drop is initiated, we hand over the mouse handling to the OS, but we still need to provide the data and we need to indicate what can be done with the object (Move, Copy etc). This would be best handled by the view model.
  3. While the mouse is being dragged we may need to provide some visual feedback as to what is going to happen when the object will get dropped. This again would be best handled by the view model but needs to be initiated from an event handler.
  4. Once the object is dropped, we just need to consume it but the operation requires Drop event handler in the code behind.

After a bit of head scratching and various discussions with John, I managed to come up wit ha solution which allows the drag and drop logic to be both reusable and testable. The main actors are as follows:

  1. DragSourceBehaviour  implements the event handling required to initiate the drag operation
  2. An object implementing IDragSource interface provides the data to be dragged
  3. DropTargetBehaviour implements drop related event handlers
  4. Object implementing IDropTarget handles the business logic of the “drop”
  5. Helper classes provide shortcuts for implementing both IDropTarget and IDragSource

The rest of this post discusses details of the implementation.

Handling the “Drag”

The start the drag & drop we need two event handlers: one to handle PreviewMouseButtonDown event and record the position and another one to handle PreviewMouseMove to see if the mouse have moved far enough to initiate drag & drop. Implementing those handlers over and over again in the code behind is not my idea of fun, so obviously another solution is required and a WPF behaviour fits the bill nicely. The drag and drop operation also needs a piece of data that will be dragged and we need to know what sort of drag operation will be supported: as indicated earlier this would be best handled by the view model.

To glue the event handling aspects and the data handling aspect together, I came up with the attached property of IDragSource type exposed by the DragSourceBehaviour. Once the property is set to a non null value, the behaviour will take care of the event handling while still delegating the task of providing data to implementation of IDragSource. I hope the following fragment from the DragSourceBehaviour class explains it all:

   1:  private static void PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
   2:  {
   3:      _startPoint = e.GetPosition(null);
   4:  }
   5:   
   6:  private static void MouseLeave(object sender, MouseEventArgs e)
   7:  {
   8:      // Need to reset since the mouse left in order to prevent mouse movement 
   9:      // in another element to pick drag an drop
  10:      _startPoint = null;
  11:  }
  12:   
  13:  private static void PreviewMouseMove(object sender, MouseEventArgs e)
  14:  {
  15:      if (e.LeftButton != MouseButtonState.Pressed || _startPoint == null)
  16:          return;
  17:   
  18:      if(!HasMouseMovedFarEnough(e))
  19:          return;
  20:   
  21:      var dependencyObject = (FrameworkElement) sender;
  22:      var dataContext = dependencyObject.GetValue(FrameworkElement.DataContextProperty);
  23:      var dragSource = GetDragSource(dependencyObject);
  24:   
  25:      if (dragSource.GetDragEffects(dataContext) == DragDropEffects.None)
  26:          return;
  27:   
  28:      DragDrop.DoDragDrop(dependencyObject,
  29:                          dragSource.GetData(dataContext),
  30:                          dragSource.GetDragEffects(dataContext));
  31:              
  32:  }
  33:   

 

The object implementing IDragSource interface does not have to be in any sense “related” to the visual initiating the operation, but whoever implements the IDragSource would be often interested in what is the data context of the object and for this reason IDragSource takes object “dataContext” parameter to both of it’s methods

   1:      /// <summary>
   2:      /// Business end of the drag source
   3:      /// </summary>
   4:      public interface IDragSource
   5:      {
   6:          /// <summary>
   7:          /// Gets the supported drop effects.
   8:          /// </summary>
   9:          /// <param name="dataContext">The data context.</param>
  10:          /// <returns></returns>
  11:          DragDropEffects GetDragEffects(object dataContext);
  12:   
  13:          /// <summary>
  14:          /// Gets the data.
  15:          /// </summary>
  16:          /// <param name="dataContext">The data context.</param>
  17:          /// <returns></returns>
  18:          object GetData(object dataContext);
  19:      }

Implementing IDragSource interface over and over again may become tedious very quickly so I came up wit ha shortcut for implementing the interface which simply takes two delegates to be executes as and when required:

   1:  /// <summary>
   2:  /// Gets the (drag) source of cookies.
   3:  /// </summary>
   4:  /// <value>The source of cookies.</value>
   5:  public IDragSource SourceOfCookies
   6:  {
   7:      get
   8:      {
   9:          if (_source == null)
  10:              _source = new DragSource<CookieJar>(GetDragEffects, GetData);
  11:   
  12:          return _source;
  13:      }
 

Handling the “Drop”

The drop operation is being handled in a similar fashion. This time however the attached property is of type IDropTarget and here’s how it is defined.

   1:  public interface IDropTarget
   2:  {
   3:      /// <summary>
   4:      /// Gets the effects.
   5:      /// </summary>
   6:      /// <param name="dataObject">The data object.</param>
   7:      /// <returns></returns>
   8:      DragDropEffects GetDropEffects(IDataObject dataObject);
   9:   
  10:      /// <summary>
  11:      /// Drops the specified data object
  12:      /// </summary>
  13:      /// <param name="dataObject">The data object.</param>
  14:      void Drop(IDataObject dataObject);
  15:  }

Similarly the code in the DropTargetBehaviour class delegates the task of handling the data to the object implementing IDropTarget:

   1:  private static void Drop(object sender, DragEventArgs e)
   2:  {
   3:      var dropTarget = GetDropTarget((DependencyObject)sender);
   4:   
   5:      dropTarget.Drop(e.Data);
   6:      e.Handled = true;
   7:  }
   8:   
   9:  private static void DragOver(object sender, DragEventArgs e)
  10:  {
  11:      var dropTarget = GetDropTarget((DependencyObject)sender);
  12:   
  13:      e.Effects = dropTarget.GetDropEffects(e.Data);
  14:      e.Handled = true;            
  15:  }

Putting it all together

To test the entire machinery I have developed a sample application which allows you to drag cookies between cookie jars. But there are constraints to it: you cannot drag anything out of an empty jar, and each jar will accept no mo re than ten cookies. These are the business rules which are enforced by corresponding unit tests.

image

The following fragment of XAML illustrates how the control is glued together with the DragSource and DropTarget behaviours:

<DragDrop:CookieJarControl Behaviours:DragSourceBehaviour.DragSource="{Binding SourceOfCookies}" 
                            Behaviours:DropTargetBehaviour.DropTarget="{Binding CookieSink}"
                            AllowDrop="True"/>

The source code for the entire project is available as part of the SharpFellows.Toolkit. As we develop more and more reusable goodies I am sure they will make it’s way into the library. Feel free to use it in any which way you want (this includes copy-pasting of suitable fragments) but please let us know if  you find it useful!

August 20 2010
Newer Posts Older Posts