Saleem
Saleem

Reputation: 145

How to change the order of data from properties displayed in a WPF DataGrid from a base class and derived class?

I would like to display data (Properties) from base class followed by data from a derived class in the columns of a DataGrid. Data displays OK, but data from Derived class is displayed first followed by the data from the derived class. How can I change the order of data so that Data first comes from base class and then from derived class.

    abstract class BaseNumbers
    {
        public int Count { get; set; }
        public double B1 { get; set; }
        public double B2 { get; set; }
        public double B3 { get; set; }


        protected BaseNumbers (int c, double b1, double b2, double b3)
        {
            Count = c;
            B1 = b1;
            B2 = b2;
            B3 = b3;
        } // end ctor

        public override string ToString() => $"{B1:F},{B2:F},{B3:F}";
   class DerivedNumbers : BaseNumbers
    {
        public double D1 { get; set; }
        public double D2 { get; set; }
        public double D3 { get; set; }

        public DerivedNumbers(int cnt, double b1, double b2, double b3,
            double d1, double d2, double d3)
            : base(cnt, b1, b2, b3)
        {
            D1 = d1;
            D2 = d2;
            D3 = d3;
        } // end ctor

        public override string ToString() => base.ToString() + $",{D1:F},{D2:F},{D3:F}";
    } 
<Window x:Class="OOPGridIssue.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:OOPGridIssue"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="275">

    <Window.Resources>
        <!-- Data Grid Style -->
        <Style x:Key="DataGridStyle" TargetType="DataGrid">
            <Setter Property="HorizontalAlignment" Value="Left" />
            <Setter Property="VerticalAlignment" Value="Top" />
            <Setter Property="AlternatingRowBackground" Value="LightGray" />
            <Setter Property="MinWidth" Value="200" />
            <Setter Property="MaxHeight" Value="440" />
            <Setter Property="Margin" Value="10,5,0,0" />
            <Setter Property="ScrollViewer.CanContentScroll" Value="True" />
            <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
            <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
        </Style>
    </Window.Resources>
    
    <Grid>
        <!-- Root level grid 6 rows x 4 col -->
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />

        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        
        <DataGrid x:Name="dg_Numbers" Grid.Row="0" Grid.Column="0"
                  Style="{StaticResource DataGridStyle}" />
        
        <DockPanel x:Name="dockPanelButtons" Grid.Row="1" VerticalAlignment="Top" Margin="0,10,10,10" Height="50">
            <Button x:Name="btnLoadNumbers" Content ="Load Numbers" FontWeight="Bold"
                    Margin="10" MaxHeight="25" Click="ButtonLoadNumbers_Click" />

            <Button x:Name="ButtonClearNumbers" Content ="Clear Numbers"
                    Margin="0,10,10,10" MaxHeight="25" Click="ButtonClearNumbers_Click" />
        </DockPanel>
    </Grid>
</Window>
using System.Collections.Generic;
using System.Windows;

namespace OOPGridIssue
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        readonly List<DerivedNumbers> numbers = new List<DerivedNumbers>()
        {
            new DerivedNumbers(1,1.0, 2.22, 3, 4.0, 5.555, 6),
            new DerivedNumbers(2,1.0, 2.22, 3, 4.0, 5.555, 6),
            new DerivedNumbers(3,1.0, 2.22, 3, 4.0, 5.555, 6)
        };
        public MainWindow()
        {
            InitializeComponent();
        }

        private void ButtonClearNumbers_Click(object sender, RoutedEventArgs e)
        {
            dg_Numbers.ItemsSource = null;
            dg_Numbers.Items.Refresh();

        }

        private void ButtonLoadNumbers_Click(object sender, RoutedEventArgs e)
        {
            dg_Numbers.ItemsSource = numbers;
            dg_Numbers.Items.Refresh();
        }
    }
}

Upvotes: 1

Views: 428

Answers (1)

Seb
Seb

Reputation: 660

I can propose you two solutions:

Solution #1 Make use of the DataGrid's AutoGenerated event

Firstly, we create an interface

public interface IDerivedNumbers
{
    int Count { get; set; }
    double B1 { get; set; }
    double B2 { get; set; }
    double B3 { get; set; }

    double D1 { get; set; }
    double D2 { get; set; }
    double D3 { get; set; }

    string ToString();
}

Which we will apply it to the DerivedNumbers class

class DerivedNumbers : BaseNumbers, IDerivedNumbers
{
    ...
}

Now, lets add our event to the DataGrid

<DataGrid x:Name="dg_Numbers" 
          Grid.Row="0" 
          Grid.Column="0"
          Style="{StaticResource DataGridStyle}"
          AutoGeneratedColumns="dg_Numbers_AutoGeneratedColumns" />

..and code behind

private void dg_Numbers_AutoGeneratedColumns(object sender, EventArgs e)
{
    var names = typeof(IDerivedNumbers).GetProperties()
                                       .Select ( (x, index) => new { name = x.Name, index } );

    if ( sender is DataGrid datagrid )
    {
        foreach ( var column in datagrid.Columns )
        {
            column.DisplayIndex = names.Single ( x => x.name == (string)column.Header ).index;
        }
    }
}  

Solution #2 Hide your numbers classes behind an interface. The trick is your concrete classes must not be visible by the Datagrid, which means you'll need to move them in a separate assembly.

Firstly, lets move BaseNumbers & DerivedNumbers in another assembly and keep them internal.

Then Create a new public interface in that assembly

public interface IDerivedNumbers
{
    int Count { get; set; }
    double B1 { get; set; }
    double B2 { get; set; }
    double B3 { get; set; }

    double D1 { get; set; }
    double D2 { get; set; }
    double D3 { get; set; }

    string ToString();
}

Apply it to the DerivedNumbers class

class DerivedNumbers : BaseNumbers, IDerivedNumbers
{
    ...
}

Next, we need to create a public factory

public static class NumbersFactory
{

    public static IDerivedNumbers Create ( int cnt, 
                                           double b1, 
                                           double b2, 
                                           double b3,
                                           double d1, 
                                           double d2, 
                                           double d3 )
    {
        new DerivedNumbers(cnt, b1, b2, b3, d1, d2, d3);
    }
}

Finally link your new assembly and use the factory to create the numbers

public partial class MainWindow : Window
{
    readonly List<IDerivedNumbers> numbers = new List<IDerivedNumbers>()
    {
        NumbersFactory.Create(1,1.0, 2.22, 3, 4.0, 5.555, 6),
        NumbersFactory.Create(2,1.0, 2.22, 3, 4.0, 5.555, 6),
        NumbersFactory.Create(3,1.0, 2.22, 3, 4.0, 5.555, 6)
    };

    ....
}

Now the Datagrid will only pick up the interface and auto generate the columns in the right order.

Both methods will generate this result:

datagrid with ordered columns

PS. Thank you for providing a working example by the way !

Upvotes: 1

Related Questions