Thursday, December 1, 2011

Creating an Editable GridView inside a ListView in XAML

So it's been a while since I posted last, but recently I had the opportunity to do some serious XAML binding stuff, which I thought I'd share with you. If you've ever wanted an easy way to create an editable gridview inside a listview, it can seem like a pretty daunting task, but actually, it's really not that tough. You can setup a binding scenario that will do 80% of the work for you. Try the following out for size: First, the XAML code:
 <Window x:Class="EditableGridViewExample.Window1"  
     xmlns:local="clr-namespace:EditableGridViewExample"  
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
     Title="Window1">  
  <Window.Resources>  
   <local:BoolToVisibilityConverter x:Key="b2v" />  
   <Style TargetType="{x:Type TextBlock}" x:Key="TextBlockStyle">  
    <Setter Property="Visibility">  
     <Setter.Value>  
      <MultiBinding Converter="{StaticResource b2v}" ConverterParameter="False" >  
       <Binding ElementName="EditModeCheckBox" Path="IsChecked" />  
       <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}" Path="IsSelected" />  
      </MultiBinding>  
     </Setter.Value>  
    </Setter>  
    <Setter Property="VerticalAlignment" Value="Center" />  
   </Style>  
   <Style TargetType="{x:Type TextBox}" x:Key="TextBoxStyle">  
    <Setter Property="Visibility">  
     <Setter.Value>  
      <MultiBinding Converter="{StaticResource b2v}" ConverterParameter="True" >  
       <Binding ElementName="EditModeCheckBox" Path="IsChecked" />  
       <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}" Path="IsSelected" />  
      </MultiBinding>  
     </Setter.Value>  
    </Setter>  
    <Setter Property="VerticalAlignment" Value="Center" />  
   </Style>  
  </Window.Resources>  
  <Grid>  
   <Grid.RowDefinitions>  
    <RowDefinition Height="Auto" />  
    <RowDefinition Height="*" />  
   </Grid.RowDefinitions>  
   <CheckBox x:Name="EditModeCheckBox" Content="Edit Mode" />  
   <ListView Grid.Row="1" x:Name="WindowListView" ItemsSource="{Binding}">  
    <ListView.View>  
     <GridView>  
      <GridViewColumn>  
       <GridViewColumnHeader Tag="ColumnA" Content="Object" />  
       <GridViewColumn.CellTemplate>  
        <DataTemplate>  
         <Grid>  
          <TextBlock Text="{Binding Path=ColumnA}" Style="{StaticResource TextBlockStyle}" />  
          <TextBox Text="{Binding Path=ColumnA}" Style="{StaticResource TextBoxStyle}" />  
         </Grid>  
        </DataTemplate>  
       </GridViewColumn.CellTemplate>  
      </GridViewColumn>  
      <GridViewColumn>  
       <GridViewColumnHeader Tag="ColumnB" Content="Type" />  
       <GridViewColumn.CellTemplate>  
        <DataTemplate>  
         <Grid>  
          <TextBlock Text="{Binding Path=ColumnB}" Style="{StaticResource TextBlockStyle}"/>  
          <TextBox Text="{Binding Path=ColumnB}" Style="{StaticResource TextBoxStyle}" />  
         </Grid>  
        </DataTemplate>  
       </GridViewColumn.CellTemplate>  
      </GridViewColumn>  
     </GridView>  
    </ListView.View>  
   </ListView>  
  </Grid>  
 </Window>  
