WPF 实现 Gitee 气泡菜单(一)
气泡菜单(一)
作者:WPFDevelopersOrg
原文链接: https://github.com/WPFDevelopersOrg/WPFDevelopers
需要实现气泡菜单需要使用Canvas 画布进行添加内容; 但是为了使用方便需要在ListBox 中使用Canvas 做为ItemsPanelTemplate ; 使用时只需要在ListBox 中添加ListBoxItem 就行; 1) BubbleControl.cs 代码如下;
BubbleControl.cs 继承 Control 创建依赖属性Content ;
根据Content 集合循环随机生成Width 、Height 在生成SetLeft 、SetTop 需要判断重叠面积不能超过当前面积的百分之六十~九十;
using System;using System.Collections.Generic;using System.Collections.ObjectModel;using System.Diagnostics;using System.Linq;using System.Windows;using System.Windows.Controls;using System.Windows.Media;using System.Windows.Shapes;using WPFDevelopers.Helpers;namespace WPFDevelopers.Controls { [TemplatePart(Name = BorderTemplateName, Type = typeof(Border)) ] public class BubbleControl : ContentControl { private const string BorderTemplateName = "PART_Border" ; public new static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content" , typeof (ObservableCollection), typeof (BubbleControl), new PropertyMetadata(null )); public new static readonly DependencyProperty BorderBackgroundProperty = DependencyProperty.Register("BorderBackground" , typeof (Brush), typeof (BubbleControl), new PropertyMetadata(null )); private Border _border; private double _bubbleItemX, _bubbleItemY; private Ellipse _ellipse; private int _number;
private readonly List _rects = new List(); private RotateTransform _rotateTransform; private double _size; private const int _maxSize = 120 ; static BubbleControl ( ) { DefaultStyleKeyProperty.OverrideMetadata(typeof (BubbleControl), new FrameworkPropertyMetadata(typeof (BubbleControl))); } public BubbleControl ( ) { Loaded += delegate { double left = 0 d, top = 0 d; for (var y = 0 ; y int)Height / _maxSize; y++) { double yNum = y + 1 ; yNum = _maxSize * yNum; for (var x = 0 ; x int)Width / _maxSize; x++) { if (_number > Content.Count - 1 ) return ; var item = Content[_number]; if (double .IsNaN(item.Width) || double .IsNaN(item.Height)) SetBubbleItem(item); _bubbleItemX = Canvas.GetLeft(item); _bubbleItemY = Canvas.GetTop(item); if (double .IsNaN(_bubbleItemX) || double .IsNaN(_bubbleItemY)) { double xNum = x + 1 ; xNum = _maxSize * xNum; _bubbleItemX = ControlsHelper.NextDouble(left,xNum - _size * ControlsHelper.NextDouble(0.6 ,0.9 )); var _width = _bubbleItemX + _size; _width = _width > Width ? Width - (Width - _bubbleItemX) - _size : _bubbleItemX; _bubbleItemX = _width; _bubbleItemY = ControlsHelper.NextDouble(top,yNum - _size * ControlsHelper.NextDouble(0.6 , 0.9 )); var _height = _bubbleItemY + _size; _height = _height > Height ? Height - (Height - _bubbleItemY) - _size : _bubbleItemY; _bubbleItemY = _height; } Canvas.SetLeft(item, _bubbleItemX); Canvas.SetTop(item, _bubbleItemY); left = left + _size; if (item.Background is null ) item.Background = ControlsHelper.RandomBrush(); _rects.Add(new Rect(_bubbleItemX, _bubbleItemY, _size, _size)); _number++; } left = 0 d; top = top + _maxSize; } }; } public new ObservableCollection Content { get => (ObservableCollection)GetValue(ContentProperty); set => SetValue(ContentProperty, value ); } public Brush BorderBackground { get => (Brush)this .GetValue(BorderBackgroundProperty); set => this .SetValue(BorderBackgroundProperty, (object )value ); } private void SetBubbleItem (BubbleItem item ) { if (item.Text.Length >= 4 ) _size = ControlsHelper.GetRandom.Next(80 , _maxSize); else _size = ControlsHelper.GetRandom.Next(50 , _maxSize); item.Width = _size; item.Height = _size; } public override void OnApplyTemplate ( ) { base .OnApplyTemplate(); _border = GetTemplateChild(BorderTemplateName) as Border; } public override void BeginInit ( ) { Content = new ObservableCollection(); base .BeginInit(); } } } 2) ControlsHelper.cs 代码如下;
private static long _tick = DateTime.Now.Ticks; public static Random GetRandom = new Random((int )(_tick & 0xffffffff L) | (int )(_tick >> 32 )); public static double NextDouble (double miniDouble, double maxiDouble ) { if (GetRandom != null ) { return GetRandom.NextDouble() * (maxiDouble - miniDouble) + miniDouble; } else { return 0.0 d; } } public static Brush RandomBrush ( ) { var R = GetRandom.Next(255 ); var G = GetRandom.Next(255 ); var B = GetRandom.Next(255 ); var color = Color.FromRgb((byte )R, (byte )G, (byte )B); var solidColorBrush = new SolidColorBrush(color); return solidColorBrush; }3) BubbleItem.cs 代码如下;
using System.Windows;using System.Windows.Controls;using System.Windows.Input;namespace WPFDevelopers { public class BubbleItem : ListBoxItem { public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text" , typeof (string ), typeof (BubbleItem), new PropertyMetadata(string .Empty)); public static readonly DependencyProperty SelectionCommandProperty = DependencyProperty.Register("SelectionCommand" , typeof (ICommand), typeof (BubbleItem), new PropertyMetadata(null )); static BubbleItem ( ) { DefaultStyleKeyProperty.OverrideMetadata(typeof (BubbleItem), new FrameworkPropertyMetadata(typeof (BubbleItem))); } public string Text { get => (string )GetValue(TextProperty); set => SetValue(TextProperty, value ); } public ICommand SelectionCommand { get => (ICommand)GetValue(SelectionCommandProperty); set => SetValue(SelectionCommandProperty, value ); } } }4) BubbleControl.xaml 代码如下;
<ResourceDictionary xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls ="clr-namespace:WPFDevelopers.Controls" > <Style TargetType ="{x:Type controls:BubbleControl}" > <Setter Property ="Width" Value ="400" /> <Setter
Property ="Height" Value ="400" /> <Setter Property ="Template" > <Setter.Value > <ControlTemplate TargetType ="local:BubbleControl" > <Grid Width ="{TemplateBinding Width}" Height ="{TemplateBinding Height}" > <Border BorderBrush ="#E9E9E9" BorderThickness ="1" Background ="#FAFAFA" Margin ="45" CornerRadius ="400" x:Name ="PART_Border" > <Ellipse Fill ="#FFFFFF" Margin ="20" /> Border > <ListBox ItemsSource ="{TemplateBinding Content}" Background ="Transparent" BorderBrush ="Transparent" ScrollViewer.HorizontalScrollBarVisibility ="Disabled" ScrollViewer.VerticalScrollBarVisibility ="Disabled" > <ListBox.ItemContainerStyle > <Style TargetType ="{x:Type local:BubbleItem}" > <Setter Property ="Canvas.Left" Value ="{Binding (Canvas.Left)}" /> <Setter Property ="Canvas.Top" Value ="{Binding (Canvas.Top)}" /> <ResourceDictionary xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls ="clr-namespace:WPFDevelopers.Controls" > <ResourceDictionary.MergedDictionaries > <ResourceDictionary Source ="Basic/ControlBasic.xaml" /> <ResourceDictionary Source ="Basic/Animations.xaml" /> ResourceDictionary.MergedDictionaries > <Style TargetType ="{x:Type controls:BubbleControl}" BasedOn ="{StaticResource ControlBasicStyle}" > <Setter Property ="Width" Value ="400" /> <Setter Property ="Height" Value ="400" /> <Setter Property ="Background" Value ="{StaticResource WhiteSolidColorBrush}" /> <Setter Property ="BorderThickness" Value ="1" /> <Setter Property ="BorderBrush" Value ="{StaticResource SecondaryTextSolidColorBrush}" /> <Setter Property ="BorderBackground" Value ="{StaticResource BaseSolidColorBrush}" /> <Setter Property ="Template" > <Setter.Value >
<ControlTemplate TargetType ="controls:BubbleControl" > <Grid Width ="{TemplateBinding Width}" Height ="{TemplateBinding Height}" > <Border BorderBrush ="{TemplateBinding BorderBrush}" BorderThickness ="{TemplateBinding BorderThickness}" Background ="{TemplateBinding BorderBackground}" Margin ="45" CornerRadius ="400" x:Name ="PART_Border" > <Ellipse Fill ="{TemplateBinding Background}" Margin ="20" /> Border > <ListBox ItemsSource ="{TemplateBinding Content}" Background ="Transparent" BorderBrush ="Transparent" ScrollViewer.HorizontalScrollBarVisibility ="Disabled" ScrollViewer.VerticalScrollBarVisibility ="Disabled" Style ="{x:Null}" > <ListBox.ItemContainerStyle > <Style TargetType ="{x:Type controls:BubbleItem}" > <Setter Property ="Canvas.Left" Value ="{Binding (Canvas.Left)}" /> <Setter Property ="Canvas.Top" Value ="{Binding (Canvas.Top)}" /> <Setter Property ="Template" > <Setter.Value > <ControlTemplate TargetType ="controls:BubbleItem" > <Grid Width ="{TemplateBinding Width}" Height ="{TemplateBinding Height}" > <Ellipse Fill ="{TemplateBinding Background}" Opacity =".6" /> <TextBlock VerticalAlignment ="Center" HorizontalAlignment ="Center" Padding ="10,0" > <Hyperlink Foreground ="{StaticResource BlackSolidColorBrush}" Command ="{TemplateBinding SelectionCommand}" > <TextBlock Text ="{TemplateBinding Text}" TextAlignment ="Center" TextTrimming ="CharacterEllipsis" ToolTip ="{TemplateBinding Text}" /> Hyperlink > TextBlock > Grid > <ControlTemplate.Triggers > ControlTemplate.Triggers > ControlTemplate > Setter.Value >
Setter > Style > ListBox.ItemContainerStyle > <ListBox.ItemsPanel > <ItemsPanelTemplate > <Canvas IsItemsHost ="True" Width ="{TemplateBinding Width}" Height ="{TemplateBinding Height}" /> ItemsPanelTemplate > ListBox.ItemsPanel > ListBox > Grid > ControlTemplate > Setter.Value > Setter > Style >ResourceDictionary >5) BubbleControlExample.xaml 代码如下;
<local:BubbleControl x:Name ="MyBubbleControl" > <wpfdev:BubbleControl.Content > <wpfdev:BubbleItem Text ="WPF" SelectionCommand ="{Binding WPFCommand,RelativeSource={RelativeSource AncestorType=local:BubbleControlExample}}" /> <wpfdev:BubbleItem Text ="ASP.NET" /> <wpfdev:BubbleItem Text ="WinUI" /> <wpfdev:BubbleItem Text ="WebAPI" /> <wpfdev:BubbleItem Text ="Blazor" /> <wpfdev:BubbleItem Text ="MAUI" SelectionCommand ="{Binding MAUICommand,RelativeSource={RelativeSource AncestorType=local:BubbleControlExample}}" /> <wpfdev:BubbleItem Text ="Xamarin" /> <wpfdev:BubbleItem Text ="WinForm" /> <wpfdev:BubbleItem Text ="UWP" /> wpfdev:BubbleControl.Content > local:BubbleControl >6) BubbleControlExample.xaml.cs 代码如下;
using System.Windows.Controls;using System.Windows.Input;using WPFDevelopers.Samples.Helpers;namespace WPFDevelopers.Samples.ExampleViews { /// /// BubbleControlExample.xaml 的交互逻辑 /// public partial class BubbleControlExample : UserControl { public BubbleControlExample ( ) { InitializeComponent(); } public ICommand WPFCommand => new RelayCommand(delegate { WPFDevelopers.Minimal.Controls.MessageBox.Show("点击了“WPF开发者”." , "提示" ); }); public ICommand MAUICommand => new RelayCommand(delegate {
WPFDevelopers.Minimal.Controls.MessageBox.Show("点击了“MAUI开发者”." , "提示" ); }); } }