【问题标题】:WPF TextBlock highlight certain parts based on search conditionWPF TextBlock 根据搜索条件突出显示某些部分
【发布时间】:2010-10-19 14:24:47
【问题描述】:

我有 TextBlock,其中动态添加了 Inlines(基本上是一堆斜体或粗体的 Run 对象)。

在我的应用程序中,我有搜索功能。

我希望能够突出显示正在搜索的 TextBlock 的文本。

突出显示是指更改 TextBlock 文本颜色的某些部分(请记住,它可能一次突出显示多个不同的 Run 对象)。

我试过这个例子http://blogs.microsoft.co.il/blogs/tamir/archive/2008/05/12/search-and-highlight-any-text-on-wpf-rendered-page.aspx

但它的接缝很不稳定:(

有没有简单的方法来解决这个问题?

【问题讨论】:

    标签: c# wpf highlight textblock


    【解决方案1】:

    这个问题类似于How to display search results in a WPF items control with highlighted query terms

    为了回答这个问题,我想出了一种使用 IValueConverter 的方法。转换器采用文本 sn-p,将其格式化为有效的 XAML 标记,并使用 XamlReader 将标记实例化为框架对象。

    完整的解释比较长,所以我把它发布到我的博客:Highlighting Query Terms in a WPF TextBlock

    【讨论】:

      【解决方案2】:

      我接受了dthrasers answer 并消除了对 XML 解析器的需求。他很好地解释了his blog 中的每一个部分,但这并不需要我添加任何额外的库,我就是这样做的。

      第一步,制作一个转换器类:

      class StringToXamlConverter : IValueConverter
      {
      
          public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
          {
              string input = value as string;
              if (input != null)
              {
                  var textBlock = new TextBlock();
                  textBlock.TextWrapping = TextWrapping.Wrap;
                  string escapedXml = SecurityElement.Escape(input);
      
                  while (escapedXml.IndexOf("|~S~|") != -1) {
                  //up to |~S~| is normal
                  textBlock.Inlines.Add(new Run(escapedXml.Substring(0, escapedXml.IndexOf("|~S~|"))));
                  //between |~S~| and |~E~| is highlighted
                  textBlock.Inlines.Add(new Run(escapedXml.Substring(escapedXml.IndexOf("|~S~|") + 5,
                                            escapedXml.IndexOf("|~E~|") - (escapedXml.IndexOf("|~S~|") + 5))) 
                                            { FontWeight = FontWeights.Bold, Background= Brushes.Yellow });
                  //the rest of the string (after the |~E~|)
                  escapedXml = escapedXml.Substring(escapedXml.IndexOf("|~E~|") + 5);
                  }
      
                  if (escapedXml.Length > 0)
                  {
                      textBlock.Inlines.Add(new Run(escapedXml));                      
                  }
                  return textBlock;
              }
      
              return null;
          }
      
          public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
          {
              throw new NotImplementedException("This converter cannot be used in two-way binding.");
          }
      
      }
      

      第二步: 使用 ContentBlock 代替 TextBlock。将字符串(您将用于 textBlock)传递给内容块,如下所示:

      <ContentControl Margin="7,0,0,0"
                      HorizontalAlignment="Left"
                      VerticalAlignment="Center"
                      Content="{Binding Description, Converter={StaticResource CONVERTERS_StringToXaml}, Mode=OneTime}">
      </ContentControl>
      

      第三步: 确保您传递的文本在要突出显示的文本部分之前包含|~S~|,在您想要突出显示的文本部分之后包含|~E~|。例如,在这个字符串"my text |~S~|is|~E~| good" 中,is 将以黄色突出显示。

      注意事项:
      您可以在运行中更改样式以确定突出显示文本的内容和方式
      确保将 Converter 类添加到命名空间和资源中。这可能还需要重建才能开始工作。

      【讨论】:

        【解决方案3】:

        与其他解决方案的区别

        • 更易于重用 -> 附加行为而不是自定义控件
        • MVVM 友好 -> 没有代码隐藏
        • 双向工作! -> 更改要突出显示的术语或文本,两者都会更新文本块中的突出显示。我检查的其他解决方案存在问题,即更改文本不会重新应用突出显示。只有更改突出显示的术语/搜索文本才有效。

        如何使用

        • 重要提示:不要再使用 TextBlock 的常规 Text="blabla" 属性。而是将您的文本绑定到 HighlightTermBehavior.Text="blabla"
        • 像这样将附加属性添加到您的 TextBlock 中
        <TextBlock local:HighlightTermBehavior.TermToBeHighlighted="{Binding MyTerm}"
                   local:HighlightTermBehavior.Text="{Binding MyText}" />
        

        或硬编码

        <TextBlock local:HighlightTermBehavior.TermToBeHighlighted="highlight this"
                   local:HighlightTermBehavior.Text="bla highlight this bla" />
        

        添加此类

        • 要更改突出显示的类型,只需更改以下方法:
          AddPartToTextBlock() 用于未突出显示的文本
          AddHighlightedPartToTextBlock() 用于突出显示的文本。
        • 目前突出显示为FontWeights.ExtraBold,未突出显示的文本为FontWeights.Light
        • 如果没有 IDE,可能很难阅读,抱歉。
        public static class HighlightTermBehavior
        {
            public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached(
                "Text",
                typeof(string),
                typeof(HighlightTermBehavior),
                new FrameworkPropertyMetadata("", OnTextChanged));
        
            public static string GetText(FrameworkElement frameworkElement)               => (string) frameworkElement.GetValue(TextProperty);
            public static void   SetText(FrameworkElement frameworkElement, string value) => frameworkElement.SetValue(TextProperty, value);
        
        
            public static readonly DependencyProperty TermToBeHighlightedProperty = DependencyProperty.RegisterAttached(
                "TermToBeHighlighted",
                typeof(string),
                typeof(HighlightTermBehavior),
                new FrameworkPropertyMetadata("", OnTextChanged));
        
            public static string GetTermToBeHighlighted(FrameworkElement frameworkElement)
            {
                return (string) frameworkElement.GetValue(TermToBeHighlightedProperty);
            }
        
            public static void SetTermToBeHighlighted(FrameworkElement frameworkElement, string value)
            {
                frameworkElement.SetValue(TermToBeHighlightedProperty, value);
            }
        
        
            private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                if (d is TextBlock textBlock)
                    SetTextBlockTextAndHighlightTerm(textBlock, GetText(textBlock), GetTermToBeHighlighted(textBlock));
            }
        
            private static void SetTextBlockTextAndHighlightTerm(TextBlock textBlock, string text, string termToBeHighlighted)
            {
                textBlock.Text = string.Empty;
        
                if (TextIsEmpty(text))
                    return;
        
                if (TextIsNotContainingTermToBeHighlighted(text, termToBeHighlighted))
                {
                    AddPartToTextBlock(textBlock, text);
                    return;
                }
        
                var textParts = SplitTextIntoTermAndNotTermParts(text, termToBeHighlighted);
        
                foreach (var textPart in textParts)
                    AddPartToTextBlockAndHighlightIfNecessary(textBlock, termToBeHighlighted, textPart);
            }
        
            private static bool TextIsEmpty(string text)
            {
                return text.Length == 0;
            }
        
            private static bool TextIsNotContainingTermToBeHighlighted(string text, string termToBeHighlighted)
            {
                return text.Contains(termToBeHighlighted, StringComparison.Ordinal) == false;
            }
        
            private static void AddPartToTextBlockAndHighlightIfNecessary(TextBlock textBlock, string termToBeHighlighted, string textPart)
            {
                if (textPart == termToBeHighlighted)
                    AddHighlightedPartToTextBlock(textBlock, textPart);
                else
                    AddPartToTextBlock(textBlock, textPart);
            }
        
            private static void AddPartToTextBlock(TextBlock textBlock, string part)
            {
                textBlock.Inlines.Add(new Run {Text = part, FontWeight = FontWeights.Light});
            }
        
            private static void AddHighlightedPartToTextBlock(TextBlock textBlock, string part)
            {
                textBlock.Inlines.Add(new Run {Text = part, FontWeight = FontWeights.ExtraBold});
            }
        
        
            public static List<string> SplitTextIntoTermAndNotTermParts(string text, string term)
            {
                if (text.IsNullOrEmpty())
                    return new List<string>() {string.Empty};
        
                return Regex.Split(text, $@"({Regex.Escape(term)})")
                            .Where(p => p != string.Empty)
                            .ToList();
            }
        }
        

        【讨论】:

        • 我只是将 Regex.Split 更改为忽略大小写 Regex.Split(text, $@"({Regex.Escape(term)})", RegexOptions.IgnoreCase)
        • @shmoltz 要不区分大小写,您还需要更改 TextIsNotContainingTermToBeHighlightedAddPartToTextBlockAndHighlightIfNecessary 以比较不区分大小写。
        【解决方案4】:

        巧合的是,我最近写了一篇文章来解决同样的问题。它是一个自定义控件,具有与 TextBlock 相同的属性(因此您可以在任何需要它的地方交换 TextBlock),并且它有一个额外的属性,您可以绑定到称为 HighLightText,并且无论在哪里HighLightText 的值在 Text 主属性中找到(不区分大小写),它被突出显示。

        这是一个相当简单的控件,您可以在这里找到这篇文章:

        WPF TextBlock With Search String Matching

        这里有完整的代码作为解决方案:

        SearchMatchTextblock(GitHub)

        【讨论】:

        • 您提供的链接已失效。
        • 他们还没死。
        • 这篇文章已经不在了,但是源代码是——上面的 github 链接可以正常工作
        • 干得好!具有出色性能的便捷控制。
        【解决方案5】:

        这是我通过构建现有的 TextBlock 并添加一个名为 SearchText 的新依赖属性得出的结论:

        public class SearchHightlightTextBlock : TextBlock
        {
            public SearchHightlightTextBlock() : base() { }
        
            public String SearchText { get { return (String)GetValue(SearchTextProperty); }
                                       set { SetValue(SearchTextProperty, value); } }      
        
            private static void OnDataChanged(DependencyObject source,
                                              DependencyPropertyChangedEventArgs e)
            {
                TextBlock tb = (TextBlock)source;
        
                if (tb.Text.Length == 0)
                    return;
        
                string textUpper = tb.Text.ToUpper();
                String toFind = ((String) e.NewValue).ToUpper();
                int firstIndex = textUpper.IndexOf(toFind);
                String firstStr = tb.Text.Substring(0, firstIndex);
                String foundStr = tb.Text.Substring(firstIndex, toFind.Length);
                String endStr = tb.Text.Substring(firstIndex + toFind.Length, 
                                                 tb.Text.Length - (firstIndex + toFind.Length));
        
                tb.Inlines.Clear();
                var run = new Run();
                run.Text = firstStr;
                tb.Inlines.Add(run);
                run = new Run();
                run.Background = Brushes.Yellow;
                run.Text = foundStr;
                tb.Inlines.Add(run);
                run = new Run();
                run.Text = endStr;
        
                tb.Inlines.Add(run);
            }
        
            public static readonly DependencyProperty SearchTextProperty =
                DependencyProperty.Register("SearchText", 
                                            typeof(String), 
                                            typeof(SearchHightlightTextBlock), 
                                            new FrameworkPropertyMetadata(null, OnDataChanged));
        }
        

        在你看来,这是:

        <view:SearchHightlightTextBlock SearchText="{Binding TextPropertyContainingTextToSearch}" 
                                        Text="{Binding YourTextProperty}"/>
        

        【讨论】:

          【解决方案6】:

          我遇到了类似的问题 - 尝试对基本上代表报告的大量演示者实施文本搜索。该报告最初被写入一个字符串,我们利用 FlowDocumentViewer 的内置 ctrl-F - 它不是很好,有一些奇怪的选项,但已经足够了。

          如果您只是想要类似的东西,您可以执行以下操作:

                  <FlowDocumentScrollViewer>
                      <FlowDocument>
                          <Paragraph FontFamily="Lucida Console" FontSize="12">
                              <Run Text="{Binding Content, Mode=OneWay}"/>
                          </Paragraph>
                      </FlowDocument>
                  </FlowDocumentScrollViewer>
          

          我们决定进行重写,因为报告与程序的其余部分保持同步,并且基本上每次编辑都会更改它,每次都必须重新创建整个报告意味着这很慢。我们想通过迁移到你需要的更新位模型来改进这一点,但需要有视图模型(而不仅仅是一个字符串)才能以一种理智的方式做到这一点!然而,我们希望在换出报告之前保留搜索功能,并且做得更好,并以一种颜色突出显示“当前”搜索位置,并以另一种颜色突出显示其他搜索命中。

          这是我的解决方案的简化版本;一个派生自 TextBlock 的类,它添加了一个类型为 HighlightingInformation 的依赖属性。我没有包含命名空间和使用,因为它们很敏感。

          public class HighlightingTextBlock : TextBlock
          {
              public static readonly DependencyProperty HighlightingProperty =
                  DependencyProperty.Register("Highlighting", typeof (HighlightingInformation), typeof (HighlightingTextBlock));
          
              public HighlightingInformation Highlighting
              {
                  get { return (HighlightingInformation)GetValue(HighlightingProperty); }
                  set { SetValue(HighlightingProperty, value); }
              }
          
              public HighlightingTextBlock()
              {
                  AddValueChangedCallBackTo(HighlightingProperty, UpdateText);
              }
          
              private void AddValueChangedCallBackTo(DependencyProperty property, Action updateAction)
              {
                  var descriptor = DescriptorFor(property);
                  descriptor.AddValueChanged(this, (src, args) => updateAction());
              }
          
              private DependencyPropertyDescriptor DescriptorFor(DependencyProperty property)
              {
                  return DependencyPropertyDescriptor.FromProperty(property, GetType());
              }
          
              private void UpdateText()
              {
                  var highlighting = Highlighting;
                  if (highlighting == null)
                      return;
                  highlighting.SetUpdateMethod(UpdateText);
          
                  var runs = highlighting.Runs;
                  Inlines.Clear();
                  Inlines.AddRange(runs);
              }
          }
          

          该类可以绑定的类型在其文本和高亮列表发生变化时使用update方法来更新Runs列表。亮点本身看起来像这样:

          public class Highlight
          {
              private readonly int _length;
              private readonly Brush _colour;
          
              public int Start { get; private set; }
          
              public Highlight(int start, int length,Brush colour)
              {
                  Start = start;
                  _length = length;
                  _colour = colour;
              }
          
              private string TextFrom(string currentText)
              {
                  return currentText.Substring(Start, _length);
              }
          
              public Run RunFrom(string currentText)
              {
                  return new Run(TextFrom(currentText)){Background = _colour};
              }
          }
          

          要生成正确的高光集合是一个单独的问题,我基本上通过将演示者集合视为递归搜索内容的树来解决这个问题 - 叶节点是具有内容的节点,而其他节点只有子节点。如果您搜索深度优先,您会得到您期望的顺序。然后,您基本上可以在结果列表周围编写一个包装器来跟踪位置。我不会为此发布所有代码 - 我在这里的回应是记录如何使 wpf 以 MVP 样式进行多色突出显示。

          我没有在这里使用INotifyPropertyChangedCollectionChanged,因为我们不需要多播更改(例如,一个演示者有多个视图)。最初,我尝试通过为 Text 添加一个事件更改通知和一个为列表添加一个事件更改通知(您还必须手动订阅 INotifyCollectionChanged 事件)。然而,我担心事件订阅会导致内存泄漏,而且文本和高亮显示的更新没有同时进行这一事实造成了问题。

          这种方法的一个缺点是人们不应该绑定到该控件的 Text 属性。在真实版本中,我添加了一些检查+异常抛出来阻止人们这样做,但为了清楚起见,在示例中省略了它!

          【讨论】:

            【解决方案7】:

            在这里,我提出了另一种突出显示文本的方法。我有一个用例,我需要在 WPF 中装饰一堆 C# 代码,但是我不想使用 textBlock.Inlines.Add 类型的语法,而是想动态生成突出显示的 XAML,然后动态添加它到 WPF 中的 Canvas 或其他容器。

            所以假设你想给下面这段代码着色并突出它的一部分:

            public static void TestLoop(int count)
            { 
               for(int i=0;i<count;i++)
                 Console.WriteLine(i);
            }
            

            假设上述代码位于名为 Test.txt 的文件中。 假设您要将所有 C# 关键字(public、static、void 等)和简单类型(int、string)用蓝色着色,Console.WriteLine 用黄色突出显示。

            步骤 0. 创建一个新的 WPF 应用程序并在名为 Test.txt 的文件中包含一些类似于上面的示例代码

            步骤 1. 创建代码高亮类:

            using System.IO;
            using System.Text;
            
            public enum HighLightType
            {
                Type = 0,
                Keyword = 1,
                CustomTerm = 2
            }
            
            public class CodeHighlighter
            {
                public static string[] KeyWords = { "public", "static", "void", "return", "while", "for", "if" };
                public static string[] Types = { "string", "int", "double", "long" };
            
                private string FormatCodeInXaml(string code, bool withLineBreak)
                {
                    string[] mapAr = { "<","&lt;" , //Replace less than sign
                                        ">","&gt;" }; //Replace greater than sign
                    StringBuilder sb = new StringBuilder();
            
                    using (StreamReader sr = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(code))))
                    {
                        while (!sr.EndOfStream)
                        {
                            string line = sr.ReadLine();
            
                            line = line.Replace("\t", "&#160;&#160;&#160;&#160;"); //Replace tabs
                            line = line.Replace(" ", "&#160;"); //Replace spaces
            
                            for (int i = 0; i < mapAr.Length; i += 2)
                                line = line.Replace(mapAr[i], mapAr[i + 1]);
            
                            if (withLineBreak)
                                sb.AppendLine(line + "<LineBreak/>"); //Replace line breaks
                            else
                                sb.AppendLine(line);
                        }
            
                    }
                    return sb.ToString();
                }
            
            
                private string BuildForegroundTag(string highlightText, string color)
                {
                    return "<Span Foreground=\"" + color + "\">" + highlightText + "</Span>";
                }
            
                private string BuildBackgroundTag(string highlightText, string color)
                {
                    return "<Span Background=\"" + color + "\">" + highlightText + "</Span>";
                }
            
                private string HighlightTerm(HighLightType type, string term, string line)
                {
                    if (term == string.Empty)
                        return line;
            
                    string keywordColor = "Blue";
                    string typeColor = "Blue";
                    string statementColor = "Yellow";
            
                    if (type == HighLightType.Type)
                        return line.Replace(term, BuildForegroundTag(term, typeColor));
                    if (type == HighLightType.Keyword)
                        return line.Replace(term, BuildForegroundTag(term, keywordColor));
                    if (type == HighLightType.CustomTerm)
                        return line.Replace(term, BuildBackgroundTag(term, statementColor));
            
                    return line;
                }
            
                public string ApplyHighlights(string code, string customTerm)
                {
                    code = FormatCodeInXaml(code, true);
                    customTerm = FormatCodeInXaml(customTerm, false).Trim();
            
                    StringBuilder sb = new StringBuilder();
                    using (StreamReader sr = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(code))))
                    {
                        while (!sr.EndOfStream)
                        {
                            string line = sr.ReadLine();
            
                            line = HighlightTerm(HighLightType.CustomTerm, customTerm, line);
            
                            foreach (string keyWord in KeyWords)
                                line = HighlightTerm(HighLightType.Keyword, keyWord, line);
            
                            foreach (string type in Types)
                                line = HighlightTerm(HighLightType.Type, type, line);
            
                            sb.AppendLine(line);
                        }
                    }
            
                    return sb.ToString();
            
                }
            }
            

            步骤 2. 将 Canvas XAML 标记添加到 MainWindow.xaml

            <Window x:Class="TestCodeVisualizer.MainWindow"
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                    xmlns:local="clr-namespace:TestCodeVisualizer"
                    mc:Ignorable="d"
                    Title="MainWindow" Height="350" Width="525">
            
                <Canvas Name="canvas" />
            </Window>
            

            第 3 步。在您的 WPF 应用程序中添加以下代码:(确保 test.txt 位于正确的位置):

            using System.Text;
            using System.IO;
            using System.Windows;
            using System.Windows.Markup;
            
            namespace TestCodeVisualizer
            {
                /// <summary>
                /// Interaction logic for MainWindow.xaml
                /// </summary>
                public partial class MainWindow : Window
                {
                    public MainWindow()
                    {
                        InitializeComponent();
            
                        string testText = File.ReadAllText("Test.txt");
                        FrameworkElement fe = GenerateHighlightedTextBlock(testText, "Console.WriteLine");
                        this.canvas.Children.Add(fe);
                    }
            
            
                    private FrameworkElement GenerateHighlightedTextBlock(string code, string term)
                    {
                        CodeHighlighter ch = new CodeHighlighter();
                        string uc = "<UserControl xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>[CONTENT]</UserControl>";
            
                        string content = "<TextBlock>" + ch.ApplyHighlights(code, term) + "</TextBlock>";
                        uc = uc.Replace("[CONTENT]", content);
            
                        FrameworkElement fe = XamlReader.Load(new System.IO.MemoryStream(Encoding.UTF8.GetBytes(uc))) as FrameworkElement;
                        return fe;
                    }
            
                }
            }
            

            【讨论】:

              【解决方案8】:

              最后写了以下代码

              目前bug很少,但问题解决了

              if (Main.IsFullTextSearch)
              {
                  for (int i = 0; i < runs.Count; i++)
                  {
                      if (runs[i] is Run)
                      {
                          Run originalRun = (Run)runs[i];
              
                          if (Main.SearchCondition != null && originalRun.Text.ToLower()
                              .Contains(Main.SearchCondition.ToLower()))
                          {
                              int pos = originalRun.Text.ToLower()
                                        .IndexOf(Main.SearchCondition.ToLower());
              
                              if (pos > 0)
                              {
                                  Run preRun = CloneRun(originalRun);
                                  Run postRun = CloneRun(originalRun);
              
                                  preRun.Text = originalRun.Text.Substring(0, pos);
                                  postRun.Text = originalRun.Text
                                      .Substring(pos + Main.SearchCondition.Length);
              
                                  runs.Insert(i - 1 < 0 ? 0 : i - 1, preRun);
                                  runs.Insert(i + 1, new Run(" "));
                                  runs.Insert(i + 2, postRun);
              
                                  originalRun.Text = originalRun.Text
                                      .Substring(pos, Main.SearchCondition.Length);
              
                                  SolidColorBrush brush = new SolidColorBrush(Colors.Yellow);
                                  originalRun.Background = brush;
              
                                  i += 3;
                              }
                          }
                      }
                  }
              }
              

              【讨论】:

                【解决方案9】:

                如果您正在为您的 ListViewBase 处理 ContainerContentChanging,您可以采取以下方法:TextBlock highlighting for WinRT/ContainerContentChanging

                请注意,此代码适用于 Windows RT。 WPF 语法会略有不同。另请注意,如果您使用绑定来填充 TextBlock.Text 属性,我的方法生成的文本将被覆盖。我使用 ContainerContentChanging 来填充目标字段,因为与普通绑定相比,性能和内存使用量有了根本性的提高和改进。我只使用绑定来管理源数据,而不是数据视图。

                【讨论】:

                  【解决方案10】:

                  以下突出显示搜索方法采用您的 TextBlock搜索字词,然后返回带有该字词或包含该字词的字词的块以紫色突出显示。

                      private TextBlock HighlightSearch(TextBlock textBlock, string searchTerm)
                      {
                          string[] words = textBlock.Text.Split(' ');
                  
                          textBlock.Text = string.Empty; 
                          
                          foreach (string word in words)
                          {
                              if (!string.IsNullOrEmpty(searchTerm) &&
                                  word.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) >= 0)
                              {
                                  textBlock.Inlines.Add(new Run($"{word} ") { Foreground = Brushes.Purple, FontWeight = FontWeights.DemiBold });
                              }
                              else
                              {
                                  textBlock.Inlines.Add($"{word} ");
                              }
                          }
                  
                          return textBlock; 
                      }
                  

                  `

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2021-02-13
                    • 2017-11-13
                    • 2018-09-22
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多