Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.
Реляционная база данных - база данных, основанная на реляционной модели данных. Слово "реляционный" происходит от англ. relation (отношение). Для работы с реляционными БД применяют реляционные СУБД. Использование реляционных баз данных было предложено доктором Коддом из компании IBM в 1970 году.
Чаще всего данные в хранилище являются именно реляционными. Отдельные таблицы связаны между собой определенным отношением и настоящая СУБД должна строго следить за целостностью этих связей. Не вдаваясь в
Первичный ключ (primary key) - на практике термин обозначает поле (столбец) или группу полей таблицы базы данных, значение которого (или комбинация значений которых) используется в качестве уникального идентификатора записи (строки) этой таблицы. Внешний ключ (foreign key) - поле (столбец) таблицы, предназначенное для хранения значения первичного ключа другой таблицы с целью организации связи между этими таблицами.
Применяя объектную модель ADO.NET, мы сами вынуждены строить аналог СУБД и поэтому сами должны учитывать связи, которые существуют между таблицами сырых данных в хранилище. В ADO.NET отношения между таблицами объектной модели устанавливаются с помощью объектов DataRelation, которые попарно связывают столбец в одной таблице со столбцом в другой. В WPF связанные записи можно извлечь, привязавшись к зависимостям данных.
Извлеченные данные можно просматривать, редактировать и фиксировать изменения обратной отсылкой в БД. Для редактирования привязанных данных используется двунаправленная привязка. Но здесь мы будем пользоваться только однонаправленной привязкой для просмотра данных, чтобы не усложнять задачу. Этого пока будет достаточно.
Вначале воспользуемся инструментами оболочки для извлечения и отображения реляционных данных, потом повторим задачу вручную. По-прежнему будем работать с файловой БД Northwind.mdb и еще раз приведем ее структуру.

Командой 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.

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. В нашем случае обе
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 посмотрите их названия: это будут связиCustomersOrdersOrders_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, но тогда их нужно менять и в выражениях привязки разметки.
Может случиться так (как у Нано-Сидоркина на его компьютере), что при копировании БД в проект окно мастера 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>
Запустите приложение - функциональность вкладки В двух предыдущих примерах мы пользовались типом набора данных, который автоматически сгенерировал мастер оболочки 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>
Запустите приложение - получим результат, в точности совпадающий с двумя предыдущими
До сих пор мы пользовались свойством 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. Но не станем мелочиться на учебных примерах, а будем последовательны и дисциплинированы.
Запустите приложение - опять на вкладке | Свойство | Тип | Описание |
|---|---|---|
ConstructorParameters |
System.Collections. |
Список параметров для передачи конструктору, если они есть |
IsAsynchronous |
bool |
Устанавливает режим создания объекта и вызова метода в текущем или отдельном потоке |
MethodName |
string |
Имя вызываемого метода, возвращающего объект или коллекцию привязки |
MethodParameters |
System.Collections. |
Список параметров для передачи методу, если они есть |
ObjectInstance |
object |
Задает или извлекает объект, используемый в качестве источника привязки |
ObjectType |
System.Type |
Задает или извлекает тип объекта, содержащего метод привязки |
Следует иметь в виду, что данные, поставляемые методом пользовательскому интерфейсу, можно только просматривать, то есть привязка всегда будет только для чтения.
Класс 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=ContentCopy to Output Directory=Copy if newer
Запустите приложение - Атрибут IsSynchronizedWithCurrentItem="True" из именованного списка listCustomers5 можно удалить, но тогда текстовые поля получат первое значение только при выделении элемента списка.
Найдите в подкаталоге Data папки размещения исполнимой сборки файл XmlDocument.xml и откройте егоМы видим, что все реляционные зависимости между таблицами при преобразовании из набора DataSet в XML -формат были утрачены, поэтому нам не удалось построить такую же функциональность, как в предыдущих примерах. Но пример применения класса XmlDataProvider в качестве источника данных для привязываемых элементов интерфейса WPF мы все-таки рассмотрели и отправную точку для более глубокого изучения обозначили.
Дальше сами: автобусом до сходней, а там по шпалам, опять по шпалам...!
Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.
Реляционная база данных - база данных, основанная на реляционной модели данных. Слово "реляционный" происходит от англ. relation (отношение). Для работы с реляционными БД применяют реляционные СУБД. Использование реляционных баз данных было предложено доктором Коддом из компании IBM в 1970 году.
Чаще всего данные в хранилище являются именно реляционными. Отдельные таблицы связаны между собой определенным отношением и настоящая СУБД должна строго следить за целостностью этих связей. Не вдаваясь в
Первичный ключ (primary key) - на практике термин обозначает поле (столбец) или группу полей таблицы базы данных, значение которого (или комбинация значений которых) используется в качестве уникального идентификатора записи (строки) этой таблицы. Внешний ключ (foreign key) - поле (столбец) таблицы, предназначенное для хранения значения первичного ключа другой таблицы с целью организации связи между этими таблицами.
Применяя объектную модель ADO.NET, мы сами вынуждены строить аналог СУБД и поэтому сами должны учитывать связи, которые существуют между таблицами сырых данных в хранилище. В ADO.NET отношения между таблицами объектной модели устанавливаются с помощью объектов DataRelation, которые попарно связывают столбец в одной таблице со столбцом в другой. В WPF связанные записи можно извлечь, привязавшись к зависимостям данных.
Извлеченные данные можно просматривать, редактировать и фиксировать изменения обратной отсылкой в БД. Для редактирования привязанных данных используется двунаправленная привязка. Но здесь мы будем пользоваться только однонаправленной привязкой для просмотра данных, чтобы не усложнять задачу. Этого пока будет достаточно.
Вначале воспользуемся инструментами оболочки для извлечения и отображения реляционных данных, потом повторим задачу вручную. По-прежнему будем работать с файловой БД Northwind.mdb и еще раз приведем ее структуру.

Командой 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.

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. В нашем случае обе
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 посмотрите их названия: это будут связиCustomersOrdersOrders_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, но тогда их нужно менять и в выражениях привязки разметки.
Может случиться так (как у Нано-Сидоркина на его компьютере), что при копировании БД в проект окно мастера 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>
Запустите приложение - функциональность вкладки В двух предыдущих примерах мы пользовались типом набора данных, который автоматически сгенерировал мастер оболочки 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>
Запустите приложение - получим результат, в точности совпадающий с двумя предыдущими
До сих пор мы пользовались свойством 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. Но не станем мелочиться на учебных примерах, а будем последовательны и дисциплинированы.
Запустите приложение - опять на вкладке | Свойство | Тип | Описание |
|---|---|---|
ConstructorParameters |
System.Collections. |
Список параметров для передачи конструктору, если они есть |
IsAsynchronous |
bool |
Устанавливает режим создания объекта и вызова метода в текущем или отдельном потоке |
MethodName |
string |
Имя вызываемого метода, возвращающего объект или коллекцию привязки |
MethodParameters |
System.Collections. |
Список параметров для передачи методу, если они есть |
ObjectInstance |
object |
Задает или извлекает объект, используемый в качестве источника привязки |
ObjectType |
System.Type |
Задает или извлекает тип объекта, содержащего метод привязки |
Следует иметь в виду, что данные, поставляемые методом пользовательскому интерфейсу, можно только просматривать, то есть привязка всегда будет только для чтения.
Класс 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=ContentCopy to Output Directory=Copy if newer
Запустите приложение - Атрибут IsSynchronizedWithCurrentItem="True" из именованного списка listCustomers5 можно удалить, но тогда текстовые поля получат первое значение только при выделении элемента списка.
Найдите в подкаталоге Data папки размещения исполнимой сборки файл XmlDocument.xml и откройте егоМы видим, что все реляционные зависимости между таблицами при преобразовании из набора DataSet в XML -формат были утрачены, поэтому нам не удалось построить такую же функциональность, как в предыдущих примерах. Но пример применения класса XmlDataProvider в качестве источника данных для привязываемых элементов интерфейса WPF мы все-таки рассмотрели и отправную точку для более глубокого изучения обозначили.
Дальше сами: автобусом до сходней, а там по шпалам, опять по шпалам...!
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.