none
gesuchten Text Highlighten in ListView RRS feed

  • Frage

  • Hallo NG,

    ich suche nach einer Möglichkeit einen gesuchten Text in einer ListView zu Highlighten/Markieren.

    Ich habe eine ListView->ListviewItemTemplate->DataTemplate->Stackpanel->Textbox

    Die Textbox ist gebunden über den ItemSource.

    Jetzt habe ich eine Suche mit Linq welche mir nur noch die Items anzeigt welche auch gesucht werden. Das klappt. Aber wie kann ich den gesuchten Text markieren? Gibt es ein Event oder ähnliches?

    Danke euch

    Oliver

    Freitag, 19. Juni 2015 09:24

Antworten

  • Hallo oliverw,
    das ändert nicht allzuviel. Es gibt eine Änderung beim Binden der Text Property:

    <TextBlock Text="{Binding ElementName=textbox1, Path=Text, NotifyOnTargetUpdated=True}" Grid.Row="1" TargetUpdated="TextBlock_TargetUpdated" />

    Du musst noch aktivieren, dass die Events beim Updaten des Ziels gefeuert werden. Das geschieht durch "NotifyOnTargetUpdated=True". Danach kannst du das Event "TargetUpdated" behandeln".

    Ich bin mir jetzt nicht ganz sicher, ob du MVVM verwendest. Falls ja, muss das komplett anders aufgebaut werden. Dann läuft alles in den ViewModels ab.


    Viele Grüße Holger M. Rößler


    Freitag, 19. Juni 2015 21:28
  • Hallo Oliver,

    nach Holgers Hinweis auf NotifyOnTargetUpdated und TargetUpdated ist die Markierung noch relativ einfach realisierbar. Mit diesen beiden Properties lassen sich die grafischen Elemente nach ihrer Bestückung noch nachbearbeiten:

    public class Person : INotifyPropertyChanged
    {
    	public event PropertyChangedEventHandler	PropertyChanged;
    
    	protected string	_Vorname;
    	protected string	_Nachname;
    
    	public virtual string Vorname
    	{
    		get { return _Vorname; }
    		set
    		{
    			if (value != _Vorname)
    			{
    				_Vorname = value;
    				NotifyPropertyChanged();
    			}
    		}
    	}
    
    	public virtual string Nachname
    	{
    		get { return _Nachname; }
    		set
    		{
    			if (value != _Nachname)
    			{
    				_Nachname = value;
    				NotifyPropertyChanged();
    			}
    		}
    	}
    
    	public void NotifyAllPropertiesChanged()
    	{
    		NotifyPropertyChanged("Vorname");
    		NotifyPropertyChanged("Nachname");
    	}
    
    	protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    	{
    		if (PropertyChanged != null)
    			PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    	}
    }
    
    <DataTemplate>
    	<StackPanel Orientation="Horizontal">
    		<!--Mit Hilfe von NotifyOnTargetUpdated=True und dem Event TargetUpdated läßt
    			sich für ein XAML-Element mit Binding nach Bestückung des Elements mit
    			Inhalt aus dessen Binding eine Nachbearbeitung im C# anstoßen.-->
    		<TextBlock Margin="4" Text="{Binding Vorname, NotifyOnTargetUpdated=True,
    						Mode=TwoWay}"
    				   Name="textBlockVorname"
    				   TargetUpdated="textBlockVorname_TargetUpdated"
    				   HorizontalAlignment="Left"/>
    
    		<!--Damit das TargetUpdated-Event nach jedem NotifyPropertyChanged-Event
    			ausgelöst wird (auch dann, wenn sich der Textinhalt in der Liste nicht
    			geändert hat), müssen im Binding NotifyOnTargetUpdated=True und Mode=TwoWay
    			gesetzt sein.-->
    		<TextBlock Margin="4" Text="{Binding Nachname, NotifyOnTargetUpdated=True,
    						Mode=TwoWay}"
    				   Name="textBlockNachname"
    				   TargetUpdated="textBlockNachname_TargetUpdated"
    				   HorizontalAlignment="Left"/>
    	</StackPanel>
    </DataTemplate>
    
    ObservableCollection<Person>	Personen;
    string				MarkedText = null;
    
    public MainWindow()
    {
    	Personen = new ObservableCollection<Person>();
    	Personen.Add(new Person() { Vorname = "Heiko", Nachname = "Bonober" });
    	Personen.Add(new Person() { Vorname = "Hans", Nachname = "Erker" });
    	Personen.Add(new Person() { Vorname = "Holger", Nachname = "Folgt" });
    	Personen.Add(new Person() { Vorname = "Tom", Nachname = "Beringstraße" });
    	Personen.Add(new Person() { Vorname = "Thomas", Nachname = "Frau" });
    	Personen.Add(new Person() { Vorname = "Anika", Nachname = "Mossad" });
    	Personen.Add(new Person() { Vorname = "Angelika", Nachname = "Werkelt" });
    
    	DataContext = Personen;
    
    	InitializeComponent();
    }
    
    private void buttonMarkText_Click(object sender, RoutedEventArgs e)
    {
    	string	text = textBoxMarker.Text;
    
    	if (MarkedText == null && String.IsNullOrEmpty(text))
    		return;
    
    	// Ist MarkedText != null und 'text' leer, muß die ltzte Markierung zurückgenommen werden.
    
    	MarkedText = text.ToLower();
    
    	// Beim Füllen der ListBox werden für die einzelnen TextBlock-Elemente in der ListBox keine
    	// TagetUpdated-Events ausgelöst, sondern nur für die ListBox selbst. Deshalb muß hier vor
    	// dem Markieren zu allen in der Liste angezeigten Einträgen, deren Inhalt eine Markierung
    	// erhalten soll, ein NotifyPropertyChanged-Event ausgelöst werden. Durch das Auslösen der
    	// NotifyPropertyChanged-Events werden auch die TargetUpdated-Events ausgelöst. Damit diese
    	// jedoch auch nach jedem weiteren NotifyPropertyChanged-Event für einen TextBlock ausgelöst
    	// werden, muß das Binding den Mode 'TwoWay' besitzen.
    	foreach (Person person in Personen)
    		person.NotifyAllPropertiesChanged();
    
    	if (String.IsNullOrEmpty(text))
    		MarkedText = null;
    	textBoxMarker.Focus();
    }
    
    private void textBlockVorname_TargetUpdated(object sender, DataTransferEventArgs e)
    {
    	MarkText(sender as TextBlock);
    	e.Handled = true;
    }
    
    private void textBlockNachname_TargetUpdated(object sender, DataTransferEventArgs e)
    {
    	MarkText(sender as TextBlock);
    	e.Handled = true;
    }
    
    private void MarkText(TextBlock textBlock)
    {
    	if (textBlock == null)
    		return;
    
    	string	text = textBlock.Text,
    			lower = text.ToLower();
    
    	if (String.IsNullOrWhiteSpace(text))
    		return;
    
    	// Wurde bereits eine Markierung vorgenommen, wird diese hier wieder entfernt
    	// und damit auch der gesamte Textinhalt.
    	textBlock.Inlines.Clear();
    
    	int	pos = String.IsNullOrEmpty(MarkedText) ? -1 : lower.IndexOf(MarkedText);
    
    	if (pos < 0)
    	{
    		// Da alle Inline's entfernt wurden, ist in der TextBlock kein Text mehr
    		// vorhanden. Deshalb muß er hier wieder neu gesetzt werden.
    		textBlock.Inlines.Add(new Run(text));
    		return;
    	}
    
    	int	lastPos = 0;
    	Run	run;
    
    	while (pos >= 0)
    	{
    		if (pos > lastPos)
    			textBlock.Inlines.Add(new Run(text.Substring(lastPos, pos - lastPos)));
    
    		run = new Run(text.Substring(pos, MarkedText.Length));
    
    		// Setzen der Hintergundfarbe.
    		run.Background = Brushes.Yellow;
    		
    		// Setzen der Vordergrundfarbe
    		run.Foreground = Brushes.Red;
    		
    		// Fettschrift
    		run.FontWeight = FontWeights.Bold;
    
    		textBlock.Inlines.Add(run);
    		lastPos = pos + MarkedText.Length;
    		pos = lower.IndexOf(MarkedText, lastPos);
    	}
    	if (lastPos < text.Length)
    		textBlock.Inlines.Add(new Run(text.Substring(lastPos, text.Length - lastPos)));
    }
    

    Viele Grüße
    Heiko

    Sonntag, 21. Juni 2015 18:12

