Showing posts with label WPF. Show all posts
Showing posts with label WPF. Show all posts

Wednesday, 23 July 2008

Is INotifyPropertyChanged an anti-pattern?

Something is starting to bother me recently about data binding and the INotifyPropertyChanged interface. It's just that EVERY class you write ends up having to implement it (I am generally talking about writing WPF/Silverlight applications here). And that's just not sitting well with me!

It doesn't sit well because I believe it violates the principles of separations of concern. The major annoyance for me lies with calculated properties (those which are derived from another). Lets start with a simple class, Order, which contains two properties, ItemPrice and Quantity (other fields and methods omitted for sake of brevity):

public class Order : INotifyPropertyChanged
{
  public decimal ItemPrice 
  { 
    get { return this.itemPrice; }
    set 
    {
       this.itemPrice = value;
       this.RaisePropertyChanged("ItemPrice");
    }
  }

  public int Quantity 
  { 
    get { return this.quantity; }
    set 
    {
       this.quantity= value;
       this.RaisePropertyChanged("Quantity");
    }
  }
}

We need to add a property TotalPrice to this entity which will expose the total price of the order. What do we do here? This is a calculated value so should clearly be read only, and I usually implement the calculation within the property itself. What I have found myself doing many times in the past (and I have seen done in a lot of examples) is:

public class Order : INotifyPropertyChanged
{
  public decimal ItemPrice 
  { 
    get { return this.itemPrice; }
    set 
    {
       this.itemPrice = value;
       this.RaisePropertyChanged("ItemPrice");
       this.RaisePropertyChanged("TotalPrice");
    }
  }

  public int Quantity 
  { 
    get { return this.quantity; }
    set 
    {
       this.quantity= value;
       this.RaisePropertyChanged("Quantity");
       this.RaisePropertyChanged("TotalPrice");
    }
  }

  public decimal TotalPrice
  {
    get { return this.ItemPrice * this.Quantity; }    
  }
}

This is nasty. We have had to modify our other two properties just because we have added this new one. It works, but as you add more and more dependent properties your code gets messy. Those properties shouldn't need to know that the TotalPrice property exists!

That pattern breaks down completely when you start using inheritance in your domain objects. Imagine if I now require a new type of domain object, SalesOrder, where I want to express the fact that there is a commission (expressed as a fraction) which needs to be paid to some salesperson on this order:

public class SalesOrder : Order
{
  public decimal SalesCommision
  { 
    get { return this.salesCommision}
    set 
    {
       this.salesCommisionPercentage = value;
       this.RaisePropertyChanged("SalesCommision");
       this.RaisePropertyChanged("TotalCommission");
    }
  }

  public decimal TotalCommission
  { 
    get { return this.TotalPrice * this.SalesCommission; }
  }
}

Uh-oh! It's come a bit unstuck here. If the price changes in the base class, my UI is not going to redisplay the total commission! This could be great for my sales person but not so great for my accountant!

So what do we do here to get around this? We clearly can't modify our base class, so how about we listen to our own property notifications, like thus:

public class SalesOrder : Order
{
  // Properties defined as previously //

  protected override void RaisePropertyChanged(string propertyName)
  {
     base.RaisePropertyChanged(propertyName);
     if (propertyName == "TotalPrice")
     {
        this.RaisePropertyChanged("TotalCommission");
     }
  }
}

Here we override the method which raises the event, check if the relevant dependent property is being raised, and if so we raise a changed event on our commission property. This works, but YUCK! Just imagine having to unit test all this! :-(

Note that we end up with the same problem if we use composition. Imagine that instead of storing the sales commission on our entity, it was actually stored on a related SalesPerson entity and we need to calculate it from there. Now we need to know that if the sales person's commission changes, the order's total commission has changed:

public class SalesOrder : Order
{
  public SalesPerson SalesPerson
  { 
    get { return this.salesPerson; }
    set 
    {
       if (this.salesPerson != null)
       {
          this.salesPerson.PropertyChanged -= HandleSalesPersonPropertyChanged;
       }

       this.salesPerson = value;
       this.RaisePropertyChanged("SalesPerson");
       this.RaisePropertyChanged("TotalCommission");

       this.salesPerson.PropertyChanged += HandleSalesPersonPropertyChanged;
    }
  }

  public decimal TotalCommission
  { 
    get { return this.TotalPrice * this.SalesPerson.Commission; }
  }

  protected virtual void HandleSalesPersonPropertyChanged(object sender, PropertyChangedEventArgs e)
  {
     if (e.PropertyName == "Commission")
     {
        this.RaisePropertyChanged("TotalCommission");
     }
  }
}

We subscribe to the PropertyChanged event on our sales person, and when its commision is changed, we act accordingly.

I think this is all a mess, and it just gets worse and worse as you build up your domain model and add more classes and more relations. It's easy to forget one of these dependencies. If you are building a fairly complex UI on top of this (e.g. one which allows you to drill down and navigate through the relationships and make changes to objects), I'm so sure you will introduce bugs due to not raising the event, I know it's happened to me!

On my next (real) WPF project this is definitely something I will be thinking about seriously.

Anyway I would be interested to know what people think about this. Is this a problem which other people have faced? How do you architect your applications to avoid this complexity?

Wednesday, 9 July 2008

An unmanaged version of WPF coming?

This sounds incredibly interesting - it appears Microsoft are working on what looks like a native version of WPF!

Anyone heard about this or know any more about this - I would love to know some details! An unmanaged version would be great and would probably yield massive performance issues even for unmanaged WPF as a lot of the grunt work would presumably be able to be pushed to unmanaged code.

Could this be the return of the Microsoft's old Cairo project (which now seems to be being worked on by an external group of developers if I'm not mistaken - www.cairoshell.com)

Thoughts?

Sunday, 6 July 2008

Selecting the Detail Level to View at Runtime in WPF - An even better way!

Regarding my previous post Josh rightly commented that this technique could lead to lots of extra visuals being created that are never displayed, which for large data loads is not desirable and definitely a waste of resources. Josh also made a number of other salient points which are worth reading.

Well it was upon reading this comment that it struck me that there was in fact no need to be creating (and hiding lots of visuals) when they are not used! We simply need a SINGLE content presnter, and just change the template of that with a trigger, like so:

  <DataTemplate x:Key="SelectorTemplate">
    <Grid>
      <ContentPresenter x:Name="proxyDataPresenter" Content="{Binding}" />
    </Grid>
    <DataTemplate.Triggers>
      <DataTrigger Binding="{Binding ElementName=detailLevelSlider, Path=Value}" Value="1">
        <Setter TargetName="proxyDataPresenter" Property="ContentTemplate" 
                Value="{StaticResource LowTemplate}" />
      </DataTrigger>
      <DataTrigger Binding="{Binding ElementName=detailLevelSlider, Path=Value}" Value="2">
        <Setter TargetName="proxyDataPresenter" Property="ContentTemplate" 
                Value="{StaticResource MediumTemplate}" />
      </DataTrigger>
      <DataTrigger Binding="{Binding ElementName=detailLevelSlider, Path=Value}" Value="3">
        <Setter TargetName="proxyDataPresenter" Property="ContentTemplate" 
                Value="{StaticResource HighTemplate}" />
      </DataTrigger>
    </DataTemplate.Triggers>
  </DataTemplate>

The simplicity baffles me and it really works a treat... again this kind of thing just makes me appreciate the sheer power of WPF and it's declarative style of programming.

Saturday, 5 July 2008

Selecting the Detail Level to View at Runtime in WPF - An alternate way?

I recently read Josh Smith's post on codeproject which explained how to use a Slider control to dynamically apply a WPF data template a runtime. If you haven't read it, I suggest you read it before continuing.

So I have come up with an alternative solution which is done in pure XAML. This is a technique which I have used on previous projects and it involves creating a "surrogate" data template which simply passes control to another data template via a content presenter. Because a data template is used to do this, it has access to the inheritance context so does not require any freezable hacks to find the slider or other data templates.

Note that in order to make this a pure xaml solution I replaced the data source (which was originally in code) with an XmlDataProvider nested in the XAML document. Hence that lovely Pam girl does not appear in my version, which will probably disappoint most people...

Anyway check it out below, simple copy and paste in XamlPad/Kaxaml to see it working!

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <DockPanel>  
    <DockPanel.Resources>
    
      <XmlDataProvider x:Key="Data" XPath="/people">
        <x:XData>
          <people xmlns="">
            <person name="Neil" age="29" gender="M" />
            <person name="Jane" age="40" gender="F" />
            <person name="Jack" age="23" gender="M" />
          </people>
        </x:XData>
      </XmlDataProvider>        
    
      <DataTemplate x:Key="LowTemplate">
        <TextBlock Text="{Binding XPath=@name}" />
      </DataTemplate>
    
      <DataTemplate x:Key="MediumTemplate">
        <TextBlock>
            <TextBlock Text="{Binding XPath=@name}" />
            <Run>(</Run>
            <TextBlock Text="{Binding XPath=@age}" Margin="-4,0" />
            <Run>)</Run>
          </TextBlock>
      </DataTemplate>
    
      <DataTemplate x:Key="HighTemplate">
        <TextBlock>
          <TextBlock Text="{Binding XPath=@name}" />
          <Run>(</Run>
          <TextBlock Text="{Binding XPath=@age}" Margin="-4,0" />
          <Run>) -</Run>
          <TextBlock Text="{Binding XPath=@gender}" />
        </TextBlock>
      </DataTemplate>
      
      <DataTemplate x:Key="SelectorTemplate">
        <Grid>
          <ContentPresenter x:Name="lowPresenter" 
                Content="{Binding}" ContentTemplate="{StaticResource LowTemplate}" 
                Visibility="Collapsed" />
          <ContentPresenter x:Name="mediumPresenter" 
                Content="{Binding}" ContentTemplate="{StaticResource MediumTemplate}" 
                Visibility="Collapsed" />
          <ContentPresenter x:Name="highPresenter" 
                Content="{Binding}" ContentTemplate="{StaticResource HighTemplate}" 
                Visibility="Collapsed" />
        </Grid>
        <DataTemplate.Triggers>
          <DataTrigger 
                Binding="{Binding ElementName=detailLevelSlider, Path=Value}" 
                Value="1">
            <Setter TargetName="lowPresenter" 
                       Property="Visibility" 
                       Value="Visible" />
          </DataTrigger>
          <DataTrigger 
                Binding="{Binding ElementName=detailLevelSlider, Path=Value}" 
                Value="2">
            <Setter TargetName="mediumPresenter" 
                       Property="Visibility" 
                       Value="Visible" />
          </DataTrigger>
          <DataTrigger 
                Binding="{Binding ElementName=detailLevelSlider, Path=Value}" 
                Value="3">
            <Setter TargetName="highPresenter" 
                       Property="Visibility" 
                       Value="Visible" />
          </DataTrigger>
        </DataTemplate.Triggers>
      </DataTemplate>
      
    </DockPanel.Resources>
      
    <StackPanel 
      DockPanel.Dock="Bottom" 
      Background="LightGray"
      Margin="4" 
      Orientation="Horizontal"
      >
      <TextBlock 
        Margin="2,0,4,0" 
        Text="Detail Level:" 
        VerticalAlignment="Center" 
        />
      <Slider 
        x:Name="detailLevelSlider"
        DockPanel.Dock="Bottom" 
        Minimum="1" Maximum="3" 
        SmallChange="1" LargeChange="1" 
        IsSnapToTickEnabled="True" TickFrequency="1"
        Value="0" 
        Width="120" 
        />
    </StackPanel>
    
    <ScrollViewer>
      <ItemsControl
        ItemsSource="{Binding Source={StaticResource Data}, XPath=person}"
        ItemTemplate="{StaticResource SelectorTemplate}"
        />
    </ScrollViewer>
    
  </DockPanel>
</Page>

Wednesday, 12 March 2008

Prism - New CAB/MVC style framework for building WPF applications

About a year ago Microsoft's patterns and practices team released Acropolis as a beta - a UI framework for building WPF applications using a MVC style approach. I played around with it but to be honest I didn't feel it was advanced enough for building fully fledged business applications, but a very good start.

For the National Express project we gradually applied the MVVM (Model-View-ViewModel) pattern, which we fully crafted by hand. Obviously it was a learning experience but it worked well and fitted in well with WPF. Also as we crafted it by hand we had full control of pretty much everything going on in the application and could refactor as we pleased.

Well the Acropolis framework development project was halted about six months ago, after much user feedback. The patterns and practices team have been working on a new CAB style framework, codenamed Prism (not to be confused with Mozilla's Prism project, which integrates web applications with the desktop) . Briefly looking at it, it does remind me of the existing composite application frameworks developed by that team - namely Web Client Software Factory for ASP.NET MVP applications, and the Composite Application Blocks for building WinForms applications.

I have to say I like the approach. This feels very familar for those of us that have used either of these two frameworks, which can only be a good thing and helpful for those architecting WPF applications who have come from a WebForms/WinForms background. It will help in increasing the adoption of WPF applications for the business/enterprise.

The team have released a reference implementation - a stock trader application. Download it here and read the blog post which provides more details about the reference implementation.

Monday, 30 April 2007

“Default” buttons in WPF and multiple default buttons per page

Most user interface frameworks have the concept of a default button. The default button is the button which is activated when the Enter key is pressed. This is an important aspect for usability. For example, on a search form, most users expect to be able to perform a search by pressing Enter on any form element. If this does not happen as expected, the user gets frustrated as they need to either tab onto the button and press Enter, or use the mouse to click. I think the web has pushed this standard as HTML forms automatically post themselves when the user presses Enter within them. WPF offers the ability to have both Default and Cancel buttons. The Cancel button is activated by pressing the ESC key, although in my opinion this is not quite as common as the Enter button as most users don’t expect to be able to press ESC within forms – only dialog windows maybe. How do default buttons work? There is nothing particularly special going on under the covers when you set a button to be the default button. When the IsDefault property is set, AccessKeyManager.Register is called, passing in “\r” as the access key. Similarly, the act of setting IsCancel calls AccessKeyManager.Register passing in the character code for ESC - “\x001b”. The only special thing that setting IsDefault will do is ensure that the IsDefaulted property is updated correctly. This allows, for example, buttons to be styled differently if they are defaulted. How do I get multiple default buttons per page? In ASP.NET a common problem was that because you are forced to have a single server-side form, it was not possible to have multiple logical forms on the page and have the Enter button behave as expected without a lot of hacking. For example, you might want the template for all your pages to have a small search form at the top with a search button, and also have the search performed when the user presses enter within the search box. When the user is on a page with its own form on it, you would want that form to be submitted when the user pressed enter. This was not possible natively. This issue was solved in ASP.NET2 by allowing any Panel or Table to have set a DefaultButton property. Behind the scenes this would set up an event handler in JavaScript which would activate the button when enter was pressed. In WPF, there is no such property. If you define two panels on a page, and into each panel place a TextBox and a Button with IsDefault="True", you will see that pressing enter in either TextBox always activates the first button. This is because that button registered itself first so is at the top of the invocation list of the Enter access key:

<Window x:Class="WindowsApplication7.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WindowsApplication7"
    Title="WindowsApplication7" Height="300" Width="300"
    >
    <StackPanel>
        <StackPanel Margin="5" Background="Yellow">
            <TextBox Margin="5" />
            <Button Margin="5" IsDefault="True" Content="Yellow" />
        </StackPanel>
        <StackPanel Margin="5" Background="Green">
            <TextBox Margin="5" />
            <Button Margin="5" IsDefault="True" Content="Green" />
        </StackPanel>
    </StackPanel>
</Window>
In the above example, no matter which TextBox has focus, the Yellow button is always activated when you press enter. To solve this problem, there is a fairly undocumented feature within the AccessKeyManager called scoping. This allows you to define a scope for an access key such that it is only activated within that scope. The way this is done is by handling the routed event and adding the scope to the event args. Then when it returns to the AccessKeyManager, it detects that it has been scoped and will only apply it to buttons within that scope. The code is quite simple to write, but I have created a small helper class to encourage readibility and code reuse. In WPF it’s so easy to write small helper classes and seamlessly integrate via the use of attached properties, so I made an attached property called AccessKeyScoper.IsAccessKeyScope which can be applied to any element. With this, the above example simply needs to be modified as follows, and the correct button will be activated:
<StackPanel Margin="5" Background="Yellow" local:AccessKeyScoper.IsAccessKeyScope="True">
    <TextBox Margin="5" />
    <Button Margin="5" IsDefault="True" Content="Yellow" />
</StackPanel>
<StackPanel Margin="5" Background="Green" local:AccessKeyScoper.IsAccessKeyScope="True">
    <TextBox Margin="5" />
    <Button Margin="5" IsDefault="True" Content="Green" />
</StackPanel>
The code for the helper class is here:
using System;
using System.Windows;
using System.Windows.Input;
namespace WindowsApplication7
{
    /// <summary>
    ///    Class used to manage generic scoping of access keys
    /// </summary>
    public static class AccessKeyScoper
    {
        /// <summary>
        ///    Identifies the IsAccessKeyScope attached dependency property
        /// </summary>
        public static readonly DependencyProperty IsAccessKeyScopeProperty =
            DependencyProperty.RegisterAttached("IsAccessKeyScope", typeof(bool), typeof(AccessKeyScoper), new PropertyMetadata(false, HandleIsAccessKeyScopePropertyChanged));
        /// <summary>
        ///    Sets the IsAccessKeyScope attached property value for the specified object
        /// </summary>
        /// <param name="obj">The object to retrieve the value for</param>
        /// <param name="value">Whether the object is an access key scope</param>
        public static void SetIsAccessKeyScope(DependencyObject obj, bool value)
        {
            obj.SetValue(AccessKeyScoper.IsAccessKeyScopeProperty, value);
        }
        /// <summary>
        ///    Gets the value of the IsAccessKeyScope attached property for the specified object
        /// </summary>
        /// <param name="obj">The object to retrieve the value for</param>
        /// <returns>The value of IsAccessKeyScope attached property for the specified object</returns>
        public static bool GetIsAccessKeyScope(DependencyObject obj)
        {
            return (bool) obj.GetValue(AccessKeyScoper.IsAccessKeyScopeProperty);
        }
        private static void HandleIsAccessKeyScopePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue.Equals(true))
            {
                AccessKeyManager.AddAccessKeyPressedHandler(d, HandleScopedElementAccessKeyPressed);
            }
            else
            {
                AccessKeyManager.RemoveAccessKeyPressedHandler(d, HandleScopedElementAccessKeyPressed);
            }
        }
        private static void HandleScopedElementAccessKeyPressed(object sender, AccessKeyPressedEventArgs e)
        {
            if (!Keyboard.IsKeyDown(Key.LeftAlt) && !Keyboard.IsKeyDown(Key.RightAlt) && GetIsAccessKeyScope((DependencyObject)sender))
            {
                e.Scope = sender;
                e.Handled = true;
            }
        }
    }
}

