Search code examples
c#wpfxamldata-bindingtransformation

Boolean binding, decide to rotate DataGrid or not


In my code I have a DataGrid that I want to rotate for certain cases. Basically "rotating" just means switching the view mode of the DataGrid (displaying columns horizontally or vertically).

The idea is to have a boolean variable coming from an interface. Depending on that, I want to activate/deactivate the transformed view. I think I will need like a converter or something..?

I just copy-pasted this code from another stackoverflow-question. I played a bit with it but I cannot answer questions to. I know, that following values HAVE TO be changed when rotating or not:

  • RotateTransform Angle="-90"
  • MatrixTransform Matrix="-1 0 0 1 0 0"
  • RotateTransform Angle="-90"
  • ScaleTransform ScaleX="1" ScaleY="-1"
  • RotateTransform Angle="-90"
  • ScaleTransform ScaleX="1" ScaleY="-1"

This is my XAML:

<DataGrid ItemsSource="{Binding Path=CurrentTable.DefaultView,IsAsync=True}"
          AutoGenerateColumns="True" 
          AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" SelectedItem="{Binding 
          SelectedItem}" IsReadOnly="False" 
          PreviewMouseRightButtonDown="DataGrid_PreviewMouseRightButtonDown" 
          PreviewMouseLeftButtonDown="DataGrid_PreviewMouseLeftButtonDown"  
          CellEditEnding="DataGrid_CellEditEnding" CanUserAddRows="False" 
          CanUserReorderColumns="False" 
          EnableColumnVirtualization="True" EnableRowVirtualization="True" 
          VirtualizingStackPanel.VirtualizationMode="Standard">

  <DataGrid.LayoutTransform>
    <TransformGroup>
      <RotateTransform Angle="90"/>
      <MatrixTransform Matrix="-1 0 0 1 0 0" />
    </TransformGroup>
  </DataGrid.LayoutTransform>
  <DataGrid.ColumnHeaderStyle>
    <Style TargetType="{x:Type DataGridColumnHeader}"
          BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
      <Setter Property="LayoutTransform">
        <Setter.Value>
          <TransformGroup>
            <RotateTransform Angle="-90"/>
            <ScaleTransform ScaleX="1" ScaleY="-1" />
          </TransformGroup>
        </Setter.Value>
      </Setter>
    </Style>
  </DataGrid.ColumnHeaderStyle>
  <DataGrid.CellStyle>
    <Style TargetType="DataGridCell">
      <Setter Property="LayoutTransform">
        <Setter.Value>
          <TransformGroup>
            <RotateTransform Angle="-90"/>
            <ScaleTransform ScaleX="1" ScaleY="-1" />
          </TransformGroup>
        </Setter.Value>
      </Setter>
    </Style>
  </DataGrid.CellStyle>
</DataGrid>

Could you help me? I really do not know, where to start and what to start with.


Edit 1: With big help of @thatguy I was able to rotate my DataGrid. I wonder how I can change the weird sizes of the cells after being rotated. An example of my code.

Before rotating:

the cells have normal size

After rotating:

the size of the cells is way too big


