Цель лекции: Обработка касаний, жестов, событий, написание кода.
Экран телефона чувствителен к прикосновению и существенно отличается от старых сенсорных экранов, в основном повторяющих ввод с мыши, или экранов планшетных устройств, которые могут распознавать рукописный ввод.
Мультисенсорный экран устройства Windows Phone 7 может распознавать одновременное касание как минимум в четырех точках. Именно обработка взаимодействия этих одновременных касаний делает задачу реализации мультисенсорного ввода такой сложной для разработчиков. Мы будем рассматривать сенсорный интерфейс в контексте примеров приложений, которые могут реагировать на простые касания.
Тестирование критически важного кода реализации мультисенсорного ввода необходимо выполнять на реальном устройстве, работающем под управлением Windows Phone 7. Между тем эмулятор телефона будет реагировать на действия мыши и преобразовывать их в сенсорный ввод. Чтобы использовать сенсорный ввод непосредственно на эмуляторе, его необходимо запустить под управлением Windows 7 на устройстве с поддерживающим мультисенсорный ввод экраном.
Приведенные в данной главе программы во многом схожи с простыми приложениями "XnaHelloPhone" из четвёртой лекции. Единственное отличие в том, что при касании текста пальцем, он будет случайным образом менять свой цвет, а при касании вне области текста, он будет опять возвращаться к исходному белому цвету (или любому другому цвету, какой будет применен к тексту при запуске программы).
В программе на Silverlight сенсорный ввод реализован через события. В приложении на XNA сенсорный ввод передается через статический класс, опрашиваемый в ходе выполнения метода Update. Одно из основных назначений XNA-метода Update – проверка состояния сенсорного ввода и внесение изменений, которые отображаются на экране во время выполнения метода Draw.
В XNA устройства мультисенсорного ввода называют сенсорной панелью. Для обработки такого ввода используются методы статического класса TouchPanel (Сенсорная панель). Имеется также возможность обработки жестов, но пока начнем с более простыми данными касания.
Можно (но это не является обязательным) получать сведения о самом устройстве мультисенсорного ввода через вызов статического метода TouchPanel.GetCapabilities. Объект TouchPanelCapabilities (Возможности сенсорной панели), возвращаемый этим методом, имеет два свойства:
Для большинства задач достаточно использовать один из двух статических методов TouchPanel. Для получения ввода от простого касания при каждом вызове Update после запуска программы, скорее всего, будет вызываться этот метод:
TouchCollection touchLocations = TouchPanel.GetState();
TouchLocation (Место касания). TouchLocation имеет три свойства:
State (Состояние), его значениями являются элементы перечисления TouchLocationState (Состояние места касания): Pressed (Нажат), Moved (Перемещен), Released (Высвобожден). Position (Местоположение) – это Vector2, обозначающий положение пальца относительно верхнего левого угла окна просмотра. Id – целое число, идентифицирующее отдельное касание от состояния Pressed до Released, то есть в течение всего времени касания. Если ни один палец не касается экрана, коллекция
Единственное исключение, если палец быстро "постукивает" по экрану – т.е. поднимается и опускается на экран с частотой примерно 1/30 секунды – свойство State объекта TouchLocation от значения Pressed сразу перейдет к значению Released, минуя состояния Moved.
Как правило, экрана будут касаться множество пальцев; и опускаться, перемещаться и покидать экран они будут независимо друг от друга. Для отслеживания отдельного касания используется свойство Id (Идентификатор). Для одного отдельно взятого касания Id будет неизменным от состояния Pressed, на протяжении всех перемещений (значения Moved) и до состояния Released.
TouchLocation также имеет очень удобный метод TryGetPreviousLocation (Попытаться получить предыдущее местоположение), который вызывается следующим образом:
TouchLocation previousTouchLocation;
bool success = touchLocation.TryGetPreviousLocation(out previousTouchLocation);
Вызов этого метода практически всегда происходит, когда touchLocation.State имеет значение Moved, для получения предыдущего местоположения и вычисления разницы. Если значение touchLocation.State равно Pressed, TryGetPreviousLocation возвратит false, и значением previousTouchLocation.State будет элемент перечисления TouchLocationState.Invalid. Такие же результаты будут получены в случае вызова этого метода для TouchLocation, который был возвращен TryGetPreviousLocation.
Разрабатываемое нами приложение будет менять цвет текста при касании пользователем экрана, т.е. обработка TouchPanel.GetStates будет относительно простой. Логика приложения будет проверять только объекты TouchLocation, значение свойства State которых равно Pressed.
Назовем новый проект XnaTouchHello. Как и в предыдущем примере на XNA нам понадобится шрифт, который мы создадим. Назовём его "segoe36" и, открыв файл со шрифтом, увеличим его размер до. 36:
<Size>36</Size>
Потребуется еще несколько дополнительных полей:
Проект XNA: XnaTouchHello Файл: Game1.cs (фрагмент, демонстрирующий поля)
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Random rand = new Random();
string text = "Hello, Windows Phone 7!";
SpriteFont segoe36; Vector2 textSize;
Vector2 textPosition;
Color textColor = Color.White;
…
}
Метод LoadContent аналогичен используемому ранее, за исключением того, что textSize сохраняется как поле. Это обеспечит возможности доступа к нему при последующих вычислениях:
Проект XNA: XnaTouchHello Файл: Game1.cs (фрагмент)
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
segoe36 = this.Content.Load<SpriteFont>("Segoe36");
textSize = segoe36.MeasureString(text);
Viewport viewport = this.GraphicsDevice.Viewport;
textPosition = new Vector2((viewport.Width - textSize.X) / 2, (viewport.Height - textSize.Y) / 2);
}
Как это свойственно приложениям на XNA, "действие" происходит преимущественно в методе Update. Этот метод вызывает TouchPanel.GetStates и затем поэлементно обходит коллекцию объектов TouchLocation, выбирая те из них, значение State которых равно Pressed.
Проект XNA: XnaTouchHello Файл: Game1.cs (фрагмент)
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
TouchCollection touchLocations = TouchPanel.GetState();
foreach (TouchLocation touchLocation in touchLocations) {
if (touchLocation.State == TouchLocationState.Pressed) {
Vector2 touchPosition = touchLocation.Position;
if (touchPosition.X >= textPosition.X
touchPosition.X < textPosition.X + textSize.X
touchPosition.Y >= textPosition.Y
touchPosition.Y < textPosition.Y + textSize.Y) {
textColor = new Color((byte)rand.Next(256), (byte)rand.Next(256), (byte)rand.Next(256));
} else { textColor = Color.White; }
}
} base.Update(gameTime);
}
Если Position оказывается где-нибудь внутри области, занимаемой текстовой строкой, полю textColor (Цвет текста) присваивается случайное значение RGB для цвета с помощью одного из конструкторов структуры Color (Цвет). В противном случае textColor присваивается значение Color.White.
Метод Draw практически аналогичен используемому в предыдущих примерах, только цвет текста теперь стал переменным:
Проект XNA: XnaTouchHello Файл: Game1.cs (фрагмент)
protected override void Draw(GameTime gameTime)
{
this.GraphicsDevice.Clear(Color.Navy);
spriteBatch.Begin();
spriteBatch.DrawString(segoe36, text, textPosition, textColor);
spriteBatch.End();
base.Draw(gameTime);
}
Единственная проблема в том, что касание не так строго детерминировано, как этого хотелось бы. Даже при касании одним пальцем контактов с экраном может быть несколько. В некоторых случаях один и тот же цикл foreach в методе Update может задавать textColor несколько раз.
Запустим наш проект, в результате при касании текста, его цвет будет случайным образом меняться, при касании вне его, цвет текста будет устанавливаться на цвет по умолчанию.
(рис 6.1) Результат исполнения программы
Создадим новый проект под названием XnaTapHello. Повторим все действия, что и для предыдущего приложения до изменения метода LoadContent.
Класс TouchPanel также включает возможности распознавания жестов, что демонстрирует проект XnaTapHello. В данном проекте используются те же поля, что и в XnaTouchHello, но несколько отличается метод LoadContent:
Проект XNA: XnaTapHello Файл: Game1.cs (фрагмент)
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
segoe36 = this.Content.Load<SpriteFont>("Segoe36");
textSize = segoe36.MeasureString(text);
Viewport viewport = this.GraphicsDevice.Viewport;
textPosition = new Vector2((viewport.Width - textSize.X) / 2, (viewport.Height - textSize.Y) / 2);
TouchPanel.EnabledGestures = GestureType.Tap;
}
Обратите внимание на последнее выражение. GestureType (Тип жеста) – это перечисление, элементами которого являются:
Эти элементы определены как битовые флаги, таким образом, они могут комбинироваться с помощью побитового C#-оператора OR.
Метод Update совсем другой.
Проект XNA: XnaTapHello Файл: Game1.cs (фрагмент)
protected override void Update(GameTime gameTime)
{
// Обеспечиваем возможность выхода из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
while (TouchPanel.IsGestureAvailable)
{
GestureSample gestureSample = TouchPanel.ReadGesture();
if (gestureSample.GestureType == GestureType.Tap)
{
Vector2 touchPosition = gestureSample.Position;
if (touchPosition.X >= textPosition.X
touchPosition.X < textPosition.X + textSize.X
touchPosition.Y >= textPosition.Y
touchPosition.Y < textPosition.Y + textSize.Y)
{
textColor = new Color((byte)rand.Next(256), (byte)rand.Next(256), (byte)rand.Next(256));
}
else
{
textColor = Color.White;
}
}
}
base.Update(gameTime);
}
Несмотря на то, что данное приложение рассматривает только один тип жеста, код довольно универсален. Если жест доступен, метод TouchPanel.ReadGesture (Прочитать жест) возвращает его как объект типа GestureSample (Пример жеста). Кроме применяемых здесь GestureType и Position, у этого объекта имеется еще свойство Delta (Приращение), обеспечивающее данные о перемещении в виде объекта Vector2. Для некоторых жестов (таких как Pinch) GestureSample также предоставляет состояние второй точки касания через свойства Position2 и Delta2.
Метод Draw аналогичен используемому в предыдущем случае, но поведение данного приложения будет несколько иным. В предыдущей программе текст меняет цвет, когда палец касается экрана; во втором изменение цвета происходит, когда палец убирается с экрана. Средству распознавания жестов необходимо дождаться завершения жеста, чтобы определить его тип.
Как и XNA, Silverlight поддерживает два разных программных интерфейса для работы с мультисенсорным вводом, которые можно категоризировать как интерфейс обработки простого и интерфейс обработки сложного касания. Интерфейс обработки простого касания построен на событии Touch.FrameReported, которое очень похоже на XNA-класс TouchPanel. Отличается оно лишь тем, что это событие, и оно не включает обработку жестов.
Интерфейс обработки сложного касания включает три события, определяемые классом UIElement: ManipulationStarted (Обработка началась), ManipulationDelta (Приращение в ходе обработки) и ManipulationCompleted (Обработка завершилась). События Manipulation, как их обобщенно называют, консолидируют взаимодействие множества касаний в движение и коэффициенты масштабирования.
Ядром интерфейса обработки простого касания в Silverlight является класс
Action (Действие) типа TouchAction (Действие касания) – перечисление с элементами Down (Вниз), Move (Перемещение) и Up (Вверх). Position типа Point (Точка), значение которого определяется относительно верхнего левого угла конкретного элемента. Будем называть этот элемент опорным. Size типа Size. Это свойство должно представлять область касания (и, следовательно, давление, создаваемое пальцем, в некотором роде), но эмулятор Windows Phone 7 не возвращает полезных значений. TouchDevice типа TouchDevice.Объект TouchDevice имеет два свойства только для чтения:
Id типа int, используется для идентификации касаний. Каждое отдельное касание ассоциировано с уникальным Id на протяжении всех событий, начиная от Down и до Up.DirectlyOver (Непосредственно над) типа UIElement – самый верхний элемент, расположенный прямо под пальцем.Как видите, Silverlight-объекты и TouchDevice предоставляют преимущественно те же сведения, что и XNA-объект TouchLocation. Свойство DirectlyOver объекта TouchDevice часто очень полезно для определения, какого элемента пользователь касается.
Для использования интерфейса обработки простого касания необходимо установить обработчик статического события Touch.FrameReported:
Touch.FrameReported += OnTouchFrameReported;
Метод OnTouchFrameReported выглядит следующим образом:
void OnTouchFrameReported(object sender, TouchFrameEventArgs args)
{
…
}
Этот обработчик события принимает все события касания в ходе выполнения приложения. Объект TouchFrameEventArgs (Аргументы события касания рамки) имеет свойство TimeStamp (Отметка времени) типа int и три метода:
GetTouchPoints(refElement) (Получить точки касания) возвращает TouchPointCollection (Коллекция точек касания) GetPrimaryTouchPoint(refElement) (Получить основную точку касания) возвращает один TouchPoint SuspendMousePromotionUntilTouchUp() (Приостановить перемещения мыши до завершения касания)В общем случае вызывается метод GetTouchPoints и в него передается опорный элемент. Значения свойств Position объектов null в GetTouchPoints, значения свойств Position будут установлены относительно верхнего левого угла окна просмотра приложения.
Между опорным элементом и элементом DirectlyOver нет никакой связи. Событие всегда обрабатывает все касания приложения в целом. Вызов GetTouchPoints или GetPrimaryTouchPoint для конкретного элемента не означает, что будут обрабатываться касания только этого элемента, это означает лишь то, что значение свойства Position будет определяться относительно этого элемента. (Поэтому координаты Position вполне могут быть отрицательными, если место касания находится слева или над опорным элементом.)
Элемент DirectlyOver определяет элемент, находящийся непосредственно под пальцем.
Для разговора о втором и третьем методах необходимо сделать небольшое вступление. Событие Touch.FrameReported появилось в Silverlight для настольных приложений, в которых для логики обработки событий мыши существующих элементов управления удобно автоматически использовать сенсорный ввод. По этой причине события касания приравнены к событиям мыши.
Но это распространяется только на "первую" точку касания, т.е. на действия пальца, коснувшегося экрана первым, когда ни один другой палец его не касается. Если вы не хотите, чтобы действия этого касания трактовались как события мыши, обработчик события должен начинаться так:
void OnTouchFrameReported(object sender, TouchFrameEventArgs args)
{
TouchPoint primaryTouchPoint = args.GetPrimaryTouchPoint(null);
if (primaryTouchPoint != null primaryTouchPoint.Action == TouchAction.Down)
{
args.SuspendMousePromotionUntilTouchUp();
}
…
}
Метод SuspendMousePromotionUntilTouchUp может вызываться только в момент первого касания первым пальцем, когда все остальные пальцы еще не коснулись экрана.
Для Windows Phone 7 такая логика представляет некоторые проблемы. Как сказано выше, по сути, происходит отключение обработки событий мыши для всего приложения. Если приложение для телефона включает элементы управления Silverlight, изначально написанные для ввода с помощью мыши и не обновленные для приема сенсорного ввода, эти элементы управления фактически будут деактивированы.
Конечно, можно проверять свойство DirectlyOver и делать селективную приостановку обработки событий мыши. Но в телефоне не должно быть элементов, обрабатывающих ввод с помощью мыши, кроме тех которые не обрабатывают сенсорный ввод! Поэтому, вероятно, больше смысла будет в том, чтобы никогда не приостанавливать обработку событий мыши.
Для приложения, которое мы напишем, важна только первоначальная точка касания в момент, когда ее TouchAction имеет значение Down, поэтому мы используем ту же самую логику.
В проекте SilverlightTouchHello TextBlock описывается в XAML-файле:
Проект Silverlight: SilverlightTouchHello Файл: MainPage.xaml (фрагмент)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <TextBlock Name="txtblk" Text="Hello, Windows Phone 7!" Padding="0 34" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Grid>
Обратите внимание на значение Padding. Свойство FontSize отображаемого здесь текста равно 20 пикселям, это обеспечивает TextBlock высоту, равную около 27 пикселей. Также нам известно о рекомендации, что мишень касания не должна быть меньше 9 миллиметров. Если разрешение экрана телефона равно 264 DPI, 9 миллиметров – это 94 пикселей. (9 миллиметров разделить на 25,4 миллиметра/дюйм и умножить на 264 пикселей/дюйм.) TextBlock не хватает 67 пикселей. Поэтому я задаю значение Padding, которое добавляет по 34 пикселя сверху и снизу (но не по бокам).
Мы применили здесь Padding, а не Margin, потому что Padding – это область внутри TextBlock. Таким образом, TextBlock фактически становится больше, чем предполагает размер текста. Margin – это область вне TextBlock. Она не является частью TextBlock и касания ее не учитываются как касания TextBlock.
Рассмотрим файл выделенного кода полностью. Конструктор MainPage определяет обработчик событий Touch.FrameReported.
Проект Silverlight: SilverlightTouchHello Файл: MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
namespace SilverlightTouchHello
{
public partial class MainPage : PhoneApplicationPage
{
Random rand = new Random(); Brush originalBrush;
public MainPage()
{
InitializeComponent();
originalBrush = txtblk.Foreground;
Touch.FrameReported += OnTouchFrameReported;
}
void OnTouchFrameReported(object sender, TouchFrameEventArgs args)
{
TouchPoint primaryTouchPoint = args.GetPrimaryTouchPoint(null);
if (primaryTouchPoint != null primaryTouchPoint.Action == TouchAction.Down)
{
if (primaryTouchPoint.TouchDevice.DirectlyOver == txtblk)
{
txtblk.Foreground = new SolidColorBrush(Color.FromArgb(255, (byte)rand.Next(256),
(byte)rand.Next(256), (byte)rand.Next(256)));
}
else
{
txtblk.Foreground = originalBrush;
}
}
}
}
}
Этот обработчик событий обрабатывает только первые точки касания, для которых Action имеет значение Down. Если свойство DirectlyOver возвращает элемент txtblk, создается случайный цвет. В отличие от XNA структура Color в Silverlight не имеет конструктора для задания цвета как комбинации значений красного, зеленого и синего, но в ней есть статический метод FromArgb, который создает объект Color на основании значений альфа, красного, зеленого и синего каналов, где альфа – это прозрачность. Для получения непрозрачного цвета, задайте альфа-каналу значение 255. Это очевидно не во всех файлах XAML, но свойство Foreground типа Brush (Кисть) – это абстрактный класс, от которого наследуется SolidColorBrush (Одноцветная кисть).
Если DirectlyOver не txtblk, цвет текста не меняется на белый. В случае если бы пользователь выбрал цветовую тему с черным текстом на белом фоне, это привело бы к тому, что текст исчез бы с экрана. Вместо этого свойству Foreground присваивается изначально заданный для TextBlock цвет. Он определяется в конструкторе.
Запустим программу и посмотрим её в действии:
(рис 6.2) Результат исполнения программы
В данной лекции мы:
Добавьте во все проекты вторую строку, измените все программы так, чтобы при нажатии на одну из строк, менялся цвет другой.
Цель лекции: Обработка касаний, жестов, событий, написание кода.
Экран телефона чувствителен к прикосновению и существенно отличается от старых сенсорных экранов, в основном повторяющих ввод с мыши, или экранов планшетных устройств, которые могут распознавать рукописный ввод.
Мультисенсорный экран устройства Windows Phone 7 может распознавать одновременное касание как минимум в четырех точках. Именно обработка взаимодействия этих одновременных касаний делает задачу реализации мультисенсорного ввода такой сложной для разработчиков. Мы будем рассматривать сенсорный интерфейс в контексте примеров приложений, которые могут реагировать на простые касания.
Тестирование критически важного кода реализации мультисенсорного ввода необходимо выполнять на реальном устройстве, работающем под управлением Windows Phone 7. Между тем эмулятор телефона будет реагировать на действия мыши и преобразовывать их в сенсорный ввод. Чтобы использовать сенсорный ввод непосредственно на эмуляторе, его необходимо запустить под управлением Windows 7 на устройстве с поддерживающим мультисенсорный ввод экраном.
Приведенные в данной главе программы во многом схожи с простыми приложениями "XnaHelloPhone" из четвёртой лекции. Единственное отличие в том, что при касании текста пальцем, он будет случайным образом менять свой цвет, а при касании вне области текста, он будет опять возвращаться к исходному белому цвету (или любому другому цвету, какой будет применен к тексту при запуске программы).
В программе на Silverlight сенсорный ввод реализован через события. В приложении на XNA сенсорный ввод передается через статический класс, опрашиваемый в ходе выполнения метода Update. Одно из основных назначений XNA-метода Update – проверка состояния сенсорного ввода и внесение изменений, которые отображаются на экране во время выполнения метода Draw.
В XNA устройства мультисенсорного ввода называют сенсорной панелью. Для обработки такого ввода используются методы статического класса TouchPanel (Сенсорная панель). Имеется также возможность обработки жестов, но пока начнем с более простыми данными касания.
Можно (но это не является обязательным) получать сведения о самом устройстве мультисенсорного ввода через вызов статического метода TouchPanel.GetCapabilities. Объект TouchPanelCapabilities (Возможности сенсорной панели), возвращаемый этим методом, имеет два свойства:
Для большинства задач достаточно использовать один из двух статических методов TouchPanel. Для получения ввода от простого касания при каждом вызове Update после запуска программы, скорее всего, будет вызываться этот метод:
TouchCollection touchLocations = TouchPanel.GetState();
TouchLocation (Место касания). TouchLocation имеет три свойства:
State (Состояние), его значениями являются элементы перечисления TouchLocationState (Состояние места касания): Pressed (Нажат), Moved (Перемещен), Released (Высвобожден). Position (Местоположение) – это Vector2, обозначающий положение пальца относительно верхнего левого угла окна просмотра. Id – целое число, идентифицирующее отдельное касание от состояния Pressed до Released, то есть в течение всего времени касания. Если ни один палец не касается экрана, коллекция
Единственное исключение, если палец быстро "постукивает" по экрану – т.е. поднимается и опускается на экран с частотой примерно 1/30 секунды – свойство State объекта TouchLocation от значения Pressed сразу перейдет к значению Released, минуя состояния Moved.
Как правило, экрана будут касаться множество пальцев; и опускаться, перемещаться и покидать экран они будут независимо друг от друга. Для отслеживания отдельного касания используется свойство Id (Идентификатор). Для одного отдельно взятого касания Id будет неизменным от состояния Pressed, на протяжении всех перемещений (значения Moved) и до состояния Released.
TouchLocation также имеет очень удобный метод TryGetPreviousLocation (Попытаться получить предыдущее местоположение), который вызывается следующим образом:
TouchLocation previousTouchLocation;
bool success = touchLocation.TryGetPreviousLocation(out previousTouchLocation);
Вызов этого метода практически всегда происходит, когда touchLocation.State имеет значение Moved, для получения предыдущего местоположения и вычисления разницы. Если значение touchLocation.State равно Pressed, TryGetPreviousLocation возвратит false, и значением previousTouchLocation.State будет элемент перечисления TouchLocationState.Invalid. Такие же результаты будут получены в случае вызова этого метода для TouchLocation, который был возвращен TryGetPreviousLocation.
Разрабатываемое нами приложение будет менять цвет текста при касании пользователем экрана, т.е. обработка TouchPanel.GetStates будет относительно простой. Логика приложения будет проверять только объекты TouchLocation, значение свойства State которых равно Pressed.
Назовем новый проект XnaTouchHello. Как и в предыдущем примере на XNA нам понадобится шрифт, который мы создадим. Назовём его "segoe36" и, открыв файл со шрифтом, увеличим его размер до. 36:
<Size>36</Size>
Потребуется еще несколько дополнительных полей:
Проект XNA: XnaTouchHello Файл: Game1.cs (фрагмент, демонстрирующий поля)
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Random rand = new Random();
string text = "Hello, Windows Phone 7!";
SpriteFont segoe36; Vector2 textSize;
Vector2 textPosition;
Color textColor = Color.White;
…
}
Метод LoadContent аналогичен используемому ранее, за исключением того, что textSize сохраняется как поле. Это обеспечит возможности доступа к нему при последующих вычислениях:
Проект XNA: XnaTouchHello Файл: Game1.cs (фрагмент)
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
segoe36 = this.Content.Load<SpriteFont>("Segoe36");
textSize = segoe36.MeasureString(text);
Viewport viewport = this.GraphicsDevice.Viewport;
textPosition = new Vector2((viewport.Width - textSize.X) / 2, (viewport.Height - textSize.Y) / 2);
}
Как это свойственно приложениям на XNA, "действие" происходит преимущественно в методе Update. Этот метод вызывает TouchPanel.GetStates и затем поэлементно обходит коллекцию объектов TouchLocation, выбирая те из них, значение State которых равно Pressed.
Проект XNA: XnaTouchHello Файл: Game1.cs (фрагмент)
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
TouchCollection touchLocations = TouchPanel.GetState();
foreach (TouchLocation touchLocation in touchLocations) {
if (touchLocation.State == TouchLocationState.Pressed) {
Vector2 touchPosition = touchLocation.Position;
if (touchPosition.X >= textPosition.X
touchPosition.X < textPosition.X + textSize.X
touchPosition.Y >= textPosition.Y
touchPosition.Y < textPosition.Y + textSize.Y) {
textColor = new Color((byte)rand.Next(256), (byte)rand.Next(256), (byte)rand.Next(256));
} else { textColor = Color.White; }
}
} base.Update(gameTime);
}
Если Position оказывается где-нибудь внутри области, занимаемой текстовой строкой, полю textColor (Цвет текста) присваивается случайное значение RGB для цвета с помощью одного из конструкторов структуры Color (Цвет). В противном случае textColor присваивается значение Color.White.
Метод Draw практически аналогичен используемому в предыдущих примерах, только цвет текста теперь стал переменным:
Проект XNA: XnaTouchHello Файл: Game1.cs (фрагмент)
protected override void Draw(GameTime gameTime)
{
this.GraphicsDevice.Clear(Color.Navy);
spriteBatch.Begin();
spriteBatch.DrawString(segoe36, text, textPosition, textColor);
spriteBatch.End();
base.Draw(gameTime);
}
Единственная проблема в том, что касание не так строго детерминировано, как этого хотелось бы. Даже при касании одним пальцем контактов с экраном может быть несколько. В некоторых случаях один и тот же цикл foreach в методе Update может задавать textColor несколько раз.
Запустим наш проект, в результате при касании текста, его цвет будет случайным образом меняться, при касании вне его, цвет текста будет устанавливаться на цвет по умолчанию.
(рис 6.1) Результат исполнения программы
Создадим новый проект под названием XnaTapHello. Повторим все действия, что и для предыдущего приложения до изменения метода LoadContent.
Класс TouchPanel также включает возможности распознавания жестов, что демонстрирует проект XnaTapHello. В данном проекте используются те же поля, что и в XnaTouchHello, но несколько отличается метод LoadContent:
Проект XNA: XnaTapHello Файл: Game1.cs (фрагмент)
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
segoe36 = this.Content.Load<SpriteFont>("Segoe36");
textSize = segoe36.MeasureString(text);
Viewport viewport = this.GraphicsDevice.Viewport;
textPosition = new Vector2((viewport.Width - textSize.X) / 2, (viewport.Height - textSize.Y) / 2);
TouchPanel.EnabledGestures = GestureType.Tap;
}
Обратите внимание на последнее выражение. GestureType (Тип жеста) – это перечисление, элементами которого являются:
Эти элементы определены как битовые флаги, таким образом, они могут комбинироваться с помощью побитового C#-оператора OR.
Метод Update совсем другой.
Проект XNA: XnaTapHello Файл: Game1.cs (фрагмент)
protected override void Update(GameTime gameTime)
{
// Обеспечиваем возможность выхода из игры
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
while (TouchPanel.IsGestureAvailable)
{
GestureSample gestureSample = TouchPanel.ReadGesture();
if (gestureSample.GestureType == GestureType.Tap)
{
Vector2 touchPosition = gestureSample.Position;
if (touchPosition.X >= textPosition.X
touchPosition.X < textPosition.X + textSize.X
touchPosition.Y >= textPosition.Y
touchPosition.Y < textPosition.Y + textSize.Y)
{
textColor = new Color((byte)rand.Next(256), (byte)rand.Next(256), (byte)rand.Next(256));
}
else
{
textColor = Color.White;
}
}
}
base.Update(gameTime);
}
Несмотря на то, что данное приложение рассматривает только один тип жеста, код довольно универсален. Если жест доступен, метод TouchPanel.ReadGesture (Прочитать жест) возвращает его как объект типа GestureSample (Пример жеста). Кроме применяемых здесь GestureType и Position, у этого объекта имеется еще свойство Delta (Приращение), обеспечивающее данные о перемещении в виде объекта Vector2. Для некоторых жестов (таких как Pinch) GestureSample также предоставляет состояние второй точки касания через свойства Position2 и Delta2.
Метод Draw аналогичен используемому в предыдущем случае, но поведение данного приложения будет несколько иным. В предыдущей программе текст меняет цвет, когда палец касается экрана; во втором изменение цвета происходит, когда палец убирается с экрана. Средству распознавания жестов необходимо дождаться завершения жеста, чтобы определить его тип.
Как и XNA, Silverlight поддерживает два разных программных интерфейса для работы с мультисенсорным вводом, которые можно категоризировать как интерфейс обработки простого и интерфейс обработки сложного касания. Интерфейс обработки простого касания построен на событии Touch.FrameReported, которое очень похоже на XNA-класс TouchPanel. Отличается оно лишь тем, что это событие, и оно не включает обработку жестов.
Интерфейс обработки сложного касания включает три события, определяемые классом UIElement: ManipulationStarted (Обработка началась), ManipulationDelta (Приращение в ходе обработки) и ManipulationCompleted (Обработка завершилась). События Manipulation, как их обобщенно называют, консолидируют взаимодействие множества касаний в движение и коэффициенты масштабирования.
Ядром интерфейса обработки простого касания в Silverlight является класс
Action (Действие) типа TouchAction (Действие касания) – перечисление с элементами Down (Вниз), Move (Перемещение) и Up (Вверх). Position типа Point (Точка), значение которого определяется относительно верхнего левого угла конкретного элемента. Будем называть этот элемент опорным. Size типа Size. Это свойство должно представлять область касания (и, следовательно, давление, создаваемое пальцем, в некотором роде), но эмулятор Windows Phone 7 не возвращает полезных значений. TouchDevice типа TouchDevice.Объект TouchDevice имеет два свойства только для чтения:
Id типа int, используется для идентификации касаний. Каждое отдельное касание ассоциировано с уникальным Id на протяжении всех событий, начиная от Down и до Up.DirectlyOver (Непосредственно над) типа UIElement – самый верхний элемент, расположенный прямо под пальцем.Как видите, Silverlight-объекты и TouchDevice предоставляют преимущественно те же сведения, что и XNA-объект TouchLocation. Свойство DirectlyOver объекта TouchDevice часто очень полезно для определения, какого элемента пользователь касается.
Для использования интерфейса обработки простого касания необходимо установить обработчик статического события Touch.FrameReported:
Touch.FrameReported += OnTouchFrameReported;
Метод OnTouchFrameReported выглядит следующим образом:
void OnTouchFrameReported(object sender, TouchFrameEventArgs args)
{
…
}
Этот обработчик события принимает все события касания в ходе выполнения приложения. Объект TouchFrameEventArgs (Аргументы события касания рамки) имеет свойство TimeStamp (Отметка времени) типа int и три метода:
GetTouchPoints(refElement) (Получить точки касания) возвращает TouchPointCollection (Коллекция точек касания) GetPrimaryTouchPoint(refElement) (Получить основную точку касания) возвращает один TouchPoint SuspendMousePromotionUntilTouchUp() (Приостановить перемещения мыши до завершения касания)В общем случае вызывается метод GetTouchPoints и в него передается опорный элемент. Значения свойств Position объектов null в GetTouchPoints, значения свойств Position будут установлены относительно верхнего левого угла окна просмотра приложения.
Между опорным элементом и элементом DirectlyOver нет никакой связи. Событие всегда обрабатывает все касания приложения в целом. Вызов GetTouchPoints или GetPrimaryTouchPoint для конкретного элемента не означает, что будут обрабатываться касания только этого элемента, это означает лишь то, что значение свойства Position будет определяться относительно этого элемента. (Поэтому координаты Position вполне могут быть отрицательными, если место касания находится слева или над опорным элементом.)
Элемент DirectlyOver определяет элемент, находящийся непосредственно под пальцем.
Для разговора о втором и третьем методах необходимо сделать небольшое вступление. Событие Touch.FrameReported появилось в Silverlight для настольных приложений, в которых для логики обработки событий мыши существующих элементов управления удобно автоматически использовать сенсорный ввод. По этой причине события касания приравнены к событиям мыши.
Но это распространяется только на "первую" точку касания, т.е. на действия пальца, коснувшегося экрана первым, когда ни один другой палец его не касается. Если вы не хотите, чтобы действия этого касания трактовались как события мыши, обработчик события должен начинаться так:
void OnTouchFrameReported(object sender, TouchFrameEventArgs args)
{
TouchPoint primaryTouchPoint = args.GetPrimaryTouchPoint(null);
if (primaryTouchPoint != null primaryTouchPoint.Action == TouchAction.Down)
{
args.SuspendMousePromotionUntilTouchUp();
}
…
}
Метод SuspendMousePromotionUntilTouchUp может вызываться только в момент первого касания первым пальцем, когда все остальные пальцы еще не коснулись экрана.
Для Windows Phone 7 такая логика представляет некоторые проблемы. Как сказано выше, по сути, происходит отключение обработки событий мыши для всего приложения. Если приложение для телефона включает элементы управления Silverlight, изначально написанные для ввода с помощью мыши и не обновленные для приема сенсорного ввода, эти элементы управления фактически будут деактивированы.
Конечно, можно проверять свойство DirectlyOver и делать селективную приостановку обработки событий мыши. Но в телефоне не должно быть элементов, обрабатывающих ввод с помощью мыши, кроме тех которые не обрабатывают сенсорный ввод! Поэтому, вероятно, больше смысла будет в том, чтобы никогда не приостанавливать обработку событий мыши.
Для приложения, которое мы напишем, важна только первоначальная точка касания в момент, когда ее TouchAction имеет значение Down, поэтому мы используем ту же самую логику.
В проекте SilverlightTouchHello TextBlock описывается в XAML-файле:
Проект Silverlight: SilverlightTouchHello Файл: MainPage.xaml (фрагмент)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <TextBlock Name="txtblk" Text="Hello, Windows Phone 7!" Padding="0 34" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Grid>
Обратите внимание на значение Padding. Свойство FontSize отображаемого здесь текста равно 20 пикселям, это обеспечивает TextBlock высоту, равную около 27 пикселей. Также нам известно о рекомендации, что мишень касания не должна быть меньше 9 миллиметров. Если разрешение экрана телефона равно 264 DPI, 9 миллиметров – это 94 пикселей. (9 миллиметров разделить на 25,4 миллиметра/дюйм и умножить на 264 пикселей/дюйм.) TextBlock не хватает 67 пикселей. Поэтому я задаю значение Padding, которое добавляет по 34 пикселя сверху и снизу (но не по бокам).
Мы применили здесь Padding, а не Margin, потому что Padding – это область внутри TextBlock. Таким образом, TextBlock фактически становится больше, чем предполагает размер текста. Margin – это область вне TextBlock. Она не является частью TextBlock и касания ее не учитываются как касания TextBlock.
Рассмотрим файл выделенного кода полностью. Конструктор MainPage определяет обработчик событий Touch.FrameReported.
Проект Silverlight: SilverlightTouchHello Файл: MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
namespace SilverlightTouchHello
{
public partial class MainPage : PhoneApplicationPage
{
Random rand = new Random(); Brush originalBrush;
public MainPage()
{
InitializeComponent();
originalBrush = txtblk.Foreground;
Touch.FrameReported += OnTouchFrameReported;
}
void OnTouchFrameReported(object sender, TouchFrameEventArgs args)
{
TouchPoint primaryTouchPoint = args.GetPrimaryTouchPoint(null);
if (primaryTouchPoint != null primaryTouchPoint.Action == TouchAction.Down)
{
if (primaryTouchPoint.TouchDevice.DirectlyOver == txtblk)
{
txtblk.Foreground = new SolidColorBrush(Color.FromArgb(255, (byte)rand.Next(256),
(byte)rand.Next(256), (byte)rand.Next(256)));
}
else
{
txtblk.Foreground = originalBrush;
}
}
}
}
}
Этот обработчик событий обрабатывает только первые точки касания, для которых Action имеет значение Down. Если свойство DirectlyOver возвращает элемент txtblk, создается случайный цвет. В отличие от XNA структура Color в Silverlight не имеет конструктора для задания цвета как комбинации значений красного, зеленого и синего, но в ней есть статический метод FromArgb, который создает объект Color на основании значений альфа, красного, зеленого и синего каналов, где альфа – это прозрачность. Для получения непрозрачного цвета, задайте альфа-каналу значение 255. Это очевидно не во всех файлах XAML, но свойство Foreground типа Brush (Кисть) – это абстрактный класс, от которого наследуется SolidColorBrush (Одноцветная кисть).
Если DirectlyOver не txtblk, цвет текста не меняется на белый. В случае если бы пользователь выбрал цветовую тему с черным текстом на белом фоне, это привело бы к тому, что текст исчез бы с экрана. Вместо этого свойству Foreground присваивается изначально заданный для TextBlock цвет. Он определяется в конструкторе.
Запустим программу и посмотрим её в действии:
(рис 6.2) Результат исполнения программы
В данной лекции мы:
Добавьте во все проекты вторую строку, измените все программы так, чтобы при нажатии на одну из строк, менялся цвет другой.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.