Adorning XamDataGrid with a Popup Editor

In a recent post on the XamDataGrid Forum someone asked how to add an additional editor to a cell when it goes into edit mode.  The scenario is that the data source objects have twenty four numeric properties, and each numeric property is accompanied by a text property that stores an explanation for why the numeric property was modified.  This is a common scenario for applications where creating a verbose audit trail is required.  The goal was to have the grid display only the numeric properties, but when a cell goes into edit mode, show an additional editor so that the user can explain why he/she is updating the numeric value.

This got me thinking.  I decided that putting another editor into the cell, while possible, might not produce the best user experience.  In my opinion, such “auxiliary” information should not interfere with the structure and layout of the core data in the grid.  I think of an explanation for why a cell’s value has changed as “metadata” about the cell, and, as such, it should be displayed outside of the cell.  In this blog post, I’ll show you how I took that metadata and hosted it in the adorner layer.

When you run the demo app, edit the value of a cell, and then press Tab, the UI looks like this (after typing in some text):


 
In that screenshot, the user edited the first cell in the third row.  This is why the "Change Reason" editor appears beneath it.  After entering in an explanation for the change, and pressing Tab again, the edited cell has a new background color and a tooltip, which shows the change reason.  It looks like this:

 

Now let’s see how this works.  The data source is a collection of my Numbers class, which is defined as:

public class Numbers : INotifyPropertyChanged
{
    double _number1;
    double _number2;
    double _number3;
    string _number1ChangeReason;
    string _number2ChangeReason;
    string _number3ChangeReason;

    public Numbers(double number1, double number2, double number3)
    {
        _number1 = number1;
        _number2 = number2;
        _number3 = number3;
    }

    public double Number1
    {
        get { return _number1; }
        set
        {
            if (_number1 == value)
                return;

            _number1 = value;
            this.OnPropertyChanged("Number1");

            if (this.Number1ChangeReason == null)
                this.Number1ChangeReason = String.Empty;
        }
    }

    public string Number1ChangeReason
    {
        get { return _number1ChangeReason; }
        set
        {
            if (_number1ChangeReason == value)
                return;

            _number1ChangeReason = value;
            this.OnPropertyChanged("Number1ChangeReason");
        }
    }

    // All other NumberX and NumberXChangeReason
    // properties were omitted for clarity.
 
    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    void OnPropertyChanged(string prop)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(prop));
    }

    #endregion
}

Notice how each NumberX property has a corresponding NumberXChangeReason property.  When the user types an explanation for why they edited a numeric value, they are putting a value into one of those properties.  The editor for the “Change Reason” value is called ChangeReasonEditControl.  Its XAML file contains this:

<UserControl
  x:Class="XamDataGridAdorningEditor.ChangeReasonEditControl"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Background="Transparent"
  >
  <Border
    Background="WhiteSmoke"
    BorderBrush="LightGray"
    BorderThickness="0.5"
    CornerRadius="4"
    >
    <Border.BitmapEffect>
      <DropShadowBitmapEffect />
    </Border.BitmapEffect>

    <GroupBox
      BorderBrush="Transparent"
      FontWeight="Bold"
      Header="Change Reason"
      >
      <TextBox
        x:FieldModifier="private"
        x:Name="textBox"
        FontWeight="Normal"
        MaxLines="10"
        Text="{Binding Path=ChangeReason, UpdateSourceTrigger=PropertyChanged}"
        TextWrapping="Wrap"
        Width="100" Height="80"
        />
    </GroupBox>
  </Border>
</UserControl>

In order to make that control appear directly beneath a modified cell in the XamDataGrid, I use my trusty UIElementAdorner class to inject it into the adorner layer.  I won’t show that class here, because it is a good amount of code, but you can see it if you download the demo project.

Look at the Text property binding on the TextBox in the ChangeReasonEditControl.  Notice how it is bound to a property named ChangeReason.  The Numbers class does not have a property called ChangeReason.  In order to allow ChangeReasonEditControl to bind to any of the NumberXChangeReason properties, I had to create a proxy class.  That class is a private nested class within the main Window, defined as:

private class ChangeReasonProxy
{
    readonly Numbers _numbers;
    readonly PropertyDescriptor _changeReasonPropDesc;

    public ChangeReasonProxy(Numbers numbers, string changeReasonPropName)
    {
        _numbers = numbers;
        _changeReasonPropDesc = TypeDescriptor.GetProperties(numbers)[changeReasonPropName];
    }

    public string ChangeReason
    {
        get { return _changeReasonPropDesc.GetValue(_numbers) as string; }
        set { _changeReasonPropDesc.SetValue(_numbers, value); }
    }
}

When the adorning editor is displayed, its DataContext is set to an instance of that proxy class.  Here’s the code that handles this task:

void CreateProxyForAdorner()
{
    if (_adorner == null || this.xamDG.ActiveCell == null)
        return;

    Numbers numbers = this.xamDG.ActiveCell.Record.DataItem as Numbers;
    string changeReasonPropertyName = this.xamDG.ActiveCell.Field.Name + "ChangeReason";
    _adorner.DataContext = new ChangeReasonProxy(numbers, changeReasonPropertyName);
}

I won’t burden this blog post with all of the code involved, but the final thing to take note of is the Style applied to each Field’s editor.  Those Styles apply the tooltip and background color after the user has edited a cell in a Field.  Here is the declaration of the first Field in the grid:

<igDP:Field Name="Number1">
  <igDP:Field.Settings>
    <igDP:FieldSettings>
      <igDP:FieldSettings.EditorStyle>
        <Style TargetType="{x:Type igEditors:XamNumericEditor}">                    
          <Setter Property="ToolTip" Value="{Binding Path=DataItem.Number1ChangeReason}" />
          <Setter Property="Background" Value="{StaticResource ModifiedCellBackBrush}" />
          <Style.Triggers>
            <DataTrigger
               Binding="{Binding Path=DataItem.Number1ChangeReason}"
               Value="{x:Null}"
               >
              <Setter Property="Background" Value="Transparent" />
            </DataTrigger>
          </Style.Triggers>
        </Style>

      </igDP:FieldSettings.EditorStyle>
    </igDP:FieldSettings>
  </igDP:Field.Settings>
</igDP:Field>

Download the demo project here.  This project was built in Visual Studio 2008 using NetAdvantage for WPF v8.1.


Tags /

Comments  (2 )

Yuri
on Sun, Jun 3 2012 4:44 AM

Thank you!

www.amazon.com/Xamsa-CNT-135-Squash-Racket/dp/B00ME3EFJI
on Sat, Oct 11 2014 1:37 AM

Adorning XamDataGrid with a Popup Editor - Visual Design - Infragistics.com Blog

Add a Comment

Please Login or Register to add a comment.