WPF实现 Gitee泡泡菜单
框架使用大于等于.NET40
;
Visual Studio 2022
;
项目使用 MIT 开源许可协议;

- 需要实现泡泡菜单需要使用Canvas画布进行添加内容;
- 保证颜色随机,位置不重叠;
- 点击泡泡获得当前泡泡的值;
实现代码
1) BubblleCanvas.cs 代码如下;
using?System.Windows;
using?System.Windows.Controls;
using?WPFDevelopers.Helpers;
using?WPFDevelopers.Utilities;
namespace?WPFDevelopers.Controls
{
????public?class?BubblleCanvas?:?Canvas
????{
????????private?double?_bubbleItemX;
????????private?double?_bubbleItemY;
????????private?int?_number;
????????private?double?_size;
????????private?const?int?_maxSize?=?120;
????????protected?override?Size?ArrangeOverride(Size?arrangeSize)
????????{
????????????var?width?=?arrangeSize.Width;
????????????var?height?=?arrangeSize.Height;
????????????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?>?InternalChildren.Count?-?1)
????????????????????????return?arrangeSize;
????????????????????var?item?=?InternalChildren[_number]?as?FrameworkElement;
????????????????????if?(DoubleUtil.IsNaN(item.ActualWidth)?||?DoubleUtil.IsZero(item.ActualWidth)?||?DoubleUtil.IsNaN(item.ActualHeight)?||?DoubleUtil.IsZero(item.ActualHeight))
????????????????????????ResizeItem(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;
????????????????????_number++;
????????????????????item.Arrange(new?Rect(new?Point(_bubbleItemX,?_bubbleItemY),?new?Size(_size,?_size)));
????????????????}
????????????????left?=?0d;
????????????????top?=?top?+?_maxSize;
????????????}
????????????return?arrangeSize;
????????}
????????private?void?ResizeItem(FrameworkElement?item)
????????{
????????????if?(DoubleUtil.GreaterThanOrClose(item.DesiredSize.Width,?55))
????????????????_size?=?ControlsHelper.GetRandom.Next(80,?_maxSize);
????????????else
????????????????_size?=?ControlsHelper.GetRandom.Next(55,?_maxSize);
????????????item.Width?=?_size;
????????????item.Height?=?_size;
????????}
????}
}
2) ControlsHelper.cs 代码如下;
?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) BubbleControl.cs 代码如下;
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.Input;
using?System.Windows.Media;
using?System.Windows.Shapes;
using?WPFDevelopers.Helpers;
namespace?WPFDevelopers.Controls
{
????[TemplatePart(Name?=?BorderTemplateName,?Type?=?typeof(Border))]
????[TemplatePart(Name?=?EllipseTemplateName,?Type?=?typeof(Ellipse))]
????[TemplatePart(Name?=?RotateTransformTemplateName,?Type?=?typeof(RotateTransform))]
????public?class?BubblleControl?:?Control
????{
????????private?const?string?BorderTemplateName?=?"PART_Border";
????????private?const?string?EllipseTemplateName?=?"PART_Ellipse";
????????private?const?string?RotateTransformTemplateName?=?"PART_EllipseRotateTransform";
????????private?const?string?ListBoxTemplateName?=?"PART_ListBox";
????????private?static?readonly?Type?_typeofSelf?=?typeof(BubblleControl);
????????private?ObservableCollection<BubblleItem>?_items?=?new?ObservableCollection<BubblleItem>();
????????private?Border?_border;
????????private?Ellipse?_ellipse;
????????private?RotateTransform?_rotateTransform;
????????private?Brush[]?brushs;
????????private?ItemsControl?_listBox;
????????private?static?RoutedCommand?_clieckCommand;
????????class?BubblleItem
????????{
????????????public?string?Text?{?get;?set;?}
????????????public?Brush?Bg?{?get;?set;?}
????????}
????????static?BubblleControl()
????????{
????????????InitializeCommands();
????????????DefaultStyleKeyProperty.OverrideMetadata(_typeofSelf,?new?FrameworkPropertyMetadata(_typeofSelf));
????????}
????????#region?Event
????????public?static?readonly?RoutedEvent?ClickEvent?=?EventManager.RegisterRoutedEvent("Click",?RoutingStrategy.Bubble,?typeof(RoutedEventHandler),?_typeofSelf);
????????public?event?RoutedEventHandler?Click
????????{
????????????add?{?AddHandler(ClickEvent,?value);?}
????????????remove?{?RemoveHandler(ClickEvent,?value);?}
????????}
????????#endregion
????????#region?Command
????????private?static?RoutedCommand?_clickCommand?=?null;
????????private?static?void?InitializeCommands()
????????{
????????????_clickCommand?=?new?RoutedCommand("Click",?_typeofSelf);
????????????CommandManager.RegisterClassCommandBinding(_typeofSelf,?new?CommandBinding(_clickCommand,?OnClickCommand,?OnCanClickCommand));
????????}
????????public?static?RoutedCommand?ClickCommand
????????{
????????????get?{?return?_clickCommand;?}
????????}
????????private?static?void?OnClickCommand(object?sender,?ExecutedRoutedEventArgs?e)
????????{
????????????var?ctrl?=?sender?as?BubblleControl;
????????????ctrl.SetValue(SelectedTextPropertyKey,?e.Parameter?.ToString());
????????????ctrl.RaiseEvent(new?RoutedEventArgs(ClickEvent));
????????}
????????private?static?void?OnCanClickCommand(object?sender,?CanExecuteRoutedEventArgs?e)
????????{
????????????e.CanExecute?=?true;
????????}
????????#endregion
????????#region?readonly?Properties
????????private?static?readonly?DependencyPropertyKey?SelectedTextPropertyKey?=
???????????DependencyProperty.RegisterReadOnly("SelectedText",?typeof(string),?_typeofSelf,?new?PropertyMetadata(null));
????????public?static?readonly?DependencyProperty?SelectedTextProperty?=?SelectedTextPropertyKey.DependencyProperty;
????????public?string?SelectedText
????????{
????????????get?{?return?(string)GetValue(SelectedTextProperty);?}
????????}
????????public?new?static?readonly?DependencyProperty?BorderBackgroundProperty?=
????????????DependencyProperty.Register("BorderBackground",?typeof(Brush),?typeof(BubblleControl),
????????????????new?PropertyMetadata(null));
????????public?new?static?readonly?DependencyProperty?EarthBackgroundProperty?=
????????????DependencyProperty.Register("EarthBackground",?typeof(Brush),?typeof(BubblleControl),
????????????????new?PropertyMetadata(Brushes.DarkOrchid));
????????public?Brush?BorderBackground
????????{
????????????get?=>?(Brush)this.GetValue(BorderBackgroundProperty);
????????????set?=>?this.SetValue(BorderBackgroundProperty,?(object)value);
????????}
????????public?Brush?EarthBackground
????????{
????????????get?=>?(Brush)this.GetValue(EarthBackgroundProperty);
????????????set?=>?this.SetValue(EarthBackgroundProperty,?(object)value);
????????}
????????#endregion
????????#region?Property
????????public?static?readonly?DependencyProperty?ItemsSourceProperty?=
????????????DependencyProperty.Register("ItemsSource",?typeof(IEnumerable<string>),?typeof(BubblleControl),?new?PropertyMetadata(null,?OnItemsSourcePropertyChanged));
????????public?IEnumerable<string>?ItemsSource
????????{
????????????get?{?return?(IEnumerable<string>)GetValue(ItemsSourceProperty);?}
????????????set?{?SetValue(ItemsSourceProperty,?value);?}
????????}
????????private?static?void?OnItemsSourcePropertyChanged(DependencyObject?obj,?DependencyPropertyChangedEventArgs?e)
????????{
????????????var?ctrl?=?obj?as?BubblleControl;
????????????var?newValue?=?e.NewValue?as?IEnumerable<string>;
????????????if?(newValue?==?null)
????????????{
????????????????ctrl._items.Clear();
????????????????return;
????????????}
????????????foreach?(var?item?in?newValue)
????????????{
????????????????ctrl._items.Add(new?BubblleItem?{?Text?=?item,?Bg?=?ControlsHelper.RandomBrush()?});
????????????}
????????}
????????#endregion
????????#region?Override
????????public?override?void?OnApplyTemplate()
????????{
????????????base.OnApplyTemplate();
????????????_border?=?GetTemplateChild(BorderTemplateName)?as?Border;
????????????_ellipse?=?GetTemplateChild(EllipseTemplateName)?as?Ellipse;
????????????_rotateTransform?=?GetTemplateChild(RotateTransformTemplateName)?as?RotateTransform;
????????????Loaded?+=?delegate
????????????{
????????????????var?point?=?_border.TranslatePoint(new?Point(_border.ActualWidth?/?2,?_border.ActualHeight?/?2),
????????????????????_ellipse);
????????????????_rotateTransform.CenterX?=?point.X?-?_ellipse.ActualWidth?/?2;
????????????????_rotateTransform.CenterY?=?point.Y?-?_ellipse.ActualHeight?/?2;
????????????};
????????????_listBox?=?GetTemplateChild(ListBoxTemplateName)?as?ItemsControl;
????????????_listBox.ItemsSource?=?_items;
????????}
????????#endregion
????}
}
4) BubblleControl.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">
????<ResourceDictionary.MergedDictionaries>
????????<ResourceDictionary?Source="Basic/ControlBasic.xaml"/>
????????<ResourceDictionary?Source="Basic/Animations.xaml"/>
????</ResourceDictionary.MergedDictionaries>
????<Style?TargetType="controls:BubblleControl"?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:BubblleControl">
????????????????????<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>
????????????????????????<Ellipse?Fill="{TemplateBinding?EarthBackground}"
?????????????????????????????????????????????????Width="26"?Height="26"
?????????????????????????????????????????????????RenderTransformOrigin=".5,.5"
?????????????????????????????????????????????????x:Name="PART_Ellipse"
?????????????????????????????????????????????????VerticalAlignment="Top"?Margin="0,35,0,0">
????????????????????????????<Ellipse.RenderTransform>
????????????????????????????????<RotateTransform?x:Name="PART_EllipseRotateTransform"></RotateTransform>
????????????????????????????</Ellipse.RenderTransform>
????????????????????????????<Ellipse.Triggers>
????????????????????????????????<EventTrigger?RoutedEvent="Loaded">
????????????????????????????????????<BeginStoryboard>
????????????????????????????????????????<Storyboard>
????????????????????????????????????????????<DoubleAnimation?Storyboard.TargetProperty="(Ellipse.RenderTransform).(RotateTransform.Angle)"
?????????????????????????????????????????????????????????????????????????????RepeatBehavior="Forever"
?????????????????????????????????????????????????????????????????????????????From="0"?To="360"
?????????????????????????????????????????????????????????????????????????????Duration="00:00:13"></DoubleAnimation>
????????????????????????????????????????</Storyboard>
????????????????????????????????????</BeginStoryboard>
????????????????????????????????</EventTrigger>
????????????????????????????</Ellipse.Triggers>
????????????????????????</Ellipse>
????????????????????????<ItemsControl?x:Name="PART_ListBox"
??????????????????????????????????????ItemsSource="{TemplateBinding?ItemsSource}">
????????????????????????????<ItemsControl.ItemTemplate>
????????????????????????????????<DataTemplate>
????????????????????????????????????<Grid>
????????????????????????????????????????<Grid?Width="{TemplateBinding?Width}"?
??????????????????????????????????????????????Height="{TemplateBinding?Height}">
????????????????????????????????????????????<Ellipse?Fill="{Binding?Bg}"
?????????????????????????????????????????????????????????????????Opacity=".4"/>
????????????????????????????????????????????<Ellipse?Stroke="{Binding?Bg}"?
?????????????????????????????????????????????????????????????????StrokeThickness=".8"/>
????????????????????????????????????????</Grid>
????????????????????????????????????????<TextBlock?VerticalAlignment="Center"?
???????????????????????????????????????????????????????????????HorizontalAlignment="Center"
???????????????????????????????????????????????????????????????Padding="10,0">
????????????????????????????????????????????????????????<Hyperlink?
????????????????????????????????????????????????????????????Foreground="{Binding?Bg}"
????????????????????????????????????????????????????????????Command="{x:Static?controls:BubblleControl.ClickCommand}"
????????????????????????????????????????????????????????????CommandParameter="{Binding?Text}"
????????????????????????????????????????????????????????????FontWeight="Normal">
????????????????????????????????????????????????????????????<TextBlock?Text="{Binding?Text}"
???????????????????????????????????????????????????????????????????????TextAlignment="Center"
???????????????????????????????????????????????????????????????????????TextTrimming="CharacterEllipsis"
???????????????????????????????????????????????????????????????????????ToolTip="{Binding?Text}"/>
????????????????????????????????????????????????????????</Hyperlink>
????????????????????????????????????????????????????</TextBlock>
????????????????????????????????????</Grid>
????????????????????????????????</DataTemplate>
????????????????????????????</ItemsControl.ItemTemplate>
????????????????????????????<ItemsControl.ItemsPanel>
????????????????????????????????<ItemsPanelTemplate>
????????????????????????????????????<controls:BubblleCanvas/>
????????????????????????????????</ItemsPanelTemplate>
????????????????????????????</ItemsControl.ItemsPanel>
????????????????????????</ItemsControl>
????????????????????</Grid>
????????????????</ControlTemplate>
????????????</Setter.Value>
????????</Setter>
????</Style>
</ResourceDictionary>
5) BubblleControlExample.xaml 代码如下;
- TabItem随机 是自动设置位置和颜色;
- TabItem自定义 可以自行定义展示的内容;
<UserControl?x:Class="WPFDevelopers.Samples.ExampleViews.BubblleControlExample"
?????????????xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
?????????????xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
?????????????xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"?
?????????????xmlns:d="http://schemas.microsoft.com/expression/blend/2008"?
?????????????xmlns:wpfdev="https://github.com/WPFDevelopersOrg/WPFDevelopers"
?????????????xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews"
?????????????xmlns:sys="clr-namespace:System;assembly=mscorlib"
?????????????mc:Ignorable="d"?
?????????????d:DesignHeight="450"?d:DesignWidth="800">
????
????<Grid>
????????<TabControl>
????????????<TabItem?Header="随机">
????????????????<wpfdev:BubblleControl?x:Name="MyBubblleControl"??Click="BubblleControl_Click">
????????????????????<wpfdev:BubblleControl.ItemsSource>
????????????????????????<x:Array?Type="sys:String">
????????????????????????????<sys:String>WPF</sys:String>
????????????????????????????<sys:String>ASP.NET</sys:String>
????????????????????????????<sys:String>WinUI</sys:String>
????????????????????????????<sys:String>WebAPI</sys:String>
????????????????????????????<sys:String>Blazor</sys:String>
????????????????????????????<sys:String>MAUI</sys:String>
????????????????????????????<sys:String>Xamarin</sys:String>
????????????????????????????<sys:String>WinForm</sys:String>
????????????????????????????<sys:String>UWP</sys:String>
????????????????????????</x:Array>
????????????????????</wpfdev:BubblleControl.ItemsSource>
????????????????</wpfdev:BubblleControl>
????????????</TabItem>
????????????<TabItem?Header="自定义">
????????????????<wpfdev:BubblleCanvas?Width="400"?Height="400">
????????????????????<Grid>
????????????????????????<Grid?Width="60"?
??????????????????????????????Height="60">
????????????????????????????<Ellipse?Fill="MediumSpringGreen"
?????????????????????????????????????Opacity=".4"/>
????????????????????????????<Ellipse?Stroke="MediumSpringGreen"?
?????????????????????????????????????StrokeThickness=".8"/>
????????????????????????</Grid>
????????????????????????<TextBlock?VerticalAlignment="Center"?
???????????????????????????????????HorizontalAlignment="Center"
???????????????????????????????????Padding="10,0">
????????????????????????????<Hyperlink?
????????????????????????????????Foreground="MediumSpringGreen"
????????????????????????????????FontWeight="Normal"
????????????????????????????????Command="{Binding?ClickCommand,RelativeSource={RelativeSource?AncestorType=local:BubblleControlExample}}">
????????????????????????????????<TextBlock?Text="WPF"
???????????????????????????????????????????TextAlignment="Center"
???????????????????????????????????????????TextTrimming="CharacterEllipsis"/>
????????????????????????????</Hyperlink>
????????????????????????</TextBlock>
????????????????????</Grid>
????????????????????<Grid>
????????????????????????<Grid?Width="60"?
??????????????????????????????Height="60">
????????????????????????????<Ellipse?Fill="Brown"
?????????????????????????????????????Opacity=".4"/>
????????????????????????????<Ellipse?Stroke="Brown"?
?????????????????????????????????????StrokeThickness=".8"/>
????????????????????????</Grid>
????????????????????????<TextBlock?VerticalAlignment="Center"?
???????????????????????????????????HorizontalAlignment="Center"
???????????????????????????????????Padding="10,0">
????????????????????????????<Hyperlink?
????????????????????????????????Foreground="Brown"
????????????????????????????????FontWeight="Normal"
????????????????????????????????Command="{Binding?ClickCommand,RelativeSource={RelativeSource?AncestorType=local:BubblleControlExample}}">
????????????????????????????????<TextBlock?Text="MAUI"
???????????????????????????????????????????TextAlignment="Center"
???????????????????????????????????????????TextTrimming="CharacterEllipsis"/>
????????????????????????????</Hyperlink>
????????????????????????</TextBlock>
????????????????????</Grid>
????????????????????<Grid>
????????????????????????<Grid?Width="60"?
??????????????????????????????Height="60">
????????????????????????????<Ellipse?Fill="DeepSkyBlue"
?????????????????????????????????????Opacity=".4"/>
????????????????????????????<Ellipse?Stroke="DeepSkyBlue"?
?????????????????????????????????????StrokeThickness=".8"/>
????????????????????????</Grid>
????????????????????????<TextBlock?VerticalAlignment="Center"?
???????????????????????????????????HorizontalAlignment="Center"
???????????????????????????????????Padding="10,0">
????????????????????????????<Hyperlink?
????????????????????????????????Foreground="DeepSkyBlue"
????????????????????????????????FontWeight="Normal"
????????????????????????????????Command="{Binding?ClickCommand,RelativeSource={RelativeSource?AncestorType=local:BubblleControlExample}}">
????????????????????????????????<TextBlock?Text="Blazor"
???????????????????????????????????????????TextAlignment="Center"
???????????????????????????????????????????TextTrimming="CharacterEllipsis"/>
????????????????????????????</Hyperlink>
????????????????????????</TextBlock>
????????????????????</Grid>
????????????????</wpfdev:BubblleCanvas>
????????????</TabItem>
????????</TabControl>
????????
????</Grid>
</UserControl>
6) BubblleControlExample.xaml.cs 代码如下;
using?System.Windows;
using?System.Windows.Controls;
using?System.Windows.Input;
using?WPFDevelopers.Samples.Helpers;
namespace?WPFDevelopers.Samples.ExampleViews
{
????///?<summary>
????///?BubbleControlExample.xaml?的交互逻辑
????///?</summary>
????public?partial?class?BubblleControlExample?:?UserControl
????{
????????public?BubblleControlExample()
????????{
????????????InitializeComponent();
????????}
????????public?ICommand?ClickCommand?=>?new?RelayCommand(delegate
????????{
???????????WPFDevelopers.Minimal.Controls.MessageBox.Show("点击完成。");
????????});
????????private?void?BubblleControl_Click(object?sender,?System.Windows.RoutedEventArgs?e)
????????{
????????????MessageBox.Show($"点击了“?{MyBubblleControl.SelectedText}开发者?”.",?"提示",MessageBoxButton.OK,MessageBoxImage.Information);
????????}
????}
}

以上就是WPF模拟实现Gitee泡泡菜单的示例代码的详细内容,更多关于WPF泡泡菜单的资料请关注w3xue其它相关文章!