Поделиться через


Создание базового приложения данных с помощью WPF и Entity Framework 6

Замечание

Если вы используете Visual Studio 2022, убедитесь, что для работы с этим руководством используется версия 17.3 или более поздняя.

В этом руководстве показано, как создать базовые формы для приложения данных в Visual Studio. Приложение использует SQL Server LocalDB, базу данных Northwind, Entity Framework 6 (не Entity Framework Core) и Windows Presentation Foundation (WPF) для .NET Framework (не .NET Core или .NET 5 или более поздней версии). В нем показано, как выполнять базовую привязку данных с представлением master-detail и включать пользовательский элемент управления BindingNavigator с кнопками, которые выполняют следующие действия: переход к первому, переход к предыдущему, переход к следующему, переход к последнему, удаление, добавление, установка нового порядка, обновление и отмена.

В этом руководстве основное внимание уделяется использованию средств данных в Visual Studio и не ставит целью подробное объяснение лежащих в основе технологий. Предполагается, что у вас есть базовое знакомство с расширяемым языком разметки приложений (XAML), Entity Framework и SQL. Хотя код в этом руководстве не демонстрирует архитектуру model-View-ViewModel (MVVM), которая является стандартной для приложений WPF, вы можете скопировать код с несколькими изменениями в собственное приложение MVVM.

Чтобы просмотреть окончательный код для этого руководства, ознакомьтесь с примерами учебников Visual Studio — EF6.

Изучив это руководство, вы:

  • Установка и подключение к Northwind
  • Настройка проекта приложения WPF
  • Создание модели данных сущности ADO.NET
  • Привязка данных модели к странице XAML
  • Настройка макета страницы и добавление сетки
  • Добавление кнопок для навигации, добавления, обновления и удаления
  • Запуск приложения WPF

Предпосылки

  • Visual Studio с установленной рабочей нагрузкой разработки настольных приложений .NET и установленным компонентом Windows Communication Foundation. Чтобы установить его, выполните приведенные далее действия.

    1. Откройте приложение Установщик Visual Studio или выберите Инструменты>Получить средства и компоненты в меню Visual Studio.
    2. В Visual Studio Installer выберите "Изменить " рядом с версией Visual Studio, которую вы хотите изменить.
    3. Перейдите на вкладку "Отдельные компоненты" и выберите Windows Communication Foundation в разделе "Действия по разработке".
    4. Нажмите кнопку Изменить.
  • SQL Server Express LocalDB. Если у вас нет SQL Server Express LocalDB, его можно установить на странице загрузки SQL Server. Кроме того, вы можете установить его с приложением Установщика Visual Studio в качестве отдельного компонента.

  • Обозреватель объектов SQL Server. Чтобы установить его, установите нагрузку на хранение и обработку данных в приложении Инсталлятора Visual Studio.

  • Средства Entity Framework 6. Обычно это устанавливается при установке компонента разработки .NET Desktop.

Установка и подключение к Northwind

В следующем примере используется SQL Server Express LocalDB и пример базы данных Northwind. Если поставщик данных ADO.NET для этого продукта поддерживает Entity Framework, он также должен работать с другими продуктами базы данных SQL.

Установите пример базы данных Northwind, выполнив следующие действия.

  1. В Visual Studio откройте окно обозревателя объектов SQL Server в меню "Вид ". Разверните узел SQL Server. Щелкните правой кнопкой мыши экземпляр LocalDB и выберите новый запрос.

    Откроется окно редактора запросов.

  2. Скопируйте скрипт Northwind Transact-SQL (T-SQL) в буфер обмена.

  3. Вставьте скрипт T-SQL в редактор запросов и нажмите кнопку "Выполнить".

    Запрос скрипта T-SQL создает базу данных Northwind и заполняет ее данными.

  4. Добавьте новые подключения для базы данных Northwind.

Настройка проекта приложения WPF

Чтобы настроить проект приложения WPF, выполните следующие действия.

  1. В Visual Studio создайте новый проект приложения WPF (.NET Framework) C#.

  2. Добавьте пакет NuGet для Entity Framework 6. В обозревателе решенийвыберите узел проекта. В главном меню выберите Project>Manage NuGet Packages.

  3. В диспетчере пакетов NuGet выберите ссылку "Обзор ". Найдите и выберите пакет EntityFramework . Выберите "Установить" в правой области и следуйте инструкциям.

    Окно вывода отображает ход выполнения и уведомляет вас о завершении установки.

    Снимок экрана: пакет NuGet Entity Framework.

    Снимок экрана: пакет NuGet Entity Framework.

Теперь visual Studio можно использовать для создания модели на основе базы данных Northwind.

Создание модели данных сущности ADO.NET

