1

Introduction to the problem

I have a DataGrid that has a DataGridTemplateColumn, the template of which contains a ComboBox control. My problem is that when the displayed member of the selected item is too long to fit into the width of the ComboBox, then the width of the ComboBox does not expand to accommodate the width of the displayed memeber, as it does if the same ComboBox is not in the DataGrid.

Working Example

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="350" Width="525">
   <StackPanel>
      <DataGrid Height="150"
                Margin="0,4,0,0"
                CanUserAddRows="False"
                CanUserDeleteRows="False"
                CanUserReorderColumns="False"
                CanUserResizeColumns="False"
                HorizontalAlignment="Stretch"
                ColumnWidth="SizeToCells"
                HeadersVisibility="Column"
                AutoGenerateColumns="False"
                RowHeaderWidth="0"
                IsSynchronizedWithCurrentItem="True"
                ItemsSource="{Binding Path=Entities, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
                SelectedItem="{Binding Path=SelectedEntity}"
                SelectionUnit="FullRow"
                SelectionMode="Single">
         <DataGrid.Resources>
            <DataTemplate x:Key="TextBox_Template">
               <TextBox Text="{Binding Path=Text}" Margin="2,2,2,2"/>
            </DataTemplate>
            <DataTemplate x:Key="ComboBox_Template">
               <ComboBox Margin="2,2,2,2"
                         Width="Auto"
                         ItemsSource="{Binding Path=DataContext.AvailableActions,
                                               Mode=OneTime,
                                               RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
                         SelectedValue="{Binding Path=Action, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                         SelectedValuePath="Key"
                         DisplayMemberPath="Value"
                         IsEditable="False"
                         IsSynchronizedWithCurrentItem="False"/>
            </DataTemplate>
         </DataGrid.Resources>
         <DataGrid.Columns>
            <DataGridTemplateColumn Width="*"
                                    CanUserReorder="False" CanUserResize="False"
                                    CellTemplate="{StaticResource TextBox_Template}"
                                    Header="Text Field"/>
            <DataGridTemplateColumn Width="Auto"
                                    CanUserReorder="False" CanUserResize="False"
                                    CellTemplate="{StaticResource ComboBox_Template}"
                                    Header="Action"/>
         </DataGrid.Columns>
      </DataGrid>
      <Separator Margin="0,5,0,5"/>
      <StackPanel Orientation="Horizontal">
         <Button Content="Add Row" Margin="2,2,2,2"
                 Command="{Binding AddRowCommand}"/>
         <Button Content="Remove Row" Margin="2,2,2,2"
                 Command="{Binding RemoveRowCommand}"/>
      </StackPanel>
      <Separator Margin="0,5,0,5"/>
      <ComboBox Width="Auto"
                ItemsSource="{Binding Path=AvailableActions}"
                SelectedValuePath="Key"
                DisplayMemberPath="Value"
                IsEditable="False"
                HorizontalAlignment="Left"/>
   </StackPanel>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;

namespace WpfApplication1
{
   /// <summary>
   /// Interaction logic for MainWindow.xaml
   /// </summary>
   public partial class MainWindow : Window, INotifyPropertyChanged
   {
      public event PropertyChangedEventHandler PropertyChanged;

      public static Dictionary<ActionType, string> AvailableActions { get; set; }

      public EntityClass SelectedEntity { get; set; }

      public ObservableCollection<EntityClass> Entities { get; set; }

      public AddRowCommandClass AddRowCommand { get; set; }

      public RemoveRowCommandClass RemoveRowCommand { get; set; }

      static MainWindow()
      {
         AvailableActions = new Dictionary<ActionType, string>()
         {
            {ActionType.Accept,   "Accept the text"},
            {ActionType.Reject,   "Reject the text"},
            {ActionType.Refer,    "Refer the text"},
            {ActionType.Postpone, "Postpone the text"},
         };
      }

