1

I am sorry I don't know how to explain this any better, but I want to wrap columns in a datagrid.

I am not trying to wrap the text inside the datagrid columns, but the actual columns of the datagrid.

I have searched the net and I have looked through WPF 4.5 by Adam Nathan and most likely due to my lack of ability to properly describe what I want to do , the searches only ever come up with text wrapping. Honestly , I don't the faintest idea how to even approach this.

This is the XAML I have for the datagrid

<DataGrid Name="InfoCountTextBlock" ColumnWidth="100" Grid.Row="3" Grid.ColumnSpan="4" Margin="5" GridLinesVisibility="None" AlternationCount="2">
                    <DataGrid.RowStyle>
                        <Style TargetType="DataGridRow">
                            <Setter Property="Background" Value="White"/>
                            <Setter Property="FontWeight" Value="Normal"/>
                            <Style.Triggers>
                                <Trigger Property="AlternationIndex" Value="1">
                                    <Setter Property="Background" Value="#ddebf7"/>
                                </Trigger>
                                <Trigger Property="IsMouseOver" Value="True">
                                    <Setter Property="Background" Value="#338fff"/>
                                    <Setter Property="Foreground" Value="White"/>
                                </Trigger>
                            </Style.Triggers>
                        </Style>
                    </DataGrid.RowStyle>
                </DataGrid>

And this is the code behind,

foreach (string infoOption in InfoOptions)
            {
                infoCount.Add(
                    new InfoOption {
                        Info = infoOption,
                        Instances = filteredSelection.Count(staff => staff.Info_Data == infoOption)
                    }
                );
            }

            InfoCountTextBlock.ItemsSource = infoCount;

This is to illustrate what I am trying to achieve. As it is right now.

enter image description here

And this is is the out come I am trying achieve, I was hoping to keep the data in one datagrid as I probably will eventually connecting this data to a DataBase. So arbitrarily, when the data goes over 5 rows, it is then moved to the to the next block of columns and so forth.

enter image description here

KyloRen
  • 2,691
  • 5
  • 29
  • 59
  • What is the basis of the data, and why 4 pairs of columns (1&2). With more context, maybe a better alternative may be available. You could always create a sort of pivot table, but would need more info. – DRapp Jun 11 '16 at 02:50
  • @DRapp no reason for the four pairs of columns that was just to show what that block of data should look like. I have no constraints , only that say when the data goes over 5 rows that it then moves the data to the next pair of columns. – KyloRen Jun 11 '16 at 03:32
  • @DRapp, looking at pivot tables, but that looks beyond me as well. – KyloRen Jun 11 '16 at 05:25
  • 1
    I'm in a hurry right now, but I'll leave u some clues and maybe you could find your way till I return. OK. Firstly, you don't need a `DataGrid` here. Use `ItemsControl` instead. Inside of it, use `UniformGrid` as its Panel. Use `ItemContainerStyle` together with a Converter to decide the `Row` and `Column` of each value. Use another Converter to decide the total number of rows and columns of your `UniformGrid`. Don't know if you have enough WPF skill to do all of that, but just in case. – dotNET Jun 11 '16 at 13:40
  • @dotNET, I will give that a try. I have never used `UniforGrid`, but I will give it a try. Thanks for the help. – KyloRen Jun 12 '16 at 00:09
  • @dotNET, Just working on `UniformGrid` now ,but I am having difficulty. Do I put another datagrid inside the uniform grid? There is not much on `UniformGrid` in Adam Nathan's book on WPF 4.5. Sorry ,this to be above me as I am just starting out with WPF. – KyloRen Jun 12 '16 at 04:00

1 Answers1

1

Right. Now that I had some time, I achieved it. Here are the steps:

Firstly, I'm going to define sample data type and some sample data to help visualize things right inside the designer, instead of having to run it every time. This step is not necessary for you since you already have your data types defined. Just take care of one thing though. Our control needs access to a property named RowNumber to decide the row/col of each element. This should simply be an int type property showing the row index of the element within the collection.

public class FiveRowData
{
  public int RowNumber { get; set; }
  public string Column1 { get; set; }
  public string Column2 { get; set; }
}

public class FiveRowList : List<FiveRowData>
{
}

Next, add a sample xml data file (Add New Item > Resource Dictionary) to the project. This too is not necessary and is here just for design-time support. Paste the following sample data into the file:

<YourProject:FiveRowList xmlns:YourProject="clr-namespace:YourProject" Capacity="37">
  <YourProject:FiveRowData RowNumber="1" Column1="Content 1" Column2="Item 1" />
  <YourProject:FiveRowData RowNumber="2" Column1="Content 2" Column2="Item 2" />
  <YourProject:FiveRowData RowNumber="3" Column1="Content 3" Column2="Item 3" />
  <YourProject:FiveRowData RowNumber="4" Column1="Content 4" Column2="Item 4" />
  <YourProject:FiveRowData RowNumber="5" Column1="Content 5" Column2="Item 5" />
  <YourProject:FiveRowData RowNumber="6" Column1="Content 6" Column2="Item 6" />
  <YourProject:FiveRowData RowNumber="7" Column1="Content 7" Column2="Item 7" />
  <YourProject:FiveRowData RowNumber="8" Column1="Content 8" Column2="Item 8" />
  <YourProject:FiveRowData RowNumber="9" Column1="Content 9" Column2="Item 9" />
  <YourProject:FiveRowData RowNumber="10" Column1="Content 10" Column2="Item 10" />
  <YourProject:FiveRowData RowNumber="11" Column1="Content 11" Column2="Item 11" />
  <YourProject:FiveRowData RowNumber="12" Column1="Content 12" Column2="Item 12" />
  <YourProject:FiveRowData RowNumber="13" Column1="Content 13" Column2="Item 13" />
  <YourProject:FiveRowData RowNumber="14" Column1="Content 14" Column2="Item 14" />
  <YourProject:FiveRowData RowNumber="15" Column1="Content 15" Column2="Item 15" />
  <YourProject:FiveRowData RowNumber="16" Column1="Content 16" Column2="Item 16" />
  <YourProject:FiveRowData RowNumber="17" Column1="Content 17" Column2="Item 17" />