Чтобы создать модель данных сущности ADO.NET, выполните следующие действия.

  1. Щелкните правой кнопкой мыши узел проекта приложения WPF в обозревателе решений и выберите пункт "Добавить>новый элемент". В левой области, под узлом C#, выберите Данные, а в средней области выберите Модель данных сущностей ADO.NET.

    Снимок экрана: окно

    Снимок экрана: окно

  2. Введите Northwind_model для имени и нажмите кнопку "Добавить".

  3. В мастере модели данных сущностей выберите EF Designer из базы данных и нажмите кнопку "Далее".

    Снимок экрана: конструктор EF из базы данных, выбранный в мастере модели данных сущностей.

  4. В разделе "Выбор подключения к данным" выберите подключение LocalDB Northwind (например, (localdb)\MSSQLLocalDB), а затем нажмите кнопку "Далее".

  5. Если подключение не видно:

    1. Выберите новое подключение. Если Microsoft SQL Server не выбран в качестве источника данных в диалоговом окне "Свойства подключения ", нажмите кнопку "Изменить". В диалоговом окне "Выбор источника данных " выберите Microsoft SQL Server и нажмите кнопку "ОК".

    2. В диалоговом окне "Свойства подключения" введите (localdb)\MSSQLLocalDB в качестве имени сервера.

    3. Для выбора или введите имя базы данных, выберите Northwind и нажмите кнопку "ОК".

    4. В разделе "Выбор подключения к данным" выберите подключение LocalDB Northwind и нажмите кнопку "Далее".

  6. Если появится запрос, выберите версию Entity Framework, которую вы используете, и нажмите кнопку "Далее".

    Снимок экрана: выбор версии для Entity Framework.

  7. На следующей странице мастера выберите таблицы, хранимые процедуры и другие объекты базы данных для включения в модель Entity Framework. Разверните узел dbo под узлом таблиц в представлении дерева. Выберите "Клиенты", "Сведения о заказе" и "Заказы". Оставьте флажки по умолчанию и нажмите кнопку "Готово".

    Снимок экрана: выбранные объекты базы данных модели данных.

    Мастер создает классы C#, представляющие модель Entity Framework, и представляют собой привязку данных Visual Studio к пользовательскому интерфейсу WPF. Он создает следующие файлы в проекте:

    • Файл .edmx описывает связи и другие метаданные, которые связывают классы с объектами в базе данных.

    • Файлы .tt — это шаблоны T4, создающие код, который работает с моделью и сохраняет изменения в базе данных.

    Эти файлы отображаются в обозревателе решений под узломNorthwind_model :

    Снимок экрана, на котором показаны файлы модели Entity Framework в обозревателе решений.

    Снимок экрана, показывающий файлы модели Entity Framework в Проводнике решений.

    Конструктор форм для .edmx файла не используется в этом руководстве, но его можно использовать для изменения определенных свойств и связей в модели.

Файлы .tt являются универсальными, и необходимо редактировать один из них для работы с привязкой данных WPF, для которой требуются ObservableCollection объекты. Выполните следующие действия.

  1. В обозревателе решений разверните узел Northwind_model , пока не найдете Northwind_model.tt. Дважды щелкните этот файл и внесите следующие изменения:

  2. Нажмите клавишу F5, чтобы выполнить сборку и запуск проекта. При первом запуске приложения классы модели видны мастеру источников данных.

Теперь вы готовы подключить эту модель к странице XAML, чтобы вы могли просматривать, перемещаться и изменять данные.

Привязка данных модели к странице XAML

Хотя вы можете написать собственный код привязки данных, проще позволить Visual Studio сделать это для вас. Для этого выполните следующие действия.

  1. Из главного меню выберите Проект>Добавить новый источник данных, чтобы отобразить Мастер настройки источника данных. Так как вы привязаны к классам модели, а не к базе данных, выберите объект. Нажмите кнопку Далее.

    Снимок экрана: мастер настройки источника данных с объектом, выбранным в качестве источника данных.

  2. Разверните узел проекта, выберите объект Customer и нажмите кнопку Готово. Источники объектов Order автоматически создаются из свойства навигации Orders в Customer.

    Снимок экрана: объект Customer, выбранный в качестве источника данных.

    Снимок экрана: объект Customer, выбранный в качестве источника данных.

  3. В обозревателе решений дважды щелкните MainWindow.xaml в проекте, чтобы изменить XAML. Измените значение Title MainWindow на что-то более описательное и увеличьте его Height и Width до 600 и 800 (при необходимости эти значения можно изменить позже).

  4. Добавьте эти три определения строк в основную сетку, одну строку для кнопок навигации, одну для сведений клиента и одну для сетки, отображающую свои заказы:

        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
    

Затем вы отображаете каждое свойство класса Customers в его отдельном текстовом поле. Выполните следующие действия.

  1. В обозревателе решений дважды щелкните MainWindow.xaml , чтобы открыть его в конструкторе.

    Вкладка "Источники данных" отображается в левой области Visual Studio рядом с панелью элементов.

  2. Чтобы открыть окно "Источники данных", перейдите на вкладку "Источники данных" или выберите "Просмотреть> другиеисточники данныхWindows>" в меню.

  3. В источниках данных выберите "Клиенты" и выберите "Сведения " в раскрывающемся списке.

  4. Перетащите узел в средний ряд области конструирования. Если вы его потеряли, можно впоследствии указать строку вручную в XAML, выбрав Grid.Row="1".

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

    Снимок экрана: привязка источника данных клиентов к отдельным элементам управления.

    Снимок экрана: привязка источника данных клиентов к отдельным элементам управления.

    В представлении XAML теперь можно увидеть новый Grid элемент в строке 1 (средней строке) родительской сетки. Родительская сетка имеет атрибут DataContext, который ссылается на CollectionViewSource, принадлежащий элементу Windows.Resources. Учитывая контекст данных, когда первое текстовое поле привязывается к Address, это имя сопоставляется со свойством Address в текущем Customer объекте в CollectionViewSource.

    <Grid DataContext="{StaticResource customerViewSource}">
    
  5. OrderПеретащите свойство Customers объекта класса в нижнюю половину формы, чтобы конструктор положил его в строку 2.

    Когда клиент отображается в верхней части формы, вы должны видеть его заказы в нижней части. Вы показываете заказы с помощью элемента управления в виде сетки. Чтобы привязка данных master-detail работала должным образом, важно привязать к свойству Orders в классе Customers, а не к отдельному узлу Orders.

    Снимок экрана, на котором показаны классы Orders, перетаскиваемые и удаленные в виде сетки.

    Снимок экрана, на котором показаны классы Orders, перетаскиваемые и сброшенные в сетку.

    Visual Studio теперь создает весь код привязки, который подключает элементы управления пользовательского интерфейса к событиям в модели.

  6. Чтобы просмотреть некоторые данные, напишите код для заполнения модели. Перейдите к MainWindow.xaml.cs и добавьте элемент данных в класс MainWindow для контекста данных.

    Этот объект, созданный для вас, действует как элемент управления, который отслеживает изменения и события в модели.

  7. Добавьте CollectionViewSource элементы данных для клиентов и заказов и связанную логику инициализации конструктора в существующий конструктор MainWindow(). Первая часть класса должна выглядеть следующим образом:

    public partial class MainWindow : Window
    {
        NorthwindEntities context = new NorthwindEntities();
        CollectionViewSource custViewSource;
        CollectionViewSource ordViewSource;
    
        public MainWindow()
        {
            InitializeComponent();
            custViewSource = ((CollectionViewSource)(FindResource("customerViewSource")));
            ordViewSource = ((CollectionViewSource)(FindResource("customerOrdersViewSource")));
            DataContext = this;
        }
    
  8. Если он не существует, добавьте директиву using, чтобы System.Data.Entity включить метод расширения Load в область действия.

    using System.Data.Entity;
    
  9. Прокрутите вниз и найдите Window_Loaded обработчик событий. Обратите внимание, что Visual Studio добавил CollectionViewSource объект. Этот объект представляет NorthwindEntities объект, который вы выбрали при создании модели. Так как вы уже добавили его здесь, вам больше не нужно это делать. Замените код Window_Loaded таким образом, чтобы метод выглядел следующим образом:

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        // Load is an extension method on IQueryable,    
        // defined in the System.Data.Entity namespace.   
        // This method enumerates the results of the query,    
        // similar to ToList but without creating a list.   
        // When used with Linq to Entities, this method    
        // creates entity objects and adds them to the context.   
        context.Customers.Load();
    
        // After the data is loaded, call the DbSet<T>.Local property    
        // to use the DbSet<T> as a binding source.   
        custViewSource.Source = context.Customers.Local;
    }
    
  10. Нажмите клавишу F5.

    Вы должны увидеть сведения о первом клиенте, которые были загружены в CollectionViewSource, а также их заказы в сетке данных. Вы исправите форматирование в следующем разделе. Вы также можете создать способ просмотра других записей и выполнения базовых операций создания, чтения, обновления и удаления (CRUD).

Настройка макета страницы и добавление сетки для новых клиентов и заказов

Расположение по умолчанию, созданное Visual Studio, не идеально подходит для приложения, поэтому мы предоставляем окончательный код XAML для копирования в код. Кроме того, вам потребуется несколько таблиц, чтобы пользователь смог добавить нового клиента или заказ.

