在VSTO中开发时,因要对导航栏进行定制,因此重新开发了一个导航栏容器,用于展示文档大纲结构,构建的根据是每个段落的大纲级别。
构建方法如下:

        /// <summary>
        /// 创建左侧大纲树
        /// </summary>
        public static void CreateNavigator()
        {
            var m_Doc = ActiveDocument;
            var Paragraphs = m_Doc.Paragraphs;

            var m_DicTree = new Dictionary<TreeCustomItem, List<Paragraph>>();

            var DicLevelAndLastNode = new Dictionary<int, TreeCustomItem>();
            var lstTree = new ObservableCollection<TreeCustomItem>();

            var RootNode = new TreeCustomItem
            {
                IsExpand = false,
                //IsExpand = true,
            };

            var NavNode = new TreeCustomItem()
            {
                ID = (int)EnumNavPaneTreeID.TreeItemNav,
                Header = "大纲",
                Parent = RootNode,
                IsExpand = false,
                //IsExpand = true,
            };
            RootNode.Childrens.Add(NavNode);

            DicLevelAndLastNode.Add(0, NavNode);

            var m_Last = RootNode;
            //最上层节点与开头段落
            m_DicTree.Add(m_Last, new List<Paragraph>());

            foreach (Paragraph pg in Paragraphs)
            {
                var lstBms = pg.Range.Bookmarks;
                var lstFwSq = new List<PRJ_BGDZSQ>();
                for (int i = 1; i <= lstBms.Count; i++)
                {
                    var x = lstBms.get_Item(i);
                    var sq = PRJ_BGDZSQ.Singleton.SelectByCondition(
                        $"{PRJ_BGDZSQ.FIELD_SQMC} = '{x.Name}' and {PRJ_BGDZSQ.FIELD_BGMBID} = '{CurMb.BSM}' and {PRJ_BGDZSQ.FIELD_SQLX} = '{EnumSQLX.范围书签.ToString()}'");
                    if (sq.Count > 0)
                    {
                        lstFwSq.AddRange(sq);
                    }
                }

                var Level = pg.OutlineLevel;
                //如果是正文
                if (Level == WdOutlineLevel.wdOutlineLevelBodyText)
                {
                    //正文段落挂在节点下
                    m_DicTree[m_Last].Add(pg);
                    //有范围书签则要找到正文的上一级
                    if (lstFwSq.Count > 0)
                    {
                        lstFwSq.ForEach(x =>
                        {
                            var header = string.Empty;
                            var frontStr = string.Empty;
                            var rearStr = string.Empty;
                            var range = m_Doc.Bookmarks[x.SQMC].Range;
                            try
                            {
                                frontStr = m_Doc.Range(range.Start, range.Start + RpCore.SQQHWBCD).Text;
                                rearStr = m_Doc.Range(range.End - RpCore.SQQHWBCD,
                                    range.End).Text;
                            }
                            catch (Exception ex)
                            {
                                var str = ex.Message;
                                //ignore
                            }

                            header = frontStr + "......" + rearStr;
                            var fwNode = new TreeCustomItem
                            {
                                ID = (int)EnumNavPaneTreeID.TreeItemFwsq,
                                Header = header,
                                Tag = x,
                                IsExpand = false,
                                Foreground = "Red",
                                Parent = m_Last,
                                Childrens = new ObservableCollection<TreeCustomItem>(),
                            };
                            fwNode.Childrens.Add(new TreeCustomItem
                            {
                                ID = (int)EnumNavPaneTreeID.TreeItemDxsq,
                                Parent = fwNode,
                                IsExpand = false
                            });
                            //fwNode.ExpandedTreeItem -= FwNodeOnExpandedTreeItem;
                            //fwNode.ExpandedTreeItem += FwNodeOnExpandedTreeItem;
                            if (m_Last.Childrens.Count(t =>
                            {
                                var sq = t.Tag as PRJ_BGDZSQ;
                                if (sq == null) return false;
                                if (x.BSM.Equals(sq.BSM)) return true;
                                return false;
                            }) <= 0)
                                m_Last.Childrens.Add(fwNode);
                        });
                    }

                    continue;
                }

                //如果此级的上一级不存在
                else if (!DicLevelAndLastNode.ContainsKey((int)Level - 1))
                {
                    m_DicTree = null;
                    TreeNav.BindingTreeViewData(null);
                    WaitHelper.CloseWaiting();
                    ControlUtility.ShowWarningMsgbox($"本文档大纲级别存在跳级错误,如一级大纲下为三级、四级大纲,无法生成导航栏,建议检查文档");
                    return;
                }

                //当前级别的上一级别的最后一个节点,也就是此级别的上级节点
                var ParentNode = DicLevelAndLastNode[(int)Level - 1];
                var Text = pg.Range.Text;
                if (Text.EndsWith("\r") || Text.EndsWith("\f"))
                    Text = Text.Substring(0, Text.Length - 1);
                if (Text.StartsWith("\r") || Text.StartsWith("\f"))
                    Text = Text.Substring(1, Text.Length - 1);
                //当不是正文时,新建节点,同时更新节点-段落字典
                var Node = new TreeCustomItem
                {
                    ID = (int)EnumNavPaneTreeID.TreeItemPg,
                    Tag = pg,
                    Header = Text,
                    IsExpand = false,
                    //IsExpand = true,
                    Parent = ParentNode,
                };
                m_Last = Node;
                if (lstFwSq.Count > 0)
                {
                    lstFwSq.ForEach(x =>
                    {
                        var header = string.Empty;
                        var frontStr = string.Empty;
                        var rearStr = string.Empty;
                        var range = m_Doc.Bookmarks[x.SQMC].Range;
                        try
                        {
                            frontStr = m_Doc.Range(range.Start, range.Start + RpCore.SQQHWBCD).Text;
                            rearStr = m_Doc.Range(range.End - RpCore.SQQHWBCD,
                                range.End).Text;
                        }
                        catch
                        {
                            //ignore
                        }

                        header = frontStr + "......" + rearStr;

                        var fwNode = new TreeCustomItem
                        {
                            ID = (int)EnumNavPaneTreeID.TreeItemFwsq,
                            Header = header,
                            Tag = x,
                            IsExpand = false,
                            Foreground = "Red",
                            Parent = m_Last,
                            Childrens = new ObservableCollection<TreeCustomItem>(),
                        };
                        fwNode.Childrens.Add(new TreeCustomItem
                        {
                            ID = (int)EnumNavPaneTreeID.TreeItemDxsq,
                            Parent = fwNode,
                            IsExpand = false
                        });
                        //fwNode.ExpandedTreeItem -= FwNodeOnExpandedTreeItem;
                        //fwNode.ExpandedTreeItem += FwNodeOnExpandedTreeItem;
                        m_Last.Childrens.Add(fwNode);
                    });
                }

                if (!m_DicTree.ContainsKey(m_Last))
                    m_DicTree.Add(m_Last, new List<Paragraph>());
                m_DicTree[m_Last].Add(pg);
                ParentNode.Childrens.Add(Node);

                if (!DicLevelAndLastNode.ContainsKey((int)Level))
                    DicLevelAndLastNode.Add((int)Level, Node);
                else
                    DicLevelAndLastNode[(int)Level] = Node;
            }

            TreeNav.BindingTreeViewData(RootNode.Childrens);
            //treeNav.ExpandedAll();
            TreeNav.ExpandItem(NavNode);
        }

构建好大纲树后,要对大纲书的节点订阅点击事件,其点击事件就是定位到具体的书签、段落。
定位方法如下:
1、最常用的Goto

        /// <summary>
        /// 定位到指定区域
        /// </summary>
        /// <param name="range">指定区域</param>
        public static void LocateTo(object page, object line)
        {
            if (page != null)
            {
                object goFunc = WdGoToDirection.wdGoToFirst;
                object goToPage = WdGoToItem.wdGoToPage;
                App.Selection.GoTo(ref goToPage, ref goFunc, ref page);
            }
            if (line != null)
            {
                object goToLine = WdGoToItem.wdGoToLine;
                object goNext = WdGoToDirection.wdGoToNext;
                App.Selection.GoTo(ref goToLine, ref goNext, ref line);
            }
        }

        /// <summary>
        /// 定位到区域
        /// </summary>
        /// <param name="range">区域
        /// </param>
        public static void LocateTo(Range range)
        {
            object goFunc = WdGoToDirection.wdGoToFirst;
            object goToPage = WdGoToItem.wdGoToPage;
            object pageNum = range.Information[WdInformation.wdActiveEndPageNumber];
            App.Selection.GoTo(ref goToPage, ref goFunc, ref pageNum);

            object goToLine = WdGoToItem.wdGoToLine;
            object goNext = WdGoToDirection.wdGoToNext;
            object lineNum = (int)range.Information[WdInformation.wdFirstCharacterLineNumber] - 1;
            App.Selection.GoTo(ref goToLine, ref goNext, ref lineNum);
        }