      public MainWindow()
      {
         Entities = new ObservableCollection<EntityClass>()
         {
            new EntityClass() { Text = "First Example Text",  Action = ActionType.Accept},
            new EntityClass() { Text = "Second Example Text", Action = ActionType.Reject},
            new EntityClass() { Text = "Third Example Text",  Action = ActionType.Refer},
         };
         AddRowCommand = new AddRowCommandClass(this);
         RemoveRowCommand = new RemoveRowCommandClass(this);

         InitializeComponent();
      }

      public enum ActionType
      {
         Accept,
         Reject,
         Refer,
         Postpone,
      }

      public class EntityClass
      {
         public string Text { get; set; }
         public ActionType Action { get; set; }
      }

      public class AddRowCommandClass : ICommand
      {
         public event EventHandler CanExecuteChanged;
         private MainWindow _window;

         public AddRowCommandClass(MainWindow window)
         {
            _window = window;
         }

         public bool CanExecute(object parameter)
         {
            return true;
         }

         public void Execute(object parameter)
         {
            _window.Entities.Add(new EntityClass() { Text = "Hello World!", Action = ActionType.Postpone });
         }
      }

      public class RemoveRowCommandClass : ICommand
      {
         public event EventHandler CanExecuteChanged;
         private MainWindow _window;

         public RemoveRowCommandClass(MainWindow window)
         {
            _window = window;
         }

         public bool CanExecute(object parameter)
         {
            return _window.SelectedEntity != null;
         }

         public void Execute(object parameter)
         {
            _window.Entities.Remove(_window.SelectedEntity);
            _window.SelectedEntity = null;
            _window.PropertyChanged?.Invoke(_window, new PropertyChangedEventArgs("SelectedEntity"));
         }
      }
   }
}

Steps to reproduce

  • First observe that the ComboBox at the bottom of the page changes size as the size of it's selected value changes.
  • Next add a new row to the DataGrid or edit the ComboBox in one of the already present rows to select the "Postpone the text" option; notice how the ComboBox doesn't change size to fit the larger text, leading to the text being clipped.
  • Finally resize the window to a larger size with one of the rows having selected "Postpone the text", and notice how the ComboBox now increases in width to accommodate the longer text without clipping.

Conclusion/Question

My question is how can I force the ComboBox controls to automatically accommodate the width of their selected item, and increase the width of the grid row if necessary to do so. I want it such that the selected text will never be clipped, even if this means modifying the width of particular columns in the DataGrid to accommodate.

  • Have you tried adding it to the DataGridTemplateColumn directly instead of creating a DataTemplate in the Resources? I had some problems with this before. – lightlike Aug 22 '17 at 06:46
  • @lightlike I'm afraid I don't follow what you mean, I thought the idea of a `DataGridTemplateColumn` was to apply a `DataTemplate` to a cell. I don't know how you would directly add a `ComboBox` to a column. – Jake Conkerton-Darby Aug 22 '17 at 07:37
  • Try looking at [WPF contents of cell of DataGridTemplateColumn](https://stackoverflow.com/questions/6840344/wpf-contents-of-cell-of-datagridtemplatecolumn). You have more possibilities if you do it like that. Will also add an answer for your Example. – lightlike Aug 22 '17 at 09:38

4 Answers4

0

I would suggest setting the width of the second column from "auto" to "*", just like you did in the first column. And then setting a MaxWidth so the combobox wont get to big when sizing.

<DataGridTemplateColumn Width="*" MaxWidth="200"
                                CanUserReorder="False" CanUserResize="False"
                                CellTemplate="{StaticResource ComboBox_Template}"
                                Header="Action"/>
Thomas V
  • 151
  • 14
  • Interesting suggestion, unfortunately this will raise two issues that I would like to avoid: First I don't know how long the strings are (the actual strings may be under translation so have indeterminate length) which means that any max width may be too small; Second I'm not sure that `*` sizing is the correct sizing for this problem as this will give too much space to the `ComboBox`, taking it away from other columns, resulting in wasted space. – Jake Conkerton-Darby Aug 09 '17 at 13:36
  • Hmm oke tricky, then i would suggest finding a way to change the width of combobox based on width of its widest element. Have a look at these answers: https://stackoverflow.com/questions/1034505/how-can-i-make-a-wpf-combo-box-have-the-width-of-its-widest-element-in-xaml – Thomas V Aug 09 '17 at 14:04
0

The problem is reproducable with a lot simpler datagrid: just place a star-sized column in front of another column that is not star-sized. The Layout process will not try to shrink the columns to the left in order to give columns to the right more space as long as the grid size as a whole stays the same.

The following solution is based on https://stackoverflow.com/a/5651287/5265292

Establish some event that indicates a possibly necessary column resize, then remove the star size column widths, update the layout, restore the column widths. In my example, I used the SelectionChanged event and need to dispatch the layout update for it to work:

void dg1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (e.OriginalSource is ComboBox)
    {
        Dispatcher.BeginInvoke(new Action(() =>
            {
                dg1.Columns[0].Width = new DataGridLength();
                dg1.UpdateLayout();
                dg1.Columns[0].Width = new DataGridLength(1, DataGridLengthUnitType.Star);
            }));
    }
}

