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

Основы привязки данных в WPF

Использование свойства DataContext элементов для назначения источника привязки

Иногда несколько приемников нужно привязать к одному и тому же источнику. Чтобы не дублировать в каждом выражении привязки имя источника и как-то автоматизировать эту задачу, в WPF очень часто используют свойство DataContext. Это свойство введено классом FrameworkElement и наследуется каждым элементом, производным от этого класса. Его использование является альтернативным способом обозначения объекта-источника, задействованного в привязке.

Если в выражении привязки целевого элемента источник не указан с помощью значения атрибута Source, то WPF автоматически будет искать его вначале в элементе с выражением привязки, а затем - в родительских элементах вверх по направлению к корню логического дерева до тех пор, пока источник не будет найден. Если источник не будет обнаружен, то WPF просто будет молчать, поскольку механизм привязки не генерирует исключений. Обычно стараются сузить диапазон поиска, помещая определение свойства DataContext в ближайший родительский контейнер, охватывающий привязываемые приемники.

Выполняя поиск по дереву, WPF пытается найти первый контекст данных DataContext, не равный null. Если контекст данных найден, он используется для привязки, если нет, то выражение привязки не применяет никакого значения к целевому элементу и он ничего отображать не будет.

Предположим, что имеется несколько элементов управления TextBlock, явным образом связанных с одним и тем же источником привязки:

<StackPanel>
        <TextBlock 
            Text="{Binding Source={StaticResource personView}, 
                           Path=FullName}" />
        <TextBlock 
            Text="{Binding Source={StaticResource personView}, 
                           Path=Title}" />
        <TextBlock 
            Text="{Binding Source={StaticResource personView}, 
                           Path=City}" />
    </StackPanel>

А вот те же самые три элемента управления TextBlock, связанные с контекстом данных DataContext контейнера StackPanel:

<StackPanel DataContext="{Binding Source={StaticResource personView}}">
        <TextBlock Text="{Binding Path=FullName}" />
        <TextBlock Text="{Binding Path=Title}" />
        <TextBlock Text="{Binding Path=City}" />
    </StackPanel>

Если в очередном ближнем контейнере атрибут DataContext не определен, поиск будет продолжен в следующем внешнем контейнере, пока не будет найден ненулевой контекст данных DataContext или поиск закончится неудачей.

Вкладка Page3. Использование свойства DataContext в целевом элементе

В следующем примере свойство DataContext помещено в приемник привязки.

  • Добавьте в контейнер TabControl новую вкладку Page3 со следующей разметкой
<!-- Page3. Использование свойства DataContext -->
            <TabItem Header="Page3">
                <StackPanel>
                    <!-- Источник привязки -->
                    <ScrollBar Name="scroll3"
                        Orientation="Horizontal" Margin="24"
                        Maximum="100" LargeChange="10" SmallChange="1" 
                        Value="50"
                    />
    
                    <!-- Приемник привязки -->
                    <Label HorizontalAlignment="Center"
                        ContentStringFormat="F0"
                        DataContext="{Binding ElementName=scroll3}"
                        Content="{Binding Path=Value}"
                    />
                </StackPanel>
            </TabItem>

В элементе ScrollBar атрибут Maximum="100" означает верхнюю границу диапазона значений, LargeChange="10" - шаг изменения положения бегунка по щелчкам на областях справа и слева от него, SmallChange="1" - шаг изменения значения по щелчкам на концевых элементах линейки. В приведенном примере свойство DataContext никаких преимуществ не дает, а просто делит выражение привязки на две части.

  • Запустите приложение - вкладка Page3 обеспечит следующую область вывода

Вкладка Page4. Использование свойства DataContext в родительском элементе

  • Добавьте в контейнер TabControl новую вкладку Page4
  • Переместите назначение источника привязки свойства DataContext из приемника в родительский элемент <StackPanel> следующим образом
<!-- Page4. Использование свойства DataContext -->
            <TabItem Header="Page4">
                <StackPanel DataContext="{Binding ElementName=scroll4}">
                    <!-- Источник привязки -->
                    <ScrollBar Name="scroll4"
                        Orientation="Horizontal" Margin="24"
                        Maximum="100" LargeChange="10" SmallChange="1"
                        Value="50"
                    />
    
                    <!-- Приемник привязки -->
                    <Label HorizontalAlignment="Center"
                        ContentStringFormat="F0"
                        Content="{Binding Path=Value}"
                    />
                </StackPanel>
            </TabItem>

Элемент <StackPanel> тоже наследует классу FrameworkElement и содержит свойство DataContext.

  • Испытайте работу вкладки Page4 - она ничем не будет отличаться от вкладки Page3

Вкладка Page5. Привязка нескольких приемников к одному источнику без свойства DataContext

Рассмотрим пример, когда отказ от использования свойства DataContext влечет за собой относительно громоздкий код разметки.

  • Добавьте в контейнер TabControl вкладку Page5 со следующей разметкой
<!-- Page5. Разметка без свойства DataContext --> 
             <TabItem Header="Page5"> 
                 <StackPanel> 
                     <TextBlock 
                        FontSize="18" FontWeight="Bold"
                        Text="{Binding Source={x:Static SystemFonts.IconFontFamily},
                        Path=Source}" 
                    /> 
                     <TextBlock 
                        FontSize="18" FontWeight="Bold"
                        Text="{Binding Source={x:Static SystemFonts.IconFontFamily},
                        Path=LineSpacing}" 
                    /> 
                     <TextBlock 
                        FontSize="18" FontWeight="Bold"
                        Text="{Binding Source={x:Static SystemFonts.IconFontFamily},
                        Path=FamilyTypefaces[0].Style}" 
                    /> 
                     <TextBlock 
                        FontSize="18" FontWeight="Bold"
                        Text="{Binding Source={x:Static SystemFonts.IconFontFamily},
                        Path=FamilyTypefaces[0].Weight}" 
                    /> 
                     <TextBlock 
                        FontSize="18" FontWeight="Bold"
                        Text="{Binding Source={x:Static SystemFonts.IconFontFamily},
                        Path=FamilyNames.Count}" 
                    /> 
                     <TextBlock 
                        FontSize="18" FontWeight="Bold"
                        Text="{Binding Source={x:Static SystemFonts.IconFontFamily},
                        Path=FamilyMaps.Count}"
                    /> 
                     <TextBlock 
                        FontSize="18" FontWeight="Bold"
                        Text="{Binding Source={x:Static SystemFonts.IconFontFamily},
                        Path=Baseline}"  
                    /> 
                 </StackPanel> 
             </TabItem>
  • Запустите приложение - результат будет таким

Вкладка Page6. Привязка нескольких приемников к одному источнику с использованием свойства DataContext

  • Добавьте новую вкладку Page6 и перепишите разметку предудущего примера так
<!-- Page6. Разметка с использованием свойства DataContext -->
            <TabItem Header="Page6">
                <StackPanel
                    DataContext="{Binding Source={x:Static SystemFonts.IconFontFamily}}"
                    >
                    <TextBlock 
                        FontSize="18" FontWeight="Bold"
                        Text="{Binding Path=Source}" 
                    />
                    <TextBlock 
                        FontSize="18" FontWeight="Bold"
                        Text="{Binding Path=LineSpacing}" 
                    />
                    <TextBlock 
                        FontSize="18" FontWeight="Bold"
                        Text="{Binding Path=FamilyTypefaces[0].Style}" 
                    />
                    <TextBlock 
                        FontSize="18" FontWeight="Bold"
                        Text="{Binding Path=FamilyTypefaces[0].Weight}" 
                    />
                    <TextBlock 
                        FontSize="18" FontWeight="Bold"
                        Text="{Binding Path=FamilyNames.Count}" 
                    />
                    <TextBlock 
                        FontSize="18" FontWeight="Bold"
                        Text="{Binding Path=FamilyMaps.Count}"
                    />
                    <TextBlock 
                        FontSize="18" FontWeight="Bold"
                        Text="{Binding Path=Baseline}"  
                    />
                </StackPanel>
            </TabItem>

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

  • Запустите приложение - представление вкладки Page6 полностью совпадает с областью вывода вкладки Page5

Сделаем несколько замечаний:

  1. 1.Если в родительском элементе определен источник через свойство DataContext, но одновременно в каком-то целевом элементе (или нескольких) в выражении привязки явно определен свой источник через свойство Source, то приемник использует свой источник и не будет далее искать контекст данных.
  2. 2.В разметке свойству DataContext может быть присвоена ссылка на логический ресурс объекта
  3. 3.Когда свойство DataContext задается в процедурном коде, то связанные элементы управления не будут отображать связанные данные до тех пор, пока этому свойству не будет присвоена ссылка на объект-источник.
Алексей Бабушкин
Алексей Бабушкин

При выполнении в лабораторной работе упражнения №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