исключение в лабораторной работе № 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 -запроса и он попадал в коллекцию источника уже заполненным.