Чтобы добавить нового клиента и заказ, создайте отдельный набор текстовых полей, которые не привязаны к CollectionViewSourceданным. Вы управляете сеткой, которую пользователь видит в любое время, задав свойство Visible в методах обработчика. Наконец, вы добавите кнопку "Удалить" в каждую строку в сетке "Заказы", чтобы пользователь мог удалить отдельный заказ.

  1. Откройте MainWindow.xaml и добавьте следующие стили в элемент Windows.Resources.

    <Style x:Key="Label" TargetType="{x:Type Label}" BasedOn="{x:Null}">
       <Setter Property="HorizontalAlignment" Value="Left"/>
       <Setter Property="VerticalAlignment" Value="Center"/>
       <Setter Property="Margin" Value="3"/>
       <Setter Property="Height" Value="23"/>
    </Style>
    <Style x:Key="CustTextBox" TargetType="{x:Type TextBox}" BasedOn="{x:Null}">
       <Setter Property="HorizontalAlignment" Value="Right"/>
       <Setter Property="VerticalAlignment" Value="Center"/>
       <Setter Property="Margin" Value="3"/>
       <Setter Property="Height" Value="26"/>
       <Setter Property="Width" Value="120"/>
    </Style>
    
  2. Замените всю внешнюю сетку этой разметкой:

    <Grid>
      <Grid.RowDefinitions>
          <RowDefinition Height="auto"/>
          <RowDefinition Height="auto"/>
          <RowDefinition Height="*"/>
      </Grid.RowDefinitions>
      <Grid x:Name="existingCustomerGrid" Grid.Row="1" HorizontalAlignment="Left" Margin="5" Visibility="Visible" VerticalAlignment="Top" Background="AntiqueWhite" DataContext="{StaticResource customerViewSource}">
          <Grid.ColumnDefinitions>
              <ColumnDefinition Width="Auto" MinWidth="233"/>
              <ColumnDefinition Width="Auto" MinWidth="397"/>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
          </Grid.RowDefinitions>
          <Label Content="Customer ID:" Grid.Row="0" Style="{StaticResource Label}"/>
          <TextBox x:Name="customerIDTextBox" Grid.Row="0" Style="{StaticResource CustTextBox}"
                   Text="{Binding CustomerID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Company Name:" Grid.Row="1" Style="{StaticResource Label}"/>
          <TextBox x:Name="companyNameTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
                   Text="{Binding CompanyName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Contact Name:" Grid.Row="2" Style="{StaticResource Label}"/>
          <TextBox x:Name="contactNameTextBox" Grid.Row="2" Style="{StaticResource CustTextBox}"
                   Text="{Binding ContactName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Contact title:" Grid.Row="3" Style="{StaticResource Label}"/>
          <TextBox x:Name="contactTitleTextBox" Grid.Row="3" Style="{StaticResource CustTextBox}"
                   Text="{Binding ContactTitle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Address:" Grid.Row="4" Style="{StaticResource Label}"/>
          <TextBox x:Name="addressTextBox" Grid.Row="4" Style="{StaticResource CustTextBox}"
                   Text="{Binding Address, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="City:" Grid.Column="1" Grid.Row="0" Style="{StaticResource Label}"/>
          <TextBox x:Name="cityTextBox" Grid.Column="1" Grid.Row="0" Style="{StaticResource CustTextBox}"
                   Text="{Binding City, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Country:" Grid.Column="1" Grid.Row="1" Style="{StaticResource Label}"/>
          <TextBox x:Name="countryTextBox" Grid.Column="1" Grid.Row="1" Style="{StaticResource CustTextBox}"
                   Text="{Binding Country, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Fax:" Grid.Column="1" Grid.Row="2" Style="{StaticResource Label}"/>
          <TextBox x:Name="faxTextBox" Grid.Column="1" Grid.Row="2" Style="{StaticResource CustTextBox}"
                   Text="{Binding Fax, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Phone:" Grid.Column="1" Grid.Row="3" Style="{StaticResource Label}"/>
          <TextBox x:Name="phoneTextBox" Grid.Column="1" Grid.Row="3" Style="{StaticResource CustTextBox}"
                   Text="{Binding Phone, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Postal Code:" Grid.Column="1" Grid.Row="4" VerticalAlignment="Center" Style="{StaticResource Label}"/>
          <TextBox x:Name="postalCodeTextBox" Grid.Column="1" Grid.Row="4" Style="{StaticResource CustTextBox}"
                   Text="{Binding PostalCode, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Region:" Grid.Column="1" Grid.Row="5" Style="{StaticResource Label}"/>
          <TextBox x:Name="regionTextBox" Grid.Column="1" Grid.Row="5" Style="{StaticResource CustTextBox}"
                   Text="{Binding Region, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
      </Grid>
      <Grid x:Name="newCustomerGrid" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=newCustomer, UpdateSourceTrigger=Explicit}" Visibility="Collapsed" Background="CornflowerBlue">
          <Grid.ColumnDefinitions>
              <ColumnDefinition Width="Auto" MinWidth="233"/>
              <ColumnDefinition Width="Auto" MinWidth="397"/>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
          </Grid.RowDefinitions>
          <Label Content="Customer ID:" Grid.Row="0" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_customerIDTextBox" Grid.Row="0" Style="{StaticResource CustTextBox}"
                   Text="{Binding CustomerID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Company Name:" Grid.Row="1" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_companyNameTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
                   Text="{Binding CompanyName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true }"/>
          <Label Content="Contact Name:" Grid.Row="2" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_contactNameTextBox" Grid.Row="2" Style="{StaticResource CustTextBox}"
                   Text="{Binding ContactName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Contact title:" Grid.Row="3" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_contactTitleTextBox" Grid.Row="3" Style="{StaticResource CustTextBox}"
                   Text="{Binding ContactTitle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Address:" Grid.Row="4" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_addressTextBox" Grid.Row="4" Style="{StaticResource CustTextBox}"
                   Text="{Binding Address, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="City:" Grid.Column="1" Grid.Row="0" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_cityTextBox" Grid.Column="1" Grid.Row="0" Style="{StaticResource CustTextBox}"
                   Text="{Binding City, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Country:" Grid.Column="1" Grid.Row="1" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_countryTextBox" Grid.Column="1" Grid.Row="1" Style="{StaticResource CustTextBox}"
                   Text="{Binding Country, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Fax:" Grid.Column="1" Grid.Row="2" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_faxTextBox" Grid.Column="1" Grid.Row="2" Style="{StaticResource CustTextBox}"
                   Text="{Binding Fax, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Phone:" Grid.Column="1" Grid.Row="3" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_phoneTextBox" Grid.Column="1" Grid.Row="3" Style="{StaticResource CustTextBox}"
                   Text="{Binding Phone, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Postal Code:" Grid.Column="1" Grid.Row="4" VerticalAlignment="Center" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_postalCodeTextBox" Grid.Column="1" Grid.Row="4" Style="{StaticResource CustTextBox}"
                   Text="{Binding PostalCode, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Region:" Grid.Column="1" Grid.Row="5" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_regionTextBox" Grid.Column="1" Grid.Row="5" Style="{StaticResource CustTextBox}"
                   Text="{Binding Region, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
      </Grid>
      <Grid x:Name="newOrderGrid" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" DataContext="{Binding Path=newOrder, Mode=TwoWay}" Visibility="Collapsed" Background="LightGreen">
          <Grid.ColumnDefinitions>
              <ColumnDefinition Width="Auto" MinWidth="233"/>
              <ColumnDefinition Width="Auto" MinWidth="397"/>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
          </Grid.RowDefinitions>
          <Label Content="New Order Form" FontWeight="Bold"/>
          <Label Content="Employee ID:"  Grid.Row="1" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_employeeIDTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
                   Text="{Binding EmployeeID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Order Date:"  Grid.Row="2" Style="{StaticResource Label}"/>
          <DatePicker x:Name="add_orderDatePicker" Grid.Row="2"  HorizontalAlignment="Right" Width="120"
                  SelectedDate="{Binding OrderDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
          <Label Content="Required Date:" Grid.Row="3" Style="{StaticResource Label}"/>
          <DatePicker x:Name="add_requiredDatePicker" Grid.Row="3" HorizontalAlignment="Right" Width="120"
                   SelectedDate="{Binding RequiredDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
          <Label Content="Shipped Date:"  Grid.Row="4"  Style="{StaticResource Label}"/>
          <DatePicker x:Name="add_shippedDatePicker"  Grid.Row="4"  HorizontalAlignment="Right" Width="120"
                  SelectedDate="{Binding ShippedDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
          <Label Content="Ship Via:"  Grid.Row="5" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_ShipViaTextBox"  Grid.Row="5" Style="{StaticResource CustTextBox}"
                   Text="{Binding ShipVia, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
          <Label Content="Freight"  Grid.Row="6" Style="{StaticResource Label}"/>
          <TextBox x:Name="add_freightTextBox" Grid.Row="6" Style="{StaticResource CustTextBox}"
                   Text="{Binding Freight, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
      </Grid>
      <DataGrid x:Name="ordersDataGrid" SelectionUnit="Cell" SelectionMode="Single" AutoGenerateColumns="False" CanUserAddRows="false" IsEnabled="True" EnableRowVirtualization="True" Width="auto" ItemsSource="{Binding Source={StaticResource customerOrdersViewSource}}" Margin="10,10,10,10" Grid.Row="2" RowDetailsVisibilityMode="VisibleWhenSelected">
          <DataGrid.Columns>
              <DataGridTemplateColumn>
                  <DataGridTemplateColumn.CellTemplate>
                      <DataTemplate>
                          <Button Content="Delete" Command="{StaticResource DeleteOrderCommand}" CommandParameter="{Binding}"/>
                      </DataTemplate>
                  </DataGridTemplateColumn.CellTemplate>
              </DataGridTemplateColumn>
              <DataGridTextColumn x:Name="customerIDColumn" Binding="{Binding CustomerID}" Header="Customer ID" Width="SizeToHeader"/>
              <DataGridTextColumn x:Name="employeeIDColumn" Binding="{Binding EmployeeID}" Header="Employee ID" Width="SizeToHeader"/>
              <DataGridTextColumn x:Name="freightColumn" Binding="{Binding Freight}" Header="Freight" Width="SizeToHeader"/>
              <DataGridTemplateColumn x:Name="orderDateColumn" Header="Order Date" Width="SizeToHeader">
                  <DataGridTemplateColumn.CellTemplate>
                      <DataTemplate>
                          <DatePicker SelectedDate="{Binding OrderDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
                      </DataTemplate>
                  </DataGridTemplateColumn.CellTemplate>
              </DataGridTemplateColumn>
              <DataGridTextColumn x:Name="orderIDColumn" Binding="{Binding OrderID}" Header="Order ID" Width="SizeToHeader"/>
              <DataGridTemplateColumn x:Name="requiredDateColumn" Header="Required Date" Width="SizeToHeader">
                  <DataGridTemplateColumn.CellTemplate>
                      <DataTemplate>
                          <DatePicker SelectedDate="{Binding RequiredDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
                      </DataTemplate>
                  </DataGridTemplateColumn.CellTemplate>
              </DataGridTemplateColumn>
              <DataGridTextColumn x:Name="shipAddressColumn" Binding="{Binding ShipAddress}" Header="Ship Address" Width="SizeToHeader"/>
              <DataGridTextColumn x:Name="shipCityColumn" Binding="{Binding ShipCity}" Header="Ship City" Width="SizeToHeader"/>
              <DataGridTextColumn x:Name="shipCountryColumn" Binding="{Binding ShipCountry}" Header="Ship Country" Width="SizeToHeader"/>
              <DataGridTextColumn x:Name="shipNameColumn" Binding="{Binding ShipName}" Header="Ship Name" Width="SizeToHeader"/>
              <DataGridTemplateColumn x:Name="shippedDateColumn" Header="Shipped Date" Width="SizeToHeader">
                  <DataGridTemplateColumn.CellTemplate>
                      <DataTemplate>
                          <DatePicker SelectedDate="{Binding ShippedDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
                      </DataTemplate>
                  </DataGridTemplateColumn.CellTemplate>
              </DataGridTemplateColumn>
              <DataGridTextColumn x:Name="shipPostalCodeColumn" Binding="{Binding ShipPostalCode}" Header="Ship Postal Code" Width="SizeToHeader"/>
              <DataGridTextColumn x:Name="shipRegionColumn" Binding="{Binding ShipRegion}" Header="Ship Region" Width="SizeToHeader"/>
              <DataGridTextColumn x:Name="shipViaColumn" Binding="{Binding ShipVia}" Header="Ship Via" Width="SizeToHeader"/>
          </DataGrid.Columns>
       </DataGrid>
    </Grid>
    

