WPF模拟实现Gitee泡泡菜单的示例代码
作者:驚鏵 发布时间:2023-09-19 00:53:16
标签:WPF,Gitee,菜单
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);
}
}
}
来源:https://mp.weixin.qq.com/s/5vBrLB4tTudDFR-zh67lPQ
0
投稿
猜你喜欢
- ArrayList简介:ArrayList实现了List接口它是一个可调整大小的数组可以用来存放各种形式的数据。并提供了包括CRUD在内的多
- 本文实例讲述了C#接口在派生类和外部类中的调用方法。分享给大家供大家参考,具体如下:C#的接口通过interface关键字进行创建,在接口中
- jdk中自带了很多工具可以用于性能分析,位于jdk的bin目录下,jvisualvm工具可以以图形化的方式更加直观的监控本地以及远程的jav
- 本文实例为大家分享了java实现斗地主发牌系统的具体代码,供大家参考,具体内容如下玩家类package com.softeem.exampl
- 在开发的过程中大家一般都会选择使用数据线连接的方式进行调试,但是有些时候比如使用模拟器时就不能这样了,所以有必要来研究下怎么使用adb通过w
- 前言在有些业务场景中,系统对于响应时间有一定的要求,而一个方法里面同步执行的业务逻辑太多势必会影响响应速度,带来不好的用户体验。比如登录时记
- 在项目开发中某些场景必须要用到启动项目后立即执行方式的功能,如我们需要去初始化数据到redis缓存,或者启动后读取相应的字典配置等,这篇文章
- 在C/C++跨平台开发中,我们知道在Windows上可以通过VS,进行单步断点调试,这非常方便。但是我们如果编译好的动态库so,想要跟踪下其
- 从主线程发送消息到子线程(准确地说应该是非UI线程)package com.zhuozhuo;import android.app.Acti
- springboot去除控制台打印的debug日志1.创建logback-spring.xml文件文件内容如下<?xml versio
- 本文介绍了JAVA利用HttpClient进行HTTPS接口调用的方法,分享给大家,具体如下:1.为了避免需要证书,所以用一个类继承Defa
- 项目配置依赖首先搭建一个标准的SpringBoot项目工程,相关版本以及依赖如下本项目借助SpringBoot 2.2.1.RELEASE
- 本文实例讲述了WinForm实现状态栏跑马灯效果的方法。分享给大家供大家参考,具体如下:using System;using System.
- 最近 IDEA 2020最后一个版本发布了 ,已经内置了Lombok插件,SpringBoot 2.1.x之后的版本也在Starter中内置
- 楔子近期公司程序被安全扫描出 远程主机允许明文身份验证 中风险漏洞,查了下修复方案,RabbitMQ官方提供了SSL连接方式,而且 Spri
- 本文实例讲述了C#获取指定年份第一个星期一具体日期的方法。分享给大家供大家参考。具体如下:DateTime day = DateTime.P
- C#小程序飞行棋,程序效果图1、设计分析这个程序界面大致分为四部分:① 最上面游戏名字界面②信息提示区③游戏界面区④游戏操作提示区2、分区设
- 应用:直接使用bootcdn提供的静态资源,不需要本地存储bootcdn 官网:https://www.bootcdn.cn/staticf
- 前言:由于最近有解析协议的一些业务场景,需要用到一些字节操作工具,这里封装了一些比较常用的转换方法,测试后基本没有问题,可能一些比较偏门的数
- MyBatis查询返回null可能原因SQL语句查询条件有问题数据库中没数据返回字段与Entity的属性不对应解决方案针对 SQL