WPF Ribbon: RibbonCommands Can Cause Memory Leaks

April 15th, 2009

I stumbled over an issue when dealing with Microsoft’s WPF ribbon today. Apparently, the control that heavily relies on RibbonCommands rather than arbitrary ICommand instances may cause severe memory leaks. Have a look at this simple Window:

 

<Window ...>
  <Grid>
    <Grid.Resources>
      <r:RibbonCommand
        x:Key="MyCommand"
        Executed="OnRibbonClicked"
        LabelTitle="Click Me" />
    </Grid.Resources>

    <!-- a ribbon that only displays a command in the quick access toolbar -->
    <r:Ribbon>
      <r:Ribbon.QuickAccessToolBar>
        <r:RibbonQuickAccessToolBar>
          <r:RibbonButton
            Command="{StaticResource MyCommand}"
            r:RibbonQuickAccessToolBar.Placement="InToolBar" />
        </r:RibbonQuickAccessToolBar>
      </r:Ribbon.QuickAccessToolBar>
    </r:Ribbon>
  </Grid>
</Window>

 

…and here’s the event listener that was declared for the RibbonCommand:

private void OnRibbonClicked(object sender, ExecutedRoutedEventArgs e)
{
  MessageBox.Show("Ribbon command executing.");
}

 

Once opened and closed, this window will not be garbage collected until the application shuts down. The reason is the OnRibbonClicked event handler that doesn’t get deregistered.

There are signs that Microsoft will dump RibbonCommand completely once the ribbon goes live. However: This probably won’t happen too fast, so you will have to solve the issue on your own. On the bright side: It forces a cleaner design on you.

I prefer custom commands anyway, so the natural way to go was to simply implement my own class that derives from RibbonCommand:

 

///<summary>
/// Base class for custom <see cref="RibbonCommand"/>
/// implementations.
///</summary>
public abstract class RibbonCommandBase : RibbonCommand
{
  /// <summary>
  /// Creates the command and registers weak event listeners.
  /// </summary>
  protected RibbonCommandBase()
  {
    Executed += OnExecute;
    CanExecute += OnCanExecute;
  }


  /// <summary>
  /// Determines whether the command can be executed or not.
  /// The default implementation always allows that.
  /// </summary>
  protected virtual void OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
  {
    e.CanExecute = true;
  }


  /// <summary>
  /// Command implementation - executes command logic.
  /// </summary>
  protected abstract void OnExecute(object sender, ExecutedRoutedEventArgs e);
}

 

The noteworthy thing here is that this abstract base class registers event listeners for its own events – one virtual, one abstract. Accordingly, a deriving command implementation only has to provide its own OnExecute and OnCanExecute (optional) methods to get going:

 

public class MessageCommand : RibbonCommandBase
{
  protected override void OnExecute(object sender, ExecutedRoutedEventArgs e)
  {
    MessageBox.Show("Ribbon command executing.");
  }
}

 

The last thing to correct is the command declaration in XAML – rather than declaring a RibbonCommand with a code-behind event listener, you declare your custom command (MessageCommand in the sample below). As you can see, there is no longer a listener for the Executed event – the command takes care of this on its own. Accordingly, this command can be declared in a resource dictionary without a code-behind file.

<!-- Custom command - can be declared in a resource dictionary -->
<cmd:MessageCommand x:Key="MyCommand"
                    LabelTitle="Click Me"
                    SmallImageSource="About.png"
/>

 

Sample Project

The attached sample project shows the two variants and the missing garbage collection of the standard ribbon commands.

I wasn’t sure about licensing restrictions, therefore I did not include the WPF ribbon assembly in the downloadable sample – you’ll have to add the reference yourself before compiling. Sorry for the inconvenience :/

Download: ribbon-commands.zip

 

 image

 

kick it on DotNetKicks.com

Author: Categories: WPF, WPF Controls Tags: ,

WPF Behavior Samples

April 13th, 2009

