2010-12-06 38 views
1

Dans mon fichier App.xaml, j'ai le code suivant pour autoriser les en-têtes de colonnes "à deux étages" dans DataGrid s.WPF - DataGrid - Logique de redimensionnement automatique des colonnes

XAML:

<adv:ColumnHeaderFontSizeToMaxHeightConverter x:Key="columnHeaderFontSizeToMaxHeightConverter" /> 
<DataTemplate x:Key="WrappingDataGridColumnHeaderTemplate" DataType="{x:Type sys:String}"> 
    <TextBlock TextWrapping="WrapWithOverflow" 
       Text="{Binding}" 
       ToolTip="{Binding}" 
       MaxHeight="{Binding Path=FontSize, Mode=OneWay, 
            RelativeSource={RelativeSource Self}, 
            Converter={StaticResource columnHeaderFontSizeToMaxHeightConverter} }" /> 
</DataTemplate> 

Converter:

internal class ColumnHeaderFontSizeToMaxHeightConverter : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, 
     CultureInfo culture) 
    { 
     Debug.Assert(value.GetType() == typeof(double)); 

     // We want to have up to 2 lines of text here plus a little bit of space for margins, etc 
     // WPF will automatically use the smallest height required 
     return (double)value * 2.9; 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, 
     CultureInfo culture) 
    { 
     Debug.Assert(false); 
     throw new NotImplementedException(); 
    } 
} 

Cependant, le redimensionnement automatique des colonnes que la classe DataGrid fournit en double-cliquant sur les séparateurs de colonnes ne prend pas en compte le fait que les en-têtes peuvent devenez encore plus petit avec l'emballage de mots.

Par exemple, supposons que vous avez une colonne avec une longue-tête et courtes valeurs comme ceci:

| Long Header  | 
------------------- 
| A    | 
| B    | 
| C    | 

Un double-clic sur le séparateur se traduira par ceci:

| Long Header | 
--------------- 
| A   | 
| B   | 
| C   | 

Mais ce qui serait encore mieux est si elle se traduirait par ceci:

| Long | 
| Header | 
---------- 
| A  | 
| B  | 
| C  | 

Ma question est la suivante:

Existe-t-il un moyen de fournir un "indice" au redimensionnement automatique pour lui faire savoir qu'il peut aller encore plus petit? Ou devrais-je complètement réimplémenter la logique de redimensionnement automatique sur double clic?

EDIT: Avec l'aide de Malaek, j'ai mis à jour mon code comme suit, mais un problème persiste. Il ne joue pas bien avec mon MaxHeight et 3 mots ou plus. Le premier double clic a une partie cachée de l'en-tête, mais il réapparaît au second double clic même si la colonne ne change pas de largeur. Je posterai le code que j'ai utilisé sous peu. Code

<DataGrid.Resources> 
    ... 
    <Style TargetType="{x:Type DataGridColumnHeader}" 
      BasedOn="{StaticResource {x:Type DataGridColumnHeader}}"> 
     <Setter Property="ContentTemplate" Value="{StaticResource WrappingDataGridColumnHeaderTemplate}" /> 
     <EventSetter Event="SizeChanged" Handler="DataGridColumnHeader_SizeChanged"/> 
     <EventSetter Event="Loaded" Handler="DataGridColumnHeader_Loaded" /> 
    </Style> 
    ... 
</DataGrid.Resources> 

DataGrid derrière::

DataGrid XAML

private void RightThumb_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e) 
{ 
    Thumb thumb = sender as Thumb; 
    DataGridColumnHeader dataGridColumnHeader = VisualTreeHelpers.GetVisualParent<DataGridColumnHeader>(thumb); 
    DataGridColumn column = dataGridColumnHeader.Column; 
    UpdateColumnForResize(column); 
} 

private void LeftThumb_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e) 
{ 
    Thumb thumb = sender as Thumb; 
    DataGridColumnHeader dataGridColumnHeader = VisualTreeHelpers.GetVisualParent<DataGridColumnHeader>(thumb); 
    DataGridColumn column = this.Columns.FirstOrDefault(
     c => c.DisplayIndex == dataGridColumnHeader.Column.DisplayIndex - 1); 
    UpdateColumnForResize(column); 
} 

