הצגת תוצאות חיפוש מסומנות
-
כבר יומיים שאני מנסה לפתור את הבעיה הזאת ואינני מצליח :oops:
יש לי תכנה שמציגה ספרים, ומאפשרת למשתמש חיפוש בתוכם, העניין הוא שאני רוצה שתוצאות החיפוש יוצגו כמה שורות מהקטע הרלוונטי, כאשר מילת החיפוש תהיה מודגשת, כמו שעושים בפרויקט השו"ת ודומיו.
עד היום אפשרתי חיפוש של מילה אחת, ואת תוצאות החיפוש (הקטע שבו מופיעה המילה) הצגתי ברשימה (listview) של טקסטבלוקים, שבדטה טמפלייט הגדרתי שלשה חלקים (מסוג Run) שעשיתי להם בינדינג לאובייקט שמכיל את התוצאות, והחלק האמצעי היה מודגש, כך שמילת החיפוש הייתה מודגשת, בעוד הקטע לפני ואחרי - לא.<Run Text="{Binding BeforeValue}"></Run> <Run FontWeight="Bold" Text="{Binding Value}"></Run> <Run Text="{Binding AfterValue}"/>
זה עבד טוב.
העניין הוא שהיום אני מאפשר למשתמש לחפש מספר מילים בלתי מוגבל, כך שהדרך הקודמת אינה מספקת.
חשבתי ליצור זאת בקוד.
הענין הוא שיש לי מחלקה נפרדת שמטפלת בחיפוש, ומחלקה שמשמשת לתצוגה.
ניסיתי בתחילה ליצור flowDocument ולעשות אליו Binding בדטה טמפלייט אך זה לא עבד לי, כיון שהוא כתב שיש לו בעיה של טריידים שהוא לא יכול להציג אותו כיון שהוא לא על הטרייד הראשי.
מה שעשיתי לבסוף זה שבקוד יצרתי סטרינג כמו בHTML (דהיינו שלפני המילה המודגשת יש <Bold> וגם אחריה. ואח"כ בקוד של התצוגה אני מפרק את הסטרינג ובונה ממנו אובייקט טקסט בלוק (עם כל ההדגשות הנצרכות) כך:TextBlock tb = new TextBlock(); tb.TextWrapping = TextWrapping.Wrap; tb.Margin = new Thickness(10); bool isBold = false; string[] text = Text.TextToWords(result.TextResult); for (int i = 0; i < text.Length; i++) { if (text[i] == "<Bold>" ) { isBold = true; } else if (text[i] == "</Bold>") { isBold = false; } switch (isBold) { case true: tb.Inlines.Add(new Run(text[i]) { FontWeight = FontWeights.Bold,Background=Brushes.Yellow }); break; case false: tb.Inlines.Add(new Run(text[i])); break; } } return tb;
העניין הוא שאני בונה טקסטבלוקים כאלה כמניין תוצאות החיפוש, ואח"כ הם אמורים להיות מוצגים בתוך ליסטוייוו שאותו הגדרתי בדטה טמפלייט. הבעיה היא שאינני יודע כיצד אני יכול להוסיף אותם מהקוד לתוך הטמפלייט?! או שאתה הכל אני אצטרך להגדיר מתוך הקוד??
או שלמישהו יש רעיון או דרך יותר פשוטה לעשות זאת?!?! :shock:
תודה רבה רבה!
אברהםפורסם במקור בפורום CODE613 ב20/06/2016 22:52 (+03:00)
-
לבעיית חלוקת הטקסט אני חושב להשתמש עם RegEX.
בעיית הבינדינג נכונה, אי אפשר בגלל שהמאפיין Inlines של הטקסטבלוק הוא לא ניתן לאיגוד. למעט אם עושים מולטי בינדינג ומעבירים כפרמטר גם את התיבה בעצמה, מה שלא נחמד לדעתי.
פה http://stackoverflow.com/a/1960148/1271037 יש סיכום האפשרויות, ובפוסט אחריו אחד ביצע את אחת האפשרויות: פקד מותאם אישית. אני הלכתי על זה, ושים לב שאם כבר עושים פקד מותאם אישית יכלנו לחסוך את הקונורטר, אבל: זה הפרדת קוד תועלתית, וגם זה חוסך משאבים כי הקונוורטר הוא נר למאה.
אגב פה יש אחד שעשה קונוורטר לכאילו html - כמו שעשית - לחסוך קוד מלוכלך: http://stackoverflow.com/a/8309907/1271037.הנה מה שעשיתי, קודם מחלקת טקסטבלוק ממש העתקה מהבחור חוץ משינויים נדרשים לדעתי:
public class BindableTextBlock : TextBlock
{
public IEnumerable<Inline> InlineList
{
get { return (IEnumerable<Inline>)GetValue(InlineListProperty); }
set { SetValue(InlineListProperty, value); }
}public static readonly DependencyProperty InlineListProperty = DependencyProperty.Register("InlineList", typeof(IEnumerable<Inline>), typeof(BindableTextBlock), new UIPropertyMetadata(null, OnPropertyChanged)); private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { BindableTextBlock textBlock = sender as BindableTextBlock; foreach (var item in e.NewValue as IEnumerable<Inline>) textBlock.Inlines.Add(item); } }
קונוורטר, עם הלוגיקה לחלק את הטקסט לחתיכות:
class ResultConverter : IValueConverter { Regex reg; public string[] wordToHighlighted { set { reg = new Regex("\\b(" + string.Join("|", value) + ")\\b", RegexOptions.Compiled); } } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => FormatPiece(GetStringPieces(value.ToString())); public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => null; IEnumerable<Inline> FormatPiece(IEnumerable<Tuple<string, bool>> pieces) { foreach (var item in pieces) if (item.Item2) yield return new Run(item.Item1) { FontWeight = FontWeights.Bold }; else yield return new Run(item.Item1); } IEnumerable<Tuple<string, bool>> GetStringPieces(string text) { var results = reg.Matches(text); int lastPos = 0; Match current = results[0]; if (current.Index > 0) yield return Tuple.Create(text.Substring(0, current.Index), false); yield return Tuple.Create(text.Substring(current.Index, current.Length), true); lastPos = current.Index + current.Length; for (int i = 1; i < results.Count; i++) { current = results[i]; yield return Tuple.Create(text.Substring(lastPos, current.Index - lastPos), false); yield return Tuple.Create(text.Substring(current.Index, current.Length), true); lastPos = current.Index + current.Length; } if (lastPos < text.Length) yield return Tuple.Create(text.Substring(lastPos), false); ; } }
דוגמה לזאמל:
<ListBox x:Name="list"> <ListBox.Resources> <local:ResultConverter x:Key="ResultConverter" /> </ListBox.Resources> <ListBox.ItemTemplate> <DataTemplate> <local:BindableTextBlock InlineList="{Binding Converter={StaticResource ResultConverter}}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
קוד:
(list.Resources["ResultConverter"] as ResultConverter).wordToHighlighted = new[] {"אני" }; list.ItemsSource = Enumerable.Repeat( "אני אוהב לאכול דגים" , 15);
פורסם במקור בפורום CODE613 ב21/06/2016 10:37 (+03:00)
-
קודם כל תודה רבה רבה על ההשקעה העצומה בכתיבת תשובה מפורטת כל כך!!
עשית לי את כל העבודה :lol:העתקתי את כל המחלקות לפרוייקט שלי, והוספתי את הדוגמא שלך ככתבה וכלשונה (כמובן, רק שיניתי את הרפרנסים לשלי..)
אך הוא זורק לי בזמן ריצה את השגיאה הבאה:
אין אפשרות לבצע המרה של אובייקט מסוג 'BooksRavKookNew.ViewModel.ResultConverter' לסוג 'System.Windows.Data.IValueConverter'.
מדוע ולמה?פורסם במקור בפורום CODE613 ב22/06/2016 21:40 (+03:00)