AnnaBauer21
New member
- Joined
- Nov 29, 2024
- Messages
- 3
- Programming Experience
- 3-5
Hello,
I'm facing a problem and can't get any further. I hope you can help me.
Short explanation:
I have a DataGrid (VirtualizingPanel.IsVirtualizing="true") and a search bar. When I type something into the search bar, the list is filtered accordingly and the search text is highlighted using an AttachedProperty.
The highlighting is done by splitting the TextBlock text into several inlines. A corresponding background color is then set in the inlines that contain the search text.
--> Works fine
Problem:
As soon as I scroll down and up again, the elements that have left the visible area suddenly contain the same text as the 2nd element. The same thing happens at the end of the list. The strange thing is, when I scroll down and up again, some of the text is correct again.
I checked the Grid's LoadingRow event and saw that the Row that comes into the visible area contains the correct data in the DataContext, but the texts in the TextBlocks have not updated. My thought was that perhaps the binding was broken by manipulating the inlines, but that doesn't seem to be the problem.
If EnableRowVirtualization is set to false, it works, but unfortunately virtualization is absolutely necessary because the list can basically have n entries, the current estimate is up to 5000.
I hope you can help me, below is the code for my test project.
I'm facing a problem and can't get any further. I hope you can help me.
Short explanation:
I have a DataGrid (VirtualizingPanel.IsVirtualizing="true") and a search bar. When I type something into the search bar, the list is filtered accordingly and the search text is highlighted using an AttachedProperty.
The highlighting is done by splitting the TextBlock text into several inlines. A corresponding background color is then set in the inlines that contain the search text.
--> Works fine
Problem:
As soon as I scroll down and up again, the elements that have left the visible area suddenly contain the same text as the 2nd element. The same thing happens at the end of the list. The strange thing is, when I scroll down and up again, some of the text is correct again.
I checked the Grid's LoadingRow event and saw that the Row that comes into the visible area contains the correct data in the DataContext, but the texts in the TextBlocks have not updated. My thought was that perhaps the binding was broken by manipulating the inlines, but that doesn't seem to be the problem.
If EnableRowVirtualization is set to false, it works, but unfortunately virtualization is absolutely necessary because the list can basically have n entries, the current estimate is up to 5000.
I hope you can help me, below is the code for my test project.
Formular.xaml:
<DataGrid
Grid.Row="0"
local:Highlighter.Filter="{Binding Filter, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="true"
ColumnWidth="100"
ItemsSource="{Binding Path=DisplayedItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
RowHeight="30"
SelectionMode="Single" />
<WrapPanel Grid.Row="1">
<Label Content="Filter: " />
<TextBox Width="100" Text="{Binding Path=Filter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</WrapPanel>
Formular.xaml.cs:
public partial class Formular : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = (sender, args) => { };
public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public ICollectionView DisplayedItems { get; set; }
private string filter;
public string Filter
{
get => this.filter;
set
{
this.filter = value;
this.DisplayedItems.Refresh();
this.RaisePropertyChanged();
}
}
public Formular()
{
InitializeComponent();
this.DataContext = this;
var listItems = new ObservableCollection<MyListItem>()
{
new MyListItem("Alpha", "Mission1"),
new MyListItem("Beta1", "Mission1"),
new MyListItem("Beta1", "Mission2"),
new MyListItem("Beta1", "Mission3"),
new MyListItem("Beta1", "Mission4"),
new MyListItem("Beta1", "Mission5"),
new MyListItem("Beta1", "Mission6"),
new MyListItem("Beta1", "Mission7"),
new MyListItem("Beta1", "Mission8"),
new MyListItem("Beta1", "Mission9"),
new MyListItem("Beta2", "Mission2"),
};
this.DisplayedItems = CollectionViewSource.GetDefaultView(listItems);
this.DisplayedItems.Filter = this.FilterCallback;
}
public bool FilterCallback(object obj)
{
var item = (MyListItem) obj;
return string.IsNullOrEmpty(this.Filter)
|| item.Name.ToUpper().Contains(Filter.ToUpper())
|| item.MissionName.ToUpper().Contains(Filter.ToUpper());
}
}
Highlighter.cs:
public static class Highlighter
{
private static string filter;
static Highlighter(){}
#region Filter
public static readonly DependencyProperty FilterProperty =
DependencyProperty.RegisterAttached("Filter", typeof(string), typeof(Highlighter), new PropertyMetadata("", PropertyChangedCallback));
public static void SetFilter(DependencyObject obj, string value)
{
obj.SetValue(FilterProperty, value);
}
public static string GetFilter(DependencyObject obj)
{
return (string)obj?.GetValue(FilterProperty);
}
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => DoAction(d)));
}
#endregion
private static void DoAction(DependencyObject d)
{
filter = GetFilter(d);
if (filter == null)
{
return;
}
var grid = (DataGrid)d;
grid.LoadingRow += GridOnLoadingRow;
// Get DataGridRows
var gridRows = grid.GetDescendants<DataGridRow>().ToList();
foreach (var row in gridRows)
{
HighlightRow(row);
}
}
private static void HighlightRow(DataGridRow row)
{
// Get TextBlocks
var txtBlocks = row.GetDescendants<TextBlock>().ToList();
if (!txtBlocks.Any())
{
return;
}
foreach (var txtBlock in txtBlocks)
{
HighlightTextBlock(txtBlock);
}
}
private static void HighlightTextBlock(TextBlock txtBlock)
{
var text = txtBlock.Text;
if (string.IsNullOrEmpty(text))
{
return;
}
// Check whether the text contains the filter text
var index = text.IndexOf(filter, StringComparison.CurrentCultureIgnoreCase);
if (index < 0)
{
// Filter text not found
return;
}
// Generate Inlines with highlighting information
var inlines = new List<Inline>();
while (true)
{
// Text from beginning to filter text
inlines.Add(new Run(text.Substring(0, index)));
// Text that corresponds to the filter text
inlines.Add(new Run(text.Substring(index, filter.Length))
{
Background = Brushes.Yellow
});
// Text from filter text to ending
text = text.Substring(index + filter.Length);
// Check whether the remaining text also contains the filter text
index = text.IndexOf(filter, StringComparison.CurrentCultureIgnoreCase);
if (index < 0)
{
// If not, add remaining text and exit loop
inlines.Add(new Run(text));
break;
}
}
// Replace Inlines
txtBlock.Inlines.Clear();
txtBlock.Inlines.AddRange(inlines);
}
private static void GridOnLoadingRow(object sender, DataGridRowEventArgs e)
{
var dataContext = (MyListItem) e.Row.DataContext;
var newData = $"{dataContext.Name}_{dataContext.MissionName}";
var oldData = string.Join("_", e.Row.GetDescendants<TextBlock>().Select(t => t.Text).ToList());
}
}
MyListItem.cs:
public class MyListItem : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = (sender, args) => { };
public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public string name;
public string Name
{
get => name;
set
{
this.name = value;
this.RaisePropertyChanged();
}
}
public string missionName;
public string MissionName
{
get => missionName;
set
{
this.missionName = value;
this.RaisePropertyChanged();
}
}
public MyListItem(string name, string missionName)
{
this.Name = name;
this.MissionName = missionName;
}
}
Last edited: