Практикум прикладного программирования на C# в среде VS.NET 2008

Привязка WPF к реляционным данным

Разбить на страницы
Показывать лекцию целиком

Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.

Реляционная база данных - база данных, основанная на реляционной модели данных. Слово "реляционный" происходит от англ. relation (отношение). Для работы с реляционными БД применяют реляционные СУБД. Использование реляционных баз данных было предложено доктором Коддом из компании IBM в 1970 году.

Чаще всего данные в хранилище являются именно реляционными. Отдельные таблицы связаны между собой определенным отношением и настоящая СУБД должна строго следить за целостностью этих связей. Не вдаваясь в теорию реляционных баз данных отметим, что таблица может иметь ключи. Различают несколько видов ключей, но наибольшее распространение получили первичный и внешний ключи.

Первичный ключ (primary key) - на практике термин обозначает поле (столбец) или группу полей таблицы базы данных, значение которого (или комбинация значений которых) используется в качестве уникального идентификатора записи (строки) этой таблицы. Внешний ключ (foreign key) - поле (столбец) таблицы, предназначенное для хранения значения первичного ключа другой таблицы с целью организации связи между этими таблицами.

Упражнение 1. Привязка WPF к реляционным таблицам набора данных

Применяя объектную модель ADO.NET, мы сами вынуждены строить аналог СУБД и поэтому сами должны учитывать связи, которые существуют между таблицами сырых данных в хранилище. В ADO.NET отношения между таблицами объектной модели устанавливаются с помощью объектов DataRelation, которые попарно связывают столбец в одной таблице со столбцом в другой. В WPF связанные записи можно извлечь, привязавшись к зависимостям данных.

Извлеченные данные можно просматривать, редактировать и фиксировать изменения обратной отсылкой в БД. Для редактирования привязанных данных используется двунаправленная привязка. Но здесь мы будем пользоваться только однонаправленной привязкой для просмотра данных, чтобы не усложнять задачу. Этого пока будет достаточно.

Вначале воспользуемся инструментами оболочки для извлечения и отображения реляционных данных, потом повторим задачу вручную. По-прежнему будем работать с файловой БД Northwind.mdb и еще раз приведем ее структуру.

