Search code examples
c#sqlwpfxamldatagrid

Create multiple DataGrids in WPF dynamically


I am trying to dynamically create DataGrids in WPF, one below the other in a Grid. the problem is that I don't even know where to start, I saw that it could be done through code but I would like to use XAML correctly.

void PopulateDatagridSQL(string Data, string index, string TabName)
{
   try
   {
      Data = Data.Replace((Char)6, (Char)124);

      List<DataTable> Results = new List<DataTable>();

      Results = JsonConvert.DeserializeObject<List<DataTable>>(Data);
      foreach (SQLWindow SingleSQLWindows in StaticVar.MySQLWindows)
      {
         if (SingleSQLWindows.MyINDEX == index)
         {
            SingleSQLWindows.Dispatcher.Invoke(new Action(() =>
            {
               foreach (TabItem item in SingleSQLWindows._tabItems)
               {
                  if (item.Name == TabName)
                  {
                     //create multiple datagrids up to results.count
                     ((SQLPage)((Frame)item.Content).Content).DataGrid1.ItemsSource = Results[0].DefaultView;//foreach
                     ((SQLPage)((Frame)item.Content).Content).TxtSqlLog.Text = "Records in Datagrid: " + Results[0].Rows.Count;//foreach
                  }
               }
            }));
         }
      }
   }
   catch (Exception asd)
   {

      foreach (SQLWindow SingleSQLWindows in StaticVar.MySQLWindows)
      {
         if (SingleSQLWindows.MyINDEX == index)
         {
            SingleSQLWindows.Dispatcher.Invoke(new Action(() =>
            {
               foreach (TabItem item in SingleSQLWindows._tabItems)
               {
                  if (item.Name == TabName)
                  {
                     ((SQLPage)((Frame)item.Content).Content).TxtSqlLog.Text = "Error in $SqlResponse";
                  }
               }
            }));
         }
      }
   }
}

In this function I receive a list of DataTables and for each DataTable I have to create a DataGrid.

<Page x:Class="Emergency_APP_Server_WPF.Forms.SQLPage"
  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:Emergency_APP_Server_WPF.Forms"
  mc:Ignorable="d" x:Name="SQLPageXaml" Loaded="SQLPage_Loaded"
  d:DesignHeight="450" d:DesignWidth="800"
  Title="SQLPage" >

<Grid>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" MinHeight="100"></RowDefinition>
            <RowDefinition Height="0"></RowDefinition>
            <RowDefinition Height="*" MinHeight="150"></RowDefinition>
            <RowDefinition Height="35"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition></ColumnDefinition>
                <ColumnDefinition Width="70"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <avalonEdit:TextEditor Grid.Column="0" Background="White"
            xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"  
            Name="MyAvalonEdit" FontFamily="Consolas"
            FontSize="11pt" Margin="10,10,10,10" ShowLineNumbers="True"
            LineNumbersForeground="Gray" ScrollViewer.VerticalScrollBarVisibility="Auto"
            ScrollViewer.HorizontalScrollBarVisibility="Auto" KeyUp="MyAvalonEdit_KeyUp"/>
            <Button  Grid.Column="1" x:Name="BtnSendSQL" Margin="0,10,10,10" Click="BtnSendSQL_Click">
                <StackPanel>
                    <Image Height="25" Source="..//Resources/send.png"></Image>
                    <TextBlock Margin="0,10,0,0" VerticalAlignment="Top" Foreground="White" Text="SendSQL"></TextBlock>
                </StackPanel>
            </Button>
        </Grid>
        <GridSplitter Margin="0,-10,0,0" Grid.Row="1" HorizontalAlignment="Stretch" Background="Transparent" ResizeBehavior="PreviousAndNext">
        </GridSplitter>
        <DataGrid Grid.Row="2" x:Name="DataGrid1" RowStyle="{StaticResource DataGridRowSql}" Style="{StaticResource DataGridStyleSQL}" >
            <DataGrid.CommandBindings>
                <CommandBinding Command="Copy" Executed="CommandBinding_Executed"></CommandBinding>
            </DataGrid.CommandBindings>
            <DataGrid.InputBindings>
                <KeyBinding Key="C" Modifiers="Ctrl" Command="Copy"></KeyBinding>
            </DataGrid.InputBindings>
        </DataGrid>
        <TextBlock TextAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Bottom" Grid.Row="3" x:Name="TxtSqlLog" Text="Wait For Commands..."
                   FontSize="14" FontFamily="Consolas" Foreground="White"></TextBlock>
    </Grid>
</Grid>

Solution

  • I do not know the exact layout where you want to put all the DataGrids, so I just assume you want to place them in row 2 of your Grid. As you want to stack them, you can use an ItemsControl in a ScrollViewer. The latter is just there, so you can scroll through the DataGrids in case the page is too small to display them all. So this is the basic layout for the solutions below.

    <Grid>
       <Grid.RowDefinitions>
          <!-- ...your row definitions. -->
       </Grid.RowDefinitions>
       <!-- ...your other controls. -->
       <ScrollViewer Grid.Row="2">
          <ItemsControl/>
       </ScrollViewer>
    </Grid>
    

    Code-behind solution

    In code-behind, you need to access the ItemsControl, so assign it a name.

    <ItemsControl x:Name="DataGridContainer"/>
    

    Then just create DataGrids in a loop from your DataTable list.

    foreach (var dataTable in Results)
    {
       var dataGrid = new DataGrid { ItemsSource = dataTable.DefaultView };
       DataGridContainer.Items.Add(dataGrid);
    };
    

    By the way, in this scenario, you could also use a StackPanel instead of an ItemsControl.

    MVVM solution

    You can create a property in your view model that exposes the default views of your DataTables.

    public ObservableCollection<DataView> DataViews { get; }
    

    Make sure you instantiate the collection in the constructor or implement INotifyPropertyChanged, so that the collection is available in XAML. Then bind the ItemsControl to this collection.

    <ItemsControl ItemsSource="{Binding DataViews}"/>
    

    Next create a DataTemplate for the DataView type. As each data view in the bound collection represents a data table that should be displayed as a DataGrid, it would look like this.

    <DataTemplate DataType="{x:Type data:DataView}">
       <DataGrid ItemsSource="{Binding}"/>
    </DataTemplate>
    

    Then you have to assign this data template as ItemTemplate in the ItemsControl. I just inline it here, but you can also put it in any ResourceDictionary and reference it via StaticResource.

    <ItemsControl ItemsSource="{Binding DataViews}">
       <ItemsControl.ItemTemplate>
          <DataTemplate DataType="{x:Type data:DataView}">
             <DataGrid ItemsSource="{Binding}"/>
          </DataTemplate>
       </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    Now, you just have to add the default views to the DataViews collection. As this collection is an ObservableCollection, it will notify the ItemsControl to update on each add.

    foreach (var dataTable in Results)
    {
       DataViews.Add(dataTable.DefaultView);
    };
    

    As an item is added, the ItemsControl will get notified and creates an item using the DataTemplate.

    I recommend you to use the MVVM solution, as it separates the presentation from your data and is much easier to realize and customization of the DataGrids via the DataTemplate is much easier, convenient and maintainable in XAML.