WPF 实现 Gitee 气泡菜单
Posted dotNET跨平台
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF 实现 Gitee 气泡菜单相关的知识,希望对你有一定的参考价值。
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开发者”.", "提示");
);
以上是关于WPF 实现 Gitee 气泡菜单的主要内容,如果未能解决你的问题,请参考以下文章