WPF MVVM: как отразить изменения ObservableCollection в пользовательском интерфейсе - PullRequest
0 голосов
/ 03 декабря 2018

Я относительно новичок в WPF и пытаюсь понять шаблон MVVM и то, как привязка данных работает с ObservableCollection, чтобы создать приложение, над которым я работаю с MVVM.Я создал образец моего приложения с MainWindow, в котором, в зависимости от того, какую кнопку нажимает пользователь, отображается другое представление (UserControl).Общая идея заключается в том, что пользователь будет иметь доступ к данным некоторых элементов из базы данных (например, клиенты, продукты и т. Д.) И сможет добавлять новые, редактировать или удалять существующие.

Итак, есть CustomerView с его CustomerViewModel и ProductView с его ProductViewModel соответственно.Кроме того, есть два класса (Customer.cs & Product.cs), которые представляют Модели.Структура проекта отображается здесь .

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

<Window.Resources>
    <DataTemplate DataType="{x:Type viewModels:CustomerViewModel}">
        <views:CustomerView DataContext="{Binding}"/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type viewModels:ProductViewModel}">
        <views:ProductView DataContext="{Binding}"/>
    </DataTemplate>
</Window.Resources>

<Grid >
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="20*"/>
        <ColumnDefinition Width="80*"/>
    </Grid.ColumnDefinitions>

    <StackPanel Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button x:Name="btnCustomers" Click="btnCustomers_Click" Content="Customers" Width="80" Height="50" Margin="10"/>
        <Button x:Name="btnProducts" Click="btnProducts_Click" Content="Products" Width="80" Height="50" Margin="10"/>
    </StackPanel>

    <Grid Grid.Column="1">
        <ContentControl Grid.Column="0" Content="{Binding}"/>
    </Grid>
</Grid>

и код MainWindow.xaml.cs:

public partial class MainWindow : Window
{
    public CustomerViewModel customerVM;
    public ProductViewModel productVM;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void btnCustomers_Click(object sender, RoutedEventArgs e)
    {
        if (customerVM == null)
        {
            customerVM = new CustomerViewModel();
        }
        this.DataContext = customerVM;
    }

    private void btnProducts_Click(object sender, RoutedEventArgs e)
    {
        if (productVM == null)
        {
            productVM = new ProductViewModel();
        }
        this.DataContext = productVM;
    }
}

Наконец, CustomerView.xaml выглядит следующим образом:

<UserControl.Resources>
    <viewModel:CustomerViewModel x:Key="customerVM"/>
    <!-- Styling code here...-->
</UserControl.Resources>

<Grid DataContext="{StaticResource ResourceKey=customerVM}">
    <Grid.RowDefinitions>
        <RowDefinition Height="2*"/>
        <RowDefinition Height="7*"/>
        <RowDefinition Height="3*"/>
    </Grid.RowDefinitions>

    <Grid Grid.Row="0">
        <TextBlock Text="Customers" FontSize="18"/>
    </Grid>

    <Grid Grid.Row="1">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="5*"/>
            <ColumnDefinition Width="5*"/>
        </Grid.ColumnDefinitions>

        <ComboBox x:Name="cmbCustomers" Grid.Column="0" VerticalAlignment="Top"
                  IsEditable="True"
                  Text="Select customer"
                  ItemsSource="{Binding}"
                  DisplayMemberPath="FullName" IsSynchronizedWithCurrentItem="True">
        </ComboBox>

        <StackPanel Grid.Column="1" Margin="5">
            <StackPanel Orientation="Horizontal">
                <TextBlock Grid.Column="0" Text="Id:" />
                <TextBlock Grid.Column="1" x:Name="txtId" Text="{Binding Path=Id}" FontSize="16"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Grid.Column="0" Text="Name:" />
                <TextBlock Grid.Column="1" x:Name="txtFirstName" Text="{Binding Path=FirstName}" FontSize="16"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Grid.Column="0" Text="Surname:" />
                <TextBlock Grid.Column="1" x:Name="txtLastName" Text="{Binding Path=LastName}" FontSize="16"/>
            </StackPanel>
        </StackPanel>
    </Grid>

    <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center">
        <Button x:Name="btnAddNew" Content="Add New" Click="btnAddNew_Click"/>
        <Button x:Name="btnDelete" Content="Delete Customer" Click="btnDelete_Click"/>
    </StackPanel>
</Grid>

и CustomerViewModel.cs:

public class CustomerViewModel : ObservableCollection<Customer>
{
    public CustomerViewModel()
    {
        LoadCustomers();
    }

    private void LoadCustomers()
    {
        for (int i = 1; i <= 5; i++)
        {
            var customer = new Customer()
            {
                Id = i,
                FirstName = "Customer_" + i.ToString(),
                LastName = "Surname_" + i.ToString()
            };
            this.Add(customer);
        }
    }

    public void AddNewCustomer(int id)
    {
        var customer = new Customer()
        {
            Id = id,
            FirstName = "Customer_" + id.ToString(),
            LastName = "Surname_" + id.ToString()
        };
        Add(customer);
    }
}

Обратите внимание, что ProductView.xaml & ProductViewModel.cs похожи.В настоящее время, когда пользователь нажимает кнопку «Клиенты» или «Продукты» в MainWindow, отображается соответствующее представление, и коллекции загружаются в соответствии с методом LoadCustomers (или LoadProducts), который вызывается конструктором ViewModel.Кроме того, когда пользователь выбирает другой объект из ComboBox, тогда его свойства отображаются правильно (т. Е. Идентификатор, имя и т. Д.).Проблема заключается в том, что пользователь добавляет новый (или удаляет существующий) элемент.


Вопрос 1 : Какой правильный и лучший способ обновить измененную Наблюдаемую коллекциюэлемент и отражать его изменения в пользовательском интерфейсе (Combobox, свойства и т. д.)?

Вопрос 2 : во время тестирования этого проекта я заметил, что конструктор ViewModels (следовательно, LoadCustomers & LoadProductsметод) вызываются дважды.Однако он вызывается только тогда, когда пользователь нажимает кнопку «Клиенты» или «Продукты» соответственно.Это также вызывается через привязку данных XAML?Это оптимальная реализация?

Ответы [ 2 ]

0 голосов
/ 03 декабря 2018

Ваш первый вопрос в основном UX, нет правильного или "лучшего" способа.Вы определенно будете использовать какой-то ItemsControl, но который сильно зависит от того, как вы хотите, чтобы ваши пользователи взаимодействовали с ним.

На ваш второй вопрос в вашем коде есть несколько ошибок:

  1. <viewModel:CustomerViewModel x:Key="customerVM"/> Создает новую модель представления, кроме той, которую создало основное приложение

  2. Grid DataContext="{StaticResource ResourceKey=customerVM}" Затем использует эту "локальную" модель представления, игнорируя унаследованную модель из основного приложения.

Вот почему вы дважды видите срабатывание конструктора, вы создаете два экземпляра!Удалите локальную виртуальную машину и не назначайте DC на сетке.Другие проблемы:

  • <views:ProductView DataContext="{Binding}"/> Назначение DataContext совершенно не нужно, так как он находится в шаблоне данных, его контекст данных уже настроен
  • <ContentControl Grid.Column="0" Content="{Binding}"/> Yuck, у вас должен быть «MainViewModel» со ​​свойством , которое он использует.Не делайте это целым контекстом данных

  • Отсутствие команд для нажатия кнопок (относится к пункту выше)

0 голосов
/ 03 декабря 2018

Существует 3 вида уведомлений об изменениях, которые вам нужны со списками в MVVM:

  1. Изменение уведомлений для каждого свойства элементов списка.
  2. Уведомление об изменениях для свойства, представляющего списокв случае, если весь экземпляр должен быть заменен (что является общим из-за 3)
  3. Уведомление об изменении, если элементы добавляются или удаляются из коллекции.Это единственное, что ObservableCollection берет на себя.К сожалению, опция Addrange отсутствует, поэтому массовые операции будут сопровождаться графическим интерфейсом с уведомлениями.Это то, что Nr.2 для.

В качестве расширенного варианта рассмотрите возможность представления CollectionView, а не необработанного Collection.Элементы графического интерфейса WPF не привязываются к необработанным коллекциям, а только к коллекциям.Но если вы не дадите им один, они сами его создадут.

...