private void UpdateColumnForResize(DataGridColumn column) 
{ 
    if (column != null) 
    { 
     column.Width = column.Width.DisplayValue; 
     string header = (string)column.Header; 

     if (header.Contains("\r\n")) 
      return; 

     int middle = header.Length/2; 
     int closestToMiddle = -1; 
     for (int i = 0; i < header.Length; ++i) 
     { 
      if (header[i] == ' ') 
      { 
       if (closestToMiddle == -1) 
        closestToMiddle = i; 
       else if (Math.Abs(i - middle) < Math.Abs(closestToMiddle - middle)) 
        closestToMiddle = i; 
      } 
     } 

     if (closestToMiddle != -1) 
     { 
      StringBuilder newHeader = new StringBuilder(header); 
      newHeader.Replace(" ", "\r\n", closestToMiddle, 1); 
      column.Header = newHeader.ToString(); 
     } 
    } 
} 

private void DataGridColumnHeader_SizeChanged(object sender, SizeChangedEventArgs e) 
{ 
    DataGridColumnHeader columnHeader = sender as DataGridColumnHeader; 
    DataGridColumn column = columnHeader.Column; 
    if (column != null && column.Header.ToString().IndexOf("\r\n") >= 0) 
    { 
     column.Header = column.Header.ToString().Replace("\r\n", " "); 
     column.Width = column.Width.DisplayValue; 
    } 
} 

private void DataGridColumnHeader_Loaded(object sender, EventArgs e) 
{ 
    DataGridColumnHeader columnHeader = sender as DataGridColumnHeader; 
    Thumb thumb = columnHeader.Template.FindName("PART_RightHeaderGripper", columnHeader) as Thumb; 

    if (thumb != null) 
     thumb.PreviewMouseDoubleClick += RightThumb_PreviewMouseDoubleClick; 

    thumb = columnHeader.Template.FindName("PART_LeftHeaderGripper", columnHeader) as Thumb; 

    if (thumb != null) 
     thumb.PreviewMouseDoubleClick += LeftThumb_PreviewMouseDoubleClick; 
} 

EDIT2: Apparemment, le problème est un peu plus subtile. L'en-tête "Dernière mise à jour sur" le déclenche, mais "Dernière mise à jour par" ne le déclenche pas. Je suis toujours en train d'enquêter.

Répondre

3

Je pense que vous devrez re-template DataGridColumnHeader afin d'accéder à l'événement de dimensionnement automatique. La seule chose qui se passe réellement dans l'événement auto-size est que DataGridColumn obtient sa largeur définie sur Auto, donc si nous pouvons prévisualiser cet événement, et remplacer chaque '' avec un linebreak '\ r \ n' nous pouvons l'obtenir pour envelopper le Texte pour nous. Ensuite, nous pouvons le changer dans l'événement SizeChanged. J'ai également ajouté un ContentTemplate pour DataGridColumnHeader lorsque j'essayais cela.

Bien sûr, cela ne fonctionnera que si vous n'avez pas de linebreak dans les ColumnHeaders pour commencer, sinon ça va être gâché mais j'espère que vous pouvez toujours l'utiliser d'une manière ou d'une autre.

