| исключение в лабораторной работе № 3 |
Привязка WPF к таблице данных ADO.NET
Вкладка Page4. Привязка к таблице Employees через набор данных DataSet
Источником для свойства DataContext можно назначить набор DataSet инфраструктуры ADO.NET, а не DataTable. DataSet считается аналогом реляционной БД, только находится в оперативной памяти. Поскольку объект DataSet способен одновременно инкапсулировать много различных таблиц-объектов DataTable (и воспроизводить отношения между ними), то в выражениях привязки интерфейсных элементов XAML следует делать уточнения, к какой именно таблице набора привязываться. Рассмотрим это на примере вкладки Page4.
Загрузим в набор данных ADO.NET три таблицы сразу, которые в дальнейшем, при необходимости, можно будет использовать для рассмотрения других способов привязки. Но в текущей вкладке Page4 привяжем для отображения только одну таблицу Employees, чтобы глубже почувствовать разницу с предыдущими способами на одинаковом результате. Мы уже неоднократно убеждались в том, что .NET Framework позволяет решать одну и ту же задачу разными способами - поистине мощная библиотека.
-
Дополните класс StoreNorthwindDB в одноименном файле новым методом ReadDataSet() со следующим содержимым
//*********************************************************
// Метод извлечения нетипизированного набора
// данных из хранилища данных Northwind.mdb
//*********************************************************
DataSet ds = null;// Ссылка на нетипизированный набор DataSet
public DataSet ReadDataSet()
{
// Загрузим набор данных только один раз
if (ds != null)
return ds;
ds = new DataSet();// Создаем множественный набор данных
// Заполняем множественный набор данных из БД
using (OleDbConnection conn = new OleDbConnection(connectionString))
{
OleDbCommand selectCommand = conn.CreateCommand();
OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
// Загружаем всю таблицу Employees
selectCommand.CommandText = "SELECT * FROM Employees";
adapter.Fill(ds);// Сам открывает, загружает и закрывает
// Назначаем загруженным данным в наборе такое же имя,
// как и в хранилище, чтобы в дальнейшем не путаться
ds.Tables[0].TableName = "Employees";
// Загружает всю таблицу Customers
selectCommand.CommandText = "SELECT * FROM Customers";
// Назначаем загружаемым данным в наборе имя, в этом способе
// надо переопределить дежурное имя Table до заполнения набора
adapter.TableMappings.Add("Table", "Customers");// Должен стоять перед
adapter.Fill(ds); // Должен стоять после
// Загружает всю таблицу Orders
selectCommand.CommandText = "SELECT * FROM Orders";
// Загружаем и сразу этой порции данных назначаем имя в наборе
adapter.Fill(ds, "Orders");
// Для проверки способов именования в панели Output режима Debug
System.Diagnostics.Debug.WriteLine(ds.Tables[0].TableName);
System.Diagnostics.Debug.WriteLine(ds.Tables[1].TableName);
System.Diagnostics.Debug.WriteLine(ds.Tables[2].TableName);
return ds;
}
}Чтобы развеять сомнения в том, что таблицы действительно загружены в набор данных и получили заданные имена, в конце метода ReadDataSet() добавлен отладочный код, который работает только для режима Debug. Он выводит результат в панель Output оболочки и его удалять необязательно, потому что в рабочем режиме Release он сам отключится.
-
Чтобы проверить работу метода, вставьте его вызов в обработчик Window_Loaded() класса Window1 файла Window1.xaml.cs так
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Page1();
Page2();
Page3();
App.StoreNorthwindDB.ReadDataSet();
}-
Запустите проект и проверьте отладочный вывод в панели Output (панель можно открыть командой меню оболочки View/Output )
В конце отладочной информации найдем строки
Employees
Customers
Orders
Можно для пробы поменять имена таблиц TableName в наборе данных объектной модели метода и убедиться, что код верно реагирует на эти изменения. При этом строки самих SQL -запросов трогать не следует, поскольку это зона ответственности самого хранилища данных, а не объектной модели ADO.NET.
Теперь осталось сделать вкладку Page4 для размещения интерфейсных элементов отображения данных и привязать к ним заполненный набор данных.
-
Добавьте в контейнер TabControl файла Window1.xaml копию вкладки Page3 и сделайте очевидные переименования, чтобы разметка новой вкладки Page4 стала такой
<!-- Привязка к таблице Employees через объект DataSet из ADO.NET -->
<TabItem Header="Page4">
<Grid
DataContext="{Binding ElementName=listEmployees4, Path=SelectedItem}"
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Margin="0,0,0,3"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Name="listEmployees4"
ItemsSource="{Binding}" DisplayMemberPath="FullName"
/>
<TextBlock Grid.Row="1" Margin="0,0,5,0">EmployeeID:</TextBlock>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding Path=EmployeeID, Mode=OneWay}"
Focusable="False"
/>
<TextBlock Grid.Row="2">FullName:</TextBlock>
<TextBox Grid.Row="2" Grid.Column="1"
Text="{Binding Path=FullName, Mode=OneWay}"
Focusable="False"
/>
<TextBlock Grid.Row="3">Address:</TextBlock>
<TextBox Grid.Row="3" Grid.Column="1"
Text="{Binding Path=Address, Mode=OneWay}"
Focusable="False"
/>
<TextBlock Grid.Row="4">BirthDate:</TextBlock>
<TextBox Grid.Row="4" Grid.Column="1"
Text="{Binding Path=BirthDate, Mode=OneWay}"
Focusable="False"
/>
<TextBlock Grid.Row="5">Region:</TextBlock>
<TextBox Grid.Row="5" Grid.Column="1"
Text="{Binding Path=Region, Mode=OneWay}"
Focusable="False"
/>
</Grid>
</TabItem>Заметим, что список ListBox имеет имя для того, чтобы в процедурном коде к нему можно было обратиться и присвоить свойству DataContext значение источника привязки - набора данных.
#region Вкладка Page4
private void Page4()
{
// Настраиваем списковый элемент ListBox
listEmployees4.SelectedIndex = 0;
listEmployees4.Focus();
// Назначаем источником набор данных DataSet
listEmployees4.DataContext =
App.StoreNorthwindDB.ReadDataSet();
}
#endregion-
Примените в обработчике события Loaded класса Window1 вызов функции Page4()
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Page1();
Page2();
Page3();
//App.StoreNorthwindDB.ReadDataSet();
Page4();
}-
Измените дескриптор списка вкладки Page4 следующим образом
| Было |
<ListBox
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Margin="0,0,0,3"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Name="listEmployees4"
ItemsSource="{Binding}" DisplayMemberPath="FullName"
/> |
|---|---|
| Стало |
<ListBox
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Margin="0,0,0,3"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Name="listEmployees4"
ItemsSource="{Binding Path=Employees}" DisplayMemberPath="FullName"
/> |
-
Запустите приложение - вкладка Page4 стала работать, но только частично
Текстовые поля TextBox функционируют (кроме поля FullName ) и список тоже, только в нем не отображается информация, установленная в настройках DisplayMemberPath="FullName".
-
При запущенном приложении загляните в панель Output оболочки
Мы видим, что механизм привязки хоть и не возбуждает исключений, но под управлением оболочки все-таки сигнализирует об ошибке, выдавая серию предупреждений, примерно таких
System.Windows.Data Error: 39 : BindingExpression path error:
'FullName' property not found on 'object' ''DataRowView' (HashCode=48835636)'.
BindingExpression:Path=FullName; DataItem='DataRowView' (HashCode=48835636);
target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')-
Установите в разметке настроек списка ListBox вкладки Page4 другое значение отображаемого столбца объектной модели источника
| Было |
<ListBox
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Margin="0,0,0,3"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Name="listEmployees4"
ItemsSource="{Binding Path=Employees}" DisplayMemberPath="FullName"
/> |
|---|---|
| Стало |
<ListBox
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Margin="0,0,0,3"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Name="listEmployees4"
ItemsSource="{Binding Path=Employees}" DisplayMemberPath="FirstName"
/> |
-
Запустите приложение - список стал отображать столбец FirstName, который был загружен из хранилища, а текстовый элемент для поля FullName, которое мы хотели показать, попрежнему остается пустым
Причина тут в том, что физически столбца с именем FullName ни в хранилище, ни в объектной модели набора данных просто не сущестует. Он является вычислимым, то есть формируется налету при извлечении данных. Ранее мы его могли использовать потому, что заранее придумали в выражении SQL -запроса и он попадал в коллекцию источника уже заполненным.

