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

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

Вкладка Page5. Связывание элементов в цепочку

Передачу информации можно осуществлять по цепочке, связывая элементы последовательно друг с другом. Например, источник <Slider> можно связать с приемником <Label>, а затем считать <Label> как источник и связать с ним приемник <TextBlock>. При манипулировании с источником <Slider> информация от него через элемент <Label> транзитом будет передаваться в <TextBlock>. Несмотря на то, что такой способ работает исправно, он может изрядно запутать код разметки. Лучше связывать источник и приемник напрямую. Но мы, как программисты, должны знать об этом, поэтому приведем простой пример.

  • Добавьте в контейнер TabControl файла Window1.xaml новую вкладку Page5, представленную следующей разметкой
<!-- Page5. Цепочки привязок -->
            <TabItem Header="Page5">
                <StackPanel>
                    <TextBlock FontSize="16" TextDecorations="Underline"
                        TextAlignment="Center" Margin="0,5,0,15" Foreground="Red"
                        Text="Цепочки привязок" 
                        />
                    <StackPanel Orientation="Horizontal" Margin="5">
                        <Label Name="label5" Width="40"
                            Content="{Binding ElementName=slider5, Path=Value, Mode=OneWay}" 
                            />
                        <Slider Name="slider5" Width="230" 
                            Minimum="11" Maximum="54"
                            TickFrequency="2" TickPlacement="TopLeft" 
                            IsSnapToTickEnabled="True"
                            />
                    </StackPanel>
                    <TextBlock Margin="0,10" 
                        Foreground="Blue"
                        FontSize="{Binding ElementName=label5, Path=Content, Mode=OneWay}"
                        Text="Simple Text"
                        />
                </StackPanel>
            </TabItem>
  • Запустите приложение и испытайте работу вкладки Page5, реализующей цепочку привязок

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


Этот способ может пригодиться в том случае, если источник находится где-то в труднодоступном месте (например, в другом окне или сборке) и код привязки достаточно объемен. Основательно потрудившись над привязкой к такому источнику первого приемника, другие приемники проще будет привязать к нему, чем к первоисточнику.

Вкладка Page6. Двунаправленная привязка приемника

  • Добавьте в контейнер TabControl файла Window1.xaml новую вкладку Page6, представленную следующей разметкой
<!-- Page6. Двунаправленная привязка -->
            <TabItem Header="Page6">
                <StackPanel>
                    <TextBlock FontSize="16" TextDecorations="Underline"
                        TextAlignment="Center" Margin="0,5,0,15" Foreground="Red">
                        Двунаправленная привязка 
                        <LineBreak />
                        элемента TextBox к Slider
                        <LineBreak />
                        без проверки достоверности
                    </TextBlock>
                    <StackPanel Orientation="Horizontal" Margin="5">
                        <Label Width="40"
                            Content="{Binding ElementName=slider6, Path=Value, Mode=OneWay}" 
                            />
                        <Slider Name="slider6" Width="230" 
                            Minimum="11" Maximum="54"
                            TickFrequency="2" TickPlacement="TopLeft" 
                            IsSnapToTickEnabled="True"
                            Value="35"
                            />
                    </StackPanel>
                    <TextBox Margin="5" 
                        Text="{Binding ElementName=slider6, Path=Value, Mode=TwoWay}" 
                        />
                    <TextBlock Margin="0,10" 
                        Foreground="Blue"
                        FontSize="{Binding ElementName=slider6, Path=Value, Mode=OneWay}"
                        Text="Simple Text"
                        />
                </StackPanel>
            </TabItem>

В разметку вкладки мы добавили текстовое поле с двунаправленной привязкой. У всех привязанных элементов отсутствуют имена кроме источника, поскольку его имя используется при привязке этими элементами. Мы установили начальное положение бегунка Value="35", которое при инициализации получают все привязанные к нему элементы. Теперь, за счет двунаправленности привязки, значение бегунка можно менять как перемещением головки, так и через текстовое поле. Значения не просто выталкиваются из источника к целевому элементу, но и наоборот - от цели к источнику.

  • Запустите приложение и испытайте работу вкладки Page6. Попробуйте вводить в текстовое поле некорректное содержимое, которое нельзя трактовать как число, например, буквы

