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 代码如下;
- 随机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) 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泡泡菜单的资料请关注编程网其它相关文章!