Behaviors in WPF and Silverlight are one of the features that were introduced with Blend 3, and unlike attached behaviors, they benefit from full designer support in Blend 3, thus providing a much higher level of accessibility to developers and designers alike.

Laurent Bugnion has posted a great tutorial where he walks you through the various aspects of a magnifying glass behavior, and Jeremiah Morrill generates eye candy through a behavior that adds glass effects to your visuals.

Last but not least, the Expression team created a new section on the Expression Gallery that is dedicated to behaviors. I sure like shiny new toys 😉

Author: Categories: WPF Tags:

String Encryption using DPAPI and Extension Methods

April 12th, 2009

Latest Version: 2009.04.14 (Bugfix in Matches extension method)

 

The Windows Data Protection API (DPAPI) is a great technology to securely encrypt user or machine specific data without having to worry about an encryption key. Since .NET 2.0, DPAPI is part of the .NET framework, so encrypting data is as easy as this:

 

public static byte[] Encrypt(byte[] data)
{
  var scope = DataProtectionScope.CurrentUser;
  return ProtectedData.Protect(data, null, scope);
}

 

As you can see, the Protect method of the ProtectedData class takes binary input and returns a byte array that contains the encrypted data. This means that you’ll have to do some conversions when dealing with strings, and the result of the encryption is a byte array anyway.

NetDrives relies on the DPAPI to encrypt user passwords that are stored on disk. Accordingly, I didn’t want to deal with binary data at all: Both input and output were supposed to be strings, which why I came up with a few extension methods that nicely wrap string encryptions for me:

image

 

Basic String Encryption

In case in-memory protection is not an issue and you just need to encrypt/decrypt strings (e.g. to store encrypted data in a configuration file), you just need two extension methods. First, in order to encrypt a string, just invoke the Encrypt extension method:

string password = "hello world";
string encrypted = password.Encrypt();

 

Encrypt returns you the encrypted data, represented as base64 encoded string. In order to get your password back, just invoke the Decrypt extension method:

string plainText = encrypted.Decrypt();

 

Managed Strings vs. SecureString

The above methods are convenient to encrypt sensitive data that is supposed to be serialized or transmitted in any way. They do, however, not protect data at runtime as the decrypted strings remain in memory. In case this is an issue, you should revert to the SecureString rather than using plain strings (but keep in mind that this may lure you into a false sense of security!).

Accordingly, I also created extension methods that use SecureString instances rather than managed strings and allow you to wrap / unwrap strings quite easily. Here’s a test that shows off the various conversions:

Attention: Always keep in mind that once you are dealing with a managed string (such as the plainText variable below), your code can be compromised! Accordingly, the ToSecureString / Unwrap methods should be treated carefully.

 

[Test]
public void Encryption_And_Decryption_Cycle_Should_Return_Original_Value()
{
  string plainText = "this is a password";

  //encrypt plain text
  string cipher = plainText.Encrypt();
  Assert.AreNotEqual(plainText, cipher);

  //decrypt cipher into managed string
  string decrypted = cipher.Decrypt();
  Assert.AreEqual(plainText, decrypted);

  //create a SecureString from the plain text
  SecureString plainSecure = plainText.ToSecureString();

  //test unwrapping of a SecureString
  Assert.AreEqual(plainText, plainSecure.Unwrap());

  //encrypt the string that is wrapped into the SecureString
  string cipherFromSecure = plainSecure.Encrypt();
  
  //decrypt the cipher that was created from the the SecureString
  Assert.AreEqual(plainText, cipherFromSecure.Decrypt());
}

 

Implementation

Here’s the class that provides the extension methods including a few helper methods that facilitate dealing with SecureString (e.g. SecureString.IsNullOrEmpty).

Note that you need to set an assembly reference to the System.Security assembly. Also keep in mind that the class always performs DPAPI encryption with user scope. You might want to provide some additional overloads in order to support encryption that uses the context of the machine rather than the user’s. The same goes for the optional entropy that is not used at all for simplicity.

kick it on DotNetKicks.com

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Text;