</YourProject:FiveRowList>

Now actual work. Add a new UserControl to your WPF project. Name it FiveRowWrapper (or whatever you wish, but then take care of renaming things in the following code). Go to XAML and replace existing code with the following:

<ItemsControl x:Class="FiveRowWrapper" 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
  xmlns:local="clr-namespace:YourProject"
  xmlns:syswin="clr-namespace:System.Windows;assembly=PresentationFramework"
  mc:Ignorable="d" 
  d:DesignHeight="180" d:DesignWidth="600" 
  d:DataContext="{d:DesignData Source=FiveRowListSampleData.xaml}"
  ItemsSource="{Binding}">

  <ItemsControl.Resources>
    <local:ItemsCountToColCountConverter x:Key="ItemsCountToColCountConverter" />
    <local:FiveRowToRowNumberConverter x:Key="FiveRowToRowNumberConverter" />
    <local:FiveRowToColNumberConverter x:Key="FiveRowToColNumberConverter" />
  </ItemsControl.Resources>

  <ItemsControl.ItemTemplate>
    <DataTemplate DataType="local:FiveRowData">
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding Column1}" Margin="10" />
        <TextBlock Text="{Binding Column2}" Margin="10" />
      </StackPanel>
    </DataTemplate>
  </ItemsControl.ItemTemplate>

  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <Grid local:GridHelpers.RowCount="5" 
        local:GridHelpers.ColumnCount="{Binding Count, Converter={StaticResource ItemsCountToColCountConverter}}" />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>

  <ItemsControl.ItemContainerStyle>
    <Style TargetType="{x:Type ContentPresenter}">
      <Setter Property="Grid.Row" Value="{Binding RowNumber, Converter={StaticResource FiveRowToRowNumberConverter}}" />
      <Setter Property="Grid.Column" Value="{Binding RowNumber, Converter={StaticResource FiveRowToColNumberConverter}}" />
    </Style>
  </ItemsControl.ItemContainerStyle>
</ItemsControl>

A few points to elaborate. We are using a custom ItemsControl that uses Grid as its panel. This will serve as the overall container of our individual items. Those items in turn will use the ItemTemplate defined above containing a StackPanel with two TextBlocks to show the properties of each item. For my sample class, I have used Column1 and Column2 properties, but you can use your properties or even customize the appearance of individual items in any way you want by defining your own ItemTemplate here. The ItemContainerStyle property at the bottom is responsible for binding Grid.Row and Grid.Column properties of each individual item and thus placing the item into appropriate cell within the main Grid.

There are two other things in the XAML code above that need explanation. Firstly, we need to decide the number of columns in our Grid, which can simply be computed by dividing the number of items by 5. The problem however is that due to Grid's design, we cannot bind the number of columns to a property and will need to handle indirectly through attached properties.

Add a new class to your project and name it GridHelpers (thanks to Rachel's helpful answer). Replace the contents of this file with the following:

public class GridHelpers
{
  public static readonly DependencyProperty ColumnCountProperty = DependencyProperty.RegisterAttached("ColumnCount", typeof(int), typeof(GridHelpers), new PropertyMetadata(-1, ColumnCountChanged));
  public static int GetColumnCount(DependencyObject obj)
  {
    return obj.GetValue(ColumnCountProperty);
  }

  public static void SetColumnCount(DependencyObject obj, int value)
  {
    obj.SetValue(ColumnCountProperty, value);
  }

  public static void ColumnCountChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  {
    if (!(obj is Grid) || (int)e.NewValue < 0)
      return;

    Grid grid = (Grid)obj;
    grid.ColumnDefinitions.Clear();

    for (int i = 0; i <= (int)e.NewValue - 1; i++) {
      grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
    }
  }
}

As you see, this class adds an attached property to Grid using which we can define the number of columns dynamically. I'm using GridLength.Auto here, but in case you want equal-sized columns, you can use fixed-size columns too.

Lastly we need the following 3 converters:

  1. Number of elements to number of columns (divide number of elements by 5, take ceiling)
  2. RowNumber to Row (modulo 5)
  3. RowNumber to Column (divide by 5, take floor)

Here's the code for the 3 converters. Define them anywhere in your project:

public class ItemsCountToColCountConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    return (int)Math.Ceiling((int)value / 5f);
  }

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

public class FiveRowToRowNumberConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    return ((int)value - 1) % 5f;
  }

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

public class FiveRowToColNumberConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    return (int)Math.Floor(((int)value - 1) / 5f);
  }

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

The final output of the above effort looks like this in my WPF Designer:

enter image description here

Lastly, the control will expand horizontally as you add more items to your collection. You should place this control inside a ScrollViewer to handle that situation.

I leave background coloring and other smaller activities as exercise for you.

Community
  • 1
  • 1
dotNET
  • 33,414
  • 24
  • 162
  • 251