其中,WdGoToItem这个枚举类有很多可选类型,供开发者能定位到各种不同的地方,如:

public enum WdGoToItem
    {
        //
        // 摘要:
        //     A bookmark.
        wdGoToBookmark = -1,
        //
        // 摘要:
        //     A section.
        wdGoToSection = 0,
        //
        // 摘要:
        //     A page.
        wdGoToPage = 1,
        //
        // 摘要:
        //     A table.
        wdGoToTable = 2,
        //
        // 摘要:
        //     A line.
        wdGoToLine = 3,
        //
        // 摘要:
        //     A footnote.
        wdGoToFootnote = 4,
        //
        // 摘要:
        //     An endnote.
        wdGoToEndnote = 5,
        //
        // 摘要:
        //     A comment.
        wdGoToComment = 6,
        //
        // 摘要:
        //     A field.
        wdGoToField = 7,
        //
        // 摘要:
        //     A graphic.
        wdGoToGraphic = 8,
        //
        // 摘要:
        //     An object.
        wdGoToObject = 9,
        //
        // 摘要:
        //     An equation.
        wdGoToEquation = 10,
        //
        // 摘要:
        //     A heading.
        wdGoToHeading = 11,
        //
        // 摘要:
        //     A percent.
        wdGoToPercent = 12,
        //
        // 摘要:
        //     A spelling error.
        wdGoToSpellingError = 13,
        //
        // 摘要:
        //     A grammatical error.
        wdGoToGrammaticalError = 14,
        //
        // 摘要:
        //     A proofreading error.
        wdGoToProofreadingError = 15
    }

public enum WdGoToDirection
    {
        //
        // 摘要:
        //     The last instance of the specified object.
        wdGoToLast = -1,
        //
        // 摘要:
        //     The first instance of the specified object.
        wdGoToFirst = 1,
        //
        // 摘要:
        //     An absolute position.
        wdGoToAbsolute = 1,
        //
        // 摘要:
        //     The next instance of the specified object.
        wdGoToNext = 2,
        //
        // 摘要:
        //     A position relative to the current position.
        wdGoToRelative = 2,
        //
        // 摘要:
        //     The previous instance of the specified object.
        wdGoToPrevious = 3
    }

2、使用wdGotoBookmark:
每个段落都有Range对象,而且有大纲结构的,回自动产生一个_Toc 前缀命名的隐藏书签。根据段落找到Range,再遍历Range内的Bookmarks,就可以找到带有Toc 开头的书签,然后使用GoToBookmark定位过去。

                    if (bm.Name.ToUpper().StartsWith("_TOC"))
                    {
                        bm.Select();
                        //object what = WdGoToItem.wdGoToBookmark;
                        //object missing = System.Reflection.Missing.Value;
                        //object name = bm.Name;
                        //OfficeCore.App.Selection.GoTo(ref what, ref missing, ref missing, ref name);
                        hadGone = true;
                        break;
                    }

3、第三种方法最简单,但是效果不太好

pg.Range.Select();
bm.Range.Select();

直接Range.select()可以选中,但是跳转的效果比较僵硬。却也是最简单的跳转方法。

至于像书签窗口那样定位到某一书签那样的顺畅的滑动效果,现在还没有发现怎么实现。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