?WPF 實現 Gitee 氣泡菜單(一)
氣泡菜單(一)
作者:WPFDevelopersOrg
原文鏈接: ? ?https://github.com/WPFDevelopersOrg/WPFDevelopers
框架使用大于等于
.NET40
;Visual Studio 2022
;項目使用 MIT 開源許可協議;

需要實現氣泡菜單需要使用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<BubbleItem>),?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<Rect>?_rects?=?new?List<Rect>();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?=?0d,?top?=?0d;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?=?0d;top?=?top?+?_maxSize;}};}public?new?ObservableCollection<BubbleItem>?Content{get?=>?(ObservableCollection<BubbleItem>)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<BubbleItem>();base.BeginInit();}}
}
2) ControlsHelper.cs 代碼如下;
隨機Double值;
隨機顏色;
private?static?long?_tick?=?DateTime.Now.Ticks;public?static?Random?GetRandom?=?new?Random((int)(_tick?&?0xffffffffL)?|?(int)(_tick?>>?32));public?static?double?NextDouble(double?miniDouble,?double?maxiDouble){if?(GetRandom?!=?null){return?GetRandom.NextDouble()?*?(maxiDouble?-?miniDouble)?+?miniDouble;}else{return?0.0d;}}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
{///?<summary>///?BubbleControlExample.xaml?的交互邏輯///?</summary>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開發者”.",?"提示");});}
}
