WPF/C#实现支持层级跳级的Markdown Document Map方案咨询
WPF Markdown文档大纲控件实现方案
你最初选用TreeView的思路完全可行,所谓“不支持层级跳级”是节点构建逻辑的问题,和控件本身无关,以下是两种经过验证的落地方案:
方案1:基于原生TreeView实现(推荐,开发成本最低)
TreeView本身对任意层级的嵌套节点没有任何限制,不需要标题层级连续,核心是用栈逻辑正确构建节点树,不需要自己重写任何交互逻辑:
- 第一步:解析标题生成扁平数据
扫描文本时,逐行匹配/+#+开头的行,过滤掉代码块内的匹配项,为每个识别到的标题生成数据模型,包含核心字段:Level:整数,对应标题层级,即行首#的数量(比如/####对应Level=4)Title:字符串,标题的文本内容Children:可观察集合,用于存储当前节点的子节点- (可选)
LineNumber:整数,对应标题在原文本中的行号,用于实现点击跳转定位
- 第二步:用栈逻辑构建层级树(核心,支持任意层级跳转)
初始化一个存储(节点层级, 节点对象)的栈,先压入一个Level=0的虚拟根节点,之后逐一遍历所有解析出的标题:- 循环判断:如果栈顶节点的Level >= 当前标题的Level,弹出栈顶节点,直到栈顶节点的Level < 当前标题的Level
- 此时栈顶节点就是当前标题的直接父节点,将当前标题加入父节点的
Children集合 - 将当前标题的
(Level, 节点对象)压入栈
以你给出的示例文本为例,构建过程完全不需要特殊处理层级跳变:
遍历流程: 1. # animals(Level=1):栈顶为虚拟根Level=0 <1,挂到根节点下,压栈 → 栈[0,1] 2. #### little ones(Level=4):栈顶Level=1 <4,挂到animals节点下,压栈 → 栈[0,1,4] 3. #### big ones(Level=4):栈顶Level=4 >=4,弹出后栈顶Level=1 <4,挂到animals节点下,压栈 → 栈[0,1,4] 4. ### amibas(Level=3):栈顶Level=4 >=3弹出,栈顶Level=1 <3,挂到animals节点下,压栈 → 栈[0,1,3] 5. ### squirrels(Level=3):栈顶Level=3 >=3弹出,栈顶Level=1 <3,挂到animals节点下,压栈 → 栈[0,1,3] 6. #### flying ones(Level=4):栈顶Level=3 <4,挂到squirrels节点下,压栈 → 栈[0,1,3,4] 7. # rocks(Level=1):依次弹出4、3、1,栈顶回到虚拟根Level=0 <1,挂到根节点下,压栈 → 栈[0,1] 8. ## shiny rocks(Level=2):栈顶Level=1 <2,挂到rocks节点下,压栈 → 栈[0,1,2] - UI绑定
直接给TreeView配置HierarchicalDataTemplate,绑定Children作为子节点源,给不同层级的标题配置对应缩进、字体大小样式即可。原生控件自带展开折叠、键盘导航、选中高亮能力,大文档场景下开启VirtualizingStackPanel.IsVirtualizing="True"即可流畅运行,不需要额外写代码。
方案2:基于带缩进的ListBox实现(适合高定制化UI场景)
你提到的ListBox方案完全可落地,适合不想受TreeView默认样式限制、需要做扁平风格大纲的场景,实现逻辑如下:
- 同样先解析生成扁平的标题数据模型,每个模型额外增加
IsExpanded(当前节点是否展开)、IsVisible(当前节点是否在列表中显示)、AncestorLevels(存储当前节点所有祖先节点的层级,用于快速判断可见性)三个字段 - 列表项的左侧Margin绑定
Level * 固定缩进值(通常取16px)模拟层级缩进,Visibility属性绑定IsVisible,通过布尔值转可见性转换器自动控制显示隐藏 - 展开折叠逻辑:点击节点的展开按钮时切换自身
IsExpanded状态,之后遍历当前节点之后的所有列表项:只要该项的任意一个祖先节点处于折叠状态,就将其IsVisible设为false,否则设为true - 该方案的缺点是键盘导航(左右键展开折叠、上下键切换选中)、虚拟化下的滚动定位都需要自行实现,开发量比TreeView方案高。
注意事项:解析标题时一定要维护代码块标记位,遇到
```包裹的代码块段时跳过标题匹配,否则代码块内的同格式文本会被误识别为标题,导致大纲结构错乱。
内容的提问来源于stack exchange,提问作者pico
相关产品推荐
相关产品推荐

