На сайте MSDN есть статья How to add a menu to an app bar, в которой рассказывается как сделать дополнительное меню в AppBar приложения для магазина Windows. Я решил немного улучшить данный вариант и сделал элемент управления, который автоматически показывал бы всплывающее меню с нужным мне контентом.
Собственно что из этого получилось я покажу на скриншоте и уже потом немного опишу весь процесс.
Для начала я наследовал новый класс от класса Button. Учитывая то, что контент будет отображаться либо снизу (в случае если это Page.TopAppBar) либо сверху (если Page.BottomAppBar), я добавил насколько вспомогательных свойств зависимостей:
Так же добавил элемент Popup, который будет содержать сам контент и свойство IsOpen, при изменении которого контент будет открываться или закрываться.
Теперь остается только правильно рассчитать положения всплывающего меню:
Остальные классы и методы расширений (такие как WaitForNonZeroSizeAsync и GetFirstAncestorOfType) вы можете найти в исходном коде проекта.
Использовать даннный элемент управления достаточно просто:
Исходный код
Code:
#region DependencyProperty Members /// <summary> /// Identifies the HorizontalOffset dependency property. /// </summary> public static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.Register("HorizontalOffset", typeof(double), typeof(PopupAppBarButton), new PropertyMetadata(0.0)); /// <summary> /// Get or sets the horizontal distance between the target origin and the popup alignment point. /// </summary> public double HorizontalOffset { get { return (double)GetValue(HorizontalOffsetProperty); } set { SetValue(HorizontalOffsetProperty, value); } } /// <summary> /// Identifies the VerticalOffset dependency property. /// </summary> public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.Register("VerticalOffset", typeof(double), typeof(PopupAppBarButton), new PropertyMetadata(0.0)); /// <summary> /// Gets or sets the vertical distance between the target origin and the popup alignment point. /// </summary> public double VerticalOffset { get { return (double)GetValue(VerticalOffsetProperty); } set { SetValue(VerticalOffsetProperty, value); } } /// <summary> /// Identifies the Placement dependency property. /// </summary> public static readonly DependencyProperty PlacementProperty = DependencyProperty.Register("Placement", typeof(Placement), typeof(PopupAppBarButton), new PropertyMetadata(Placement.Default)); /// <summary> /// Gets or sets the orientation of the Popup control when the control opens, and specifies the behavior of the Popup control when it overlaps screen boundaries. /// </summary> public Placement Placement { get { return (Placement)GetValue(PlacementProperty); } set { SetValue(PlacementProperty, value); } } /// <summary> /// Identifies the Popup dependency property. /// </summary> public static readonly DependencyProperty PopupProperty = DependencyProperty.Register("Popup", typeof(FrameworkElement), typeof(PopupAppBarButton), new PropertyMetadata(null, PopupPropertyChangedCallback)); /// <summary> /// Gets or sets a pop-up window that has content. /// </summary> public FrameworkElement Popup { get { return (FrameworkElement)GetValue(PopupProperty); } set { SetValue(PopupProperty, value); } } /// <summary> /// /// </summary> /// <param name="dependencyObject"></param> /// <param name="dependencyPropertyChangedEventArgs"></param> private static void PopupPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) { var target = (PopupAppBarButton)dependencyObject; if (target == null) return; target.CreatePopup(); } /// <summary> /// Identifies the <see cref="IsOpen"/> dependency property. /// </summary> public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register("IsOpen", typeof(Boolean), typeof(PopupAppBarButton), new PropertyMetadata(false, IsOpenPropertyChangedCallback)); /// <summary> /// An implementation of <see cref="Boolean"/> designed to be used as is open. /// </summary> public Boolean IsOpen { get { return (Boolean)GetValue(IsOpenProperty); } set { SetValue(IsOpenProperty, value); } } /// <summary> /// Represents the callback that is invoked when the effective property value of a dependency property changes. /// </summary> /// <param name="dependencyObject">The DependencyObject on which the property has changed value.</param> /// <param name="dependencyPropertyChangedEventArgs">Event data that is issued by any event that tracks changes to the effective value of this property.</param> private static void IsOpenPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) { var target = (PopupAppBarButton)dependencyObject; if (target.IsOpen) target.Show(); else target.Hide(); } #endregion
Так же добавил элемент Popup, который будет содержать сам контент и свойство IsOpen, при изменении которого контент будет открываться или закрываться.
Теперь остается только правильно рассчитать положения всплывающего меню:
Code:
#region Private Members /// <summary> /// /// </summary> private void CreatePopup() { popup = new Popup { IsLightDismissEnabled = true, ChildTransitions = new TransitionCollection { new PopupThemeTransition() }, Child = Popup }; popup.Closed += delegate { if (IsOpen) IsOpen = false; var appBar = this.GetFirstAncestorOfType<AppBar>(); if (appBar != null) appBar.IsOpen = false; }; } /// <summary> /// Show the popup on the screen. /// </summary> private void Show() { if (popup == null) return; popup.DataContext = DataContext; Popup.WaitForNonZeroSizeAsync().ContinueWith(task => { var point = CalculateOffset(); popup.HorizontalOffset = point.X; popup.VerticalOffset = point.Y; }, TaskContinuationOptions.ExecuteSynchronously); popup.IsOpen = true; } /// <summary> /// Hide the popup on the screen. /// </summary> private void Hide() { if (popup == null) return; if (popup.IsOpen) popup.IsOpen = false; } /// <summary> /// /// </summary> /// <returns></returns> private Point CalculateOffset() { // Get a practical transform var transform = TransformToVisual(Window.Current.Content); var offset = transform.TransformPoint(default(Point)); // Calculate the offset in the X var offsetX = offset.X - (Popup.ActualWidth - ActualWidth) / 2; if (offsetX < HorizontalOffset) offsetX = HorizontalOffset; if (offsetX + Popup.ActualWidth > Window.Current.Bounds.Right - HorizontalOffset) offsetX = Window.Current.Bounds.Right - Popup.ActualWidth - HorizontalOffset; // Calculate the offset in the Y double offsetY = offset.Y; switch (Placement) { case Placement.Above: offsetY = offset.Y - Popup.ActualHeight - VerticalOffset; break; case Placement.Below: offsetY = offset.Y + ActualHeight + VerticalOffset; break; case Placement.Left: break; case Placement.Right: break; case Placement.Default: break; } // Return value return new Point(offsetX, offsetY); } #endregion
Остальные классы и методы расширений (такие как WaitForNonZeroSizeAsync и GetFirstAncestorOfType) вы можете найти в исходном коде проекта.
Использовать даннный элемент управления достаточно просто:
Code:
<Page.TopAppBar> <AppBar x:Name="topAppBar" Padding="10,0,10,0"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50*"/> <ColumnDefinition Width="50*"/> <ColumnDefinition Width="50*"/> </Grid.ColumnDefinitions> <StackPanel x:Name="TopLeftPanel" Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Left"> <local:PopupAppBarButton Placement="Below" VerticalOffset="4" HorizontalOffset="4" IsTabStop="False" Style="{StaticResource HomeAppBarButtonStyle}"> <local:PopupAppBarButton.Popup> <Border Background="{StaticResource AppBarBackgroundThemeBrush}" Width="250" Height="250"> <StackPanel VerticalAlignment="Center"> <TextBlock Text="Placement = Below" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="18"/> <TextBlock Text="VerticalOffset = 4" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="18"/> <TextBlock Text="HorizontalOffset = 4" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="18"/> </StackPanel> </Border> </local:PopupAppBarButton.Popup> </local:PopupAppBarButton> </StackPanel> </Grid> </AppBar> </Page.TopAppBar>
Исходный код
Комментариев нет:
Отправить комментарий