На данном примере мы наблюдаем несколько особенностей поведения WPF:

  1. Одной из особенностей механизма привязки WPF является то, что он не возбуждает никаких исключений, чтобы известить нас о проблемах привязки данных. Если мы привязываем несуществующий элемент или свойство, или если типы соединенных привязкой свойств отличаются так, что данные источника не могут быть корректно приведены к типу данных приемника, WPF будет молчать и данные просто не появятся в приемнике. Однако, возникновение такой ситуации отслеживает оболочка, которая выводит в панель Output трассировочную информацию о работе средств привязки. Другой способ - применить механизм, выполняющий проверку достоверности (validation) данных (об этом речь пойдет ниже), или реализовать такую проверку в процедурном коде
  2. Мы уже говорили, что при однонаправленной привязке на стороне приемника данные источника непрерывно отображаются в приемнике. При двунаправленной привязке на стороне приемника TextBox, введенные в текстовое поле значения приемника не применяются к источнику и к другим связанным с ним элементам до тех пор, пока TextBox не потеряет фокус ввода (по нажатию клавиши Tab или щелчку мыши на другом интерфейсном элементе, при переходе на другую вкладку или активации другого окна). Такое поведение можно изменить, установив непрерывное обновление, если использовать свойство UpdateSourceTrigger объекта Binding, которое принимает одно из значений одноименного перечисления System.Windows.Data.UpdateSourceTrigger (об этом речь пойдет ниже)

Вкладка Page7. Двунаправленная привязка источника-приемника

  • Добавьте в контейнер TabControl файла Window1.xaml новую вкладку Page7, представленную следующей разметкой
<!-- Page7. Двунаправленная привязка источника-приемника -->
            <TabItem Header="Page7">
                <StackPanel>
                    <TextBlock FontSize="16" TextDecorations="Underline"
                        TextAlignment="Center" Margin="0,5,0,15" Foreground="Red">
                        Двунаправленная привязка 
                        <LineBreak />
                        источника - приемника
                    </TextBlock>
                    <StackPanel Orientation="Horizontal" Margin="5">
                        <Label Width="40"
                            Content="{Binding ElementName=slider7, Path=Value, Mode=OneWay}" 
                            />
                        <Slider Name="slider7" Width="230" 
                            Minimum="11" Maximum="54"
                            TickFrequency="2" TickPlacement="TopLeft" 
                            IsSnapToTickEnabled="True"
                            Value="{Binding ElementName=textBox7, Path=Text, Mode=TwoWay}" 
                            />
                    </StackPanel>
                    <TextBox Name="textBox7" Margin="5" Text="21" />
                    <TextBlock Margin="0,10" 
                        Foreground="Blue"
                        FontSize="{Binding ElementName=slider7, Path=Value, Mode=OneWay}"
                        Text="Simple Text"
                        />
                </StackPanel>
            </TabItem>
  • Запустите приложение и испытайте работу вкладки Page7 - она продолжает работать точно также, как и для вкладки Page6

Элемент <TextBox> у нас оказался несвязанным, зато пришлось ему присвоить имя, чтобы можно было его привязать на стороне элемента <Slider> в качестве источника. Теперь ползунок находится под контролем элемента TextBox и одновременно является источником для элементов <Label> и <TextBlock>. Когда окно отображается впервые, оно извлекает значение свойства Text объекта TextBox и копирует его в свойство Value объекта Slider. Оттуда, в свою очередь, это значение тиражируется в связанные объекты Label и TextBlock.

Когда пользователь перетаскивает ползунок в новую позицию, действует канал обратной отсылки от ползунка-приемника к текстовому полю-источнику. Когда пользователь меняет значение текстового поля-источника, действует прямой канал передачи информации от источника TextBox к приемнику Slider.

  • Уберите инициализацию Text="21" из элемента TextBox - при запуске приложения ползунок не получает данные от источника и принимает состояние по умолчанию, устанавливая вслед за собой и другие его приемники

Обратите внимание, за счет прямой передачи данных от источника TextBox к приемнику Slider последний не ждет потери фокуса ввода источником, а непрерывно меняет свое значение. В обратной отсылке от приемника к источнику требуется потеря приемником фокуса ввода. В нашем случае при движении ползунка-приемника источник TextBox его и не получает, поэтому тоже изменяется непрерывно.

Представление окна приложения с открытой вкладкой Page7 будет таким


Вкладка Page8. Создание привязки элементов в процедурном коде

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

  1. Создать другую привязку в зависимости от возникших обстоятельств в работающем приложении
  2. Удалить существующую связь и перейти к программному изменению свойств элементов обычным способом на основе событий
  3. При создании пользовательских элементов управления вынести возможности привязки в программный интерфейс элемента для последующей его настройки сторонним программистом

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

  • Поместите в контейнер TabControl файла Window1.xaml новую вкладку Page8, стартовый код разметки которой имеет вид
<!-- Page8. Привязка элементов в коде -->
            <TabItem Header="Page8">
                <StackPanel>
                    <TextBlock FontSize="16" TextDecorations="Underline"
                        TextAlignment="Center" Margin="0,5,0,15" Foreground="Red"
                        Text="Привязка элементов в коде" 
                        />
                    <StackPanel Orientation="Horizontal" Margin="5">
                        <Label Width="40"
                            Content="{Binding ElementName=slider8, Path=Value, Mode=OneWay}" 
                            />
                        <Slider Name="slider8" Width="230" 
                            Minimum="11" Maximum="54"
                            TickFrequency="2" TickPlacement="TopLeft" 
                            IsSnapToTickEnabled="True"
                            />
                    </StackPanel>
                    <TextBlock Margin="0,10" 
                        Foreground="Blue"
                        FontSize="{Binding ElementName=slider8, Path=Value, Mode=OneWay}"
                        Text="Simple Text"
                        />
                </StackPanel>
            </TabItem>
  • Удалите код привязки из разметки элементов Label и TextBlock и добавьте на их место имена с атрибутами Name="label8" и Name="textBlock8" соответственно, - для возможности доступа из процедурного кода. После чего код разметки должен стать таким
<!-- Page8. Привязка элементов в коде -->
            <TabItem Header="Page8">
                <StackPanel>
                    <TextBlock FontSize="16" TextDecorations="Underline"
                        TextAlignment="Center" Margin="0,5,0,15" Foreground="Red"
                        Text="Привязка элементов в коде" 
                        />
                    <StackPanel Orientation="Horizontal" Margin="5">
                        <Label Width="40"
                            Name="label8" 
                            />
                        <Slider Name="slider8" Width="230" 
                            Minimum="11" Maximum="54"
                            TickFrequency="2" TickPlacement="TopLeft" 
                            IsSnapToTickEnabled="True"
                            />
                    </StackPanel>
                    <TextBlock Margin="0,10" 
                        Foreground="Blue"
                        Name="textBlock8"
                        Text="Simple Text"
                        />
                </StackPanel>
            </TabItem>
  • Перейдите к редактированию файла Window1.xaml.cs и добавьте в конструктор класса Window1 следующий процедурный код
public Window1()
        {
            InitializeComponent();
    
            // Создаем и настраиваем объект привязки
            Binding binding = new Binding();
            binding.Source = slider8;// Источник 
            binding.Path = new PropertyPath("Value"); 
            binding.Mode = BindingMode.OneWay;
    
            // Привязываем элементы-приемники
            label8.SetBinding(Label.ContentProperty, binding);          // Label
            textBlock8.SetBinding(TextBlock.FontSizeProperty, binding); // TextBlock
        }

В начале мы создаем сам узел (объект) привязки с необходимой информацией об источнике. Затем с помощью экземплярного метода SetBinding(), который каждый интерфейсный элемент наследует от класса FrameworkElement, присоединяем узел привязки к соответствующему свойству зависимости элемента-приемника. Свойства зависимости элементов определены как статические только для чтения с отложенной одноразовой инициализацией и имеют тип DependencyProperty. Например, в библиотечном классе System.Windows.Controls.TextBlock свойство зависимости FontSizeProperty определено так

static readonly System.Windows.DependencyProperty FontSizeProperty

Иными словами, в отличие от кода разметки, где для читабельности привязка осуществляется с использованием имени экземплярного свойства, например FontSize, в процедурном коде привязка к объекту-приемнику выполняется к применением имени типа элемента и статического свойства зависимости с окончанием Property, например FontSizeProperty. Такое правило справедливо для всех типов интерфейсных элементов при создании привязки в процедурном коде.

  • Запустите приложение и испытайте работу вкладки Page8 - она работает как и положено

Представление окна приложения с открытой вкладкой Page8 будет таким


Прежде, чем двигаться дальше в демонстрации примеров данного упражнения, нужно оговорить следующее (чтобы не посчитали дураками).

  • Запустите приложение и откройте вкладку Page4, где у нас больше всего интерфейсных элементов

Если максимально увеличить размер шрифта текста, то он выходит за границы окна за счет того, что мы после разработки этой вкладки добавили еще несколько таких же. Полезная область окна уменьшилась по вине появления дополнительных закладок. А мы еще в данном упражнении собираемся добавлять все новые вкладки, чтобы продолжить рассмотрение темы привязки элемента к элементу (извилины к извилине, денежки к денежке).


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

Алексей Бабушкин
Алексей Бабушкин

При выполнении в лабораторной работе упражнения №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" или один из зависимых от них компонентов. Не удается найти указанный файл.

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

Иван Циферблат
Иван Циферблат
Россия, Таганрог, 36, 2000