Archive

Archive for March, 2008

Property declaration snippets for ReSharper

March 21st, 2008

Automatic properties in .NET 3.5 are a nice thing, but they are out of the equation if you want to take advantage of the almighty INotifyPropertyChanged interface which plays a crucial role in WPF. Here’s a set of ReSharper snippets that simplify interface implementation and property declaration.

Event Declaration

The first snippet just prints out an interface implementation and adds some additional value to it, including a runtime check of property names in Debug builds (credits go to Josh Smith for this one). Just type inpc to print out this statement:

#region INotifyPropertyChanged event

///<summary>
///Occurs when a property value changes.
///</summary>
public event PropertyChangedEventHandler PropertyChanged;


/// <summary>
/// Raises the <see cref="PropertyChanged"/> event for
/// a given property.
/// </summary>
/// <param name="propertyName">The name of the changed property.</param>
protected void OnPropertyChanged(string propertyName)
{
  //validate the property name in debug builds
  VerifyProperty(propertyName);

  if (PropertyChanged != null)
  {
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  }
}


/// <summary>
/// Verifies whether the current class provides a property with a given
/// name. This method is only invoked in debug builds, and results in
/// a runtime exception if the <see cref="OnPropertyChanged"/> method
/// is being invoked with an invalid property name. This may happen if
/// a property's name was changed but not the parameter of the property's
/// invocation of <see cref="OnPropertyChanged"/>.
/// </summary>
/// <param name="propertyName">The name of the changed property.</param>
[Conditional("DEBUG")]
private void VerifyProperty(string propertyName)
{
  Type type = this.GetType();

  //look for a *public* property with the specified name
  PropertyInfo pi = type.GetProperty(propertyName);
  if (pi == null)
  {
    //there is no matching property - notify the developer
    string msg = "OnPropertyChanged was invoked with invalid property name {0}: ";
    msg += "{0} is not a public property of {1}.";
    msg = String.Format(msg, propertyName, type.FullName);
    Debug.Fail(msg);
  }
}

#endregion

 

…and don’t forget to declare the INotifyPropertyChanged implementation. The snippet won’t do that for you:

public class Class1 : INotifyPropertyChanged

 

Property Declaration

The second snippet (shortcut is pcp) simplifies the declaration of a given property for you. You can define property name, type, default value and a description summary. The generated code for a sample property looks like this:

#region UserId

/// <summary>
/// The numeric ID of the user. Defaults to null.
/// </summary>
private int? userId = null;


/// <summary>
/// The numeric ID of the user. Defaults to null.
/// </summary>
public int? UserId
{
  get { return userId; }
  set
  {
    //ignore if values are equal
    if (value == userId) return;

    userId = value;
    OnPropertyChanged("UserId");
  }
}

#endregion

 

Grab the snippet here: Download

Author: Categories: C#, ReSharper, WPF Tags: , ,

WPF is for LOB – and we can even do RAD (WTF?)

March 18th, 2008

Agreed – the WPF learning curve is steep, but once one figures out how the basics work, things tend to get amazingly easy. And of course, the emerging sets of toolkits dont’t hurt either.

Here’s another sample – a customer of mine needed a utility to maintain data in their web shop (MySql) and update the shop database based on external data. Here’s the result of about 1.5 days work, starting pretty much from scratch (click on thumbnail for the screenshot):

pw_shopadmin

And once more, I have to say: LLBLGen ist a wonderful tool – setting up database mappings is a breeze, and the generated entities play very nice with WPF. Kudos to Frans Bourma for this one!

Author: Categories: WPF Tags: ,

A WPF File Selection control

March 14th, 2008

 fileselector

This is a pretty simple user control, which allows you to display a file dialog to open or save files. Its look can be easily adjusted, and it provides built-in truncation of the file string to a predefined length if necessary. Here’s the XAML for the above sample control:

<files:FileSelector x:Name="openFileSelector"
                    Mode="Open"
                    MaxDisplayLength="50"
                    Height="24"
                    Width="400" />

 

The TextBlock in the screenshot which displays the full file path was simply bound to the control’s FileName dependency property:

<TextBlock Text="{Binding ElementName=openFileSelector, Path=FileName}" />

 

The control does not provide too many extension or styling points – the idea is that you just copy it into your solution, adjust the styling of the control’s contents (Border, Button etc.) and be on your way. The source comes with a small sample project – enjoy 🙂

Download Control

Author: Categories: WPF Controls Tags: ,

New Blendables

March 6th, 2008

Just saw that Blendables have extended their portfolio of WPF controls. The stuff looks good, but unfortunately, their licensing scheme doesn’t:

A license is required for each machine utilizing the blendables controls. […] As we do not offer a deactivation method, if you must reactivate on a new developer machine you are allowed up to 3 activations. This is for the case of re-imaging or setting up a new developer machine. Once this limit is reached you must contact blendables support at […] to proceed with activation.

I must say, not purchasing their product is a no-brainer…

Author: Categories: WPF Controls Tags:

Preventing WPF TreeView items from being selected

March 6th, 2008

If you have a WPF TreeView control that shows nested data, and you don’t want the user to select nodes that contain child nodes, you can solve this declaratively as TreeViewItem provides all we need:

  • HasItems dependency property (bool)
  • Focusable dependency property (bool)

As both properties have the same type, you can use a binding expression (needs inversion of the boolean), or just use a trigger. Here’s the trigger:

 

<!-- root items can only be expanded, not selected -->
<Trigger Property="HasItems"
         Value="true">
  <Setter Property="Focusable"
          Value="false" />
</Trigger>

 

tree_dialogikWith this trigger in place, simply clicking on a node that contains child items does not change the tree’s current selection, while expanding/collapsing still works.

A common scenario for such a behavior is a tree that contains nodes which are used for grouping purposes only as in the example screenshot.

Author: Categories: WPF, WPF Controls, WPF TreeView Tags: ,

A KeyedCollection for int keys

March 5th, 2008

I’m using this class so much that I thought I’d share it with you. This generic class is an implementation of the KeyedCollection<TKey, TItem> class for items that have a numeric key:

public abstract class NumericallyKeyedCollection<T> : KeyedCollection<int, T>
{ }

