Создание приложений Silverlight
8.2. Изменение и отображение данных
У созданной нами программы Сумматор имеется недостаток, связанный с тем, что для получения результата нужно каждый раз нажимать на кнопку. Было бы лучше, если бы результат обновлялся автоматически при изменении текста в одном из текстовых полей. Это можно осуществить с помощью событий.
Событие TextChanged
Ранее мы использовали событие элемента Button, которое происходит при нажатии на кнопку. С точки зрения программы событие является вызовом метода. Код XAML, описывающий кнопку, может выглядеть так:
<Button Content="сумма" Height="72" HorizontalAlignment="Left" Margin="158,275,0,0" Name="equalsButton" VerticalAlignment="Top" Width="160" Click="equalsButton_Click" />
При нажатии на кнопку вызывается указанный обработчик события. В нашем случае в нем вызывается метод calculateResult.
private void equalsButton_Click(object sender, RoutedEventArgs e) { calculateResult(); }
Можно узнать, какие события может вызывать элемент, щелкнув по нему в редакторе Visual Studio и затем нажав на кнопку События в области свойств. Там же можно назначить, и даже автоматически создать обработчики событий, которые требуется обработать в программе.
Для элемента firstNumberTextBox можно создать обработчик события TextChanged, дважды щелкнув в строке TextChanged в пустом поле справа, и вызвать в нем метод calculateResult:
private void firstNumberTextBox_TextChanged(object sender, TextChangedEventArgs e) { calculateResult(); }
То же самое можно сделать для второго текстового поля. Теперь, при изменении текста в любом из этих полей результат будет обновляться. Следовательно, теперь можно удалить кнопку сумма.
Однако, возникает проблема с проверкой допустимости введенных значений. Если пользователь введет текст вместо числа, то программа это обнаружит и выведет на экран сообщение. Однако, это сообщение будет выводиться на экран по два раза при вводе недопустимого символа. То есть, событие происходит дважды — и это известная проблема для приложений Windows Phone. Обойти эту проблему можно, проверяя, были ли изменены данные при последовательных вызовах событий с теми же данными:
string oldFirstNumber = ""; private void firstNumberTextBox_TextChanged(object sender, TextChangedEventArgs e) { if (firstNumberTextBox.Text == oldFirstNumber) return; oldFirstNumber = firstNumberTextBox.Text; calculateResult(); }
Этот обработчик вызывает метод calculateResult, только если текст в элементе TextBox действительно изменился.
Привязка данных
Привязка данных позволяет связать визуальные элементы с методами, которые работают с этими элементами. Это позволяет связать свойство визуального элемента напрямую с объектом. Все что нужно для этого сделать — это создать особый объект, значения которого будут автоматически передаваться элементам Silverlight.
Привязка данных может работать в любом направлении. Можно связать текст элемента TextBox с приложением таким образом, чтобы при изменении текста в этом текстовом поле приложение было об этом уведомлено. Также можно связать элемент TextBlock с объектом так, чтобы при изменении свойства объекта содержимое элемента TextBlock соответственно обновлялось. Это пример двунаправленной привязки, когда программа может сама изменить элемент и отреагировать на изменения в этом элементе. Можно задать однонаправленную привязку, чтобы программа только выводила на экран результаты при изменении свойства объекта. Наконец, можно связать любые свойства объектов так, чтобы программа могла переместить элемент Silverlight по экрану при изменении свойств X и Y объекта игры.
Создание объекта для привязки. Попробуем создать версию программы Сумматор, использующую привязку данных. Начнем с класса, который сделает всю работу. Нужно определить свойства класса, которые будут связаны со свойствами визуальных элементов. Наш объект будет содержать три свойства:
- текст верхнего элемента TextBox;
- текст нижнего элемента TextBox;
- текст элемента TextBlock.
Можно создать класс AdderClass, имеющий следующую структуру:
public class AdderClass { private int topValue; public int TopValue { get { return topValue; } set { topValue = value; } } private int bottomValue; public int BottomValue { get { return bottomValue; } set { bottomValue = value; } } public int AnswerValue { get { return topValue + bottomValue; } } }
У этого класса три свойства. Первые два предназначены для чтения и записи, чтобы можно было получить текст верхнего и нижнего текстового поля и занести в них новые значения. Третье свойство предназначено только для получения последнего результата без возможности его изменения напрямую. Программа может создать экземпляр этого класса, установить значения свойств TopValue и BottomValue и затем получить результат.
Добавление функций уведомления. Для того чтобы класс можно было связать с объектом Silverlight, в нем должен быть реализован интерфейс INotifyPropertyChanged:
public interface INotifyPropertyChanged { // Событие происходит при изменении значений свойства event PropertyChangedEventHandler PropertyChanged; }
Класс, который реализует этот интерфейс, должен содержать делегат события, которое будет использовать класс AdderClass для сообщения об изменении значения свойства.
public event PropertyChangedEventHandler PropertyChanged;
Класс PropertyChangedEventHandler используется элементами Silverlight для управления сообщениями событий. Он описан в пространстве имен System.ComponentModel. Если компонент Silverlight нужно связать с каким-либо свойством класса, можно добавить к этому событию делегаты.
Класс AdderClass будет использовать этот делегат для того, чтобы сообщить об изменении одного из свойств в классе. При этом произойдет обновление экрана. Связанные с этими свойствами объекты Silverlight связываются с делегатом, чтобы они могли получить уведомление в случае необходимости. Окончательная версия класса может выглядеть так:
public class AdderClass : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private int topValue; public int TopValue { get { return topValue; } set { topValue = value; if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("AnswerValue")); } } } // то же самое для свойства BottomValue public int AnswerValue { get { return topValue + bottomValue; } } }
Класс AdderClass теперь содержит свойство PropertyChanged. Каждый раз при изменении значения свойства он должен проверить значение свойства PropertyChanged. Если оно не равно null, то оно связано с делегатом. Можно рассматривать свойство PropertyChanged как список рассылки. Процесс может подписаться на рассылку и получать уведомления об интересующих событиях, которые происходят в AdderClass. Если никто не подписан на список рассылки (то есть значение PropertyChanged равно null), то нет точки вызова метода для описания изменений. Однако, если оно не равно null, это означает, что есть объект, который интересуют изменения, происходящие в AdderClass.
Вызов метода, который генерирует событие, выглядит следующим образом:
PropertyChanged(this, new PropertyChangedEventArgs("AnswerValue"));
Первый параметр является ссылкой на объект, который генерирует событие. Вторым параметром является значение аргумента, которое содержит имя свойства, значение которого изменилось. Элемент Silverlight использует технологию отражения (т.е. он будет считывать публичные свойства класса AdderClass), чтобы узнать, какие свойства доступны. Если он получит уведомление, что значение свойства AnswerValue изменилось, то он будет обновлять все свойства визуальных элементов, связанные со свойством этого класса.
Когда программа сгенерирует событие, на экран будет выведено обновленное значение свойства AnswerValue. При вызове кода секции get свойства AnswerValue он вычисляет и возвращает результат.
public int AnswerValue { get { return topValue + bottomValue; } }
Следующим шагом будет соединение этого объекта с пользовательским интерфейсом. Пользовательский интерфейс сообщит классу AdderClass, когда значения двух текстовых полей будут изменены пользователем. Когда будут вводиться новые значения полей, экземпляр класса AdderClass должен сообщить пользовательскому интерфейсу, что результат изменился, и его нужно вывести на экран.
Добавление пространства имен на страницу MainPage. Для того чтобы связать созданный объект с данными, нужно связать код объекта с XAML-кодом, описывающим пользовательский интерфейс. Необходимо добавить в код XAML пространство имен, содержащее класс AdderClass:
xmlns:local="clr-namespace:AddingMachine"
Этот атрибут корневого элемента в XAML-файле для страницы MainPage.xaml делает доступными любые классы пространства имен AddingMachine для использования в XAML-коде. Как и в программе на C# в начале файла должны указываться все директивы using, так и в начале файла XAML также должны указываться все используемые пространства имен.
После добавления пространства имен нужно объявить имя класса, который описан в этом пространстве имен и используется в качестве ресурса:
<phone:PhoneApplicationPage.Resources> <local:AdderClass x:Key="AdderClass" /> </phone:PhoneApplicationPage.Resources>
Добавление класса к элементу на странице. После того как пространство имен стало доступным, можно соединить нужный класс из этого пространства имен с элементами страницы. В нашем случае таким элементом будет элемент Grid, который содержит все визуальные элементы страницы. Добавление класса к элементу автоматически делает его доступным для любых вложенных элементов, и таким образом, элементы TextBox и TextBlock могут использовать этот класс.
<Grid x:Name="LayoutRoot" Background="Transparent" DataContext="{StaticResource AdderClass}">
Этот код указывает элементу Grid использовать класс AdderClass, указанный в значении атрибута DataContext. В нашем приложении он является статическим ресурсом, в котором класс является частью программы.
Привязка элемента Silverlight к свойству объекта. Теперь класс AdderClass доступен в контексте данных элементам на странице. При этом создается связь между страницей Silverlight и объектом, в котором описано поведение.
Теперь нужно привязать свойства каждого элемента к свойствам объекта. Для этого можно использовать область свойств для каждого элемента. Нужно связать текст элемента firstNumberTextBox со свойством TopValue класса AdderClass. Если щелкнуть по элементу TextBox в редакторе Visual Studio, откроется область свойств для этого элемента, в которой для свойства Text в контекстном меню нужно выбрать пункт Применить привязку данных…. После этого откроется окно, в котором нужно указать, с каким свойством какого объекта требуется связать выбранное свойство элемента.
Выбрав необходимый элемент из списка доступных элементов и их свойств, можно привязать свойство элемента Silverlight к свойству объекта. Обратите внимание, что в разделе Параметры выбран режим TwoWay, при котором при изменении пользователем значения в элементе TextBox объект автоматически был об этом уведомлен.
При связывании с элементом TextBlock доступен только режим OneWay, поскольку нельзя передать в программу данные из объекта TextBlock.
После установки привязки программа будет работать. При этом содержимое файла MainPage.xaml.cs с кодом программы не изменится. Вся работа теперь выполняется в классе, который соединен с визуальными элементами. Изменение содержимого элементов TextBox, используемых для ввода значений, приведет к возникновению события класса AdderClass, которые приведут к изменению содержимого класса AnswerValue, связанного с элементом ResultTextBlock.
Привязка данных через свойство DataContext
Для установки привязки к данным потребовалось проделать большую работу. Однако, существует способ упростить выполнение этих действий.
Установка привязки данных в XAML. Можно просто связать свойство объекта со свойством элемента в XAML-коде, и Silverlight выполнит привязку.
<TextBox Height="72" HorizontalAlignment="Left" Margin="8,19,0,0" Name="firstNumberTextBox" Text="{Binding TopValue, Mode=TwoWay}" VerticalAlignment="Top" Width="460" TextAlignment="Center" />
В этом коде указана привязка текста элемента к свойству TopValue объекта. Режим привязки установлен в значение TwoWay, чтобы изменения в программе (при вычислении результата) отображались на экране. Если нужно использовать визуальный элемент только для того, чтобы показать значение (например, результат вычисления), можно использовать режим привязки OneWay.
Установка свойства DataContext. Теперь остается создать экземпляр класса AdderClass и присвоить его свойству DataContext элемента, содержащего элемент firstNumberTextbox. Это можно сделать в конструкторе для основной страницы.
// Конструктор public MainPage() { InitializeComponent(); AdderClass adder = new AdderClass(); ContentGrid.DataContext = adder; }
Свойство DataContext элемента Silverlight идентифицирует объект, который содержит все свойства, связанные с элементом и с теми элементами, которые в нем содержатся. Элемент ContentGrid содержит элементы TextBox и TextBlock, и теперь каждая привязка, которую эти элементы содержат, будет отображаться на созданный экземпляр класса AdderClass.
В этом примере вся тяжелая работа с областью свойств и ресурсами заменена на небольшое количество XAML-кода и одну строку кода.
8.3. Управление ориентацией страницы приложения
Альбомная и книжная ориентация
Существует два варианта ориентации устройства: альбомная (устройство расположено горизонтально) и книжная (устройство расположено вертикально). Телефон может определить ориентацию, и некоторые встроенные приложения правильно обрабатывают изменение ориентации телефона.
Можно создавать приложения, которые смогут корректно работать в обоих режимах ориентации. При этом, потребуется потратить вдвое больше времени на создание пользовательского интерфейса и на написание программного кода, обрабатывающего изменение ориентации телефона и изменяющего соответственно ориентацию приложений.
Указание ориентации в файле MainPage.xaml. Страница может сообщить Silverlight, какую ориентацию она поддерживает. Для этого используются свойства класса PhoneApplicationPage:
SupportedOrientations="Portrait" Orientation="Portrait"
Эти настройки Visual Studio использует по умолчанию при создании страницы. Они указывают на то, что страница использует только портретную ориентацию. В случае необходимости эти настройки можно изменить:
SupportedOrientations="PortraitOrLandscape" Orientation="Portrait"
С этими настройками при изменении ориентации телефона приложение также будет изменять ориентацию (рис. 8.4).
Теперь программа изменяет содержимое экрана при изменении ориентации устройства, но поскольку расположение всех элементов задано фиксированными значениями, некоторые из них находятся не в том месте, в каком хотелось бы их видеть, а поле для результата не видно на экране.
<TextBox Height="72" HorizontalAlignment="Left" Margin="8,19,0,0" Name="firstNumberTextBox" Text="0" VerticalAlignment="Top" Width="460" TextAlignment="Center" />
Этот элемент использует значения отступа от краев экрана, которые используются для точного указания расположения элементов на экране. При этом, если у элемента указана позиция, которая находится за пределами экрана, то Silverlight просто не будет отображать этот элемент на экране.
Существует два способа исправления подобной ошибки. Один способ заключается в изменении параметров расположения элементов при изменении ориентации телефона.
Событие OrientationChanged. Класс PhoneApplicationPage включает событие OrientationChanged, которое происходит при изменении ориентации страницы. Как и для других событий, для него можно создать метод-обработчик, в который нужно добавить код для обработки изменения ориентации экрана:
private void PhoneApplicationPage_OrientationChanged(object sender, OrientationChangedEventArgs e) { if (e.Orientation == PageOrientation.PortraitUp) { setPortrait(); } else { setLandscape(); } }
Этот код использует параметр, значение которого определяет новую ориентацию, и вызывает соответствующий метод. Методы изменяют значения отступов от краев элементов, чтобы разместить их подходящим для определенной ориентации образом:
private void setLandscape() { firstNumberTextBox.Margin = new Thickness(8, 19, 0, 0); firstNumberTextBox.Width = 207; secondNumberTextBox.Margin = new Thickness(252, 19, 0, 0); secondNumberTextBox.Width = 207; plusTextBlock.Margin = new Thickness(221, 35, 0, 0); resultTextBlock.Margin = new Thickness(538, 35, 0, 0); }
Код метода setPortrait аналогичен методу setLandscape. Элементы располагаются в соответствии со значениями свойства Margin. Отступы от краев задаются значением свойства Thickness, которое содержит величину отступа и ширину границы. В нашем случае ширина границы установлена в 0, поэтому граница не будет отображаться. После добавления этих методов приложение будет работает в обоих режимах ориентации (рис. 8.5).
Часто приложения при изменении ориентации экрана используют анимацию перемещения элементов. Для создания подобных эффектов рекомендуется использовать инструмент Expression Blend.
Использование контейнеров для группировки элементов
Среди элементов Silverlight есть элементы-контейнеры, которые могут содержать другие элементы. Существует несколько различных типов контейнеров, среди которых есть элемент StackPanel.
Элемент StackPanel содержит последовательность элементов Silverlight. Он позволяет располагать вложенные в него элементы в определенном порядке. Элементы в контейнере могут располагаться вертикально (сверху вниз) или горизонтально (слева направо). Вместо того, чтобы настраивать для визуальных элементов отступы от краев экрана, можно поручить эту работу элементу StackPanel:
<StackPanel> <TextBox InputScope="Digits" Height="72" HorizontalAlignment="Center" Name="firstNumberTextBox" VerticalAlignment="Top" Width="460" TextAlignment="Center" /> <TextBlock Height="56" HorizontalAlignment="Center" Name="plusTextBlock" Text="+" VerticalAlignment="Top" FontSize="32" Width="25" /> <TextBox InputScope="Digits" Height="72" HorizontalAlignment="Center" Name="secondNumberTextBox" VerticalAlignment="Top" Width="460" TextAlignment="Center" /> <TextBlock Height="46" HorizontalAlignment="Center" Name="resultTextBlock" VerticalAlignment="Top" FontSize="30" Width="160" TextAlignment="Center" /> </StackPanel>
Элементы StackPanel решают сами, как будут отображаться элементы на экране. В этом коде лишь указано выравнивание всех элементов по центру выделенной им области.
При таком подходе возникает очень полезный побочный эффект, который заключается в том, что контейнер StackPanel автоматически перестроит элементы при изменении ориентации экрана (рис. 8.6).
Элемент StackPanel можно добавить к проекту, перетащив его с панели инструментов Visual Studio в окно дизайнера или вводя текст XAML вручную. Если элемент StackPanel должен группировать элементы горизонтально, нужно указать соответствующий атрибут:
<StackPanel Orientation="Horizontal">
Также можно поместить один StackPanel в другой, что позволяет использовать другую группировку вложенных элементов.
Существуют и другие типы контейнеров. Один из них — элемент Grid. Он позволяет создать прямоугольную сетку из элементов, в каждом из которых может размещаться один или несколько элементов. Фактически в приложении Сумматор экран содержит сетку, состоящую из одного элемента, который занимает всю доступную область экрана.