namespace Hardcodet.NetDrives.Platform
{
  /// <summary>
  /// Provides extension methods that deal with
  /// string encryption/decryption and
  /// <see cref="SecureString"/> encapsulation.
  /// </summary>
  public static class SecurityExtensions
  {
    /// <summary>
    /// Specifies the data protection scope of the DPAPI.
    /// </summary>
    private const DataProtectionScope Scope = DataProtectionScope.CurrentUser;


    /// <summary>
    /// Encrypts a given password and returns the encrypted data
    /// as a base64 string.
    /// </summary>
    /// <param name="plainText">An unencrypted string that needs
    /// to be secured.</param>
    /// <returns>A base64 encoded string that represents the encrypted
    /// binary data.
    /// </returns>
    /// <remarks>This solution is not really secure as we are
    /// keeping strings in memory. If runtime protection is essential,
    /// <see cref="SecureString"/> should be used.</remarks>
    /// <exception cref="ArgumentNullException">If <paramref name="plainText"/>
    /// is a null reference.</exception>
    public static string Encrypt(this string plainText)
    {
      if (plainText == null) throw new ArgumentNullException("plainText");

      //encrypt data
      var data = Encoding.Unicode.GetBytes(plainText);
      byte[] encrypted = ProtectedData.Protect(data, null, Scope);

      //return as base64 string
      return Convert.ToBase64String(encrypted);
    }


    /// <summary>
    /// Decrypts a given string.
    /// </summary>
    /// <param name="cipher">A base64 encoded string that was created
    /// through the <see cref="Encrypt(string)"/> or
    /// <see cref="Encrypt(SecureString)"/> extension methods.</param>
    /// <returns>The decrypted string.</returns>
    /// <remarks>Keep in mind that the decrypted string remains in memory
    /// and makes your application vulnerable per se. If runtime protection
    /// is essential, <see cref="SecureString"/> should be used.</remarks>
    /// <exception cref="ArgumentNullException">If <paramref name="cipher"/>
    /// is a null reference.</exception>
    public static string Decrypt(this string cipher)
    {
      if (cipher == null) throw new ArgumentNullException("cipher");

      //parse base64 string
      byte[] data = Convert.FromBase64String(cipher);

      //decrypt data
      byte[] decrypted = ProtectedData.Unprotect(data, null, Scope);
      return Encoding.Unicode.GetString(decrypted);
    }


    /// <summary>
    /// Encrypts the contents of a secure string.
    /// </summary>
    /// <param name="value">An unencrypted string that needs
    /// to be secured.</param>
    /// <returns>A base64 encoded string that represents the encrypted
    /// binary data.
    /// </returns>
    /// <exception cref="ArgumentNullException">If <paramref name="value"/>
    /// is a null reference.</exception>
    public static string Encrypt(this SecureString value)
    {
      if (value == null) throw new ArgumentNullException("value");

      IntPtr ptr = Marshal.SecureStringToCoTaskMemUnicode(value);
      try
      {
        char[] buffer = new char[value.Length];
        Marshal.Copy(ptr, buffer, 0, value.Length);

        byte[] data = Encoding.Unicode.GetBytes(buffer);
        byte[] encrypted = ProtectedData.Protect(data, null, Scope);

        //return as base64 string
        return Convert.ToBase64String(encrypted);
      }
      finally
      {
        Marshal.ZeroFreeCoTaskMemUnicode(ptr);
      }
    }


    /// <summary>
    /// Decrypts a base64 encrypted string and returns the decrpyted data
    /// wrapped into a <see cref="SecureString"/> instance.
    /// </summary>
    /// <param name="cipher">A base64 encoded string that was created
    /// through the <see cref="Encrypt(string)"/> or
    /// <see cref="Encrypt(SecureString)"/> extension methods.</param>
    /// <returns>The decrypted string, wrapped into a
    /// <see cref="SecureString"/> instance.</returns>
    /// <exception cref="ArgumentNullException">If <paramref name="cipher"/>
    /// is a null reference.</exception>
    public static SecureString DecryptSecure(this string cipher)
    {
      if (cipher == null) throw new ArgumentNullException("cipher");

      //parse base64 string
      byte[] data = Convert.FromBase64String(cipher);

      //decrypt data
      byte[] decrypted = ProtectedData.Unprotect(data, null, Scope);

      SecureString ss = new SecureString();

      //parse characters one by one - doesn't change the fact that
      //we have them in memory however...
      int count = Encoding.Unicode.GetCharCount(decrypted);
      int bc = decrypted.Length/count;
      for (int i = 0; i < count; i++)
      {
        ss.AppendChar(Encoding.Unicode.GetChars(decrypted, i*bc, bc)[0]);
      }

      //mark as read-only
      ss.MakeReadOnly();
      return ss;
    }


    /// <summary>
    /// Wraps a managed string into a <see cref="SecureString"/> 
    /// instance.
    /// </summary>
    /// <param name="value">A string or char sequence that 
    /// should be encapsulated.</param>
    /// <returns>A <see cref="SecureString"/> that encapsulates the
    /// submitted value.</returns>
    /// <exception cref="ArgumentNullException">If <paramref name="value"/>
    /// is a null reference.</exception>
    public static SecureString ToSecureString(this IEnumerable<char> value)
    {
      if (value == null) throw new ArgumentNullException("value");

      var secured = new SecureString();

      var charArray = value.ToArray();
      for (int i = 0; i < charArray.Length; i++)
      {
        secured.AppendChar(charArray[i]);
      }

      secured.MakeReadOnly();
      return secured;
    }


    /// <summary>
    /// Unwraps the contents of a secured string and
    /// returns the contained value.
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    /// <remarks>Be aware that the unwrapped managed string can be
    /// extracted from memory.</remarks>
    /// <exception cref="ArgumentNullException">If <paramref name="value"/>
    /// is a null reference.</exception>
    public static string Unwrap(this SecureString value)
    {
      if (value == null) throw new ArgumentNullException("value");

      IntPtr ptr = Marshal.SecureStringToCoTaskMemUnicode(value);
      try
      {
        return Marshal.PtrToStringUni(ptr);
      }
      finally
      {
        Marshal.ZeroFreeCoTaskMemUnicode(ptr);
      }
    }


    /// <summary>
    /// Checks whether a <see cref="SecureString"/> is either
    /// null or has a <see cref="SecureString.Length"/> of 0.
    /// </summary>
    /// <param name="value">The secure string to be inspected.</param>
    /// <returns>True if the string is either null or empty.</returns>
    public static bool IsNullOrEmpty(this SecureString value)
    {
      return value == null || value.Length == 0;
    }



    /// <summary>
    /// Performs bytewise comparison of two secure strings.
    /// </summary>
    /// <param name="value"></param>
    /// <param name="other"></param>
    /// <returns>True if the strings are equal.</returns>
    public static bool Matches(this SecureString value, SecureString other)
    {
      if (value == null && other == null) return true;
      if (value == null || other == null) return false;
      if (value.Length != other.Length) return false;
      if (value.Length == 0 && other.Length == 0) return true;

      IntPtr ptrA = Marshal.SecureStringToCoTaskMemUnicode(value);
      IntPtr ptrB = Marshal.SecureStringToCoTaskMemUnicode(other);
      try
      {
        //parse characters one by one - doesn't change the fact that
        //we have them in memory however...
        byte byteA = 1;
        byte byteB = 1;

        int index = 0;
        while (((char)byteA) != '' && ((char)byteB) != '')
        {
          byteA = Marshal.ReadByte(ptrA, index);
          byteB = Marshal.ReadByte(ptrB, index);
          if (byteA != byteB) return false;
          index += 2;
        }

        return true;
      }
      finally
      {
        Marshal.ZeroFreeCoTaskMemUnicode(ptrA);
        Marshal.ZeroFreeCoTaskMemUnicode(ptrB);
      }
    }
  }
}

 

 

Author: Categories: C#, Security Tags: ,

From Lambda Dependencies to Lambda Bindings

April 3rd, 2009

Lambda-based binding for the business layer or your View Model 🙂

I’ve had this on the shelf for quite a while, but Kent Boogaart’s article on POCOs vs. DependencyObjects finally got me to cleaning things up a little. Kent is coming up with a similar approach that looks very promising, so you should keep your eyes on his blog, too.

Lambda Bindings are built on top of the Lambda Dependencies project I published a while ago. The original Lambda Dependencies allow you to observe object graphs for changes using simple LINQ expressions. Lambda Bindings leverage this pattern by not just publishing a change event but synchronizing target properties or fields automatically.

This provides you with a generic binding framework that can be used wherever you want to synchronize objects. Let’s have a first example:

 

public void TestBinding(Student student)
{
  string cityName = "";

  //synchronize the cityName field with the City property of the school's address
  var binding = LambdaBinding.BindOneWay(
      () => student.School.Address.City,
      () => cityName);

  //change the property on the School object
  student.School.Address.City = "Sin City";

  //the binding expression updated the local variable
  Assert.AreEqual("Sin City", cityName);
}

What’s happening in the snippet above is that I created a binding between a nested property of a referenced object and a local field. As soon as the binding source (the City property of a school’s address) is changed, the local cityName field is being updated as well.

 

However, the Lambda Dependencies not only cover the source properties but the whole object graph. Accordingly, exchanging the whole School (or the Student instance) also triggers an update. In the snippet below, the cityName variable is being updated twice:

 

[Test]
public void Updating_Intermediary_Object_Should_Update_Target(Student student)
{
  string cityName = "";

  //synchronize the cityName field with the City property of the school's address
  var binding = LambdaBinding.BindOneWay(
      () => student.School.Address.City,
      () => cityName);


  //change bound City property -> triggers update of the local variable
  student.School.Address.City = "Paris";
  Assert.AreEqual("Paris", cityName);

  //create a completely different school instance
  School englishSchool = new School();
  englishSchool.Address = new Address {City = "London"};

  //assign the new school to the student
  student.School = englishSchool;

  //setting the School property also triggered the binding
  Assert.AreEqual("London", cityName);
}

 

Value Conversion

You can do simple value conversion by just submitting a converter to the binding expression. This allows you to intercept the binding pipeline or bind objects of different types together. If you’re coming from WPF, this feels natural anyway, but the solution here does not require you to implement a value converter – a simple Func<TSource, TTarget> is sufficient.

Here’s a simple sample that performs a conversion of a boolean flag to into a corresponding Visibility enum value:

 

[Test]
public void Boolean_Should_Be_Converted_To_Visibility()
{
  //create a hidden window
  Window window = new Window { Visibility = Visibiliy.Collapsed };
  
  //create a view model
  MyViewModel viewModel = new MyViewModel { IsVisible = false };
  
  //create binding that casts the Visibility into a boolean
  LambdaBinding.BindOneWay(
      () => viewModel.IsVisible,
      () => window.Visibility,
      b => b == true ? Visibility.Visible : Visibility.Collapsed;
  
  //a change in the ViewModel shows/hides the window
  viewModel.IsVisible = true;
  
  Assert.AreEqual(Visibility.Visible, window.Visibility);  
}

 

Two-Way-Binding

Two way binding works too, of course:

[Test]
public void Updates_Should_Work_Both_Ways()
{
  //create two-way binding
  var binding = LambdaBinding.BindTwoWay(
    () => FirstStudent.Name,
    () => SecondStudent.Name);

  //change property on source
  FirstStudent.Name = "Peter";
  Assert.AreEqual("Peter", SecondStudent.Name);

  //change property on target
  SecondStudent.Name = "Parker";
  Assert.AreEqual("Parker", FirstStudent.Name);
}

In case you need to perform type conversion, you need to supply two converter functions for forward / reverse conversion:

//bind a boolean property to a control's Visibility property
var binding = LambdaBinding.BindTwoWay(
    () => ModelItem.IsEnabled,
    () => MyControl.IsVisible,
    b => b == true ? Visibility.Visible : Visibiliy.Collapsed
    v => v == Visibility.Visible ? true : false);

 

Default Values

In case the object graph is being broken (e.g. because the School was set to null), the target node will be automatically set to its default value (null for an object, 0 for an int etc.). However, you can also specify a default value of your own:

//a local field to be updated
private string schoolCity;

[Test]
public void Breaking_The_Chain_Should_Assign_Default_Value_To_Target_If_Specified()
{
  var binding = LambdaBinding.BindOneWay(
      () => Student.School.Address.City,
      () => schoolCity,
      "[No City]");

  //break the source chain
  Student.School = null;

  //the default value was assigned to the target
  Assert.AreEqual("[No City]", schoolCity);
}

(btw: the above snippet also shows you that you can easily bind to a field rather than a property).

 

Weak References

The underlying Lambda Dependencies only use weak references so you’re not at risk of creating memory leaks. However, LambdaBinding implements IDisposable, so the proper way to clean things up would be to dispose your binding.

Things to Consider

Remember that that the underlying Lambda Dependencies rely on the INotifyPropertyChanged interface, so don’t expect source binding to fields (or properties that do not fire a PropertyChanged event) to magically update your targets.

 

Download: lambda-dependencies.zip

 

kick it on DotNetKicks.com

WPF NotifyIcon – Release Candidate

April 1st, 2009

This is an implementation of a NotifyIcon (aka system tray icon or taskbar icon) for the WPF platform.

image

Update:

The control has now its own project page. Please go to

http://www.hardcodet.net/projects/wpf-notifyicon

Moving WPF DataGrid Rows using Drag and Drop

March 24th, 2009

For my upcoming NetDrives tool (will be released shortly) I wanted to enable the user to reorder managed network shares using drag and drop using a preview of the dragged row:

image

 

As it turned out, it’s not too hard to implement, but it took my a while to find all pieces of the puzzle, so I compiled a short sample. You can find the sample link at the end of the article.

 

Drag Indicator Popup

I used a popup as an drag indicator, which I bound to the item that was currently dragged (DraggedItem dependency property):

<!-- the popup that is displayed if user moves rows -->
<Popup
  x:Name="popup1"
  IsHitTestVisible="False"
  Placement="RelativePoint"
  PlacementTarget="{Binding ElementName=me}"
  AllowsTransparency="True">
  <Border
    BorderBrush="{DynamicResource CellBorderBrush}"
    BorderThickness="2"
    Background="White"
    Opacity="0.75">
    <StackPanel
      Orientation="Horizontal"
      Margin="4,3,8,3">
      <Image
        Source="/Shared/Images/DragInsert.png"
        Width="16"
        Height="16" />
      <TextBlock
        Style="{DynamicResource DefaultLabel}"
        FontWeight="Bold"
        VerticalAlignment="Center"
        Text="{Binding ElementName=me, Path=DraggedItem.Name}"
        Margin="8,0,0,0" />
    </StackPanel>
  </Border>
</Popup>

 

Disabling Drag and Drop in Edit Mode

I didn’t want to enable drag and drop if the grid was in edit mode. Accordingly, I registered two event listeners on the grid:

<dg:DataGrid
  BeginningEdit="OnBeginEdit"
  CellEditEnding="OnEndEdit"
  .. />

 

The corresponding event listeners just set the IsEditing flag, which is evaluated when handling mouse events:

/// <summary>
/// State flag which indicates whether the grid is in edit
/// mode or not.
/// </summary>
public bool IsEditing { get; set; }

private void OnBeginEdit(object sender, DataGridBeginningEditEventArgs e)
{
  IsEditing = true;
  //in case we are in the middle of a drag/drop operation, cancel it...
  if (IsDragging) ResetDragDrop();
}

private void OnEndEdit(object sender, DataGridCellEditEndingEventArgs e)
{
  IsEditing = false;
}

 

Listening to Mouse Events

In order to display and move the popup with the mouse, I registered listeners for the following three mouse events:

  • PreviewMouseLeftButtonDown (on the datagrid)
  • MouseLeftButtonUp (directly on the layout root)
  • MouseMove (directly on the layout root)

 

Note: I started with listeners on the grid only, which caused some side effects. Apparently, the datagrid (current March release) not always fires the mouse events properly. This caused choppy animations when hovering over certain cells. Fortunately, this is not an issue with the MouseMove event of the layout root.

 

Starting Drag and Drop

DnD is started as soon as the user presses the left mouse button on the datagrid. I had to use the PreviewLeftMouseButton event in order to get the notification, and I needed to determine the clicked row based on the mouse position. I blogged about finding an element under the mouse a while ago here, but the UIHelpers class is part of the sample project here.

My mouse button event listener basically does the following:

  • Check if the mouse is being placed over a grid row.
  • Set the IsDragging flag to true.
  • Store the dragged item in the DraggedItem dependency property (used by the popup to display the name).

 

private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
  //exit if in edit mode
  if (IsEditing) return;

  //find the clicked row
  var row = UIHelpers.TryFindFromPoint<DataGridRow>((UIElement) sender,
                                                    e.GetPosition(shareGrid));
  if (row == null) return;

  //set flag that indicates we're capturing mouse movements
  IsDragging = true;
  DraggedItem = (IShareConfiguration) row.Item;
}

 