Вкладка Page1. Построение типизированного DataSet с помощью мастера

  • Командой File/New/Project создайте новый проект WPF Application с именем
  • В панели Solution Explorer добавьте к корню проекта контекстной командой Add/New Folder каталог Data и скопируйте в него командой Add/Existing Item файл Northwind.mdb из прилагаемого каталога Source.
  • Откроется окно мастера Data Source Configuration Wizard на вкладке Choose Your Database Object.

    Если окно мастера Data Source Configuration Wizard на этом шаге не появилось (Нано-Сидоркин!), перейдите к выполнению вкладки Page2, а затем вернитесь к этому примеру.
  • Разверните узел
  • Разверните узел Views (Представления), поднимите флажок на Order Details Extended и щелкните на кнопке Finish
  • Мастер оболочки создаст класс типизированного набора данных. Мы уже говорили, что набор данных DataSet объектной модели ADO.NET (типизированный или обычный) является аналогом реляционной БД с настраиваемым набором таблиц, связей между ними, условиями выборки и обновления данных. До своего заполнения данными набор представляет собой только схему или структуру виртуальных данных.

    Последним действием мы включили в набор представление ( Views ) с именем Order Details Extended. Представления данных Views обеспечивают определенный уровень абстракции для источника данных, позволяя виртуально менять структуру исходных данных, не затрагивая сами данные. В представлении можно заранее указать нужные таблицы, определить связи между ними, добавить вычислимые поля, а также именованные запросы без необходимости вносить модификации в исходные данные. Это как бы шаблон нужного среза с оригинальных данных.

  • В панели
  • Мы видим, что представление Order Details Extended является некой искусственной абстракцией реальных данных. Оно отображается как таблица, готовая к заполнению соответствующим SQL -запросом. Очень часто представления Views и называют запросами ( Query ), заранее заготовленными, как и хранимые процедуры Stored Procedures.

    Таблица Customers хранит уникальные сведения о заказчиках. Каждый заказчик может сделать несколько заказов, которые фиксируются в таблице Orders. Каждый заказ может включать в себя несколько продуктов из таблицы Products, доступ к которой можно получить через таблицу Order Details. В нашем случае обе реальные таблицы источника объединены в виртуальную таблицу-представление Order Details Extended набора данных, создаваемую следующим SQL -запросом.

    SELECT DISTINCTROW [Order Details].OrderID, [Order Details].ProductID, 
       Products.ProductName,
      [Order Details].UnitPrice, [Order Details].Quantity, [Order Details].Discount,
      CCur([Order Details].[UnitPrice]*[Quantity]*(1-[Discount])/100)*100 AS ExtendedPrice 
      FROM Products INNER JOIN [Order Details] ON Products.ProductID = [Order Details].ProductID 
      ORDER BY [Order Details].OrderID;

    Мы хотим создать три списка пользовательского интерфейса, каждый из которых будет привязан к соответствующей таблице. Выбор заказчика ( Customer ) в первом списке должен привести к заполнению второго списка заказами, а выделенный заказ ( Order ) во втором списке должен заполнить продуктами третий список. Как видно из рисунка, зависимость Customers-Orders в типизированном наборе уже существует. Осталось добавить связь Orders-Order Details Extended. Выполним это.

  • Вызовите контекстное меню для NorthwindDataSet.xsd в режиме Dataset Designer и выполните команду Add/Relation
  • Настройте окно
  • Щелчком на кнопке
  • Выделите поочередно каждую из двух изображенных связей и в панели Properties посмотрите их названия: это будут связи
  • CustomersOrders
  • Orders_Order_Details_Extended
  • Откомпилируйте проект, чтобы проверить отсутствие синтаксических ошибок
  • Теперь созданный тип набора данных нужно превратить в экземпляр, заполнить данными и отобразить в окне через привязанные элементы пользовательского интерфейса.

  • Заполните типизированный набор данных в классе Window1 процедурного кода так
  • using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
        
    // Добавили пространства имен типизированных классов
    using DataBindingRelation.Data;
    using DataBindingRelation.Data.NorthwindDataSetTableAdapters;
        
    namespace DataBindingRelation
    {
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
        
                Page1();
            }
        
            #region Вкладка Page1
            void Page1()
            {
                // Создание экземпляров сгененированных классов
                NorthwindDataSet dataSet = new NorthwindDataSet();
                CustomersTableAdapter custAdap = new CustomersTableAdapter();
                OrdersTableAdapter ordAdap = new OrdersTableAdapter();
                Order_Details_ExtendedTableAdapter ord_det_extAdap =
                    new Order_Details_ExtendedTableAdapter();
        
                // Потабличное наполнение экземпляра типизированного набора данных
                custAdap.Fill(dataSet.Customers);
                ordAdap.Fill(dataSet.Orders);
                ord_det_extAdap.Fill(dataSet.Order_Details_Extended);
            }
            #endregion
        }
    }
  • Сконструируйте интерфейсную часть с вкладкой Page1 в файле Window1.xaml следующим образом
  • <Window x:Class="DataBindingRelation.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="300" Width="300" 
        Title="WPF и типизированный DataSet" 
        MinHeight="300" 
        MinWidth="300"
        WindowStartupLocation="CenterScreen"
        >
        <Window.Resources>
            <SolidColorBrush x:Key="ControlColorBrush" 
                Color="{x:Static SystemColors.ControlColor}" />
        </Window.Resources>
        <Grid Background="{StaticResource ResourceKey=ControlColorBrush}">
            <TabControl>
                <!-- Привязка к типизированному DataSet -->
                <TabItem Header="Page1">
                    <Grid Name="grid1">
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="2*" />
                                <ColumnDefinition Width="1.5*" />
                                <ColumnDefinition Width="2*" />
                            </Grid.ColumnDefinitions>
                            
                            <TextBlock Grid.Column="0" HorizontalAlignment="Center">ContactName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="0"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding}"
                                DisplayMemberPath="ContactName"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="1" HorizontalAlignment="Center">OrderDate</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="1"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Path=CustomersOrders}"
                                DisplayMemberPath="OrderDate"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="2" HorizontalAlignment="Center">ProductName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="2"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Path=CustomersOrders/Orders_Order_Details_Extended}"
                                DisplayMemberPath="ProductName"
                                />
                        </Grid>
                        <Grid Grid.Row="1">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            
                            <TextBlock Grid.Row="0" Margin="0,0,5,0">ShipName:</TextBlock>
                            <TextBox Grid.Row="0" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipName, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="1">ShipAddress:</TextBlock>
                            <TextBox Grid.Row="1" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipAddress, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="2">ShipCity:</TextBlock>
                            <TextBox Grid.Row="2" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipCity, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="3">ShipCountry:</TextBlock>
                            <TextBox Grid.Row="3" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipCountry, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                               />
                        </Grid>
                    </Grid>
                </TabItem>
            </TabControl>
        </Grid>
    </Window>
  • Подключите к свойству DataContext сетки таблицу Customers из типизированного набора данных для заполнения первого списка
  • #region Вкладка Page1
            void Page1()
            {
                .................................................
        
                // Подключение набора данных к интерфейсу
                grid1.DataContext = dataSet.Customers;
            }
            #endregion

    Остальные два списка будут привязаны к зависимостям CustomersOrders и Orders_Order_Details_Extended. Для текстовых полей источником привязки будет служить именованная зависимость CustomersOrders.

  • Запустите приложение и испытайте его работу
  • Списки управляют связанными данными таблиц, конкретизируя их слева направо. Это происходит за счет установленного в двух первых списках свойства IsSynchronizedWithCurrentItem="True". Обратите внимание на работу верстки (компоновка, раскладка) интерфейсных элементов, которая обеспечивает их автоматическое масштабирование при изменении размеров окна. Названия связей можно всегда изменить в панели Properties режима Dataset Designer, но тогда их нужно менять и в выражениях привязки разметки.

    Вкладка Page2. Повторение предыдущей задачи с новым типизированным набором

    Может случиться так (как у Нано-Сидоркина на его компьютере), что при копировании БД в проект окно мастера Data Source Configuration Wizard не откроется. Или мы захотим (можем захотеть) создать еще один типизированный набор данных с другими настройками. Как вызвать мастер Data Source Configuration Wizard из проекта WPF Application, если он нам вдруг понадобиться? Рассмотрим это.

  • Выполните команду меню оболочки
  • Выберите источник
  • Оставьте строку соединения прежней и перейдите к следующей странице мастера, в которой поднимите указанные на снимке флажки, установите имя набора и щелкните на кнопке
  • В панели Solution Explorer перетащите мышью созданный узел NorthDataSet.xsd в папку Data и раскройте его двойным щелчком в режиме Dataset Designer
  • Выполните команду Add/Relation контекстного меню и создайте новую связь с именем
  • Загляните в панель
  • Добавьте в классе Window1 следующий процедурный код
  • using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
        
    // Добавили пространства имен типизированных классов
    using DataBindingRelation.Data;
    using DataBindingRelation.Data.NorthwindDataSetTableAdapters;
    using DS2 = DataBindingRelation.Data.NorthDataSetTableAdapters;// Псевдоним
        
    namespace DataBindingRelation
    {
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
        
                Page1();
                Page2();
            }
        
            #region Вкладка Page1
            void Page1()
            {
                // Создание экземпляров сгененированных классов
                NorthwindDataSet dataSet = new NorthwindDataSet();
                CustomersTableAdapter custAdap = new CustomersTableAdapter();
                OrdersTableAdapter ordAdap = new OrdersTableAdapter();
                Order_Details_ExtendedTableAdapter ord_det_extAdap =
                    new Order_Details_ExtendedTableAdapter();
        
                // Потабличное наполнение экземпляра типизированного набора данных
                custAdap.Fill(dataSet.Customers);
                ordAdap.Fill(dataSet.Orders);
                ord_det_extAdap.Fill(dataSet.Order_Details_Extended);
        
                // Подключение набора данных к интерфейсу
                grid1.DataContext = dataSet.Customers;
            }
            #endregion
        
            #region Вкладка Page2
            void Page2()
            {
                // Создание экземпляров сгененированных классов
                NorthDataSet dataSet = new NorthDataSet();
                DS2.CustomersTableAdapter custAdap = new DS2.CustomersTableAdapter();
                DS2.OrdersTableAdapter ordAdap = new DS2.OrdersTableAdapter();
                DS2.Order_Details_ExtendedTableAdapter ord_det_extAdap =
                    new DS2.Order_Details_ExtendedTableAdapter();
        
                // Потабличное наполнение экземпляра типизированного набора данных
                custAdap.Fill(dataSet.Customers);
                ordAdap.Fill(dataSet.Orders);
                ord_det_extAdap.Fill(dataSet.Order_Details_Extended);
        
                // Подключение набора данных к интерфейсу
                grid2.DataContext = dataSet.Customers;
            }
            #endregion
        }
    }
  • Добавьте в разметку контейнера TabControl файла Window1.xaml следующий дескрипторный код с вкладкой Page2
  • <!-- Привязка к типизированному DataSet -->
                <TabItem Header="Page2">
                    <Grid Name="grid2">
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="2*" />
                                <ColumnDefinition Width="1.5*" />
                                <ColumnDefinition Width="2*" />
                            </Grid.ColumnDefinitions>
                            
                            <TextBlock Grid.Column="0" HorizontalAlignment="Center">ContactName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="0"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding}"
                                DisplayMemberPath="ContactName"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="1" HorizontalAlignment="Center">OrderDate</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="1"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Path=CustomersOrders}"
                                DisplayMemberPath="OrderDate"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="2" HorizontalAlignment="Center">ProductName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="2"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Path=CustomersOrders/Orders_Order_Details_Extended}"
                                DisplayMemberPath="ProductName"
                                />
                        </Grid>
                        <Grid Grid.Row="1">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            
                            <TextBlock Grid.Row="0" Margin="0,0,5,0">ShipName:</TextBlock>
                            <TextBox Grid.Row="0" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipName, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="1">ShipAddress:</TextBlock>
                            <TextBox Grid.Row="1" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipAddress, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="2">ShipCity:</TextBlock>
                            <TextBox Grid.Row="2" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipCity, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="3">ShipCountry:</TextBlock>
                            <TextBox Grid.Row="3" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipCountry, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                               />
                        </Grid>
                    </Grid>
                </TabItem>
  • Запустите приложение - функциональность вкладки
  • Вкладка Page3. Повторение предыдущей задачи вручную

    В двух предыдущих примерах мы пользовались типом набора данных, который автоматически сгенерировал мастер оболочки Data Source Configuration Wizard по нашим настройкам. В данном примере мы все настройки выполним сами в процедурном коде, опираясь на инфраструктуру объекта DataSet.

    Объект DataSet кроме коллекции Tables объектов DataTable содержит еще и свойство Relations, которое может адресовать экземпляр класса System.Data.DataRelationCollection. Коллекция Relations ожидает объекты DataRelation, в которых можно определять отношения между таблицами коллекции Tables. Следовательно, объект DataSet можно рассматривать как набор таблиц и отношений. Минимальное количество как таблиц, так и отношений в DataSet равно нулю.

    В данном примере мы заполним коллекцию Tables таблицами Customers, Orders и виртуальной таблицей представления Order Details Extended, установим вручную отношения между таблицами, затем привяжем набор к пользовательскому интерфейсу WPF. В итоге должен получиться такой же результат, что и для типизированного набора данных.

  • В панели
  • Установите вкладку
  • Для записи Order Details Extended вызовите контекстное меню и выполните команду Конструктор
  • Щелкните правой кнопкой мыши на заголовке (или свободном месте) окна
  • В результате мы получим строку SQL -запроса, которая формирует виртуальную таблицу представления Order Details Extended (ранее уже приводилась)

    SELECT DISTINCTROW [Order Details].OrderID, [Order Details].ProductID, Products.ProductName, 
    [Order Details].UnitPrice, [Order Details].Quantity, [Order Details].Discount, 
    CCur([Order Details].[UnitPrice]*[Quantity]*(1-[Discount])/100)*100 AS ExtendedPrice
    FROM Products INNER JOIN [Order Details] ON Products.ProductID = [Order Details].ProductID
    ORDER BY [Order Details].OrderID;
  • В панели
  • В панели
  • Заполните файл App.config следующей разметкой
  • <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <connectionStrings>
        <add name="MyNorthwind"
            connectionString="Provider=Microsoft.Jet.OLEDB.4.0;
                Data Source=|DataDirectory|\Data\Northwind.mdb"
            providerName="System.Data.OleDb" />
      </connectionStrings>
    </configuration>
  • Добавьте в файл Window1.xaml.cs следующий процедурный код
  • using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
        
    // Добавили пространства имен типизированных классов
    using DataBindingRelation.Data;
    using DataBindingRelation.Data.NorthwindDataSetTableAdapters;
    using DS2 = DataBindingRelation.Data.NorthDataSetTableAdapters;// Псевдоним
        
    // Подключение пространств имен инфраструктуры ADO.NET
    using System.Data;
    using System.Data.OleDb;
        
    namespace DataBindingRelation
    {
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
        
                Page1();
                Page2();
                Page3();
            }
        
            #region Вкладка Page1
            void Page1()
            {
                // Создание экземпляров сгененированных классов
                NorthwindDataSet dataSet = new NorthwindDataSet();
                CustomersTableAdapter custAdap = new CustomersTableAdapter();
                OrdersTableAdapter ordAdap = new OrdersTableAdapter();
                Order_Details_ExtendedTableAdapter ord_det_extAdap =
                    new Order_Details_ExtendedTableAdapter();
        
                // Потабличное наполнение экземпляра типизированного набора данных
                custAdap.Fill(dataSet.Customers);
                ordAdap.Fill(dataSet.Orders);
                ord_det_extAdap.Fill(dataSet.Order_Details_Extended);
        
                // Подключение набора данных к интерфейсу
                grid1.DataContext = dataSet.Customers;
            }
            #endregion
        
            #region Вкладка Page2
            void Page2()
            {
                // Создание экземпляров сгененированных классов
                NorthDataSet dataSet = new NorthDataSet();
                DS2.CustomersTableAdapter custAdap = new DS2.CustomersTableAdapter();
                DS2.OrdersTableAdapter ordAdap = new DS2.OrdersTableAdapter();
                DS2.Order_Details_ExtendedTableAdapter ord_det_extAdap =
                    new DS2.Order_Details_ExtendedTableAdapter();
        
                // Потабличное наполнение экземпляра типизированного набора данных
                custAdap.Fill(dataSet.Customers);
                ordAdap.Fill(dataSet.Orders);
                ord_det_extAdap.Fill(dataSet.Order_Details_Extended);
        
                // Подключение набора данных к интерфейсу
                grid2.DataContext = dataSet.Customers;
            }
            #endregion
        
            #region Вкладка Page3
            void Page3()
            {
                // Извлекаем в поле строку соединения из файла App.config
                String connectionString = System.Configuration.
                    ConfigurationManager.ConnectionStrings["MyNorthwind"].ConnectionString;
        
                DataSet dataSet = new DataSet();// Создаем множественный набор данных
        
                // Заполняем множественный набор данных из БД
                using (OleDbConnection conn = new OleDbConnection(connectionString))
                {
                    OleDbCommand selectCommand = conn.CreateCommand();
                    OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
        
                    // Загружает всю таблицу Customers
                    selectCommand.CommandText = "SELECT * FROM Customers";
                    adapter.Fill(dataSet, "Customers");
        
                    // Загружает всю таблицу Orders
                    selectCommand.CommandText = "SELECT * FROM Orders";
                    // Загружаем и сразу этой порции данных назначаем имя в наборе
                    adapter.Fill(dataSet, "Orders");
        
                    // Загружаем представление по SQL-запросу
                    selectCommand.CommandText = @"SELECT DISTINCTROW [Order Details].OrderID, [Order Details].ProductID,
       Products.ProductName,
                        [Order Details].UnitPrice, [Order Details].Quantity, [Order Details].Discount,
                        CCur([Order Details].[UnitPrice]*[Quantity]*(1-[Discount])/100)*100 AS ExtendedPrice 
                        FROM Products INNER JOIN [Order Details] ON Products.ProductID = [Order Details].ProductID 
                        ORDER BY [Order Details].OrderID";
                    adapter.Fill(dataSet, "Order_Details_Extended");
                }
        
                // Создание отношения между таблицами Customers и Orders
                dataSet.Relations.Add("CustomersOrders",
                    dataSet.Tables["Customers"].Columns["CustomerID"],
                    dataSet.Tables["Orders"].Columns["CustomerID"]);
        
                // Создание отношения между таблицами Orders и Order_Details_Extended
                // Немного другим способом
                DataColumn parentColumn = dataSet.Tables["Orders"].Columns["OrderID"];
                DataColumn childColumn = dataSet.Tables["Order_Details_Extended"].Columns["OrderID"];
                DataRelation relation = new DataRelation(
                    "Orders_Order_Details_Extended",
                    parentColumn, childColumn);
                dataSet.Relations.Add(relation);
        
                // Подключение набора данных к интерфейсу
                grid3.DataContext = dataSet.Tables["Customers"];
            }
            #endregion
        }
    }
  • В контейнер TabControl добавьте копию вкладки Page2 и переименуйте в ней первую сетку на grid3, чтобы разметка стала такой
  • <!-- Привязка к нетипизированному DataSet -->
                <TabItem Header="Page3">
                    <Grid Name="grid3">
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="2*" />
                                <ColumnDefinition Width="1.5*" />
                                <ColumnDefinition Width="2*" />
                            </Grid.ColumnDefinitions>
                            
                            <TextBlock Grid.Column="0" HorizontalAlignment="Center">ContactName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="0"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding}"
                                DisplayMemberPath="ContactName"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="1" HorizontalAlignment="Center">OrderDate</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="1"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Path=CustomersOrders}"
                                DisplayMemberPath="OrderDate"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="2" HorizontalAlignment="Center">ProductName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="2"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Path=CustomersOrders/Orders_Order_Details_Extended}"
                                DisplayMemberPath="ProductName"
                                />
                        </Grid>
                        <Grid Grid.Row="1">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            
                            <TextBlock Grid.Row="0" Margin="0,0,5,0">ShipName:</TextBlock>
                            <TextBox Grid.Row="0" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipName, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="1">ShipAddress:</TextBlock>
                            <TextBox Grid.Row="1" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipAddress, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="2">ShipCity:</TextBlock>
                            <TextBox Grid.Row="2" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipCity, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="3">ShipCountry:</TextBlock>
                            <TextBox Grid.Row="3" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipCountry, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                               />
                        </Grid>
                    </Grid>
                </TabItem>
  • Запустите приложение - получим результат, в точности совпадающий с двумя предыдущими
  • Вкладка Page4. Привязка интерфейса WPF к объекту данных с помощью класса ObjectDataProvider

    До сих пор мы пользовались свойством DataContext для назначения источника привязки. WPF имеет еще один механизм назначения источника - через ресурс с помощью класса ObjectDataProvider. Класс позволяет создать ресурс, ссылающийся на коллекцию данных, возвращенную методом стороннего объекта, а потом привязать интерфейсный элемент или его свойство к этому ресурсу. Класс ObjectDataProvider может использоваться для привязки любых объектов, но мы рассмотрим его применение на предыдущем примере. Это позволит не делать подготовительную работу и не отвлекаться на новый код, а сосредоточиться только на нюансах привязки.

  • Добавьте в файле Window1.xaml к открывающему дескриптору окна Window строку подключения пространства имен приложения
  • <Window x:Class="DataBindingRelation.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="300" Width="300" 
        Title="WPF и типизированный DataSet" 
        MinHeight="300" 
        MinWidth="300"
        WindowStartupLocation="CenterScreen"
            
        xmlns:local="clr-namespace:DataBindingRelation"
        >

    Конечно, подключение пространства имен можно поместить и в открывающий дескриптор вкладки Page4

    <TabItem Header="Page4" xmlns:local="clr-namespace:DataBindingRelation">

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

  • Добавьте в контейнер TabControl файла Window1.xaml разметку для новой вкладки Page4
  • <!-- Привязка к объекту с помощью класса ObjectDataProvider -->
                <TabItem Header="Page4">
                    <TabItem.Resources>
                        <ObjectDataProvider 
                            x:Key="myObjectProvider"
                            ObjectType="{x:Type local:MyObject}"
                            MethodName="GetCollection" />
                    </TabItem.Resources>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="2*" />
                                <ColumnDefinition Width="1.5*" />
                                <ColumnDefinition Width="2*" />
                            </Grid.ColumnDefinitions>
                            
                            <TextBlock Grid.Column="0" HorizontalAlignment="Center">ContactName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="0"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Source={StaticResource myObjectProvider},
                                                      Path=Customers}"
                                DisplayMemberPath="ContactName"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="1" HorizontalAlignment="Center">OrderDate</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="1"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Source={StaticResource myObjectProvider},
                                                      Path=Customers/CustomersOrders}"
                                DisplayMemberPath="OrderDate"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="2" HorizontalAlignment="Center">ProductName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="2"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Source={StaticResource myObjectProvider},
                                                      Path=Customers/CustomersOrders/Orders_Order_Details_Extended}"
                                DisplayMemberPath="ProductName"
                                />
                        </Grid>
                        <Grid Grid.Row="1">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            
                            <TextBlock Grid.Row="0" Margin="0,0,5,0">ShipName:</TextBlock>
                            <TextBox Grid.Row="0" Grid.Column="1" 
                                Text="{Binding Source={StaticResource myObjectProvider}, 
                                               Path=Customers/CustomersOrders/ShipName, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="1">ShipAddress:</TextBlock>
                            <TextBox Grid.Row="1" Grid.Column="1" 
                                Text="{Binding Source={StaticResource myObjectProvider}, 
                                               Path=Customers/CustomersOrders/ShipAddress, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="2">ShipCity:</TextBlock>
                            <TextBox Grid.Row="2" Grid.Column="1" 
                                Text="{Binding Source={StaticResource myObjectProvider}, 
                                               Path=Customers/CustomersOrders/ShipCity, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="3">ShipCountry:</TextBlock>
                            <TextBox Grid.Row="3" Grid.Column="1" 
                                Text="{Binding Source={StaticResource myObjectProvider}, 
                                               Path=Customers/CustomersOrders/ShipCountry, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                               />
                        </Grid>
                    </Grid>
                </TabItem>
  • Добавьте в файл Window1.xaml.cs после класса Window1 новый класс с именем MyObject с единственным методом GetCollection(), возвращающим реляционный набор данных
  • namespace DataBindingRelation
    {
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
        
                Page1();
                Page2();
                Page3();
            }
        
            .........................................................
        }
        
        // Вкладка Page4. Объект с методом, возвращающим коллекцию привязки
        class MyObject
        {
            public DataSet GetCollection()
            {
                // Извлекаем в поле строку соединения из файла App.config
                String connectionString = System.Configuration.
                    ConfigurationManager.ConnectionStrings["MyNorthwind"].ConnectionString;
        
                DataSet dataSet = new DataSet();// Создаем множественный набор данных
        
                // Заполняем множественный набор данных из БД
                using (OleDbConnection conn = new OleDbConnection(connectionString))
                {
                    OleDbCommand selectCommand = conn.CreateCommand();
                    OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
        
                    // Загружает всю таблицу Customers
                    selectCommand.CommandText = "SELECT * FROM Customers";
                    adapter.Fill(dataSet, "Customers");
        
                    // Загружает всю таблицу Orders
                    selectCommand.CommandText = "SELECT * FROM Orders";
                    adapter.Fill(dataSet, "Orders");
        
                    // Загружаем представление по SQL-запросу
                    selectCommand.CommandText = @"SELECT DISTINCTROW [Order Details].OrderID, [Order Details].ProductID, 
     Products.ProductName,
                        [Order Details].UnitPrice, [Order Details].Quantity, [Order Details].Discount,
                        CCur([Order Details].[UnitPrice]*[Quantity]*(1-[Discount])/100)*100 AS ExtendedPrice 
                        FROM Products INNER JOIN [Order Details] ON Products.ProductID = [Order Details].ProductID 
                        ORDER BY [Order Details].OrderID";
                    adapter.Fill(dataSet, "Order_Details_Extended");
                }
        
                // Создание отношения между таблицами Customers и Orders
                dataSet.Relations.Add("CustomersOrders",
                    dataSet.Tables["Customers"].Columns["CustomerID"],
                    dataSet.Tables["Orders"].Columns["CustomerID"]);
        
                // Создание отношения между таблицами Orders и Order_Details_Extended
                // Немного другим способом
                DataColumn parentColumn = dataSet.Tables["Orders"].Columns["OrderID"];
                DataColumn childColumn = dataSet.Tables["Order_Details_Extended"].Columns["OrderID"];
                DataRelation relation = new DataRelation(
                    "Orders_Order_Details_Extended",
                    parentColumn, childColumn);
                dataSet.Relations.Add(relation);
        
                return dataSet;
            }
        }
    }

    По сути, метод класса MyObject использует тот же самый код, который мы создавали для вкладки Page3. Здесь можно было бы кое-что подсократить, например, сразу упаковать код вкладки Page3 в метод и его использовать в примере вкладки Page4. Но не станем мелочиться на учебных примерах, а будем последовательны и дисциплинированы.

  • Запустите приложение - опять на вкладке
  • Свойства класса ObjectDataProvider
    Свойство Тип Описание
    ConstructorParameters System.Collections.IList Список параметров для передачи конструктору, если они есть
    IsAsynchronous bool Устанавливает режим создания объекта и вызова метода в текущем или отдельном потоке
    MethodName string Имя вызываемого метода, возвращающего объект или коллекцию привязки
    MethodParameters System.Collections.IList Список параметров для передачи методу, если они есть
    ObjectInstance object Задает или извлекает объект, используемый в качестве источника привязки
    ObjectType System.Type Задает или извлекает тип объекта, содержащего метод привязки

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

    Вкладка Page5. Привязка интерфейса WPF к XML-данным с помощью класса XmlDataProvider

    Класс XmlDataProvider позволяет создать логический ресурс из данных XML -формата и уже к нему привязывать интерфейс WPF. Рассмотрим это на простом примере.

  • В контейнер TabControl файла Window1.xaml добавьте вкладку Page5
  • <!-- Привязка к объекту с помощью класса XmlDataProvider -->
                <TabItem Header="Page5" Selector.IsSelected="True">
                    <TabItem.Resources>
                        <XmlDataProvider
                            x:Key="myDocument" 
                            Source="Data/XmlDocument.xml" />
                    </TabItem.Resources>
                    <Grid DataContext="{Binding ElementName=listCustomers5, Path=SelectedItem}">
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="2*" />
                                <ColumnDefinition Width="1.5*" />
                                <ColumnDefinition Width="2*" />
                            </Grid.ColumnDefinitions>
                            
                            <TextBlock Grid.Column="0" HorizontalAlignment="Center">ContactName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="0"
                                Name="listCustomers5"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Source={StaticResource myDocument},
                                                      XPath=NewDataSet/Customers}"
                                DisplayMemberPath="ContactName"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="1" HorizontalAlignment="Center">CustomerID</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="1"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Source={StaticResource myDocument},
                                                      XPath=NewDataSet/Orders}"
                                DisplayMemberPath="CustomerID"
                                />
                            
                            <TextBlock Grid.Column="2" HorizontalAlignment="Center">ProductName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="2"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Source={StaticResource myDocument},
                                                      XPath=NewDataSet/Order_Details_Extended}"
                                DisplayMemberPath="ProductName"
                                />
                        </Grid>
                        <Grid Grid.Row="1">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            
                            <TextBlock Grid.Row="0" Margin="0,0,5,0">CompanyName:</TextBlock>
                            <TextBox Grid.Row="0" Grid.Column="1" 
                                Text="{Binding XPath=CompanyName, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="1">Address:</TextBlock>
                            <TextBox Grid.Row="1" Grid.Column="1" 
                                Text="{Binding XPath=Address, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="2">City:</TextBlock>
                            <TextBox Grid.Row="2" Grid.Column="1" 
                                Text="{Binding XPath=City, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="3">Country:</TextBlock>
                            <TextBox Grid.Row="3" Grid.Column="1" 
                                Text="{Binding XPath=Country, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                               />
                        </Grid>
                    </Grid>
                </TabItem>
  • В файл Window1.xaml.cs добавьте код создания XML -документа
  • public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
        
                Page1();
                Page2();
                Page3();
                Page5();
            }
            ............................................................
        
            #region Вкладка Page5
            void Page5()
            {
                // Готовим путь к сохранению XML-документа
                String path;
                //path = System.Environment.CurrentDirectory + "\\Data";//Вариант 1
                path = AppDomain.CurrentDomain.BaseDirectory + "Data";  //Вариант 2
                string fileName = System.IO.Path.Combine(path, "XmlDocument.xml");
        
                // Создаем только раз
                if (System.IO.File.Exists(fileName))
                    return;
        
                // Создаем заполненный реляционный набор данных 
                MyObject myObject = new MyObject();
                DataSet dataSet = myObject.GetCollection();
        
                // Создаем XML-документ и загружаем его данными набора
                System.Xml.XmlDataDocument document =
                    new System.Xml.XmlDataDocument(dataSet);
        
                // Сохраняем документ в файле
                document.Save(fileName);
        
                // Бросаем набор и документ (для очевидности!) 
                dataSet.Dispose();
                document = null;
        
                // Вызываем сборщик мусора (для разнообразия!)
                GC.WaitForFullGCComplete(); // Ждать завершения сборки мусора
                GC.Collect();               // Начать сборку мусора
            }
            #endregion
        }
  • Запустите приложение, чтобы создать XML -документ
  • В панели Solution Explorer для узла Data выполните команду Add/Existing Item и присоедините к проекту файл XmlDocument.xml из папки Data, дочерней к каталогу нахождения исполнимой сборки приложения ( bin\Debug\Data или bin\Release\Data ). Без последнего действия при запуске через оболочку вкладка Page5 работать не будет, а при запуске напрямую через исполнимую сборку - будет
  • Проследите, чтобы свойства присоединенного файла XmlDocument.xml в панели Properties были
  • Build Action=Content
  • Copy to Output Directory=Copy if newer
  • Запустите приложение -
  • Атрибут IsSynchronizedWithCurrentItem="True" из именованного списка listCustomers5 можно удалить, но тогда текстовые поля получат первое значение только при выделении элемента списка.

  • Найдите в подкаталоге Data папки размещения исполнимой сборки файл XmlDocument.xml и откройте его
  • Мы видим, что все реляционные зависимости между таблицами при преобразовании из набора DataSet в XML -формат были утрачены, поэтому нам не удалось построить такую же функциональность, как в предыдущих примерах. Но пример применения класса XmlDataProvider в качестве источника данных для привязываемых элементов интерфейса WPF мы все-таки рассмотрели и отправную точку для более глубокого изучения обозначили.

    Дальше сами: автобусом до сходней, а там по шпалам, опять по шпалам...!

    Страницы:

    Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.

    Реляционная база данных - база данных, основанная на реляционной модели данных. Слово "реляционный" происходит от англ. relation (отношение). Для работы с реляционными БД применяют реляционные СУБД. Использование реляционных баз данных было предложено доктором Коддом из компании IBM в 1970 году.

    Чаще всего данные в хранилище являются именно реляционными. Отдельные таблицы связаны между собой определенным отношением и настоящая СУБД должна строго следить за целостностью этих связей. Не вдаваясь в теорию реляционных баз данных отметим, что таблица может иметь ключи. Различают несколько видов ключей, но наибольшее распространение получили первичный и внешний ключи.

    Первичный ключ (primary key) - на практике термин обозначает поле (столбец) или группу полей таблицы базы данных, значение которого (или комбинация значений которых) используется в качестве уникального идентификатора записи (строки) этой таблицы. Внешний ключ (foreign key) - поле (столбец) таблицы, предназначенное для хранения значения первичного ключа другой таблицы с целью организации связи между этими таблицами.

    Упражнение 1. Привязка WPF к реляционным таблицам набора данных

    Применяя объектную модель ADO.NET, мы сами вынуждены строить аналог СУБД и поэтому сами должны учитывать связи, которые существуют между таблицами сырых данных в хранилище. В ADO.NET отношения между таблицами объектной модели устанавливаются с помощью объектов DataRelation, которые попарно связывают столбец в одной таблице со столбцом в другой. В WPF связанные записи можно извлечь, привязавшись к зависимостям данных.

    Извлеченные данные можно просматривать, редактировать и фиксировать изменения обратной отсылкой в БД. Для редактирования привязанных данных используется двунаправленная привязка. Но здесь мы будем пользоваться только однонаправленной привязкой для просмотра данных, чтобы не усложнять задачу. Этого пока будет достаточно.

    Вначале воспользуемся инструментами оболочки для извлечения и отображения реляционных данных, потом повторим задачу вручную. По-прежнему будем работать с файловой БД Northwind.mdb и еще раз приведем ее структуру.

    Вкладка Page1. Построение типизированного DataSet с помощью мастера

  • Командой File/New/Project создайте новый проект WPF Application с именем
  • В панели Solution Explorer добавьте к корню проекта контекстной командой Add/New Folder каталог Data и скопируйте в него командой Add/Existing Item файл Northwind.mdb из прилагаемого каталога Source.
  • Откроется окно мастера Data Source Configuration Wizard на вкладке Choose Your Database Object.

    Если окно мастера Data Source Configuration Wizard на этом шаге не появилось (Нано-Сидоркин!), перейдите к выполнению вкладки Page2, а затем вернитесь к этому примеру.
  • Разверните узел
  • Разверните узел Views (Представления), поднимите флажок на Order Details Extended и щелкните на кнопке Finish
  • Мастер оболочки создаст класс типизированного набора данных. Мы уже говорили, что набор данных DataSet объектной модели ADO.NET (типизированный или обычный) является аналогом реляционной БД с настраиваемым набором таблиц, связей между ними, условиями выборки и обновления данных. До своего заполнения данными набор представляет собой только схему или структуру виртуальных данных.

    Последним действием мы включили в набор представление ( Views ) с именем Order Details Extended. Представления данных Views обеспечивают определенный уровень абстракции для источника данных, позволяя виртуально менять структуру исходных данных, не затрагивая сами данные. В представлении можно заранее указать нужные таблицы, определить связи между ними, добавить вычислимые поля, а также именованные запросы без необходимости вносить модификации в исходные данные. Это как бы шаблон нужного среза с оригинальных данных.

  • В панели
  • Мы видим, что представление Order Details Extended является некой искусственной абстракцией реальных данных. Оно отображается как таблица, готовая к заполнению соответствующим SQL -запросом. Очень часто представления Views и называют запросами ( Query ), заранее заготовленными, как и хранимые процедуры Stored Procedures.

    Таблица Customers хранит уникальные сведения о заказчиках. Каждый заказчик может сделать несколько заказов, которые фиксируются в таблице Orders. Каждый заказ может включать в себя несколько продуктов из таблицы Products, доступ к которой можно получить через таблицу Order Details. В нашем случае обе реальные таблицы источника объединены в виртуальную таблицу-представление Order Details Extended набора данных, создаваемую следующим SQL -запросом.

    SELECT DISTINCTROW [Order Details].OrderID, [Order Details].ProductID, 
       Products.ProductName,
      [Order Details].UnitPrice, [Order Details].Quantity, [Order Details].Discount,
      CCur([Order Details].[UnitPrice]*[Quantity]*(1-[Discount])/100)*100 AS ExtendedPrice 
      FROM Products INNER JOIN [Order Details] ON Products.ProductID = [Order Details].ProductID 
      ORDER BY [Order Details].OrderID;

    Мы хотим создать три списка пользовательского интерфейса, каждый из которых будет привязан к соответствующей таблице. Выбор заказчика ( Customer ) в первом списке должен привести к заполнению второго списка заказами, а выделенный заказ ( Order ) во втором списке должен заполнить продуктами третий список. Как видно из рисунка, зависимость Customers-Orders в типизированном наборе уже существует. Осталось добавить связь Orders-Order Details Extended. Выполним это.

  • Вызовите контекстное меню для NorthwindDataSet.xsd в режиме Dataset Designer и выполните команду Add/Relation
  • Настройте окно
  • Щелчком на кнопке
  • Выделите поочередно каждую из двух изображенных связей и в панели Properties посмотрите их названия: это будут связи
  • CustomersOrders
  • Orders_Order_Details_Extended
  • Откомпилируйте проект, чтобы проверить отсутствие синтаксических ошибок
  • Теперь созданный тип набора данных нужно превратить в экземпляр, заполнить данными и отобразить в окне через привязанные элементы пользовательского интерфейса.

  • Заполните типизированный набор данных в классе Window1 процедурного кода так
  • using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
        
    // Добавили пространства имен типизированных классов
    using DataBindingRelation.Data;
    using DataBindingRelation.Data.NorthwindDataSetTableAdapters;
        
    namespace DataBindingRelation
    {
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
        
                Page1();
            }
        
            #region Вкладка Page1
            void Page1()
            {
                // Создание экземпляров сгененированных классов
                NorthwindDataSet dataSet = new NorthwindDataSet();
                CustomersTableAdapter custAdap = new CustomersTableAdapter();
                OrdersTableAdapter ordAdap = new OrdersTableAdapter();
                Order_Details_ExtendedTableAdapter ord_det_extAdap =
                    new Order_Details_ExtendedTableAdapter();
        
                // Потабличное наполнение экземпляра типизированного набора данных
                custAdap.Fill(dataSet.Customers);
                ordAdap.Fill(dataSet.Orders);
                ord_det_extAdap.Fill(dataSet.Order_Details_Extended);
            }
            #endregion
        }
    }
  • Сконструируйте интерфейсную часть с вкладкой Page1 в файле Window1.xaml следующим образом
  • <Window x:Class="DataBindingRelation.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="300" Width="300" 
        Title="WPF и типизированный DataSet" 
        MinHeight="300" 
        MinWidth="300"
        WindowStartupLocation="CenterScreen"
        >
        <Window.Resources>
            <SolidColorBrush x:Key="ControlColorBrush" 
                Color="{x:Static SystemColors.ControlColor}" />
        </Window.Resources>
        <Grid Background="{StaticResource ResourceKey=ControlColorBrush}">
            <TabControl>
                <!-- Привязка к типизированному DataSet -->
                <TabItem Header="Page1">
                    <Grid Name="grid1">
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="2*" />
                                <ColumnDefinition Width="1.5*" />
                                <ColumnDefinition Width="2*" />
                            </Grid.ColumnDefinitions>
                            
                            <TextBlock Grid.Column="0" HorizontalAlignment="Center">ContactName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="0"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding}"
                                DisplayMemberPath="ContactName"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="1" HorizontalAlignment="Center">OrderDate</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="1"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Path=CustomersOrders}"
                                DisplayMemberPath="OrderDate"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="2" HorizontalAlignment="Center">ProductName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="2"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Path=CustomersOrders/Orders_Order_Details_Extended}"
                                DisplayMemberPath="ProductName"
                                />
                        </Grid>
                        <Grid Grid.Row="1">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            
                            <TextBlock Grid.Row="0" Margin="0,0,5,0">ShipName:</TextBlock>
                            <TextBox Grid.Row="0" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipName, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="1">ShipAddress:</TextBlock>
                            <TextBox Grid.Row="1" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipAddress, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="2">ShipCity:</TextBlock>
                            <TextBox Grid.Row="2" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipCity, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="3">ShipCountry:</TextBlock>
                            <TextBox Grid.Row="3" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipCountry, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                               />
                        </Grid>
                    </Grid>
                </TabItem>
            </TabControl>
        </Grid>
    </Window>
  • Подключите к свойству DataContext сетки таблицу Customers из типизированного набора данных для заполнения первого списка
  • #region Вкладка Page1
            void Page1()
            {
                .................................................
        
                // Подключение набора данных к интерфейсу
                grid1.DataContext = dataSet.Customers;
            }
            #endregion

    Остальные два списка будут привязаны к зависимостям CustomersOrders и Orders_Order_Details_Extended. Для текстовых полей источником привязки будет служить именованная зависимость CustomersOrders.

  • Запустите приложение и испытайте его работу
  • Списки управляют связанными данными таблиц, конкретизируя их слева направо. Это происходит за счет установленного в двух первых списках свойства IsSynchronizedWithCurrentItem="True". Обратите внимание на работу верстки (компоновка, раскладка) интерфейсных элементов, которая обеспечивает их автоматическое масштабирование при изменении размеров окна. Названия связей можно всегда изменить в панели Properties режима Dataset Designer, но тогда их нужно менять и в выражениях привязки разметки.

    Вкладка Page2. Повторение предыдущей задачи с новым типизированным набором

    Может случиться так (как у Нано-Сидоркина на его компьютере), что при копировании БД в проект окно мастера Data Source Configuration Wizard не откроется. Или мы захотим (можем захотеть) создать еще один типизированный набор данных с другими настройками. Как вызвать мастер Data Source Configuration Wizard из проекта WPF Application, если он нам вдруг понадобиться? Рассмотрим это.

  • Выполните команду меню оболочки
  • Выберите источник
  • Оставьте строку соединения прежней и перейдите к следующей странице мастера, в которой поднимите указанные на снимке флажки, установите имя набора и щелкните на кнопке
  • В панели Solution Explorer перетащите мышью созданный узел NorthDataSet.xsd в папку Data и раскройте его двойным щелчком в режиме Dataset Designer
  • Выполните команду Add/Relation контекстного меню и создайте новую связь с именем
  • Загляните в панель
  • Добавьте в классе Window1 следующий процедурный код
  • using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
        
    // Добавили пространства имен типизированных классов
    using DataBindingRelation.Data;
    using DataBindingRelation.Data.NorthwindDataSetTableAdapters;
    using DS2 = DataBindingRelation.Data.NorthDataSetTableAdapters;// Псевдоним
        
    namespace DataBindingRelation
    {
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
        
                Page1();
                Page2();
            }
        
            #region Вкладка Page1
            void Page1()
            {
                // Создание экземпляров сгененированных классов
                NorthwindDataSet dataSet = new NorthwindDataSet();
                CustomersTableAdapter custAdap = new CustomersTableAdapter();
                OrdersTableAdapter ordAdap = new OrdersTableAdapter();
                Order_Details_ExtendedTableAdapter ord_det_extAdap =
                    new Order_Details_ExtendedTableAdapter();
        
                // Потабличное наполнение экземпляра типизированного набора данных
                custAdap.Fill(dataSet.Customers);
                ordAdap.Fill(dataSet.Orders);
                ord_det_extAdap.Fill(dataSet.Order_Details_Extended);
        
                // Подключение набора данных к интерфейсу
                grid1.DataContext = dataSet.Customers;
            }
            #endregion
        
            #region Вкладка Page2
            void Page2()
            {
                // Создание экземпляров сгененированных классов
                NorthDataSet dataSet = new NorthDataSet();
                DS2.CustomersTableAdapter custAdap = new DS2.CustomersTableAdapter();
                DS2.OrdersTableAdapter ordAdap = new DS2.OrdersTableAdapter();
                DS2.Order_Details_ExtendedTableAdapter ord_det_extAdap =
                    new DS2.Order_Details_ExtendedTableAdapter();
        
                // Потабличное наполнение экземпляра типизированного набора данных
                custAdap.Fill(dataSet.Customers);
                ordAdap.Fill(dataSet.Orders);
                ord_det_extAdap.Fill(dataSet.Order_Details_Extended);
        
                // Подключение набора данных к интерфейсу
                grid2.DataContext = dataSet.Customers;
            }
            #endregion
        }
    }
  • Добавьте в разметку контейнера TabControl файла Window1.xaml следующий дескрипторный код с вкладкой Page2
  • <!-- Привязка к типизированному DataSet -->
                <TabItem Header="Page2">
                    <Grid Name="grid2">
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="2*" />
                                <ColumnDefinition Width="1.5*" />
                                <ColumnDefinition Width="2*" />
                            </Grid.ColumnDefinitions>
                            
                            <TextBlock Grid.Column="0" HorizontalAlignment="Center">ContactName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="0"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding}"
                                DisplayMemberPath="ContactName"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="1" HorizontalAlignment="Center">OrderDate</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="1"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Path=CustomersOrders}"
                                DisplayMemberPath="OrderDate"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="2" HorizontalAlignment="Center">ProductName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="2"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Path=CustomersOrders/Orders_Order_Details_Extended}"
                                DisplayMemberPath="ProductName"
                                />
                        </Grid>
                        <Grid Grid.Row="1">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            
                            <TextBlock Grid.Row="0" Margin="0,0,5,0">ShipName:</TextBlock>
                            <TextBox Grid.Row="0" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipName, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="1">ShipAddress:</TextBlock>
                            <TextBox Grid.Row="1" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipAddress, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="2">ShipCity:</TextBlock>
                            <TextBox Grid.Row="2" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipCity, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="3">ShipCountry:</TextBlock>
                            <TextBox Grid.Row="3" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipCountry, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                               />
                        </Grid>
                    </Grid>
                </TabItem>
  • Запустите приложение - функциональность вкладки
  • Вкладка Page3. Повторение предыдущей задачи вручную

    В двух предыдущих примерах мы пользовались типом набора данных, который автоматически сгенерировал мастер оболочки Data Source Configuration Wizard по нашим настройкам. В данном примере мы все настройки выполним сами в процедурном коде, опираясь на инфраструктуру объекта DataSet.

    Объект DataSet кроме коллекции Tables объектов DataTable содержит еще и свойство Relations, которое может адресовать экземпляр класса System.Data.DataRelationCollection. Коллекция Relations ожидает объекты DataRelation, в которых можно определять отношения между таблицами коллекции Tables. Следовательно, объект DataSet можно рассматривать как набор таблиц и отношений. Минимальное количество как таблиц, так и отношений в DataSet равно нулю.

    В данном примере мы заполним коллекцию Tables таблицами Customers, Orders и виртуальной таблицей представления Order Details Extended, установим вручную отношения между таблицами, затем привяжем набор к пользовательскому интерфейсу WPF. В итоге должен получиться такой же результат, что и для типизированного набора данных.

  • В панели
  • Установите вкладку
  • Для записи Order Details Extended вызовите контекстное меню и выполните команду Конструктор
  • Щелкните правой кнопкой мыши на заголовке (или свободном месте) окна
  • В результате мы получим строку SQL -запроса, которая формирует виртуальную таблицу представления Order Details Extended (ранее уже приводилась)

    SELECT DISTINCTROW [Order Details].OrderID, [Order Details].ProductID, Products.ProductName, 
    [Order Details].UnitPrice, [Order Details].Quantity, [Order Details].Discount, 
    CCur([Order Details].[UnitPrice]*[Quantity]*(1-[Discount])/100)*100 AS ExtendedPrice
    FROM Products INNER JOIN [Order Details] ON Products.ProductID = [Order Details].ProductID
    ORDER BY [Order Details].OrderID;
  • В панели
  • В панели
  • Заполните файл App.config следующей разметкой
  • <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <connectionStrings>
        <add name="MyNorthwind"
            connectionString="Provider=Microsoft.Jet.OLEDB.4.0;
                Data Source=|DataDirectory|\Data\Northwind.mdb"
            providerName="System.Data.OleDb" />
      </connectionStrings>
    </configuration>
  • Добавьте в файл Window1.xaml.cs следующий процедурный код
  • using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
        
    // Добавили пространства имен типизированных классов
    using DataBindingRelation.Data;
    using DataBindingRelation.Data.NorthwindDataSetTableAdapters;
    using DS2 = DataBindingRelation.Data.NorthDataSetTableAdapters;// Псевдоним
        
    // Подключение пространств имен инфраструктуры ADO.NET
    using System.Data;
    using System.Data.OleDb;
        
    namespace DataBindingRelation
    {
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
        
                Page1();
                Page2();
                Page3();
            }
        
            #region Вкладка Page1
            void Page1()
            {
                // Создание экземпляров сгененированных классов
                NorthwindDataSet dataSet = new NorthwindDataSet();
                CustomersTableAdapter custAdap = new CustomersTableAdapter();
                OrdersTableAdapter ordAdap = new OrdersTableAdapter();
                Order_Details_ExtendedTableAdapter ord_det_extAdap =
                    new Order_Details_ExtendedTableAdapter();
        
                // Потабличное наполнение экземпляра типизированного набора данных
                custAdap.Fill(dataSet.Customers);
                ordAdap.Fill(dataSet.Orders);
                ord_det_extAdap.Fill(dataSet.Order_Details_Extended);
        
                // Подключение набора данных к интерфейсу
                grid1.DataContext = dataSet.Customers;
            }
            #endregion
        
            #region Вкладка Page2
            void Page2()
            {
                // Создание экземпляров сгененированных классов
                NorthDataSet dataSet = new NorthDataSet();
                DS2.CustomersTableAdapter custAdap = new DS2.CustomersTableAdapter();
                DS2.OrdersTableAdapter ordAdap = new DS2.OrdersTableAdapter();
                DS2.Order_Details_ExtendedTableAdapter ord_det_extAdap =
                    new DS2.Order_Details_ExtendedTableAdapter();
        
                // Потабличное наполнение экземпляра типизированного набора данных
                custAdap.Fill(dataSet.Customers);
                ordAdap.Fill(dataSet.Orders);
                ord_det_extAdap.Fill(dataSet.Order_Details_Extended);
        
                // Подключение набора данных к интерфейсу
                grid2.DataContext = dataSet.Customers;
            }
            #endregion
        
            #region Вкладка Page3
            void Page3()
            {
                // Извлекаем в поле строку соединения из файла App.config
                String connectionString = System.Configuration.
                    ConfigurationManager.ConnectionStrings["MyNorthwind"].ConnectionString;
        
                DataSet dataSet = new DataSet();// Создаем множественный набор данных
        
                // Заполняем множественный набор данных из БД
                using (OleDbConnection conn = new OleDbConnection(connectionString))
                {
                    OleDbCommand selectCommand = conn.CreateCommand();
                    OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
        
                    // Загружает всю таблицу Customers
                    selectCommand.CommandText = "SELECT * FROM Customers";
                    adapter.Fill(dataSet, "Customers");
        
                    // Загружает всю таблицу Orders
                    selectCommand.CommandText = "SELECT * FROM Orders";
                    // Загружаем и сразу этой порции данных назначаем имя в наборе
                    adapter.Fill(dataSet, "Orders");
        
                    // Загружаем представление по SQL-запросу
                    selectCommand.CommandText = @"SELECT DISTINCTROW [Order Details].OrderID, [Order Details].ProductID,
       Products.ProductName,
                        [Order Details].UnitPrice, [Order Details].Quantity, [Order Details].Discount,
                        CCur([Order Details].[UnitPrice]*[Quantity]*(1-[Discount])/100)*100 AS ExtendedPrice 
                        FROM Products INNER JOIN [Order Details] ON Products.ProductID = [Order Details].ProductID 
                        ORDER BY [Order Details].OrderID";
                    adapter.Fill(dataSet, "Order_Details_Extended");
                }
        
                // Создание отношения между таблицами Customers и Orders
                dataSet.Relations.Add("CustomersOrders",
                    dataSet.Tables["Customers"].Columns["CustomerID"],
                    dataSet.Tables["Orders"].Columns["CustomerID"]);
        
                // Создание отношения между таблицами Orders и Order_Details_Extended
                // Немного другим способом
                DataColumn parentColumn = dataSet.Tables["Orders"].Columns["OrderID"];
                DataColumn childColumn = dataSet.Tables["Order_Details_Extended"].Columns["OrderID"];
                DataRelation relation = new DataRelation(
                    "Orders_Order_Details_Extended",
                    parentColumn, childColumn);
                dataSet.Relations.Add(relation);
        
                // Подключение набора данных к интерфейсу
                grid3.DataContext = dataSet.Tables["Customers"];
            }
            #endregion
        }
    }
  • В контейнер TabControl добавьте копию вкладки Page2 и переименуйте в ней первую сетку на grid3, чтобы разметка стала такой
  • <!-- Привязка к нетипизированному DataSet -->
                <TabItem Header="Page3">
                    <Grid Name="grid3">
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="2*" />
                                <ColumnDefinition Width="1.5*" />
                                <ColumnDefinition Width="2*" />
                            </Grid.ColumnDefinitions>
                            
                            <TextBlock Grid.Column="0" HorizontalAlignment="Center">ContactName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="0"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding}"
                                DisplayMemberPath="ContactName"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="1" HorizontalAlignment="Center">OrderDate</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="1"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Path=CustomersOrders}"
                                DisplayMemberPath="OrderDate"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="2" HorizontalAlignment="Center">ProductName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="2"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Path=CustomersOrders/Orders_Order_Details_Extended}"
                                DisplayMemberPath="ProductName"
                                />
                        </Grid>
                        <Grid Grid.Row="1">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            
                            <TextBlock Grid.Row="0" Margin="0,0,5,0">ShipName:</TextBlock>
                            <TextBox Grid.Row="0" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipName, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="1">ShipAddress:</TextBlock>
                            <TextBox Grid.Row="1" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipAddress, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="2">ShipCity:</TextBlock>
                            <TextBox Grid.Row="2" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipCity, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="3">ShipCountry:</TextBlock>
                            <TextBox Grid.Row="3" Grid.Column="1" 
                                Text="{Binding Path=CustomersOrders/ShipCountry, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                               />
                        </Grid>
                    </Grid>
                </TabItem>
  • Запустите приложение - получим результат, в точности совпадающий с двумя предыдущими
  • Вкладка Page4. Привязка интерфейса WPF к объекту данных с помощью класса ObjectDataProvider

    До сих пор мы пользовались свойством DataContext для назначения источника привязки. WPF имеет еще один механизм назначения источника - через ресурс с помощью класса ObjectDataProvider. Класс позволяет создать ресурс, ссылающийся на коллекцию данных, возвращенную методом стороннего объекта, а потом привязать интерфейсный элемент или его свойство к этому ресурсу. Класс ObjectDataProvider может использоваться для привязки любых объектов, но мы рассмотрим его применение на предыдущем примере. Это позволит не делать подготовительную работу и не отвлекаться на новый код, а сосредоточиться только на нюансах привязки.

  • Добавьте в файле Window1.xaml к открывающему дескриптору окна Window строку подключения пространства имен приложения
  • <Window x:Class="DataBindingRelation.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="300" Width="300" 
        Title="WPF и типизированный DataSet" 
        MinHeight="300" 
        MinWidth="300"
        WindowStartupLocation="CenterScreen"
            
        xmlns:local="clr-namespace:DataBindingRelation"
        >

    Конечно, подключение пространства имен можно поместить и в открывающий дескриптор вкладки Page4

    <TabItem Header="Page4" xmlns:local="clr-namespace:DataBindingRelation">

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

  • Добавьте в контейнер TabControl файла Window1.xaml разметку для новой вкладки Page4
  • <!-- Привязка к объекту с помощью класса ObjectDataProvider -->
                <TabItem Header="Page4">
                    <TabItem.Resources>
                        <ObjectDataProvider 
                            x:Key="myObjectProvider"
                            ObjectType="{x:Type local:MyObject}"
                            MethodName="GetCollection" />
                    </TabItem.Resources>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="2*" />
                                <ColumnDefinition Width="1.5*" />
                                <ColumnDefinition Width="2*" />
                            </Grid.ColumnDefinitions>
                            
                            <TextBlock Grid.Column="0" HorizontalAlignment="Center">ContactName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="0"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Source={StaticResource myObjectProvider},
                                                      Path=Customers}"
                                DisplayMemberPath="ContactName"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="1" HorizontalAlignment="Center">OrderDate</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="1"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Source={StaticResource myObjectProvider},
                                                      Path=Customers/CustomersOrders}"
                                DisplayMemberPath="OrderDate"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="2" HorizontalAlignment="Center">ProductName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="2"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Source={StaticResource myObjectProvider},
                                                      Path=Customers/CustomersOrders/Orders_Order_Details_Extended}"
                                DisplayMemberPath="ProductName"
                                />
                        </Grid>
                        <Grid Grid.Row="1">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            
                            <TextBlock Grid.Row="0" Margin="0,0,5,0">ShipName:</TextBlock>
                            <TextBox Grid.Row="0" Grid.Column="1" 
                                Text="{Binding Source={StaticResource myObjectProvider}, 
                                               Path=Customers/CustomersOrders/ShipName, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="1">ShipAddress:</TextBlock>
                            <TextBox Grid.Row="1" Grid.Column="1" 
                                Text="{Binding Source={StaticResource myObjectProvider}, 
                                               Path=Customers/CustomersOrders/ShipAddress, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="2">ShipCity:</TextBlock>
                            <TextBox Grid.Row="2" Grid.Column="1" 
                                Text="{Binding Source={StaticResource myObjectProvider}, 
                                               Path=Customers/CustomersOrders/ShipCity, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="3">ShipCountry:</TextBlock>
                            <TextBox Grid.Row="3" Grid.Column="1" 
                                Text="{Binding Source={StaticResource myObjectProvider}, 
                                               Path=Customers/CustomersOrders/ShipCountry, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                               />
                        </Grid>
                    </Grid>
                </TabItem>
  • Добавьте в файл Window1.xaml.cs после класса Window1 новый класс с именем MyObject с единственным методом GetCollection(), возвращающим реляционный набор данных
  • namespace DataBindingRelation
    {
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
        
                Page1();
                Page2();
                Page3();
            }
        
            .........................................................
        }
        
        // Вкладка Page4. Объект с методом, возвращающим коллекцию привязки
        class MyObject
        {
            public DataSet GetCollection()
            {
                // Извлекаем в поле строку соединения из файла App.config
                String connectionString = System.Configuration.
                    ConfigurationManager.ConnectionStrings["MyNorthwind"].ConnectionString;
        
                DataSet dataSet = new DataSet();// Создаем множественный набор данных
        
                // Заполняем множественный набор данных из БД
                using (OleDbConnection conn = new OleDbConnection(connectionString))
                {
                    OleDbCommand selectCommand = conn.CreateCommand();
                    OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
        
                    // Загружает всю таблицу Customers
                    selectCommand.CommandText = "SELECT * FROM Customers";
                    adapter.Fill(dataSet, "Customers");
        
                    // Загружает всю таблицу Orders
                    selectCommand.CommandText = "SELECT * FROM Orders";
                    adapter.Fill(dataSet, "Orders");
        
                    // Загружаем представление по SQL-запросу
                    selectCommand.CommandText = @"SELECT DISTINCTROW [Order Details].OrderID, [Order Details].ProductID, 
     Products.ProductName,
                        [Order Details].UnitPrice, [Order Details].Quantity, [Order Details].Discount,
                        CCur([Order Details].[UnitPrice]*[Quantity]*(1-[Discount])/100)*100 AS ExtendedPrice 
                        FROM Products INNER JOIN [Order Details] ON Products.ProductID = [Order Details].ProductID 
                        ORDER BY [Order Details].OrderID";
                    adapter.Fill(dataSet, "Order_Details_Extended");
                }
        
                // Создание отношения между таблицами Customers и Orders
                dataSet.Relations.Add("CustomersOrders",
                    dataSet.Tables["Customers"].Columns["CustomerID"],
                    dataSet.Tables["Orders"].Columns["CustomerID"]);
        
                // Создание отношения между таблицами Orders и Order_Details_Extended
                // Немного другим способом
                DataColumn parentColumn = dataSet.Tables["Orders"].Columns["OrderID"];
                DataColumn childColumn = dataSet.Tables["Order_Details_Extended"].Columns["OrderID"];
                DataRelation relation = new DataRelation(
                    "Orders_Order_Details_Extended",
                    parentColumn, childColumn);
                dataSet.Relations.Add(relation);
        
                return dataSet;
            }
        }
    }

    По сути, метод класса MyObject использует тот же самый код, который мы создавали для вкладки Page3. Здесь можно было бы кое-что подсократить, например, сразу упаковать код вкладки Page3 в метод и его использовать в примере вкладки Page4. Но не станем мелочиться на учебных примерах, а будем последовательны и дисциплинированы.

  • Запустите приложение - опять на вкладке
  • Свойства класса ObjectDataProvider
    Свойство Тип Описание
    ConstructorParameters System.Collections.IList Список параметров для передачи конструктору, если они есть
    IsAsynchronous bool Устанавливает режим создания объекта и вызова метода в текущем или отдельном потоке
    MethodName string Имя вызываемого метода, возвращающего объект или коллекцию привязки
    MethodParameters System.Collections.IList Список параметров для передачи методу, если они есть
    ObjectInstance object Задает или извлекает объект, используемый в качестве источника привязки
    ObjectType System.Type Задает или извлекает тип объекта, содержащего метод привязки

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

    Вкладка Page5. Привязка интерфейса WPF к XML-данным с помощью класса XmlDataProvider

    Класс XmlDataProvider позволяет создать логический ресурс из данных XML -формата и уже к нему привязывать интерфейс WPF. Рассмотрим это на простом примере.

  • В контейнер TabControl файла Window1.xaml добавьте вкладку Page5
  • <!-- Привязка к объекту с помощью класса XmlDataProvider -->
                <TabItem Header="Page5" Selector.IsSelected="True">
                    <TabItem.Resources>
                        <XmlDataProvider
                            x:Key="myDocument" 
                            Source="Data/XmlDocument.xml" />
                    </TabItem.Resources>
                    <Grid DataContext="{Binding ElementName=listCustomers5, Path=SelectedItem}">
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="2*" />
                                <ColumnDefinition Width="1.5*" />
                                <ColumnDefinition Width="2*" />
                            </Grid.ColumnDefinitions>
                            
                            <TextBlock Grid.Column="0" HorizontalAlignment="Center">ContactName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="0"
                                Name="listCustomers5"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Source={StaticResource myDocument},
                                                      XPath=NewDataSet/Customers}"
                                DisplayMemberPath="ContactName"
                                IsSynchronizedWithCurrentItem="True"
                                />
                            
                            <TextBlock Grid.Column="1" HorizontalAlignment="Center">CustomerID</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="1"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Source={StaticResource myDocument},
                                                      XPath=NewDataSet/Orders}"
                                DisplayMemberPath="CustomerID"
                                />
                            
                            <TextBlock Grid.Column="2" HorizontalAlignment="Center">ProductName</TextBlock>
                            <ListBox Grid.Row="1" Grid.Column="2"
                                Margin="0,0,0,3"
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ItemsSource="{Binding Source={StaticResource myDocument},
                                                      XPath=NewDataSet/Order_Details_Extended}"
                                DisplayMemberPath="ProductName"
                                />
                        </Grid>
                        <Grid Grid.Row="1">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            
                            <TextBlock Grid.Row="0" Margin="0,0,5,0">CompanyName:</TextBlock>
                            <TextBox Grid.Row="0" Grid.Column="1" 
                                Text="{Binding XPath=CompanyName, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="1">Address:</TextBlock>
                            <TextBox Grid.Row="1" Grid.Column="1" 
                                Text="{Binding XPath=Address, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="2">City:</TextBlock>
                            <TextBox Grid.Row="2" Grid.Column="1" 
                                Text="{Binding XPath=City, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                                />
                            
                            <TextBlock Grid.Row="3">Country:</TextBlock>
                            <TextBox Grid.Row="3" Grid.Column="1" 
                                Text="{Binding XPath=Country, Mode=OneWay}"
                                Focusable="False"
                                Margin="5,0,0,0"
                               />
                        </Grid>
                    </Grid>
                </TabItem>
  • В файл Window1.xaml.cs добавьте код создания XML -документа
  • public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
        
                Page1();
                Page2();
                Page3();
                Page5();
            }
            ............................................................
        
            #region Вкладка Page5
            void Page5()
            {
                // Готовим путь к сохранению XML-документа
                String path;
                //path = System.Environment.CurrentDirectory + "\\Data";//Вариант 1
                path = AppDomain.CurrentDomain.BaseDirectory + "Data";  //Вариант 2
                string fileName = System.IO.Path.Combine(path, "XmlDocument.xml");
        
                // Создаем только раз
                if (System.IO.File.Exists(fileName))
                    return;
        
                // Создаем заполненный реляционный набор данных 
                MyObject myObject = new MyObject();
                DataSet dataSet = myObject.GetCollection();
        
                // Создаем XML-документ и загружаем его данными набора
                System.Xml.XmlDataDocument document =
                    new System.Xml.XmlDataDocument(dataSet);
        
                // Сохраняем документ в файле
                document.Save(fileName);
        
                // Бросаем набор и документ (для очевидности!) 
                dataSet.Dispose();
                document = null;
        
                // Вызываем сборщик мусора (для разнообразия!)
                GC.WaitForFullGCComplete(); // Ждать завершения сборки мусора
                GC.Collect();               // Начать сборку мусора
            }
            #endregion
        }
  • Запустите приложение, чтобы создать XML -документ
  • В панели Solution Explorer для узла Data выполните команду Add/Existing Item и присоедините к проекту файл XmlDocument.xml из папки Data, дочерней к каталогу нахождения исполнимой сборки приложения ( bin\Debug\Data или bin\Release\Data ). Без последнего действия при запуске через оболочку вкладка Page5 работать не будет, а при запуске напрямую через исполнимую сборку - будет
  • Проследите, чтобы свойства присоединенного файла XmlDocument.xml в панели Properties были
  • Build Action=Content
  • Copy to Output Directory=Copy if newer
  • Запустите приложение -
  • Атрибут IsSynchronizedWithCurrentItem="True" из именованного списка listCustomers5 можно удалить, но тогда текстовые поля получат первое значение только при выделении элемента списка.

  • Найдите в подкаталоге Data папки размещения исполнимой сборки файл XmlDocument.xml и откройте его
  • Мы видим, что все реляционные зависимости между таблицами при преобразовании из набора DataSet в XML -формат были утрачены, поэтому нам не удалось построить такую же функциональность, как в предыдущих примерах. Но пример применения класса XmlDataProvider в качестве источника данных для привязываемых элементов интерфейса WPF мы все-таки рассмотрели и отправную точку для более глубокого изучения обозначили.

    Дальше сами: автобусом до сходней, а там по шпалам, опять по шпалам...!

    Вернуться к учебному плану