Solution

  • You do not need a converter, it can all be done in the style using triggers. Suppose you created a bool property named IsCertainCase in your view model which must implement the INotifyPropertyChanged interface to notify bindings to update their value, otherwise a changed value in the view model will not be reflected in the user interface.

    public class MyViewModel : INotifyPropertyChanged
    {
          private bool _isCertainCase;
       public bool IsCertainCase
       {
          get => _isCertainCase;
          set
          {
             if (_isCertainCase == value)
                return;
       
             _isCertainCase = value;
             OnPropertyChanged();
          }
       }
    
       public event PropertyChangedEventHandler PropertyChanged;
       
       protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
       {
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
       }
    
       // ...other code.
    }
    

    Then you can create a Style for the DataGrid. Note that it needs to extend the default style for DataGrid, otherwise its visual appearance or behavior might be different. That is what the BasedOn property is for.

    A DataTrigger is used to bind the IsCertainCase property and check whether it is True or not.

    Represents a trigger that applies property values or performs actions when the bound data meets a specified condition.

    In case it is, the LayoutTransform is applied. The other styles work just the same, with one major difference. Their DataContext is different than the DataContext of the DataGrid. Consequently, a binding to IsCertainCase would fail. In this case we use a RelativeSource which refers to a parent element of a certain type specified with AncestorType, here DataGrid. As path, we specify its DataContext and on that the property IsCertainCase. Now we bind the same data as on the DataGrid style.

    <DataGrid ItemsSource="{Binding Path=CurrentTable.DefaultView, IsAsync=True}" AutoGenerateColumns="True" AutoGeneratingColumn="DataGrid_AutoGeneratingColumn"   
              SelectedItem="{Binding SelectedItem}" IsReadOnly="False">
       <DataGrid.Style>
          <Style TargetType="{x:Type DataGrid}"
                 BasedOn="{StaticResource {x:Type DataGrid}}">
             <Style.Triggers>
                <DataTrigger Binding="{Binding IsCertainCase}" Value="True">
                   <Setter Property="LayoutTransform">
                      <Setter.Value>
                         <TransformGroup>
                            <RotateTransform Angle="90"/>
                            <MatrixTransform Matrix="-1 0 0 1 0 0" />
                         </TransformGroup>
                      </Setter.Value>
                   </Setter>
                </DataTrigger>
             </Style.Triggers>
          </Style>
       </DataGrid.Style>
       <DataGrid.ColumnHeaderStyle>
          <Style TargetType="{x:Type DataGridColumnHeader}"
                 BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
             <Style.Triggers>
                <DataTrigger Binding="{Binding DataContext.IsCertainCase, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" Value="True">
                   <Setter Property="LayoutTransform">
                      <Setter.Value>
                         <TransformGroup>
                            <RotateTransform Angle="-90"/>
                            <ScaleTransform ScaleX="1" ScaleY="-1" />
                         </TransformGroup>
                      </Setter.Value>
                   </Setter>
                </DataTrigger>
             </Style.Triggers>
          </Style>
       </DataGrid.ColumnHeaderStyle>
       <DataGrid.CellStyle>
          <Style TargetType="DataGridCell"
                 BasedOn="{StaticResource {x:Type DataGridCell}}">
             <Style.Triggers>
                <DataTrigger Binding="{Binding DataContext.IsCertainCase, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" Value="True">
                   <Setter Property="LayoutTransform">
                      <Setter.Value>
                         <TransformGroup>
                            <RotateTransform Angle="-90"/>
                            <ScaleTransform ScaleX="1" ScaleY="-1" />
                         </TransformGroup>
                      </Setter.Value>
                   </Setter>
                </DataTrigger>
             </Style.Triggers>
          </Style>
       </DataGrid.CellStyle>
    </DataGrid>
    

    Update For Your Edit

    The DataGrid is not entirely compatible with these transformations applied. Indeed you can do it and the columns and cells will be oriented correctly, but resizing still assumes that everything is oriented as before, meaning you the width and height are swapped and the column resize indicator will still show horizontally and behave this way, although visually everything is scaled vertically. Please be aware that there may be other side-effects of uninteded usage of the DataGrid.

    The reason for the odd cell scaling is simply that the headers were sized to their content and on layout transformation the sizes are not recalculated. Now that width and height are swapped, the row height equals the former column width. The following attached property provides a workaround by forcing recalculation of sizes through resetting the Width on all columns.

    public static class DataGridLayoutTransformProperties
    {
       public static bool GetIsHeaderSizeUpdateRequired(DependencyObject obj)
       {
          return (bool)obj.GetValue(IsHeaderSizeUpdateRequiredProperty);
       }
    
       public static void SetIsHeaderSizeUpdateRequired(DependencyObject obj, bool value)
       {
          obj.SetValue(IsHeaderSizeUpdateRequiredProperty, value);
       }
    
       public static readonly DependencyProperty IsHeaderSizeUpdateRequiredProperty = DependencyProperty.RegisterAttached(
          "IsHeaderSizeUpdateRequired", typeof(bool), typeof(DataGridLayoutTransformProperties),
          new PropertyMetadata(false, IsHeaderSizeUpdateRequired));
    
       private static void IsHeaderSizeUpdateRequired(DependencyObject d, DependencyPropertyChangedEventArgs e)
       {
          var dataGrid = (DataGrid)d;
          var isHeaderSizeUpdateRequired = (bool)e.NewValue;
          if (isHeaderSizeUpdateRequired)
          {
             foreach (var dataGridColumn in dataGrid.Columns)
             {
                dataGridColumn.Width = 0;
                dataGridColumn.Width = DataGridLength.Auto;
             }
          }
       }
    }
    

    Bind the attached property IsHeaderSizeUpdateRequired on the DataGrid to IsCertainCase.

    <DataGrid ... local:DataGridLayoutTransformProperties.IsHeaderSizeUpdateRequired="{Binding IsCertainCase}">