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 шт):
У вас пропущен 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));