基于CefSharp开发浏览器(八)浏览器收藏夹栏

时间:2024-01-28 16:35:19

一、前言

上一篇文章 基于CefSharp开发(七)浏览器收藏夹菜单 简单实现了部分收藏夹功能 如(添加文件夹、添加收藏、删除、右键菜单部分功能)

后续代码中对MTreeViewItem进行了扩展,增加了TextBox用于编辑Item及相应的依赖属性,实现了重命名操作。

浏览器除了有收藏夹菜单,还需要有收藏夹栏用于快捷访问,本章将开发简易的收藏夹栏。

二、收藏夹栏分析

如下面两幅图所示,前者为收藏夹菜单样式,后者为收藏夹栏样式,两者数据结构相同,只是展示形式略有差异。故可采用同一数据结构

 

观其展示形式与MMenu接近,故此处将采用Menu进行样式扩展开发。

收藏夹栏支持右键功能,故需要添加ContextMenu支持。

收藏夹栏与收藏夹菜单展示的同一数据,属于同一功能,故需增加联动。

下面将逐步完成以上内容的开发,由于样式及结构均与收藏夹菜单接近,故本章不对样式进行叙述,直撸代码

三、创建自定义控件并添加至WebTabControlUc

1、新增定义控件MFavorites、MFavoritesItem

样式可复制MMenu、MMenuItem

MFavorites.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:Cys_CustomControls.Controls">
    <Style TargetType="{x:Type local:MFavorites}">
        <Setter Property="Foreground" Value="{DynamicResource ColorBrush.FontDefaultColor}"/>
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Width" Value="Auto"/>
        <Setter Property="Height" Value="35"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:MFavorites}">
                    <Border Background="{TemplateBinding Background}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            BorderBrush="{TemplateBinding BorderBrush}" 
                            Padding="{TemplateBinding Padding}" 
                            SnapsToDevicePixels="true">
                        <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
View Code

MFavorites.xaml.cs

public class MFavorites : Menu
{
    static MFavorites()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MFavorites), new FrameworkPropertyMetadata(typeof(MFavorites)));
    }
}
View Code

MFavoritesItem.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:Cys_CustomControls.Controls">

    <ControlTemplate x:Key="MTopLevelHeaderTemplate" TargetType="{x:Type local:MFavoritesItem}">
        <Border x:Name="templateRoot" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" CornerRadius="{TemplateBinding CornerRadius}" SnapsToDevicePixels="true">
            <Grid>
                <Grid VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="PART_TextGrid" Opacity="0.8" Margin="10,0">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock x:Name="Icon" FontSize="20" HorizontalAlignment="Center" Text="{TemplateBinding Icon}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center" Foreground="{TemplateBinding IconForeground}"/>
                    <ContentPresenter Margin="10,0,0,0" Grid.Column="1" ContentSource="Header" x:Name="PART_Header" HorizontalAlignment="Center" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/>
                </Grid>
                <Popup x:Name="PART_Popup" AllowsTransparency="true" Focusable="false" IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" 
                   Placement="Bottom" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}" PlacementTarget="{Binding ElementName=templateRoot}" Width="{TemplateBinding PopupWidth}">
                    <Border x:Name="SubMenuBorder" Margin="0 0 5 5" >
                        <Border.Effect>
                            <DropShadowEffect Color="{DynamicResource Color.MenuItemDropShadowBrush}" Opacity="0.3" ShadowDepth="3"/>
                        </Border.Effect>
                        <Border Background="{DynamicResource WebBrowserBrushes.WebMenuBackground}" BorderThickness="1" CornerRadius="5">
                            <ScrollViewer x:Name="SubMenuScrollViewer" Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer, TypeInTargetAssembly={x:Type FrameworkElement}}}" Margin="0,5">
                                <Grid RenderOptions.ClearTypeHint="Enabled" Background="Transparent">
                                    <Canvas HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
                                        <Rectangle x:Name="OpaqueRect" Fill="{Binding Background, ElementName=SubMenuBorder}" 
                                                   Height="{Binding ActualHeight, ElementName=SubMenuBorder}"
                                                   Width="{Binding ActualWidth, ElementName=SubMenuBorder}"/>
                                    </Canvas>
                                    <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Cycle" 
                                                    Grid.IsSharedSizeScope="true" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" KeyboardNavigation.TabNavigation="Cycle"/>
                                </Grid>
                            </ScrollViewer>
                        </Border>
                    </Border>
                </Popup>
            </Grid>
        </Border>
        <ControlTemplate.Triggers>
            <Trigger Property="IsSuspendingPopupAnimation" Value="true">
                <Setter Property="PopupAnimation" TargetName="PART_Popup" Value="None"/>
            </Trigger>
            <!--<Trigger Property="Icon" Value="{x:Null}">
                <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
            </Trigger>-->
            <Trigger Property="ScrollViewer.CanContentScroll" SourceName="SubMenuScrollViewer" Value="false">
                <Setter Property="Canvas.Top" TargetName="OpaqueRect" Value="{Binding VerticalOffset, ElementName=SubMenuScrollViewer}"/>
                <Setter Property="Canvas.Left" TargetName="OpaqueRect" Value="{Binding HorizontalOffset, ElementName=SubMenuScrollViewer}"/>
            </Trigger>
            <Trigger Property="IsHighlighted" Value="True">
                <Setter TargetName="templateRoot" Property="Background"  Value="{DynamicResource WebBrowserBrushes.WebMenuIsMouseOverBackground}"/>
                <Setter TargetName="PART_TextGrid" Property="Opacity"  Value="1"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <ControlTemplate x:Key="MTopLevelItemTemplate" TargetType="{x:Type local:MFavoritesItem}">
        <Border x:Name="templateRoot" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" CornerRadius="{TemplateBinding CornerRadius}" SnapsToDevicePixels="true">
            <Grid VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="PART_TextGrid" Opacity="0.8" Margin="10,0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <TextBlock x:Name="Icon" FontSize="20" HorizontalAlignment="Center" Text="{TemplateBinding Icon}" Foreground="{TemplateBinding IconForeground}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/>
                <ContentPresenter Margin="10,0,0,0" ContentSource="Header" HorizontalAlignment="Center" Grid.Column="1" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/>
            </Grid>
        </Border>
        <ControlTemplate.Triggers>
            <!--<Trigger Property="Icon" Value="{x:Null}">
                <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
            </Trigger>-->
            <Trigger Property="IsHighlighted" Value="True">
                <Setter TargetName="templateRoot" Property="Background"  Value="{DynamicResource WebBrowserBrushes.WebMenuIsMouseOverBackground}"/>
                <Setter TargetName="PART_TextGrid" Property="Opacity"  Value="1"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <ControlTemplate  x:Key="MSubmenuHeaderTemplate" TargetType="{x:Type local:MFavoritesItem}">
        <Border x:Name="templateRoot" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Height="35" SnapsToDevicePixels="true">
            <Grid >
                <Grid HorizontalAlignment="Left" VerticalAlignment="Center" Margin="10,0">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Column="0" x:Name="Icon" Width="20" FontSize="20" HorizontalAlignment="Center" Text="{TemplateBinding Icon}" Foreground="{TemplateBinding IconForeground}"
                               SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/>
                    <ContentPresenter Margin="10,0,0,0" Grid.Column="1" ContentSource="Header"  HorizontalAlignment="Center" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/>
                </Grid>
                <Path x:Name="RightArrow" Data="M 0,0 L 7,7 L 0,14 Z" 
                      Fill="{TemplateBinding Foreground}" 
                      HorizontalAlignment="Right" Margin="0,0,5,0" VerticalAlignment="Center" Opacity="0.6"/>

                <Popup x:Name="PART_Popup" AllowsTransparency="true" Focusable="false" IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" 
                   Placement="Right" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}" PlacementTarget="{Binding ElementName=templateRoot}" VerticalOffset="-5" Width="{TemplateBinding PopupWidth}">
                    <Border x:Name="SubMenuBorder" Margin="0 0 5 5" >
                        <Border.Effect>
                            <DropShadowEffect Color="{DynamicResource Color.MenuItemDropShadowBrush}" Opacity="0.3" ShadowDepth="3"/>
                        </Border.Effect>
                        <Border Background="{DynamicResource WebBrowserBrushes.WebMenuBackground}" BorderThickness="1" CornerRadius="5">
                            <ScrollViewer x:Name="SubMenuScrollViewer" Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer, TypeInTargetAssembly={x:Type FrameworkElement}}}" Margin="0,5">
                                <Grid RenderOptions.ClearTypeHint="Enabled" Background="Transparent">
                                    <Canvas HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
                                        <Rectangle x:Name="OpaqueRect" Fill="{Binding Background, ElementName=SubMenuBorder}" 
                                                   Height="{Binding ActualHeight, ElementName=SubMenuBorder}"
                                                   Width="{Binding ActualWidth, ElementName=SubMenuBorder}"/>
                                    </Canvas>
                                    <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Cycle" 
                                                    Grid.IsSharedSizeScope="true" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" KeyboardNavigation.TabNavigation="Cycle"/>
                                </Grid>
                            </ScrollViewer>
                        </Border>
                    </Border>
                </Popup>
            </Grid>
        </Border>
        <ControlTemplate.Triggers>
            <Trigger Property="IsSuspendingPopupAnimation" Value="true">
                <Setter Property="PopupAnimation" TargetName="PART_Popup" Value="None"/>
            </Trigger>
            <!--<Trigger Property="Icon" Value="{x:Null}">
                <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
            </Trigger>-->
            <Trigger Property="IsChecked" Value="True">
                <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
            </Trigger>
            <Trigger Property="ScrollViewer.CanContentScroll" SourceName="SubMenuScrollViewer" Value="false">
                <Setter Property="Canvas.Top" TargetName="OpaqueRect" Value="{Binding VerticalOffset, ElementName=SubMenuScrollViewer}"/>
                <Setter Property="Canvas.Left" TargetName="OpaqueRect" Value="{Binding HorizontalOffset, ElementName=SubMenuScrollViewer}"/>
            </Trigger>
            <Trigger Property="IsHighlighted" Value="True">
                <Setter TargetName="templateRoot" Property="Background"  Value="{DynamicResource WebBrowserBrushes.WebMenuIsMouseOverBackground}"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <ControlTemplate x:Key="MSubmenuItemTemplate" TargetType="{x:Type local:MFavoritesItem}">
        <Border x:Name="templateRoot" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Height="35" SnapsToDevicePixels="true">
            <Grid HorizontalAlignment="Left" VerticalAlignment="Center" Margin="10,0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Column="0" x:Name="Icon" Width="20" FontSize="20" HorizontalAlignment="Center" Text="{TemplateBinding Icon}" Foreground="{TemplateBinding IconForeground}"
                           SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/>
                <ContentPresenter Margin="10,0,0,0" Grid.Column="1" x:Name="menuHeaderContainer"  HorizontalAlignment="Center" ContentSource="Header" RecognizesAccessKey="True" 
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/>
            </Grid>
        </Border>
        <ControlTemplate.Triggers>
            <!--<Trigger Property="Icon" Value="{x:Null}">
                <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
            </Trigger>-->
            <Trigger Property="IsChecked" Value="True">
                <Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
            </Trigger>
            <Trigger Property="IsHighlighted" Value="True">
                <Setter TargetName="templateRoot" Property="Background"  Value="{DynamicResource WebBrowserBrushes.WebMenuIsMouseOverBackground}"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <Style TargetType="{x:Type local:MFavoritesItem}">
        <Setter Property="BorderBrush" Value="Transparent"/>
        <Setter Property="CornerRadius" Value="5"/>
        <Setter Property="Background" Value="{DynamicResource WebBrowserBrushes.TabHeaderIsSelectedBackground}"/>
        <Setter Property="Foreground" Value="{DynamicResource ColorBrush.FontPrimaryColor}"/>
        <Setter Property="FontSize" Value="13"/>
        <Setter Property="FontFamily" Value="Microsoft YaHei"/>
        <Setter Property="Cursor" Value="Hand"/>
        <Setter Property="Height" Value="35"/>
        <Setter Property="Width" Value="Auto"/>
        <Setter Property="MinWidth" Value="40"/>
        <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="ScrollViewer.PanningMode" Value="Both"/>
        <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
        <Setter Property="Template" Value="{StaticResource MSubmenuItemTemplate}"/>
        <Style.Triggers>
            <Trigger Property="Role" Value="TopLevelHeader">
                <Setter Property="BorderThickness" Value="0,0,1,0"/>
                <Setter Property="PopupWidth" Value="300"/>
                <Setter Property="Template" Value="{StaticResource MTopLevelHeaderTemplate}"/>
            </Trigger>
            <Trigger Property="Role" Value="TopLevelItem">
                <Setter Property="BorderThickness" Value="0,0,1,0"/>
                <Setter Property="Template" Value="{StaticResource MTopLevelItemTemplate}"/>
            </Trigger>
            <Trigger Property="Role" Value="SubmenuHeader">
                <Setter Property="PopupWidth" Value="300"/>
                <Setter Property="Background" Value="Transparent"/>
                <Setter Property="Foreground" Value="{DynamicResource ColorBrush.FontPrimaryColor}"/>
                <Setter Property="Template" Value="{StaticResource MSubmenuHeaderTemplate}"/>
                <Setter Property="Padding" Value="3,0,0,0"/>
            </Trigger>
            <Trigger Property="Role" Value="SubmenuItem">
                <Setter Property="Foreground" Value="{DynamicResource ColorBrush.FontPrimaryColor}"/>
                <Setter Property="Background" Value="Transparent"/>
                <Setter Property="Template" Value="{StaticResource MSubmenuItemTemplate}"/>
                <Setter Property="Padding" Value="3,0,0,0"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>
View Code

MFavoritesItem.xaml.cs

public class MFavoritesItem : MenuItem
{
    static MFavoritesItem()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MFavoritesItem), new FrameworkPropertyMetadata(typeof(MFavoritesItem)));
    }

    #region == DependencyProperty==
    #region == CornerRadius==
    public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(MFavoritesItem),
        new PropertyMetadata(null));

    /// <summary>
    /// CornerRadius
    /// </summary>
    public CornerRadius CornerRadius
    {
        get => (CornerRadius)GetValue(CornerRadiusProperty);
        set => SetValue(CornerRadiusProperty, value);
    }
    #endregion

    #region == PopupWidth==
    public static readonly DependencyProperty PopupWidthProperty = DependencyProperty.Register("PopupWidth", typeof(double), typeof(MFavoritesItem),
        new PropertyMetadata(null));

    /// <summary>
    /// PopupWidth
    /// </summary>
    public double PopupWidth
    {
        get => (double)GetValue(PopupWidthProperty);
        set => SetValue(PopupWidthProperty, value);
    }
    #endregion

    /// <summary>
    /// IconForeground 字体图标前景色
    /// </summary>
    public static readonly DependencyProperty IconForegroundProperty = DependencyProperty.Register("IconForeground", typeof(Brush), typeof(MFavoritesItem));
    public Brush IconForeground
    {
        get => (Brush)GetValue(IconForegroundProperty);
        set => SetValue(IconForegroundProperty, value);
    }

    /// <summary>
    /// ItemMargin 
    /// </summary>
    public static readonly DependencyProperty ItemMarginProperty = DependencyProperty.Register("ItemMargin", typeof(Thickness), typeof(MFavoritesItem));
    public Thickness ItemMargin
    {
        get => (Thickness)GetValue(ItemMarginProperty);
        set => SetValue(ItemMarginProperty, value);
    }

    /// <summary>
    /// TextMaxWidth 
    /// </summary>
    public static readonly DependencyProperty TextMaxWidthProperty = DependencyProperty.Register("TextMaxWidth", typeof(double), typeof(MFavoritesItem));
    public double TextMaxWidth
    {
        get => (double)GetValue(TextMaxWidthProperty);
        set => SetValue(TextMaxWidthProperty, value);
    }
    #endregion

    public int Type { get; set; }

    public int Level { get; set; }
    public int NodeId { get; set; }
}
View Code

2、接着新增用户控件FavoritesBarUc

用于承接MFavorites代码如下:

FavoritesBarUc.xaml

<UserControl x:Class="MWebBrowser.View.FavoritesBarUc"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:controls="clr-namespace:Cys_CustomControls.Controls;assembly=Cys_CustomControls"
             mc:Ignorable="d" 
             d:DesignHeight="40" d:DesignWidth="800" Height="40" Background="{DynamicResource WebBrowserBrushes.TabHeaderIsSelectedBackground}">
    <Grid VerticalAlignment="Center">
        <controls:MFavorites x:Name="MenuParent" ContextMenuOpening="FavoritesTree_OnContextMenuOpening" ScrollViewer.HorizontalScrollBarVisibility="Hidden" PreviewMouseLeftButtonUp="FavoritesTree_OnPreviewMouseLeftButtonUp">
            <controls:MFavorites.ContextMenu>
                <ContextMenu x:Name="FavoritesContextMenu" Style="{DynamicResource WebCustomMenus.DefaultContextMenu}">
                    <controls:MMenuItem Tag="0" x:Name="OpenAllFolder" Header="全部打开(16个)" Icon="&#xe600;"/>
                    <controls:MMenuItem Tag="1" x:Name="OpenNewAllFolder" Header="在新建窗口中全部打开(16个)" Icon="&#xe602;"/>
                    <controls:MMenuItem Tag="2" Header="在新 InPrivate窗口全部打开(16个)" Icon="&#xe68c;"/>
                    <controls:MMenuItem Tag="4" Header="按名称排序" Icon="&#xe606;"/>
                    <controls:MMenuItem Tag="5" x:Name="ReName" Header="重命名" Icon="&#xe712;" Click="ReName_OnClick"/>
                    <controls:MMenuItem Tag="6" x:Name="DeleteNode" Header="删除" Icon="&#xe74e;" IconFontSize="26" Click="Delete_OnClick"/>
                    <controls:MMenuItem Tag="7" Header="将当前标签页添加到文件夹" Icon="&#xe659;" Click="AddFavorites_OnClick"/>
                    <controls:MMenuItem Tag="8" Header="将所有标签页添加到文件夹" Visibility="Collapsed"/>
                    <controls:MMenuItem Tag="9" Header="添加文件夹" Icon="&#xe652;" Click="AddFolder_OnClick"/>
                </ContextMenu>
            </controls:MFavorites.ContextMenu>
        </controls:MFavorites>
        <Popup x:Name="ReNamePop" PopupAnimation="Fade" Placement="Bottom"  PlacementTarget="{Binding ElementName=MenuParent}"
               StaysOpen="False" AllowsTransparency="True" VerticalOffset="-40">
            <Border Background="{DynamicResource WebBrowserBrushes.WebMenuBackground}" CornerRadius="5">
                <Border.Effect>
                    <DropShadowEffect Color="{DynamicResource Color.MenuItemDropShadowBrush}" Opacity="0.3" ShadowDepth="3"/>
                </Border.Effect>
                <Grid Width="320" Height="140">
                    <Grid Margin="20,20,20,0">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <TextBlock Grid.Row="0" Text="编辑文件夹名称" FontSize="18" Foreground="{DynamicResource ColorBrush.FontPrimaryColor}"/>
                        <StackPanel Grid.Row="1" Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Center">
                            <TextBlock Text="名称" Foreground="{DynamicResource ColorBrush.FontPrimaryColor}" VerticalAlignment="Center"/>
                            <TextBox x:Name="FolderName" Height="30" Width="236" Margin="10,0,0,0" Style="{DynamicResource TextBox.ReName}" VerticalAlignment="Center"/>
                        </StackPanel>
                        <StackPanel Grid.Row="2" Margin="0,15,0,0" Orientation="Horizontal" HorizontalAlignment="Right">
                            <Button Content="保存" Style="{DynamicResource Button.ReSave}" Click="ReSave_OnClick"/>
                            <Button Content="取消" Style="{DynamicResource Button.ReCancel}" Click="ReCancel_OnClick" Margin="10,0,0,0"/>
                        </StackPanel>
                    </Grid>
                </Grid>
            </Border>
        </Popup>
    </Grid>
</UserControl>
View Code

FavoritesBarUc.xaml.cs 

using Cys_Common;
using Cys_Controls.Code;
using Cys_CustomControls.Controls;
using Cys_Model;
using MWebBrowser.Code.Helpers;
using MWebBrowser.ViewModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace MWebBrowser.View
{
    /// <summary>
    /// Interaction logic for FavoritesBarUc.xaml
    /// </summary>
    public partial class FavoritesBarUc : UserControl
    {
        private readonly double _textMaxWidth = 300;
        /// <summary>
        /// 记录当前右键选中的Item;
        /// </summary>
        private MFavoritesItem _currentRightItem;
        public Func<WebTabControlViewModel> GetWebUrlEvent;
        public Action<string> OpenNewTabEvent;
        public FavoritesBarUc()
        {
            InitializeComponent();
            this.Loaded += FavoritesBarUc_Loaded;
        }

        private void FavoritesBarUc_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            if (this.IsInDesignMode()) return;
            GetFavoritesInfo();
        }

        private void GetFavoritesInfo()
        {
            List<TreeNode> root = GetNodes(-1, GlobalInfo.FavoritesSetting.FavoritesInfos);
            if (root == null || root.Count <= 0 || root[0].ChildNodes.Count <= 0) return;
            foreach (var child in root[0].ChildNodes)
            {
                AddFavoritesItem(null, child, true);
            }
        }

        private List<TreeNode> GetNodes(int parentId, List<TreeNode> nodes)
        {
            List<TreeNode> mainNodes = nodes.Where(x => x.ParentId == parentId).OrderByDescending(x => x.Type).ToList();
            List<TreeNode> otherNodes = nodes.Where(x => x.ParentId != parentId).OrderByDescending(x => x.Type).ToList();
            foreach (TreeNode node in mainNodes)
                node.ChildNodes = GetNodes(node.NodeId, otherNodes);
            return mainNodes;
        }

        /// <summary>
        /// 递归添加子集
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="treeNode"></param>
        /// <param name="isRoot"></param>
        private void AddFavoritesItem(MFavoritesItem parent, TreeNode treeNode, bool isRoot)
        {
            var item = GetNewFavoritesItem(treeNode);
            if (treeNode.ChildNodes.Count > 0)
            {
                foreach (var child in treeNode.ChildNodes)
                {
                    AddFavoritesItem(item, child, false);
                }
            }

            if (!isRoot)
                parent.Items.Add(item);
            else
                MenuParent.Items.Add(item);
        }

        /// <summary>
        /// 获取FavoritesItem
        /// </summary>
        /// <param name="treeNode"></param>
        /// <returns></returns>
        private MFavoritesItem GetNewFavoritesItem(TreeNode treeNode)
        {
            return new MFavoritesItem
            {
                Header = treeNode.NodeName,
                Type = treeNode.Type,
                NodeId = treeNode.NodeId,
                Level = treeNode.Level,
                TextMaxWidth = _textMaxWidth,
                Icon = treeNode.Type == 0 ? "\ueb1e" : "\ue903",
                IconForeground = treeNode.Type == 0 ? new SolidColorBrush(Color.FromRgb(255, 255, 255)) : new SolidColorBrush(Color.FromRgb(255, 205, 44)),
            };
        }

        /// <summary>
        /// 处理右键菜单打开前的行为
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FavoritesTree_OnContextMenuOpening(object sender, ContextMenuEventArgs e)
        {
            _currentRightItem = ControlHelper.FindVisualParent<MFavoritesItem>(e.OriginalSource as DependencyObject);
            if (null == _currentRightItem)
            {
                e.Handled = true;
                return;
            }
            if (_currentRightItem.Type == 0)
            {
                OpenAllFolder.Visibility = Visibility.Collapsed;
                OpenNewAllFolder.Visibility = Visibility.Collapsed;
                ReName.Visibility = Visibility.Collapsed;
            }
            else
            {
                OpenAllFolder.Visibility = Visibility.Visible;
                OpenNewAllFolder.Visibility = Visibility.Visible;
                ReName.Visibility = Visibility.Visible;
            }
        }

        private void FavoritesTree_OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            var item = ControlHelper.FindVisualParent<MFavoritesItem>(e.OriginalSource as DependencyObject);
            if (item.Type == 1) return;
            if (!GlobalInfo.FavoritesSetting.FavoritesInfos.Exists(x => x.NodeId == item.NodeId)) return;
            var treeNode = GlobalInfo.FavoritesSetting.FavoritesInfos.First(x => x.NodeId == item.NodeId);
            OpenNewTabEvent?.Invoke(treeNode.Url);
        }

        /// <summary>
        /// 添加收藏
        /// </summary>
        /// <param name="sender"></param> 
        /// <param name="e"></param>
        private void AddFavorites_OnClick(object sender, RoutedEventArgs e)
        {
            var model = GetWebUrlEvent?.Invoke();
            if (null == model) return;
            if (_currentRightItem?.Type != 1) return;
            var newTreeNode = GetNewTreeNodeInfo(false, 0, model.Title, model.CurrentUrl);
            _currentRightItem.Items.Add(newTreeNode.Item2);
            GlobalInfo.FavoritesSetting.FavoritesInfos.Add(newTreeNode.Item1);
        }
        /// <summary>
        /// 添加文件夹
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AddFolder_OnClick(object sender, RoutedEventArgs e)
        {
            var newTreeNode = GetNewTreeNodeInfo(false, 1, "新建文件夹", null);
            if (_currentRightItem != null && _currentRightItem.Type == 1)
            {
                _currentRightItem.Items.Add(newTreeNode.Item2);
                GlobalInfo.FavoritesSetting.FavoritesInfos.Add(newTreeNode.Item1);
            }
        }

        private Tuple<TreeNode, MFavoritesItem> GetNewTreeNodeInfo(bool isRoot, int type, string nodeName, string url)
        {
            int parentId = 0;
            int level = 1;
            if (!isRoot)
            {
                parentId = _currentRightItem.NodeId;
                level = parentId == -1 ? +1 : _currentRightItem.Level + 1;
            }
            int nodeMax = GlobalInfo.FavoritesSetting.FavoritesInfos.Max(x => x.NodeId);
            var treeNode = new TreeNode
            {
                Url = url,
                ParentId = parentId,
                NodeId = nodeMax + 1,
                NodeName = nodeName,
                Type = type,
                Level = level,
            };
            var favoritesItem = GetNewFavoritesItem(treeNode);
            return new Tuple<TreeNode, MFavoritesItem>(treeNode, favoritesItem);
        }

        #region 右键菜单操作

        /// <summary>
        /// 删除当前节点
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Delete_OnClick(object sender, RoutedEventArgs e)
        {
            if (_currentRightItem?.Parent == null) return;
            for (int i = _currentRightItem.Items.Count; i > 0; i--)
            {
                _currentRightItem.Items.Remove(_currentRightItem.Items[^1]);
                if (!GlobalInfo.FavoritesSetting.FavoritesInfos.Exists(x => x.NodeId == _currentRightItem.NodeId))
                    continue;
            }

            if (_currentRightItem.Parent is MFavoritesItem items)
            {
                if (GlobalInfo.FavoritesSetting.FavoritesInfos.Exists(x => x.NodeId == _currentRightItem.NodeId))
                {
                    var currentNode = (GlobalInfo.FavoritesSetting.FavoritesInfos.FirstOrDefault(x => x.NodeId == _currentRightItem.NodeId));
                    GlobalInfo.FavoritesSetting.FavoritesInfos.Remove(currentNode);
                }
                items.Items.Remove(_currentRightItem);
            }

            if (_currentRightItem.Parent is MFavorites parent)
            {
                if (GlobalInfo.FavoritesSetting.FavoritesInfos.Exists(x => x.NodeId == _currentRightItem.NodeId))
                {
                    var currentNode = (GlobalInfo.FavoritesSetting.FavoritesInfos.FirstOrDefault(x => x.NodeId == _currentRightItem.NodeId));
                    GlobalInfo.FavoritesSetting.FavoritesInfos.Remove(currentNode);
                }
                parent.Items.Remove(_currentRightItem);
            }
        }

        #region 重命名

        private void ReName_OnClick(object sender, RoutedEventArgs e)
        {
            if (null == _currentRightItem) return;
            if (_currentRightItem.Type == 0) return;

            ReNamePop.HorizontalOffset = (this.ActualWidth - 320) / 2;
            ReNamePop.IsOpen = true;
        }

        private void ReCancel_OnClick(object sender, RoutedEventArgs e)
        {
            ReNamePop.IsOpen = false;
        }

        private void ReSave_OnClick(object sender, RoutedEventArgs e)
        {
            ReNamePop.IsOpen = false;
            _currentRightItem.Header = FolderName.Text;
            if (!GlobalInfo.FavoritesSetting.FavoritesInfos.Exists(x => x.NodeId == _currentRightItem.NodeId)) return;
            var treeNode = GlobalInfo.FavoritesSetting.FavoritesInfos.First(x => x.NodeId == _currentRightItem.NodeId);
            treeNode.NodeName = FolderName.Text;
        }

        #endregion

        #endregion
    }
}
View Code

该类中的方法用于初始化MFavorites数据

3、更改WebTabControlUc布局

新增一行用于展示FavoritesBarUc

<webBrowser:FavoritesBarUc Grid.Row="2"/>

四、运行效果

五、源码地址

gitee地址:https://gitee.com/sirius_machao/mweb-browser

项目邀请:如对该项目有兴趣,欢迎联系我共同开发!!!