Как Rowi, отличный клиент для Twitter для Windows Phone, выпустил очередное обновление, он представил очень интересный тип меню — «show-on-tap». Он отображается всякий раз, когда пользователь нажимает на один твит в любом месте приложения. Это в основном список опций, которые привязаны к ItemTemplate в ListBox , где элементы агрегированного. Вы можете увидеть, как это выглядит на скриншотах ниже:
Я думал, что для одного из приложений, над которыми я работаю, такая концепция будет работать намного лучше, чем меню всплывающих подсказок, которое разработчики привыкли использовать прямо сейчас. Основным недостатком, связанным с использованием традиционной всплывающей подсказки, является тот факт, что пользователь не знает, доступна ли эта опция или нет — требуется время реакции для отображения элемента управления.
С другой стороны, меню «показ на касании» обеспечивает мгновенную обратную связь — как только пользователь нажимает на элемент, связанные действия мгновенно отображаются. Вот как я решил реализовать свой собственный элемент управления, который будет делать именно это — TapMenu . Думая о его структуре, легко выделить несколько основных компонентов, которые должны быть его частью:
- ItemsPresenter с горизонтальным ScrollViewer — таким образом, элементы располагаются последовательно в горизонтальном пространстве.
- TapMenuButton экземпляры внутри ListBox — это отдельные квадратные кнопки с настраиваемой DataTemplate для размещения в изображение и TextBlock для подписи.
- Полупрозрачная сетка — используется для хранения ListBox и все еще обеспечивает визуальное представление содержимого позади него.
Я создал новый проект библиотеки классов для Windows Phone и назвал его ControlKit. В конечном итоге это станет моей собственной коллекцией элементов управления, которые я создал, так что, возможно, и название будет простым, но описательным. Я добавил класс с именем TapMenu, и вот что в нем:
using System.Windows.Controls;
namespace ControlKit
{
public class TapMenu : ItemsControl
{
public TapMenu()
{
DefaultStyleKey = typeof(TapMenu);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
}
}
Ничего особенного — он унаследован от ItemsControl, поэтому я могу легко отобразить множество элементов внутри него. Большая часть декларации управления происходит в файле темы, и я буду говорить об этом позже. TapMenu будет содержать несколько экземпляров TapMenuButton — те, которые должны содержать изображение и подпись. Вот как выглядит класс TapMenuButton:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ControlKit
{
public class TapMenuButton : Button
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(TapMenuButton), new PropertyMetadata(string.Empty));
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(TapMenuButton), null);
public TapMenuButton()
{
DefaultStyleKey = typeof(TapMenuButton);
}
public ImageSource ImageSource
{
get { return GetValue(ImageSourceProperty) as ImageSource; }
set { SetValue(ImageSourceProperty, value); }
}
public string Text
{
get { return GetValue(TextProperty) as string; }
set { SetValue(TextProperty, value); }
}
}
}
Теперь вам нужно установить визуальный стиль элемента управления. Это делается в файле Generic.xaml — создайте новую папку в проекте и назовите ее Themes . Затем добавьте новый файл и назовите его Generic.xaml .
ПРИМЕЧАНИЕ. Именование важно: текущее соглашение является требованием Silverlight и должно соблюдаться, чтобы элементы управления отображались правильно.
Вот что у меня там:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
xmlns:local="clr-namespace:ControlKit">
<Style TargetType="local:TapMenu">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Opacity=".8" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TapMenu">
<ScrollViewer VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Hidden" Height="150">
<ItemsPresenter></ItemsPresenter>
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:TapMenuButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TapMenuButton">
<Button Margin="0,0,-15,0" Height="120" Width="120" Background="{StaticResource PhoneAccentBrush}" BorderThickness="0" Padding="0">
<StackPanel>
<Image Margin="0,12,0,12" HorizontalAlignment="Center" Height="36" Width="36" Stretch="UniformToFill" Source="{TemplateBinding ImageSource}"></Image>
<TextBlock Style="{StaticResource PhoneTextSmallStyle}" HorizontalAlignment="Center" Text="{TemplateBinding Text}"></TextBlock>
</StackPanel>
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Есть несколько вещей, которые стоит упомянуть:
- Обратите внимание, что для StackPanel , представляющей ItemsPanelTemplate , для свойства Orientation установлено значение Horizontal . Это гарантирует, что элементы, которые будут использоваться в элементе управления, будут размещены в горизонтальной плоскости.
- Для основного шаблона TapMenu обратите внимание, что в ScrollViewer отключена вертикальная прокрутка, а горизонтальная полоса прокрутки скрыта.
- Для TapMenuButton и Image, и TextBlock используют TemplateBinding . Не делайте ошибку и используйте Binding — свойство, которое вам нужно прочитать, является частью основного шаблона.
- TapMenuButton — это безграничная реализация простого элемента управления Button.
- Цветная кисть фона для TapMenuButton определяется текущим цветом акцента, установленным пользователем.
Управление теперь готово. Чтобы протестировать его, создайте новый проект приложения Windows Phone Silverlight в контексте того же решения. Добавьте ссылку на ControlKit . В MainPage.xaml добавьте ссылку на сборку ControlKit, добавив дополнительное пространство имен:
xmlns:ckit="clr-namespace:ControlKit;assembly=ControlKit"
Теперь вы можете сделать что-то вроде этого:
<ckit:TapMenu>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
</ckit:TapMenu>
Результат будет выглядеть примерно так:
Отлично, но Rowi показывает это меню всякий раз, когда пользователь нажимает на один из элементов в ListBox. Как вы можете видеть на изображении выше, я удобно реализовал список образцов с совершенно не относящимися к делу данными. Текущий ListBox XAML объявлен так:
<ListBox ItemsSource="{Binding Path=Instance.RandomTextItems,Source={StaticResource Binder}}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Tap="Grid_Tap">
<StackPanel Margin="0,0,0,30">
<TextBlock Style="{StaticResource PhoneTextGroupHeaderStyle}" Text="{Binding TextOne}"></TextBlock>
<TextBlock Style="{StaticResource PhoneTextSmallStyle}" Text="{Binding TextTwo}"></TextBlock>
<TextBlock Style="{StaticResource PhoneTextAccentStyle}" Text="{Binding TextThree}"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Пример кода для TapMenu сверху можно легко перенести сюда в рабочей сетке DataTemplate. В отличие от StackPanel, элементы управления в Grid расположены друг над другом, если явно не указано иное. Это идеальный сценарий для того, что я собираюсь сделать:
<ListBox ItemsSource="{Binding Path=Instance.RandomTextItems,Source={StaticResource Binder}}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Tap="Grid_Tap">
<StackPanel Margin="0,0,0,30">
<TextBlock Style="{StaticResource PhoneTextGroupHeaderStyle}" Text="{Binding TextOne}"></TextBlock>
<TextBlock Style="{StaticResource PhoneTextSmallStyle}" Text="{Binding TextTwo}"></TextBlock>
<TextBlock Style="{StaticResource PhoneTextAccentStyle}" Text="{Binding TextThree}"></TextBlock>
</StackPanel>
<ckit:TapMenu x:Name="tapMenu" Visibility="Collapsed">
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
<ckit:TapMenuButton ImageSource="ApplicationIcon.png" Text="Test"></ckit:TapMenuButton>
</ckit:TapMenu>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
TapMenu теперь имеет имя и по умолчанию невидим. Сетка контейнера имеет обработчик событий, который обрабатывает событие Tap — Grid_Tap :
private void Grid_Tap(object sender, GestureEventArgs e)
{
Grid mainGrid = sender as Grid;
TapMenu menu = mainGrid.FindName("tapMenu") as TapMenu;
if (menu.Visibility == System.Windows.Visibility.Collapsed)
menu.Visibility = System.Windows.Visibility.Visible;
else
menu.Visibility = System.Windows.Visibility.Collapsed;
}
Если пользователь нажимает на сетку контейнера, а TapMenu не отображается, он отображается. В противном случае оно рухнет.
Вы можете добавить дополнительные обработчики событий для отдельных экземпляров TapMenuButton , например Click.
Визуальный вывод будет таким: