一、Edge收藏夹菜单分析

如下图所示为Edge收藏夹菜单, 点击收藏夹菜单按钮(红框部分)弹出收藏夹菜单窗体,窗体中包含工具栏(绿框部分)和树型菜单(黄框部分)

工具栏按钮功能分别为添加当前网页到根节点、创建新文件夹到根节点、搜索收藏夹内容、单中单(收藏夹菜单中的其他功能)、收藏夹菜单固定到右侧

在树节点上右键,有如下右键菜单功能

要完成此功能,需先仿其形,再谋其神。

二、仿其形

这里所谓的仿其形,即模仿它的样式(Style)

1、创建收藏夹菜单UserControl

创建FavoritesMenuUc,Grid内容如下:ToggleButton控制菜单显隐,Popup展示弹出菜单,

StackPanel 中添加五个按钮用于实现工具栏中的功能,MTreeView用于实现树型菜单

关于MTreeView的实现请参照 Cys_Control(六) MTreeView

<Grid>
<ToggleButton x:Name="FavoritesButton" Style="{DynamicResource ToggleButton.FontButton}" Checked="FavoritesButton_OnChecked"
Unchecked="FavoritesButton_OnUnchecked" Content="" FontSize="26" Background="Transparent" IsChecked="{Binding ElementName=FavoritesPop,Path=IsOpen}"/>
<Popup x:Name="FavoritesPop" PopupAnimation="Fade" Placement="Bottom" PlacementTarget="{Binding ElementName=FavoritesButton}"
StaysOpen="False" AllowsTransparency="True" HorizontalOffset="-330">
<Border Background="{DynamicResource WebBrowserBrushes.WebMenuBackground}" CornerRadius="5">
<Border.Effect>
<DropShadowEffect Color="{DynamicResource Color.MenuItemDropShadowBrush}" Opacity="0.3" ShadowDepth="3"/>
</Border.Effect>
<Grid Width="360" Height="660">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Height="40">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="收藏夹栏" VerticalAlignment="Center" FontSize="18" Margin="10,0,0,0" Foreground="{DynamicResource WebBrowserBrushes.DefaultForeground}"/>
<StackPanel Grid.Column="2" Orientation="Horizontal" Margin="0,0,10,0">
<Button Style="{DynamicResource Button.FontButton}" Content=""/>
<Button Style="{DynamicResource Button.FontButton}" Content=""/>
<Button Style="{DynamicResource Button.FontButton}" Content=""/>
<Button Style="{DynamicResource Button.FontButton}" Content="..." Padding="0,0,0,12"/>
<Button Style="{DynamicResource Button.FontButton}" Content=""/>
</StackPanel>
</Grid>
<Rectangle Grid.Row="1" Height="1" Fill="{DynamicResource WebBrowserBrushes.WebMenuDivideLine}"/>
<controls:MTreeView Grid.Row="2" x:Name="FavoritesTree"/>
</Grid>
</Border>
</Popup>
</Grid>

新建类TreeNode用于显示TreeView显示

public class TreeNode
{
public int NodeId { get; set; }
public int ParentId { get; set; }
public string NodeName { get; set; }
public string Url { get; set; } = "http://www.baidu.com";
public List<TreeNode> ChildNodes { get; set; } = new List<TreeNode>();
public int Type { get; set; } //0-文件,1-文件夹
}

FavoritesMenuUc.xaml.cs文件中增加测试数据

