VSTO/C# Word开发,定位到书签、段落、Range
在VSTO中开发时,因要对导航栏进行定制,因此重新开发了一个导航栏容器,用于展示文档大纲结构,构建的根据是每个段落的大纲级别。构建方法如下:/// <summary>/// 创建左侧大纲树/// </summary>public static void CreateNavigator()...
·
在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()可以选中,但是跳转的效果比较僵硬。却也是最简单的跳转方法。
至于像书签窗口那样定位到某一书签那样的顺畅的滑动效果,现在还没有发现怎么实现。
更多推荐
已为社区贡献2条内容
所有评论(0)