Как 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.
Визуальный вывод будет таким: