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

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

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

Если мы уже создали привязку в разметке или процедурном коде, то чтобы установить новую привязку для тех же элементов, нужно вначале удалить прежнюю. Для этого существуют статические методы BindingOperations. ClearBinding() и BindingOperations. ClearAllBindings() пространства имен System.Windows.Data. Метод ClearBinding() ожидает ссылку на объект, содержащий узел привязки, и ссылку на его свойство зависимостей, которое имеет привязку. Он удаляет привязку указанного свойства зависимостей в указанном объекте. Метод ClearAllBindings() ожидает ссылку на объект, содержащий привязки, и удаляет привязки для всех свойств этого объекта.

Для демонстрации сказанного используем стартовый код разметки из предыдущего примера с уже готовыми декларативными привязками элементов.

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

Окно с вкладкой Page9 будет таким


Вкладка Page10. Размещение узла привязки на стороне источника

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

Но иногда свойство приемника, к которому мы хотим привязать передачу информации, не является свойством зависимости, а свойство источника - является таковым, и узел привязки приходится размещать в источнике. Тогда атрибуту ElementName нужно присвоить имя целевого элемента - приемника, а режим привязки установить как Mode=OneWayToSource. Рассмотрим пример.

  • Добавьте в контейнер TabControl файла Window1.xaml новую вкладку Page10 со следующим пробным кодом разметки
<!-- Page10. Размещение привязки на стороне источника -->
            <TabItem Header="Page10" Selector.IsSelected="True">
                <StackPanel>
                    <TextBlock FontSize="16" TextDecorations="Underline"
                        TextAlignment="Center" Margin="5" Foreground="Red"
                        Text="Размещение привязки на источнике" 
                        />
                    <FlowDocumentScrollViewer Height="110" Margin="5" BorderThickness="2">
                        <FlowDocument Background="White">
                            <Paragraph>
                                Этот приемник текста!
                            </Paragraph>
                            <Paragraph>
                                <Run Name="runParagraph10" 
                                    Text="{Binding ElementName=textBox10, Path=Text}"
                                    /> 
                            </Paragraph>
                        </FlowDocument>
                    </FlowDocumentScrollViewer>
                    <TextBox Name="textBox10"
                        Margin="5" MinLines="3" MaxLines="3"
                        TextWrapping="Wrap"
                        AcceptsReturn="True"
                        AcceptsTab="True"
                        VerticalScrollBarVisibility="Visible"
                        />
                </StackPanel>
            </TabItem>

Элемент <Run> с именем runParagraph10 в этом примере является приемником, а элемент <TextBox> с именем textBox10 - источником. По-старинке, мы пытаемся привязать свойство Text приемника к свойству Text источника. Но уже на этапе проектирования редактор кода (а за ним и компилятор - при попытке компиляции) начинает ругаться, что свойство Text приемника не является свойством зависимостей, порожденным классом DependencyObject.

  • Зная наверняка, что свойство Text элемента <TextBox> является свойством зависимости, переместите (удалите в приемнике - добавьте к источнику) узел привязки из элемента <Run> в элемент <TextBox> с именем textBox10, чтобы последний стал таким (разметка вкладки Page10 приводится полностью)
<!-- Page10. Размещение привязки на стороне источника -->
            <TabItem Header="Page10" Selector.IsSelected="True">
                <StackPanel>
                    <TextBlock FontSize="16" TextDecorations="Underline"
                        TextAlignment="Center" Margin="5" Foreground="Red"
                        Text="Размещение привязки на источнике" 
                        />
                    <FlowDocumentScrollViewer Height="110" Margin="5" BorderThickness="2">
                        <FlowDocument Background="White">
                            <Paragraph>
                                Этот приемник текста!
                            </Paragraph>
                            <Paragraph>
                                <Run Name="runParagraph10" /> 
                            </Paragraph>
                        </FlowDocument>
                    </FlowDocumentScrollViewer>
                    <TextBox
                        Margin="5" MinLines="3" MaxLines="3"
                        TextWrapping="Wrap"
                        AcceptsReturn="True"
                        AcceptsTab="True"
                        VerticalScrollBarVisibility="Visible"
                        Text="{Binding ElementName=runParagraph10, 
                            Path=Text,
                            Mode=OneWayToSource, 
                            UpdateSourceTrigger=PropertyChanged}"
                            />
                </StackPanel>
            </TabItem>

Мы удалили имя элемента TextBox как ненужное и добавили свойство Text, привязанное к именованному приемнику по синтаксису расширения разметки. Атрибут UpdateSourceTrigger=PropertyChanged синхронизирует передачу информации в непрерывном режиме. Если его убрать, то приемник будет обновляться только после потери источником фокуса ввода в соответствии с установкой по умолчанию UpdateSourceTrigger=LostFocus.

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

Вкладка Page11. Режимы обновления привязки

Обновления привязки устанавливают режим, как быстро изменения на одном связанном элементе отобразятся на другом. Обычно такая задача имеет место тогда, когда целевое свойство берет на себя еще и роль источника в привязках типа TwoWay или OneWayToSource. Этот режим определяется в выражении привязки элемента Binding и зависит от его свойства UpdateSourceTrigger. Свойство UpdateSourceTrigger может принимать следующие значения из одноименного перечисления System.Windows.Data.UpdateSourceTrigger

Значения из перечисления UpdateSourceTrigger
Значение Описание
Default Поведение обновления целевого свойства определяется установками по умолчанию. Обычно это PropertyChanged, хотя для TextBox.Text значение по умолчанию равно LostFocus
Explicit Источник не обновляется, пока не будет вызван один из экземплярных методов UpdateSource() или UpdateTarget() класса BindingExpression пространства имен System.Windows.Data сборки PresentationFramework.dll. Но перед этим из элемента нужно извлечь выражение привязки с помощью метода GetBindingExpression()
LostFocus Источник обновляется, когда целевой элемент теряет фокус
PropertyChanged Источник обновляется немедленно после изменения целевого свойства

Конечно, самым удобным режимом обновления являются непрерывные изменения PropertyChanged. Но это и самый затратный режим, который может создать впечатление замедленного выполнения приложения и нарушить логику обновления источника до завершения процесса редактирования. Режим UpdateSourceTrigger.Explicit требует программной поддержки, зато самый предсказуемый. Можно, например, добавить кнопку Apple (Применить), по щелчку на которой будет вызвано обновление источника экземплярным методом UpdateSource(), когда процесс подготовки данных уже полностью завершен. В этом случае пользователь сам полностью контролирует процесс обновлений.

Поясним сказанное на разметке предыдущего примера.

  • Добавьте в контейнер TabControl файла Window1.xaml новую вкладку Page11 со следующим кодом разметки
<!-- Page11. Обновление элемента привязки -->
            <TabItem Header="Page11" Selector.IsSelected="True">
                <StackPanel>
                    <TextBlock FontSize="16" TextDecorations="Underline"
                        TextAlignment="Center" Margin="5" Foreground="Red"
                        Text="Обновление элемента привязки" 
                        />
                    <FlowDocumentScrollViewer Height="110" Margin="5" BorderThickness="2">
                        <FlowDocument Background="White">
                            <Paragraph>
                                <Run Name="runParagraph11" />
                            </Paragraph>
                        </FlowDocument>
                    </FlowDocumentScrollViewer>
                    <DockPanel LastChildFill="True">
                        <Button DockPanel.Dock="Left" 
                            Content="Apple" 
                            Width="75" Height="25" 
                            VerticalAlignment="Center" 
                            Margin="5,0"
                            Click="Button_Click" 
                            />
                        <TextBox Name="textBox11"
                            Margin="5" MinLines="3" MaxLines="3"
                            TextWrapping="Wrap"
                            AcceptsReturn="True"
                            AcceptsTab="True"
                            VerticalScrollBarVisibility="Visible"
                            Text="{Binding ElementName=runParagraph11, 
                                Path=Text,
                                Mode=OneWayToSource, 
                                UpdateSourceTrigger=Explicit}"
                                />
                    </DockPanel>
                </StackPanel>
            </TabItem>
  • Щелкните на записи Click="Button_Click" правой кнопкой мыши и командой Navigate to Event Handler создайте обработчик Button_Click()
  • Запустите приложение с вкладкой Page11 на выполнение и убедитесь, что сам интерфейс функционирует, но при выбранном режиме обновления Explicit данные в элемент <FlowDocument> не передаются, потому что обработчик не заполнен соответствующим кодом
  • Перейдите в файл Window1.xaml.cs и заполните обработчик кнопки Apple следующим процедурным кодом
private void Button_Click(object sender, RoutedEventArgs e)
        {
            // Получить выражение привязки к свойству зависимости Text, 
            // которая размещена в текстовом элементе TextBox с именем textBox11
            BindingExpression binding = 
                textBox11.GetBindingExpression(TextBox.TextProperty);
            // Обновить связанный элемент FlowDocument
            binding.UpdateSource();
        }
  • Запустите приложение и испытайте работу вкладки Page11, которая будет выглядеть примерно так

В принятой схеме обновления применяются в целевом элементе только по нажатию кнопки Apple.

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

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