• 基于CefSharp开发浏览器(七)浏览器收藏夹菜单


    一、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="&#xe646;" 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="&#xe659;"/>
                            <Button Style="{DynamicResource Button.FontButton}" Content="&#xe652;"/>
                            <Button Style="{DynamicResource Button.FontButton}" Content="&#xe65c;"/>
                            <Button Style="{DynamicResource Button.FontButton}" Content="..." Padding="0,0,0,12"/>
                            <Button Style="{DynamicResource Button.FontButton}" Content="&#xe6b9;"/>
                        </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="&#xe600;"/>
                <controls:MMenuItem Tag="1" Header="在新建窗口中全部打开(16个)" Icon="&#xe602;"/>
                <controls:MMenuItem Tag="2" Header="在新 InPrivate窗口全部打开(16个)" Icon="&#xe68c;"/>
                <controls:MMenuItem Tag="4" Header="按名称排序" Icon="&#xe606;"/>
                <controls:MMenuItem Tag="5" Header="重命名" Icon="&#xe712;"/>
                <controls:MMenuItem Tag="5" Header="删除" Icon="&#xe74e;" IconFontSize="26"/>
                <controls:MMenuItem Tag="5" Header="将当前标签页添加到文件夹" Icon="&#xe659;"/>
                <controls:MMenuItem Tag="5" Header="将所有标签页添加到文件夹"/>
                <controls:MMenuItem Tag="5" Header="添加文件夹" Icon="&#xe652;"/>
            </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

  • 相关阅读:
    填坑总结:python内存泄漏排查小技巧
    springMVC注解中@RequestMapping中常用参数value params 以及@RequestParam 详解
    springMVC 自定义类型转换器
    为什么Java需要lambda 表达式? 上帝爱吃苹果
    利器| Cypress 强大全新的 Web UI 测试框架应用尝鲜
    缺少锻炼面试的机会?城市群之北上广杭一起来了!
    实战 | 基于JMeter 完成典型电商场景(首页浏览)的性能压测
    一文搞定 pytest 自动化测试框架(一)
    测试面试 | Java 经典面试题汇总
    软件测试工程师成长痛点和职业发展建议
  • 原文地址:https://www.cnblogs.com/mchao/p/14237923.html
Copyright © 2020-2023  润新知