Using int-Keys takes a bit more work than other types, because KeyedCollection provides indexers for both key and index. However, if the key’s type is int as well, you don’t have that option anymore. This class fills that gap by providing a few things for you:

  • GetByKey, TryGetByKey, and GetByIndex methods
  • Simplified exception handling and much better exception messages that include the invalid parameters. You can even customize exception messages by overriding the virtual GetKeyNotFoundMessage method.
  • An AddRange method which takes an IEnumerable<T>
  • For security reasons, the indexer has been overwritten and throws an InvalidOperationException if it is invoked. I just caught myself too much using it with the wrong parameters (index rather than key or vice versa). You might want to reverse that if you need to automatically serialize the collection to XML.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace Hardcodet.Util
{
  /// <summary>
  /// An implementation of a <see cref="KeyedCollection{TKey,TItem}"/> for items that
  /// have a numeric key, an therefore do not provide individual
  /// indexers for both key and index. As an alternative, this class provides
  /// <see cref="GetByIndex"/> and <see cref="GetByKey"/> methods plus more
  /// sophisticated exception handling for invalid keys.<br/>
  /// For security measures, the numeric indexer has been disabled, as using it
  /// is just misleading because one cannot tell whether it returns an item by
  /// index or by key...
  /// </summary>
  public abstract class NumericallyKeyedCollection<T> : KeyedCollection<int, T>
  {
    #region constructors

    /// <summary>
    /// Creates an empty collection.
    /// </summary>
    public NumericallyKeyedCollection()
    {
    }


    /// <summary>
    /// Inits the collection by copying all items of another
    /// collection.
    /// </summary>
    /// <param name="items">A collection of items to be added
    /// to this collection.</param>
    public NumericallyKeyedCollection(IEnumerable<T> items)
    {
      AddRange(items);
    }

    #endregion


    #region get by id

    /// <summary>
    /// Tries to retrieve a given item by its key.
    /// </summary>
    /// <param name="key">The key that was used to store the
    /// item.</param>
    /// <returns>The matching item, if any. Otherwise
    /// <c>default(T)</c> (null in case of standard objects).</returns>
    public T TryGetByKey(int key)
    {
      return Contains(key) ? GetByKey(key) : default(T);
    }


    /// <summary>
    /// Overrides the default implementation in order to provide a more sophisticated
    /// exception handling. In case of an invalid ID, an exception with a message is
    /// being thrown that is built the <see cref="GetKeyNotFoundMessage"/> method.
    /// </summary>
    /// <returns>The item with the matching key.</returns>
    /// <exception cref="KeyNotFoundException">Thrown if no descriptor
    /// with a matching ID was found.</exception>
    public T GetByKey(int key)
    {
      try
      {
        return base[key];
      }
      catch (KeyNotFoundException)
      {
        //throw custom exception that contains the key
        string msg = GetKeyNotFoundMessage(key);
        throw new KeyNotFoundException(msg);
      }
    }


    /// <summary>
    /// Overrides the default implementation in order disable usage of the indexer,
    /// as its usage is no longer clear because index and item keys are both of
    /// the same type.<br/>
    /// Invoking this indexer always results in a <see cref="InvalidOperationException"/>.
    /// </summary>
    /// <returns>Nothing - invoking the indexer results in a <see cref="InvalidOperationException"/>.</returns>
    /// <exception cref="InvalidOperationException">Thrown if the indexer is being invoked.</exception>
    public new virtual T this[int key]
    {
      get
      {
        string msg = "Using the indexer is disabled because both access through index and item key take the same type.";
        msg += " Use the GetByKey or GetByIndex methods instead.";
        throw new InvalidOperationException(msg);
      }
    }


    /// <summary>
    /// Gets an exception message that is being submitted with
    /// the <see cref="KeyNotFoundException"/> which is thrown
    /// if the indexer was called with an unknown key.
    /// This template method might be overridden in order to provide
    /// a more specific message.
    /// </summary>
    /// <param name="key">The submitted (and unknown) key.</param>
    /// <returns>This default implementation returns an error
    /// message that contains the requested key.</returns>
    protected virtual string GetKeyNotFoundMessage(int key)
    {
      string msg = "No matching item found for key '{0}'.";
      return String.Format(msg, key);
    }

    #endregion


    #region get by index

    /// <summary>
    /// Gets the item at the specified <paramref name="index"/>.
    /// </summary>
    /// <param name="index">Index within the collection.</param>
    /// <returns>The item at the specified index.</returns>
    /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/>
    /// if not a valid index of the internal list.</exception>
    public virtual T GetByIndex(int index)
    {
      return Items[index];
    }

    #endregion


    #region add items

    /// <summary>
    /// Adds a number of items to the list.
    /// </summary>
    /// <param name="items">The items to be appended.</param>
    public void AddRange(IEnumerable<T> items)
    {
      foreach (T item in items)
      {
        Add(item);
      }
    }


    /// <summary>
    /// Adds an item to the end of the collection.
    /// </summary>
    /// <param name="item">Item to be added to the collection.</param>
    /// <remarks>This override just provides a more sophisticated exception
    /// message.</remarks>
    public new void Add(T item)
    {
      try
      {
        base.Add(item);
      }
      catch (ArgumentException e)
      {
        int key = GetKeyForItem(item);
        if (Contains(key))
        {
          string msg = "An item with key '{0}' has already been added.";
          msg = String.Format(msg, key);
          throw new ArgumentException(msg, "item", e);
        }
        else
        {
          //in case of any other argument exception, just throw the
          //original exception
          throw;
        }
      }
    }

    #endregion
  }
}

Using it is fairly easy. Imagine you have a user collection that stores User objects by their numeric UserId. All you need to get going is this:

/// <summary>
/// Stores <see cref="User"/> instances by their numer
/// <see cref="User.UserId"/>.
/// </summary>
public class UserCollection : NumericallyKeyedCollection<User>
{
  protected override int GetKeyForItem(User item)
  {
    return item.UserId;
  }
}

 

…accordingly, here’s how you manipulate the collection:

public void Test()
{
  UserCollection col = new UserCollection();
  col.Add(new User(123));

  User userByIndex = col.GetByIndex(0);
  User userByKey = col.GetByKey(123);
}
Author: Categories: C# Tags: