WPF泡泡菜单
驚鏵 人气:0WPF实现 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); } } }
加载全部内容