Now, the codebehind (I put everything in one file to make it easy to use):
 using System;  
 using System.Collections.ObjectModel;  
 using System.Windows;  
 using System.Windows.Data;  
   
 namespace EditableGridViewExample  
 {  
  public class ListItems : ObservableCollection<ListData>  
  {  
   /// <summary>  
   /// Initializes a new instance of the <see cref="ListItems"/> class.  
   /// </summary>  
   public ListItems()  
   {  
    this.Add(new ListData() { ColumnA = "apple", ColumnB = "fruit" });  
    this.Add(new ListData() { ColumnA = "jaguar", ColumnB = "animal" });  
    this.Add(new ListData() { ColumnA = "lullaby", ColumnB = "music" });  
    this.Add(new ListData() { ColumnA = "monkey", ColumnB = "animal" });  
    this.Add(new ListData() { ColumnA = "orange", ColumnB = "fruit" });  
    this.Add(new ListData() { ColumnA = "whale", ColumnB = "mammal" });  
    this.Add(new ListData() { ColumnA = "coathanger", ColumnB = "other" });  
   }  
  }  
   
  public class BoolToVisibilityConverter : IMultiValueConverter  
  {  
   /// <summary>  
   /// Converts source values to a value for the binding target. The data binding engine calls this method when it propagates the values from source bindings to the binding target.  
   /// </summary>  
   /// <param name="values">The array of values that the source bindings in the <see cref="T:System.Windows.Data.MultiBinding"/> produces. The value <see cref="F:System.Windows.DependencyProperty.UnsetValue"/> indicates that the source binding has no value to provide for conversion.</param>  
   /// <param name="targetType">The type of the binding target property.</param>  
   /// <param name="parameter">The converter parameter to use.</param>  
   /// <param name="culture">The culture to use in the converter.</param>  
   /// <returns>  
   /// A converted value.If the method returns null, the valid null value is used.A return value of <see cref="T:System.Windows.DependencyProperty"/>.<see cref="F:System.Windows.DependencyProperty.UnsetValue"/> indicates that the converter did not produce a value, and that the binding will use the <see cref="P:System.Windows.Data.BindingBase.FallbackValue"/> if it is available, or else will use the default value.A return value of <see cref="T:System.Windows.Data.Binding"/>.<see cref="F:System.Windows.Data.Binding.DoNothing"/> indicates that the binding does not transfer the value or use the <see cref="P:System.Windows.Data.BindingBase.FallbackValue"/> or the default value.  
   /// </returns>  
   public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)  
   {  
    bool param = bool.Parse(parameter as string);  
    bool combined = true;  
    foreach (bool val in values) combined &= val;  
    return combined == param ? Visibility.Visible : Visibility.Hidden;  
   }  
   
   public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)  
   {  
    throw new NotImplementedException();  
   }  
  }  
   
  public class ListData : DependencyObject  
  {  
   public static readonly DependencyProperty ColumnAProperty =  
    DependencyProperty.Register("ColumnAProperty", typeof(string),  
    typeof(ListData), new UIPropertyMetadata(null));  
   
   public static readonly DependencyProperty ColumnBProperty =  
    DependencyProperty.Register("ColumnBProperty", typeof(string),  
    typeof(ListData), new UIPropertyMetadata(null));  
   
   /// <summary>  
   /// Gets or sets the column A.  
   /// </summary>  
   /// <value>  
   /// The column A.  
   /// </value>  
   public string ColumnA  
   {  
    get { return (string)GetValue(ColumnAProperty); }  
    set { SetValue(ColumnAProperty, value); }  
   }  
   
   /// <summary>  
   /// Gets or sets the column B.  
   /// </summary>  
   /// <value>  
   /// The column B.  
   /// </value>  
   public string ColumnB  
   {  
    get { return (string)GetValue(ColumnBProperty); }  
    set { SetValue(ColumnBProperty, value); }  
   }  
  }  
   
  public partial class Window1 : Window  
  {  
   /// <summary>  
   /// Initializes a new instance of the <see cref="MainWindow"/> class.  
   /// </summary>  
   public Window1()  
   {  
    InitializeComponent();  
   
    // bind datacontext of the list view to a new listitems observable collection  
    this.WindowListView.DataContext = new ListItems();  
   }  
  }  
 }  
   
Oh, and if you're wondering how I got it to format so nicely, I'm using this the following blogspot source code formatting tool.

No comments: