社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Git

WPF 实现 Gitee 泡泡菜单「完」

dotNET跨平台 • 4 年前 • 354 次点击  

 WPF 实现 Gitee 泡泡菜单「完」

气泡菜单「完」

作者:WPFDevelopersOrg

原文链接:    https://github.com/WPFDevelopersOrg/WPFDevelopers

  • 框架使用大于等于.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 _items = new ObservableCollection();


        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>WPFsys:String>
                            <sys:String>ASP.NETsys:String>
                            <sys:String>WinUIsys:String>
                            <sys:String>WebAPIsys:String>
                            <sys:String>Blazorsys:String>
                            <sys:String>MAUIsys:String>
                            <sys:String>Xamarinsys:String>
                            <sys:String>WinFormsys:String>
                            <sys:String>UWPsys: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
{
    /// 
    /// BubbleControlExample.xaml 的交互逻辑
    /// 
    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);
        }
    }
}



Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/138368