Опубликован: 05.08.2010 | Уровень: специалист | Доступ: свободно
Лекция 3:

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

< Лекция 2 || Лекция 3: 12345 || Лекция 4 >
Аннотация: Дается определение реляционных баз данных и рассматривается привязка WPF к реляционным таблицам набора данных.

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

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

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

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

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

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

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

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

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

  • Командой File/New/Project создайте новый проект WPF Application с именем DataBindingRelation в решении DataBindingStore
  • В панели Solution Explorer добавьте к корню проекта контекстной командой Add/New Folder каталог Data и скопируйте в него командой Add/Existing Item файл Northwind.mdb из прилагаемого каталога Source.

Откроется окно мастера Data Source Configuration Wizard на вкладке Choose Your Database Object.


Если окно мастера Data Source Configuration Wizard на этом шаге не появилось (Нано-Сидоркин!), перейдите к выполнению вкладки Page2, а затем вернитесь к этому примеру.
  • Разверните узел Tables и поднимите флажки на таблицах Customers и Orders

  • Разверните узел Views (Представления), поднимите флажок на Order Details Extended и щелкните на кнопке Finish

Мастер оболочки создаст класс типизированного набора данных. Мы уже говорили, что набор данных DataSet объектной модели ADO.NET (типизированный или обычный) является аналогом реляционной БД с настраиваемым набором таблиц, связей между ними, условиями выборки и обновления данных. До своего заполнения данными набор представляет собой только схему или структуру виртуальных данных.

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

  • В панели Solution Explorer двойным щелчком раскройте файл NorthwindDataSet.xsd в режиме Dataset Designer с графическим представлением схемы объекта набора данных.

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

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

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

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

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

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

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

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

  • Запустите приложение и испытайте его работу

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

< Лекция 2 || Лекция 3: 12345 || Лекция 4 >
Алексей Бабушкин
Алексей Бабушкин

При выполнении в лабораторной работе упражнения №1 , а именно при выполнении нижеследующего кода:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using Microsoft.Xna.Framework.Graphics;

   

namespace Application1

{

    public partial class MainForm : Form

    {

        // Объявим поле графического устройства для видимости в методах

        GraphicsDevice device;

   

        public MainForm()

        {

            InitializeComponent();

   

            // Подпишемся на событие Load формы

            this.Load += new EventHandler(MainForm_Load);

   

            // Попишемся на событие FormClosed формы

            this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed);

        }

   

        void MainForm_FormClosed(object sender, FormClosedEventArgs e)

        {

            //  Удаляем (освобождаем) устройство

            device.Dispose();

            // На всякий случай присваиваем ссылке на устройство значение null

            device = null;       

        }

   

        void MainForm_Load(object sender, EventArgs e)

        {

            // Создаем объект представления для настройки графического устройства

            PresentationParameters presentParams = new PresentationParameters();

            // Настраиваем объект представления через его свойства

            presentParams.IsFullScreen = false; // Включаем оконный режим

            presentParams.BackBufferCount = 1;  // Включаем задний буфер

                                                // для двойной буферизации

            // Переключение переднего и заднего буферов

            // должно осуществляться с максимальной эффективностью

            presentParams.SwapEffect = SwapEffect.Discard;

            // Устанавливаем размеры заднего буфера по клиентской области окна формы

            presentParams.BackBufferWidth = this.ClientSize.Width;

            presentParams.BackBufferHeight = this.ClientSize.Height;

   

            // Создадим графическое устройство с заданными настройками

            device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware,

                this.Handle, presentParams);

        }

   

        protected override void OnPaint(PaintEventArgs e)

        {

            device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);

   

            base.OnPaint(e);

        }

    }

}

Выбрасывается исключение:

Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл.

Делаю все пунктуально. В чем может быть проблема?

Юрий Макушин
Юрий Макушин
Россия, Москва, РЭА им. Плеханова, 2004