Saturday, 28 April 2007

Pasting to multiple cells in Xceed WPF DataGrid

In my previous article, Implementing Copy & Paste between cells in Xceed WPF DataGrid, I outined the method I was using to copy and paste values between cells in the Xceed WPF grid. To recap, this involves using the ApplicationCommands to register to the Copy command and copying the content to the clipboard as text. We then register to the Paste command and when this is received, set the current cell to Edit mode, simulate the text input, and commit the change. The next requirement is to support the selection of a range of cells, and to allow the user to paste a single value to all selected cells. Selection should be in one of three ways - either by dragging the mouse from the first cell to the last, by clicking the cell then clicking the last with shift held down, or by clicking cells with control pressed. As Xceed grid doesn't support multiple cell selection, and I don't want to mess around with the internal workings of Xceed's grid, I decided to create an attached property of type Boolean to attach to each selected cell. The attached property would indicate that that cell is currently selected. However, I then realised that as the Xceed grid is not inheriting from the Selector class, I might as well just reuse the Selector.IsSelected attached property, and avoid having to create my own. So here is the function to select a range of cells and set the attached property on them all. The range to be selected is defined by the "current" cell (which we can get natively from the DataGridControl) and a cell passed to the function.

        /// <summary>
        ///     Selects the range of cell from the "current" cell to the specified cell
        /// </summary>
        /// <param name="toCell">The cell to select to</param>
        private static void SelectRangeFromCurrent(Cell toCell)
        {
            DataGridControl dataGridControl = DataGridControl.GetParentDataGridControl(toCell);
 
            // Need a current item to do anything
            if (dataGridControl.CurrentItem == null)
            {
                return;
            }
 
            // Find the current cell
            Row currentRow = (Row) dataGridControl.GetContainerFromItem(dataGridControl.CurrentItem);
            Cell currentCell = null;
 
            if (currentRow != null && dataGridControl.CurrentColumn != null)
            {
                currentCell = currentRow.Cells[dataGridControl.CurrentColumn.Index];
            }
 
            // Do nothing if it's being edited
            if (currentCell == null || currentCell.IsBeingEdited)
            {
                return;
            }
 
            // Clear the selected status on all the cells
            ClearSelectedStatus(DataGridControl.GetParentDataGridControl(toCell));
 
            // Get the indexes of the rows and columns we need to loop through
            int fromRow, toRow, fromColumn, toColumn;
            GetFromAndToRowAndColumns(dataGridControl, currentCell, toCell, out fromRow, out toRow, out fromColumn, out toColumn);
 
            // Loop and set the IsSelected on all cells
            for (int rowIndex = fromRow; rowIndex <= toRow; rowIndex++)
            {
                Row row = (Row) dataGridControl.GetContainerFromIndex(rowIndex);
                for (int columnIndex = fromColumn; columnIndex <= toColumn; columnIndex++)
                {
                    Selector.SetIsSelected(row.Cells[columnIndex], true);
                }
            }
This function uses two other functions - ClearSelectedStatus(), which clears the IsSelected on all cells in a grid, and GetFromAndToRowAndColumns() which works out the indexes of the start and end points based on the cells. Once we have the indexes, it's a simple case of looping through setting the IsSelected on all cells between those indexes. Visually, setting the attached property will do nothing to the cells (and rightly so - this WPF we're talking about!). Therefore I define a style in my App.xaml which will change the background colour of the cells when the IsSelected is set
    <Style TargetType="xcdg:DataCell">
        <Style.Triggers>
            <Trigger Property="Selector.IsSelected" Value="True">
                <Setter Property="Background" Value="{StaticResource LightBlueBrush}" />
            </Trigger>
        </Style.Triggers>
    </Style>
