WPF. Ошибка при инициализации компонентов в MainPage

Есть следующий замл код:

<Grid Margin="0,0,0.4,1.2">
    <Button Content="Parse" HorizontalAlignment="Left" Margin="311,38,0,0" VerticalAlignment="Top" Width="253" Height="91" FontSize="36" Command="{Binding ButtonClick}"/>
    <DataGrid Margin="10,373,10,10.2" ItemsControl.ItemsSource="{Binding Source = InformationAboutSong}" AutoGenerateColumns="False" >
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="Thumbnail" Width="200">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Image/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
            <DataGridTextColumn Header="Song name" Width="260" />
            <DataGridTextColumn Header="Artist name" Width="260" />
            <DataGridTextColumn Header="Duration" Width="100"/>
        </DataGrid.Columns>
    </DataGrid>
    
    <TextBox x:Name="PlayListId_textBox" HorizontalAlignment="Left" Height="38" Margin="104,153,0,0" TextWrapping="Wrap" Text="Enter playlist id" VerticalAlignment="Top" Width="667" FontSize="20"/>

    <DataGrid Margin="10,191,0,369.2" ItemsControl.ItemsSource="{Binding Source = InformationAboutPlaylist}" AutoGenerateColumns="False" >
        <DataGridTemplateColumn Header="Playlist thumbnail" Width="300">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <Image />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="Playlist description" Width="*">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid>
</Grid>

Дата контекст используется из модели другого проекта (на проект добавлены линки). С помощью замл файла App - <Application ... ... Startup="App_Startup">

VM выглядит следующим образом :

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private string _link;

    public string Link
    {
        get { return _link; }
        set { _link = value;
            OnPropertyChanged("InformationAboutSong");
            OnPropertyChanged("InformationAboutPlaylist");
        }
    }

    public BindingList<Track> InformationAboutSong { get; set; }
    public BindingList<Playlist> InformationAboutPlaylist { get; set; }

    public ICommand ButtonClick
    {
        get
        {
            return new DelegateCommand((obj) =>
            {
                MainModel mm = new MainModel();

                InformationAboutSong = mm.GetSongInfo(Link);
                InformationAboutPlaylist = mm.GetPlaylistInfo(Link);

            });
        }

    }
}

И при запуске проекта в cs InitializeComponent() ловлю следующую ошибку :

"....InvalidOperationException: Операция недопустима, когда ItemsSource используется. Вместо этого получите доступ и измените элементы с помощью ItemsControl.ItemsSource....."


Ответы (1 шт):

Автор решения: aepot

У вас пропущен DataGrid.Columns у второй таблицы.

Вот, поправил разметку

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal">
        <TextBox Text="{Binding Link}" Margin="5" ToolTip="Enter playlist id" Width="500" FontSize="20" VerticalContentAlignment="Center"/>
        <Button Content="Parse" Margin="5" Padding="10,5" FontSize="20" Command="{Binding ButtonClick}"/>
    </StackPanel>
    <DataGrid Margin="5" Grid.Row="1" ItemsSource="{Binding InformationAboutSong}" AutoGenerateColumns="False" >
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="Thumbnail" Width="200">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Image/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
            <DataGridTextColumn Header="Song name" Width="260" />
            <DataGridTextColumn Header="Artist name" Width="260" />
            <DataGridTextColumn Header="Duration" Width="100"/>
        </DataGrid.Columns>
    </DataGrid>
    <DataGrid Margin="5" Grid.Row="2" ItemsSource="{Binding InformationAboutPlaylist}" AutoGenerateColumns="False" >
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="Playlist thumbnail" Width="300">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Image />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
            <DataGridTextColumn Header="Playlist description" Width="*"/>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

Учитесь использовать Grid и StackPanel/WrapPanel, иначе война с марджинами превратится в бесконечный поток мучений.

И еще немного советов

BindingList замените на ObservableCollection и реализуйте свойства по шаблону, как это обычно делается при реализации INotifyPropertyChanged.

Как я понял, BindingList вам от WinForms достался, в WPF его заменяет ObservableCollection.

private string _link;
private ObservableCollection<Track> _informationAboutSong;
private ObservableCollection<Playlist> _informationAboutPlaylist;

public string Link
{
    get => _link;
    set
    {
        _link = value;
        OnPropertyChanged(nameof(Link));
    }
}
public ObservableCollection<Track> InformationAboutSong
{
    get => _informationAboutSong;
    set
    {
        _informationAboutSong = value;
        OnPropertyChanged(nameof(InformationAboutSong));
    }
}
public ObservableCollection<Playlist> InformationAboutPlaylist
{
    get => _informationAboutPlaylist;
    set
    {
        _informationAboutPlaylist = value;
        OnPropertyChanged(nameof(InformationAboutPlaylist));
    }
}

По команде, возмите вот такой класс, перенаправляющий логику команды

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;

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

    public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
        => (_execute, _canExecute) = (execute, canExecute);

    public bool CanExecute(object parameter) 
        => _canExecute == null || _canExecute(parameter);

    public void Execute(object parameter) 
        => _execute(parameter);
}

Он поддерживает CommandParameter, и если будете использовать CanExecute, то узнаете, что он вызывает CanExecute автоматически при любом событии ввода. Не я его придумал, я его стащил из документации по MVVM, и слегка причесал.

Сейчас ваша команда инициализируется заново каждый раз, когда кто-либо обращается в геттеру свойства команды, чтобы этого не происходило, можно использовать "ленивую" инициализацию, которая произойдет только один раз при первом обращении к команде.

private ICommand _buttonClick;

public ICommand ButtonClick => _buttonClick ?? (_buttonClick = new RelayCommand(parameter =>
{
    MainModel mm = new MainModel();

    InformationAboutSong = mm.GetSongInfo(Link);
    InformationAboutPlaylist = mm.GetPlaylistInfo(Link);
}, parameter => Link?.Length > 0));

Если у вас .NET Core и C# 8, то синтаксис будет немного поприятнее

public ICommand ButtonClick => _buttonClick ??= new RelayCommand(parameter =>
{
    MainModel mm = new MainModel();

    InformationAboutSong = mm.GetSongInfo(Link);
    InformationAboutPlaylist = mm.GetPlaylistInfo(Link);
}, parameter => Link?.Length > 0);

И ButtonClick - странное название для команды, назовите ее ParseCommand

Обновил: добавил CanExecute parameter => Link?.Length > 0


Если вот это не заработает

InformationAboutSong = mm.GetSongInfo(Link);

то попробуйте вот так

InformationAboutSong = new ObservableCollection<Track>(mm.GetSongInfo(Link));
→ Ссылка