Moving the Popup

I registered a listener for the MouseMove event directly on the layout root (not on the datagrid). Basically, the event listener just moves the popup to the current mouse location along with a few minor tasks:

  • If the popup has not been opened yet, display it.
  • Set the grid to read-only.
  • Reposition the popup by setting the PlacementRectangle property.
  • Make sure the grid row under the mouse is being selected. Once again, this didn’t work reliably if I relied on the datagrid to do it by itself.

 

/// <summary>
/// Completes a drag/drop operation.
/// </summary>
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
  if (!IsDragging || IsEditing)
  {
    return;
  }

  //get the target item
  ShareConfiguration targetItem = (ShareConfiguration) shareGrid.SelectedItem;

  if (targetItem == null || !ReferenceEquals(DraggedItem, targetItem))
  {
    //remove the source from the list
    ShareList.Remove(DraggedItem);

    //get target index
    var targetIndex = ShareList.IndexOf(targetItem);

    //move source at the target's location
    ShareList.Insert(targetIndex, DraggedItem);

    //select the dropped item
    shareGrid.SelectedItem = DraggedItem;
  }

  //reset
  ResetDragDrop();
}

 

Finishing Drag and Drop

Once the user releases the mouse button, I need to perform the actual drop operation. I already had the dragged item (DraggedItem property, was set when the operation started) so all I needed was the drop target. My target is the currently selected row.

/// <summary>
/// Completes a drag/drop operation.
/// </summary>
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
  if (!IsDragging || IsEditing || shareGrid.SelectedItem == null)
  {
    return;
  }
  
  //get the target item
  IShareConfiguration targetItem = (IShareConfiguration)shareGrid.SelectedItem;

  if (!ReferenceEquals(DraggedItem, targetItem))
  {
    //the actual business logic that works on source and target
  }

  //reset
  ResetDragDrop();
}

 

You might also want to check whether the mouse is currently over the grid (or a grid row) or not – in that case just use the TryFindFromPoint method from the UIHelpers class.

 

Cleaning Up

The ResetDragDrop method just performs a cleanup of the code by closing the popup and adjusting a few properties:

/// <summary>
/// Closes the popup and resets the
/// grid to read-enabled mode.
/// </summary>
private void ResetDragDrop()
{
  IsDragging = false;
  popup1.IsOpen = false;
  shareGrid.IsReadOnly = false;      
}

 

Download Sample: http://blog.hardcodet.net/wp-content/uploads/2009/03/datagrid_dragdrop.zip

Author: Categories: DataGrid Tags: ,