So now that we can select multiple of cells, we need to wire up the event handling to actually trigger the selection. We subscribe to the MouseDown and MouseEnter events and detect if the left mouse button is pressed. If so, we act accordingly:
        /// <summary>
        ///     Handles when the mouse goes down over a cell, either deselecting everything, or selecting the single cell (if control is pressed)
        ///     or selecting the range of cells (if shift is pressed)
        /// </summary>
        private static void HandleCellMouseDown(object sender, RoutedEventArgs e)
        {
            Cell cell = sender as Cell;
 
            if (cell != null && Mouse.LeftButton == MouseButtonState.Pressed)
            {
                if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
                {
                    SelectRangeFromCurrent(cell);
                }
                else if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
                {
                    Selector.SetIsSelected(cell, true);
                }
                else
                {
                    ClearSelectedStatus(DataGridControl.GetParentDataGridControl(cell));
                }
            }
        }
 
        /// <summary>
        ///     Handles when the mouse enters a cell with the left button depressed, selecting the appropriate range of cells
        /// </summary>
        private static void HandleCellMouseEnter(object sender, RoutedEventArgs e)
        {
            Xceed.Wpf.DataGrid.Cell cell = sender as Xceed.Wpf.DataGrid.Cell;
 
            if (cell != null && Mouse.LeftButton == MouseButtonState.Pressed)
            {
                SelectRangeFromCurrent(cell);
            }
        }
And that's it - we have multiple cell selection working pretty nicely! The last thing to do is update my previous code for pasting, and rather than just pasting to the current cell we paste to every cell which has the Selector.IsSelected property set to true. I have put all the code to do this into a helper class - DataGridControlHelper.cs. Therefore I won't include any more code here as it's mostly straightforward glue code with a few usability tweaks (such as unselecting everything when someone navigates with the keyboard). Just call the static RegisterEventHandlers() method when your application starts up.

Wednesday, 25 April 2007

Implementing Copy & Paste between cells in Xceed WPF DataGrid

I am using the Xceed grid in my application to allow the user to enter monetary values into cells. They would like to be able to copy and paste values between cells in the grid, but this is not supported by the grid natively. Support from the Clipboard is very good in WPF (via the System.Windows.Clipboard static class) so this actually is not a particularly difficult thing to implement. My aim is to write a generic reusable solution here that will work for any Xceed grid in my application, rather than a specific one, so I neither want to do any copying or updating of actual data. What we first need to do is register execution handlers for the ApplicationCommands.Copy and the ApplicationCommands.Paste commands against the DataGrid. We then either copy the content of the current cell or paste into it depending on which command is executed. I think the best place to perform generic event handling against a class is in the static initialiser of the App class, like follows:

        static App()
        {
            CommandManager.RegisterClassCommandBinding(typeof(DataGridControl), newCommandBinding(ApplicationCommands.Paste, HandleXceedPasteCommandExecuted)); 
            CommandManager.RegisterClassCommandBinding(typeof(DataGridControl), newCommandBinding(ApplicationCommands.Copy, HandleXceedCopyCommandExecuted)); 
        }
We'll start with the Copy handler. This is quite simple - we first work out which cell has the focus, then copy the textual version of its content to the clipboard.
        private static void HandleXceedCopyCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            DataGridControl dataGridControl = sender as DataGridControl;
 
            if (dataGridControl != null)
            {
                Xceed.Wpf.DataGrid.Row row = (Xceed.Wpf.DataGrid.Row) dataGridControl.ItemContainerGenerator.ContainerFromItem(dataGridControl.CurrentItem);
                Xceed.Wpf.DataGrid.Cell currentCell = row.Cells[dataGridControl.CurrentColumn.Index];
 
                if (currentCell != null && currentCell.HasContent)
                {
                    Clipboard.SetText(currentCell.Content.ToString());
                }
            }
        }
Note that we copy the ToString() of the content to the clipboard, so this technique would not work if the Cell's content was an object whose string was not valid to be entered into a Cell. As we are copying and pasting between Cells, this shouldn't be a problem. Now onto the paste command. The paste command is more tricky and I will explain why. We could just update the content directly, but that would bypass any text input handling and formatting that we may be doing in the Xceed grid. For example, one grid in my application uses the NumericInputScope class to block all non numbers from being typed into the Grid. If we just updated the grid at this point, the PreviewKeyDown and PreviewTextInput would never be fired, therefore we would be allowing invalid characters to be typed into the grid. The solution I came up with for this is to switch the cell into Edit Mode, simulate keypresses for each character being pasted (via raising the TextInput event), and the commit the changes back to the cell. This should ensure that the paste is essentially doing exactly the same thing as it would do if the user pressed the keys themselves! Note we dispatch the work back to the Dispatcher... this is to allow the cell's editor to activate and the cell to get focus before we simulate the key presses.
        public delegate void Callback();
 
        private static void HandleXceedPasteCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            DataGridControl dataGridControl = sender as DataGridControl;
 
            if (dataGridControl != null && Clipboard.ContainsText())
            {
                Xceed.Wpf.DataGrid.Row row = (Xceed.Wpf.DataGrid.Row)dataGridControl.ItemContainerGenerator.ContainerFromItem(dataGridControl.CurrentItem);
                Xceed.Wpf.DataGrid.Cell currentCell = row.Cells[dataGridControl.CurrentColumn.Index];
 
                if (!currentCell.ReadOnly)
                {
                    string text = Clipboard.GetText();
 
                    currentCell.BeginEdit();
 
                    Application.Current.Dispatcher.Invoke(DispatcherPriority.Input, (Callback)delegate
                    {
                        if (Keyboard.FocusedElement != null)
                        {
                            foreach (char c in text)
                            {
                                TextComposition textComposition = new TextComposition(InputManager.Current, Keyboard.FocusedElement, new string(c, 1));
 
                                TextCompositionEventArgs textCompositionEventArgs = new TextCompositionEventArgs(Keyboard.PrimaryDevice, textComposition);
                                textCompositionEventArgs.RoutedEvent = UIElement.TextInputEvent;
 
                                Keyboard.FocusedElement.RaiseEvent(textCompositionEventArgs);
                            }
                        }
 
                        currentCell.EndEdit();
                    });
                }
            }
        }
I'd be interested to know what people think of this method, and if anyone has tried any other solutions to implement copy and paste in the grid let me know. The next requirement is to support selection of and pasting to multiple cells within the Grid. Again, this is not currently supported in the Xceed DataGrid, but I will hopefully come up with a way of doing it!

Saturday, 21 April 2007

New tool for viewing WPF default control templates

I have always used StyleSnooper to get at the templates for my WPF controls. It's pretty useful and simple, and can get you the style and templates.

Now I just stumbled into a new tool, by Chris Sells and Ian Griffiths, called ShowMeTheTemplate.

This is nice. It formats the output a lot better than StyleSnooper (you can expand and collapse ala IE) and it splits the different templates (e.g HeaderTemplate and ContentTemplate into separate text boxes). You also get to see a preview of the control you are viewing the template for. Finally, you get to choose which Theme to use.

This should prove a useful addition to the WPF developer's toolkit especially combined with StyleSnooper for seeing all the other styles applied by default to a control.

Tuesday, 6 February 2007

Using a ComboBox to select an Enum value in XAML

It is often a requirement in UI to provide a combo box which is used to select an Enum value. There are many ways to achieve this task. However, some are more elegant than others. Here I present a simple way to do this which uses nothing but XAML.

Firstly, we wish to get all the possible values of the Enum and set them to the ItemsSource of the ComboBox. For this, we can utilise the static GetValues() method on the Enum class. This can be done in XAML using an ObjectDataProvider. We then bind the ItemsSource property of our ComboBox to the ObjectDataProvider.

For this example, I am displaying the "BindingMode" enumeration which is built into WPF.

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:sys="clr-namespace:System;assembly=mscorlib">
    <Page.Resources>
        <ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="PossibleValues">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="BindingMode" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Page.Resources>
    <ComboBox ItemsSource="{Binding Source={StaticResource PossibleValues}}" SelectedValue="{Binding Source={StaticResource PossibleValues}, Path=[4]}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Page>

Note that in order to use the GetValues() method, we need to include a reference to the System namespace in the mscorlib assembly.

So now we have a ComboBox which looks something like this:

Well that's all good, but we can do better than that! In most applications we want to provide descriptive text for the values than just the ToString() of the Enum.

As usual, DataTemplates come to our rescue - we can do it as follows:

<DataTemplate DataType="{x:Type BindingMode}">
    <TextBlock Text="{Binding}" x:Name="PART_Text" />
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding}" Value="OneWay">
            <Setter TargetName="PART_Text" Property="Text" Value="One way binding" />
            <Setter TargetName="PART_Text" Property="FontWeight" Value="Bold" />
        </DataTrigger>
        <DataTrigger Binding="{Binding}" Value="TwoWay">
            <Setter TargetName="PART_Text" Property="Text" Value="Two Way Binding" />
        </DataTrigger>
        <DataTrigger Binding="{Binding}" Value="OneTime">
            <Setter TargetName="PART_Text" Property="Visibility" Value="Collapsed" />
        </DataTrigger>
        <DataTrigger Binding="{Binding}" Value="OneWayToSource">
            <Setter TargetName="PART_Text" Property="Text" Value="One Way To Source Binding" />
        </DataTrigger>
        <DataTrigger Binding="{Binding}" Value="Default">
            <Setter TargetName="PART_Text" Property="Text" Value="Default Binding" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

Here is the final ComboBox:

I could have been more adventurous and used some images or colours or anything really in the DataTemplate, but I hope this example is enough to demonstrate what you can achieve.

Friday, 29 December 2006

WPF/XAML - x:Type and nested classes

Well now I am getting started with XAML and WPF I am hoping to start blogging a bit more! So I was trying to reference a nested type with the {x:Type} markup extension and it wasn't working. Say you have a class as follows:

public class MyClass
{
    public class MyInnerClass
    {
    }
}

You want to reference the inner class (e.g. for a DataTemplate). The following XAML does not work

<DataTemplate DataType="{x:Type MyClass.MyInnerClass}">

What you need to do is:

<DataTemplate DataType="{x:Type MyClass+MyInnerClass}">

Thanks to Seb for the hint on that one!