Добавление кнопок для навигации, добавления, обновления и удаления

В приложении Windows Forms вы получаете объект с BindingNavigator кнопками для навигации по строкам в базе данных и выполнения основных операций CRUD. Хотя WPF не предоставляет BindingNavigator, легко создать его, создав кнопки внутри горизонтального StackPanelи связав кнопки с командами, связанными с методами в файле кода программной части.

В логике команды есть четыре части:

  • Команды
  • Привязки
  • Кнопки
  • Обработчики команд в коде

Добавление команд, привязок и кнопок в XAML

  1. MainWindow.xaml В файле добавьте команды внутри Windows.Resources элемента следующим образом:

    <RoutedUICommand x:Key="FirstCommand" Text="First"/>
    <RoutedUICommand x:Key="LastCommand" Text="Last"/>
    <RoutedUICommand x:Key="NextCommand" Text="Next"/>
    <RoutedUICommand x:Key="PreviousCommand" Text="Previous"/>
    <RoutedUICommand x:Key="DeleteCustomerCommand" Text="Delete Customer"/>
    <RoutedUICommand x:Key="DeleteOrderCommand" Text="Delete Order"/>
    <RoutedUICommand x:Key="UpdateCommand" Text="Update"/>
    <RoutedUICommand x:Key="AddCommand" Text="Add"/>
    <RoutedUICommand x:Key="CancelCommand" Text="Cancel"/>
    
  2. Событие CommandBinding сопоставляется с методом RoutedUICommand в коде позади. Добавьте этот CommandBindings элемент после закрывающего тега Windows.Resources следующим образом:

    <Window.CommandBindings>
        <CommandBinding Command="{StaticResource FirstCommand}" Executed="FirstCommandHandler"/>
        <CommandBinding Command="{StaticResource LastCommand}" Executed="LastCommandHandler"/>
        <CommandBinding Command="{StaticResource NextCommand}" Executed="NextCommandHandler"/>
        <CommandBinding Command="{StaticResource PreviousCommand}" Executed="PreviousCommandHandler"/>
        <CommandBinding Command="{StaticResource DeleteCustomerCommand}" Executed="DeleteCustomerCommandHandler"/>
        <CommandBinding Command="{StaticResource DeleteOrderCommand}" Executed="DeleteOrderCommandHandler"/>
        <CommandBinding Command="{StaticResource UpdateCommand}" Executed="UpdateCommandHandler"/>
        <CommandBinding Command="{StaticResource AddCommand}" Executed="AddCommandHandler"/>
        <CommandBinding Command="{StaticResource CancelCommand}" Executed="CancelCommandHandler"/>
    </Window.CommandBindings>
    
  3. StackPanel Добавьте кнопки навигации, добавления, удаления и обновления. Добавьте этот стиль в Windows.Resources:

    <Style x:Key="NavButton" TargetType="{x:Type Button}" BasedOn="{x:Null}">
        <Setter Property="FontSize" Value="24"/>
        <Setter Property="FontFamily" Value="Segoe UI Symbol"/>
        <Setter Property="Margin" Value="2,2,2,0"/>
        <Setter Property="Width" Value="40"/>
        <Setter Property="Height" Value="auto"/>
    </Style>
    
  4. Вставьте этот код в верхней части страницы XAML, сразу после RowDefinitions для внешнего элемента Grid.

     <StackPanel Orientation="Horizontal" Margin="2,2,2,0" Height="36" VerticalAlignment="Top" Background="Gainsboro" DataContext="{StaticResource customerViewSource}" d:LayoutOverrides="LeftMargin, RightMargin, TopMargin, BottomMargin">
         <Button Name="btnFirst" Content="|◄" Command="{StaticResource FirstCommand}" Style="{StaticResource NavButton}"/>
         <Button Name="btnPrev" Content="◄" Command="{StaticResource PreviousCommand}" Style="{StaticResource NavButton}"/>
         <Button Name="btnNext" Content="►" Command="{StaticResource NextCommand}" Style="{StaticResource NavButton}"/>
         <Button Name="btnLast" Content="►|" Command="{StaticResource LastCommand}" Style="{StaticResource NavButton}"/>
         <Button Name="btnDelete" Content="Delete Customer" Command="{StaticResource DeleteCustomerCommand}" FontSize="11" Width="120" Style="{StaticResource NavButton}"/>
         <Button Name="btnAdd" Content="New Customer" Command="{StaticResource AddCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/>
         <Button Content="New Order" Name="btnNewOrder" FontSize="11" Width="80" Style="{StaticResource NavButton}" Click="NewOrder_click"/>
         <Button Name="btnUpdate" Content="Commit" Command="{StaticResource UpdateCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/>
         <Button Content="Cancel" Name="btnCancel" Command="{StaticResource CancelCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/>
     </StackPanel>
    

