Не работает DataGrid в TabControl

219
16 июля 2018, 19:50

C#:

class ViewModelsWindow2 : INotifyPropertyChanged
{
    private List<BookBase> _listBase;
    private ObservableCollection<Tab> _tabs;
    public List<BookBase> ListBase {...} // It's for work and serialization to file.
    public ObservableCollection<Tab> Tabs {...} // It's for binding to WPF.
    public ViewModelsWindow2()
    {
        ListBase = new List<BookBase>()
        {
            new BookBase() { Author="Mark Twain", Name="The Adventures of Tom Sawyer"},
            new BookBase() { Author="Mark Twain", Name="Adventures of Huckleberry Finn"}
        };
        var newTab = new Tab()
        {
            Header = "New",
            BookBase = ListBase,
            AddBook = new Command(o => { MessageBox.Show("!"); ListBase.Add(new BookBase() { Author = "", Name = "" }); })
    };
        Tabs = new ObservableCollection<Tab>();
        Tabs.Add(newTab);
    }
}

Вспомогательные классы:

public class Tab
{
    public string Header { get; set; }
    public List<BookBase> BookBase { get; set; }
    public Command AddBook { get; set; }
    public Tab()
    {
        Header = null;
        BookBase = new List<BookBase>();
        AddBook = null;
    }
}
public class BookBase
{
    public string Author { get; set; }
    public string Name { get; set; }
    public BookBase()
    {
        Author = null;
        Name = null;
    }
}

XAML:

<Window
    x:Class="WpfApp1.Views.Window2"
    ...
    xmlns:local="clr-namespace:WpfApp1.Views"
    ...
    xmlns:vm="clr-namespace:WpfApp1.ViewModels"
    ...
    mc:Ignorable="d">
    <Window.DataContext>
        <vm:ViewModelsWindow2 />
    </Window.DataContext>
    <Grid>
        <TabControl Grid.Column="0" ItemsSource="{Binding Tabs}">
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Header}" />
                </DataTemplate>
            </TabControl.ItemTemplate>
            <TabControl.ContentTemplate>
                <DataTemplate>
                    <Grid Margin="5">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="25" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <Grid Grid.Row="0">
                            <Button Command="{Binding AddBook}" Content="Continue" />
                        </Grid>
                        <DataGrid Grid.Row="1" AutoGenerateColumns="False" CanUserAddRows="False" ItemsSource="{Binding BookBase}">
                            <DataGrid.Columns>
                                <DataGridTextColumn MinWidth="70" Binding="Binding Author}" Header="Author" />
                                <DataGridTextColumn MinWidth="100" Binding="{Binding Name}" Header="Name" />
                            </DataGrid.Columns>
                        </DataGrid>
                    </Grid>
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>
    </Grid>
</Window>

Вообщем не получается заставить эту программу нормально работать, решил обратиться к сообществу.

Суть в том, что не работает кнопка добавления строк. Также не влияет добавляю я к ObservableCollection или к List . Кнопка не работает при обоих вариантах.

Восросы:

  1. Почему не работает кнопка в таком формате?
  2. Возможно ли сделать что-бы кнопка заработала?
  3. Возможно ли что бы AddBook находилась в основном класе, а не в классе Tab , а вызывальсь из XAML с того же места, что и сейчас. Не вижу смысла плодить комманду, если она делает одну и ту же функцию. Скажу проще, возможно изменить XAML, что бы перенисти комману в основной класс?
Answer 1

Также не влияет добавляю я к ObservableCollection или к List . Кнопка не работает при обоих вариантах.

Основная ваша проблема в том, что вы привязываетесь к List<T>, которому зачем то пытаетесь реализовать не тот интерфейс. List<T> - что это, коллекция или свойство? А INotifyPropertyChanged - за что отвечает, за коллекцию или за свойство?

Вердикт таков: Хотите мучать мозг - реализовывайте INotifyCollectionChanged, не хотите - переделывайте все на уже реализующие данный интерфейс коллекции (ObservableCollection или BindingList). В данном случае вы данные добавляете, но интерфейс не знает о том, что коллекция обновилась.
P.S. Проверил весь ваш код изменил все List<T> на ObservableCollection<T> и о чудо, данные появляются!.

Хорошо, давайте теперь поговорим о коде:

  1. О неверном интерфейсе для коллекций мы поговорили, значит смело можем убирать все INPC с коллекций!
    1. Команда. Ее лучше инициализировать при помощью ICommand интерфейса, а не класса, который ее реализует.
    2. INotifyPropertyChanged лучше вынести в отдельный класс (дабы не дублировать его по сотни раз). P.S. Я его вынесу, но он вам не нужен, по этому закомментирую его наследование.
    3. Ну и для сокращения кода, можно использовать конструктор нужного класса.

Исходя из этого мы получаем такой код:

public class Tab
{
    public string Header { get; set; }
    public ObservableCollection<BookBase> BookBase { get; set; }
    public ICommand AddBook { get; set; }
    public Tab(string header, ObservableCollection<BookBase> bookBase, ICommand addBook = null)
    {
        Header = header;
        BookBase = bookBase;
        AddBook = addBook;
    }
}
public class BookBase
{
    public string Author { get; set; }
    public string Name { get; set; }
    public BookBase(string author, string name)
    {
        Author = author;
        Name = name;
    }
}
public class ViewBase : INotifyPropertyChanged
{
    internal void RaisePropertyChanged(string property)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
    }
    public event PropertyChangedEventHandler PropertyChanged;
}
public class ViewModelsWindow2 /*: ViewBase*/
{
    public ObservableCollection<BookBase> ListBase { get; set; }
    public ObservableCollection<Tab> Tabs { get; set; }
    private ICommand AddCommand { get; }
    public ViewModelsWindow2()
    {
        AddCommand = new RelayCommand(Add);
        ListBase = new ObservableCollection<BookBase>()
        {
            new BookBase("Mark Twain", "The Adventures of Tom Sawyer"),
            new BookBase("Mark Twain", "Adventures of Huckleberry Finn")
        };
        Tabs = new ObservableCollection<Tab> { new Tab("New", ListBase, AddCommand) };
    }
    private void Add()
    {
        MessageBox.Show("!");
        ListBase.Add(new BookBase("", ""));
    }
}

Теперь что касается команды в главном классе:
В паттерне MVVM каждый VM должен отвечать за что то одно, у вас идет отдельно реализация VM вкладок и все, что касается именно вкладок должно быть внутри VM. Вы можете инициализировать команду в другом месте и передавать на нее ссылку в нужный VM (как собственно сделал я в коде выше), но не изменять пути путем XAML кода, это не правильно!

READ ALSO
С# передача файла в теле POST запроса

С# передача файла в теле POST запроса

Возникла задача отправлять email напрямую из программыПо мимо просто текста прикреплять файл лога к письму

166
C# System.IO unauthorizedaccessexception

C# System.IO unauthorizedaccessexception

Делаю поиск файла на всех дисках доступных на компе:

186
Как сделать бэкап из .bak файла?

Как сделать бэкап из .bak файла?

Как выгрузить данные из databasebak в базу данных? Иcпользую ASP

207
Блок-схема всего проекта(C# Unity)

Блок-схема всего проекта(C# Unity)

Хотелось бы сократить время "вникания" в новый проект на 500+ классовЕсть ли какая то возможность в Unity, или просто в проекте Visual Studio, наглядно...

235