private void GetFavoritesInfo()
{
nodes = new List<TreeNode>()
{
new TreeNode(){ParentId=-1, NodeId=0, NodeName = "收藏夹",Type = 1},
new TreeNode(){ParentId=0, NodeId=1, NodeName = "文本",Type = 1},
new TreeNode(){ParentId=0, NodeId=2, NodeName = "音频",Type = 1},
new TreeNode(){ParentId=0, NodeId=3, NodeName = "视频",Type = 1}, new TreeNode(){ParentId=1, NodeId=11, NodeName = "文本1",Type = 0},
new TreeNode(){ParentId=1, NodeId=12, NodeName = "文本2",Type = 0},
new TreeNode(){ParentId=1, NodeId=13, NodeName = "文本3",Type = 0},
};
List<TreeNode> root = GetNodes(-1, nodes);
AddTreeViewItems(null, root[0], 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;
} private void AddTreeViewItems(MTreeViewItem parent, TreeNode treeNode, bool isRoot)
{
var treeViewItem = new MTreeViewItem();
if (treeNode.ChildNodes.Count <= 0)
{
if (treeNode.Type == 0)
{
treeViewItem.Header = treeNode.Url;
treeViewItem.Icon = "\ueb1e";
treeViewItem.IsExpandedIcon = "\ueb1e";
treeViewItem.IconForeground = new SolidColorBrush(Color.FromRgb(255, 255, 255));
}
else
{
treeViewItem.Header = treeNode.NodeName;
}
}
else
{
treeViewItem.Header = treeNode.NodeName;
foreach (var child in treeNode.ChildNodes)
{
AddTreeViewItems(treeViewItem, child, false);
}
}
if (!isRoot)
parent.Items.Add(treeViewItem);
else
{
FavoritesTree.Items.Add(treeViewItem);
}
}

运行效果如下:

2、为MTreeView添加右键菜单

右键子菜单可使用MMneuItem,xaml样式如下

<controls:MTreeView Grid.Row="2" x:Name="FavoritesTree" ContextMenuOpening="FavoritesTree_OnContextMenuOpening">
<controls:MTreeView.ContextMenu>
<ContextMenu x:Name="FavoritesContextMenu" Style="{DynamicResource WebCustomMenus.DefaultContextMenu}">
<controls:MMenuItem Tag="0" Header="全部打开(16个)" Icon=""/>
<controls:MMenuItem Tag="1" Header="在新建窗口中全部打开(16个)" Icon=""/>
<controls:MMenuItem Tag="2" Header="在新 InPrivate窗口全部打开(16个)" Icon=""/>
<controls:MMenuItem Tag="4" Header="按名称排序" Icon=""/>
<controls:MMenuItem Tag="5" Header="重命名" Icon=""/>
<controls:MMenuItem Tag="5" Header="删除" Icon="" IconFontSize="26"/>
<controls:MMenuItem Tag="5" Header="将当前标签页添加到文件夹" Icon=""/>
<controls:MMenuItem Tag="5" Header="将所有标签页添加到文件夹"/>
<controls:MMenuItem Tag="5" Header="添加文件夹" Icon=""/>
</ContextMenu>
</controls:MTreeView.ContextMenu>
</controls:MTreeView>

当右键未选中时增加屏蔽右键菜单

private void FavoritesTree_OnContextMenuOpening(object sender, ContextMenuEventArgs e)
{
_currentRightItem = ControlHelper.FindVisualParent<MTreeViewItem>(e.OriginalSource as DependencyObject);
if (null == _currentRightItem)
{
e.Handled = true;
}
}

运行效果如下:

三、谋其神

这里的谋其神,就是使其可以完成正常的业务处理。

前面仿造了Edge浏览器的大致样式,接下来要对及增加数据存储、命令处理等

1、增加Favorites数据存储

这里使用同数据下载同样的处理逻辑,将收藏夹保存为Json文件

新建FavoritesDataRepository,类中添加 SaveFavoritesSetting、GetFavoritesSetting用于保存与读取数据。

public class FavoritesDataRepository
{
public void SaveFavoritesSetting()
{
try
{
var setting = GlobalInfo.FavoritesSetting;
if (setting == null) return;
var fileName = FileDataPath.GetFilePath(DataFileType.Favorites);
CommonOperator.SaveDataJson(setting, fileName);
}
catch (Exception ex)
{ }
} public FavoritesSetting GetFavoritesSetting()
{
var fileName = FileDataPath.GetFilePath(DataFileType.Favorites);
var setting = CommonOperator.GetDataJson<FavoritesSetting>(fileName);
setting ??= new FavoritesSetting();
setting.FavoritesInfos ??= new List<TreeNode>();
if (setting.FavoritesInfos.Count <= 0)
{
setting.FavoritesInfos.Add(new TreeNode()
{
ParentId = -1,
NodeId = 0,
NodeName = "收藏夹",
Type = 1,
});
}
return setting;
}
}

2、增加菜单事件

由于事件较多,目前仅处理添加文件夹、添加收藏、删除功能

添加folder,这里的if用来判断是收藏夹工具栏的按钮还是右键菜单C

private void AddFolder_OnClick(object sender, RoutedEventArgs e)
{
if (sender is Button)
{
var newTreeNode = GetNewTreeNodeInfo(true, 1, "新建文件夹", null);
if (null == FavoritesTree || FavoritesTree.Items.Count <= 0) return;
var treeNodeUc = FavoritesTree.Items[0];
if (!(treeNodeUc is MTreeViewItem item)) return;
item.Items.Add(newTreeNode.Item2);
GlobalInfo.FavoritesSetting.FavoritesInfos.Add(newTreeNode.Item1);
}
else if (sender is MMenuItem)
{
var newTreeNode = GetNewTreeNodeInfo(false, 1, "新建文件夹", null);
if (_currentRightItem != null && _currentRightItem.Type == 1)
{
_currentRightItem.Items.Add(newTreeNode.Item2);
GlobalInfo.FavoritesSetting.FavoritesInfos.Add(newTreeNode.Item1);
}
}
}

添加收藏,添加收藏增加了GetWebUrlEvent事件 用于获取当前tab页的url;

private void AddFavorites_OnClick(object sender, RoutedEventArgs e)
{
var model = GetWebUrlEvent?.Invoke();
if (null == model) return; if (sender is Button)
{
if (!(FavoritesTree.Items[0] is MTreeViewItem item)) return;
var newTreeNode = GetNewTreeNodeInfo(true, 0, model.Title, model.CurrentUrl);
GlobalInfo.FavoritesSetting.FavoritesInfos.Add(newTreeNode.Item1);
item.Items.Add(newTreeNode.Item2);
}
else if (sender is MMenuItem)
{
if (_currentRightItem != null && _currentRightItem.Type == 1)
{
var newTreeNode = GetNewTreeNodeInfo(false, 0, model.Title, model.CurrentUrl);
_currentRightItem.Items.Add(newTreeNode.Item2);
GlobalInfo.FavoritesSetting.FavoritesInfos.Add(newTreeNode.Item1);
}
}
}

删除当前节点

private void Delete_OnClick(object sender, RoutedEventArgs e)
{
if (_currentRightItem?.Parent == null) return; foreach (var item in _currentRightItem.Items)
{
_currentRightItem.Items.Remove(item);
if (!GlobalInfo.FavoritesSetting.FavoritesInfos.Exists(x => x.NodeId == _currentRightItem.NodeId))
continue;
var currentNode = (GlobalInfo.FavoritesSetting.FavoritesInfos.FirstOrDefault(x => x.NodeId == _currentRightItem.NodeId));
GlobalInfo.FavoritesSetting.FavoritesInfos.Remove(currentNode);
} if (_currentRightItem.Parent is MTreeViewItem 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);
}
}

关于创建TreeNode代码细节请参考文章末尾源码

增加OpenNewTabEvent事件用于点击收藏内容时打开网页

四、运行效果

五、源码地址

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

基于CefSharp开发(七)浏览器收藏夹菜单的更多相关文章

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

    一.前言 上一篇文章 基于CefSharp开发(七)浏览器收藏夹菜单 简单实现了部分收藏夹功能 如(添加文件夹.添加收藏.删除.右键菜单部分功能) 后续代码中对MTreeViewItem进行了扩展,增 ...

  2. 基于CefSharp开发浏览器(九)浏览器历史记录弹窗面板

    一.前言 前两篇文章写的是关于浏览器收藏夹的内容,因为收藏夹的内容不会太多,故采用json格式的文本文件作为收藏夹的存储方式. 关于浏览器历史记录,我个人每天大概会打开百来次网页甚至更多,时间越长历史 ...

  3. mac 下基于firebreath 开发多浏览器支持的浏览器插件

    mac 下基于firebreath 开发多浏览器支持的浏览器插件 首先要区分什么是浏览器扩展和浏览器插件;插件可以像本地程序一样做的更多 一. 关于 firebreath http://www.fir ...

  4. 基于python对B站收藏夹按照视频发布时间进行排序

    基于python对B站收藏夹按照视频发布时间进行排序 前言 在最一开始,我的B站收藏一直是存放在默认收藏夹中,但是随着视频收藏的越来越多,没有分类的视频放在一起,想在众多视频中找到想要的视频非常困难, ...

  5. 基于CefSharp开发(五)浏览器菜单样式

    一.菜单分析 上图为Edge浏览器现有的菜单内容,菜单中即有子菜单也有组合菜单. 本章节将开发浏览器菜单样式,菜单部分功能将后期进行处理. 二.创建菜单用户控件 新建用户控件命名为WebMenuUc, ...

  6. 基于CefSharp开发(二)自定义浏览器窗体

    上一篇 https://www.cnblogs.com/mchao/p/13914726.html 简单了解了CefSharp引用配置但页面光秃秃的,这一篇着手开发简单浏览器窗体 一.Edge浏览器窗 ...

  7. 如何备份Chrome浏览器收藏夹

    前言:最近,由于工作需要,要卸载当前Chrome版本,并安装最新版Chrome.卸载前,意识到之前收藏在收藏夹里的很多知识链接还未备份,于是有了今天的话题:如何备份Chrome浏览器的收藏夹? 主题: ...

  8. (转)chrome浏览器收藏夹(书签)的导出与导入

    导出chrome浏览器的书签到一个文件中.首先选择chrome浏览器的书签管理器菜单.然后点击“整理”,然后选择“将书签导出到html文件”. 步骤阅读 2 将导出的html文件保存,用于下次导入,这 ...

  9. 浏览器收藏夹插件-Xmarks

    Xmarks 一一 一款简约实用的浏览器书签同步插件 首先还是想吐槽一下firefox的收藏夹同步功能,感觉不实用,密钥的长度如果不是存到手机或者别的终端,压根没办法实现同步. 而且还区分了,如果两台 ...

随机推荐

  1. 《深入理解计算机系统》实验一 —Data Lab

    本文是CSAPP第二章的配套实验,通过使用有限的运算符来实现正数,负数,浮点数的位级表示.通过完成这13个函数,可以使我们更好的理解计算机中数据的编码方式. 准备工作   首先去官网Lab Assig ...

  2. centos7.5以上poenssl和openssh升级

    2020年12月09日,360CERT监测发现  openssl  发布了  openssl 拒绝服务漏洞  的风险通告,该漏洞编号为  CVE-2020-1971  ,漏洞等级:高危 ,漏洞评分:7 ...

  3. APIO2020 交换城市

    我是真的不稳定的垃圾选手. 对于一张图来说,两个人能满足题面关系等价于这张图不是链,很好证明,如果有度数 \(> 2\) 的点,让一个人跑到一个度数 \(= 1\) 的地方就可以了. 如果离线就 ...

  4. 题解-CF348E Pilgrims

    题面 CF348E Pilgrims 有一棵 \(n\) 个点的 带权 树和 \(m\) 个关键点,要求杀了一个不关键的点,满足最多的关键点到离它最远的所有关键点的路径都被打断.求可以满足的最多关键点 ...

  5. 【题解】「SP867」 CUBES - Perfect Cubes

    这道题明显是一道暴力. 暴力枚举每一个 \(a, b, c, d\) 所以我就写了一个暴力.每个 \(a, b, c, d\) 都从 \(1\) 枚举到 \(100\) #include<ios ...

  6. 题解 P5401 [CTS2019]珍珠

    蒟蒻语 这题太玄学了,蒟蒻写篇题解来让之后复习 = = 蒟蒻解 假设第 \(i\) 个颜色有 \(cnt_i\) 个珍珠. \(\sum\limits_{i=1}^{n} \left\lfloor\f ...

  7. 【Django admin 中文配置】

    打开settings.py文件,找到语言编码.时区的设置项,将内容改为如下: [其中 zh-Hans是简体中文 zh-Hant是繁体中文] LANGUAGE_CODE = 'zh-Hans' # LA ...

  8. GET和POST的区别与联系

    每日知识-GET和POST HTTP:超文本传输协议 组成部分:请求行,请求头部,一个空行,请求数据 GET和POST GET:get就是获取的意思,默认的HTTP请求方式,把参数通过 key/val ...

  9. 01-flask-helloWorld

    代码 from flask import Flask # 创建Flask对象 app = Flask(__name__) # 定义路由 @app.route('/') def index(): # 函 ...

  10. html 09-HTML5详解(三)

    09-HTML5详解(三) #Web 存储 随着互联网的快速发展,基于网页的应用越来越普遍,同时也变的越来越复杂,为了满足各种各样的需求,会经常性在本地存储大量的数据,传统方式我们以document. ...