Рукописный ввод в Windows Phone 7
Ввод информации в Windows Phone 7 осуществляется путем использования встроенной программной или аппаратной (на некоторых устройствах) клавиатуры. Программная клавиатура способна подстраиваться под текущую ситуацию, в которой находится пользователь. Тем не менее, иногда может потребоваться нарисовать что-то на экране – в этом случае необходим рукописный ввод.

Рукописный ввод хорошо использовать когда нужно что-то нарисовать – небольшую картинку, например, или оставить свою подпись на экране. Такие задачи встречаются не слишком часто, но тем не менее они появляются. Для того, чтобы реализовать в приложении такую функциональность можно воспользоваться объектом InkPresenter
. Для этого просто добавим его на форму.
<phoneNavigation:PhoneApplicationPage
x:Class="InkTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phoneNavigation="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}">
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneBackgroundBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitleGrid is the name of the application and page title-->
<Grid x:Name="TitleGrid" Grid.Row="0">
<TextBlock Text="MY APPLICATION" x:Name="textBlockPageTitle" Style="{StaticResource PhoneTextPageTitle1Style}"/>
<TextBlock Text="Ink" x:Name="textBlockListTitle" Style="{StaticResource PhoneTextPageTitle2Style}"/>
</Grid>
<!--ContentGrid is empty. Place new content here-->
<Grid x:Name="ContentGrid" Grid.Row="1">
<InkPresenter Background="White" x:Name="ink1" />
</Grid>
</Grid>
</phoneNavigation:PhoneApplicationPage>
Однако, простого добавления элемента на форму будет недостаточно. После добавление необходимо обработать несколько событий – MouseMove
, MouseLeftButtonDown
и MouseLeftButtonUp
.
Дело в том, что объект InkPresenter
содержит коллекцию Strokes
, которая объекты Stroke
. Stroke
— это набор точек, нарисованных пользователем. Наша задача заключается в том, чтобы при нажатии и перемещении указателя сохранять все координаты в объект Stroke
и при отпускании добавить все эти точки в коллекцию Strokes
объекта InkPresenter
.
Для этих целей при нажатии на экран мы создадим новый объект Stroke
и при перемещении указателя будем добавлять каждую точку в этот объект. Для этих целей будем использовать события MouseLeftButtonDown
и MouseMove
.
public MainPage()
{
InitializeComponent();
SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;
ink1.MouseMove += new MouseEventHandler(ink1_MouseMove);
ink1.MouseLeftButtonDown += new MouseButtonEventHandler(ink1_MouseLeftButtonDown);
}
private Stroke _currentStroke;
void ink1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ink1.CaptureMouse();
_currentStroke = new Stroke();
_currentStroke.DrawingAttributes.Color = Colors.Red;
var currentPosition = e.GetPosition(ink1);
_currentStroke.StylusPoints.Add(new StylusPoint(currentPosition.X, currentPosition.Y));
ink1.Strokes.Add(_currentStroke);
}
void ink1_MouseMove(object sender, MouseEventArgs e)
{
if (_currentStroke != null)
{
var currentPosition = e.GetPosition(ink1);
_currentStroke.StylusPoints.Add(new StylusPoint(currentPosition.X, currentPosition.Y));
}
}
После того, как пользователь нарисовал то, что хотел, нужно просто присвоить полю _currentStroke
пустое значение (для того, чтобы больше не обрабатывалось событие MouseMove
).
void ink1_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_currentStroke = null;
}
После этого рукописный ввод уже будет работать. Давайте немного усовершенствуем наше приложение и добавим возможность отмены последнего ввода. Как вы, наверное, догадались, для этого необходимо удалить последний объект Stroke
из коллекции Strokes
.
Добавим меню, в котором будет пункт, позволяющий отменять последнее действие. Для этого сделаем ссылку на сборку Microsoft.Phone.Shell
и определим пространство имен shell
в XAML.
<phoneNavigation:PhoneApplicationPage
x:Class="InkTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phoneNavigation="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}">
<!-- содержимое формы -->
</phoneNavigation:PhoneApplicationPage>
Осталось только определить содержимое меню и создать для него обработчик.
<phoneNavigation:PhoneApplicationPage
x:Class="InkTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phoneNavigation="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}">
<phoneNavigation:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsMenuEnabled="True">
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text="Undo" Click="ApplicationBarMenuItem_Click"/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phoneNavigation:PhoneApplicationPage.ApplicationBar>
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneBackgroundBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitleGrid is the name of the application and page title-->
<Grid x:Name="TitleGrid" Grid.Row="0">
<TextBlock Text="MY APPLICATION" x:Name="textBlockPageTitle" Style="{StaticResource PhoneTextPageTitle1Style}"/>
<TextBlock Text="Ink" x:Name="textBlockListTitle" Style="{StaticResource PhoneTextPageTitle2Style}"/>
</Grid>
<!--ContentGrid is empty. Place new content here-->
<Grid x:Name="ContentGrid" Grid.Row="1">
<InkPresenter Background="White" x:Name="ink1" />
</Grid>
</Grid>
</phoneNavigation:PhoneApplicationPage>
Обработчик для отмены ввода будет выглядеть очень просто – в нем мы проверим вводил ли пользователь что-либо, и если да, то удалим этот фрагмент (объект Stroke
).
private void ApplicationBarMenuItem_Click(object sender, EventArgs e)
{
if (ink1.Strokes != null && ink1.Strokes.Count > 0)
{
ink1.Strokes.RemoveAt(ink1.Strokes.Count - 1);
}
}
Теперь у нас есть возможность отменить последний ввод. Но что, делать, если мы отменили его случайно? Логично, что рядом с Undo
должен быть и пункт Redo
. Сделать его очень просто – нужно просто вернуть объект Stroke
обратно в коллекцию Strokes
. Давайте добавим еще один пункт меню и создадим для него обработчик.
<phoneNavigation:PhoneApplicationPage
x:Class="InkTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phoneNavigation="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}">
<phoneNavigation:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsMenuEnabled="True">
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text="Undo" Click="ApplicationBarMenuItem_Click"/>
<shell:ApplicationBarMenuItem Text="Redo" Click="ApplicationBarMenuItem_Click_1"/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phoneNavigation:PhoneApplicationPage.ApplicationBar>
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneBackgroundBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitleGrid is the name of the application and page title-->
<Grid x:Name="TitleGrid" Grid.Row="0">
<TextBlock Text="MY APPLICATION" x:Name="textBlockPageTitle" Style="{StaticResource PhoneTextPageTitle1Style}"/>
<TextBlock Text="Ink" x:Name="textBlockListTitle" Style="{StaticResource PhoneTextPageTitle2Style}"/>
</Grid>
<!--ContentGrid is empty. Place new content here-->
<Grid x:Name="ContentGrid" Grid.Row="1">
<InkPresenter Background="White" x:Name="ink1" />
</Grid>
</Grid>
</phoneNavigation:PhoneApplicationPage>
Модифицируем обработчик отмены ввода таким образом, чтобы он сохранял удаленный объект. Обработчик для восстановления объекта просто будет добавлять к коллекцию этот удаленный объект. Сам удаленный объект будем хранить в локальном поле.
private void ApplicationBarMenuItem_Click(object sender, EventArgs e)
{
if (ink1.Strokes != null && ink1.Strokes.Count > 0)
{
_canceledStroke = ink1.Strokes.Last();
ink1.Strokes.RemoveAt(ink1.Strokes.Count - 1);
}
}
Stroke _canceledStroke = null;
private void ApplicationBarMenuItem_Click_1(object sender, EventArgs e)
{
if (_canceledStroke != null)
{
ink1.Strokes.Add(_canceledStroke);
_canceledStroke = null;
}
}
Понятно, что хранить только последнюю отмену – не очень правильно. Гораздо правильнее – хранить список, или лучше стек всех отмененных вводов. Но это я оставлю вам для тренировки пальцев.
Ну и, наконец, давайте отобразим количество объектов в коллекции Strokes
для большей наглядности. Я привяжу свойство Count к тексту на форме.
<TextBlock Text="{Binding ElementName=ink1, Path=Strokes.Count}" x:Name="textBlockPageTitle" Style="{StaticResource PhoneTextPageTitle1Style}"/>
Теперь можно запустить приложение и попробовать нарисовать что-то очень красивое. Например, яблоко :).

Добавить комментарий