如何从 WPF RichTextBox 获取所有编号列表

How to get all numbered lists from WPF RichTextBox

问题:从上面WPF RichTextBox,我们如何通过编程得到上面显示的列表?

详细信息:使用以下 Extension Methods, I can read all paragraphs from the above RichTextBox. The btnTest_Click(...) event (code shown below) returns all the paragraphs as follows (as you may have guessed from the above image), each line is a paragraph. But below code does not tell me which object (element) is a list

btnTest_Click(...) 事件的输出:

This is a test.
Following is a list number:
1.   Item 1
2.   Item 2
3.   Item 3
End of test.

MainWindow.xaml:

.....
   <RichTextBox x:Name="rtbTest" AcceptsTab="True" FontFamily="Calibri"/>
.....

File1.cs:

using System.Windows.Documents;
using System.Linq;

namespace MyProjectName
{
    public static class FlowDocumentExtensions
    {
        public static IEnumerable<Paragraph> Paragraphs(this FlowDocument doc)
        {
            return doc.Descendants().OfType<Paragraph>();
        }
    }
}

File2.cs:

using System.Windows;
using System.Linq;

namespace MyProjectName
{
    public static class DependencyObjectExtensions
    {
        public static IEnumerable<DependencyObject> Descendants(this DependencyObject root)
        {
            if (root == null)
                yield break;
            yield return root;
            foreach (var child in LogicalTreeHelper.GetChildren(root).OfType<DependencyObject>())
                foreach (var descendent in child.Descendants())
                    yield return descendent;
        }
    }
}

读取所有段落的代码:

private void btnTest_Click(object sender, RoutedEventArgs e)
{
    foreach (Paragraph paragraph in rtbTest.Document.Paragraphs())
    {
        System.System.Diagnostics.Debug.WriteLine(new TextRange(paragraph.ContentStart, paragraph.ContentEnd).Text);
    }
}

要枚举 RichTextBox select 中的所有列表项,所有 System.Windows.Documents.List 来自 RichTextBox.Document.Blocks 集合:

MainWindow.xaml:

<Window ...
        Title="MainWindow" Height="350" Width="500">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>            
        </Grid.RowDefinitions>
        <RichTextBox Grid.Row="0" x:Name="rtb" AllowDrop="True" VerticalScrollBarVisibility="Auto" Padding="2">
            <FlowDocument>
                <Paragraph>
                    <Run Text="First list:"/>
                </Paragraph>
                <List MarkerStyle="Decimal">
                    <ListItem>
                        <Paragraph>C++</Paragraph>
                    </ListItem>
                    <ListItem>
                        <Paragraph>C#</Paragraph>
                        <List MarkerStyle="LowerLatin">
                            <ListItem>
                                <Paragraph>v 7.0</Paragraph>
                            </ListItem>
                            <ListItem>
                                <Paragraph>v 8.0</Paragraph>
                            </ListItem>
                        </List>
                    </ListItem>
                </List>
                <Paragraph>
                    <Run Text="Second list:"/>
                </Paragraph>
                <List MarkerStyle="Decimal">
                    <ListItem>
                        <Paragraph>Perl</Paragraph>
                    </ListItem>
                    <ListItem>
                        <Paragraph>Logo</Paragraph>
                    </ListItem>
                </List>
            </FlowDocument>
        </RichTextBox>
        <Button Grid.Row="1" Click="EnumerateList">Enumerate List</Button>
    </Grid>
</Window>

MainWindow.xaml.cs:

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void EnumerateList(object sender, RoutedEventArgs e)
    {
         var lists = rtb.EnumerateLists();
         for (int i=0; i < lists.Count; i++)
         {
             System.Diagnostics.Debug.WriteLine("\nList " + (i+1));
             foreach (var litem in lists[i])
             {
                TextRange range = new TextRange(litem.ElementStart, litem.ElementEnd);
                System.Diagnostics.Debug.WriteLine(range.Text);
             }
         }
    }
}

public static class RichTextBoxExt
{
    public static List<List<ListItem>> EnumerateLists(this RichTextBox rtb)
    {
        var result = new List<List<ListItem>>();
        foreach (var block in rtb.Document.Blocks)
        {
            if (block is List list && list.ListItems.Count > 0)
            {
                //var marker = list.MarkerStyle;
                result.Add(list.EnumerateList().ToList());
            }
        }
        return result;
    } 

    private static IEnumerable<ListItem> EnumerateList(this System.Windows.Documents.List list)
    {
        foreach (var litem in list.ListItems) yield return litem;            
    }    
}

上面的代码产生以下输出:

List 1
1.  C++
2.  C#
a.  v 7.0
b.  v 8.0

List 2
1.  Perl
2.  Logo

注意:

If some list item has subitems (SiblingListItems), in example above the range.Text will contain "2.\tC#\r\na.\tv 7.0\r\nb.\tv 8.0". That means all sibling list items included to the text of parent item but separated by \r\n.