Alle Antworten

  • Hallo Oliver,

    eine TextBox nimmt leider nur Strings auf. Wenn du in einem Listeintrag Text hast, der teilweise farblich markiert, teilweise normal ist, benötigst du vermutlich eine RichEditBox für editierbare und formatierbare Listeinträge oder einen TextBlock für nicht-editierbare Einträge, den du ebenfalls unterschiedlich formatieren kannst.

    Dem TextBlock würde man dann mehrere Inline- (Run-)Elemente zufügen, die jeweils z.B. unterschiedliche Vordergrundfarben oder Fettschrift erhalten. Ich frag mich allerdings gerade, ob man auch die Hintergrundfarbe für Textabschnitte setzen kann.

    Gruß
    Heiko


    • Bearbeitet Heiko65456465 Freitag, 19. Juni 2015 10:22 TextBlock & TextBox verwechselt
    Freitag, 19. Juni 2015 09:44
  • Hallo,
    so sollte es gehen:

    private void textbox1_KeyDown(object sender, KeyEventArgs e) {
       this.listbox1.UnselectAll();
    
       var selectedItems =  this.listbox1.Items.Cast<ListBoxItem>()
       .Where(t => t.Content.ToString().Contains((sender as TextBox).Text));
    				
       selectedItems.ToList().ForEach(t => t.IsSelected = true);
    }

    Kann man bestimmt noch etwas optimieren. Aber funktionell tuts.

    Ich hoffe das bringt dich weiter...


    Viele Grüße Holger M. Rößler

    Freitag, 19. Juni 2015 10:27
  • oh sorry vertan ich habe nen TextBlock...keine textbox...sorry

    <ListView x:Name="listView" Background="#FFCCD0D6" Margin="0,240,0,60" IsItemClickEnabled="True" HorizontalAlignment="Right" Width="1095" ScrollViewer.HorizontalScrollBarVisibility="Auto" Tapped="listView_Tapped" DoubleTapped="listView_DoubleTapped" SelectionChanged="listView_SelectionChanged">

    <ListView.ItemTemplate>

    <DataTemplate>

    <StackPanel Orientation="Horizontal">

    <TextBlock Margin="4" Text="{Binding Vorname}" HorizontalAlignment="Left"/>

    <TextBlock Margin="4" Text="{Binding Nachname}" HorizontalAlignment="Left"/>

    Freitag, 19. Juni 2015 17:13
  • Hallo Oliver,

    wenn du jeweils den kompletten Vornamen oder Nachnamen markieren möchtest, könnte man in das Datenobjekt (bzw. ViewModel) für die einzelnen ListView-Einträge ein weiteres Property für den Foreground aufnehmen, in dem du je nachdem, ob eine Filterung erfolgte, einen anderen Brush zurückgibst.

    Im DataTemplate setzt du die Farbe dann über

    <TextBlock Foreground="{Binding VornameColor}" ... />

    <TextBlock Foreground="{Binding NachnameColor}" ... />

    Eine andere Möglichkeit wäre eventuell, wenn du über die 'selectedItems' (siehe Holgers Vorschlag) an die TextBlock's herankommst und je nachdem, ob Vor- oder Nachname gesucht wurde, den jeweiligen TextBlock farblich änderst.

    Wenn du nur Teile eines Namens markieren können möchtest, müßten nach meiner Auffassung Run-Elemente verwendet werden, die dann dynamisch als InlineCollection jedes TextBlock's für Vor- und Nachnamen hinzugefügt werden. Das würde aufwendiger. Vielleicht lohnte es sich, dafür ein eigenes UserControl zu bauen.

    Gruß
    Heiko

    Freitag, 19. Juni 2015 17:56
  • Hallo oliverw,
    das ändert nicht allzuviel. Es gibt eine Änderung beim Binden der Text Property:

    <TextBlock Text="{Binding ElementName=textbox1, Path=Text, NotifyOnTargetUpdated=True}" Grid.Row="1" TargetUpdated="TextBlock_TargetUpdated" />

    Du musst noch aktivieren, dass die Events beim Updaten des Ziels gefeuert werden. Das geschieht durch "NotifyOnTargetUpdated=True". Danach kannst du das Event "TargetUpdated" behandeln".

    Ich bin mir jetzt nicht ganz sicher, ob du MVVM verwendest. Falls ja, muss das komplett anders aufgebaut werden. Dann läuft alles in den ViewModels ab.


    Viele Grüße Holger M. Rößler


    Freitag, 19. Juni 2015 21:28
  • Hallo Oliver,

    nach Holgers Hinweis auf NotifyOnTargetUpdated und TargetUpdated ist die Markierung noch relativ einfach realisierbar. Mit diesen beiden Properties lassen sich die grafischen Elemente nach ihrer Bestückung noch nachbearbeiten:

    public class Person : INotifyPropertyChanged
    {
    	public event PropertyChangedEventHandler	PropertyChanged;
    
    	protected string	_Vorname;
    	protected string	_Nachname;
    
    	public virtual string Vorname
    	{
    		get { return _Vorname; }
    		set
    		{
    			if (value != _Vorname)
    			{
    				_Vorname = value;
    				NotifyPropertyChanged();
    			}
    		}
    	}
    
    	public virtual string Nachname
    	{
    		get { return _Nachname; }
    		set
    		{
    			if (value != _Nachname)
    			{
    				_Nachname = value;
    				NotifyPropertyChanged();
    			}
    		}
    	}
    
    	public void NotifyAllPropertiesChanged()
    	{
    		NotifyPropertyChanged("Vorname");
    		NotifyPropertyChanged("Nachname");
    	}
    
    	protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    	{
    		if (PropertyChanged != null)
    			PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    	}
    }
    
    <DataTemplate>
    	<StackPanel Orientation="Horizontal">
    		<!--Mit Hilfe von NotifyOnTargetUpdated=True und dem Event TargetUpdated läßt
    			sich für ein XAML-Element mit Binding nach Bestückung des Elements mit
    			Inhalt aus dessen Binding eine Nachbearbeitung im C# anstoßen.-->
    		<TextBlock Margin="4" Text="{Binding Vorname, NotifyOnTargetUpdated=True,
    						Mode=TwoWay}"
    				   Name="textBlockVorname"
    				   TargetUpdated="textBlockVorname_TargetUpdated"
    				   HorizontalAlignment="Left"/>
    
    		<!--Damit das TargetUpdated-Event nach jedem NotifyPropertyChanged-Event
    			ausgelöst wird (auch dann, wenn sich der Textinhalt in der Liste nicht
    			geändert hat), müssen im Binding NotifyOnTargetUpdated=True und Mode=TwoWay
    			gesetzt sein.-->
    		<TextBlock Margin="4" Text="{Binding Nachname, NotifyOnTargetUpdated=True,
    						Mode=TwoWay}"
    				   Name="textBlockNachname"
    				   TargetUpdated="textBlockNachname_TargetUpdated"
    				   HorizontalAlignment="Left"/>
    	</StackPanel>
    </DataTemplate>
    
    ObservableCollection<Person>	Personen;
    string				MarkedText = null;
    
    public MainWindow()
    {
    	Personen = new ObservableCollection<Person>();
    	Personen.Add(new Person() { Vorname = "Heiko", Nachname = "Bonober" });
    	Personen.Add(new Person() { Vorname = "Hans", Nachname = "Erker" });
    	Personen.Add(new Person() { Vorname = "Holger", Nachname = "Folgt" });
    	Personen.Add(new Person() { Vorname = "Tom", Nachname = "Beringstraße" });
    	Personen.Add(new Person() { Vorname = "Thomas", Nachname = "Frau" });
    	Personen.Add(new Person() { Vorname = "Anika", Nachname = "Mossad" });
    	Personen.Add(new Person() { Vorname = "Angelika", Nachname = "Werkelt" });
    
    	DataContext = Personen;
    
    	InitializeComponent();
    }
    
    private void buttonMarkText_Click(object sender, RoutedEventArgs e)
    {
    	string	text = textBoxMarker.Text;
    
    	if (MarkedText == null && String.IsNullOrEmpty(text))
    		return;
    
    	// Ist MarkedText != null und 'text' leer, muß die ltzte Markierung zurückgenommen werden.
    
    	MarkedText = text.ToLower();
    
    	// Beim Füllen der ListBox werden für die einzelnen TextBlock-Elemente in der ListBox keine
    	// TagetUpdated-Events ausgelöst, sondern nur für die ListBox selbst. Deshalb muß hier vor
    	// dem Markieren zu allen in der Liste angezeigten Einträgen, deren Inhalt eine Markierung
    	// erhalten soll, ein NotifyPropertyChanged-Event ausgelöst werden. Durch das Auslösen der
    	// NotifyPropertyChanged-Events werden auch die TargetUpdated-Events ausgelöst. Damit diese
    	// jedoch auch nach jedem weiteren NotifyPropertyChanged-Event für einen TextBlock ausgelöst
    	// werden, muß das Binding den Mode 'TwoWay' besitzen.
    	foreach (Person person in Personen)
    		person.NotifyAllPropertiesChanged();
    
    	if (String.IsNullOrEmpty(text))
    		MarkedText = null;
    	textBoxMarker.Focus();
    }
    
    private void textBlockVorname_TargetUpdated(object sender, DataTransferEventArgs e)
    {
    	MarkText(sender as TextBlock);
    	e.Handled = true;
    }
    
    private void textBlockNachname_TargetUpdated(object sender, DataTransferEventArgs e)
    {
    	MarkText(sender as TextBlock);
    	e.Handled = true;
    }
    
    private void MarkText(TextBlock textBlock)
    {
    	if (textBlock == null)
    		return;
    
    	string	text = textBlock.Text,
    			lower = text.ToLower();
    
    	if (String.IsNullOrWhiteSpace(text))
    		return;
    
    	// Wurde bereits eine Markierung vorgenommen, wird diese hier wieder entfernt
    	// und damit auch der gesamte Textinhalt.
    	textBlock.Inlines.Clear();
    
    	int	pos = String.IsNullOrEmpty(MarkedText) ? -1 : lower.IndexOf(MarkedText);
    
    	if (pos < 0)
    	{
    		// Da alle Inline's entfernt wurden, ist in der TextBlock kein Text mehr
    		// vorhanden. Deshalb muß er hier wieder neu gesetzt werden.
    		textBlock.Inlines.Add(new Run(text));
    		return;
    	}
    
    	int	lastPos = 0;
    	Run	run;
    
    	while (pos >= 0)
    	{
    		if (pos > lastPos)
    			textBlock.Inlines.Add(new Run(text.Substring(lastPos, pos - lastPos)));
    
    		run = new Run(text.Substring(pos, MarkedText.Length));
    
    		// Setzen der Hintergundfarbe.
    		run.Background = Brushes.Yellow;
    		
    		// Setzen der Vordergrundfarbe
    		run.Foreground = Brushes.Red;
    		
    		// Fettschrift
    		run.FontWeight = FontWeights.Bold;
    
    		textBlock.Inlines.Add(run);
    		lastPos = pos + MarkedText.Length;
    		pos = lower.IndexOf(MarkedText, lastPos);
    	}
    	if (lastPos < text.Length)
    		textBlock.Inlines.Add(new Run(text.Substring(lastPos, text.Length - lastPos)));
    }
    

    Viele Grüße
    Heiko

    Sonntag, 21. Juni 2015 18:12