Добавление обработчиков команд в класс MainWindow

Код-обработчик в MainWindow.xaml.cs минимален, за исключением методов добавления и удаления:

  1. Чтобы перейти, вызовите методы свойства View у CollectionViewSource.

  2. Чтобы выполнить каскадное удаление на заказе, используйте DeleteOrderCommandHandler, как показано в примере кода. Однако сначала необходимо удалить Order_Details, связанный с заказом.

  3. UpdateCommandHandler Используйте для добавления клиента или заказа в коллекцию или обновления существующего клиента или заказа с изменениями, внесенными пользователем в текстовые поля.

  4. Добавьте эти методы обработчика в класс MainWindow в MainWindow.xaml.cs. Если для таблицы "Клиенты" ваше CollectionViewSource имеет другое имя, вы должны скорректировать это имя в каждом методе.

    private void LastCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        custViewSource.View.MoveCurrentToLast();
    }
    
    private void PreviousCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        custViewSource.View.MoveCurrentToPrevious();
    }
    
    private void NextCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        custViewSource.View.MoveCurrentToNext();
    }
    
    private void FirstCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        custViewSource.View.MoveCurrentToFirst();
    }
    
    private void DeleteCustomerCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        // If existing window is visible, delete the customer and all their orders.  
        // In a real application, you should add warnings and allow the user to cancel the operation.  
        var cur = custViewSource.View.CurrentItem as Customer;
    
        var cust = (from c in context.Customers
                    where c.CustomerID == cur.CustomerID
                    select c).FirstOrDefault();
    
        if (cust != null)
        {
            foreach (var ord in cust.Orders.ToList())
            {
                Delete_Order(ord);
            }
            context.Customers.Remove(cust);
        }
        context.SaveChanges();
        custViewSource.View.Refresh();
    }
    
    // Commit changes from the new customer form, the new order form,  
    // or edits made to the existing customer form.  
    private void UpdateCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        if (newCustomerGrid.IsVisible)
        {
            // Create a new object because the old one  
            // is being tracked by EF now.  
            Customer newCustomer = new Customer
            {
                Address = add_addressTextBox.Text,
                City = add_cityTextBox.Text,
                CompanyName = add_companyNameTextBox.Text,
                ContactName = add_contactNameTextBox.Text,
                ContactTitle = add_contactTitleTextBox.Text,
                Country = add_countryTextBox.Text,
                CustomerID = add_customerIDTextBox.Text,
                Fax = add_faxTextBox.Text,
                Phone = add_phoneTextBox.Text,
                PostalCode = add_postalCodeTextBox.Text,
                Region = add_regionTextBox.Text
            };
    
            // Perform very basic validation  
            if (newCustomer.CustomerID.Length == 5)
            {
                // Insert the new customer at correct position:  
                int len = context.Customers.Local.Count();
                int pos = len;
                for (int i = 0; i < len; ++i)
                {
                    if (String.CompareOrdinal(newCustomer.CustomerID, context.Customers.Local[i].CustomerID) < 0)
                    {
                        pos = i;
                        break;
                    }
                }
                context.Customers.Local.Insert(pos, newCustomer);
                custViewSource.View.Refresh();
                custViewSource.View.MoveCurrentTo(newCustomer);
            }
            else
            {
                MessageBox.Show("CustomerID must have 5 characters.");
            }
    
            newCustomerGrid.Visibility = Visibility.Collapsed;
            existingCustomerGrid.Visibility = Visibility.Visible;
        }
        else if (newOrderGrid.IsVisible)
        {
            // Order ID is auto-generated so we don't set it here.  
            // For CustomerID, address, etc we use the values from current customer.  
            // User can modify these in the datagrid after the order is entered.  
    
            Customer currentCustomer = (Customer)custViewSource.View.CurrentItem;
    
            Order newOrder = new Order()
            {
                OrderDate = add_orderDatePicker.SelectedDate,
                RequiredDate = add_requiredDatePicker.SelectedDate,
                ShippedDate = add_shippedDatePicker.SelectedDate,
                CustomerID = currentCustomer.CustomerID,
                ShipAddress = currentCustomer.Address,
                ShipCity = currentCustomer.City,
                ShipCountry = currentCustomer.Country,
                ShipName = currentCustomer.CompanyName,
                ShipPostalCode = currentCustomer.PostalCode,
                ShipRegion = currentCustomer.Region
            };
    
            try
            {
                newOrder.EmployeeID = Int32.Parse(add_employeeIDTextBox.Text);
            }
            catch
            {
                MessageBox.Show("EmployeeID must be a valid integer value.");
                return;
            }
    
            try
            {
                // Exercise for the reader if you are using Northwind:  
                // Add the Northwind Shippers table to the model.
                
                // Acceptable ShipperID values are 1, 2, or 3.  
                if (add_ShipViaTextBox.Text == "1" || add_ShipViaTextBox.Text == "2"
                    || add_ShipViaTextBox.Text == "3")
                {
                    newOrder.ShipVia = Convert.ToInt32(add_ShipViaTextBox.Text);
                }
                else
                {
                    MessageBox.Show("Shipper ID must be 1, 2, or 3 in Northwind.");
                    return;
                }
            }
            catch
            {
                MessageBox.Show("Ship Via must be convertible to int");
                return;
            }
    
            try
            {
                newOrder.Freight = Convert.ToDecimal(add_freightTextBox.Text);
            }
            catch
            {
                MessageBox.Show("Freight must be convertible to decimal.");
                return;
            }
    
            // Add the order into the EF model  
            context.Orders.Add(newOrder);
            ordViewSource.View.Refresh();
        }
    
        // Save the changes, either for a new customer, a new order  
        // or an edit to an existing customer or order.
        context.SaveChanges();
    }
    
    // Sets up the form so that user can enter data. Data is later  
    // saved when user clicks Commit.  
    private void AddCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        existingCustomerGrid.Visibility = Visibility.Collapsed;
        newOrderGrid.Visibility = Visibility.Collapsed;
        newCustomerGrid.Visibility = Visibility.Visible;
    
        // Clear all the text boxes before adding a new customer.  
        foreach (var child in newCustomerGrid.Children)
        {
            var tb = child as TextBox;
            if (tb != null)
            {
                tb.Text = "";
            }
        }
    }
    
    private void NewOrder_click(object sender, RoutedEventArgs e)
    {
        var cust = custViewSource.View.CurrentItem as Customer;
        if (cust == null)
        {
            MessageBox.Show("No customer selected.");
            return;
        }
    
        existingCustomerGrid.Visibility = Visibility.Collapsed;
        newCustomerGrid.Visibility = Visibility.Collapsed;
        newOrderGrid.UpdateLayout();
        newOrderGrid.Visibility = Visibility.Visible;
    }
    
    // Cancels any input into the new customer form  
    private void CancelCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        add_addressTextBox.Text = "";
        add_cityTextBox.Text = "";
        add_companyNameTextBox.Text = "";
        add_contactNameTextBox.Text = "";
        add_contactTitleTextBox.Text = "";
        add_countryTextBox.Text = "";
        add_customerIDTextBox.Text = "";
        add_faxTextBox.Text = "";
        add_phoneTextBox.Text = "";
        add_postalCodeTextBox.Text = "";
        add_regionTextBox.Text = "";
    
        existingCustomerGrid.Visibility = Visibility.Visible;
        newCustomerGrid.Visibility = Visibility.Collapsed;
        newOrderGrid.Visibility = Visibility.Collapsed;
    }
    
    private void Delete_Order(Order order)
    {
        // Find the order in the EF model.  
        var ord = (from o in context.Orders.Local
                   where o.OrderID == order.OrderID
                   select o).FirstOrDefault();
    
        // Delete all the order_details that have  
        // this Order as a foreign key  
        foreach (var detail in ord.Order_Details.ToList())
        {
            context.Order_Details.Remove(detail);
        }
    
        // Now it's safe to delete the order.  
        context.Orders.Remove(ord);
        context.SaveChanges();
    
        // Update the data grid.  
        ordViewSource.View.Refresh();
    }
    
    private void DeleteOrderCommandHandler(object sender, ExecutedRoutedEventArgs e)
    {
        // Get the Order in the row in which the Delete button was clicked.  
        Order obj = e.Parameter as Order;
        Delete_Order(obj);
    }
    

Запуск приложения WPF

  1. Чтобы начать отладку, нажмите клавишу F5 . Убедитесь, что данные клиента и заказа заполнены в сетке, а кнопки навигации работают должным образом.

  2. Нажмите Commit, чтобы добавить нового клиента или заказ в модель после ввода данных.

  3. Выберите "Отмена", чтобы выйти из формы нового клиента или нового заказа, не сохраняя данные.

  4. Чтобы вносить изменения напрямую в существующих клиентах и заказах, используйте текстовые поля, которые автоматически записывают эти изменения в модель.