0

Изучаю вопрос наполнения DataGrid из DataTable с целью динамического формирования отображаемых данных.

Цель программы: Выполнения массовых запросов в множество баз данных и наполнения таблицы результатами из запросов. Соответственно в каждой из базы могут появляться n+ новых столбцов, и r+ записей.

Хотелось бы динамически отображать новые данные, но затык у меня произошел в самом начале визуализации данных: DataGrid напрочь отказывается отобразить информацию из DataTable, хотя в отладке я вижу, что информация корректно наполнилась в DataTable.

Был бы безмерно благодарен, если бы тыкнули пальцем где я неправ и что еще по своему незнанию забыл сделать? Скрещиваю два экземпляра в первые :-(

Для тестов реализовал маленькую программульку:

По кнопке выполняется наполнение DataTable во ViewModel

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        DataContext = MainVm;
    }

    public ViewModel MainVm { get; } = new ViewModel();
    private void Button_Click(object sender, RoutedEventArgs e)
    {
            MainVm.DataTable.Columns.Add("col1");
            MainVm.DataTable.Columns.Add("col2");
            var row = MainVm.DataTable.NewRow();
            row[0] = "test1";
            row[1] = "test2";
            MainVm.DataTable.Rows.Add(row);
    }

Сама ViewModel:

public class ViewModel : INotifyPropertyChanged
    {
        private DataTable dataTable = new DataTable();
        public DataTable DataTable
        {
            get
            {
                return dataTable;
            }
            set
            {
                dataTable = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DataTable)));
            }
        }
    public event PropertyChangedEventHandler PropertyChanged;
}

Ну и само окно программы:

<Window x:Class="Test_Datagrid.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Test_Datagrid"
        d:DataContext="{d:DesignInstance Type=local:ViewModel}"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="450" Width="800"
>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <Button Click="Button_Click">Start</Button>
        </StackPanel>
        <DataGrid Grid.Row="1" DataContext="{Binding DataTable}" ItemsSource="{Binding DefaultView}" AutoGenerateColumns="True"></DataGrid>
    </Grid>
</Window>
pincher1519
  • 2,548
  • DataTable не имеет внутри коллекций, которые умеют сообщать о том, что они изменились. Как это обычно делает ObservableCollection. Поэтому обновится он только если вы насильно ему перепишите связь: сначала обязательно занулить myGrid.ItemsSource = null; и потом myGrid.ItemsSource = myDataSource;. Но лучше перейти с DataTable на что-то более подходящее. – Alex Krass Jan 05 '22 at 12:16
  • @AlexKrass а что можно использовать более подходящее, если я не знаю изначально сколько у меня строк и сколько столбцов будет после каждого запроса в базу? Ранее работал с datagrid только с жесткими структурами, а тут получается динамика как по высоте, так и по ширине :-( – pincher1519 Jan 05 '22 at 12:44
  • Я для интереса попробовал что-то сделать, можете посмотреть в ответе, хотя конечно проще использовать первый вариант и не думать особо. – Alex Krass Jan 05 '22 at 16:31

1 Answers1

1

Самый просто вариант обновлять данные перезаписью DataTable.

myGrid.ItemsSource = null;
myGrid.ItemsSource = myDataSource;

Можно так же изменять напрямую у DataGrid его колонки из кода, а сами данные связывать через ObservableCollection.

myGrid.Columns.Clear();
foreach(var header in Headers) 
{
    myGrid.Columns.Add(new DataGridTextColumn() 
    { 
        Header = header.DisplayName, 
        Binding = new Binding(header.BindingName) 
    })
} 

Можно заморочиться и кастомизировать DataGrid, чтобы работать только из VM с коллекциями и разорвать связь с View. Но я не уверен, что этот подход будет хорошим, мне просто было интересно попробовать и код можно еще долго улучшать.

Header

public class Header 
{ 
    public string DisplayName { get; set; }
    public string BindingName { get; set; }
}

CustomDataGrid

public class CustomDataGrid : DataGrid 
{
    public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register(
        "Columns", 
        typeof(ObservableCollection<Header>), 
        typeof(CustomDataGrid), 
        new FrameworkPropertyMetadata(null, new PropertyChangedCallback(ColumnsPropertyCallback)));
public new ObservableCollection&lt;Header&gt; Columns
{
    get { return (ObservableCollection&lt;Header&gt;)GetValue(ColumnsProperty); }
    set {
        SetValue(ColumnsProperty, value);   
    }
}

private static void ColumnsPropertyCallback(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
    var customDataGrid = dp as CustomDataGrid;
    if (e.OldValue != null) 
    {
        customDataGrid.Columns.CollectionChanged -= customDataGrid.CustomColumns_CollectionChanged;
    }

    if (e.NewValue != null)
    {
        customDataGrid.Columns.CollectionChanged += customDataGrid.CustomColumns_CollectionChanged;
    }
    customDataGrid.Refresh();
}


private void CustomColumns_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    Refresh();
}

private void Refresh() 
{
    base.Columns.Clear();
    if (this.Columns != null)
        this.Columns.ToList().ForEach(header =&gt; base.Columns.Add(new DataGridTextColumn() { Header = header.DisplayName, Binding = new Binding(header.BindingName) }));
}

}

ViewModel класс с примером

public class ViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Header> customColumns = new ObservableCollection<Header>();
    public ObservableCollection<Header> CustomColumns
    {
        get { return customColumns; }
        set 
        {
            customColumns = value;
            OnPropertyChanged();
        }
    }
private ObservableCollection&lt;object&gt; customData = new ObservableCollection&lt;object&gt;();
public ObservableCollection&lt;object&gt; CustomData
{
    get { return customData; }
    set
    {
        customData = value;
        OnPropertyChanged();
    }
}

private int counter = 1;

private ICommand _btnClick;
public ICommand BtnClick
{
    get
    {
        if (_btnClick == null)
        {
            _btnClick = new RelayCommand(
                p =&gt; true,
                p =&gt; 
                {
                    CustomColumns.Clear();
                    CustomData.Clear();

                    dynamic v = null;
                    if (counter % 2 == 0)
                    {
                        CustomColumns.Add(new Header() { DisplayName = &quot;ID&quot;, BindingName = &quot;ID&quot; });
                        CustomColumns.Add(new Header() { DisplayName = &quot;Имя&quot;, BindingName = &quot;Name&quot; }) ;
                        CustomColumns.Add(new Header() { DisplayName = &quot;Фамилия&quot;, BindingName = &quot;Surname&quot; });
                        for (int i = 0; i &lt; counter; i++)
                        {
                            v = new { ID = i, Name = &quot;Имя &quot; + i, Surname = &quot;Фамилия &quot; + i };
                            CustomData.Add(v);
                        }
                    }
                    else
                    {
                        CustomColumns.Add(new Header() { DisplayName = &quot;ID&quot;, BindingName = &quot;ID&quot; });
                        CustomColumns.Add(new Header() { DisplayName = &quot;Кол-во товара&quot;, BindingName = &quot;Amount&quot; });
                        CustomColumns.Add(new Header() { DisplayName = &quot;Цена&quot;, BindingName = &quot;Price&quot; });
                        CustomColumns.Add(new Header() { DisplayName = &quot;Описание&quot;, BindingName = &quot;Description&quot; });
                        CustomColumns.Add(new Header() { DisplayName = &quot;Поставщик&quot;, BindingName = &quot;Retailer&quot; });
                        for (int i = 0; i &lt; counter; i++)
                        {
                            v = new { ID = i, Amount = i + i, Price = i + &quot;$&quot;, Description = &quot;Описание&quot;, Retailer = &quot;Поставщик &quot; + i };
                            CustomData.Add(v);
                        }
                    }
                    counter++;
                });
        }
        return _btnClick;
    }
}

public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string prop = &quot;&quot;)
{
    if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs(prop));
}

}

XAML

<Window x:Class="WpfApp1.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:WpfApp1"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
    <local:ViewModel/>
</Window.DataContext>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto" />
        <RowDefinition Height="1*" />
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal">
        <Button Command="{Binding BtnClick}">Start</Button>
    </StackPanel>
    <local:CustomDataGrid Grid.Row="1" Columns="{Binding CustomColumns}" ItemsSource="{Binding CustomData}" AutoGenerateColumns="False" x:Name="xGrid"></local:CustomDataGrid>
</Grid>
</Window>

Код View тогда получается пустой

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

Код RelayCommand на всякий случай тоже прилагаю

public class RelayCommand : ICommand
{
    private readonly Predicate<object> _canExecute;
    private readonly Action<object> _execute;
public RelayCommand(Predicate&lt;object&gt; canExecute, Action&lt;object&gt; execute)
{
    _canExecute = canExecute;
    _execute = execute;
}

public event EventHandler CanExecuteChanged
{
    add =&gt; CommandManager.RequerySuggested += value;
    remove =&gt; CommandManager.RequerySuggested -= value;
}

public bool CanExecute(object parameter)
{
    return _canExecute(parameter);
}

public void Execute(object parameter)
{
    _execute(parameter);
}

}

введите сюда описание изображения

Alex Krass
  • 17,744