<DataGrid ...> 
    <DataGrid.Resources> 
     <Style x:Key="ColumnHeaderGripperStyle" TargetType="{x:Type Thumb}"> 
      <Setter Property="Width" Value="8"/> 
      <Setter Property="Background" Value="Transparent"/> 
      <Setter Property="Cursor" Value="SizeWE"/> 
      <EventSetter Event="PreviewMouseDoubleClick" Handler="Thumb_PreviewMouseDoubleClick"/> 
      <Setter Property="Template"> 
       <Setter.Value> 
        <ControlTemplate TargetType="{x:Type Thumb}"> 
         <Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"/> 
        </ControlTemplate> 
       </Setter.Value> 
      </Setter> 
     </Style> 
     <Style TargetType="{x:Type DataGridColumnHeader}"> 
      <EventSetter Event="SizeChanged" Handler="DataGridColumnHeader_SizeChanged"/> 
      <Setter Property="VerticalContentAlignment" Value="Center"/> 
      <Setter Property="ContentTemplate"> 
       <Setter.Value> 
        <DataTemplate> 
         <TextBlock TextWrapping="WrapWithOverflow" Text="{Binding}"></TextBlock> 
        </DataTemplate> 
       </Setter.Value> 
      </Setter> 
      <Setter Property="Template"> 
       <Setter.Value> 
        <ControlTemplate TargetType="{x:Type DataGridColumnHeader}"> 
         <Grid> 
          <Microsoft_Windows_Themes:DataGridHeaderBorder BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" IsClickable="{TemplateBinding CanUserSort}" IsPressed="{TemplateBinding IsPressed}" IsHovered="{TemplateBinding IsMouseOver}" Padding="{TemplateBinding Padding}" SortDirection="{TemplateBinding SortDirection}" SeparatorBrush="{TemplateBinding SeparatorBrush}" SeparatorVisibility="{TemplateBinding SeparatorVisibility}"> 
           <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> 
          </Microsoft_Windows_Themes:DataGridHeaderBorder> 
          <Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" Style="{StaticResource ColumnHeaderGripperStyle}"/> 
          <Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right" Style="{StaticResource ColumnHeaderGripperStyle}"/> 
         </Grid> 
        </ControlTemplate> 
       </Setter.Value> 
      </Setter> 
     </Style> 
    </DataGrid.Resources> 
    <!-- ... --> 
</DataGrid> 

Code derrière EventHandlers. Passez à \ r \ n dans l'aperçu et revenez à '' dans SizeChanged.

void Thumb_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e) 
{ 
    Thumb thumb = sender as Thumb; 
    DataGridColumnHeader dataGridColumnHeader = VisualTreeHelpers.GetVisualParent<DataGridColumnHeader>(thumb); 
    DataGridColumn column = dataGridColumnHeader.Column; 
    if (column != null) 
    { 
     column.Width = column.Width.DisplayValue; 
     column.Header = column.Header.ToString().Replace(" ", "\r\n"); 
    } 
} 
void DataGridColumnHeader_SizeChanged(object sender, SizeChangedEventArgs e) 
{ 
    DataGridColumnHeader columnHeader = sender as DataGridColumnHeader; 
    DataGridColumn column = columnHeader.Column; 
    if (column != null && column.Header.ToString().IndexOf("\r\n") >= 0) 
    { 
     column.Header = column.Header.ToString().Replace("\r\n", " "); 
     column.Width = column.Width.DisplayValue; 
    } 
} 

Une mise en œuvre de GetVisualParent

public static T GetVisualParent<T>(object childObject) where T : Visual 
{ 
    DependencyObject child = childObject as DependencyObject; 
    // iteratively traverse the visual tree 
    while ((child != null) && !(child is T)) 
    { 
     child = VisualTreeHelper.GetParent(child); 
    } 
    return child as T; 
} 
+1

Je ne sais pas où vous avez trouvé la méthode VisualTreeHelpers.GetVisualParent () à partir, mais je suppose que c'est quelque chose comme celui qui est décrit ici? http://stackoverflow.com/questions/4059626/how-do-i-change-wpf-menus-icon-column-size/4063239#4063239 – Ashley

+1

@Ashley: Ah, désolé! J'ai oublié d'ajouter celui-là, venant tout droit –

+1

Hrm, eh bien, il semble que de travailler seulement une partie du temps. Il semble que parfois, lorsque vous double-cliquez sur le «Thumb», il est en fait parfois le «pouce» gauche de la colonne sur le côté droit de la colonne que vous essayez de redimensionner. Il semble qu'il devrait y avoir un événement différent pour 'PART_LeftHeaderGripper' qui saisit la colonne à gauche, puis fait les espaces pour la conversion sur ce point. Je travaille sur un correctif maintenant. – Ashley