My sample XAML with items sources in code behind:

<Grid x:Name="grid1">
    <Grid.Resources>
        <CollectionViewSource x:Key="ComboBoxItemsSource" Source="{Binding ComboItems}"/>
    </Grid.Resources>
    <DataGrid
        x:Name="dg1"
        ItemsSource="{Binding DataItems}"
        AutoGenerateColumns="False"
        SelectionChanged="dg1_SelectionChanged">
        <DataGrid.Columns>
            <DataGridTextColumn
                Width="*"/>
            <DataGridComboBoxColumn
                Header="B"
                ItemsSource="{Binding Source={StaticResource ComboBoxItemsSource}}"
                SelectedItemBinding="{Binding Text}"/>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

For sake of completeness, the initialization and viewmodels:

public MainWindow()
{
    InitializeComponent();
    grid1.DataContext = vm = new MyViewModel()
    {
        DataItems =
        {
            new ItemVM(),
        },
        ComboItems =
        {
            "A",
            "AAAAAAAAAAAAAAAAAAAAAAA"
        }
    };
}


public class MyViewModel
{
    private ObservableCollection<ItemVM> _DataItems = new ObservableCollection<ItemVM>();
    public ObservableCollection<ItemVM> DataItems
    {
        get { return _DataItems; }
    }

    private ObservableCollection<string> _ComboItems = new ObservableCollection<string>();
    public ObservableCollection<string> ComboItems
    {
        get { return _ComboItems; }
    }
}


public class ItemVM : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChangedEvent([CallerMemberName]string prop = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(prop));
    }

    private string _Text;
    public string Text
    {
        get { return _Text; }
        set { _Text = value; RaisePropertyChangedEvent(); }
    }
}
grek40
  • 13,113
  • 1
  • 24
  • 50
0

Try by adding the DataTemplate directly to the 'DataGridTemplateColumn'.

I would do something like this:

<DataGridTemplateColumn Width="Auto"
                        CanUserReorder="False" CanUserResize="False"
                        Header="Action">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
           <ComboBox Margin="2 2 2 2"
                     ItemsSource="{Binding Path=DataContext.AvailableActions,
                                           Mode=OneTime,
                                           RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
                     SelectedValue="{Binding Path=Action, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                     SelectedValuePath="Key"
                     DisplayMemberPath="Value"
                     IsEditable="False"
                     IsSynchronizedWithCurrentItem="False"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
lightlike
  • 152
  • 2
  • 17
  • As mentioned in my answer, the essential part is you need a star-sized column in front of the column that is required to resize in order to reproduce the problem. – grek40 Aug 23 '17 at 06:31
  • @grek40 I had a `*`-Column before an `Auto`-Column before and never ran into this problem, but I wouldn't say that this is the definite answer, it just worked for me. – lightlike Aug 23 '17 at 06:58
  • Tested, and unfortunately this does not solve my problem on its own. – Jake Conkerton-Darby Aug 24 '17 at 08:34
0

Have you tried HorizontalAlignment and/or HorizontalContentAlignment to Stretch on the combobox?

davidlbaileysr
  • 644
  • 6
  • 6