WPF DataGrid: CellEditingTemplate ComboBox multiple data to CellTemplate TextBox

4.1k views Asked by At

I have googled around but with very limited luck. I have a question regarding editable WPF DataGrid; in a CellEditingTemplate a ComboBox is shown, but in CellTemplate a TextBox with corresponding ComboBox value is shown. My code looks something like this:

<DataGridTemplateColumn Header="Unit">
  <DataGridTemplateColumn.CellEditingTemplate>
    <DataTemplate>
      <ComboBox Name="comboBoxUnit" ItemsSource="{Binding ...}" SelectedValue="{Binding UnitId, ValidatesOnDataErrors=True}" SelectedValuePath="Id">
        <ComboBox.ItemTemplate>
          <DataTemplate>
            <StackPanel Orientation="Horizontal">
              <TextBlock Text="{Binding Id}" />
              <TextBlock Text=" " />
              <TextBlock Text="{Binding Name}" />
            </StackPanel>
          </DataTemplate>
        </ComboBox.ItemTemplate>
      </ComboBox>
    </DataTemplate>
  </DataGridTemplateColumn.CellEditingTemplate>
  <DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
      <TextBlock Text="<would like to have selected Unit's Id and Name here>"  />
    </DataTemplate>                                    
  </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

How can I achieve this? Separate property in a class (to have UnitId and UnitName properties) is not a problem, I can add it, but how to bind both to ComboBox then? Can I access CellEditingTemplate ComboBox in CellTemplate? It seems like they are in "different namespaces" since I can name controls in both with same names...

Any ideas, pointers? Thanks in advance, DB

1

There are 1 answers

3
Fredrik Hedblad On BEST ANSWER

The easiest way to achieve the same thing is to use a DataGridComboBoxColumn.

However, in my current project we had so many problems with the DataGridComboBoxColumn that we don't use it anymore. Instead we use a DataGridTemplateColumn with a ComboBox in the CellEditingTemplate and a TextBlock in the CellTemplate (just like you're doing).

To be able to display data based on an Id (to get the same functionality in the TextBlock as in the ComboBox) we use a converter called CodeToDescriptionConverter. It's usable like this

<TextBlock>
    <TextBlock.Text>
        <MultiBinding>
            <MultiBinding.Converter>
                <con:CodeToDescriptionConverter CodeAttribute="Id"
                                                StringFormat="{}{0} - {1}">
                    <con:CodeToDescriptionConverter.DescriptionAttributes>
                        <sys:String>Id</sys:String>
                        <sys:String>Name</sys:String>
                    </con:CodeToDescriptionConverter.DescriptionAttributes>
                </con:CodeToDescriptionConverter>
            </MultiBinding.Converter>
            <Binding Path="UnitId"/>
            <Binding Path="Units"/>
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>
  • The first Binding is the value we look for (Id)
  • The second Binding is the IList we look in
  • CodeAttribute is the name of the property we want to compare to the id (first Binding)
  • DescriptionAttributes are the properties we want to return formatted as StringFormat

And in your case: Find the instance in Units where the property Id has the same value as UnitId and for this instance return the values of Id and Name formatted as {0} - {1}

CodeToDescriptionConverter uses reflection to achieve this

public class CodeToDescriptionConverter : IMultiValueConverter
{
    public string CodeAttribute { get; set; }
    public string StringFormat { get; set; }
    public List<string> DescriptionAttributes { get; set; }

    public CodeToDescriptionConverter()
    {
        StringFormat = "{0}";
        DescriptionAttributes = new List<string>();
    }

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values.Length != 2 ||
            values[0] == DependencyProperty.UnsetValue ||
            values[1] == DependencyProperty.UnsetValue ||
            values[0] == null ||
            values[1] == null)
        {
            return null;
        }

        string code = values[0].ToString();
        IList sourceCollection = values[values.Length - 1] as IList;
        object[] returnDescriptions = new object[DescriptionAttributes.Count];
        foreach (object obj in sourceCollection)
        {
            PropertyInfo codePropertyInfo = obj.GetType().GetProperty(CodeAttribute);
            if (codePropertyInfo == null)
            {
                throw new ArgumentException("Code Property " + CodeAttribute + " not found");
            }
            string codeValue = codePropertyInfo.GetValue(obj, null).ToString();
            if (code == codeValue)
            {
                for (int i = 0; i < DescriptionAttributes.Count; i++)
                {
                    string descriptionAttribute = DescriptionAttributes[i];
                    PropertyInfo descriptionPropertyInfo = obj.GetType().GetProperty(descriptionAttribute);
                    if (descriptionPropertyInfo == null)
                    {
                        throw new ArgumentException("Description Property " + descriptionAttribute + " not found");
                    }
                    object descriptionObject = descriptionPropertyInfo.GetValue(obj, null);
                    string description = "";
                    if (descriptionObject != null)
                    {
                        description = descriptionPropertyInfo.GetValue(obj, null).ToString();
                    }
                    returnDescriptions[i] = description;
                }
                break;
            }
        }

        // Ex. string.Format("{0} - {1} - {2}", arg1, arg2, arg3);
        return string.Format(StringFormat, returnDescriptions);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

I uploaded a sample application here: CodeToDescriptionSample.zip.
It includeds a DataGridTemplateColumn with CodeToDescriptionConverter and a DataGridComboBoxColumn that does the same thing. Hope this helps